mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
Offline Image Transfers #1155
This commit is contained in:
parent
b700ee006a
commit
7eaa59f626
4 changed files with 228 additions and 0 deletions
19
api.go
19
api.go
|
@ -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,
|
||||
|
|
38
commands.go
38
commands.go
|
@ -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 {
|
||||
|
|
|
@ -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
149
server.go
|
@ -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 {
|
||||
|
|
Loading…
Reference in a new issue