daemon/import: Extract common logic to api

Extract logic that would need to be duplicated in both implementations
of ImageService.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
This commit is contained in:
Paweł Gronowski 2022-12-12 16:30:04 +01:00
parent b139a7636f
commit 28327f10a2
No known key found for this signature in database
GPG key ID: B85EFCFE26DEF92A
4 changed files with 83 additions and 79 deletions

View file

@ -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
}

View file

@ -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() {

View file

@ -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)

View file

@ -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
}