Browse code

Offline Image Transfers #1155

Frederick F. Kautz IV authored on 2013/09/03 01:06:17
Showing 4 changed files
... ...
@@ -534,6 +534,23 @@ func postImagesPush(srv *Server, version float64, w http.ResponseWriter, r *http
534 534
 	return nil
535 535
 }
536 536
 
537
+func getImagesGet(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
538
+	name := vars["name"]
539
+	err := srv.ImageExport(name, w)
540
+	if err != nil {
541
+		return err
542
+	}
543
+	return nil
544
+}
545
+
546
+func postImagesLoad(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
547
+	err := srv.ImageLoad(r.Body)
548
+	if err != nil {
549
+		return err
550
+	}
551
+	return nil
552
+}
553
+
537 554
 func postContainersCreate(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
538 555
 	if err := parseForm(r); err != nil {
539 556
 		return nil
... ...
@@ -1036,6 +1053,7 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) {
1036 1036
 			"/images/json":                    getImagesJSON,
1037 1037
 			"/images/viz":                     getImagesViz,
1038 1038
 			"/images/search":                  getImagesSearch,
1039
+			"/images/{name:.*}/get":           getImagesGet,
1039 1040
 			"/images/{name:.*}/history":       getImagesHistory,
1040 1041
 			"/images/{name:.*}/json":          getImagesByName,
1041 1042
 			"/containers/ps":                  getContainersJSON,
... ...
@@ -1052,6 +1070,7 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) {
1052 1052
 			"/build":                        postBuild,
1053 1053
 			"/images/create":                postImagesCreate,
1054 1054
 			"/images/{name:.*}/insert":      postImagesInsert,
1055
+			"/images/load":                  postImagesLoad,
1055 1056
 			"/images/{name:.*}/push":        postImagesPush,
1056 1057
 			"/images/{name:.*}/tag":         postImagesTag,
1057 1058
 			"/containers/create":            postContainersCreate,
... ...
@@ -92,6 +92,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error {
92 92
 		{"insert", "Insert a file in an image"},
93 93
 		{"inspect", "Return low-level information on a container"},
94 94
 		{"kill", "Kill a running container"},
95
+		{"load", "Load an image from a tar archive"},
95 96
 		{"login", "Register or Login to the docker registry server"},
96 97
 		{"logs", "Fetch the logs of a container"},
97 98
 		{"port", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT"},
... ...
@@ -102,6 +103,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error {
102 102
 		{"rm", "Remove one or more containers"},
103 103
 		{"rmi", "Remove one or more images"},
104 104
 		{"run", "Run a command in a new container"},
105
+		{"save", "Save an image to a tar archive"},
105 106
 		{"search", "Search for an image in the docker index"},
106 107
 		{"start", "Start a stopped container"},
107 108
 		{"stop", "Stop a running container"},
... ...
@@ -1961,6 +1963,42 @@ func (cli *DockerCli) CmdCp(args ...string) error {
1961 1961
 	return nil
1962 1962
 }
1963 1963
 
1964
+func (cli *DockerCli) CmdSave(args ...string) error {
1965
+	cmd := Subcmd("save", "IMAGE DESTINATION", "Save an image to a tar archive")
1966
+	if err := cmd.Parse(args); err != nil {
1967
+		cmd.Usage()
1968
+		return nil
1969
+	}
1970
+
1971
+	if cmd.NArg() != 1 {
1972
+		cmd.Usage()
1973
+		return nil
1974
+	}
1975
+
1976
+	image := cmd.Arg(0)
1977
+
1978
+	if err := cli.stream("GET", "/images/"+image+"/get", nil, cli.out, nil); err != nil {
1979
+		return err
1980
+	}
1981
+	return nil
1982
+}
1983
+
1984
+func (cli *DockerCli) CmdLoad(args ...string) error {
1985
+	cmd := Subcmd("load", "SOURCE", "Load an image from a tar archive")
1986
+
1987
+	if cmd.NArg() != 0 {
1988
+		cmd.Usage()
1989
+		return nil
1990
+	}
1991
+
1992
+	err := cli.stream("POST", "/images/load", cli.in, cli.out, nil)
1993
+	if err != nil {
1994
+		fmt.Println("Send failed", err)
1995
+	}
1996
+
1997
+	return nil
1998
+}
1999
+
1964 2000
 func (cli *DockerCli) call(method, path string, data interface{}) ([]byte, int, error) {
1965 2001
 	var params io.Reader
1966 2002
 	if data != nil {
... ...
@@ -559,6 +559,17 @@ Known Issues (kill)
559 559
 * :issue:`197` indicates that ``docker kill`` may leave directories
560 560
   behind and make it difficult to remove the container.
561 561
 
562
+.. _cli_load:
563
+
564
+``load``
565
+--------
566
+
567
+::
568
+    Usage: docker load < repository.tar
569
+
570
+    Loads a tarred repository from the standard input stream.
571
+    Restores both images and tags.
572
+
562 573
 .. _cli_login:
563 574
 
564 575
 ``login``
... ...
@@ -852,6 +863,17 @@ Known Issues (run -volumes-from)
852 852
   could indicate a permissions problem with AppArmor. Please see the
853 853
   issue for a workaround.
854 854
 
855
+.. _cli_save:
856
+
857
+``save``
858
+
859
+::
860
+
861
+    Usage: docker save image > repository.tar
862
+
863
+    Streams a tarred repository to the standard output stream.
864
+    Contains all parent layers, and all tags + versions.
865
+
855 866
 .. _cli_search:
856 867
 
857 868
 ``search``
... ...
@@ -197,6 +197,155 @@ func (srv *Server) ContainerExport(name string, out io.Writer) error {
197 197
 	return fmt.Errorf("No such container: %s", name)
198 198
 }
199 199
 
200
+// ImageExport exports all images with the given tag. All versions
201
+// containing the same tag are exported. The resulting output is an
202
+// uncompressed tar ball.
203
+// name is the set of tags to export.
204
+// out is the writer where the images are written to.
205
+func (srv *Server) ImageExport(name string, out io.Writer) error {
206
+	// get image json
207
+	tempdir, err := ioutil.TempDir("", "docker-export-")
208
+	if err != nil {
209
+		utils.Debugf("save", name, "")
210
+		return err
211
+	}
212
+	utils.Debugf("Serializing %s", name)
213
+
214
+	rootRepo := srv.runtime.repositories.Repositories[name]
215
+	for _, rootImage := range rootRepo {
216
+		image, _ := srv.ImageInspect(rootImage)
217
+		for i := image; i != nil; {
218
+			// temporary directory
219
+			tmpImageDir := path.Join(tempdir, i.ID)
220
+			os.Mkdir(tmpImageDir, os.ModeDir)
221
+
222
+			// serialize json
223
+			b, err := json.Marshal(i)
224
+			if err != nil {
225
+				utils.Debugf("%s", err)
226
+				os.RemoveAll(tempdir)
227
+				return err
228
+			}
229
+			ioutil.WriteFile(path.Join(tmpImageDir, "json"), b, os.ModeAppend)
230
+
231
+			// serialize filesystem
232
+			fs, err := Tar(path.Join(srv.runtime.graph.Root, i.ID, "layer"), Uncompressed)
233
+			if err != nil {
234
+				utils.Debugf("%s", err)
235
+				os.RemoveAll(tempdir)
236
+				return err
237
+			}
238
+			fsTar, err := os.Create(path.Join(tmpImageDir, "layer.tar"))
239
+			if err != nil {
240
+				os.RemoveAll(tempdir)
241
+				utils.Debugf("%s", err)
242
+				return err
243
+			}
244
+			_, err = io.Copy(fsTar, fs)
245
+			if err != nil {
246
+				utils.Debugf("%s", err)
247
+				os.RemoveAll(tempdir)
248
+				return err
249
+			}
250
+			fsTar.Close()
251
+
252
+			// find parent
253
+			if i.Parent != "" {
254
+				i, err = srv.ImageInspect(i.Parent)
255
+				if err != nil {
256
+					utils.Debugf("%s", err)
257
+					os.RemoveAll(tempdir)
258
+					return err
259
+				}
260
+			} else {
261
+				i = nil
262
+			}
263
+		}
264
+	}
265
+
266
+	// write repositories
267
+	rootRepoMap := map[string]Repository{}
268
+	rootRepoMap[name] = rootRepo
269
+	rootRepoJson, _ := json.Marshal(rootRepoMap)
270
+
271
+	ioutil.WriteFile(path.Join(tempdir, "repositories"), rootRepoJson, os.ModeAppend)
272
+
273
+	fs, err := Tar(tempdir, Uncompressed)
274
+	if err != nil {
275
+		os.RemoveAll(tempdir)
276
+		return err
277
+	}
278
+	if _, err := io.Copy(out, fs); err != nil {
279
+		os.RemoveAll(tempdir)
280
+		return err
281
+	}
282
+	os.RemoveAll(tempdir)
283
+	return nil
284
+}
285
+
286
+// Loads a set of images into the repository. This is the complementary of ImageExport.
287
+// The input stream is an uncompressed tar ball containing images and metadata.
288
+func (srv *Server) ImageLoad(in io.Reader) error {
289
+	tmpImageDir, _ := ioutil.TempDir("", "docker-import-")
290
+	repoTarFile := path.Join(tmpImageDir, "repo.tar")
291
+	repoDir := path.Join(tmpImageDir, "repo")
292
+	tarFile, _ := os.Create(repoTarFile)
293
+	io.Copy(tarFile, in)
294
+	tarFile.Close()
295
+	repoFile, _ := os.Open(repoTarFile)
296
+	os.Mkdir(repoDir, os.ModeDir)
297
+	Untar(repoFile, repoDir)
298
+	repositoriesJson, _ := ioutil.ReadFile(path.Join(tmpImageDir, "repo", "repositories"))
299
+	repositories := map[string]Repository{}
300
+	json.Unmarshal(repositoriesJson, &repositories)
301
+
302
+	for imageName, tagMap := range repositories {
303
+		for tag, address := range tagMap {
304
+			err := srv.recursiveLoad(address, tmpImageDir)
305
+			if err != nil {
306
+				utils.Debugf("Error loading repository")
307
+			}
308
+			srv.runtime.repositories.Set(imageName, tag, address, true)
309
+		}
310
+	}
311
+	os.RemoveAll(tmpImageDir)
312
+	return nil
313
+}
314
+
315
+func (srv *Server) recursiveLoad(address, tmpImageDir string) error {
316
+	_, err := srv.ImageInspect(address)
317
+	utils.Debugf("Attempting to load %s", "address")
318
+	if err != nil {
319
+		utils.Debugf("Loading %s", address)
320
+		imageJson, err := ioutil.ReadFile(path.Join(tmpImageDir, "repo", address, "json"))
321
+		if err != nil {
322
+			return err
323
+			utils.Debugf("Error reading json", err)
324
+		}
325
+		layer, err := os.Open(path.Join(tmpImageDir, "repo", address, "layer.tar"))
326
+		if err != nil {
327
+			utils.Debugf("Error reading embedded tar", err)
328
+			return err
329
+		}
330
+		img, err := NewImgJSON(imageJson)
331
+		if err != nil {
332
+			utils.Debugf("Error unmarshalling json", err)
333
+			return err
334
+		}
335
+		if img.Parent != "" {
336
+			if !srv.runtime.graph.Exists(img.Parent) {
337
+				srv.recursiveLoad(img.Parent, tmpImageDir)
338
+			}
339
+		}
340
+		err = srv.runtime.graph.Register(imageJson, layer, img)
341
+		if err != nil {
342
+			utils.Debugf("Error registering image")
343
+		}
344
+	}
345
+	utils.Debugf("Completed processing %s", address)
346
+	return nil
347
+}
348
+
200 349
 func (srv *Server) ImagesSearch(term string) ([]registry.SearchResult, error) {
201 350
 	r, err := registry.NewRegistry(srv.runtime.config.Root, nil, srv.HTTPRequestFactory(nil))
202 351
 	if err != nil {