registry.go 11 KB

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