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
|
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 {
|
func postContainersCreate(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
if err := parseForm(r); err != nil {
|
if err := parseForm(r); err != nil {
|
||||||
return nil
|
return nil
|
||||||
|
@ -1036,6 +1053,7 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) {
|
||||||
"/images/json": getImagesJSON,
|
"/images/json": getImagesJSON,
|
||||||
"/images/viz": getImagesViz,
|
"/images/viz": getImagesViz,
|
||||||
"/images/search": getImagesSearch,
|
"/images/search": getImagesSearch,
|
||||||
|
"/images/{name:.*}/get": getImagesGet,
|
||||||
"/images/{name:.*}/history": getImagesHistory,
|
"/images/{name:.*}/history": getImagesHistory,
|
||||||
"/images/{name:.*}/json": getImagesByName,
|
"/images/{name:.*}/json": getImagesByName,
|
||||||
"/containers/ps": getContainersJSON,
|
"/containers/ps": getContainersJSON,
|
||||||
|
@ -1052,6 +1070,7 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) {
|
||||||
"/build": postBuild,
|
"/build": postBuild,
|
||||||
"/images/create": postImagesCreate,
|
"/images/create": postImagesCreate,
|
||||||
"/images/{name:.*}/insert": postImagesInsert,
|
"/images/{name:.*}/insert": postImagesInsert,
|
||||||
|
"/images/load": postImagesLoad,
|
||||||
"/images/{name:.*}/push": postImagesPush,
|
"/images/{name:.*}/push": postImagesPush,
|
||||||
"/images/{name:.*}/tag": postImagesTag,
|
"/images/{name:.*}/tag": postImagesTag,
|
||||||
"/containers/create": postContainersCreate,
|
"/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"},
|
{"insert", "Insert a file in an image"},
|
||||||
{"inspect", "Return low-level information on a container"},
|
{"inspect", "Return low-level information on a container"},
|
||||||
{"kill", "Kill a running container"},
|
{"kill", "Kill a running container"},
|
||||||
|
{"load", "Load an image from a tar archive"},
|
||||||
{"login", "Register or Login to the docker registry server"},
|
{"login", "Register or Login to the docker registry server"},
|
||||||
{"logs", "Fetch the logs of a container"},
|
{"logs", "Fetch the logs of a container"},
|
||||||
{"port", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT"},
|
{"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"},
|
{"rm", "Remove one or more containers"},
|
||||||
{"rmi", "Remove one or more images"},
|
{"rmi", "Remove one or more images"},
|
||||||
{"run", "Run a command in a new container"},
|
{"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"},
|
{"search", "Search for an image in the docker index"},
|
||||||
{"start", "Start a stopped container"},
|
{"start", "Start a stopped container"},
|
||||||
{"stop", "Stop a running container"},
|
{"stop", "Stop a running container"},
|
||||||
|
@ -1961,6 +1963,42 @@ func (cli *DockerCli) CmdCp(args ...string) error {
|
||||||
return nil
|
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) {
|
func (cli *DockerCli) call(method, path string, data interface{}) ([]byte, int, error) {
|
||||||
var params io.Reader
|
var params io.Reader
|
||||||
if data != nil {
|
if data != nil {
|
||||||
|
|
|
@ -559,6 +559,17 @@ Known Issues (kill)
|
||||||
* :issue:`197` indicates that ``docker kill`` may leave directories
|
* :issue:`197` indicates that ``docker kill`` may leave directories
|
||||||
behind and make it difficult to remove the container.
|
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:
|
.. _cli_login:
|
||||||
|
|
||||||
``login``
|
``login``
|
||||||
|
@ -852,6 +863,17 @@ Known Issues (run -volumes-from)
|
||||||
could indicate a permissions problem with AppArmor. Please see the
|
could indicate a permissions problem with AppArmor. Please see the
|
||||||
issue for a workaround.
|
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:
|
.. _cli_search:
|
||||||
|
|
||||||
``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)
|
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) {
|
func (srv *Server) ImagesSearch(term string) ([]registry.SearchResult, error) {
|
||||||
r, err := registry.NewRegistry(srv.runtime.config.Root, nil, srv.HTTPRequestFactory(nil))
|
r, err := registry.NewRegistry(srv.runtime.config.Root, nil, srv.HTTPRequestFactory(nil))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue