registry.go 21 KB

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