diff --git a/cli/command/image/inspect.go b/cli/command/image/inspect.go index ece81cae719a..684e4d782362 100644 --- a/cli/command/image/inspect.go +++ b/cli/command/image/inspect.go @@ -7,6 +7,7 @@ import ( "bytes" "context" + "github.com/containerd/platforms" "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command/completion" @@ -14,12 +15,14 @@ import ( flagsHelper "github.com/docker/cli/cli/flags" "github.com/docker/docker/api/types/image" "github.com/docker/docker/client" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/spf13/cobra" ) type inspectOptions struct { - format string - refs []string + format string + refs []string + platform string } // newInspectCommand creates a new cobra.Command for `docker image inspect` @@ -39,14 +42,37 @@ func newInspectCommand(dockerCli command.Cli) *cobra.Command { flags := cmd.Flags() flags.StringVarP(&opts.format, "format", "f", "", flagsHelper.InspectFormatHelp) + + // Don't default to DOCKER_DEFAULT_PLATFORM env variable, always default to + // inspecting the image as-is. This also avoids forcing the platform selection + // on older APIs which don't support it. + flags.StringVar(&opts.platform, "platform", "", `Inspect a specific platform of the multi-platform image. +If the image or the server is not multi-platform capable, the command will error out if the platform does not match. +'os[/arch[/variant]]': Explicit platform (eg. linux/amd64)`) + flags.SetAnnotation("platform", "version", []string{"1.49"}) + + _ = cmd.RegisterFlagCompletionFunc("platform", completion.Platforms) return cmd } func runInspect(ctx context.Context, dockerCLI command.Cli, opts inspectOptions) error { + var platform *ocispec.Platform + if opts.platform != "" { + p, err := platforms.Parse(opts.platform) + if err != nil { + return err + } + platform = &p + } + apiClient := dockerCLI.Client() return inspect.Inspect(dockerCLI.Out(), opts.refs, opts.format, func(ref string) (any, []byte, error) { var buf bytes.Buffer - resp, err := apiClient.ImageInspect(ctx, ref, client.ImageInspectWithRawResponse(&buf)) + _ = platform + resp, err := apiClient.ImageInspect(ctx, ref, + client.ImageInspectWithRawResponse(&buf), + client.ImageInspectWithPlatform(platform), + ) if err != nil { return image.InspectResponse{}, nil, err } diff --git a/docs/reference/commandline/image_inspect.md b/docs/reference/commandline/image_inspect.md index 1f59b0014d1b..74da0bee2c05 100644 --- a/docs/reference/commandline/image_inspect.md +++ b/docs/reference/commandline/image_inspect.md @@ -8,6 +8,7 @@ Display detailed information on one or more images | Name | Type | Default | Description | |:-----------------|:---------|:--------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `-f`, `--format` | `string` | | Format output using a custom template:
'json': Print in JSON format
'TEMPLATE': Print output using the given Go template.
Refer to https://docs.docker.com/go/formatting/ for more information about formatting output with templates | +| `--platform` | `string` | | Inspect a specific platform of the multi-platform image.
If the image or the server is not multi-platform capable, the command will error out if the platform does not match.
'os[/arch[/variant]]': Explicit platform (eg. linux/amd64) | diff --git a/vendor.mod b/vendor.mod index c97dadb07900..8b2aecc0df8e 100644 --- a/vendor.mod +++ b/vendor.mod @@ -6,6 +6,8 @@ module github.com/docker/cli go 1.23.0 +replace github.com/docker/docker => github.com/vvoland/moby v20.10.16-0.20250314163427-17840c8adc30+incompatible + require ( dario.cat/mergo v1.0.1 github.com/containerd/platforms v1.0.0-rc.1 diff --git a/vendor.sum b/vendor.sum index 7dd39222aca2..05d939c6488d 100644 --- a/vendor.sum +++ b/vendor.sum @@ -51,8 +51,6 @@ github.com/docker/cli-docs-tool v0.9.0/go.mod h1:ClrwlNW+UioiRyH9GiAOe1o3J/TsY3T github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v28.0.1+incompatible h1:FCHjSRdXhNRFjlHMTv4jUNlIBbTeRjrWfeFuJp7jpo0= -github.com/docker/docker v28.0.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.9.2 h1:50JF7ADQiHdAVBRtg/vy883Y4U5+5GmPOBNtUU+X+6A= github.com/docker/docker-credential-helpers v0.9.2/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo= github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0= @@ -269,6 +267,8 @@ github.com/theupdateframework/notary v0.7.1-0.20210315103452-bf96a202a09a h1:tlJ github.com/theupdateframework/notary v0.7.1-0.20210315103452-bf96a202a09a/go.mod h1:Y94A6rPp2OwNfP/7vmf8O2xx2IykP8pPXQ1DLouGnEw= github.com/tonistiigi/go-rosetta v0.0.0-20220804170347-3f4430f2d346 h1:TvtdmeYsYEij78hS4oxnwikoiLdIrgav3BA+CbhaDAI= github.com/tonistiigi/go-rosetta v0.0.0-20220804170347-3f4430f2d346/go.mod h1:xKQhd7snlzKFuUi1taTGWjpRE8iFTA06DeacYi3CVFQ= +github.com/vvoland/moby v20.10.16-0.20250314163427-17840c8adc30+incompatible h1:opFk89LW5aZllunsKB+iR0yjfwCVsGjKpBBmVcvhW3o= +github.com/vvoland/moby v20.10.16-0.20250314163427-17840c8adc30+incompatible/go.mod h1:nLN96xVmxZq8CPEl0UgxMpO/G2e8MQtjhAdlRDUdBi0= github.com/weppos/publicsuffix-go v0.15.1-0.20210511084619-b1f36a2d6c0b h1:FsyNrX12e5BkplJq7wKOLk0+C6LZ+KGXvuEcKUYm5ss= github.com/weppos/publicsuffix-go v0.15.1-0.20210511084619-b1f36a2d6c0b/go.mod h1:HYux0V0Zi04bHNwOHy4cXJVz/TQjYonnF6aoYhj+3QE= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= diff --git a/vendor/github.com/docker/docker/api/common.go b/vendor/github.com/docker/docker/api/common.go index 2c62cd4032e4..d75c43d7c16d 100644 --- a/vendor/github.com/docker/docker/api/common.go +++ b/vendor/github.com/docker/docker/api/common.go @@ -3,7 +3,7 @@ package api // import "github.com/docker/docker/api" // Common constants for daemon and client. const ( // DefaultVersion of the current REST API. - DefaultVersion = "1.48" + DefaultVersion = "1.49" // MinSupportedAPIVersion is the minimum API version that can be supported // by the API server, specified as "major.minor". Note that the daemon diff --git a/vendor/github.com/docker/docker/api/swagger.yaml b/vendor/github.com/docker/docker/api/swagger.yaml index a4881f951e3a..646032d6e0ef 100644 --- a/vendor/github.com/docker/docker/api/swagger.yaml +++ b/vendor/github.com/docker/docker/api/swagger.yaml @@ -5508,8 +5508,11 @@ definitions: com.example.some-other-label: "some-other-value" Data: description: | - Base64-url-safe-encoded ([RFC 4648](https://tools.ietf.org/html/rfc4648#section-5)) - data to store as secret. + Data is the data to store as a secret, formatted as a Base64-url-safe-encoded + ([RFC 4648](https://tools.ietf.org/html/rfc4648#section-5)) string. + It must be empty if the Driver field is set, in which case the data is + loaded from an external secret store. The maximum allowed size is 500KB, + as defined in [MaxSecretSize](https://pkg.go.dev/github.com/moby/swarmkit/v2@v2.0.0-20250103191802-8c1959736554/api/validation#MaxSecretSize). This field is only used to _create_ a secret, and is not returned by other endpoints. @@ -5560,8 +5563,9 @@ definitions: type: "string" Data: description: | - Base64-url-safe-encoded ([RFC 4648](https://tools.ietf.org/html/rfc4648#section-5)) - config data. + Data is the data to store as a config, formatted as a Base64-url-safe-encoded + ([RFC 4648](https://tools.ietf.org/html/rfc4648#section-5)) string. + The maximum allowed size is 1000KB, as defined in [MaxConfigSize](https://pkg.go.dev/github.com/moby/swarmkit/v2@v2.0.0-20250103191802-8c1959736554/manager/controlapi#MaxConfigSize). type: "string" Templating: description: | diff --git a/vendor/github.com/docker/docker/api/types/image/image_inspect.go b/vendor/github.com/docker/docker/api/types/image/image_inspect.go index 78e81f052c67..40d1f97a315d 100644 --- a/vendor/github.com/docker/docker/api/types/image/image_inspect.go +++ b/vendor/github.com/docker/docker/api/types/image/image_inspect.go @@ -128,11 +128,12 @@ type InspectResponse struct { // compatibility. Descriptor *ocispec.Descriptor `json:"Descriptor,omitempty"` - // Manifests is a list of image manifests available in this image. It + // Manifests is a list of image manifests available in this image. It // provides a more detailed view of the platform-specific image manifests or // other image-attached data like build attestations. // - // Only available if the daemon provides a multi-platform image store. + // Only available if the daemon provides a multi-platform image store, the client + // requests manifests AND does not request a specific platform. // // WARNING: This is experimental and may change at any time without any backward // compatibility. diff --git a/vendor/github.com/docker/docker/api/types/image/opts.go b/vendor/github.com/docker/docker/api/types/image/opts.go index 919510fe37b2..62e100dd6734 100644 --- a/vendor/github.com/docker/docker/api/types/image/opts.go +++ b/vendor/github.com/docker/docker/api/types/image/opts.go @@ -106,6 +106,15 @@ type LoadOptions struct { type InspectOptions struct { // Manifests returns the image manifests. Manifests bool + + // Platform selects the specific platform of a multi-platform image to inspect. + // + // If the server is not multi-platform capable, this option will make the + // server return an error if the actual image platform doesn't match this + // platform. + // + // This option is only available for API version 1.49 and up. + Platform *ocispec.Platform } // SaveOptions holds parameters to save images. diff --git a/vendor/github.com/docker/docker/api/types/registry/registry.go b/vendor/github.com/docker/docker/api/types/registry/registry.go index b0a4d604f5f8..8117cb09e7ee 100644 --- a/vendor/github.com/docker/docker/api/types/registry/registry.go +++ b/vendor/github.com/docker/docker/api/types/registry/registry.go @@ -49,15 +49,17 @@ func (ipnet *NetIPNet) MarshalJSON() ([]byte, error) { } // UnmarshalJSON sets the IPNet from a byte array of JSON -func (ipnet *NetIPNet) UnmarshalJSON(b []byte) (err error) { +func (ipnet *NetIPNet) UnmarshalJSON(b []byte) error { var ipnetStr string - if err = json.Unmarshal(b, &ipnetStr); err == nil { - var cidr *net.IPNet - if _, cidr, err = net.ParseCIDR(ipnetStr); err == nil { - *ipnet = NetIPNet(*cidr) - } + if err := json.Unmarshal(b, &ipnetStr); err != nil { + return err } - return + _, cidr, err := net.ParseCIDR(ipnetStr) + if err != nil { + return err + } + *ipnet = NetIPNet(*cidr) + return nil } // IndexInfo contains information about a registry diff --git a/vendor/github.com/docker/docker/api/types/swarm/config.go b/vendor/github.com/docker/docker/api/types/swarm/config.go index 16202ccce615..f9a65187ffaf 100644 --- a/vendor/github.com/docker/docker/api/types/swarm/config.go +++ b/vendor/github.com/docker/docker/api/types/swarm/config.go @@ -12,6 +12,12 @@ type Config struct { // ConfigSpec represents a config specification from a config in swarm type ConfigSpec struct { Annotations + + // Data is the data to store as a config. + // + // The maximum allowed size is 1000KB, as defined in [MaxConfigSize]. + // + // [MaxConfigSize]: https://pkg.go.dev/github.com/moby/swarmkit/v2@v2.0.0-20250103191802-8c1959736554/manager/controlapi#MaxConfigSize Data []byte `json:",omitempty"` // Templating controls whether and how to evaluate the config payload as diff --git a/vendor/github.com/docker/docker/api/types/swarm/secret.go b/vendor/github.com/docker/docker/api/types/swarm/secret.go index d5213ec981c3..aeb5bb54ad1a 100644 --- a/vendor/github.com/docker/docker/api/types/swarm/secret.go +++ b/vendor/github.com/docker/docker/api/types/swarm/secret.go @@ -12,8 +12,22 @@ type Secret struct { // SecretSpec represents a secret specification from a secret in swarm type SecretSpec struct { Annotations - Data []byte `json:",omitempty"` - Driver *Driver `json:",omitempty"` // name of the secrets driver used to fetch the secret's value from an external secret store + + // Data is the data to store as a secret. It must be empty if a + // [Driver] is used, in which case the data is loaded from an external + // secret store. The maximum allowed size is 500KB, as defined in + // [MaxSecretSize]. + // + // This field is only used to create the secret, and is not returned + // by other endpoints. + // + // [MaxSecretSize]: https://pkg.go.dev/github.com/moby/swarmkit/v2@v2.0.0-20250103191802-8c1959736554/api/validation#MaxSecretSize + Data []byte `json:",omitempty"` + + // Driver is the name of the secrets driver used to fetch the secret's + // value from an external secret store. If not set, the default built-in + // store is used. + Driver *Driver `json:",omitempty"` // Templating controls whether and how to evaluate the secret payload as // a template. If it is not set, no templating is used. diff --git a/vendor/github.com/docker/docker/client/container_create.go b/vendor/github.com/docker/docker/client/container_create.go index 9b8616f7614e..9bb106f77637 100644 --- a/vendor/github.com/docker/docker/client/container_create.go +++ b/vendor/github.com/docker/docker/client/container_create.go @@ -3,6 +3,7 @@ package client // import "github.com/docker/docker/client" import ( "context" "encoding/json" + "errors" "net/url" "path" "sort" @@ -54,6 +55,19 @@ func (cli *Client) ContainerCreate(ctx context.Context, config *container.Config // When using API under 1.42, the Linux daemon doesn't respect the ConsoleSize hostConfig.ConsoleSize = [2]uint{0, 0} } + if versions.LessThan(cli.ClientVersion(), "1.44") { + for _, m := range hostConfig.Mounts { + if m.BindOptions != nil { + // ReadOnlyNonRecursive can be safely ignored when API < 1.44 + if m.BindOptions.ReadOnlyForceRecursive { + return response, errors.New("bind-recursive=readonly requires API v1.44 or later") + } + if m.BindOptions.NonRecursive && versions.LessThan(cli.ClientVersion(), "1.40") { + return response, errors.New("bind-recursive=disabled requires API v1.40 or later") + } + } + } + } hostConfig.CapAdd = normalizeCapabilities(hostConfig.CapAdd) hostConfig.CapDrop = normalizeCapabilities(hostConfig.CapDrop) diff --git a/vendor/github.com/docker/docker/client/image_inspect.go b/vendor/github.com/docker/docker/client/image_inspect.go index 11611954675e..644439906e22 100644 --- a/vendor/github.com/docker/docker/client/image_inspect.go +++ b/vendor/github.com/docker/docker/client/image_inspect.go @@ -24,6 +24,10 @@ func (cli *Client) ImageInspect(ctx context.Context, imageID string, inspectOpts } } + if opts.apiOptions.Manifests && opts.apiOptions.Platform != nil { + return image.InspectResponse{}, fmt.Errorf("manifests and platform options cannot both be set") + } + query := url.Values{} if opts.apiOptions.Manifests { if err := cli.NewVersionError(ctx, "1.48", "manifests"); err != nil { @@ -32,6 +36,17 @@ func (cli *Client) ImageInspect(ctx context.Context, imageID string, inspectOpts query.Set("manifests", "1") } + if opts.apiOptions.Platform != nil { + if err := cli.NewVersionError(ctx, "1.48", "platform"); err != nil { + return image.InspectResponse{}, err + } + platform, err := encodePlatform(opts.apiOptions.Platform) + if err != nil { + return image.InspectResponse{}, err + } + query.Set("platform", platform) + } + resp, err := cli.get(ctx, "/images/"+imageID+"/json", query, nil) defer ensureReaderClosed(resp) if err != nil { diff --git a/vendor/github.com/docker/docker/client/image_inspect_opts.go b/vendor/github.com/docker/docker/client/image_inspect_opts.go index 2607f36789c6..5589d95748f2 100644 --- a/vendor/github.com/docker/docker/client/image_inspect_opts.go +++ b/vendor/github.com/docker/docker/client/image_inspect_opts.go @@ -4,6 +4,7 @@ import ( "bytes" "github.com/docker/docker/api/types/image" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" ) // ImageInspectOption is a type representing functional options for the image inspect operation. @@ -36,6 +37,20 @@ func ImageInspectWithManifests(manifests bool) ImageInspectOption { }) } +// ImageInspectWithPlatform sets platform API option for the image inspect operation. +// This option is only available for API version 1.49 and up. +// With this option set, the image inspect operation will return information for the +// specified platform variant of the multi-platform image. +// If the server is not multi-platform capable, this option will make the +// server return an error if the actual image platform doesn't match this +// platform. +func ImageInspectWithPlatform(platform *ocispec.Platform) ImageInspectOption { + return imageInspectOptionFunc(func(clientOpts *imageInspectOpts) error { + clientOpts.apiOptions.Platform = platform + return nil + }) +} + // ImageInspectWithAPIOpts sets the API options for the image inspect operation. func ImageInspectWithAPIOpts(opts image.InspectOptions) ImageInspectOption { return imageInspectOptionFunc(func(clientOpts *imageInspectOpts) error { diff --git a/vendor/github.com/docker/docker/client/service_create.go b/vendor/github.com/docker/docker/client/service_create.go index fb12dd5b5959..54c03b138948 100644 --- a/vendor/github.com/docker/docker/client/service_create.go +++ b/vendor/github.com/docker/docker/client/service_create.go @@ -37,6 +37,11 @@ func (cli *Client) ServiceCreate(ctx context.Context, service swarm.ServiceSpec, if err := validateServiceSpec(service); err != nil { return response, err } + if versions.LessThan(cli.version, "1.30") { + if err := validateAPIVersion(service, cli.version); err != nil { + return response, err + } + } // ensure that the image is tagged var resolveWarning string @@ -191,3 +196,18 @@ func validateServiceSpec(s swarm.ServiceSpec) error { } return nil } + +func validateAPIVersion(c swarm.ServiceSpec, apiVersion string) error { + for _, m := range c.TaskTemplate.ContainerSpec.Mounts { + if m.BindOptions != nil { + if m.BindOptions.NonRecursive && versions.LessThan(apiVersion, "1.40") { + return errors.Errorf("bind-recursive=disabled requires API v1.40 or later") + } + // ReadOnlyNonRecursive can be safely ignored when API < 1.44 + if m.BindOptions.ReadOnlyForceRecursive && versions.LessThan(apiVersion, "1.44") { + return errors.Errorf("bind-recursive=readonly requires API v1.44 or later") + } + } + } + return nil +} diff --git a/vendor/github.com/docker/docker/pkg/archive/archive.go b/vendor/github.com/docker/docker/pkg/archive/archive.go index b05780406b0c..9bbb11c197be 100644 --- a/vendor/github.com/docker/docker/pkg/archive/archive.go +++ b/vendor/github.com/docker/docker/pkg/archive/archive.go @@ -236,11 +236,9 @@ func (r *readCloserWrapper) Close() error { return nil } -var ( - bufioReader32KPool = &sync.Pool{ - New: func() interface{} { return bufio.NewReaderSize(nil, 32*1024) }, - } -) +var bufioReader32KPool = &sync.Pool{ + New: func() interface{} { return bufio.NewReaderSize(nil, 32*1024) }, +} type bufferedReader struct { buf *bufio.Reader @@ -252,17 +250,17 @@ func newBufferedReader(r io.Reader) *bufferedReader { return &bufferedReader{buf} } -func (r *bufferedReader) Read(p []byte) (n int, err error) { +func (r *bufferedReader) Read(p []byte) (int, error) { if r.buf == nil { return 0, io.EOF } - n, err = r.buf.Read(p) + n, err := r.buf.Read(p) if err == io.EOF { r.buf.Reset(nil) bufioReader32KPool.Put(r.buf) r.buf = nil } - return + return n, err } func (r *bufferedReader) Peek(n int) ([]byte, error) { @@ -428,7 +426,7 @@ func ReplaceFileTarWrapper(inputTarStream io.ReadCloser, mods map[string]TarModi pipeWriter.CloseWithError(err) return } - if _, err := copyWithBuffer(tarWriter, tarReader); err != nil { + if err := copyWithBuffer(tarWriter, tarReader); err != nil { pipeWriter.CloseWithError(err) return } @@ -731,7 +729,7 @@ func (ta *tarAppender) addTarFile(path, name string) error { return err } - _, err = copyWithBuffer(ta.TarWriter, file) + err = copyWithBuffer(ta.TarWriter, file) file.Close() if err != nil { return err @@ -778,11 +776,11 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, o if err != nil { return err } - if _, err := copyWithBuffer(file, reader); err != nil { - file.Close() + if err := copyWithBuffer(file, reader); err != nil { + _ = file.Close() return err } - file.Close() + _ = file.Close() case tar.TypeBlock, tar.TypeChar: if inUserns { // cannot create devices in a userns @@ -1438,7 +1436,7 @@ func (archiver *Archiver) CopyFileWithTar(src, dst string) (err error) { if err := tw.WriteHeader(hdr); err != nil { return err } - if _, err := copyWithBuffer(tw, srcF); err != nil { + if err := copyWithBuffer(tw, srcF); err != nil { return err } return nil diff --git a/vendor/github.com/docker/docker/pkg/archive/archive_linux.go b/vendor/github.com/docker/docker/pkg/archive/archive_linux.go index 631d2e3c5b72..7b6c3e02b643 100644 --- a/vendor/github.com/docker/docker/pkg/archive/archive_linux.go +++ b/vendor/github.com/docker/docker/pkg/archive/archive_linux.go @@ -20,7 +20,7 @@ func getWhiteoutConverter(format WhiteoutFormat) tarWhiteoutConverter { type overlayWhiteoutConverter struct{} -func (overlayWhiteoutConverter) ConvertWrite(hdr *tar.Header, path string, fi os.FileInfo) (wo *tar.Header, err error) { +func (overlayWhiteoutConverter) ConvertWrite(hdr *tar.Header, path string, fi os.FileInfo) (wo *tar.Header, _ error) { // convert whiteouts to AUFS format if fi.Mode()&os.ModeCharDevice != 0 && hdr.Devmajor == 0 && hdr.Devminor == 0 { // we just rename the file and make it normal @@ -31,38 +31,41 @@ func (overlayWhiteoutConverter) ConvertWrite(hdr *tar.Header, path string, fi os hdr.Size = 0 } - if fi.Mode()&os.ModeDir != 0 { - opaqueXattrName := "trusted.overlay.opaque" - if userns.RunningInUserNS() { - opaqueXattrName = "user.overlay.opaque" - } + if fi.Mode()&os.ModeDir == 0 { + // FIXME(thaJeztah): return a sentinel error instead of nil, nil + return nil, nil + } - // convert opaque dirs to AUFS format by writing an empty file with the prefix - opaque, err := lgetxattr(path, opaqueXattrName) - if err != nil { - return nil, err - } - if len(opaque) == 1 && opaque[0] == 'y' { - delete(hdr.PAXRecords, paxSchilyXattr+opaqueXattrName) - - // create a header for the whiteout file - // it should inherit some properties from the parent, but be a regular file - wo = &tar.Header{ - Typeflag: tar.TypeReg, - Mode: hdr.Mode & int64(os.ModePerm), - Name: filepath.Join(hdr.Name, WhiteoutOpaqueDir), // #nosec G305 -- An archive is being created, not extracted. - Size: 0, - Uid: hdr.Uid, - Uname: hdr.Uname, - Gid: hdr.Gid, - Gname: hdr.Gname, - AccessTime: hdr.AccessTime, - ChangeTime: hdr.ChangeTime, - } - } + opaqueXattrName := "trusted.overlay.opaque" + if userns.RunningInUserNS() { + opaqueXattrName = "user.overlay.opaque" } - return + // convert opaque dirs to AUFS format by writing an empty file with the prefix + opaque, err := lgetxattr(path, opaqueXattrName) + if err != nil { + return nil, err + } + if len(opaque) != 1 || opaque[0] != 'y' { + // FIXME(thaJeztah): return a sentinel error instead of nil, nil + return nil, nil + } + delete(hdr.PAXRecords, paxSchilyXattr+opaqueXattrName) + + // create a header for the whiteout file + // it should inherit some properties from the parent, but be a regular file + return &tar.Header{ + Typeflag: tar.TypeReg, + Mode: hdr.Mode & int64(os.ModePerm), + Name: filepath.Join(hdr.Name, WhiteoutOpaqueDir), // #nosec G305 -- An archive is being created, not extracted. + Size: 0, + Uid: hdr.Uid, + Uname: hdr.Uname, + Gid: hdr.Gid, + Gname: hdr.Gname, + AccessTime: hdr.AccessTime, + ChangeTime: hdr.ChangeTime, + }, nil } func (c overlayWhiteoutConverter) ConvertRead(hdr *tar.Header, path string) (bool, error) { diff --git a/vendor/github.com/docker/docker/pkg/archive/archive_unix.go b/vendor/github.com/docker/docker/pkg/archive/archive_unix.go index 9c70d1789f12..bc6b25ae07ba 100644 --- a/vendor/github.com/docker/docker/pkg/archive/archive_unix.go +++ b/vendor/github.com/docker/docker/pkg/archive/archive_unix.go @@ -73,14 +73,13 @@ func statUnix(fi os.FileInfo, hdr *tar.Header) error { return nil } -func getInodeFromStat(stat interface{}) (inode uint64, err error) { +func getInodeFromStat(stat interface{}) (uint64, error) { s, ok := stat.(*syscall.Stat_t) - - if ok { - inode = s.Ino + if !ok { + // FIXME(thaJeztah): this should likely return an error; see https://github.com/moby/moby/pull/49493#discussion_r1979152897 + return 0, nil } - - return + return s.Ino, nil } func getFileUIDGID(stat interface{}) (idtools.Identity, error) { diff --git a/vendor/github.com/docker/docker/pkg/archive/archive_windows.go b/vendor/github.com/docker/docker/pkg/archive/archive_windows.go index 031608162f9f..fd2546eab73a 100644 --- a/vendor/github.com/docker/docker/pkg/archive/archive_windows.go +++ b/vendor/github.com/docker/docker/pkg/archive/archive_windows.go @@ -48,9 +48,9 @@ func setHeaderForSpecialDevice(hdr *tar.Header, name string, stat interface{}) ( return } -func getInodeFromStat(stat interface{}) (inode uint64, err error) { +func getInodeFromStat(stat interface{}) (uint64, error) { // do nothing. no notion of Inode in stat on Windows - return + return 0, nil } // handleTarTypeBlockCharFifo is an OS-specific helper function used by diff --git a/vendor/github.com/docker/docker/pkg/archive/changes.go b/vendor/github.com/docker/docker/pkg/archive/changes.go index 79c810a6819b..1c0509d039e2 100644 --- a/vendor/github.com/docker/docker/pkg/archive/changes.go +++ b/vendor/github.com/docker/docker/pkg/archive/changes.go @@ -83,7 +83,7 @@ func aufsMetadataSkip(path string) (skip bool, err error) { if err != nil { skip = true } - return + return skip, err } func aufsDeletedFile(root, path string, fi os.FileInfo) (string, error) { diff --git a/vendor/github.com/docker/docker/pkg/archive/copy.go b/vendor/github.com/docker/docker/pkg/archive/copy.go index cddf18ecdb8b..cae0173def2a 100644 --- a/vendor/github.com/docker/docker/pkg/archive/copy.go +++ b/vendor/github.com/docker/docker/pkg/archive/copy.go @@ -25,11 +25,11 @@ var copyPool = sync.Pool{ New: func() interface{} { s := make([]byte, 32*1024); return &s }, } -func copyWithBuffer(dst io.Writer, src io.Reader) (written int64, err error) { +func copyWithBuffer(dst io.Writer, src io.Reader) error { buf := copyPool.Get().(*[]byte) - written, err = io.CopyBuffer(dst, src, *buf) + _, err := io.CopyBuffer(dst, src, *buf) copyPool.Put(buf) - return + return err } // PreserveTrailingDotOrSeparator returns the given cleaned path (after @@ -105,13 +105,13 @@ func TarResource(sourceInfo CopyInfo) (content io.ReadCloser, err error) { // TarResourceRebase is like TarResource but renames the first path element of // items in the resulting tar archive to match the given rebaseName if not "". -func TarResourceRebase(sourcePath, rebaseName string) (content io.ReadCloser, err error) { +func TarResourceRebase(sourcePath, rebaseName string) (content io.ReadCloser, _ error) { sourcePath = normalizePath(sourcePath) - if _, err = os.Lstat(sourcePath); err != nil { + if _, err := os.Lstat(sourcePath); err != nil { // Catches the case where the source does not exist or is not a // directory if asserted to be a directory, as this also causes an // error. - return + return nil, err } // Separate the source path between its directory and @@ -442,11 +442,12 @@ func CopyTo(content io.Reader, srcInfo CopyInfo, dstPath string) error { // whether to follow symbol link or not, if followLink is true, resolvedPath will return // link target of any symbol link file, else it will only resolve symlink of directory // but return symbol link file itself without resolving. -func ResolveHostSourcePath(path string, followLink bool) (resolvedPath, rebaseName string, err error) { +func ResolveHostSourcePath(path string, followLink bool) (resolvedPath, rebaseName string, _ error) { if followLink { + var err error resolvedPath, err = filepath.EvalSymlinks(path) if err != nil { - return + return "", "", err } resolvedPath, rebaseName = GetRebaseName(path, resolvedPath) @@ -454,10 +455,9 @@ func ResolveHostSourcePath(path string, followLink bool) (resolvedPath, rebaseNa dirPath, basePath := filepath.Split(path) // if not follow symbol link, then resolve symbol link of parent dir - var resolvedDirPath string - resolvedDirPath, err = filepath.EvalSymlinks(dirPath) + resolvedDirPath, err := filepath.EvalSymlinks(dirPath) if err != nil { - return + return "", "", err } // resolvedDirPath will have been cleaned (no trailing path separators) so // we can manually join it with the base path element. diff --git a/vendor/github.com/docker/docker/pkg/archive/time_nonwindows.go b/vendor/github.com/docker/docker/pkg/archive/time_nonwindows.go index 8ce83bd0b50e..5bfdfa2f17e8 100644 --- a/vendor/github.com/docker/docker/pkg/archive/time_nonwindows.go +++ b/vendor/github.com/docker/docker/pkg/archive/time_nonwindows.go @@ -17,12 +17,13 @@ func chtimes(name string, atime time.Time, mtime time.Time) error { return os.Chtimes(name, atime, mtime) } -func timeToTimespec(time time.Time) (ts unix.Timespec) { +func timeToTimespec(time time.Time) unix.Timespec { if time.IsZero() { // Return UTIME_OMIT special value - ts.Sec = 0 - ts.Nsec = (1 << 30) - 2 - return + return unix.Timespec{ + Sec: 0, + Nsec: (1 << 30) - 2, + } } return unix.NsecToTimespec(time.UnixNano()) } diff --git a/vendor/github.com/docker/docker/pkg/archive/wrap.go b/vendor/github.com/docker/docker/pkg/archive/wrap.go index 903befd76301..f8a97254eede 100644 --- a/vendor/github.com/docker/docker/pkg/archive/wrap.go +++ b/vendor/github.com/docker/docker/pkg/archive/wrap.go @@ -45,8 +45,8 @@ func Generate(input ...string) (io.Reader, error) { return buf, nil } -func parseStringPairs(input ...string) (output [][2]string) { - output = make([][2]string, 0, len(input)/2+1) +func parseStringPairs(input ...string) [][2]string { + output := make([][2]string, 0, len(input)/2+1) for i := 0; i < len(input); i += 2 { var pair [2]string pair[0] = input[i] @@ -55,5 +55,5 @@ func parseStringPairs(input ...string) (output [][2]string) { } output = append(output, pair) } - return + return output } diff --git a/vendor/github.com/docker/docker/pkg/atomicwriter/atomicwriter.go b/vendor/github.com/docker/docker/pkg/atomicwriter/atomicwriter.go index cbbe835bb128..abf46275318c 100644 --- a/vendor/github.com/docker/docker/pkg/atomicwriter/atomicwriter.go +++ b/vendor/github.com/docker/docker/pkg/atomicwriter/atomicwriter.go @@ -11,12 +11,12 @@ import ( // destination path. Writing and closing concurrently is not allowed. // NOTE: umask is not considered for the file's permissions. func New(filename string, perm os.FileMode) (io.WriteCloser, error) { - f, err := os.CreateTemp(filepath.Dir(filename), ".tmp-"+filepath.Base(filename)) + abspath, err := filepath.Abs(filename) if err != nil { return nil, err } - abspath, err := filepath.Abs(filename) + f, err := os.CreateTemp(filepath.Dir(abspath), ".tmp-"+filepath.Base(filename)) if err != nil { return nil, err } diff --git a/vendor/github.com/docker/docker/pkg/stdcopy/stdcopy.go b/vendor/github.com/docker/docker/pkg/stdcopy/stdcopy.go index 8f6e0a737aa6..854e4c371814 100644 --- a/vendor/github.com/docker/docker/pkg/stdcopy/stdcopy.go +++ b/vendor/github.com/docker/docker/pkg/stdcopy/stdcopy.go @@ -43,9 +43,9 @@ type stdWriter struct { // It inserts the prefix header before the buffer, // so stdcopy.StdCopy knows where to multiplex the output. // It makes stdWriter to implement io.Writer. -func (w *stdWriter) Write(p []byte) (n int, err error) { +func (w *stdWriter) Write(p []byte) (int, error) { if w == nil || w.Writer == nil { - return 0, errors.New("Writer not instantiated") + return 0, errors.New("writer not instantiated") } if p == nil { return 0, nil @@ -57,7 +57,7 @@ func (w *stdWriter) Write(p []byte) (n int, err error) { buf.Write(header[:]) buf.Write(p) - n, err = w.Writer.Write(buf.Bytes()) + n, err := w.Writer.Write(buf.Bytes()) n -= stdWriterPrefixLen if n < 0 { n = 0 @@ -65,7 +65,7 @@ func (w *stdWriter) Write(p []byte) (n int, err error) { buf.Reset() bufPool.Put(buf) - return + return n, err } // NewStdWriter instantiates a new Writer. diff --git a/vendor/github.com/docker/docker/registry/auth.go b/vendor/github.com/docker/docker/registry/auth.go index 8c62b83c0759..8f35dfff9c4c 100644 --- a/vendor/github.com/docker/docker/registry/auth.go +++ b/vendor/github.com/docker/docker/registry/auth.go @@ -66,13 +66,13 @@ func (scs staticCredentialStore) SetRefreshToken(*url.URL, string, string) { // loginV2 tries to login to the v2 registry server. The given registry // endpoint will be pinged to get authorization challenges. These challenges // will be used to authenticate against the registry to validate credentials. -func loginV2(authConfig *registry.AuthConfig, endpoint APIEndpoint, userAgent string) (status string, token string, _ error) { +func loginV2(authConfig *registry.AuthConfig, endpoint APIEndpoint, userAgent string) (token string, _ error) { endpointStr := strings.TrimRight(endpoint.URL.String(), "/") + "/v2/" log.G(context.TODO()).Debugf("attempting v2 login to registry endpoint %s", endpointStr) req, err := http.NewRequest(http.MethodGet, endpointStr, nil) if err != nil { - return "", "", err + return "", err } var ( @@ -84,22 +84,22 @@ func loginV2(authConfig *registry.AuthConfig, endpoint APIEndpoint, userAgent st loginClient, err := v2AuthHTTPClient(endpoint.URL, authTrans, modifiers, creds, nil) if err != nil { - return "", "", err + return "", err } resp, err := loginClient.Do(req) if err != nil { err = translateV2AuthError(err) - return "", "", err + return "", err } defer resp.Body.Close() - if resp.StatusCode == http.StatusOK { - return "Login Succeeded", credentialAuthConfig.IdentityToken, nil + if resp.StatusCode != http.StatusOK { + // TODO(dmcgowan): Attempt to further interpret result, status code and error code string + return "", errors.Errorf("login attempt to %s failed with status: %d %s", endpointStr, resp.StatusCode, http.StatusText(resp.StatusCode)) } - // TODO(dmcgowan): Attempt to further interpret result, status code and error code string - return "", "", errors.Errorf("login attempt to %s failed with status: %d %s", endpointStr, resp.StatusCode, http.StatusText(resp.StatusCode)) + return credentialAuthConfig.IdentityToken, nil } func v2AuthHTTPClient(endpoint *url.URL, authTransport http.RoundTripper, modifiers []transport.RequestModifier, creds auth.CredentialStore, scopes []auth.Scope) (*http.Client, error) { diff --git a/vendor/github.com/docker/docker/registry/config.go b/vendor/github.com/docker/docker/registry/config.go index f8d94ce80636..433454624a1a 100644 --- a/vendor/github.com/docker/docker/registry/config.go +++ b/vendor/github.com/docker/docker/registry/config.go @@ -4,13 +4,17 @@ import ( "context" "net" "net/url" + "os" + "path/filepath" "strconv" "strings" + "sync" "github.com/containerd/log" "github.com/distribution/reference" "github.com/docker/docker/api/types/registry" "github.com/docker/docker/internal/lazyregexp" + "github.com/docker/docker/pkg/homedir" ) // ServiceOptions holds command line options. @@ -56,26 +60,52 @@ var ( Host: DefaultRegistryHost, } - emptyServiceConfig, _ = newServiceConfig(ServiceOptions{}) - validHostPortRegex = lazyregexp.New(`^` + reference.DomainRegexp.String() + `$`) + validHostPortRegex = lazyregexp.New(`^` + reference.DomainRegexp.String() + `$`) - // certsDir is used to override defaultCertsDir. - certsDir string + // certsDir is used to override defaultCertsDir when running with rootlessKit. + // + // TODO(thaJeztah): change to a sync.OnceValue once we remove [SetCertsDir] + // TODO(thaJeztah): certsDir should not be a package variable, but stored in our config, and passed when needed. + setCertsDirOnce sync.Once + certsDir string ) +func setCertsDir(dir string) string { + setCertsDirOnce.Do(func() { + if dir != "" { + certsDir = dir + return + } + if os.Getenv("ROOTLESSKIT_STATE_DIR") != "" { + // Configure registry.CertsDir() when running in rootless-mode + // This is the equivalent of [rootless.RunningWithRootlessKit], + // but inlining it to prevent adding that as a dependency + // for docker/cli. + // + // [rootless.RunningWithRootlessKit]: https://github.com/moby/moby/blob/b4bdf12daec84caaf809a639f923f7370d4926ad/pkg/rootless/rootless.go#L5-L8 + if configHome, _ := homedir.GetConfigHome(); configHome != "" { + certsDir = filepath.Join(configHome, "docker/certs.d") + return + } + } + certsDir = defaultCertsDir + }) + return certsDir +} + // SetCertsDir allows the default certs directory to be changed. This function // is used at daemon startup to set the correct location when running in // rootless mode. +// +// Deprecated: the cert-directory is now automatically selected when running with rootlessKit, and should no longer be set manually. func SetCertsDir(path string) { - certsDir = path + setCertsDir(path) } // CertsDir is the directory where certificates are stored. func CertsDir() string { - if certsDir != "" { - return certsDir - } - return defaultCertsDir + // call setCertsDir with an empty path to synchronise with [SetCertsDir] + return setCertsDir("") } // newServiceConfig returns a new instance of ServiceConfig @@ -181,7 +211,7 @@ skip: // Assume `host:port` if not CIDR. indexConfigs[r] = ®istry.IndexInfo{ Name: r, - Mirrors: make([]string, 0), + Mirrors: []string{}, Secure: false, Official: false, } @@ -288,16 +318,22 @@ func ValidateMirror(val string) (string, error) { // ValidateIndexName validates an index name. It is used by the daemon to // validate the daemon configuration. func ValidateIndexName(val string) (string, error) { - // TODO: upstream this to check to reference package - if val == "index.docker.io" { - val = "docker.io" - } + val = normalizeIndexName(val) if strings.HasPrefix(val, "-") || strings.HasSuffix(val, "-") { return "", invalidParamf("invalid index name (%s). Cannot begin or end with a hyphen", val) } return val, nil } +func normalizeIndexName(val string) string { + // TODO(thaJeztah): consider normalizing other known options, such as "(https://)registry-1.docker.io", "https://index.docker.io/v1/". + // TODO: upstream this to check to reference package + if val == "index.docker.io" { + return "docker.io" + } + return val +} + func hasScheme(reposName string) bool { return strings.Contains(reposName, "://") } @@ -327,25 +363,20 @@ func validateHostPort(s string) error { } // newIndexInfo returns IndexInfo configuration from indexName -func newIndexInfo(config *serviceConfig, indexName string) (*registry.IndexInfo, error) { - var err error - indexName, err = ValidateIndexName(indexName) - if err != nil { - return nil, err - } +func newIndexInfo(config *serviceConfig, indexName string) *registry.IndexInfo { + indexName = normalizeIndexName(indexName) // Return any configured index info, first. if index, ok := config.IndexConfigs[indexName]; ok { - return index, nil + return index } // Construct a non-configured index info. return ®istry.IndexInfo{ - Name: indexName, - Mirrors: make([]string, 0), - Secure: config.isSecureIndex(indexName), - Official: false, - }, nil + Name: indexName, + Mirrors: []string{}, + Secure: config.isSecureIndex(indexName), + } } // GetAuthConfigKey special-cases using the full index address of the official @@ -358,18 +389,22 @@ func GetAuthConfigKey(index *registry.IndexInfo) string { } // newRepositoryInfo validates and breaks down a repository name into a RepositoryInfo -func newRepositoryInfo(config *serviceConfig, name reference.Named) (*RepositoryInfo, error) { - index, err := newIndexInfo(config, reference.Domain(name)) - if err != nil { - return nil, err +func newRepositoryInfo(config *serviceConfig, name reference.Named) *RepositoryInfo { + index := newIndexInfo(config, reference.Domain(name)) + var officialRepo bool + if index.Official { + // RepositoryInfo.Official indicates whether the image repository + // is an official (docker library official images) repository. + // + // We only need to check this if the image-repository is on Docker Hub. + officialRepo = !strings.ContainsRune(reference.FamiliarName(name), '/') } - official := !strings.ContainsRune(reference.FamiliarName(name), '/') return &RepositoryInfo{ Name: reference.TrimNamed(name), Index: index, - Official: official, - }, nil + Official: officialRepo, + } } // ParseRepositoryInfo performs the breakdown of a repository name into a @@ -377,5 +412,70 @@ func newRepositoryInfo(config *serviceConfig, name reference.Named) (*Repository // // It is used by the Docker cli to interact with registry-related endpoints. func ParseRepositoryInfo(reposName reference.Named) (*RepositoryInfo, error) { - return newRepositoryInfo(emptyServiceConfig, reposName) + indexName := normalizeIndexName(reference.Domain(reposName)) + if indexName == IndexName { + officialRepo := !strings.ContainsRune(reference.FamiliarName(reposName), '/') + return &RepositoryInfo{ + Name: reference.TrimNamed(reposName), + Index: ®istry.IndexInfo{ + Name: IndexName, + Mirrors: []string{}, + Secure: true, + Official: true, + }, + Official: officialRepo, + }, nil + } + + insecure := false + if isInsecure(indexName) { + insecure = true + } + + return &RepositoryInfo{ + Name: reference.TrimNamed(reposName), + Index: ®istry.IndexInfo{ + Name: indexName, + Mirrors: []string{}, + Secure: !insecure, + }, + }, nil +} + +// isInsecure is used to detect whether a registry domain or IP-address is allowed +// to use an insecure (non-TLS, or self-signed cert) connection according to the +// defaults, which allows for insecure connections with registries running on a +// loopback address ("localhost", "::1/128", "127.0.0.0/8"). +// +// It is used in situations where we don't have access to the daemon's configuration, +// for example, when used from the client / CLI. +func isInsecure(hostNameOrIP string) bool { + // Attempt to strip port if present; this also strips brackets for + // IPv6 addresses with a port (e.g. "[::1]:5000"). + // + // This is best-effort; we'll continue using the address as-is if it fails. + if host, _, err := net.SplitHostPort(hostNameOrIP); err == nil { + hostNameOrIP = host + } + if hostNameOrIP == "127.0.0.1" || hostNameOrIP == "::1" || strings.EqualFold(hostNameOrIP, "localhost") { + // Fast path; no need to resolve these, assuming nobody overrides + // "localhost" for anything else than a loopback address (sorry, not sorry). + return true + } + + var addresses []net.IP + if ip := net.ParseIP(hostNameOrIP); ip != nil { + addresses = append(addresses, ip) + } else { + // Try to resolve the host's IP-addresses. + addrs, _ := lookupIP(hostNameOrIP) + addresses = append(addresses, addrs...) + } + + for _, addr := range addresses { + if addr.IsLoopback() { + return true + } + } + return false } diff --git a/vendor/github.com/docker/docker/registry/registry.go b/vendor/github.com/docker/docker/registry/registry.go index 6b079199ddc0..a26f976ceead 100644 --- a/vendor/github.com/docker/docker/registry/registry.go +++ b/vendor/github.com/docker/docker/registry/registry.go @@ -8,7 +8,6 @@ import ( "net/http" "os" "path/filepath" - "strings" "time" "github.com/containerd/log" @@ -18,7 +17,14 @@ import ( ) // HostCertsDir returns the config directory for a specific host. +// +// Deprecated: this function was only used internally, and will be removed in a future release. func HostCertsDir(hostname string) string { + return hostCertsDir(hostname) +} + +// hostCertsDir returns the config directory for a specific host. +func hostCertsDir(hostname string) string { return filepath.Join(CertsDir(), cleanPath(hostname)) } @@ -26,11 +32,10 @@ func HostCertsDir(hostname string) string { func newTLSConfig(hostname string, isSecure bool) (*tls.Config, error) { // PreferredServerCipherSuites should have no effect tlsConfig := tlsconfig.ServerDefault() - tlsConfig.InsecureSkipVerify = !isSecure - if isSecure && CertsDir() != "" { - hostDir := HostCertsDir(hostname) + if isSecure { + hostDir := hostCertsDir(hostname) log.G(context.TODO()).Debugf("hostDir: %s", hostDir) if err := ReadCertsDirectory(tlsConfig, hostDir); err != nil { return nil, err @@ -59,7 +64,8 @@ func ReadCertsDirectory(tlsConfig *tls.Config, directory string) error { } for _, f := range fs { - if strings.HasSuffix(f.Name(), ".crt") { + switch filepath.Ext(f.Name()) { + case ".crt": if tlsConfig.RootCAs == nil { systemPool, err := tlsconfig.SystemCertPool() if err != nil { @@ -67,17 +73,17 @@ func ReadCertsDirectory(tlsConfig *tls.Config, directory string) error { } tlsConfig.RootCAs = systemPool } - log.G(context.TODO()).Debugf("crt: %s", filepath.Join(directory, f.Name())) - data, err := os.ReadFile(filepath.Join(directory, f.Name())) + fileName := filepath.Join(directory, f.Name()) + log.G(context.TODO()).Debugf("crt: %s", fileName) + data, err := os.ReadFile(fileName) if err != nil { return err } tlsConfig.RootCAs.AppendCertsFromPEM(data) - } - if strings.HasSuffix(f.Name(), ".cert") { + case ".cert": certName := f.Name() keyName := certName[:len(certName)-5] + ".key" - log.G(context.TODO()).Debugf("cert: %s", filepath.Join(directory, f.Name())) + log.G(context.TODO()).Debugf("cert: %s", filepath.Join(directory, certName)) if !hasFile(fs, keyName) { return invalidParamf("missing key %s for client certificate %s. CA certificates must use the extension .crt", keyName, certName) } @@ -86,11 +92,10 @@ func ReadCertsDirectory(tlsConfig *tls.Config, directory string) error { return err } tlsConfig.Certificates = append(tlsConfig.Certificates, cert) - } - if strings.HasSuffix(f.Name(), ".key") { + case ".key": keyName := f.Name() certName := keyName[:len(keyName)-4] + ".cert" - log.G(context.TODO()).Debugf("key: %s", filepath.Join(directory, f.Name())) + log.G(context.TODO()).Debugf("key: %s", filepath.Join(directory, keyName)) if !hasFile(fs, certName) { return invalidParamf("missing client certificate %s for key %s", certName, keyName) } diff --git a/vendor/github.com/docker/docker/registry/search.go b/vendor/github.com/docker/docker/registry/search.go index 4ce90f55d4d6..8f4739ac0e7d 100644 --- a/vendor/github.com/docker/docker/registry/search.go +++ b/vendor/github.com/docker/docker/registry/search.go @@ -93,12 +93,8 @@ func (s *Service) searchUnfiltered(ctx context.Context, term string, limit int, // Search is a long-running operation, just lock s.config to avoid block others. s.mu.RLock() - index, err := newIndexInfo(s.config, indexName) + index := newIndexInfo(s.config, indexName) s.mu.RUnlock() - - if err != nil { - return nil, err - } if index.Official { // If pull "library/foo", it's stored locally under "foo" remoteName = strings.TrimPrefix(remoteName, "library/") @@ -158,5 +154,24 @@ func splitReposSearchTerm(reposName string) (string, string) { // for that. func ParseSearchIndexInfo(reposName string) (*registry.IndexInfo, error) { indexName, _ := splitReposSearchTerm(reposName) - return newIndexInfo(emptyServiceConfig, indexName) + indexName = normalizeIndexName(indexName) + if indexName == IndexName { + return ®istry.IndexInfo{ + Name: IndexName, + Mirrors: []string{}, + Secure: true, + Official: true, + }, nil + } + + insecure := false + if isInsecure(indexName) { + insecure = true + } + + return ®istry.IndexInfo{ + Name: indexName, + Mirrors: []string{}, + Secure: !insecure, + }, nil } diff --git a/vendor/github.com/docker/docker/registry/search_session.go b/vendor/github.com/docker/docker/registry/search_session.go index a0d25c805e81..a3d2c894d2c3 100644 --- a/vendor/github.com/docker/docker/registry/search_session.go +++ b/vendor/github.com/docker/docker/registry/search_session.go @@ -83,12 +83,12 @@ type onEOFReader struct { Fn func() } -func (r *onEOFReader) Read(p []byte) (n int, err error) { - n, err = r.Rc.Read(p) +func (r *onEOFReader) Read(p []byte) (int, error) { + n, err := r.Rc.Read(p) if err == io.EOF { r.runFunc() } - return + return n, err } // Close closes the file and run the function. diff --git a/vendor/github.com/docker/docker/registry/service.go b/vendor/github.com/docker/docker/registry/service.go index 4d66523c616a..bc67a434517f 100644 --- a/vendor/github.com/docker/docker/registry/service.go +++ b/vendor/github.com/docker/docker/registry/service.go @@ -52,7 +52,7 @@ func (s *Service) ReplaceConfig(options ServiceOptions) (commit func(), err erro // Auth contacts the public registry with the provided credentials, // and returns OK if authentication was successful. // It can be used to verify the validity of a client's credentials. -func (s *Service) Auth(ctx context.Context, authConfig *registry.AuthConfig, userAgent string) (status, token string, err error) { +func (s *Service) Auth(ctx context.Context, authConfig *registry.AuthConfig, userAgent string) (statusMessage, token string, _ error) { // TODO Use ctx when searching for repositories registryHostName := IndexHostname @@ -77,19 +77,28 @@ func (s *Service) Auth(ctx context.Context, authConfig *registry.AuthConfig, use return "", "", invalidParam(err) } + var lastErr error for _, endpoint := range endpoints { - status, token, err = loginV2(authConfig, endpoint, userAgent) - if err == nil { - return - } - if errdefs.IsUnauthorized(err) { - // Failed to authenticate; don't continue with (non-TLS) endpoints. - return status, token, err + authToken, err := loginV2(authConfig, endpoint, userAgent) + if err != nil { + if errdefs.IsUnauthorized(err) { + // Failed to authenticate; don't continue with (non-TLS) endpoints. + return "", "", err + } + // Try next endpoint + log.G(ctx).WithFields(log.Fields{ + "error": err, + "endpoint": endpoint, + }).Infof("Error logging in to endpoint, trying next endpoint") + lastErr = err + continue } - log.G(ctx).WithError(err).Infof("Error logging in to endpoint, trying next endpoint") + + // TODO(thaJeztah): move the statusMessage to the API endpoint; we don't need to produce that here? + return "Login Succeeded", authToken, nil } - return "", "", err + return "", "", lastErr } // ResolveRepository splits a repository name into its components @@ -97,7 +106,8 @@ func (s *Service) Auth(ctx context.Context, authConfig *registry.AuthConfig, use func (s *Service) ResolveRepository(name reference.Named) (*RepositoryInfo, error) { s.mu.RLock() defer s.mu.RUnlock() - return newRepositoryInfo(s.config, name) + // TODO(thaJeztah): remove error return as it's no longer used. + return newRepositoryInfo(s.config, name), nil } // APIEndpoint represents a remote API endpoint diff --git a/vendor/github.com/docker/docker/registry/types.go b/vendor/github.com/docker/docker/registry/types.go index 02d7f4f383b7..63ace0fbadf9 100644 --- a/vendor/github.com/docker/docker/registry/types.go +++ b/vendor/github.com/docker/docker/registry/types.go @@ -13,6 +13,8 @@ type RepositoryInfo struct { // Official indicates whether the repository is considered official. // If the registry is official, and the normalized name does not // contain a '/' (e.g. "foo"), then it is considered an official repo. + // + // Deprecated: this field is no longer used and will be removed in the next release. The information captured in this field can be obtained from the [Name] field instead. Official bool // Class represents the class of the repository, such as "plugin" // or "image". diff --git a/vendor/modules.txt b/vendor/modules.txt index f2c9398ab0e3..f7b810eace34 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -55,7 +55,7 @@ github.com/docker/distribution/registry/client/transport github.com/docker/distribution/registry/storage/cache github.com/docker/distribution/registry/storage/cache/memory github.com/docker/distribution/uuid -# github.com/docker/docker v28.0.1+incompatible +# github.com/docker/docker v28.0.1+incompatible => github.com/vvoland/moby v20.10.16-0.20250314163427-17840c8adc30+incompatible ## explicit github.com/docker/docker/api github.com/docker/docker/api/types @@ -548,3 +548,4 @@ gotest.tools/v3/skip # tags.cncf.io/container-device-interface v0.8.0 ## explicit; go 1.20 tags.cncf.io/container-device-interface/pkg/parser +# github.com/docker/docker => github.com/vvoland/moby v20.10.16-0.20250314163427-17840c8adc30+incompatible