vendor: github.com/docker/cli v28.0.2
full diff: https://github.com/docker/cli/compare/v28.0.1...v28.0.2 Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
+5
-15
@@ -3,15 +3,12 @@ package cli
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
pluginmanager "github.com/docker/cli/cli-plugins/manager"
|
||||
"github.com/docker/cli/cli-plugins/metadata"
|
||||
"github.com/docker/cli/cli/command"
|
||||
cliflags "github.com/docker/cli/cli/flags"
|
||||
"github.com/docker/docker/pkg/homedir"
|
||||
"github.com/docker/docker/registry"
|
||||
"github.com/fvbommel/sortorder"
|
||||
"github.com/moby/term"
|
||||
"github.com/morikuni/aec"
|
||||
@@ -62,13 +59,6 @@ func setupCommonRootCommand(rootCmd *cobra.Command) (*cliflags.ClientOptions, *c
|
||||
"docs.code-delimiter": `"`, // https://github.com/docker/cli-docs-tool/blob/77abede22166eaea4af7335096bdcedd043f5b19/annotation/annotation.go#L20-L22
|
||||
}
|
||||
|
||||
// Configure registry.CertsDir() when running in rootless-mode
|
||||
if os.Getenv("ROOTLESSKIT_STATE_DIR") != "" {
|
||||
if configHome, err := homedir.GetConfigHome(); err == nil {
|
||||
registry.SetCertsDir(filepath.Join(configHome, "docker/certs.d"))
|
||||
}
|
||||
}
|
||||
|
||||
return opts, helpCommand
|
||||
}
|
||||
|
||||
@@ -252,7 +242,7 @@ func hasAdditionalHelp(cmd *cobra.Command) bool {
|
||||
}
|
||||
|
||||
func isPlugin(cmd *cobra.Command) bool {
|
||||
return pluginmanager.IsPluginCommand(cmd)
|
||||
return cmd.Annotations[metadata.CommandAnnotationPlugin] == "true"
|
||||
}
|
||||
|
||||
func hasAliases(cmd *cobra.Command) bool {
|
||||
@@ -356,9 +346,9 @@ func decoratedName(cmd *cobra.Command) string {
|
||||
}
|
||||
|
||||
func vendorAndVersion(cmd *cobra.Command) string {
|
||||
if vendor, ok := cmd.Annotations[pluginmanager.CommandAnnotationPluginVendor]; ok && isPlugin(cmd) {
|
||||
if vendor, ok := cmd.Annotations[metadata.CommandAnnotationPluginVendor]; ok && isPlugin(cmd) {
|
||||
version := ""
|
||||
if v, ok := cmd.Annotations[pluginmanager.CommandAnnotationPluginVersion]; ok && v != "" {
|
||||
if v, ok := cmd.Annotations[metadata.CommandAnnotationPluginVersion]; ok && v != "" {
|
||||
version = ", " + v
|
||||
}
|
||||
return fmt.Sprintf("(%s%s)", vendor, version)
|
||||
@@ -417,7 +407,7 @@ func invalidPlugins(cmd *cobra.Command) []*cobra.Command {
|
||||
}
|
||||
|
||||
func invalidPluginReason(cmd *cobra.Command) string {
|
||||
return cmd.Annotations[pluginmanager.CommandAnnotationPluginInvalid]
|
||||
return cmd.Annotations[metadata.CommandAnnotationPluginInvalid]
|
||||
}
|
||||
|
||||
const usageTemplate = `Usage:
|
||||
|
||||
+16
-43
@@ -8,7 +8,6 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"sync"
|
||||
@@ -21,21 +20,15 @@ import (
|
||||
"github.com/docker/cli/cli/context/store"
|
||||
"github.com/docker/cli/cli/debug"
|
||||
cliflags "github.com/docker/cli/cli/flags"
|
||||
manifeststore "github.com/docker/cli/cli/manifest/store"
|
||||
registryclient "github.com/docker/cli/cli/registry/client"
|
||||
"github.com/docker/cli/cli/streams"
|
||||
"github.com/docker/cli/cli/trust"
|
||||
"github.com/docker/cli/cli/version"
|
||||
dopts "github.com/docker/cli/opts"
|
||||
"github.com/docker/docker/api"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/registry"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/docker/go-connections/tlsconfig"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
notaryclient "github.com/theupdateframework/notary/client"
|
||||
)
|
||||
|
||||
const defaultInitTimeout = 2 * time.Second
|
||||
@@ -53,19 +46,18 @@ type Cli interface {
|
||||
Streams
|
||||
SetIn(in *streams.In)
|
||||
Apply(ops ...CLIOption) error
|
||||
ConfigFile() *configfile.ConfigFile
|
||||
config.Provider
|
||||
ServerInfo() ServerInfo
|
||||
NotaryClient(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (notaryclient.Repository, error)
|
||||
DefaultVersion() string
|
||||
CurrentVersion() string
|
||||
ManifestStore() manifeststore.Store
|
||||
RegistryClient(bool) registryclient.RegistryClient
|
||||
ContentTrustEnabled() bool
|
||||
BuildKitEnabled() (bool, error)
|
||||
ContextStore() store.Store
|
||||
CurrentContext() string
|
||||
DockerEndpoint() docker.Endpoint
|
||||
TelemetryClient
|
||||
DeprecatedNotaryClient
|
||||
DeprecatedManifestClient
|
||||
}
|
||||
|
||||
// DockerCli is an instance the docker command line client.
|
||||
@@ -96,7 +88,7 @@ type DockerCli struct {
|
||||
enableGlobalMeter, enableGlobalTracer bool
|
||||
}
|
||||
|
||||
// DefaultVersion returns api.defaultVersion.
|
||||
// DefaultVersion returns [api.DefaultVersion].
|
||||
func (*DockerCli) DefaultVersion() string {
|
||||
return api.DefaultVersion
|
||||
}
|
||||
@@ -202,16 +194,16 @@ func (cli *DockerCli) BuildKitEnabled() (bool, error) {
|
||||
|
||||
// HooksEnabled returns whether plugin hooks are enabled.
|
||||
func (cli *DockerCli) HooksEnabled() bool {
|
||||
// legacy support DOCKER_CLI_HINTS env var
|
||||
if v := os.Getenv("DOCKER_CLI_HINTS"); v != "" {
|
||||
// use DOCKER_CLI_HOOKS env var value if set and not empty
|
||||
if v := os.Getenv("DOCKER_CLI_HOOKS"); v != "" {
|
||||
enabled, err := strconv.ParseBool(v)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return enabled
|
||||
}
|
||||
// use DOCKER_CLI_HOOKS env var value if set and not empty
|
||||
if v := os.Getenv("DOCKER_CLI_HOOKS"); v != "" {
|
||||
// legacy support DOCKER_CLI_HINTS env var
|
||||
if v := os.Getenv("DOCKER_CLI_HINTS"); v != "" {
|
||||
enabled, err := strconv.ParseBool(v)
|
||||
if err != nil {
|
||||
return false
|
||||
@@ -230,21 +222,6 @@ func (cli *DockerCli) HooksEnabled() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// ManifestStore returns a store for local manifests
|
||||
func (*DockerCli) ManifestStore() manifeststore.Store {
|
||||
// TODO: support override default location from config file
|
||||
return manifeststore.NewStore(filepath.Join(config.Dir(), "manifests"))
|
||||
}
|
||||
|
||||
// RegistryClient returns a client for communicating with a Docker distribution
|
||||
// registry
|
||||
func (cli *DockerCli) RegistryClient(allowInsecure bool) registryclient.RegistryClient {
|
||||
resolver := func(ctx context.Context, index *registry.IndexInfo) registry.AuthConfig {
|
||||
return ResolveAuthConfig(cli.ConfigFile(), index)
|
||||
}
|
||||
return registryclient.NewRegistryClient(resolver, UserAgent(), allowInsecure)
|
||||
}
|
||||
|
||||
// WithInitializeClient is passed to DockerCli.Initialize by callers who wish to set a particular API Client for use by the CLI.
|
||||
func WithInitializeClient(makeClient func(dockerCli *DockerCli) (client.APIClient, error)) CLIOption {
|
||||
return func(dockerCli *DockerCli) error {
|
||||
@@ -292,6 +269,7 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions, ops ...CLIOption)
|
||||
if cli.enableGlobalTracer {
|
||||
cli.createGlobalTracerProvider(cli.baseCtx)
|
||||
}
|
||||
filterResourceAttributesEnvvar()
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -345,7 +323,10 @@ func resolveDockerEndpoint(s store.Reader, contextName string) (docker.Endpoint,
|
||||
|
||||
// Resolve the Docker endpoint for the default context (based on config, env vars and CLI flags)
|
||||
func resolveDefaultDockerEndpoint(opts *cliflags.ClientOptions) (docker.Endpoint, error) {
|
||||
host, err := getServerHost(opts.Hosts, opts.TLSOptions)
|
||||
// defaultToTLS determines whether we should use a TLS host as default
|
||||
// if nothing was configured by the user.
|
||||
defaultToTLS := opts.TLSOptions != nil
|
||||
host, err := getServerHost(opts.Hosts, defaultToTLS)
|
||||
if err != nil {
|
||||
return docker.Endpoint{}, err
|
||||
}
|
||||
@@ -403,11 +384,6 @@ func (cli *DockerCli) initializeFromClient() {
|
||||
cli.client.NegotiateAPIVersionPing(ping)
|
||||
}
|
||||
|
||||
// NotaryClient provides a Notary Repository to interact with signed metadata for an image
|
||||
func (cli *DockerCli) NotaryClient(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (notaryclient.Repository, error) {
|
||||
return trust.GetNotaryRepository(cli.In(), cli.Out(), UserAgent(), imgRefAndAuth.RepoInfo(), imgRefAndAuth.AuthConfig(), actions...)
|
||||
}
|
||||
|
||||
// ContextStore returns the ContextStore
|
||||
func (cli *DockerCli) ContextStore() store.Store {
|
||||
return cli.contextStore
|
||||
@@ -553,18 +529,15 @@ func NewDockerCli(ops ...CLIOption) (*DockerCli, error) {
|
||||
return cli, nil
|
||||
}
|
||||
|
||||
func getServerHost(hosts []string, tlsOptions *tlsconfig.Options) (string, error) {
|
||||
var host string
|
||||
func getServerHost(hosts []string, defaultToTLS bool) (string, error) {
|
||||
switch len(hosts) {
|
||||
case 0:
|
||||
host = os.Getenv(client.EnvOverrideHost)
|
||||
return dopts.ParseHost(defaultToTLS, os.Getenv(client.EnvOverrideHost))
|
||||
case 1:
|
||||
host = hosts[0]
|
||||
return dopts.ParseHost(defaultToTLS, hosts[0])
|
||||
default:
|
||||
return "", errors.New("Specify only one -H")
|
||||
}
|
||||
|
||||
return dopts.ParseHost(tlsOptions != nil, host)
|
||||
}
|
||||
|
||||
// UserAgent returns the user agent string used for making API requests
|
||||
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/docker/cli/cli/config"
|
||||
manifeststore "github.com/docker/cli/cli/manifest/store"
|
||||
registryclient "github.com/docker/cli/cli/registry/client"
|
||||
"github.com/docker/cli/cli/trust"
|
||||
"github.com/docker/docker/api/types/registry"
|
||||
notaryclient "github.com/theupdateframework/notary/client"
|
||||
)
|
||||
|
||||
type DeprecatedNotaryClient interface {
|
||||
// NotaryClient provides a Notary Repository to interact with signed metadata for an image
|
||||
//
|
||||
// Deprecated: use [trust.GetNotaryRepository] instead. This method is no longer used and will be removed in the next release.
|
||||
NotaryClient(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (notaryclient.Repository, error)
|
||||
}
|
||||
|
||||
type DeprecatedManifestClient interface {
|
||||
// ManifestStore returns a store for local manifests
|
||||
//
|
||||
// Deprecated: use [manifeststore.NewStore] instead. This method is no longer used and will be removed in the next release.
|
||||
ManifestStore() manifeststore.Store
|
||||
|
||||
// RegistryClient returns a client for communicating with a Docker distribution
|
||||
// registry.
|
||||
//
|
||||
// Deprecated: use [registryclient.NewRegistryClient]. This method is no longer used and will be removed in the next release.
|
||||
RegistryClient(bool) registryclient.RegistryClient
|
||||
}
|
||||
|
||||
// NotaryClient provides a Notary Repository to interact with signed metadata for an image
|
||||
func (cli *DockerCli) NotaryClient(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (notaryclient.Repository, error) {
|
||||
return trust.GetNotaryRepository(cli.In(), cli.Out(), UserAgent(), imgRefAndAuth.RepoInfo(), imgRefAndAuth.AuthConfig(), actions...)
|
||||
}
|
||||
|
||||
// ManifestStore returns a store for local manifests
|
||||
//
|
||||
// Deprecated: use [manifeststore.NewStore] instead. This method is no longer used and will be removed in the next release.
|
||||
func (*DockerCli) ManifestStore() manifeststore.Store {
|
||||
return manifeststore.NewStore(filepath.Join(config.Dir(), "manifests"))
|
||||
}
|
||||
|
||||
// RegistryClient returns a client for communicating with a Docker distribution
|
||||
// registry
|
||||
//
|
||||
// Deprecated: use [registryclient.NewRegistryClient]. This method is no longer used and will be removed in the next release.
|
||||
func (cli *DockerCli) RegistryClient(allowInsecure bool) registryclient.RegistryClient {
|
||||
resolver := func(ctx context.Context, index *registry.IndexInfo) registry.AuthConfig {
|
||||
return ResolveAuthConfig(cli.ConfigFile(), index)
|
||||
}
|
||||
return registryclient.NewRegistryClient(resolver, UserAgent(), allowInsecure)
|
||||
}
|
||||
+29
@@ -1,6 +1,11 @@
|
||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.22
|
||||
|
||||
package formatter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"golang.org/x/text/width"
|
||||
@@ -59,3 +64,27 @@ func Ellipsis(s string, maxDisplayWidth int) string {
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// capitalizeFirst capitalizes the first character of string
|
||||
func capitalizeFirst(s string) string {
|
||||
switch l := len(s); l {
|
||||
case 0:
|
||||
return s
|
||||
case 1:
|
||||
return strings.ToLower(s)
|
||||
default:
|
||||
return strings.ToUpper(string(s[0])) + strings.ToLower(s[1:])
|
||||
}
|
||||
}
|
||||
|
||||
// PrettyPrint outputs arbitrary data for human formatted output by uppercasing the first letter.
|
||||
func PrettyPrint(i any) string {
|
||||
switch t := i.(type) {
|
||||
case nil:
|
||||
return "None"
|
||||
case string:
|
||||
return capitalizeFirst(t)
|
||||
default:
|
||||
return capitalizeFirst(fmt.Sprintf("%s", t))
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -76,9 +76,9 @@ func (c *Context) preFormat() {
|
||||
func (c *Context) parseFormat() (*template.Template, error) {
|
||||
tmpl, err := templates.Parse(c.finalFormat)
|
||||
if err != nil {
|
||||
return tmpl, errors.Wrap(err, "template parsing error")
|
||||
return nil, errors.Wrap(err, "template parsing error")
|
||||
}
|
||||
return tmpl, err
|
||||
return tmpl, nil
|
||||
}
|
||||
|
||||
func (c *Context) postFormat(tmpl *template.Template, subContext SubContext) {
|
||||
|
||||
+49
-2
@@ -4,10 +4,11 @@ import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/docker/distribution/uuid"
|
||||
"github.com/google/uuid"
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/metric"
|
||||
sdkmetric "go.opentelemetry.io/otel/sdk/metric"
|
||||
@@ -142,7 +143,7 @@ func defaultResourceOptions() []resource.Option {
|
||||
// of the CLI is its own instance. Without this, downstream
|
||||
// OTEL processors may think the same process is restarting
|
||||
// continuously.
|
||||
semconv.ServiceInstanceID(uuid.Generate().String()),
|
||||
semconv.ServiceInstanceID(uuid.NewString()),
|
||||
),
|
||||
resource.WithFromEnv(),
|
||||
resource.WithTelemetrySDK(),
|
||||
@@ -216,3 +217,49 @@ func (r *cliReader) ForceFlush(ctx context.Context) error {
|
||||
func deltaTemporality(_ sdkmetric.InstrumentKind) metricdata.Temporality {
|
||||
return metricdata.DeltaTemporality
|
||||
}
|
||||
|
||||
// resourceAttributesEnvVar is the name of the envvar that includes additional
|
||||
// resource attributes for OTEL as defined in the [OpenTelemetry specification].
|
||||
//
|
||||
// [OpenTelemetry specification]: https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/#general-sdk-configuration
|
||||
const resourceAttributesEnvVar = "OTEL_RESOURCE_ATTRIBUTES"
|
||||
|
||||
func filterResourceAttributesEnvvar() {
|
||||
if v := os.Getenv(resourceAttributesEnvVar); v != "" {
|
||||
if filtered := filterResourceAttributes(v); filtered != "" {
|
||||
_ = os.Setenv(resourceAttributesEnvVar, filtered)
|
||||
} else {
|
||||
_ = os.Unsetenv(resourceAttributesEnvVar)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// dockerCLIAttributePrefix is the prefix for any docker cli OTEL attributes.
|
||||
// When updating, make sure to also update the copy in cli-plugins/manager.
|
||||
//
|
||||
// TODO(thaJeztah): move telemetry-related code to an (internal) package to reduce dependency on cli/command in cli-plugins, which has too many imports.
|
||||
const dockerCLIAttributePrefix = "docker.cli."
|
||||
|
||||
func filterResourceAttributes(s string) string {
|
||||
if trimmed := strings.TrimSpace(s); trimmed == "" {
|
||||
return trimmed
|
||||
}
|
||||
|
||||
pairs := strings.Split(s, ",")
|
||||
elems := make([]string, 0, len(pairs))
|
||||
for _, p := range pairs {
|
||||
k, _, found := strings.Cut(p, "=")
|
||||
if !found {
|
||||
// Do not interact with invalid otel resources.
|
||||
elems = append(elems, p)
|
||||
continue
|
||||
}
|
||||
|
||||
// Skip attributes that have our docker.cli prefix.
|
||||
if strings.HasPrefix(k, dockerCLIAttributePrefix) {
|
||||
continue
|
||||
}
|
||||
elems = append(elems, p)
|
||||
}
|
||||
return strings.Join(elems, ",")
|
||||
}
|
||||
|
||||
+5
-74
@@ -13,10 +13,9 @@ import (
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/cli/cli/config"
|
||||
"github.com/docker/cli/cli/streams"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
mounttypes "github.com/docker/docker/api/types/mount"
|
||||
"github.com/docker/docker/api/types/versions"
|
||||
"github.com/docker/docker/errdefs"
|
||||
"github.com/moby/sys/sequential"
|
||||
"github.com/moby/term"
|
||||
@@ -51,30 +50,6 @@ func CopyToFile(outfile string, r io.Reader) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// capitalizeFirst capitalizes the first character of string
|
||||
func capitalizeFirst(s string) string {
|
||||
switch l := len(s); l {
|
||||
case 0:
|
||||
return s
|
||||
case 1:
|
||||
return strings.ToLower(s)
|
||||
default:
|
||||
return strings.ToUpper(string(s[0])) + strings.ToLower(s[1:])
|
||||
}
|
||||
}
|
||||
|
||||
// PrettyPrint outputs arbitrary data for human formatted output by uppercasing the first letter.
|
||||
func PrettyPrint(i any) string {
|
||||
switch t := i.(type) {
|
||||
case nil:
|
||||
return "None"
|
||||
case string:
|
||||
return capitalizeFirst(t)
|
||||
default:
|
||||
return capitalizeFirst(fmt.Sprintf("%s", t))
|
||||
}
|
||||
}
|
||||
|
||||
var ErrPromptTerminated = errdefs.Cancelled(errors.New("prompt terminated"))
|
||||
|
||||
// DisableInputEcho disables input echo on the provided streams.In.
|
||||
@@ -166,11 +141,12 @@ func PromptForConfirmation(ctx context.Context, ins io.Reader, outs io.Writer, m
|
||||
}
|
||||
|
||||
// PruneFilters returns consolidated prune filters obtained from config.json and cli
|
||||
func PruneFilters(dockerCli Cli, pruneFilters filters.Args) filters.Args {
|
||||
if dockerCli.ConfigFile() == nil {
|
||||
func PruneFilters(dockerCLI config.Provider, pruneFilters filters.Args) filters.Args {
|
||||
cfg := dockerCLI.ConfigFile()
|
||||
if cfg == nil {
|
||||
return pruneFilters
|
||||
}
|
||||
for _, f := range dockerCli.ConfigFile().PruneFilters {
|
||||
for _, f := range cfg.PruneFilters {
|
||||
k, v, ok := strings.Cut(f, "=")
|
||||
if !ok {
|
||||
continue
|
||||
@@ -239,48 +215,3 @@ func ValidateOutputPathFileMode(fileMode os.FileMode) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func stringSliceIndex(s, subs []string) int {
|
||||
j := 0
|
||||
if len(subs) > 0 {
|
||||
for i, x := range s {
|
||||
if j < len(subs) && subs[j] == x {
|
||||
j++
|
||||
} else {
|
||||
j = 0
|
||||
}
|
||||
if len(subs) == j {
|
||||
return i + 1 - j
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// StringSliceReplaceAt replaces the sub-slice find, with the sub-slice replace, in the string
|
||||
// slice s, returning a new slice and a boolean indicating if the replacement happened.
|
||||
// requireIdx is the index at which old needs to be found at (or -1 to disregard that).
|
||||
func StringSliceReplaceAt(s, find, replace []string, requireIndex int) ([]string, bool) {
|
||||
idx := stringSliceIndex(s, find)
|
||||
if (requireIndex != -1 && requireIndex != idx) || idx == -1 {
|
||||
return s, false
|
||||
}
|
||||
out := append([]string{}, s[:idx]...)
|
||||
out = append(out, replace...)
|
||||
out = append(out, s[idx+len(find):]...)
|
||||
return out, true
|
||||
}
|
||||
|
||||
// ValidateMountWithAPIVersion validates a mount with the server API version.
|
||||
func ValidateMountWithAPIVersion(m mounttypes.Mount, serverAPIVersion string) error {
|
||||
if m.BindOptions != nil {
|
||||
if m.BindOptions.NonRecursive && versions.LessThan(serverAPIVersion, "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(serverAPIVersion, "1.44") {
|
||||
return errors.Errorf("bind-recursive=readonly requires API v1.44 or later")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
+5
@@ -69,6 +69,11 @@ func getHomeDir() string {
|
||||
return home
|
||||
}
|
||||
|
||||
// Provider defines an interface for providing the CLI config.
|
||||
type Provider interface {
|
||||
ConfigFile() *configfile.ConfigFile
|
||||
}
|
||||
|
||||
// Dir returns the directory the configuration file is stored in
|
||||
func Dir() string {
|
||||
initConfigDir.Do(func() {
|
||||
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
package jsonstream
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"github.com/docker/docker/pkg/jsonmessage"
|
||||
)
|
||||
|
||||
type (
|
||||
Stream = jsonmessage.Stream
|
||||
JSONMessage = jsonmessage.JSONMessage
|
||||
JSONError = jsonmessage.JSONError
|
||||
JSONProgress = jsonmessage.JSONProgress
|
||||
)
|
||||
|
||||
type ctxReader struct {
|
||||
err chan error
|
||||
r io.Reader
|
||||
}
|
||||
|
||||
func (r *ctxReader) Read(p []byte) (n int, err error) {
|
||||
select {
|
||||
case err = <-r.err:
|
||||
return 0, err
|
||||
default:
|
||||
return r.r.Read(p)
|
||||
}
|
||||
}
|
||||
|
||||
type Options func(*options)
|
||||
|
||||
type options struct {
|
||||
AuxCallback func(JSONMessage)
|
||||
}
|
||||
|
||||
func WithAuxCallback(cb func(JSONMessage)) Options {
|
||||
return func(o *options) {
|
||||
o.AuxCallback = cb
|
||||
}
|
||||
}
|
||||
|
||||
// Display prints the JSON messages from the given reader to the given stream.
|
||||
//
|
||||
// It wraps the [jsonmessage.DisplayJSONMessagesStream] function to make it
|
||||
// "context aware" and appropriately returns why the function was canceled.
|
||||
//
|
||||
// It returns an error if the context is canceled, but not if the input reader / stream is closed.
|
||||
func Display(ctx context.Context, in io.Reader, stream Stream, opts ...Options) error {
|
||||
if ctx.Err() != nil {
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
reader := &ctxReader{err: make(chan error, 1), r: in}
|
||||
stopFunc := context.AfterFunc(ctx, func() { reader.err <- ctx.Err() })
|
||||
defer stopFunc()
|
||||
|
||||
o := options{}
|
||||
for _, opt := range opts {
|
||||
opt(&o)
|
||||
}
|
||||
|
||||
if err := jsonmessage.DisplayJSONMessagesStream(reader, stream, stream.FD(), stream.IsTerminal(), o.AuxCallback); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ctx.Err()
|
||||
}
|
||||
+16
-17
@@ -8,7 +8,6 @@ import (
|
||||
|
||||
"github.com/distribution/reference"
|
||||
manifesttypes "github.com/docker/cli/cli/manifest/types"
|
||||
"github.com/docker/cli/cli/trust"
|
||||
"github.com/docker/distribution"
|
||||
distributionclient "github.com/docker/distribution/registry/client"
|
||||
registrytypes "github.com/docker/docker/api/types/registry"
|
||||
@@ -38,12 +37,6 @@ func NewRegistryClient(resolver AuthConfigResolver, userAgent string, insecure b
|
||||
// AuthConfigResolver returns Auth Configuration for an index
|
||||
type AuthConfigResolver func(ctx context.Context, index *registrytypes.IndexInfo) registrytypes.AuthConfig
|
||||
|
||||
// PutManifestOptions is the data sent to push a manifest
|
||||
type PutManifestOptions struct {
|
||||
MediaType string
|
||||
Payload []byte
|
||||
}
|
||||
|
||||
type client struct {
|
||||
authConfigResolver AuthConfigResolver
|
||||
insecureRegistry bool
|
||||
@@ -61,13 +54,13 @@ func (err ErrBlobCreated) Error() string {
|
||||
err.From, err.Target)
|
||||
}
|
||||
|
||||
// ErrHTTPProto returned if attempting to use TLS with a non-TLS registry
|
||||
type ErrHTTPProto struct {
|
||||
OrigErr string
|
||||
// httpProtoError returned if attempting to use TLS with a non-TLS registry
|
||||
type httpProtoError struct {
|
||||
cause error
|
||||
}
|
||||
|
||||
func (err ErrHTTPProto) Error() string {
|
||||
return err.OrigErr
|
||||
func (e httpProtoError) Error() string {
|
||||
return e.cause.Error()
|
||||
}
|
||||
|
||||
var _ RegistryClient = &client{}
|
||||
@@ -78,7 +71,7 @@ func (c *client) MountBlob(ctx context.Context, sourceRef reference.Canonical, t
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
repoEndpoint.actions = trust.ActionsPushAndPull
|
||||
repoEndpoint.actions = []string{"pull", "push"}
|
||||
repo, err := c.getRepositoryForReference(ctx, targetRef, repoEndpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -104,7 +97,7 @@ func (c *client) PutManifest(ctx context.Context, ref reference.Named, manifest
|
||||
return "", err
|
||||
}
|
||||
|
||||
repoEndpoint.actions = trust.ActionsPushAndPull
|
||||
repoEndpoint.actions = []string{"pull", "push"}
|
||||
repo, err := c.getRepositoryForReference(ctx, ref, repoEndpoint)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -121,7 +114,10 @@ func (c *client) PutManifest(ctx context.Context, ref reference.Named, manifest
|
||||
}
|
||||
|
||||
dgst, err := manifestService.Put(ctx, manifest, opts...)
|
||||
return dgst, errors.Wrapf(err, "failed to put manifest %s", ref)
|
||||
if err != nil {
|
||||
return dgst, errors.Wrapf(err, "failed to put manifest %s", ref)
|
||||
}
|
||||
return dgst, nil
|
||||
}
|
||||
|
||||
func (c *client) getRepositoryForReference(ctx context.Context, ref reference.Named, repoEndpoint repositoryEndpoint) (distribution.Repository, error) {
|
||||
@@ -135,7 +131,7 @@ func (c *client) getRepositoryForReference(ctx context.Context, ref reference.Na
|
||||
return nil, err
|
||||
}
|
||||
if !repoEndpoint.endpoint.TLSConfig.InsecureSkipVerify {
|
||||
return nil, ErrHTTPProto{OrigErr: err.Error()}
|
||||
return nil, httpProtoError{cause: err}
|
||||
}
|
||||
// --insecure was set; fall back to plain HTTP
|
||||
if url := repoEndpoint.endpoint.URL; url != nil && url.Scheme == "https" {
|
||||
@@ -157,7 +153,10 @@ func (c *client) getHTTPTransportForRepoEndpoint(ctx context.Context, repoEndpoi
|
||||
c.userAgent,
|
||||
repoEndpoint.actions,
|
||||
)
|
||||
return httpTransport, errors.Wrap(err, "failed to configure transport")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to configure transport")
|
||||
}
|
||||
return httpTransport, nil
|
||||
}
|
||||
|
||||
// GetManifest returns an ImageManifest for the reference
|
||||
|
||||
+6
-13
@@ -6,7 +6,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/cli/cli/trust"
|
||||
"github.com/docker/distribution/registry/client/auth"
|
||||
"github.com/docker/distribution/registry/client/transport"
|
||||
registrytypes "github.com/docker/docker/api/types/registry"
|
||||
@@ -31,10 +30,7 @@ func (r repositoryEndpoint) BaseURL() string {
|
||||
}
|
||||
|
||||
func newDefaultRepositoryEndpoint(ref reference.Named, insecure bool) (repositoryEndpoint, error) {
|
||||
repoInfo, err := registry.ParseRepositoryInfo(ref)
|
||||
if err != nil {
|
||||
return repositoryEndpoint{}, err
|
||||
}
|
||||
repoInfo, _ := registry.ParseRepositoryInfo(ref)
|
||||
endpoint, err := getDefaultEndpointFromRepoInfo(repoInfo)
|
||||
if err != nil {
|
||||
return repositoryEndpoint{}, err
|
||||
@@ -94,7 +90,7 @@ func getHTTPTransport(authConfig registrytypes.AuthConfig, endpoint registry.API
|
||||
modifiers = append(modifiers, auth.NewAuthorizer(challengeManager, passThruTokenHandler))
|
||||
} else {
|
||||
if len(actions) == 0 {
|
||||
actions = trust.ActionsPullOnly
|
||||
actions = []string{"pull"}
|
||||
}
|
||||
creds := registry.NewStaticCredentialStore(&authConfig)
|
||||
tokenHandler := auth.NewTokenHandler(authTransport, creds, repoName, actions...)
|
||||
@@ -104,14 +100,11 @@ func getHTTPTransport(authConfig registrytypes.AuthConfig, endpoint registry.API
|
||||
return transport.NewTransport(base, modifiers...), nil
|
||||
}
|
||||
|
||||
// RepoNameForReference returns the repository name from a reference
|
||||
// RepoNameForReference returns the repository name from a reference.
|
||||
//
|
||||
// Deprecated: this function is no longer used and will be removed in the next release.
|
||||
func RepoNameForReference(ref reference.Named) (string, error) {
|
||||
// insecure is fine since this only returns the name
|
||||
repo, err := newDefaultRepositoryEndpoint(ref, false)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return repo.Name(), nil
|
||||
return reference.Path(reference.TrimNamed(ref)), nil
|
||||
}
|
||||
|
||||
type existingTokenHandler struct {
|
||||
|
||||
+4
-10
@@ -220,10 +220,7 @@ func (c *client) iterateEndpoints(ctx context.Context, namedRef reference.Named,
|
||||
return err
|
||||
}
|
||||
|
||||
repoInfo, err := registry.ParseRepositoryInfo(namedRef)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
repoInfo, _ := registry.ParseRepositoryInfo(namedRef)
|
||||
|
||||
confirmedTLSRegistries := make(map[string]bool)
|
||||
for _, endpoint := range endpoints {
|
||||
@@ -241,7 +238,8 @@ func (c *client) iterateEndpoints(ctx context.Context, namedRef reference.Named,
|
||||
repo, err := c.getRepositoryForReference(ctx, namedRef, repoEndpoint)
|
||||
if err != nil {
|
||||
logrus.Debugf("error %s with repo endpoint %+v", err, repoEndpoint)
|
||||
if _, ok := err.(ErrHTTPProto); ok {
|
||||
var protoErr httpProtoError
|
||||
if errors.As(err, &protoErr) {
|
||||
continue
|
||||
}
|
||||
return err
|
||||
@@ -272,11 +270,6 @@ func (c *client) iterateEndpoints(ctx context.Context, namedRef reference.Named,
|
||||
|
||||
// allEndpoints returns a list of endpoints ordered by priority (v2, http).
|
||||
func allEndpoints(namedRef reference.Named, insecure bool) ([]registry.APIEndpoint, error) {
|
||||
repoInfo, err := registry.ParseRepositoryInfo(namedRef)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var serviceOpts registry.ServiceOptions
|
||||
if insecure {
|
||||
logrus.Debugf("allowing insecure registry for: %s", reference.Domain(namedRef))
|
||||
@@ -286,6 +279,7 @@ func allEndpoints(namedRef reference.Named, insecure bool) ([]registry.APIEndpoi
|
||||
if err != nil {
|
||||
return []registry.APIEndpoint{}, err
|
||||
}
|
||||
repoInfo, _ := registry.ParseRepositoryInfo(namedRef)
|
||||
endpoints, err := registryService.LookupPullEndpoints(reference.Domain(repoInfo.Name))
|
||||
logrus.Debugf("endpoints for %s: %v", namedRef, endpoints)
|
||||
return endpoints, err
|
||||
|
||||
+18
-7
@@ -40,10 +40,11 @@ var (
|
||||
ActionsPullOnly = []string{"pull"}
|
||||
// ActionsPushAndPull defines the actions for read-write interactions with a Notary Repository
|
||||
ActionsPushAndPull = []string{"pull", "push"}
|
||||
// NotaryServer is the endpoint serving the Notary trust server
|
||||
NotaryServer = "https://notary.docker.io"
|
||||
)
|
||||
|
||||
// NotaryServer is the endpoint serving the Notary trust server
|
||||
const NotaryServer = "https://notary.docker.io"
|
||||
|
||||
// GetTrustDirectory returns the base trust directory name
|
||||
func GetTrustDirectory() string {
|
||||
return filepath.Join(config.Dir(), "trust")
|
||||
@@ -238,6 +239,20 @@ func NotaryError(repoName string, err error) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// AddToAllSignableRoles attempts to add the image target to all the top level
|
||||
// delegation roles we can (based on whether we have the signing key and whether
|
||||
// the role's path allows us to).
|
||||
//
|
||||
// If there are no delegation roles, we add to the targets role.
|
||||
func AddToAllSignableRoles(repo client.Repository, target *client.Target) error {
|
||||
signableRoles, err := GetSignableRoles(repo, target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return repo.AddTarget(target, signableRoles...)
|
||||
}
|
||||
|
||||
// GetSignableRoles returns a list of roles for which we have valid signing
|
||||
// keys, given a notary repository and a target
|
||||
func GetSignableRoles(repo client.Repository, target *client.Target) ([]data.RoleName, error) {
|
||||
@@ -307,11 +322,7 @@ func GetImageReferencesAndAuth(ctx context.Context,
|
||||
}
|
||||
|
||||
// Resolve the Repository name from fqn to RepositoryInfo
|
||||
repoInfo, err := registry.ParseRepositoryInfo(ref)
|
||||
if err != nil {
|
||||
return ImageRefAndAuth{}, err
|
||||
}
|
||||
|
||||
repoInfo, _ := registry.ParseRepositoryInfo(ref)
|
||||
authConfig := authResolver(ctx, repoInfo.Index)
|
||||
return ImageRefAndAuth{
|
||||
original: imgName,
|
||||
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
package trust
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/cli/cli/internal/jsonstream"
|
||||
"github.com/docker/cli/cli/streams"
|
||||
"github.com/docker/docker/api/types"
|
||||
registrytypes "github.com/docker/docker/api/types/registry"
|
||||
"github.com/docker/docker/registry"
|
||||
"github.com/opencontainers/go-digest"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/theupdateframework/notary/client"
|
||||
"github.com/theupdateframework/notary/tuf/data"
|
||||
)
|
||||
|
||||
// Streams is an interface which exposes the standard input and output streams.
|
||||
//
|
||||
// Same interface as [github.com/docker/cli/cli/command.Streams] but defined here to prevent a circular import.
|
||||
type Streams interface {
|
||||
In() *streams.In
|
||||
Out() *streams.Out
|
||||
Err() *streams.Out
|
||||
}
|
||||
|
||||
// PushTrustedReference pushes a canonical reference to the trust server.
|
||||
//
|
||||
//nolint:gocyclo
|
||||
func PushTrustedReference(ctx context.Context, ioStreams Streams, repoInfo *registry.RepositoryInfo, ref reference.Named, authConfig registrytypes.AuthConfig, in io.Reader, userAgent string) error {
|
||||
// If it is a trusted push we would like to find the target entry which match the
|
||||
// tag provided in the function and then do an AddTarget later.
|
||||
notaryTarget := &client.Target{}
|
||||
// Count the times of calling for handleTarget,
|
||||
// if it is called more that once, that should be considered an error in a trusted push.
|
||||
cnt := 0
|
||||
handleTarget := func(msg jsonstream.JSONMessage) {
|
||||
cnt++
|
||||
if cnt > 1 {
|
||||
// handleTarget should only be called once. This will be treated as an error.
|
||||
return
|
||||
}
|
||||
|
||||
var pushResult types.PushResult
|
||||
err := json.Unmarshal(*msg.Aux, &pushResult)
|
||||
if err == nil && pushResult.Tag != "" {
|
||||
if dgst, err := digest.Parse(pushResult.Digest); err == nil {
|
||||
h, err := hex.DecodeString(dgst.Hex())
|
||||
if err != nil {
|
||||
notaryTarget = nil
|
||||
return
|
||||
}
|
||||
notaryTarget.Name = pushResult.Tag
|
||||
notaryTarget.Hashes = data.Hashes{string(dgst.Algorithm()): h}
|
||||
notaryTarget.Length = int64(pushResult.Size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var tag string
|
||||
switch x := ref.(type) {
|
||||
case reference.Canonical:
|
||||
return errors.New("cannot push a digest reference")
|
||||
case reference.NamedTagged:
|
||||
tag = x.Tag()
|
||||
default:
|
||||
// We want trust signatures to always take an explicit tag,
|
||||
// otherwise it will act as an untrusted push.
|
||||
if err := jsonstream.Display(ctx, in, ioStreams.Out()); err != nil {
|
||||
return err
|
||||
}
|
||||
_, _ = fmt.Fprintln(ioStreams.Err(), "No tag specified, skipping trust metadata push")
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := jsonstream.Display(ctx, in, ioStreams.Out(), jsonstream.WithAuxCallback(handleTarget)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if cnt > 1 {
|
||||
return errors.Errorf("internal error: only one call to handleTarget expected")
|
||||
}
|
||||
|
||||
if notaryTarget == nil {
|
||||
return errors.Errorf("no targets found, provide a specific tag in order to sign it")
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprintln(ioStreams.Out(), "Signing and pushing trust metadata")
|
||||
|
||||
repo, err := GetNotaryRepository(ioStreams.In(), ioStreams.Out(), userAgent, repoInfo, &authConfig, "push", "pull")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error establishing connection to trust repository")
|
||||
}
|
||||
|
||||
// get the latest repository metadata so we can figure out which roles to sign
|
||||
_, err = repo.ListTargets()
|
||||
|
||||
switch err.(type) {
|
||||
case client.ErrRepoNotInitialized, client.ErrRepositoryNotExist:
|
||||
keys := repo.GetCryptoService().ListKeys(data.CanonicalRootRole)
|
||||
var rootKeyID string
|
||||
// always select the first root key
|
||||
if len(keys) > 0 {
|
||||
sort.Strings(keys)
|
||||
rootKeyID = keys[0]
|
||||
} else {
|
||||
rootPublicKey, err := repo.GetCryptoService().Create(data.CanonicalRootRole, "", data.ECDSAKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rootKeyID = rootPublicKey.ID()
|
||||
}
|
||||
|
||||
// Initialize the notary repository with a remotely managed snapshot key
|
||||
if err := repo.Initialize([]string{rootKeyID}, data.CanonicalSnapshotRole); err != nil {
|
||||
return NotaryError(repoInfo.Name.Name(), err)
|
||||
}
|
||||
_, _ = fmt.Fprintf(ioStreams.Out(), "Finished initializing %q\n", repoInfo.Name.Name())
|
||||
err = repo.AddTarget(notaryTarget, data.CanonicalTargetsRole)
|
||||
case nil:
|
||||
// already initialized and we have successfully downloaded the latest metadata
|
||||
err = AddToAllSignableRoles(repo, notaryTarget)
|
||||
default:
|
||||
return NotaryError(repoInfo.Name.Name(), err)
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
err = repo.Publish()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
err = errors.Wrapf(err, "failed to sign %s:%s", repoInfo.Name.Name(), tag)
|
||||
return NotaryError(repoInfo.Name.Name(), err)
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprintf(ioStreams.Out(), "Successfully signed %s:%s\n", repoInfo.Name.Name(), tag)
|
||||
return nil
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package trust
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/docker/client"
|
||||
)
|
||||
|
||||
// TagTrusted tags a trusted ref. It is a shallow wrapper around [client.Client.ImageTag]
|
||||
// that updates the given image references to their familiar format for tagging
|
||||
// and printing.
|
||||
func TagTrusted(ctx context.Context, apiClient client.ImageAPIClient, out io.Writer, trustedRef reference.Canonical, ref reference.NamedTagged) error {
|
||||
// Use familiar references when interacting with client and output
|
||||
familiarRef := reference.FamiliarString(ref)
|
||||
trustedFamiliarRef := reference.FamiliarString(trustedRef)
|
||||
|
||||
_, _ = fmt.Fprintf(out, "Tagging %s as %s\n", trustedFamiliarRef, familiarRef)
|
||||
return apiClient.ImageTag(ctx, trustedFamiliarRef, familiarRef)
|
||||
}
|
||||
Reference in New Issue
Block a user