Offline Image Transfers #1155

This commit is contained in:
Frederick F. Kautz IV 2013-09-02 09:06:17 -07:00
parent b700ee006a
commit 7eaa59f626
4 changed files with 228 additions and 0 deletions

19
api.go
View File

@ -534,6 +534,23 @@ func postImagesPush(srv *Server, version float64, w http.ResponseWriter, r *http
return nil
}
func getImagesGet(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
name := vars["name"]
err := srv.ImageExport(name, w)
if err != nil {
return err
}
return nil
}
func postImagesLoad(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
err := srv.ImageLoad(r.Body)
if err != nil {
return err
}
return nil
}
func postContainersCreate(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if err := parseForm(r); err != nil {
return nil
@ -1036,6 +1053,7 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) {
"/images/json": getImagesJSON,
"/images/viz": getImagesViz,
"/images/search": getImagesSearch,
"/images/{name:.*}/get": getImagesGet,
"/images/{name:.*}/history": getImagesHistory,
"/images/{name:.*}/json": getImagesByName,
"/containers/ps": getContainersJSON,
@ -1052,6 +1070,7 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) {
"/build": postBuild,
"/images/create": postImagesCreate,
"/images/{name:.*}/insert": postImagesInsert,
"/images/load": postImagesLoad,
"/images/{name:.*}/push": postImagesPush,
"/images/{name:.*}/tag": postImagesTag,
"/containers/create": postContainersCreate,

View File

@ -92,6 +92,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error {
{"insert", "Insert a file in an image"},
{"inspect", "Return low-level information on a container"},
{"kill", "Kill a running container"},
{"load", "Load an image from a tar archive"},
{"login", "Register or Login to the docker registry server"},
{"logs", "Fetch the logs of a container"},
{"port", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT"},
@ -102,6 +103,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error {
{"rm", "Remove one or more containers"},
{"rmi", "Remove one or more images"},
{"run", "Run a command in a new container"},
{"save", "Save an image to a tar archive"},
{"search", "Search for an image in the docker index"},
{"start", "Start a stopped container"},
{"stop", "Stop a running container"},
@ -1961,6 +1963,42 @@ func (cli *DockerCli) CmdCp(args ...string) error {
return nil
}
func (cli *DockerCli) CmdSave(args ...string) error {
cmd := Subcmd("save", "IMAGE DESTINATION", "Save an image to a tar archive")
if err := cmd.Parse(args); err != nil {
cmd.Usage()
return nil
}
if cmd.NArg() != 1 {
cmd.Usage()
return nil
}
image := cmd.Arg(0)
if err := cli.stream("GET", "/images/"+image+"/get", nil, cli.out, nil); err != nil {
return err
}
return nil
}
func (cli *DockerCli) CmdLoad(args ...string) error {
cmd := Subcmd("load", "SOURCE", "Load an image from a tar archive")
if cmd.NArg() != 0 {
cmd.Usage()
return nil
}
err := cli.stream("POST", "/images/load", cli.in, cli.out, nil)
if err != nil {
fmt.Println("Send failed", err)
}
return nil
}
func (cli *DockerCli) call(method, path string, data interface{}) ([]byte, int, error) {
var params io.Reader
if data != nil {

View File

@ -559,6 +559,17 @@ Known Issues (kill)
* :issue:`197` indicates that ``docker kill`` may leave directories
behind and make it difficult to remove the container.
.. _cli_load:
``load``
--------
::
Usage: docker load < repository.tar
Loads a tarred repository from the standard input stream.
Restores both images and tags.
.. _cli_login:
``login``
@ -852,6 +863,17 @@ Known Issues (run -volumes-from)
could indicate a permissions problem with AppArmor. Please see the
issue for a workaround.
.. _cli_save:
``save``
::
Usage: docker save image > repository.tar
Streams a tarred repository to the standard output stream.
Contains all parent layers, and all tags + versions.
.. _cli_search:
``search``

149
server.go
View File

@ -197,6 +197,155 @@ func (srv *Server) ContainerExport(name string, out io.Writer) error {
return fmt.Errorf("No such container: %s", name)
}
// ImageExport exports all images with the given tag. All versions
// containing the same tag are exported. The resulting output is an
// uncompressed tar ball.
// name is the set of tags to export.
// out is the writer where the images are written to.
func (srv *Server) ImageExport(name string, out io.Writer) error {
// get image json
tempdir, err := ioutil.TempDir("", "docker-export-")
if err != nil {
utils.Debugf("save", name, "")
return err
}
utils.Debugf("Serializing %s", name)
rootRepo := srv.runtime.repositories.Repositories[name]
for _, rootImage := range rootRepo {
image, _ := srv.ImageInspect(rootImage)
for i := image; i != nil; {
// temporary directory
tmpImageDir := path.Join(tempdir, i.ID)
os.Mkdir(tmpImageDir, os.ModeDir)
// serialize json
b, err := json.Marshal(i)
if err != nil {
utils.Debugf("%s", err)
os.RemoveAll(tempdir)
return err
}
ioutil.WriteFile(path.Join(tmpImageDir, "json"), b, os.ModeAppend)
// serialize filesystem
fs, err := Tar(path.Join(srv.runtime.graph.Root, i.ID, "layer"), Uncompressed)
if err != nil {
utils.Debugf("%s", err)
os.RemoveAll(tempdir)
return err
}
fsTar, err := os.Create(path.Join(tmpImageDir, "layer.tar"))
if err != nil {
os.RemoveAll(tempdir)
utils.Debugf("%s", err)
return err
}
_, err = io.Copy(fsTar, fs)
if err != nil {
utils.Debugf("%s", err)
os.RemoveAll(tempdir)
return err
}
fsTar.Close()
// find parent
if i.Parent != "" {
i, err = srv.ImageInspect(i.Parent)
if err != nil {
utils.Debugf("%s", err)
os.RemoveAll(tempdir)
return err
}
} else {
i = nil
}
}
}
// write repositories
rootRepoMap := map[string]Repository{}
rootRepoMap[name] = rootRepo
rootRepoJson, _ := json.Marshal(rootRepoMap)
ioutil.WriteFile(path.Join(tempdir, "repositories"), rootRepoJson, os.ModeAppend)
fs, err := Tar(tempdir, Uncompressed)
if err != nil {
os.RemoveAll(tempdir)
return err
}
if _, err := io.Copy(out, fs); err != nil {
os.RemoveAll(tempdir)
return err
}
os.RemoveAll(tempdir)
return nil
}
// Loads a set of images into the repository. This is the complementary of ImageExport.
// The input stream is an uncompressed tar ball containing images and metadata.
func (srv *Server) ImageLoad(in io.Reader) error {
tmpImageDir, _ := ioutil.TempDir("", "docker-import-")
repoTarFile := path.Join(tmpImageDir, "repo.tar")
repoDir := path.Join(tmpImageDir, "repo")
tarFile, _ := os.Create(repoTarFile)
io.Copy(tarFile, in)
tarFile.Close()
repoFile, _ := os.Open(repoTarFile)
os.Mkdir(repoDir, os.ModeDir)
Untar(repoFile, repoDir)
repositoriesJson, _ := ioutil.ReadFile(path.Join(tmpImageDir, "repo", "repositories"))
repositories := map[string]Repository{}
json.Unmarshal(repositoriesJson, &repositories)
for imageName, tagMap := range repositories {
for tag, address := range tagMap {
err := srv.recursiveLoad(address, tmpImageDir)
if err != nil {
utils.Debugf("Error loading repository")
}
srv.runtime.repositories.Set(imageName, tag, address, true)
}
}
os.RemoveAll(tmpImageDir)
return nil
}
func (srv *Server) recursiveLoad(address, tmpImageDir string) error {
_, err := srv.ImageInspect(address)
utils.Debugf("Attempting to load %s", "address")
if err != nil {
utils.Debugf("Loading %s", address)
imageJson, err := ioutil.ReadFile(path.Join(tmpImageDir, "repo", address, "json"))
if err != nil {
return err
utils.Debugf("Error reading json", err)
}
layer, err := os.Open(path.Join(tmpImageDir, "repo", address, "layer.tar"))
if err != nil {
utils.Debugf("Error reading embedded tar", err)
return err
}
img, err := NewImgJSON(imageJson)
if err != nil {
utils.Debugf("Error unmarshalling json", err)
return err
}
if img.Parent != "" {
if !srv.runtime.graph.Exists(img.Parent) {
srv.recursiveLoad(img.Parent, tmpImageDir)
}
}
err = srv.runtime.graph.Register(imageJson, layer, img)
if err != nil {
utils.Debugf("Error registering image")
}
}
utils.Debugf("Completed processing %s", address)
return nil
}
func (srv *Server) ImagesSearch(term string) ([]registry.SearchResult, error) {
r, err := registry.NewRegistry(srv.runtime.config.Root, nil, srv.HTTPRequestFactory(nil))
if err != nil {