diff --git a/api/server/router/image/backend.go b/api/server/router/image/backend.go index 7ec0dc59c9..221d12e241 100644 --- a/api/server/router/image/backend.go +++ b/api/server/router/image/backend.go @@ -4,6 +4,7 @@ import ( "context" "io" + "github.com/docker/distribution/reference" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/image" @@ -31,7 +32,7 @@ type imageBackend interface { type importExportBackend interface { LoadImage(ctx context.Context, inTar io.ReadCloser, outStream io.Writer, quiet bool) error - ImportImage(ctx context.Context, src string, repository string, platform *specs.Platform, tag string, msg string, inConfig io.ReadCloser, outStream io.Writer, changes []string) error + ImportImage(ctx context.Context, ref reference.Named, platform *specs.Platform, msg string, layerReader io.Reader, changes []string) (dockerimage.ID, error) ExportImage(ctx context.Context, names []string, outStream io.Writer) error } diff --git a/api/server/router/image/image_routes.go b/api/server/router/image/image_routes.go index ab64b1511a..c49cd102b0 100644 --- a/api/server/router/image/image_routes.go +++ b/api/server/router/image/image_routes.go @@ -2,7 +2,9 @@ package image // import "github.com/docker/docker/api/server/router/image" import ( "context" + "io" "net/http" + "net/url" "strconv" "strings" "time" @@ -15,9 +17,11 @@ import ( opts "github.com/docker/docker/api/types/image" "github.com/docker/docker/api/types/registry" "github.com/docker/docker/api/types/versions" + "github.com/docker/docker/builder/remotecontext" "github.com/docker/docker/errdefs" "github.com/docker/docker/image" "github.com/docker/docker/pkg/ioutils" + "github.com/docker/docker/pkg/progress" "github.com/docker/docker/pkg/streamformatter" specs "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" @@ -33,7 +37,7 @@ func (ir *imageRouter) postImagesCreate(ctx context.Context, w http.ResponseWrit img = r.Form.Get("fromImage") repo = r.Form.Get("repo") tag = r.Form.Get("tag") - message = r.Form.Get("message") + comment = r.Form.Get("message") progressErr error output = ioutils.NewWriteFlusher(w) platform *specs.Platform @@ -67,7 +71,61 @@ func (ir *imageRouter) postImagesCreate(ctx context.Context, w http.ResponseWrit progressErr = ir.backend.PullImage(ctx, img, tag, platform, metaHeaders, authConfig, output) } else { // import src := r.Form.Get("fromSrc") - progressErr = ir.backend.ImportImage(ctx, src, repo, platform, tag, message, r.Body, output, r.Form["changes"]) + + var ref reference.Named + if repo != "" { + var err error + ref, err = reference.ParseNormalizedNamed(repo) + if err != nil { + return errdefs.InvalidParameter(err) + } + if _, isCanonical := ref.(reference.Canonical); isCanonical { + return errdefs.InvalidParameter(errors.New("cannot import digest reference")) + } + + if tag != "" { + ref, err = reference.WithTag(ref, tag) + if err != nil { + return errdefs.InvalidParameter(err) + } + } else { + ref = reference.TagNameOnly(ref) + } + } + + if len(comment) == 0 { + comment = "Imported from " + src + } + + var layerReader io.ReadCloser + defer r.Body.Close() + if src == "-" { + layerReader = r.Body + } else { + if len(strings.Split(src, "://")) == 1 { + src = "http://" + src + } + u, err := url.Parse(src) + if err != nil { + return errdefs.InvalidParameter(err) + } + + resp, err := remotecontext.GetWithStatusError(u.String()) + if err != nil { + return err + } + output.Write(streamformatter.FormatStatus("", "Downloading from %s", u)) + progressOutput := streamformatter.NewJSONProgressOutput(output, true) + layerReader = progress.NewProgressReader(resp.Body, progressOutput, resp.ContentLength, "", "Importing") + defer layerReader.Close() + } + + var id image.ID + id, progressErr = ir.backend.ImportImage(ctx, ref, platform, comment, layerReader, r.Form["changes"]) + + if progressErr == nil { + output.Write(streamformatter.FormatStatus("", id.String())) + } } if progressErr != nil { if !output.Flushed() { diff --git a/daemon/image_service.go b/daemon/image_service.go index c29da5f20c..786a47d210 100644 --- a/daemon/image_service.go +++ b/daemon/image_service.go @@ -36,7 +36,7 @@ type ImageService interface { LogImageEventWithAttributes(imageID, refName, action string, attributes map[string]string) CountImages() int ImagesPrune(ctx context.Context, pruneFilters filters.Args) (*types.ImagesPruneReport, error) - ImportImage(ctx context.Context, src string, repository string, platform *v1.Platform, tag string, msg string, inConfig io.ReadCloser, outStream io.Writer, changes []string) error + ImportImage(ctx context.Context, ref reference.Named, platform *v1.Platform, msg string, layerReader io.Reader, changes []string) (image.ID, error) TagImage(imageName, repository, tag string) (string, error) TagImageWithReference(imageID image.ID, newTag reference.Named) error GetImage(ctx context.Context, refOrID string, options imagetype.GetImageOpts) (*image.Image, error) diff --git a/daemon/images/image_import.go b/daemon/images/image_import.go index e0d246b52e..101e4f0b96 100644 --- a/daemon/images/image_import.go +++ b/daemon/images/image_import.go @@ -4,102 +4,49 @@ import ( "context" "encoding/json" "io" - "net/http" - "net/url" - "strings" "time" "github.com/containerd/containerd/platforms" "github.com/docker/distribution/reference" "github.com/docker/docker/api/types/container" "github.com/docker/docker/builder/dockerfile" - "github.com/docker/docker/builder/remotecontext" "github.com/docker/docker/dockerversion" "github.com/docker/docker/errdefs" "github.com/docker/docker/image" "github.com/docker/docker/layer" "github.com/docker/docker/pkg/archive" - "github.com/docker/docker/pkg/progress" - "github.com/docker/docker/pkg/streamformatter" "github.com/docker/docker/pkg/system" specs "github.com/opencontainers/image-spec/specs-go/v1" - "github.com/pkg/errors" ) -// ImportImage imports an image, getting the archived layer data either from -// inConfig (if src is "-"), or from a URI specified in src. Progress output is -// written to outStream. Repository and tag names can optionally be given in -// the repo and tag arguments, respectively. -func (i *ImageService) ImportImage(ctx context.Context, src string, repository string, platform *specs.Platform, tag string, msg string, inConfig io.ReadCloser, outStream io.Writer, changes []string) error { - var ( - rc io.ReadCloser - resp *http.Response - newRef reference.Named - ) - - if repository != "" { - var err error - newRef, err = reference.ParseNormalizedNamed(repository) - if err != nil { - return errdefs.InvalidParameter(err) - } - if _, isCanonical := newRef.(reference.Canonical); isCanonical { - return errdefs.InvalidParameter(errors.New("cannot import digest reference")) - } - - if tag != "" { - newRef, err = reference.WithTag(newRef, tag) - if err != nil { - return errdefs.InvalidParameter(err) - } - } - } - - // Normalize platform - default to the operating system and architecture if not supplied. +// ImportImage imports an image, getting the archived layer data from layerReader. +// Uncompressed layer archive is passed to the layerStore and handled by the +// underlying graph driver. +// Image is tagged with the given reference. +// If the platform is nil, the default host platform is used. +// Message is used as the image's history comment. +// Image configuration is derived from the dockerfile instructions in changes. +func (i *ImageService) ImportImage(ctx context.Context, newRef reference.Named, platform *specs.Platform, msg string, layerReader io.Reader, changes []string) (image.ID, error) { if platform == nil { - p := platforms.DefaultSpec() - platform = &p + def := platforms.DefaultSpec() + platform = &def } if !system.IsOSSupported(platform.OS) { - return errdefs.InvalidParameter(system.ErrNotSupportedOperatingSystem) + return "", errdefs.InvalidParameter(system.ErrNotSupportedOperatingSystem) } + config, err := dockerfile.BuildFromConfig(ctx, &container.Config{}, changes, platform.OS) if err != nil { - return err - } - if src == "-" { - rc = inConfig - } else { - inConfig.Close() - if len(strings.Split(src, "://")) == 1 { - src = "http://" + src - } - u, err := url.Parse(src) - if err != nil { - return errdefs.InvalidParameter(err) - } - - resp, err = remotecontext.GetWithStatusError(u.String()) - if err != nil { - return err - } - outStream.Write(streamformatter.FormatStatus("", "Downloading from %s", u)) - progressOutput := streamformatter.NewJSONProgressOutput(outStream, true) - rc = progress.NewProgressReader(resp.Body, progressOutput, resp.ContentLength, "", "Importing") + return "", errdefs.InvalidParameter(err) } - defer rc.Close() - if len(msg) == 0 { - msg = "Imported from " + src - } - - inflatedLayerData, err := archive.DecompressStream(rc) + inflatedLayerData, err := archive.DecompressStream(layerReader) if err != nil { - return err + return "", err } l, err := i.layerStore.Register(inflatedLayerData, "") if err != nil { - return err + return "", err } defer layer.ReleaseAndLog(i.layerStore, l) @@ -124,22 +71,20 @@ func (i *ImageService) ImportImage(ctx context.Context, src string, repository s }}, }) if err != nil { - return err + return "", err } id, err := i.imageStore.Create(imgConfig) if err != nil { - return err + return "", err } - // FIXME: connect with commit code and call refstore directly if newRef != nil { if err := i.TagImageWithReference(id, newRef); err != nil { - return err + return "", err } } i.LogImageEvent(id.String(), id.String(), "import") - outStream.Write(streamformatter.FormatStatus("", id.String())) - return nil + return id, nil }