registry.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  1. package docker
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "github.com/dotcloud/docker/auth"
  6. "io"
  7. "io/ioutil"
  8. "net/http"
  9. "path"
  10. "strings"
  11. )
  12. //FIXME: Set the endpoint in a conf file or via commandline
  13. //const REGISTRY_ENDPOINT = "http://registry-creack.dotcloud.com/v1"
  14. const REGISTRY_ENDPOINT = auth.REGISTRY_SERVER + "/v1"
  15. // Build an Image object from raw json data
  16. func NewImgJson(src []byte) (*Image, error) {
  17. ret := &Image{}
  18. Debugf("Json string: {%s}\n", src)
  19. // FIXME: Is there a cleaner way to "purify" the input json?
  20. if err := json.Unmarshal(src, ret); err != nil {
  21. return nil, err
  22. }
  23. return ret, nil
  24. }
  25. // Build an Image object list from a raw json data
  26. // FIXME: Do this in "stream" mode
  27. func NewMultipleImgJson(src []byte) ([]*Image, error) {
  28. ret := []*Image{}
  29. dec := json.NewDecoder(strings.NewReader(string(src)))
  30. for {
  31. m := &Image{}
  32. if err := dec.Decode(m); err == io.EOF {
  33. break
  34. } else if err != nil {
  35. return nil, err
  36. }
  37. ret = append(ret, m)
  38. }
  39. return ret, nil
  40. }
  41. // Retrieve the history of a given image from the Registry.
  42. // Return a list of the parent's json (requested image included)
  43. func (graph *Graph) getRemoteHistory(imgId string, authConfig *auth.AuthConfig) ([]*Image, error) {
  44. client := &http.Client{}
  45. req, err := http.NewRequest("GET", REGISTRY_ENDPOINT+"/images/"+imgId+"/history", nil)
  46. if err != nil {
  47. return nil, err
  48. }
  49. req.SetBasicAuth(authConfig.Username, authConfig.Password)
  50. res, err := client.Do(req)
  51. if err != nil || res.StatusCode != 200 {
  52. if res != nil {
  53. return nil, fmt.Errorf("Internal server error: %d trying to fetch remote history for %s", res.StatusCode, imgId)
  54. }
  55. return nil, err
  56. }
  57. defer res.Body.Close()
  58. jsonString, err := ioutil.ReadAll(res.Body)
  59. if err != nil {
  60. return nil, fmt.Errorf("Error while reading the http response: %s\n", err)
  61. }
  62. history, err := NewMultipleImgJson(jsonString)
  63. if err != nil {
  64. return nil, fmt.Errorf("Error while parsing the json: %s\n", err)
  65. }
  66. return history, nil
  67. }
  68. // Check if an image exists in the Registry
  69. func (graph *Graph) LookupRemoteImage(imgId string, authConfig *auth.AuthConfig) bool {
  70. rt := &http.Transport{Proxy: http.ProxyFromEnvironment}
  71. req, err := http.NewRequest("GET", REGISTRY_ENDPOINT+"/images/"+imgId+"/json", nil)
  72. if err != nil {
  73. return false
  74. }
  75. req.SetBasicAuth(authConfig.Username, authConfig.Password)
  76. res, err := rt.RoundTrip(req)
  77. if err != nil || res.StatusCode != 307 {
  78. return false
  79. }
  80. return res.StatusCode == 307
  81. }
  82. // Retrieve an image from the Registry.
  83. // Returns the Image object as well as the layer as an Archive (io.Reader)
  84. func (graph *Graph) getRemoteImage(stdout io.Writer, imgId string, authConfig *auth.AuthConfig) (*Image, Archive, error) {
  85. client := &http.Client{}
  86. fmt.Fprintf(stdout, "Pulling %s metadata\r\n", imgId)
  87. // Get the Json
  88. req, err := http.NewRequest("GET", REGISTRY_ENDPOINT+"/images/"+imgId+"/json", nil)
  89. if err != nil {
  90. return nil, nil, fmt.Errorf("Failed to download json: %s", err)
  91. }
  92. req.SetBasicAuth(authConfig.Username, authConfig.Password)
  93. res, err := client.Do(req)
  94. if err != nil {
  95. return nil, nil, fmt.Errorf("Failed to download json: %s", err)
  96. }
  97. if res.StatusCode != 200 {
  98. return nil, nil, fmt.Errorf("HTTP code %d", res.StatusCode)
  99. }
  100. defer res.Body.Close()
  101. jsonString, err := ioutil.ReadAll(res.Body)
  102. if err != nil {
  103. return nil, nil, fmt.Errorf("Failed to download json: %s", err)
  104. }
  105. img, err := NewImgJson(jsonString)
  106. if err != nil {
  107. return nil, nil, fmt.Errorf("Failed to parse json: %s", err)
  108. }
  109. img.Id = imgId
  110. // Get the layer
  111. fmt.Fprintf(stdout, "Pulling %s fs layer\r\n", imgId)
  112. req, err = http.NewRequest("GET", REGISTRY_ENDPOINT+"/images/"+imgId+"/layer", nil)
  113. if err != nil {
  114. return nil, nil, fmt.Errorf("Error while getting from the server: %s\n", err)
  115. }
  116. req.SetBasicAuth(authConfig.Username, authConfig.Password)
  117. res, err = client.Do(req)
  118. if err != nil {
  119. return nil, nil, err
  120. }
  121. return img, ProgressReader(res.Body, int(res.ContentLength), stdout), nil
  122. }
  123. func (graph *Graph) PullImage(stdout io.Writer, imgId string, authConfig *auth.AuthConfig) error {
  124. history, err := graph.getRemoteHistory(imgId, authConfig)
  125. if err != nil {
  126. return err
  127. }
  128. // FIXME: Try to stream the images?
  129. // FIXME: Lunch the getRemoteImage() in goroutines
  130. for _, j := range history {
  131. if !graph.Exists(j.Id) {
  132. img, layer, err := graph.getRemoteImage(stdout, j.Id, authConfig)
  133. if err != nil {
  134. // FIXME: Keep goging in case of error?
  135. return err
  136. }
  137. if err = graph.Register(layer, img); err != nil {
  138. return err
  139. }
  140. }
  141. }
  142. return nil
  143. }
  144. // FIXME: Handle the askedTag parameter
  145. func (graph *Graph) PullRepository(stdout io.Writer, remote, askedTag string, repositories *TagStore, authConfig *auth.AuthConfig) error {
  146. client := &http.Client{}
  147. fmt.Fprintf(stdout, "Pulling repository %s\r\n", remote)
  148. var repositoryTarget string
  149. // If we are asking for 'root' repository, lookup on the Library's registry
  150. if strings.Index(remote, "/") == -1 {
  151. repositoryTarget = REGISTRY_ENDPOINT + "/library/" + remote
  152. } else {
  153. repositoryTarget = REGISTRY_ENDPOINT + "/users/" + remote
  154. }
  155. req, err := http.NewRequest("GET", repositoryTarget, nil)
  156. if err != nil {
  157. return err
  158. }
  159. req.SetBasicAuth(authConfig.Username, authConfig.Password)
  160. res, err := client.Do(req)
  161. if err != nil {
  162. return err
  163. }
  164. defer res.Body.Close()
  165. if res.StatusCode != 200 {
  166. return fmt.Errorf("HTTP code: %d", res.StatusCode)
  167. }
  168. rawJson, err := ioutil.ReadAll(res.Body)
  169. if err != nil {
  170. return err
  171. }
  172. t := map[string]string{}
  173. if err = json.Unmarshal(rawJson, &t); err != nil {
  174. return err
  175. }
  176. for tag, rev := range t {
  177. fmt.Fprintf(stdout, "Pulling tag %s:%s\r\n", remote, tag)
  178. if err = graph.PullImage(stdout, rev, authConfig); err != nil {
  179. return err
  180. }
  181. if err = repositories.Set(remote, tag, rev, true); err != nil {
  182. return err
  183. }
  184. }
  185. if err = repositories.Save(); err != nil {
  186. return err
  187. }
  188. return nil
  189. }
  190. // Push a local image to the registry with its history if needed
  191. func (graph *Graph) PushImage(stdout io.Writer, imgOrig *Image, authConfig *auth.AuthConfig) error {
  192. client := &http.Client{}
  193. // FIXME: Factorize the code
  194. // FIXME: Do the puts in goroutines
  195. if err := imgOrig.WalkHistory(func(img *Image) error {
  196. jsonRaw, err := ioutil.ReadFile(path.Join(graph.Root, img.Id, "json"))
  197. if err != nil {
  198. return fmt.Errorf("Error while retreiving the path for {%s}: %s", img.Id, err)
  199. }
  200. fmt.Fprintf(stdout, "Pushing %s metadata\r\n", img.Id)
  201. // FIXME: try json with UTF8
  202. jsonData := strings.NewReader(string(jsonRaw))
  203. req, err := http.NewRequest("PUT", REGISTRY_ENDPOINT+"/images/"+img.Id+"/json", jsonData)
  204. if err != nil {
  205. return err
  206. }
  207. req.Header.Add("Content-type", "application/json")
  208. req.SetBasicAuth(authConfig.Username, authConfig.Password)
  209. res, err := client.Do(req)
  210. if err != nil {
  211. return fmt.Errorf("Failed to upload metadata: %s", err)
  212. }
  213. defer res.Body.Close()
  214. if res.StatusCode != 200 {
  215. switch res.StatusCode {
  216. case 204:
  217. // Case where the image is already on the Registry
  218. // FIXME: Do not be silent?
  219. return nil
  220. default:
  221. errBody, err := ioutil.ReadAll(res.Body)
  222. if err != nil {
  223. errBody = []byte(err.Error())
  224. }
  225. return fmt.Errorf("HTTP code %d while uploading metadata: %s", res.StatusCode, errBody)
  226. }
  227. }
  228. fmt.Fprintf(stdout, "Pushing %s fs layer\r\n", img.Id)
  229. req2, err := http.NewRequest("PUT", REGISTRY_ENDPOINT+"/images/"+img.Id+"/layer", nil)
  230. req2.SetBasicAuth(authConfig.Username, authConfig.Password)
  231. res2, err := client.Do(req2)
  232. if err != nil {
  233. return fmt.Errorf("Registry returned error: %s", err)
  234. }
  235. res2.Body.Close()
  236. if res2.StatusCode != 307 {
  237. return fmt.Errorf("Registry returned unexpected HTTP status code %d, expected 307", res2.StatusCode)
  238. }
  239. url, err := res2.Location()
  240. if err != nil || url == nil {
  241. return fmt.Errorf("Failed to retrieve layer upload location: %s", err)
  242. }
  243. // FIXME: Don't do this :D. Check the S3 requierement and implement chunks of 5MB
  244. // FIXME2: I won't stress it enough, DON'T DO THIS! very high priority
  245. layerData2, err := Tar(path.Join(graph.Root, img.Id, "layer"), Xz)
  246. tmp, err := ioutil.ReadAll(layerData2)
  247. if err != nil {
  248. return err
  249. }
  250. layerLength := len(tmp)
  251. layerData, err := Tar(path.Join(graph.Root, img.Id, "layer"), Xz)
  252. if err != nil {
  253. return fmt.Errorf("Failed to generate layer archive: %s", err)
  254. }
  255. req3, err := http.NewRequest("PUT", url.String(), ProgressReader(layerData.(io.ReadCloser), layerLength, stdout))
  256. if err != nil {
  257. return err
  258. }
  259. req3.ContentLength = int64(layerLength)
  260. req3.TransferEncoding = []string{"none"}
  261. res3, err := client.Do(req3)
  262. if err != nil {
  263. return fmt.Errorf("Failed to upload layer: %s", err)
  264. }
  265. res3.Body.Close()
  266. if res3.StatusCode != 200 {
  267. return fmt.Errorf("Received HTTP code %d while uploading layer", res3.StatusCode)
  268. }
  269. return nil
  270. }); err != nil {
  271. return err
  272. }
  273. return nil
  274. }
  275. // push a tag on the registry.
  276. // Remote has the format '<user>/<repo>
  277. func (graph *Graph) pushTag(remote, revision, tag string, authConfig *auth.AuthConfig) error {
  278. // Keep this for backward compatibility
  279. if tag == "" {
  280. tag = "lastest"
  281. }
  282. // "jsonify" the string
  283. revision = "\"" + revision + "\""
  284. Debugf("Pushing tags for rev [%s] on {%s}\n", revision, REGISTRY_ENDPOINT+"/users/"+remote+"/"+tag)
  285. client := &http.Client{}
  286. req, err := http.NewRequest("PUT", REGISTRY_ENDPOINT+"/users/"+remote+"/"+tag, strings.NewReader(revision))
  287. req.Header.Add("Content-type", "application/json")
  288. req.SetBasicAuth(authConfig.Username, authConfig.Password)
  289. res, err := client.Do(req)
  290. if err != nil {
  291. return err
  292. }
  293. res.Body.Close()
  294. if res.StatusCode != 200 && res.StatusCode != 201 {
  295. return fmt.Errorf("Internal server error: %d trying to push tag %s on %s", res.StatusCode, tag, remote)
  296. }
  297. Debugf("Result of push tag: %d\n", res.StatusCode)
  298. switch res.StatusCode {
  299. default:
  300. return fmt.Errorf("Error %d\n", res.StatusCode)
  301. case 200:
  302. case 201:
  303. }
  304. return nil
  305. }
  306. func (graph *Graph) LookupRemoteRepository(remote string, authConfig *auth.AuthConfig) bool {
  307. rt := &http.Transport{Proxy: http.ProxyFromEnvironment}
  308. var repositoryTarget string
  309. // If we are asking for 'root' repository, lookup on the Library's registry
  310. if strings.Index(remote, "/") == -1 {
  311. repositoryTarget = REGISTRY_ENDPOINT + "/library/" + remote + "/lookup"
  312. } else {
  313. repositoryTarget = REGISTRY_ENDPOINT + "/users/" + remote + "/lookup"
  314. }
  315. Debugf("Checking for permissions on: %s", repositoryTarget)
  316. req, err := http.NewRequest("PUT", repositoryTarget, strings.NewReader("\"\""))
  317. if err != nil {
  318. Debugf("%s\n", err)
  319. return false
  320. }
  321. req.SetBasicAuth(authConfig.Username, authConfig.Password)
  322. req.Header.Add("Content-type", "application/json")
  323. res, err := rt.RoundTrip(req)
  324. if err != nil || res.StatusCode != 404 {
  325. errBody, err := ioutil.ReadAll(res.Body)
  326. if err != nil {
  327. errBody = []byte(err.Error())
  328. }
  329. Debugf("Lookup status code: %d (body: %s)", res.StatusCode, errBody)
  330. return false
  331. }
  332. return true
  333. }
  334. // FIXME: this should really be PushTag
  335. func (graph *Graph) pushPrimitive(stdout io.Writer, remote, tag, imgId string, authConfig *auth.AuthConfig) error {
  336. // Check if the local impage exists
  337. img, err := graph.Get(imgId)
  338. if err != nil {
  339. fmt.Fprintf(stdout, "Skipping tag %s:%s: %s does not exist\r\n", remote, tag, imgId)
  340. return nil
  341. }
  342. fmt.Fprintf(stdout, "Pushing tag %s:%s\r\n", remote, tag)
  343. // Push the image
  344. if err = graph.PushImage(stdout, img, authConfig); err != nil {
  345. return err
  346. }
  347. fmt.Fprintf(stdout, "Registering tag %s:%s\r\n", remote, tag)
  348. // And then the tag
  349. if err = graph.pushTag(remote, imgId, tag, authConfig); err != nil {
  350. return err
  351. }
  352. return nil
  353. }
  354. // Push a repository to the registry.
  355. // Remote has the format '<user>/<repo>
  356. func (graph *Graph) PushRepository(stdout io.Writer, remote string, localRepo Repository, authConfig *auth.AuthConfig) error {
  357. // Check if the remote repository exists/if we have the permission
  358. if !graph.LookupRemoteRepository(remote, authConfig) {
  359. return fmt.Errorf("Permission denied on repository %s\n", remote)
  360. }
  361. fmt.Fprintf(stdout, "Pushing repository %s (%d tags)\r\n", remote, len(localRepo))
  362. // For each image within the repo, push them
  363. for tag, imgId := range localRepo {
  364. if err := graph.pushPrimitive(stdout, remote, tag, imgId, authConfig); err != nil {
  365. // FIXME: Continue on error?
  366. return err
  367. }
  368. }
  369. return nil
  370. }