diff --git a/cli/command/image/push.go b/cli/command/image/push.go index 88d8bec4fee4..a4ca7598fe01 100644 --- a/cli/command/image/push.go +++ b/cli/command/image/push.go @@ -14,6 +14,7 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command/completion" + "github.com/docker/cli/cli/config/configfile" "github.com/docker/cli/cli/streams" "github.com/docker/cli/internal/jsonstream" "github.com/docker/cli/internal/tui" @@ -109,14 +110,21 @@ To push the complete multi-platform image, remove the --platform flag. // Resolve the Auth config relevant for this server authConfig := command.ResolveAuthConfig(dockerCli.ConfigFile(), repoInfo.Index) - encodedAuth, err := registrytypes.EncodeAuthConfig(authConfig) + + hostname := command.GetRegistryHostname(repoInfo.Index) + + encoded, err := registrytypes.EncodeAuthConfig(registrytypes.AuthConfig{ + Username: "test", + Password: "test", + }) if err != nil { return err } + options := image.PushOptions{ All: opts.all, - RegistryAuth: encodedAuth, - PrivilegeFunc: nil, + RegistryAuth: encoded, // we already pass all configs from privilegeFuncs + PrivilegeFunc: configfile.RegistryAuthPrivilegeFunc(dockerCli.ConfigFile(), hostname), Platform: platform, } diff --git a/cli/command/image/trust.go b/cli/command/image/trust.go index c44b51eb8f07..c2a3cf066d43 100644 --- a/cli/command/image/trust.go +++ b/cli/command/image/trust.go @@ -8,6 +8,7 @@ import ( "github.com/distribution/reference" "github.com/docker/cli/cli/command" + "github.com/docker/cli/cli/config/configfile" "github.com/docker/cli/cli/streams" "github.com/docker/cli/cli/trust" "github.com/docker/cli/internal/jsonstream" @@ -17,7 +18,7 @@ import ( "github.com/opencontainers/go-digest" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/theupdateframework/notary/client" + notaryclient "github.com/theupdateframework/notary/client" "github.com/theupdateframework/notary/tuf/data" ) @@ -29,11 +30,11 @@ type target struct { // notaryClientProvider is used in tests to provide a dummy notary client. type notaryClientProvider interface { - NotaryClient(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (client.Repository, error) + NotaryClient(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (notaryclient.Repository, error) } // newNotaryClient provides a Notary Repository to interact with signed metadata for an image. -func newNotaryClient(cli command.Streams, imgRefAndAuth trust.ImageRefAndAuth) (client.Repository, error) { +func newNotaryClient(cli command.Streams, imgRefAndAuth trust.ImageRefAndAuth) (notaryclient.Repository, error) { if ncp, ok := cli.(notaryClientProvider); ok { // notaryClientProvider is used in tests to provide a dummy notary client. return ncp.NotaryClient(imgRefAndAuth, []string{"pull"}) @@ -145,13 +146,19 @@ func getTrustedPullTargets(cli command.Cli, imgRefAndAuth trust.ImageRefAndAuth) // imagePullPrivileged pulls the image and displays it to the output func imagePullPrivileged(ctx context.Context, cli command.Cli, imgRefAndAuth trust.ImageRefAndAuth, opts pullOptions) error { - encodedAuth, err := registrytypes.EncodeAuthConfig(*imgRefAndAuth.AuthConfig()) + hostname := command.GetRegistryHostname(imgRefAndAuth.RepoInfo().Index) + + encoded, err := registrytypes.EncodeAuthConfig(registrytypes.AuthConfig{ + Username: "test", + Password: "test", + }) if err != nil { return err } + responseBody, err := cli.Client().ImagePull(ctx, reference.FamiliarString(imgRefAndAuth.Reference()), image.PullOptions{ - RegistryAuth: encodedAuth, - PrivilegeFunc: nil, + RegistryAuth: encoded, // we already have credentials resolved by privilegeFuncs + PrivilegeFunc: configfile.RegistryAuthPrivilegeFunc(cli.ConfigFile(), hostname), All: opts.all, Platform: opts.platform, }) @@ -186,7 +193,7 @@ func TrustedReference(ctx context.Context, cli command.Cli, ref reference.NamedT // Only list tags in the top level targets role or the releases delegation role - ignore // all other delegation roles if t.Role != trust.ReleasesRole && t.Role != data.CanonicalTargetsRole { - return nil, trust.NotaryError(imgRefAndAuth.RepoInfo().Name.Name(), client.ErrNoSuchTarget(ref.Tag())) + return nil, trust.NotaryError(imgRefAndAuth.RepoInfo().Name.Name(), notaryclient.ErrNoSuchTarget(ref.Tag())) } r, err := convertTarget(t.Target) if err != nil { @@ -195,7 +202,7 @@ func TrustedReference(ctx context.Context, cli command.Cli, ref reference.NamedT return reference.WithDigest(reference.TrimNamed(ref), r.digest) } -func convertTarget(t client.Target) (target, error) { +func convertTarget(t notaryclient.Target) (target, error) { h, ok := t.Hashes["sha256"] if !ok { return target{}, errors.New("no valid hash, expecting sha256") diff --git a/cli/command/registry.go b/cli/command/registry.go index d02d88db2dfd..3ce9512a0622 100644 --- a/cli/command/registry.go +++ b/cli/command/registry.go @@ -62,6 +62,14 @@ func RegistryAuthenticationPrivilegedFunc(cli Cli, index *registrytypes.IndexInf } } +func GetRegistryHostname(index *registrytypes.IndexInfo) string { + configKey := index.Name + if index.Official { + configKey = authConfigKey + } + return configKey +} + // ResolveAuthConfig returns auth-config for the given registry from the // credential-store. It returns an empty AuthConfig if no credentials were // found. diff --git a/cli/config/configfile/file.go b/cli/config/configfile/file.go index 530c5228561f..13e33b56b570 100644 --- a/cli/config/configfile/file.go +++ b/cli/config/configfile/file.go @@ -1,6 +1,7 @@ package configfile import ( + "context" "encoding/base64" "encoding/json" "fmt" @@ -9,9 +10,12 @@ import ( "path/filepath" "strings" + cerrdefs "github.com/containerd/errdefs" "github.com/docker/cli/cli/config/credentials" "github.com/docker/cli/cli/config/memorystore" "github.com/docker/cli/cli/config/types" + "github.com/docker/docker/api/types/registry" + c "github.com/docker/docker/client" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -301,20 +305,9 @@ func (configFile *ConfigFile) GetCredentialsStore(registryHostname string) crede return store } - authConfig, err := parseEnvConfig(envConfig) - if err != nil { - _, _ = fmt.Fprintln(os.Stderr, "Failed to create credential store from DOCKER_AUTH_CONFIG: ", err) - return store - } - - // use DOCKER_AUTH_CONFIG if set - // it uses the native or file store as a fallback to fetch and store credentials - envStore, err := memorystore.New( - memorystore.WithAuthConfig(authConfig), - memorystore.WithFallbackStore(store), - ) + envStore, err := getEnvStore(envConfig, store) if err != nil { - _, _ = fmt.Fprintln(os.Stderr, "Failed to create credential store from DOCKER_AUTH_CONFIG: ", err) + _, _ = fmt.Fprintln(os.Stderr, err) return store } @@ -360,6 +353,83 @@ func (configFile *ConfigFile) GetAuthConfig(registryHostname string) (types.Auth return configFile.GetCredentialsStore(registryHostname).Get(registryHostname) } +func getEnvStore(envConfig string, fallbackStore credentials.Store) (credentials.Store, error) { + authConfig, err := parseEnvConfig(envConfig) + if err != nil { + return nil, fmt.Errorf("failed to create credential store from %s: %w", DockerEnvConfigKey, err) + } + + // use DOCKER_AUTH_CONFIG if set + // it uses the native or file store as a fallback to fetch and store credentials + envStore, err := memorystore.New( + memorystore.WithAuthConfig(authConfig), + memorystore.WithFallbackStore(fallbackStore), + ) + if err != nil { + return nil, fmt.Errorf("failed to create credential store from %s: %w", DockerEnvConfigKey, err) + } + return envStore, nil +} + +func getEncodedAuth(store credentials.Store, registryHostname string) (string, error) { + c, err := store.Get(registryHostname) + if err != nil { + return "", err + } + return registry.EncodeAuthConfig(registry.AuthConfig(c)) +} + +func envRequestAuthConfig(registryHostname string) registry.RequestAuthConfig { + return func(ctx context.Context) (string, error) { + fmt.Fprintln(os.Stdout, "Trying env") + + envConfig := os.Getenv(DockerEnvConfigKey) + if envConfig == "" { + return "", cerrdefs.ErrNotFound.WithMessage(DockerEnvConfigKey + " not defined") + } + + envStore, err := getEnvStore(envConfig, nil) + if err != nil { + return "", err + } + return getEncodedAuth(envStore, registryHostname) + } +} + +func nativeRequestAuthConfig(configFile *ConfigFile, registryHostname string) registry.RequestAuthConfig { + return func(ctx context.Context) (string, error) { + fmt.Fprintln(os.Stdout, "Trying native") + helper := getConfiguredCredentialStore(configFile, registryHostname) + if helper == "" { + return "", cerrdefs.ErrNotFound.WithMessage("native credential helper not supported") + } + store := newNativeStore(configFile, helper) + return getEncodedAuth(store, registryHostname) + } +} + +func fileRequestAuthConfig(configFile *ConfigFile, registryHostname string) registry.RequestAuthConfig { + return func(ctx context.Context) (string, error) { + fmt.Fprintln(os.Stdout, "Trying file") + store := credentials.NewFileStore(configFile) + return getEncodedAuth(store, registryHostname) + } +} + +// RegistryAuthPrivilegeFunc returns an ordered slice of [registry.RequestAuthConfig] +// +// The order of precedence for resolving credentials are as follows: +// - Credentials stored in the environment through DOCKER_AUTH_CONFIG +// - Native credentials through the credential-helper +// - Filestore credentials stored in `~/.docker/config.json` +func RegistryAuthPrivilegeFunc(configFile *ConfigFile, registryHostname string) registry.RequestAuthConfig { + return c.ChainPrivilegeFuncs( + envRequestAuthConfig(registryHostname), + nativeRequestAuthConfig(configFile, registryHostname), + fileRequestAuthConfig(configFile, registryHostname), + ) +} + // getConfiguredCredentialStore returns the credential helper configured for the // given registry, the default credsStore, or the empty string if neither are // configured. diff --git a/vendor/github.com/docker/docker/api/swagger.yaml b/vendor/github.com/docker/docker/api/swagger.yaml index 8d6a8f9356a4..3880635db128 100644 --- a/vendor/github.com/docker/docker/api/swagger.yaml +++ b/vendor/github.com/docker/docker/api/swagger.yaml @@ -2913,7 +2913,8 @@ definitions: be used. If multiple endpoints have the same priority, endpoints are lexicographically sorted based on their network name, and the one that sorts first is picked. - type: "number" + type: "integer" + format: "int64" example: - 10 diff --git a/vendor/github.com/docker/docker/client/auth.go b/vendor/github.com/docker/docker/client/auth.go new file mode 100644 index 000000000000..7144becb53b4 --- /dev/null +++ b/vendor/github.com/docker/docker/client/auth.go @@ -0,0 +1,85 @@ +// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16: +//go:build go1.23 + +package client + +import ( + "context" + "errors" + "slices" + + cerrdefs "github.com/containerd/errdefs" + "github.com/docker/docker/api/types/registry" +) + +// staticAuth creates a privilegeFn from the given registryAuth. +func staticAuth(registryAuth string) registry.RequestAuthConfig { + return func(ctx context.Context) (string, error) { + return registryAuth, nil + } +} + +var ( + // errNoMorePrivilegeFuncs is a sentinel error to detect when the list of + // chained privilege-funcs is exhausted. It is returned by [chainPrivilegeFuncs]. + // + // This error is not currently exported as we don't want to expose this feature + // yet for alternative implementations. + errNoMorePrivilegeFuncs = errors.New("no more privilege functions") + + // errTryNextPrivilegeFunc is a sentinel error to detect whether the same + // privilegeFunc can be re-tried. It is returned by [chainPrivilegeFuncs]. + // + // This error is not currently exported as we don't want to expose this feature + // yet for alternative implementations. + errTryNextPrivilegeFunc = errors.New("try next privilege function") +) + +// ChainPrivilegeFuncs returns a PrivilegeFunc that wraps the given funcs. +// Each call tries the next func in order, returning its result if successful. +// +// It returns errTryNextPrivilegeFunc if more funcs are available; errNoMorePrivilegeFuncs +// when exhausted. +func ChainPrivilegeFuncs(funcs ...registry.RequestAuthConfig) registry.RequestAuthConfig { + acFuncs := slices.DeleteFunc(slices.Clone(funcs), func(f registry.RequestAuthConfig) bool { return f == nil }) + + var i int + return func(ctx context.Context) (string, error) { + if i >= len(acFuncs) { + return "", errNoMorePrivilegeFuncs + } + ac, err := acFuncs[i](ctx) + switch { + case err == nil, cerrdefs.IsNotFound(err): + // success; allow caller to retry if the list is not exhausted. + // case errors.Is(err, notFound): + case errors.Is(err, errTryNextPrivilegeFunc): + // PrivilegeFunc is a chain; return without incrementing. + return ac, err + case errors.Is(err, errNoMorePrivilegeFuncs): + // PrivilegeFunc is a chain and the list is exhausted. + // Continue as if no error occurred. + default: + // terminal error; stop chain + acFuncs = nil + return ac, err + } + i++ + if i >= len(acFuncs) { + return ac, nil + } + return ac, errTryNextPrivilegeFunc + } +} + +func getAuth(ctx context.Context, fn registry.RequestAuthConfig) (auth string, tryNext bool, _ error) { + if fn == nil { + return "", false, nil + } + auth, err := fn(ctx) + if errors.Is(err, errTryNextPrivilegeFunc) { + return auth, true, nil + } + // No error, errNoMorePrivilegeFuncs and any hard failure. + return auth, false, err +} diff --git a/vendor/github.com/docker/docker/client/image_create.go b/vendor/github.com/docker/docker/client/image_create.go index 1e044d7779d0..9b313fabd34a 100644 --- a/vendor/github.com/docker/docker/client/image_create.go +++ b/vendor/github.com/docker/docker/client/image_create.go @@ -2,11 +2,15 @@ package client import ( "context" + "errors" + "fmt" "io" "net/http" "net/url" + "os" "strings" + cerrdefs "github.com/containerd/errdefs" "github.com/distribution/reference" "github.com/docker/docker/api/types/image" "github.com/docker/docker/api/types/registry" @@ -26,15 +30,48 @@ func (cli *Client) ImageCreate(ctx context.Context, parentReference string, opti if options.Platform != "" { query.Set("platform", strings.ToLower(options.Platform)) } - resp, err := cli.tryImageCreate(ctx, query, options.RegistryAuth) + resp, err := cli.tryImageCreate(ctx, query, staticAuth(options.RegistryAuth)) if err != nil { return nil, err } return resp.Body, nil } -func (cli *Client) tryImageCreate(ctx context.Context, query url.Values, registryAuth string) (*http.Response, error) { - return cli.post(ctx, "/images/create", query, nil, http.Header{ - registry.AuthHeader: {registryAuth}, - }) +func (cli *Client) tryImageCreate(ctx context.Context, query url.Values, resolveAuth registry.RequestAuthConfig) (*http.Response, error) { + hdr := http.Header{} + var lastErr error + for { + registryAuth, tryNext, err := getAuth(ctx, resolveAuth) + if err != nil { + // TODO(thaJeztah): should we return an "unauthorised error" here to allow the caller to try other options? + if errors.Is(err, errNoMorePrivilegeFuncs) && lastErr != nil { + return nil, lastErr + } + return nil, err + } + if registryAuth != "" { + hdr.Set(registry.AuthHeader, registryAuth) + } + resp, err := cli.post(ctx, "/images/create", query, nil, hdr) + fmt.Fprintf(os.Stdout, "resp: %v\nerr: %v", resp, err) + if err == nil { + // Discard previous errors + return resp, nil + } else { + if IsErrConnectionFailed(err) { + // Don't retry if we failed to connect to the API. + return nil, err + } + + // TODO(thaJeztah); only retry with "IsUnauthorized" and/or "rate limit (StatusTooManyRequests)" errors? + if !cerrdefs.IsUnauthorized(err) { + return nil, err + } + + lastErr = err + } + if !tryNext { + return nil, lastErr + } + } } diff --git a/vendor/github.com/docker/docker/client/image_pull.go b/vendor/github.com/docker/docker/client/image_pull.go index ab7606b4563c..93e7d7739601 100644 --- a/vendor/github.com/docker/docker/client/image_pull.go +++ b/vendor/github.com/docker/docker/client/image_pull.go @@ -6,7 +6,6 @@ import ( "net/url" "strings" - cerrdefs "github.com/containerd/errdefs" "github.com/distribution/reference" "github.com/docker/docker/api/types/image" ) @@ -34,14 +33,18 @@ func (cli *Client) ImagePull(ctx context.Context, refStr string, options image.P query.Set("platform", strings.ToLower(options.Platform)) } - resp, err := cli.tryImageCreate(ctx, query, options.RegistryAuth) - if cerrdefs.IsUnauthorized(err) && options.PrivilegeFunc != nil { - newAuthHeader, privilegeErr := options.PrivilegeFunc(ctx) - if privilegeErr != nil { - return nil, privilegeErr - } - resp, err = cli.tryImageCreate(ctx, query, newAuthHeader) - } + // PrivilegeFunc was added in [18472] as an alternative to passing static + // authentication. The default was still to try the static authentication + // before calling the PrivilegeFunc (if present). + // + // For now, we need to keep this behavior, as PrivilegeFunc may be an + // interactive prompt, however, we should change this to only use static + // auth if not empty. Ultimately, we should deprecate its use in favor of + // callers providing a PrivilegeFunc (which can be chained), or a list of + // PrivilegeFuncs. + // + // [18472]: https://github.com/moby/moby/commit/e78f02c4dbc3cada909c114fef6b6643969ab912 + resp, err := cli.tryImageCreate(ctx, query, ChainPrivilegeFuncs(staticAuth(options.RegistryAuth), options.PrivilegeFunc)) if err != nil { return nil, err } diff --git a/vendor/github.com/docker/docker/client/image_push.go b/vendor/github.com/docker/docker/client/image_push.go index cbbe9a25d609..9468ec059c33 100644 --- a/vendor/github.com/docker/docker/client/image_push.go +++ b/vendor/github.com/docker/docker/client/image_push.go @@ -51,22 +51,58 @@ func (cli *Client) ImagePush(ctx context.Context, image string, options image.Pu query.Set("platform", string(pJson)) } - resp, err := cli.tryImagePush(ctx, ref.Name(), query, options.RegistryAuth) - if cerrdefs.IsUnauthorized(err) && options.PrivilegeFunc != nil { - newAuthHeader, privilegeErr := options.PrivilegeFunc(ctx) - if privilegeErr != nil { - return nil, privilegeErr - } - resp, err = cli.tryImagePush(ctx, ref.Name(), query, newAuthHeader) - } + // PrivilegeFunc was added in [18472] as an alternative to passing static + // authentication. The default was still to try the static authentication + // before calling the PrivilegeFunc (if present). + // + // For now, we need to keep this behavior, as PrivilegeFunc may be an + // interactive prompt, however, we should change this to only use static + // auth if not empty. Ultimately, we should deprecate its use in favor of + // callers providing a PrivilegeFunc (which can be chained), or a list of + // PrivilegeFuncs. + // + // [18472]: https://github.com/moby/moby/commit/e78f02c4dbc3cada909c114fef6b6643969ab912 + resp, err := cli.tryImagePush(ctx, ref.Name(), query, ChainPrivilegeFuncs(staticAuth(options.RegistryAuth), options.PrivilegeFunc)) if err != nil { return nil, err } return resp.Body, nil } -func (cli *Client) tryImagePush(ctx context.Context, imageID string, query url.Values, registryAuth string) (*http.Response, error) { - return cli.post(ctx, "/images/"+imageID+"/push", query, nil, http.Header{ - registry.AuthHeader: {registryAuth}, - }) +func (cli *Client) tryImagePush(ctx context.Context, imageID string, query url.Values, resolveAuth registry.RequestAuthConfig) (*http.Response, error) { + hdr := http.Header{} + var lastErr error + for { + registryAuth, tryNext, err := getAuth(ctx, resolveAuth) + if err != nil { + // TODO(thaJeztah): should we return an "unauthorised error" here to allow the caller to try other options? + if errors.Is(err, errNoMorePrivilegeFuncs) && lastErr != nil { + return nil, lastErr + } + return nil, err + } + if registryAuth != "" { + hdr.Set(registry.AuthHeader, registryAuth) + } + resp, err := cli.post(ctx, "/images/"+imageID+"/push", query, nil, hdr) + if err == nil { + // Discard previous errors + return resp, nil + } else { + if IsErrConnectionFailed(err) { + // Don't retry if we failed to connect to the API. + return nil, err + } + + // TODO(thaJeztah); only retry with "IsUnauthorized" and/or "rate limit (StatusTooManyRequests)" errors? + if !cerrdefs.IsUnauthorized(err) { + return nil, err + } + + lastErr = err + } + if !tryNext { + return nil, lastErr + } + } } diff --git a/vendor/github.com/docker/docker/client/service_update.go b/vendor/github.com/docker/docker/client/service_update.go index 278e305d0273..9c7c3e221854 100644 --- a/vendor/github.com/docker/docker/client/service_update.go +++ b/vendor/github.com/docker/docker/client/service_update.go @@ -71,7 +71,7 @@ func (cli *Client) ServiceUpdate(ctx context.Context, serviceID string, version headers["version"] = []string{cli.version} } if options.EncodedRegistryAuth != "" { - headers[registry.AuthHeader] = []string{options.EncodedRegistryAuth} + headers.Set(registry.AuthHeader, options.EncodedRegistryAuth) } resp, err := cli.post(ctx, "/services/"+serviceID+"/update", query, service, headers) defer ensureReaderClosed(resp) diff --git a/vendor/github.com/opencontainers/image-spec/specs-go/version.go b/vendor/github.com/opencontainers/image-spec/specs-go/version.go index 7069ae44d715..c3897c7ca0c6 100644 --- a/vendor/github.com/opencontainers/image-spec/specs-go/version.go +++ b/vendor/github.com/opencontainers/image-spec/specs-go/version.go @@ -22,7 +22,7 @@ const ( // VersionMinor is for functionality in a backwards-compatible manner VersionMinor = 1 // VersionPatch is for backwards-compatible bug fixes - VersionPatch = 0 + VersionPatch = 1 // VersionDev indicates development branch. Releases will be empty string. VersionDev = "" diff --git a/vendor/modules.txt b/vendor/modules.txt index 55fe7f869848..2a7fac3b7fb1 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -65,8 +65,8 @@ 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.3.1+incompatible -## explicit +# github.com/docker/docker v28.3.1+incompatible => ../moby/ +## explicit; go 1.23.0 github.com/docker/docker/api github.com/docker/docker/api/types github.com/docker/docker/api/types/auxprogress @@ -248,7 +248,7 @@ github.com/munnerz/goautoneg # github.com/opencontainers/go-digest v1.0.0 ## explicit; go 1.13 github.com/opencontainers/go-digest -# github.com/opencontainers/image-spec v1.1.0 +# github.com/opencontainers/image-spec v1.1.1 ## explicit; go 1.18 github.com/opencontainers/image-spec/specs-go github.com/opencontainers/image-spec/specs-go/v1 @@ -367,8 +367,6 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/envconfig go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/otlpconfig go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/retry -# go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 -## explicit; go 1.22.0 # go.opentelemetry.io/otel/metric v1.35.0 ## explicit; go 1.22.0 go.opentelemetry.io/otel/metric @@ -571,6 +569,7 @@ gotest.tools/v3/internal/format gotest.tools/v3/internal/source gotest.tools/v3/poll gotest.tools/v3/skip -# tags.cncf.io/container-device-interface v0.8.0 +# tags.cncf.io/container-device-interface v1.0.1 ## explicit; go 1.20 tags.cncf.io/container-device-interface/pkg/parser +# github.com/docker/docker => ../moby/