diff --git a/daemon/containerd/image_pull.go b/daemon/containerd/image_pull.go index 457c4cd97f..6b751cb269 100644 --- a/daemon/containerd/image_pull.go +++ b/daemon/containerd/image_pull.go @@ -2,6 +2,7 @@ package containerd import ( "context" + "fmt" "io" "github.com/containerd/containerd" @@ -12,7 +13,7 @@ import ( "github.com/containerd/log" "github.com/distribution/reference" "github.com/docker/docker/api/types/events" - "github.com/docker/docker/api/types/registry" + registrytypes "github.com/docker/docker/api/types/registry" "github.com/docker/docker/distribution" "github.com/docker/docker/errdefs" "github.com/docker/docker/internal/compatcontext" @@ -21,8 +22,43 @@ import ( ocispec "github.com/opencontainers/image-spec/specs-go/v1" ) -// PullImage initiates a pull operation. ref is the image to pull. -func (i *ImageService) PullImage(ctx context.Context, ref reference.Named, platform *ocispec.Platform, metaHeaders map[string][]string, authConfig *registry.AuthConfig, outStream io.Writer) error { +// PullImage initiates a pull operation. baseRef is the image to pull. +// If reference is not tagged, all tags are pulled. +func (i *ImageService) PullImage(ctx context.Context, baseRef reference.Named, platform *ocispec.Platform, metaHeaders map[string][]string, authConfig *registrytypes.AuthConfig, outStream io.Writer) error { + out := streamformatter.NewJSONProgressOutput(outStream, false) + + if tagged, ok := baseRef.(reference.NamedTagged); ok { + return i.pullTag(ctx, tagged, platform, metaHeaders, authConfig, out) + } + + tags, err := distribution.Tags(ctx, baseRef, &distribution.Config{ + RegistryService: i.registryService, + MetaHeaders: metaHeaders, + AuthConfig: authConfig, + }) + if err != nil { + return err + } + + for _, tag := range tags { + ref, err := reference.WithTag(baseRef, tag) + if err != nil { + log.G(ctx).WithFields(log.Fields{ + "tag": tag, + "baseRef": baseRef, + }).Warn("invalid tag, won't pull") + continue + } + + if err := i.pullTag(ctx, ref, platform, metaHeaders, authConfig, out); err != nil { + return fmt.Errorf("error pulling %s: %w", ref, err) + } + } + + return nil +} + +func (i *ImageService) pullTag(ctx context.Context, ref reference.NamedTagged, platform *ocispec.Platform, metaHeaders map[string][]string, authConfig *registrytypes.AuthConfig, out progress.Output) error { var opts []containerd.RemoteOpt if platform != nil { opts = append(opts, containerd.WithPlatform(platforms.Format(*platform))) @@ -49,7 +85,6 @@ func (i *ImageService) PullImage(ctx context.Context, ref reference.Named, platf }) opts = append(opts, containerd.WithImageHandler(h)) - out := streamformatter.NewJSONProgressOutput(outStream, false) pp := pullProgress{store: i.client.ContentStore(), showExists: true} finishProgress := jobs.showProgress(ctx, out, pp) diff --git a/daemon/containerd/resolver.go b/daemon/containerd/resolver.go index 267179d5fc..23568f513d 100644 --- a/daemon/containerd/resolver.go +++ b/daemon/containerd/resolver.go @@ -31,7 +31,7 @@ func (i *ImageService) newResolverFromAuthConfig(ctx context.Context, authConfig }), tracker } -func hostsWrapper(hostsFn docker.RegistryHosts, optAuthConfig *registrytypes.AuthConfig, regService RegistryConfigProvider) docker.RegistryHosts { +func hostsWrapper(hostsFn docker.RegistryHosts, optAuthConfig *registrytypes.AuthConfig, regService registryResolver) docker.RegistryHosts { var authorizer docker.Authorizer if optAuthConfig != nil { authorizer = authorizerFromAuthConfig(*optAuthConfig) diff --git a/daemon/containerd/service.go b/daemon/containerd/service.go index 70990a39a2..94bf2215b3 100644 --- a/daemon/containerd/service.go +++ b/daemon/containerd/service.go @@ -30,16 +30,18 @@ type ImageService struct { containers container.Store snapshotter string registryHosts docker.RegistryHosts - registryService RegistryConfigProvider + registryService registryResolver eventsService *daemonevents.Events pruneRunning atomic.Bool refCountMounter snapshotter.Mounter idMapping idtools.IdentityMapping } -type RegistryConfigProvider interface { +type registryResolver interface { IsInsecureRegistry(host string) bool ResolveRepository(name reference.Named) (*registry.RepositoryInfo, error) + LookupPullEndpoints(hostname string) ([]registry.APIEndpoint, error) + LookupPushEndpoints(hostname string) ([]registry.APIEndpoint, error) } type ImageServiceConfig struct { @@ -47,7 +49,7 @@ type ImageServiceConfig struct { Containers container.Store Snapshotter string RegistryHosts docker.RegistryHosts - Registry RegistryConfigProvider + Registry registryResolver EventsService *daemonevents.Events RefCountMounter snapshotter.Mounter IDMapping idtools.IdentityMapping