registry.go 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799
  1. package registry
  2. import (
  3. "bytes"
  4. "crypto/sha256"
  5. _ "crypto/sha512"
  6. "encoding/json"
  7. "errors"
  8. "fmt"
  9. "io"
  10. "io/ioutil"
  11. "net"
  12. "net/http"
  13. "net/http/cookiejar"
  14. "net/url"
  15. "regexp"
  16. "runtime"
  17. "strconv"
  18. "strings"
  19. "time"
  20. "github.com/dotcloud/docker/dockerversion"
  21. "github.com/dotcloud/docker/utils"
  22. )
  23. var (
  24. ErrAlreadyExists = errors.New("Image already exists")
  25. ErrInvalidRepositoryName = errors.New("Invalid repository name (ex: \"registry.domain.tld/myrepos\")")
  26. errLoginRequired = errors.New("Authentication is required.")
  27. )
  28. func pingRegistryEndpoint(endpoint string) (RegistryInfo, error) {
  29. if endpoint == IndexServerAddress() {
  30. // Skip the check, we now this one is valid
  31. // (and we never want to fallback to http in case of error)
  32. return RegistryInfo{Standalone: false}, nil
  33. }
  34. httpDial := func(proto string, addr string) (net.Conn, error) {
  35. // Set the connect timeout to 5 seconds
  36. conn, err := net.DialTimeout(proto, addr, time.Duration(5)*time.Second)
  37. if err != nil {
  38. return nil, err
  39. }
  40. // Set the recv timeout to 10 seconds
  41. conn.SetDeadline(time.Now().Add(time.Duration(10) * time.Second))
  42. return conn, nil
  43. }
  44. httpTransport := &http.Transport{
  45. Dial: httpDial,
  46. Proxy: http.ProxyFromEnvironment,
  47. }
  48. client := &http.Client{Transport: httpTransport}
  49. resp, err := client.Get(endpoint + "_ping")
  50. if err != nil {
  51. return RegistryInfo{Standalone: false}, err
  52. }
  53. defer resp.Body.Close()
  54. jsonString, err := ioutil.ReadAll(resp.Body)
  55. if err != nil {
  56. return RegistryInfo{Standalone: false}, fmt.Errorf("Error while reading the http response: %s", err)
  57. }
  58. // If the header is absent, we assume true for compatibility with earlier
  59. // versions of the registry. default to true
  60. info := RegistryInfo{
  61. Standalone: true,
  62. }
  63. if err := json.Unmarshal(jsonString, &info); err != nil {
  64. utils.Debugf("Error unmarshalling the _ping RegistryInfo: %s", err)
  65. // don't stop here. Just assume sane defaults
  66. }
  67. if hdr := resp.Header.Get("X-Docker-Registry-Version"); hdr != "" {
  68. utils.Debugf("Registry version header: '%s'", hdr)
  69. info.Version = hdr
  70. }
  71. utils.Debugf("RegistryInfo.Version: %q", info.Version)
  72. standalone := resp.Header.Get("X-Docker-Registry-Standalone")
  73. utils.Debugf("Registry standalone header: '%s'", standalone)
  74. // Accepted values are "true" (case-insensitive) and "1".
  75. if strings.EqualFold(standalone, "true") || standalone == "1" {
  76. info.Standalone = true
  77. } else if len(standalone) > 0 {
  78. // there is a header set, and it is not "true" or "1", so assume fails
  79. info.Standalone = false
  80. }
  81. utils.Debugf("RegistryInfo.Standalone: %q", info.Standalone)
  82. return info, nil
  83. }
  84. func validateRepositoryName(repositoryName string) error {
  85. var (
  86. namespace string
  87. name string
  88. )
  89. nameParts := strings.SplitN(repositoryName, "/", 2)
  90. if len(nameParts) < 2 {
  91. namespace = "library"
  92. name = nameParts[0]
  93. } else {
  94. namespace = nameParts[0]
  95. name = nameParts[1]
  96. }
  97. validNamespace := regexp.MustCompile(`^([a-z0-9_]{4,30})$`)
  98. if !validNamespace.MatchString(namespace) {
  99. return fmt.Errorf("Invalid namespace name (%s), only [a-z0-9_] are allowed, size between 4 and 30", namespace)
  100. }
  101. validRepo := regexp.MustCompile(`^([a-z0-9-_.]+)$`)
  102. if !validRepo.MatchString(name) {
  103. return fmt.Errorf("Invalid repository name (%s), only [a-z0-9-_.] are allowed", name)
  104. }
  105. return nil
  106. }
  107. // Resolves a repository name to a hostname + name
  108. func ResolveRepositoryName(reposName string) (string, string, error) {
  109. if strings.Contains(reposName, "://") {
  110. // It cannot contain a scheme!
  111. return "", "", ErrInvalidRepositoryName
  112. }
  113. nameParts := strings.SplitN(reposName, "/", 2)
  114. if len(nameParts) == 1 || (!strings.Contains(nameParts[0], ".") && !strings.Contains(nameParts[0], ":") &&
  115. nameParts[0] != "localhost") {
  116. // This is a Docker Index repos (ex: samalba/hipache or ubuntu)
  117. err := validateRepositoryName(reposName)
  118. return IndexServerAddress(), reposName, err
  119. }
  120. hostname := nameParts[0]
  121. reposName = nameParts[1]
  122. if strings.Contains(hostname, "index.docker.io") {
  123. return "", "", fmt.Errorf("Invalid repository name, try \"%s\" instead", reposName)
  124. }
  125. if err := validateRepositoryName(reposName); err != nil {
  126. return "", "", err
  127. }
  128. return hostname, reposName, nil
  129. }
  130. // this method expands the registry name as used in the prefix of a repo
  131. // to a full url. if it already is a url, there will be no change.
  132. // The registry is pinged to test if it http or https
  133. func ExpandAndVerifyRegistryUrl(hostname string) (string, error) {
  134. if strings.HasPrefix(hostname, "http:") || strings.HasPrefix(hostname, "https:") {
  135. // if there is no slash after https:// (8 characters) then we have no path in the url
  136. if strings.LastIndex(hostname, "/") < 9 {
  137. // there is no path given. Expand with default path
  138. hostname = hostname + "/v1/"
  139. }
  140. if _, err := pingRegistryEndpoint(hostname); err != nil {
  141. return "", errors.New("Invalid Registry endpoint: " + err.Error())
  142. }
  143. return hostname, nil
  144. }
  145. endpoint := fmt.Sprintf("https://%s/v1/", hostname)
  146. if _, err := pingRegistryEndpoint(endpoint); err != nil {
  147. utils.Debugf("Registry %s does not work (%s), falling back to http", endpoint, err)
  148. endpoint = fmt.Sprintf("http://%s/v1/", hostname)
  149. if _, err = pingRegistryEndpoint(endpoint); err != nil {
  150. //TODO: triggering highland build can be done there without "failing"
  151. return "", errors.New("Invalid Registry endpoint: " + err.Error())
  152. }
  153. }
  154. return endpoint, nil
  155. }
  156. func setTokenAuth(req *http.Request, token []string) {
  157. if req.Header.Get("Authorization") == "" { // Don't override
  158. req.Header.Set("Authorization", "Token "+strings.Join(token, ","))
  159. }
  160. }
  161. // Retrieve the history of a given image from the Registry.
  162. // Return a list of the parent's json (requested image included)
  163. func (r *Registry) GetRemoteHistory(imgID, registry string, token []string) ([]string, error) {
  164. req, err := r.reqFactory.NewRequest("GET", registry+"images/"+imgID+"/ancestry", nil)
  165. if err != nil {
  166. return nil, err
  167. }
  168. setTokenAuth(req, token)
  169. res, err := r.client.Do(req)
  170. if err != nil {
  171. return nil, err
  172. }
  173. defer res.Body.Close()
  174. if res.StatusCode != 200 {
  175. if res.StatusCode == 401 {
  176. return nil, errLoginRequired
  177. }
  178. return nil, utils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to fetch remote history for %s", res.StatusCode, imgID), res)
  179. }
  180. jsonString, err := ioutil.ReadAll(res.Body)
  181. if err != nil {
  182. return nil, fmt.Errorf("Error while reading the http response: %s", err)
  183. }
  184. utils.Debugf("Ancestry: %s", jsonString)
  185. history := new([]string)
  186. if err := json.Unmarshal(jsonString, history); err != nil {
  187. return nil, err
  188. }
  189. return *history, nil
  190. }
  191. // Check if an image exists in the Registry
  192. // TODO: This method should return the errors instead of masking them and returning false
  193. func (r *Registry) LookupRemoteImage(imgID, registry string, token []string) bool {
  194. req, err := r.reqFactory.NewRequest("GET", registry+"images/"+imgID+"/json", nil)
  195. if err != nil {
  196. utils.Errorf("Error in LookupRemoteImage %s", err)
  197. return false
  198. }
  199. setTokenAuth(req, token)
  200. res, err := r.client.Do(req)
  201. if err != nil {
  202. utils.Errorf("Error in LookupRemoteImage %s", err)
  203. return false
  204. }
  205. res.Body.Close()
  206. return res.StatusCode == 200
  207. }
  208. // Retrieve an image from the Registry.
  209. func (r *Registry) GetRemoteImageJSON(imgID, registry string, token []string) ([]byte, int, error) {
  210. // Get the JSON
  211. req, err := r.reqFactory.NewRequest("GET", registry+"images/"+imgID+"/json", nil)
  212. if err != nil {
  213. return nil, -1, fmt.Errorf("Failed to download json: %s", err)
  214. }
  215. setTokenAuth(req, token)
  216. res, err := r.client.Do(req)
  217. if err != nil {
  218. return nil, -1, fmt.Errorf("Failed to download json: %s", err)
  219. }
  220. defer res.Body.Close()
  221. if res.StatusCode != 200 {
  222. return nil, -1, utils.NewHTTPRequestError(fmt.Sprintf("HTTP code %d", res.StatusCode), res)
  223. }
  224. // if the size header is not present, then set it to '-1'
  225. imageSize := -1
  226. if hdr := res.Header.Get("X-Docker-Size"); hdr != "" {
  227. imageSize, err = strconv.Atoi(hdr)
  228. if err != nil {
  229. return nil, -1, err
  230. }
  231. }
  232. jsonString, err := ioutil.ReadAll(res.Body)
  233. if err != nil {
  234. return nil, -1, fmt.Errorf("Failed to parse downloaded json: %s (%s)", err, jsonString)
  235. }
  236. return jsonString, imageSize, nil
  237. }
  238. func (r *Registry) GetRemoteImageLayer(imgID, registry string, token []string) (io.ReadCloser, error) {
  239. req, err := r.reqFactory.NewRequest("GET", registry+"images/"+imgID+"/layer", nil)
  240. if err != nil {
  241. return nil, fmt.Errorf("Error while getting from the server: %s\n", err)
  242. }
  243. setTokenAuth(req, token)
  244. res, err := r.client.Do(req)
  245. if err != nil {
  246. return nil, err
  247. }
  248. if res.StatusCode != 200 {
  249. res.Body.Close()
  250. return nil, fmt.Errorf("Server error: Status %d while fetching image layer (%s)",
  251. res.StatusCode, imgID)
  252. }
  253. return res.Body, nil
  254. }
  255. func (r *Registry) GetRemoteTags(registries []string, repository string, token []string) (map[string]string, error) {
  256. if strings.Count(repository, "/") == 0 {
  257. // This will be removed once the Registry supports auto-resolution on
  258. // the "library" namespace
  259. repository = "library/" + repository
  260. }
  261. for _, host := range registries {
  262. endpoint := fmt.Sprintf("%srepositories/%s/tags", host, repository)
  263. req, err := r.reqFactory.NewRequest("GET", endpoint, nil)
  264. if err != nil {
  265. return nil, err
  266. }
  267. setTokenAuth(req, token)
  268. res, err := r.client.Do(req)
  269. if err != nil {
  270. return nil, err
  271. }
  272. utils.Debugf("Got status code %d from %s", res.StatusCode, endpoint)
  273. defer res.Body.Close()
  274. if res.StatusCode != 200 && res.StatusCode != 404 {
  275. continue
  276. } else if res.StatusCode == 404 {
  277. return nil, fmt.Errorf("Repository not found")
  278. }
  279. result := make(map[string]string)
  280. rawJSON, err := ioutil.ReadAll(res.Body)
  281. if err != nil {
  282. return nil, err
  283. }
  284. if err := json.Unmarshal(rawJSON, &result); err != nil {
  285. return nil, err
  286. }
  287. return result, nil
  288. }
  289. return nil, fmt.Errorf("Could not reach any registry endpoint")
  290. }
  291. func buildEndpointsList(headers []string, indexEp string) ([]string, error) {
  292. var endpoints []string
  293. parsedUrl, err := url.Parse(indexEp)
  294. if err != nil {
  295. return nil, err
  296. }
  297. var urlScheme = parsedUrl.Scheme
  298. // The Registry's URL scheme has to match the Index'
  299. for _, ep := range headers {
  300. epList := strings.Split(ep, ",")
  301. for _, epListElement := range epList {
  302. endpoints = append(
  303. endpoints,
  304. fmt.Sprintf("%s://%s/v1/", urlScheme, strings.TrimSpace(epListElement)))
  305. }
  306. }
  307. return endpoints, nil
  308. }
  309. func (r *Registry) GetRepositoryData(remote string) (*RepositoryData, error) {
  310. indexEp := r.indexEndpoint
  311. repositoryTarget := fmt.Sprintf("%srepositories/%s/images", indexEp, remote)
  312. utils.Debugf("[registry] Calling GET %s", repositoryTarget)
  313. req, err := r.reqFactory.NewRequest("GET", repositoryTarget, nil)
  314. if err != nil {
  315. return nil, err
  316. }
  317. if r.authConfig != nil && len(r.authConfig.Username) > 0 {
  318. req.SetBasicAuth(r.authConfig.Username, r.authConfig.Password)
  319. }
  320. req.Header.Set("X-Docker-Token", "true")
  321. res, err := r.client.Do(req)
  322. if err != nil {
  323. return nil, err
  324. }
  325. defer res.Body.Close()
  326. if res.StatusCode == 401 {
  327. return nil, errLoginRequired
  328. }
  329. // TODO: Right now we're ignoring checksums in the response body.
  330. // In the future, we need to use them to check image validity.
  331. if res.StatusCode != 200 {
  332. return nil, utils.NewHTTPRequestError(fmt.Sprintf("HTTP code: %d", res.StatusCode), res)
  333. }
  334. var tokens []string
  335. if res.Header.Get("X-Docker-Token") != "" {
  336. tokens = res.Header["X-Docker-Token"]
  337. }
  338. var endpoints []string
  339. if res.Header.Get("X-Docker-Endpoints") != "" {
  340. endpoints, err = buildEndpointsList(res.Header["X-Docker-Endpoints"], indexEp)
  341. if err != nil {
  342. return nil, err
  343. }
  344. } else {
  345. // Assume the endpoint is on the same host
  346. u, err := url.Parse(indexEp)
  347. if err != nil {
  348. return nil, err
  349. }
  350. endpoints = append(endpoints, fmt.Sprintf("%s://%s/v1/", u.Scheme, req.URL.Host))
  351. }
  352. checksumsJSON, err := ioutil.ReadAll(res.Body)
  353. if err != nil {
  354. return nil, err
  355. }
  356. remoteChecksums := []*ImgData{}
  357. if err := json.Unmarshal(checksumsJSON, &remoteChecksums); err != nil {
  358. return nil, err
  359. }
  360. // Forge a better object from the retrieved data
  361. imgsData := make(map[string]*ImgData)
  362. for _, elem := range remoteChecksums {
  363. imgsData[elem.ID] = elem
  364. }
  365. return &RepositoryData{
  366. ImgList: imgsData,
  367. Endpoints: endpoints,
  368. Tokens: tokens,
  369. }, nil
  370. }
  371. func (r *Registry) PushImageChecksumRegistry(imgData *ImgData, registry string, token []string) error {
  372. utils.Debugf("[registry] Calling PUT %s", registry+"images/"+imgData.ID+"/checksum")
  373. req, err := r.reqFactory.NewRequest("PUT", registry+"images/"+imgData.ID+"/checksum", nil)
  374. if err != nil {
  375. return err
  376. }
  377. setTokenAuth(req, token)
  378. req.Header.Set("X-Docker-Checksum", imgData.Checksum)
  379. req.Header.Set("X-Docker-Checksum-Payload", imgData.ChecksumPayload)
  380. res, err := r.client.Do(req)
  381. if err != nil {
  382. return fmt.Errorf("Failed to upload metadata: %s", err)
  383. }
  384. defer res.Body.Close()
  385. if len(res.Cookies()) > 0 {
  386. r.client.Jar.SetCookies(req.URL, res.Cookies())
  387. }
  388. if res.StatusCode != 200 {
  389. errBody, err := ioutil.ReadAll(res.Body)
  390. if err != nil {
  391. return fmt.Errorf("HTTP code %d while uploading metadata and error when trying to parse response body: %s", res.StatusCode, err)
  392. }
  393. var jsonBody map[string]string
  394. if err := json.Unmarshal(errBody, &jsonBody); err != nil {
  395. errBody = []byte(err.Error())
  396. } else if jsonBody["error"] == "Image already exists" {
  397. return ErrAlreadyExists
  398. }
  399. return fmt.Errorf("HTTP code %d while uploading metadata: %s", res.StatusCode, errBody)
  400. }
  401. return nil
  402. }
  403. // Push a local image to the registry
  404. func (r *Registry) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, registry string, token []string) error {
  405. utils.Debugf("[registry] Calling PUT %s", registry+"images/"+imgData.ID+"/json")
  406. req, err := r.reqFactory.NewRequest("PUT", registry+"images/"+imgData.ID+"/json", bytes.NewReader(jsonRaw))
  407. if err != nil {
  408. return err
  409. }
  410. req.Header.Add("Content-type", "application/json")
  411. setTokenAuth(req, token)
  412. res, err := r.client.Do(req)
  413. if err != nil {
  414. return fmt.Errorf("Failed to upload metadata: %s", err)
  415. }
  416. defer res.Body.Close()
  417. if res.StatusCode == 401 && strings.HasPrefix(registry, "http://") {
  418. return utils.NewHTTPRequestError("HTTP code 401, Docker will not send auth headers over HTTP.", res)
  419. }
  420. if res.StatusCode != 200 {
  421. errBody, err := ioutil.ReadAll(res.Body)
  422. if err != nil {
  423. return utils.NewHTTPRequestError(fmt.Sprintf("HTTP code %d while uploading metadata and error when trying to parse response body: %s", res.StatusCode, err), res)
  424. }
  425. var jsonBody map[string]string
  426. if err := json.Unmarshal(errBody, &jsonBody); err != nil {
  427. errBody = []byte(err.Error())
  428. } else if jsonBody["error"] == "Image already exists" {
  429. return ErrAlreadyExists
  430. }
  431. return utils.NewHTTPRequestError(fmt.Sprintf("HTTP code %d while uploading metadata: %s", res.StatusCode, errBody), res)
  432. }
  433. return nil
  434. }
  435. func (r *Registry) PushImageLayerRegistry(imgID string, layer io.Reader, registry string, token []string, jsonRaw []byte) (checksum string, checksumPayload string, err error) {
  436. utils.Debugf("[registry] Calling PUT %s", registry+"images/"+imgID+"/layer")
  437. tarsumLayer := &utils.TarSum{Reader: layer}
  438. h := sha256.New()
  439. h.Write(jsonRaw)
  440. h.Write([]byte{'\n'})
  441. checksumLayer := &utils.CheckSum{Reader: tarsumLayer, Hash: h}
  442. req, err := r.reqFactory.NewRequest("PUT", registry+"images/"+imgID+"/layer", checksumLayer)
  443. if err != nil {
  444. return "", "", err
  445. }
  446. req.ContentLength = -1
  447. req.TransferEncoding = []string{"chunked"}
  448. setTokenAuth(req, token)
  449. res, err := r.client.Do(req)
  450. if err != nil {
  451. return "", "", fmt.Errorf("Failed to upload layer: %s", err)
  452. }
  453. if rc, ok := layer.(io.Closer); ok {
  454. if err := rc.Close(); err != nil {
  455. return "", "", err
  456. }
  457. }
  458. defer res.Body.Close()
  459. if res.StatusCode != 200 {
  460. errBody, err := ioutil.ReadAll(res.Body)
  461. if err != nil {
  462. return "", "", utils.NewHTTPRequestError(fmt.Sprintf("HTTP code %d while uploading metadata and error when trying to parse response body: %s", res.StatusCode, err), res)
  463. }
  464. return "", "", utils.NewHTTPRequestError(fmt.Sprintf("Received HTTP code %d while uploading layer: %s", res.StatusCode, errBody), res)
  465. }
  466. checksumPayload = "sha256:" + checksumLayer.Sum()
  467. return tarsumLayer.Sum(jsonRaw), checksumPayload, nil
  468. }
  469. // push a tag on the registry.
  470. // Remote has the format '<user>/<repo>
  471. func (r *Registry) PushRegistryTag(remote, revision, tag, registry string, token []string) error {
  472. // "jsonify" the string
  473. revision = "\"" + revision + "\""
  474. path := fmt.Sprintf("repositories/%s/tags/%s", remote, tag)
  475. req, err := r.reqFactory.NewRequest("PUT", registry+path, strings.NewReader(revision))
  476. if err != nil {
  477. return err
  478. }
  479. req.Header.Add("Content-type", "application/json")
  480. setTokenAuth(req, token)
  481. req.ContentLength = int64(len(revision))
  482. res, err := r.client.Do(req)
  483. if err != nil {
  484. return err
  485. }
  486. res.Body.Close()
  487. if res.StatusCode != 200 && res.StatusCode != 201 {
  488. return utils.NewHTTPRequestError(fmt.Sprintf("Internal server error: %d trying to push tag %s on %s", res.StatusCode, tag, remote), res)
  489. }
  490. return nil
  491. }
  492. func (r *Registry) PushImageJSONIndex(remote string, imgList []*ImgData, validate bool, regs []string) (*RepositoryData, error) {
  493. cleanImgList := []*ImgData{}
  494. indexEp := r.indexEndpoint
  495. if validate {
  496. for _, elem := range imgList {
  497. if elem.Checksum != "" {
  498. cleanImgList = append(cleanImgList, elem)
  499. }
  500. }
  501. } else {
  502. cleanImgList = imgList
  503. }
  504. imgListJSON, err := json.Marshal(cleanImgList)
  505. if err != nil {
  506. return nil, err
  507. }
  508. var suffix string
  509. if validate {
  510. suffix = "images"
  511. }
  512. u := fmt.Sprintf("%srepositories/%s/%s", indexEp, remote, suffix)
  513. utils.Debugf("[registry] PUT %s", u)
  514. utils.Debugf("Image list pushed to index:\n%s", imgListJSON)
  515. req, err := r.reqFactory.NewRequest("PUT", u, bytes.NewReader(imgListJSON))
  516. if err != nil {
  517. return nil, err
  518. }
  519. req.Header.Add("Content-type", "application/json")
  520. req.SetBasicAuth(r.authConfig.Username, r.authConfig.Password)
  521. req.ContentLength = int64(len(imgListJSON))
  522. req.Header.Set("X-Docker-Token", "true")
  523. if validate {
  524. req.Header["X-Docker-Endpoints"] = regs
  525. }
  526. res, err := r.client.Do(req)
  527. if err != nil {
  528. return nil, err
  529. }
  530. defer res.Body.Close()
  531. // Redirect if necessary
  532. for res.StatusCode >= 300 && res.StatusCode < 400 {
  533. utils.Debugf("Redirected to %s", res.Header.Get("Location"))
  534. req, err = r.reqFactory.NewRequest("PUT", res.Header.Get("Location"), bytes.NewReader(imgListJSON))
  535. if err != nil {
  536. return nil, err
  537. }
  538. req.SetBasicAuth(r.authConfig.Username, r.authConfig.Password)
  539. req.ContentLength = int64(len(imgListJSON))
  540. req.Header.Set("X-Docker-Token", "true")
  541. if validate {
  542. req.Header["X-Docker-Endpoints"] = regs
  543. }
  544. res, err = r.client.Do(req)
  545. if err != nil {
  546. return nil, err
  547. }
  548. defer res.Body.Close()
  549. }
  550. var tokens, endpoints []string
  551. if !validate {
  552. if res.StatusCode != 200 && res.StatusCode != 201 {
  553. errBody, err := ioutil.ReadAll(res.Body)
  554. if err != nil {
  555. return nil, err
  556. }
  557. return nil, utils.NewHTTPRequestError(fmt.Sprintf("Error: Status %d trying to push repository %s: %s", res.StatusCode, remote, errBody), res)
  558. }
  559. if res.Header.Get("X-Docker-Token") != "" {
  560. tokens = res.Header["X-Docker-Token"]
  561. utils.Debugf("Auth token: %v", tokens)
  562. } else {
  563. return nil, fmt.Errorf("Index response didn't contain an access token")
  564. }
  565. if res.Header.Get("X-Docker-Endpoints") != "" {
  566. endpoints, err = buildEndpointsList(res.Header["X-Docker-Endpoints"], indexEp)
  567. if err != nil {
  568. return nil, err
  569. }
  570. } else {
  571. return nil, fmt.Errorf("Index response didn't contain any endpoints")
  572. }
  573. }
  574. if validate {
  575. if res.StatusCode != 204 {
  576. errBody, err := ioutil.ReadAll(res.Body)
  577. if err != nil {
  578. return nil, err
  579. }
  580. return nil, utils.NewHTTPRequestError(fmt.Sprintf("Error: Status %d trying to push checksums %s: %s", res.StatusCode, remote, errBody), res)
  581. }
  582. }
  583. return &RepositoryData{
  584. Tokens: tokens,
  585. Endpoints: endpoints,
  586. }, nil
  587. }
  588. func (r *Registry) SearchRepositories(term string) (*SearchResults, error) {
  589. utils.Debugf("Index server: %s", r.indexEndpoint)
  590. u := r.indexEndpoint + "search?q=" + url.QueryEscape(term)
  591. req, err := r.reqFactory.NewRequest("GET", u, nil)
  592. if err != nil {
  593. return nil, err
  594. }
  595. if r.authConfig != nil && len(r.authConfig.Username) > 0 {
  596. req.SetBasicAuth(r.authConfig.Username, r.authConfig.Password)
  597. }
  598. req.Header.Set("X-Docker-Token", "true")
  599. res, err := r.client.Do(req)
  600. if err != nil {
  601. return nil, err
  602. }
  603. defer res.Body.Close()
  604. if res.StatusCode != 200 {
  605. return nil, utils.NewHTTPRequestError(fmt.Sprintf("Unexepected status code %d", res.StatusCode), res)
  606. }
  607. rawData, err := ioutil.ReadAll(res.Body)
  608. if err != nil {
  609. return nil, err
  610. }
  611. result := new(SearchResults)
  612. err = json.Unmarshal(rawData, result)
  613. return result, err
  614. }
  615. func (r *Registry) GetAuthConfig(withPasswd bool) *AuthConfig {
  616. password := ""
  617. if withPasswd {
  618. password = r.authConfig.Password
  619. }
  620. return &AuthConfig{
  621. Username: r.authConfig.Username,
  622. Password: password,
  623. Email: r.authConfig.Email,
  624. }
  625. }
  626. type SearchResult struct {
  627. StarCount int `json:"star_count"`
  628. IsOfficial bool `json:"is_official"`
  629. Name string `json:"name"`
  630. IsTrusted bool `json:"is_trusted"`
  631. Description string `json:"description"`
  632. }
  633. type SearchResults struct {
  634. Query string `json:"query"`
  635. NumResults int `json:"num_results"`
  636. Results []SearchResult `json:"results"`
  637. }
  638. type RepositoryData struct {
  639. ImgList map[string]*ImgData
  640. Endpoints []string
  641. Tokens []string
  642. }
  643. type ImgData struct {
  644. ID string `json:"id"`
  645. Checksum string `json:"checksum,omitempty"`
  646. ChecksumPayload string `json:"-"`
  647. Tag string `json:",omitempty"`
  648. }
  649. type RegistryInfo struct {
  650. Version string `json:"version"`
  651. Standalone bool `json:"standalone"`
  652. }
  653. type Registry struct {
  654. client *http.Client
  655. authConfig *AuthConfig
  656. reqFactory *utils.HTTPRequestFactory
  657. indexEndpoint string
  658. }
  659. func NewRegistry(authConfig *AuthConfig, factory *utils.HTTPRequestFactory, indexEndpoint string) (r *Registry, err error) {
  660. httpTransport := &http.Transport{
  661. DisableKeepAlives: true,
  662. Proxy: http.ProxyFromEnvironment,
  663. }
  664. r = &Registry{
  665. authConfig: authConfig,
  666. client: &http.Client{
  667. Transport: httpTransport,
  668. },
  669. indexEndpoint: indexEndpoint,
  670. }
  671. r.client.Jar, err = cookiejar.New(nil)
  672. if err != nil {
  673. return nil, err
  674. }
  675. // If we're working with a standalone private registry over HTTPS, send Basic Auth headers
  676. // alongside our requests.
  677. if indexEndpoint != IndexServerAddress() && strings.HasPrefix(indexEndpoint, "https://") {
  678. info, err := pingRegistryEndpoint(indexEndpoint)
  679. if err != nil {
  680. return nil, err
  681. }
  682. if info.Standalone {
  683. utils.Debugf("Endpoint %s is eligible for private registry registry. Enabling decorator.", indexEndpoint)
  684. dec := utils.NewHTTPAuthDecorator(authConfig.Username, authConfig.Password)
  685. factory.AddDecorator(dec)
  686. }
  687. }
  688. r.reqFactory = factory
  689. return r, nil
  690. }
  691. func HTTPRequestFactory(metaHeaders map[string][]string) *utils.HTTPRequestFactory {
  692. // FIXME: this replicates the 'info' job.
  693. httpVersion := make([]utils.VersionInfo, 0, 4)
  694. httpVersion = append(httpVersion, &simpleVersionInfo{"docker", dockerversion.VERSION})
  695. httpVersion = append(httpVersion, &simpleVersionInfo{"go", runtime.Version()})
  696. httpVersion = append(httpVersion, &simpleVersionInfo{"git-commit", dockerversion.GITCOMMIT})
  697. if kernelVersion, err := utils.GetKernelVersion(); err == nil {
  698. httpVersion = append(httpVersion, &simpleVersionInfo{"kernel", kernelVersion.String()})
  699. }
  700. httpVersion = append(httpVersion, &simpleVersionInfo{"os", runtime.GOOS})
  701. httpVersion = append(httpVersion, &simpleVersionInfo{"arch", runtime.GOARCH})
  702. ud := utils.NewHTTPUserAgentDecorator(httpVersion...)
  703. md := &utils.HTTPMetaHeadersDecorator{
  704. Headers: metaHeaders,
  705. }
  706. factory := utils.NewHTTPRequestFactory(ud, md)
  707. return factory
  708. }
  709. // simpleVersionInfo is a simple implementation of
  710. // the interface VersionInfo, which is used
  711. // to provide version information for some product,
  712. // component, etc. It stores the product name and the version
  713. // in string and returns them on calls to Name() and Version().
  714. type simpleVersionInfo struct {
  715. name string
  716. version string
  717. }
  718. func (v *simpleVersionInfo) Name() string {
  719. return v.name
  720. }
  721. func (v *simpleVersionInfo) Version() string {
  722. return v.version
  723. }