forked from toolshed/abra
chore: bump deps
This commit is contained in:
20
vendor/github.com/docker/cli/cli/cobra.go
generated
vendored
20
vendor/github.com/docker/cli/cli/cobra.go
generated
vendored
@ -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:
|
||||
|
||||
104
vendor/github.com/docker/cli/cli/command/cli.go
generated
vendored
104
vendor/github.com/docker/cli/cli/command/cli.go
generated
vendored
@ -1,5 +1,5 @@
|
||||
// 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
|
||||
//go:build go1.23
|
||||
|
||||
package command
|
||||
|
||||
@ -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/build"
|
||||
"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,13 +46,10 @@ 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
|
||||
@ -69,7 +59,9 @@ type Cli interface {
|
||||
}
|
||||
|
||||
// DockerCli is an instance the docker command line client.
|
||||
// Instances of the client can be returned from NewDockerCli.
|
||||
// Instances of the client should be created using the [NewDockerCli]
|
||||
// constructor to make sure they are properly initialized with defaults
|
||||
// set.
|
||||
type DockerCli struct {
|
||||
configFile *configfile.ConfigFile
|
||||
options *cliflags.ClientOptions
|
||||
@ -84,7 +76,7 @@ type DockerCli struct {
|
||||
init sync.Once
|
||||
initErr error
|
||||
dockerEndpoint docker.Endpoint
|
||||
contextStoreConfig store.Config
|
||||
contextStoreConfig *store.Config
|
||||
initTimeout time.Duration
|
||||
res telemetryResource
|
||||
|
||||
@ -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
|
||||
}
|
||||
@ -188,7 +180,7 @@ func (cli *DockerCli) BuildKitEnabled() (bool, error) {
|
||||
}
|
||||
|
||||
si := cli.ServerInfo()
|
||||
if si.BuildkitVersion == types.BuilderBuildKit {
|
||||
if si.BuildkitVersion == build.BuilderBuildKit {
|
||||
// The daemon advertised BuildKit as the preferred builder; this may
|
||||
// be either a Linux daemon or a Windows daemon with experimental
|
||||
// BuildKit support enabled.
|
||||
@ -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,30 +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 {
|
||||
var err error
|
||||
dockerCli.client, err = makeClient(dockerCli)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the dockerCli runs initialization that must happen after command
|
||||
// line flags are parsed.
|
||||
func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions, ops ...CLIOption) error {
|
||||
@ -275,13 +243,33 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions, ops ...CLIOption)
|
||||
return errors.New("conflicting options: cannot specify both --host and --context")
|
||||
}
|
||||
|
||||
if cli.contextStoreConfig == nil {
|
||||
// This path can be hit when calling Initialize on a DockerCli that's
|
||||
// not constructed through [NewDockerCli]. Using the default context
|
||||
// store without a config set will result in Endpoints from contexts
|
||||
// not being type-mapped correctly, and used as a generic "map[string]any",
|
||||
// instead of a [docker.EndpointMeta].
|
||||
//
|
||||
// When looking up the API endpoint (using [EndpointFromContext]), no
|
||||
// endpoint will be found, and a default, empty endpoint will be used
|
||||
// instead which in its turn, causes newAPIClientFromEndpoint to
|
||||
// be initialized with the default config instead of settings for
|
||||
// the current context (which may mean; connecting with the wrong
|
||||
// endpoint and/or TLS Config to be missing).
|
||||
//
|
||||
// [EndpointFromContext]: https://github.com/docker/cli/blob/33494921b80fd0b5a06acc3a34fa288de4bb2e6b/cli/context/docker/load.go#L139-L149
|
||||
if err := WithDefaultContextStoreConfig()(cli); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
cli.options = opts
|
||||
cli.configFile = config.LoadDefaultConfigFile(cli.err)
|
||||
cli.currentContext = resolveContextName(cli.options, cli.configFile)
|
||||
cli.contextStore = &ContextStoreWithDefault{
|
||||
Store: store.New(config.ContextStoreDir(), cli.contextStoreConfig),
|
||||
Store: store.New(config.ContextStoreDir(), *cli.contextStoreConfig),
|
||||
Resolver: func() (*DefaultContext, error) {
|
||||
return ResolveDefaultContext(cli.options, cli.contextStoreConfig)
|
||||
return ResolveDefaultContext(cli.options, *cli.contextStoreConfig)
|
||||
},
|
||||
}
|
||||
|
||||
@ -292,6 +280,7 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions, ops ...CLIOption)
|
||||
if cli.enableGlobalTracer {
|
||||
cli.createGlobalTracerProvider(cli.baseCtx)
|
||||
}
|
||||
filterResourceAttributesEnvvar()
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -345,7 +334,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 +395,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
|
||||
@ -523,7 +510,7 @@ func (cli *DockerCli) Apply(ops ...CLIOption) error {
|
||||
type ServerInfo struct {
|
||||
HasExperimental bool
|
||||
OSType string
|
||||
BuildkitVersion types.BuilderVersion
|
||||
BuildkitVersion build.BuilderVersion
|
||||
|
||||
// SwarmStatus provides information about the current swarm status of the
|
||||
// engine, obtained from the "Swarm" header in the API response.
|
||||
@ -553,18 +540,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
|
||||
|
||||
22
vendor/github.com/docker/cli/cli/command/cli_options.go
generated
vendored
22
vendor/github.com/docker/cli/cli/command/cli_options.go
generated
vendored
@ -11,7 +11,6 @@ import (
|
||||
|
||||
"github.com/docker/cli/cli/streams"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/docker/docker/errdefs"
|
||||
"github.com/moby/term"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
@ -101,7 +100,8 @@ func WithContentTrust(enabled bool) CLIOption {
|
||||
// WithDefaultContextStoreConfig configures the cli to use the default context store configuration.
|
||||
func WithDefaultContextStoreConfig() CLIOption {
|
||||
return func(cli *DockerCli) error {
|
||||
cli.contextStoreConfig = DefaultContextStoreConfig()
|
||||
cfg := DefaultContextStoreConfig()
|
||||
cli.contextStoreConfig = &cfg
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@ -114,6 +114,18 @@ func WithAPIClient(c client.APIClient) CLIOption {
|
||||
}
|
||||
}
|
||||
|
||||
// WithInitializeClient is passed to [DockerCli.Initialize] to initialize
|
||||
// an API Client for use by the CLI.
|
||||
func WithInitializeClient(makeClient func(*DockerCli) (client.APIClient, error)) CLIOption {
|
||||
return func(cli *DockerCli) error {
|
||||
c, err := makeClient(cli)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return WithAPIClient(c)(cli)
|
||||
}
|
||||
}
|
||||
|
||||
// envOverrideHTTPHeaders is the name of the environment-variable that can be
|
||||
// used to set custom HTTP headers to be sent by the client. This environment
|
||||
// variable is the equivalent to the HttpHeaders field in the configuration
|
||||
@ -177,7 +189,7 @@ func withCustomHeadersFromEnv() client.Opt {
|
||||
csvReader := csv.NewReader(strings.NewReader(value))
|
||||
fields, err := csvReader.Read()
|
||||
if err != nil {
|
||||
return errdefs.InvalidParameter(errors.Errorf(
|
||||
return invalidParameter(errors.Errorf(
|
||||
"failed to parse custom headers from %s environment variable: value must be formatted as comma-separated key=value pairs",
|
||||
envOverrideHTTPHeaders,
|
||||
))
|
||||
@ -194,7 +206,7 @@ func withCustomHeadersFromEnv() client.Opt {
|
||||
k = strings.TrimSpace(k)
|
||||
|
||||
if k == "" {
|
||||
return errdefs.InvalidParameter(errors.Errorf(
|
||||
return invalidParameter(errors.Errorf(
|
||||
`failed to set custom headers from %s environment variable: value contains a key=value pair with an empty key: '%s'`,
|
||||
envOverrideHTTPHeaders, kv,
|
||||
))
|
||||
@ -205,7 +217,7 @@ func withCustomHeadersFromEnv() client.Opt {
|
||||
// from an environment variable with the same name). In the meantime,
|
||||
// produce an error to prevent users from depending on this.
|
||||
if !hasValue {
|
||||
return errdefs.InvalidParameter(errors.Errorf(
|
||||
return invalidParameter(errors.Errorf(
|
||||
`failed to set custom headers from %s environment variable: missing "=" in key=value pair: '%s'`,
|
||||
envOverrideHTTPHeaders, kv,
|
||||
))
|
||||
|
||||
2
vendor/github.com/docker/cli/cli/command/context.go
generated
vendored
2
vendor/github.com/docker/cli/cli/command/context.go
generated
vendored
@ -1,5 +1,5 @@
|
||||
// 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
|
||||
//go:build go1.23
|
||||
|
||||
package command
|
||||
|
||||
|
||||
13
vendor/github.com/docker/cli/cli/command/defaultcontextstore.go
generated
vendored
13
vendor/github.com/docker/cli/cli/command/defaultcontextstore.go
generated
vendored
@ -1,5 +1,5 @@
|
||||
// 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
|
||||
//go:build go1.23
|
||||
|
||||
package command
|
||||
|
||||
@ -7,7 +7,6 @@ import (
|
||||
"github.com/docker/cli/cli/context/docker"
|
||||
"github.com/docker/cli/cli/context/store"
|
||||
cliflags "github.com/docker/cli/cli/flags"
|
||||
"github.com/docker/docker/errdefs"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
@ -117,7 +116,7 @@ func (s *ContextStoreWithDefault) List() ([]store.Metadata, error) {
|
||||
// CreateOrUpdate is not allowed for the default context and fails
|
||||
func (s *ContextStoreWithDefault) CreateOrUpdate(meta store.Metadata) error {
|
||||
if meta.Name == DefaultContextName {
|
||||
return errdefs.InvalidParameter(errors.New("default context cannot be created nor updated"))
|
||||
return invalidParameter(errors.New("default context cannot be created nor updated"))
|
||||
}
|
||||
return s.Store.CreateOrUpdate(meta)
|
||||
}
|
||||
@ -125,7 +124,7 @@ func (s *ContextStoreWithDefault) CreateOrUpdate(meta store.Metadata) error {
|
||||
// Remove is not allowed for the default context and fails
|
||||
func (s *ContextStoreWithDefault) Remove(name string) error {
|
||||
if name == DefaultContextName {
|
||||
return errdefs.InvalidParameter(errors.New("default context cannot be removed"))
|
||||
return invalidParameter(errors.New("default context cannot be removed"))
|
||||
}
|
||||
return s.Store.Remove(name)
|
||||
}
|
||||
@ -145,7 +144,7 @@ func (s *ContextStoreWithDefault) GetMetadata(name string) (store.Metadata, erro
|
||||
// ResetTLSMaterial is not implemented for default context and fails
|
||||
func (s *ContextStoreWithDefault) ResetTLSMaterial(name string, data *store.ContextTLSData) error {
|
||||
if name == DefaultContextName {
|
||||
return errdefs.InvalidParameter(errors.New("default context cannot be edited"))
|
||||
return invalidParameter(errors.New("default context cannot be edited"))
|
||||
}
|
||||
return s.Store.ResetTLSMaterial(name, data)
|
||||
}
|
||||
@ -153,7 +152,7 @@ func (s *ContextStoreWithDefault) ResetTLSMaterial(name string, data *store.Cont
|
||||
// ResetEndpointTLSMaterial is not implemented for default context and fails
|
||||
func (s *ContextStoreWithDefault) ResetEndpointTLSMaterial(contextName string, endpointName string, data *store.EndpointTLSData) error {
|
||||
if contextName == DefaultContextName {
|
||||
return errdefs.InvalidParameter(errors.New("default context cannot be edited"))
|
||||
return invalidParameter(errors.New("default context cannot be edited"))
|
||||
}
|
||||
return s.Store.ResetEndpointTLSMaterial(contextName, endpointName, data)
|
||||
}
|
||||
@ -186,7 +185,7 @@ func (s *ContextStoreWithDefault) GetTLSData(contextName, endpointName, fileName
|
||||
return nil, err
|
||||
}
|
||||
if defaultContext.TLS.Endpoints[endpointName].Files[fileName] == nil {
|
||||
return nil, errdefs.NotFound(errors.Errorf("TLS data for %s/%s/%s does not exist", DefaultContextName, endpointName, fileName))
|
||||
return nil, notFound(errors.Errorf("TLS data for %s/%s/%s does not exist", DefaultContextName, endpointName, fileName))
|
||||
}
|
||||
return defaultContext.TLS.Endpoints[endpointName].Files[fileName], nil
|
||||
}
|
||||
|
||||
13
vendor/github.com/docker/cli/cli/command/formatter/buildcache.go
generated
vendored
13
vendor/github.com/docker/cli/cli/command/formatter/buildcache.go
generated
vendored
@ -6,8 +6,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"github.com/docker/docker/api/types/build"
|
||||
"github.com/docker/go-units"
|
||||
)
|
||||
|
||||
@ -52,7 +51,7 @@ shared: {{.Shared}}
|
||||
return Format(source)
|
||||
}
|
||||
|
||||
func buildCacheSort(buildCache []*types.BuildCache) {
|
||||
func buildCacheSort(buildCache []*build.CacheRecord) {
|
||||
sort.Slice(buildCache, func(i, j int) bool {
|
||||
lui, luj := buildCache[i].LastUsedAt, buildCache[j].LastUsedAt
|
||||
switch {
|
||||
@ -71,7 +70,7 @@ func buildCacheSort(buildCache []*types.BuildCache) {
|
||||
}
|
||||
|
||||
// BuildCacheWrite renders the context for a list of containers
|
||||
func BuildCacheWrite(ctx Context, buildCaches []*types.BuildCache) error {
|
||||
func BuildCacheWrite(ctx Context, buildCaches []*build.CacheRecord) error {
|
||||
render := func(format func(subContext SubContext) error) error {
|
||||
buildCacheSort(buildCaches)
|
||||
for _, bc := range buildCaches {
|
||||
@ -88,7 +87,7 @@ func BuildCacheWrite(ctx Context, buildCaches []*types.BuildCache) error {
|
||||
type buildCacheContext struct {
|
||||
HeaderContext
|
||||
trunc bool
|
||||
v *types.BuildCache
|
||||
v *build.CacheRecord
|
||||
}
|
||||
|
||||
func newBuildCacheContext() *buildCacheContext {
|
||||
@ -115,7 +114,7 @@ func (c *buildCacheContext) MarshalJSON() ([]byte, error) {
|
||||
func (c *buildCacheContext) ID() string {
|
||||
id := c.v.ID
|
||||
if c.trunc {
|
||||
id = stringid.TruncateID(c.v.ID)
|
||||
id = TruncateID(c.v.ID)
|
||||
}
|
||||
if c.v.InUse {
|
||||
return id + "*"
|
||||
@ -131,7 +130,7 @@ func (c *buildCacheContext) Parent() string {
|
||||
parent = c.v.Parent //nolint:staticcheck // Ignore SA1019: Field was deprecated in API v1.42, but kept for backward compatibility
|
||||
}
|
||||
if c.trunc {
|
||||
return stringid.TruncateID(parent)
|
||||
return TruncateID(parent)
|
||||
}
|
||||
return parent
|
||||
}
|
||||
|
||||
42
vendor/github.com/docker/cli/cli/command/formatter/container.go
generated
vendored
42
vendor/github.com/docker/cli/cli/command/formatter/container.go
generated
vendored
@ -1,5 +1,5 @@
|
||||
// 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
|
||||
//go:build go1.23
|
||||
|
||||
package formatter
|
||||
|
||||
@ -11,10 +11,11 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/containerd/platforms"
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"github.com/docker/go-units"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -26,8 +27,18 @@ const (
|
||||
mountsHeader = "MOUNTS"
|
||||
localVolumes = "LOCAL VOLUMES"
|
||||
networksHeader = "NETWORKS"
|
||||
platformHeader = "PLATFORM"
|
||||
)
|
||||
|
||||
// Platform wraps a [ocispec.Platform] to implement the stringer interface.
|
||||
type Platform struct {
|
||||
ocispec.Platform
|
||||
}
|
||||
|
||||
func (p Platform) String() string {
|
||||
return platforms.FormatAll(p.Platform)
|
||||
}
|
||||
|
||||
// NewContainerFormat returns a Format for rendering using a Context
|
||||
func NewContainerFormat(source string, quiet bool, size bool) Format {
|
||||
switch source {
|
||||
@ -68,16 +79,14 @@ ports: {{- pad .Ports 1 0}}
|
||||
|
||||
// ContainerWrite renders the context for a list of containers
|
||||
func ContainerWrite(ctx Context, containers []container.Summary) error {
|
||||
render := func(format func(subContext SubContext) error) error {
|
||||
return ctx.Write(NewContainerContext(), func(format func(subContext SubContext) error) error {
|
||||
for _, ctr := range containers {
|
||||
err := format(&ContainerContext{trunc: ctx.Trunc, c: ctr})
|
||||
if err != nil {
|
||||
if err := format(&ContainerContext{trunc: ctx.Trunc, c: ctr}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return ctx.Write(NewContainerContext(), render)
|
||||
})
|
||||
}
|
||||
|
||||
// ContainerContext is a struct used for rendering a list of containers in a Go template.
|
||||
@ -111,6 +120,7 @@ func NewContainerContext() *ContainerContext {
|
||||
"Mounts": mountsHeader,
|
||||
"LocalVolumes": localVolumes,
|
||||
"Networks": networksHeader,
|
||||
"Platform": platformHeader,
|
||||
}
|
||||
return &containerCtx
|
||||
}
|
||||
@ -124,7 +134,7 @@ func (c *ContainerContext) MarshalJSON() ([]byte, error) {
|
||||
// option being set, the full or truncated ID is returned.
|
||||
func (c *ContainerContext) ID() string {
|
||||
if c.trunc {
|
||||
return stringid.TruncateID(c.c.ID)
|
||||
return TruncateID(c.c.ID)
|
||||
}
|
||||
return c.c.ID
|
||||
}
|
||||
@ -161,7 +171,7 @@ func (c *ContainerContext) Image() string {
|
||||
return "<no image>"
|
||||
}
|
||||
if c.trunc {
|
||||
if trunc := stringid.TruncateID(c.c.ImageID); trunc == stringid.TruncateID(c.c.Image) {
|
||||
if trunc := TruncateID(c.c.ImageID); trunc == TruncateID(c.c.Image) {
|
||||
return trunc
|
||||
}
|
||||
// truncate digest if no-trunc option was not selected
|
||||
@ -210,6 +220,16 @@ func (c *ContainerContext) RunningFor() string {
|
||||
return units.HumanDuration(time.Now().UTC().Sub(createdAt)) + " ago"
|
||||
}
|
||||
|
||||
// Platform returns a human-readable representation of the container's
|
||||
// platform if it is available.
|
||||
func (c *ContainerContext) Platform() *Platform {
|
||||
p := c.c.ImageManifestDescriptor
|
||||
if p == nil || p.Platform == nil {
|
||||
return nil
|
||||
}
|
||||
return &Platform{*p.Platform}
|
||||
}
|
||||
|
||||
// Ports returns a comma-separated string representing open ports of the container
|
||||
// e.g. "0.0.0.0:80->9090/tcp, 9988/tcp"
|
||||
// it's used by command 'docker ps'
|
||||
@ -218,7 +238,8 @@ func (c *ContainerContext) Ports() string {
|
||||
return DisplayablePorts(c.c.Ports)
|
||||
}
|
||||
|
||||
// State returns the container's current state (e.g. "running" or "paused")
|
||||
// State returns the container's current state (e.g. "running" or "paused").
|
||||
// Refer to [container.ContainerState] for possible states.
|
||||
func (c *ContainerContext) State() string {
|
||||
return c.c.State
|
||||
}
|
||||
@ -255,6 +276,7 @@ func (c *ContainerContext) Labels() string {
|
||||
for k, v := range c.c.Labels {
|
||||
joinLabels = append(joinLabels, k+"="+v)
|
||||
}
|
||||
sort.Strings(joinLabels)
|
||||
return strings.Join(joinLabels, ",")
|
||||
}
|
||||
|
||||
|
||||
16
vendor/github.com/docker/cli/cli/command/formatter/context.go
generated
vendored
16
vendor/github.com/docker/cli/cli/command/formatter/context.go
generated
vendored
@ -1,7 +1,5 @@
|
||||
package formatter
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
const (
|
||||
// ClientContextTableFormat is the default client context format.
|
||||
ClientContextTableFormat = "table {{.Name}}{{if .Current}} *{{end}}\t{{.Description}}\t{{.DockerEndpoint}}\t{{.Error}}"
|
||||
@ -30,13 +28,6 @@ type ClientContext struct {
|
||||
DockerEndpoint string
|
||||
Current bool
|
||||
Error string
|
||||
|
||||
// ContextType is a temporary field for compatibility with
|
||||
// Visual Studio, which depends on this from the "cloud integration"
|
||||
// wrapper.
|
||||
//
|
||||
// Deprecated: this type is only for backward-compatibility. Do not use.
|
||||
ContextType string `json:"ContextType,omitempty"`
|
||||
}
|
||||
|
||||
// ClientContextWrite writes formatted contexts using the Context
|
||||
@ -69,13 +60,6 @@ func newClientContextContext() *clientContextContext {
|
||||
}
|
||||
|
||||
func (c *clientContextContext) MarshalJSON() ([]byte, error) {
|
||||
if c.c.ContextType != "" {
|
||||
// We only have ContextType set for plain "json" or "{{json .}}" formatting,
|
||||
// so we should be able to just use the default json.Marshal with no
|
||||
// special handling.
|
||||
return json.Marshal(c.c)
|
||||
}
|
||||
// FIXME(thaJeztah): why do we need a special marshal function here?
|
||||
return MarshalJSON(c)
|
||||
}
|
||||
|
||||
|
||||
2
vendor/github.com/docker/cli/cli/command/formatter/custom.go
generated
vendored
2
vendor/github.com/docker/cli/cli/command/formatter/custom.go
generated
vendored
@ -1,5 +1,5 @@
|
||||
// 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
|
||||
//go:build go1.23
|
||||
|
||||
package formatter
|
||||
|
||||
|
||||
25
vendor/github.com/docker/cli/cli/command/formatter/disk_usage.go
generated
vendored
25
vendor/github.com/docker/cli/cli/command/formatter/disk_usage.go
generated
vendored
@ -4,15 +4,14 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/build"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/image"
|
||||
"github.com/docker/docker/api/types/volume"
|
||||
units "github.com/docker/go-units"
|
||||
"github.com/docker/go-units"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -39,12 +38,12 @@ type DiskUsageContext struct {
|
||||
Images []*image.Summary
|
||||
Containers []*container.Summary
|
||||
Volumes []*volume.Volume
|
||||
BuildCache []*types.BuildCache
|
||||
BuildCache []*build.CacheRecord
|
||||
BuilderSize int64
|
||||
}
|
||||
|
||||
func (ctx *DiskUsageContext) startSubsection(format string) (*template.Template, error) {
|
||||
ctx.buffer = bytes.NewBufferString("")
|
||||
ctx.buffer = &bytes.Buffer{}
|
||||
ctx.header = ""
|
||||
ctx.Format = Format(format)
|
||||
ctx.preFormat()
|
||||
@ -88,7 +87,7 @@ func (ctx *DiskUsageContext) Write() (err error) {
|
||||
if ctx.Verbose {
|
||||
return ctx.verboseWrite()
|
||||
}
|
||||
ctx.buffer = bytes.NewBufferString("")
|
||||
ctx.buffer = &bytes.Buffer{}
|
||||
ctx.preFormat()
|
||||
|
||||
tmpl, err := ctx.parseFormat()
|
||||
@ -330,9 +329,15 @@ func (c *diskUsageContainersContext) TotalCount() string {
|
||||
}
|
||||
|
||||
func (*diskUsageContainersContext) isActive(ctr container.Summary) bool {
|
||||
return strings.Contains(ctr.State, "running") ||
|
||||
strings.Contains(ctr.State, "paused") ||
|
||||
strings.Contains(ctr.State, "restarting")
|
||||
switch ctr.State {
|
||||
case container.StateRunning, container.StatePaused, container.StateRestarting:
|
||||
return true
|
||||
case container.StateCreated, container.StateRemoving, container.StateExited, container.StateDead:
|
||||
return false
|
||||
default:
|
||||
// Unknown state (should never happen).
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (c *diskUsageContainersContext) Active() string {
|
||||
@ -436,7 +441,7 @@ func (c *diskUsageVolumesContext) Reclaimable() string {
|
||||
type diskUsageBuilderContext struct {
|
||||
HeaderContext
|
||||
builderSize int64
|
||||
buildCache []*types.BuildCache
|
||||
buildCache []*build.CacheRecord
|
||||
}
|
||||
|
||||
func (c *diskUsageBuilderContext) MarshalJSON() ([]byte, error) {
|
||||
|
||||
50
vendor/github.com/docker/cli/cli/command/formatter/displayutils.go
generated
vendored
50
vendor/github.com/docker/cli/cli/command/formatter/displayutils.go
generated
vendored
@ -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.23
|
||||
|
||||
package formatter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"golang.org/x/text/width"
|
||||
@ -15,11 +20,32 @@ func charWidth(r rune) int {
|
||||
switch width.LookupRune(r).Kind() {
|
||||
case width.EastAsianWide, width.EastAsianFullwidth:
|
||||
return 2
|
||||
case width.Neutral, width.EastAsianAmbiguous, width.EastAsianNarrow, width.EastAsianHalfwidth:
|
||||
return 1
|
||||
default:
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
const shortLen = 12
|
||||
|
||||
// TruncateID returns a shorthand version of a string identifier for presentation,
|
||||
// after trimming digest algorithm prefix (if any).
|
||||
//
|
||||
// This function is a copy of [stringid.TruncateID] for presentation / formatting
|
||||
// purposes.
|
||||
//
|
||||
// [stringid.TruncateID]: https://github.com/moby/moby/blob/v28.3.2/pkg/stringid/stringid.go#L19
|
||||
func TruncateID(id string) string {
|
||||
if i := strings.IndexRune(id, ':'); i >= 0 {
|
||||
id = id[i+1:]
|
||||
}
|
||||
if len(id) > shortLen {
|
||||
id = id[:shortLen]
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
// Ellipsis truncates a string to fit within maxDisplayWidth, and appends ellipsis (…).
|
||||
// For maxDisplayWidth of 1 and lower, no ellipsis is appended.
|
||||
// For maxDisplayWidth of 1, first char of string will return even if its width > 1.
|
||||
@ -59,3 +85,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))
|
||||
}
|
||||
}
|
||||
|
||||
11
vendor/github.com/docker/cli/cli/command/formatter/formatter.go
generated
vendored
11
vendor/github.com/docker/cli/cli/command/formatter/formatter.go
generated
vendored
@ -1,5 +1,5 @@
|
||||
// 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
|
||||
//go:build go1.23
|
||||
|
||||
package formatter
|
||||
|
||||
@ -76,12 +76,15 @@ 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) {
|
||||
if c.Output == nil {
|
||||
c.Output = io.Discard
|
||||
}
|
||||
if c.Format.IsTable() {
|
||||
t := tabwriter.NewWriter(c.Output, 10, 1, 3, ' ', 0)
|
||||
buffer := bytes.NewBufferString("")
|
||||
@ -111,7 +114,7 @@ type SubFormat func(func(SubContext) error) error
|
||||
|
||||
// Write the template to the buffer using this Context
|
||||
func (c *Context) Write(sub SubContext, f SubFormat) error {
|
||||
c.buffer = bytes.NewBufferString("")
|
||||
c.buffer = &bytes.Buffer{}
|
||||
c.preFormat()
|
||||
|
||||
tmpl, err := c.parseFormat()
|
||||
|
||||
5
vendor/github.com/docker/cli/cli/command/formatter/image.go
generated
vendored
5
vendor/github.com/docker/cli/cli/command/formatter/image.go
generated
vendored
@ -6,8 +6,7 @@ import (
|
||||
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/docker/api/types/image"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
units "github.com/docker/go-units"
|
||||
"github.com/docker/go-units"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -216,7 +215,7 @@ func (c *imageContext) MarshalJSON() ([]byte, error) {
|
||||
|
||||
func (c *imageContext) ID() string {
|
||||
if c.trunc {
|
||||
return stringid.TruncateID(c.i.ID)
|
||||
return TruncateID(c.i.ID)
|
||||
}
|
||||
return c.i.ID
|
||||
}
|
||||
|
||||
2
vendor/github.com/docker/cli/cli/command/formatter/reflect.go
generated
vendored
2
vendor/github.com/docker/cli/cli/command/formatter/reflect.go
generated
vendored
@ -1,5 +1,5 @@
|
||||
// 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
|
||||
//go:build go1.23
|
||||
|
||||
package formatter
|
||||
|
||||
|
||||
2
vendor/github.com/docker/cli/cli/command/formatter/tabwriter/tabwriter.go
generated
vendored
2
vendor/github.com/docker/cli/cli/command/formatter/tabwriter/tabwriter.go
generated
vendored
@ -12,7 +12,7 @@
|
||||
|
||||
// based on https://github.com/golang/go/blob/master/src/text/tabwriter/tabwriter.go Last modified 690ac40 on 31 Jan
|
||||
|
||||
//nolint:gocyclo,nakedret,stylecheck,unused // ignore linting errors, so that we can stick close to upstream
|
||||
//nolint:gocyclo,nakedret,unused // ignore linting errors, so that we can stick close to upstream
|
||||
package tabwriter
|
||||
|
||||
import (
|
||||
|
||||
2
vendor/github.com/docker/cli/cli/command/formatter/volume.go
generated
vendored
2
vendor/github.com/docker/cli/cli/command/formatter/volume.go
generated
vendored
@ -6,7 +6,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/api/types/volume"
|
||||
units "github.com/docker/go-units"
|
||||
"github.com/docker/go-units"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
65
vendor/github.com/docker/cli/cli/command/registry.go
generated
vendored
65
vendor/github.com/docker/cli/cli/command/registry.go
generated
vendored
@ -13,9 +13,9 @@ import (
|
||||
configtypes "github.com/docker/cli/cli/config/types"
|
||||
"github.com/docker/cli/cli/hints"
|
||||
"github.com/docker/cli/cli/streams"
|
||||
"github.com/docker/cli/internal/prompt"
|
||||
"github.com/docker/cli/internal/tui"
|
||||
registrytypes "github.com/docker/docker/api/types/registry"
|
||||
"github.com/docker/docker/registry"
|
||||
"github.com/morikuni/aec"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
@ -28,16 +28,22 @@ const (
|
||||
"for organizations using SSO. Learn more at https://docs.docker.com/go/access-tokens/"
|
||||
)
|
||||
|
||||
// authConfigKey is the key used to store credentials for Docker Hub. It is
|
||||
// a copy of [registry.IndexServer].
|
||||
//
|
||||
// [registry.IndexServer]: https://pkg.go.dev/github.com/docker/docker/registry#IndexServer
|
||||
const authConfigKey = "https://index.docker.io/v1/"
|
||||
|
||||
// RegistryAuthenticationPrivilegedFunc returns a RequestPrivilegeFunc from the specified registry index info
|
||||
// for the given command.
|
||||
// for the given command to prompt the user for username and password.
|
||||
func RegistryAuthenticationPrivilegedFunc(cli Cli, index *registrytypes.IndexInfo, cmdName string) registrytypes.RequestAuthConfig {
|
||||
configKey := getAuthConfigKey(index.Name)
|
||||
isDefaultRegistry := configKey == authConfigKey || index.Official
|
||||
return func(ctx context.Context) (string, error) {
|
||||
_, _ = fmt.Fprintf(cli.Out(), "\nLogin prior to %s:\n", cmdName)
|
||||
indexServer := registry.GetAuthConfigKey(index)
|
||||
isDefaultRegistry := indexServer == registry.IndexServer
|
||||
authConfig, err := GetDefaultAuthConfig(cli.ConfigFile(), true, indexServer, isDefaultRegistry)
|
||||
authConfig, err := GetDefaultAuthConfig(cli.ConfigFile(), true, configKey, isDefaultRegistry)
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(cli.Err(), "Unable to retrieve stored credentials for %s, error: %s.\n", indexServer, err)
|
||||
_, _ = fmt.Fprintf(cli.Err(), "Unable to retrieve stored credentials for %s, error: %s.\n", configKey, err)
|
||||
}
|
||||
|
||||
select {
|
||||
@ -46,7 +52,7 @@ func RegistryAuthenticationPrivilegedFunc(cli Cli, index *registrytypes.IndexInf
|
||||
default:
|
||||
}
|
||||
|
||||
authConfig, err = PromptUserForCredentials(ctx, cli, "", "", authConfig.Username, indexServer)
|
||||
authConfig, err = PromptUserForCredentials(ctx, cli, "", "", authConfig.Username, configKey)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@ -63,7 +69,7 @@ func RegistryAuthenticationPrivilegedFunc(cli Cli, index *registrytypes.IndexInf
|
||||
func ResolveAuthConfig(cfg *configfile.ConfigFile, index *registrytypes.IndexInfo) registrytypes.AuthConfig {
|
||||
configKey := index.Name
|
||||
if index.Official {
|
||||
configKey = registry.IndexServer
|
||||
configKey = authConfigKey
|
||||
}
|
||||
|
||||
a, _ := cfg.GetAuthConfig(configKey)
|
||||
@ -132,7 +138,7 @@ func PromptUserForCredentials(ctx context.Context, cli Cli, argUser, argPassword
|
||||
|
||||
argUser = strings.TrimSpace(argUser)
|
||||
if argUser == "" {
|
||||
if serverAddress == registry.IndexServer {
|
||||
if serverAddress == authConfigKey {
|
||||
// When signing in to the default (Docker Hub) registry, we display
|
||||
// hints for creating an account, and (if hints are enabled), using
|
||||
// a token instead of a password.
|
||||
@ -143,16 +149,16 @@ func PromptUserForCredentials(ctx context.Context, cli Cli, argUser, argPassword
|
||||
}
|
||||
}
|
||||
|
||||
var prompt string
|
||||
var msg string
|
||||
defaultUsername = strings.TrimSpace(defaultUsername)
|
||||
if defaultUsername == "" {
|
||||
prompt = "Username: "
|
||||
msg = "Username: "
|
||||
} else {
|
||||
prompt = fmt.Sprintf("Username (%s): ", defaultUsername)
|
||||
msg = fmt.Sprintf("Username (%s): ", defaultUsername)
|
||||
}
|
||||
|
||||
var err error
|
||||
argUser, err = PromptForInput(ctx, cli.In(), cli.Out(), prompt)
|
||||
argUser, err = prompt.ReadInput(ctx, cli.In(), cli.Out(), msg)
|
||||
if err != nil {
|
||||
return registrytypes.AuthConfig{}, err
|
||||
}
|
||||
@ -166,7 +172,7 @@ func PromptUserForCredentials(ctx context.Context, cli Cli, argUser, argPassword
|
||||
|
||||
argPassword = strings.TrimSpace(argPassword)
|
||||
if argPassword == "" {
|
||||
restoreInput, err := DisableInputEcho(cli.In())
|
||||
restoreInput, err := prompt.DisableInputEcho(cli.In())
|
||||
if err != nil {
|
||||
return registrytypes.AuthConfig{}, err
|
||||
}
|
||||
@ -180,10 +186,13 @@ func PromptUserForCredentials(ctx context.Context, cli Cli, argUser, argPassword
|
||||
}
|
||||
}()
|
||||
|
||||
out := tui.NewOutput(cli.Err())
|
||||
out.PrintNote("A Personal Access Token (PAT) can be used instead.\n" +
|
||||
"To create a PAT, visit " + aec.Underline.Apply("https://app.docker.com/settings") + "\n\n")
|
||||
argPassword, err = PromptForInput(ctx, cli.In(), cli.Out(), "Password: ")
|
||||
if serverAddress == authConfigKey {
|
||||
out := tui.NewOutput(cli.Err())
|
||||
out.PrintNote("A Personal Access Token (PAT) can be used instead.\n" +
|
||||
"To create a PAT, visit " + aec.Underline.Apply("https://app.docker.com/settings") + "\n\n")
|
||||
}
|
||||
|
||||
argPassword, err = prompt.ReadInput(ctx, cli.In(), cli.Out(), "Password: ")
|
||||
if err != nil {
|
||||
return registrytypes.AuthConfig{}, err
|
||||
}
|
||||
@ -225,9 +234,25 @@ func resolveAuthConfigFromImage(cfg *configfile.ConfigFile, image string) (regis
|
||||
if err != nil {
|
||||
return registrytypes.AuthConfig{}, err
|
||||
}
|
||||
repoInfo, err := registry.ParseRepositoryInfo(registryRef)
|
||||
configKey := getAuthConfigKey(reference.Domain(registryRef))
|
||||
a, err := cfg.GetAuthConfig(configKey)
|
||||
if err != nil {
|
||||
return registrytypes.AuthConfig{}, err
|
||||
}
|
||||
return ResolveAuthConfig(cfg, repoInfo.Index), nil
|
||||
return registrytypes.AuthConfig(a), nil
|
||||
}
|
||||
|
||||
// getAuthConfigKey special-cases using the full index address of the official
|
||||
// index as the AuthConfig key, and uses the (host)name[:port] for private indexes.
|
||||
//
|
||||
// It is similar to [registry.GetAuthConfigKey], but does not require on
|
||||
// [registrytypes.IndexInfo] as intermediate.
|
||||
//
|
||||
// [registry.GetAuthConfigKey]: https://pkg.go.dev/github.com/docker/docker/registry#GetAuthConfigKey
|
||||
// [registrytypes.IndexInfo]:https://pkg.go.dev/github.com/docker/docker/api/types/registry#IndexInfo
|
||||
func getAuthConfigKey(domainName string) string {
|
||||
if domainName == "docker.io" || domainName == "index.docker.io" {
|
||||
return authConfigKey
|
||||
}
|
||||
return domainName
|
||||
}
|
||||
|
||||
13
vendor/github.com/docker/cli/cli/command/service/progress/progress.go
generated
vendored
13
vendor/github.com/docker/cli/cli/command/service/progress/progress.go
generated
vendored
@ -11,13 +11,12 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/cli/cli/command/formatter"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/docker/docker/pkg/progress"
|
||||
"github.com/docker/docker/pkg/streamformatter"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -89,7 +88,7 @@ func ServiceProgress(ctx context.Context, apiClient client.APIClient, serviceID
|
||||
)
|
||||
|
||||
for {
|
||||
service, _, err := apiClient.ServiceInspectWithRaw(ctx, serviceID, types.ServiceInspectOptions{})
|
||||
service, _, err := apiClient.ServiceInspectWithRaw(ctx, serviceID, swarm.ServiceInspectOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -143,7 +142,7 @@ func ServiceProgress(ctx context.Context, apiClient client.APIClient, serviceID
|
||||
return nil
|
||||
}
|
||||
|
||||
tasks, err := apiClient.TaskList(ctx, types.TaskListOptions{Filters: filters.NewArgs(
|
||||
tasks, err := apiClient.TaskList(ctx, swarm.TaskListOptions{Filters: filters.NewArgs(
|
||||
filters.KeyValuePair{Key: "service", Value: service.ID},
|
||||
filters.KeyValuePair{Key: "_up-to-date", Value: "true"},
|
||||
)})
|
||||
@ -217,7 +216,7 @@ func ServiceProgress(ctx context.Context, apiClient client.APIClient, serviceID
|
||||
//
|
||||
// TODO(thaJeztah): this should really be a filter on [apiClient.NodeList] instead of being filtered on the client side.
|
||||
func getActiveNodes(ctx context.Context, apiClient client.NodeAPIClient) (map[string]struct{}, error) {
|
||||
nodes, err := apiClient.NodeList(ctx, types.NodeListOptions{})
|
||||
nodes, err := apiClient.NodeList(ctx, swarm.NodeListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -506,7 +505,7 @@ func (u *globalProgressUpdater) writeTaskProgress(task swarm.Task, nodeCount int
|
||||
|
||||
if task.Status.Err != "" {
|
||||
u.progressOut.WriteProgress(progress.Progress{
|
||||
ID: stringid.TruncateID(task.NodeID),
|
||||
ID: formatter.TruncateID(task.NodeID),
|
||||
Action: truncError(task.Status.Err),
|
||||
})
|
||||
return
|
||||
@ -514,7 +513,7 @@ func (u *globalProgressUpdater) writeTaskProgress(task swarm.Task, nodeCount int
|
||||
|
||||
if !terminalState(task.DesiredState) && !terminalState(task.Status.State) {
|
||||
u.progressOut.WriteProgress(progress.Progress{
|
||||
ID: stringid.TruncateID(task.NodeID),
|
||||
ID: formatter.TruncateID(task.NodeID),
|
||||
Action: fmt.Sprintf("%-[1]*s", longestState, task.Status.State),
|
||||
Current: numberedStates[task.Status.State],
|
||||
Total: maxProgress,
|
||||
|
||||
51
vendor/github.com/docker/cli/cli/command/telemetry.go
generated
vendored
51
vendor/github.com/docker/cli/cli/command/telemetry.go
generated
vendored
@ -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, ",")
|
||||
}
|
||||
|
||||
2
vendor/github.com/docker/cli/cli/command/telemetry_docker.go
generated
vendored
2
vendor/github.com/docker/cli/cli/command/telemetry_docker.go
generated
vendored
@ -1,5 +1,5 @@
|
||||
// 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
|
||||
//go:build go1.23
|
||||
|
||||
package command
|
||||
|
||||
|
||||
224
vendor/github.com/docker/cli/cli/command/utils.go
generated
vendored
224
vendor/github.com/docker/cli/cli/command/utils.go
generated
vendored
@ -1,95 +1,45 @@
|
||||
// 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
|
||||
//go:build go1.23
|
||||
|
||||
package command
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/cli/cli/config"
|
||||
"github.com/docker/cli/cli/streams"
|
||||
"github.com/docker/cli/internal/prompt"
|
||||
"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"
|
||||
"github.com/moby/sys/atomicwriter"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
// CopyToFile writes the content of the reader to the specified file
|
||||
//
|
||||
// Deprecated: use [atomicwriter.New].
|
||||
func CopyToFile(outfile string, r io.Reader) error {
|
||||
// We use sequential file access here to avoid depleting the standby list
|
||||
// on Windows. On Linux, this is a call directly to os.CreateTemp
|
||||
tmpFile, err := sequential.CreateTemp(filepath.Dir(outfile), ".docker_temp_")
|
||||
writer, err := atomicwriter.New(outfile, 0o600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tmpPath := tmpFile.Name()
|
||||
|
||||
_, err = io.Copy(tmpFile, r)
|
||||
tmpFile.Close()
|
||||
|
||||
if err != nil {
|
||||
os.Remove(tmpPath)
|
||||
return err
|
||||
}
|
||||
|
||||
if err = os.Rename(tmpPath, outfile); err != nil {
|
||||
os.Remove(tmpPath)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
defer writer.Close()
|
||||
_, err = io.Copy(writer, r)
|
||||
return err
|
||||
}
|
||||
|
||||
// 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"))
|
||||
const ErrPromptTerminated = prompt.ErrTerminated
|
||||
|
||||
// DisableInputEcho disables input echo on the provided streams.In.
|
||||
// This is useful when the user provides sensitive information like passwords.
|
||||
// The function returns a restore function that should be called to restore the
|
||||
// terminal state.
|
||||
func DisableInputEcho(ins *streams.In) (restore func() error, err error) {
|
||||
oldState, err := term.SaveState(ins.FD())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
restore = func() error {
|
||||
return term.RestoreTerminal(ins.FD(), oldState)
|
||||
}
|
||||
return restore, term.DisableEcho(ins.FD(), oldState)
|
||||
return prompt.DisableInputEcho(ins)
|
||||
}
|
||||
|
||||
// PromptForInput requests input from the user.
|
||||
@ -100,23 +50,7 @@ func DisableInputEcho(ins *streams.In) (restore func() error, err error) {
|
||||
// the stack and close the io.Reader used for the prompt which will prevent the
|
||||
// background goroutine from blocking indefinitely.
|
||||
func PromptForInput(ctx context.Context, in io.Reader, out io.Writer, message string) (string, error) {
|
||||
_, _ = fmt.Fprint(out, message)
|
||||
|
||||
result := make(chan string)
|
||||
go func() {
|
||||
scanner := bufio.NewScanner(in)
|
||||
if scanner.Scan() {
|
||||
result <- strings.TrimSpace(scanner.Text())
|
||||
}
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
_, _ = fmt.Fprintln(out, "")
|
||||
return "", ErrPromptTerminated
|
||||
case r := <-result:
|
||||
return r, nil
|
||||
}
|
||||
return prompt.ReadInput(ctx, in, out, message)
|
||||
}
|
||||
|
||||
// PromptForConfirmation requests and checks confirmation from the user.
|
||||
@ -130,67 +64,45 @@ func PromptForInput(ctx context.Context, in io.Reader, out io.Writer, message st
|
||||
// the stack and close the io.Reader used for the prompt which will prevent the
|
||||
// background goroutine from blocking indefinitely.
|
||||
func PromptForConfirmation(ctx context.Context, ins io.Reader, outs io.Writer, message string) (bool, error) {
|
||||
if message == "" {
|
||||
message = "Are you sure you want to proceed?"
|
||||
}
|
||||
message += " [y/N] "
|
||||
|
||||
_, _ = fmt.Fprint(outs, message)
|
||||
|
||||
// On Windows, force the use of the regular OS stdin stream.
|
||||
if runtime.GOOS == "windows" {
|
||||
ins = streams.NewIn(os.Stdin)
|
||||
}
|
||||
|
||||
result := make(chan bool)
|
||||
|
||||
go func() {
|
||||
var res bool
|
||||
scanner := bufio.NewScanner(ins)
|
||||
if scanner.Scan() {
|
||||
answer := strings.TrimSpace(scanner.Text())
|
||||
if strings.EqualFold(answer, "y") {
|
||||
res = true
|
||||
}
|
||||
}
|
||||
result <- res
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
_, _ = fmt.Fprintln(outs, "")
|
||||
return false, ErrPromptTerminated
|
||||
case r := <-result:
|
||||
return r, nil
|
||||
}
|
||||
return prompt.Confirm(ctx, ins, outs, message)
|
||||
}
|
||||
|
||||
// PruneFilters returns consolidated prune filters obtained from config.json and cli
|
||||
func PruneFilters(dockerCli Cli, pruneFilters filters.Args) filters.Args {
|
||||
if dockerCli.ConfigFile() == nil {
|
||||
// PruneFilters merges prune filters specified in config.json with those specified
|
||||
// as command-line flags.
|
||||
//
|
||||
// CLI label filters have precedence over those specified in config.json. If a
|
||||
// label filter specified as flag conflicts with a label defined in config.json
|
||||
// (i.e., "label=some-value" conflicts with "label!=some-value", and vice versa),
|
||||
// then the filter defined in config.json is omitted.
|
||||
func PruneFilters(dockerCLI config.Provider, pruneFilters filters.Args) filters.Args {
|
||||
cfg := dockerCLI.ConfigFile()
|
||||
if cfg == nil {
|
||||
return pruneFilters
|
||||
}
|
||||
for _, f := range dockerCli.ConfigFile().PruneFilters {
|
||||
|
||||
// Merge filters provided through the CLI with default filters defined
|
||||
// in the CLI-configfile.
|
||||
for _, f := range cfg.PruneFilters {
|
||||
k, v, ok := strings.Cut(f, "=")
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if k == "label" {
|
||||
// CLI label filter supersede config.json.
|
||||
// If CLI label filter conflict with config.json,
|
||||
// skip adding label! filter in config.json.
|
||||
if pruneFilters.Contains("label!") && pruneFilters.ExactMatch("label!", v) {
|
||||
switch k {
|
||||
case "label":
|
||||
// "label != some-value" conflicts with "label = some-value"
|
||||
if pruneFilters.ExactMatch("label!", v) {
|
||||
continue
|
||||
}
|
||||
} else if k == "label!" {
|
||||
// CLI label! filter supersede config.json.
|
||||
// If CLI label! filter conflict with config.json,
|
||||
// skip adding label filter in config.json.
|
||||
if pruneFilters.Contains("label") && pruneFilters.ExactMatch("label", v) {
|
||||
pruneFilters.Add(k, v)
|
||||
case "label!":
|
||||
// "label != some-value" conflicts with "label = some-value"
|
||||
if pruneFilters.ExactMatch("label", v) {
|
||||
continue
|
||||
}
|
||||
pruneFilters.Add(k, v)
|
||||
default:
|
||||
pruneFilters.Add(k, v)
|
||||
}
|
||||
pruneFilters.Add(k, v)
|
||||
}
|
||||
|
||||
return pruneFilters
|
||||
@ -202,7 +114,7 @@ func AddPlatformFlag(flags *pflag.FlagSet, target *string) {
|
||||
_ = flags.SetAnnotation("platform", "version", []string{"1.32"})
|
||||
}
|
||||
|
||||
// ValidateOutputPath validates the output paths of the `export` and `save` commands.
|
||||
// ValidateOutputPath validates the output paths of the "docker cp" command.
|
||||
func ValidateOutputPath(path string) error {
|
||||
dir := filepath.Dir(filepath.Clean(path))
|
||||
if dir != "" && dir != "." {
|
||||
@ -228,8 +140,8 @@ func ValidateOutputPath(path string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateOutputPathFileMode validates the output paths of the `cp` command and serves as a
|
||||
// helper to `ValidateOutputPath`
|
||||
// ValidateOutputPathFileMode validates the output paths of the "docker cp" command
|
||||
// and serves as a helper to [ValidateOutputPath]
|
||||
func ValidateOutputPathFileMode(fileMode os.FileMode) error {
|
||||
switch {
|
||||
case fileMode&os.ModeDevice != 0:
|
||||
@ -240,47 +152,21 @@ 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
|
||||
func invalidParameter(err error) error {
|
||||
return invalidParameterErr{err}
|
||||
}
|
||||
|
||||
// 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
|
||||
type invalidParameterErr struct{ error }
|
||||
|
||||
func (invalidParameterErr) InvalidParameter() {}
|
||||
|
||||
func notFound(err error) error {
|
||||
return notFoundErr{err}
|
||||
}
|
||||
|
||||
// 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
|
||||
type notFoundErr struct{ error }
|
||||
|
||||
func (notFoundErr) NotFound() {}
|
||||
func (e notFoundErr) Unwrap() error {
|
||||
return e.error
|
||||
}
|
||||
|
||||
7
vendor/github.com/docker/cli/cli/compose/interpolation/interpolation.go
generated
vendored
7
vendor/github.com/docker/cli/cli/compose/interpolation/interpolation.go
generated
vendored
@ -1,5 +1,5 @@
|
||||
// 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
|
||||
//go:build go1.23
|
||||
|
||||
package interpolation
|
||||
|
||||
@ -67,7 +67,10 @@ func recursiveInterpolate(value any, path Path, opts Options) (any, error) {
|
||||
return newValue, nil
|
||||
}
|
||||
casted, err := caster(newValue)
|
||||
return casted, newPathError(path, errors.Wrap(err, "failed to cast to expected type"))
|
||||
if err != nil {
|
||||
return casted, newPathError(path, errors.Wrap(err, "failed to cast to expected type"))
|
||||
}
|
||||
return casted, nil
|
||||
|
||||
case map[string]any:
|
||||
out := map[string]any{}
|
||||
|
||||
2
vendor/github.com/docker/cli/cli/compose/loader/interpolate.go
generated
vendored
2
vendor/github.com/docker/cli/cli/compose/loader/interpolate.go
generated
vendored
@ -1,5 +1,5 @@
|
||||
// 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
|
||||
//go:build go1.23
|
||||
|
||||
package loader
|
||||
|
||||
|
||||
7
vendor/github.com/docker/cli/cli/compose/loader/loader.go
generated
vendored
7
vendor/github.com/docker/cli/cli/compose/loader/loader.go
generated
vendored
@ -1,5 +1,5 @@
|
||||
// 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
|
||||
//go:build go1.23
|
||||
|
||||
package loader
|
||||
|
||||
@ -18,9 +18,10 @@ import (
|
||||
"github.com/docker/cli/cli/compose/template"
|
||||
"github.com/docker/cli/cli/compose/types"
|
||||
"github.com/docker/cli/opts"
|
||||
"github.com/docker/cli/opts/swarmopts"
|
||||
"github.com/docker/docker/api/types/versions"
|
||||
"github.com/docker/go-connections/nat"
|
||||
units "github.com/docker/go-units"
|
||||
"github.com/docker/go-units"
|
||||
"github.com/go-viper/mapstructure/v2"
|
||||
"github.com/google/shlex"
|
||||
"github.com/pkg/errors"
|
||||
@ -925,7 +926,7 @@ func toServicePortConfigs(value string) ([]any, error) {
|
||||
|
||||
for _, key := range keys {
|
||||
// Reuse ConvertPortToPortConfig so that it is consistent
|
||||
portConfig, err := opts.ConvertPortToPortConfig(nat.Port(key), portBindings)
|
||||
portConfig, err := swarmopts.ConvertPortToPortConfig(nat.Port(key), portBindings)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
2
vendor/github.com/docker/cli/cli/compose/loader/merge.go
generated
vendored
2
vendor/github.com/docker/cli/cli/compose/loader/merge.go
generated
vendored
@ -1,5 +1,5 @@
|
||||
// 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
|
||||
//go:build go1.23
|
||||
|
||||
package loader
|
||||
|
||||
|
||||
2
vendor/github.com/docker/cli/cli/compose/schema/schema.go
generated
vendored
2
vendor/github.com/docker/cli/cli/compose/schema/schema.go
generated
vendored
@ -1,5 +1,5 @@
|
||||
// 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
|
||||
//go:build go1.23
|
||||
|
||||
package schema
|
||||
|
||||
|
||||
48
vendor/github.com/docker/cli/cli/compose/template/template.go
generated
vendored
48
vendor/github.com/docker/cli/cli/compose/template/template.go
generated
vendored
@ -1,5 +1,5 @@
|
||||
// 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
|
||||
//go:build go1.23
|
||||
|
||||
package template
|
||||
|
||||
@ -7,6 +7,8 @@ import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/cli/internal/lazyregexp"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -14,11 +16,21 @@ const (
|
||||
subst = "[_a-z][_a-z0-9]*(?::?[-?][^}]*)?"
|
||||
)
|
||||
|
||||
var defaultPattern = regexp.MustCompile(fmt.Sprintf(
|
||||
var defaultPattern = lazyregexp.New(fmt.Sprintf(
|
||||
"%s(?i:(?P<escaped>%s)|(?P<named>%s)|{(?P<braced>%s)}|(?P<invalid>))",
|
||||
delimiter, delimiter, subst, subst,
|
||||
))
|
||||
|
||||
// regexper is an internal interface to allow passing a [lazyregexp.Regexp]
|
||||
// in places where a custom ("regular") [regexp.Regexp] is accepted. It defines
|
||||
// only the methods we currently use.
|
||||
type regexper interface {
|
||||
FindAllStringSubmatch(s string, n int) [][]string
|
||||
FindStringSubmatch(s string) []string
|
||||
ReplaceAllStringFunc(src string, repl func(string) string) string
|
||||
SubexpNames() []string
|
||||
}
|
||||
|
||||
// DefaultSubstituteFuncs contains the default SubstituteFunc used by the docker cli
|
||||
var DefaultSubstituteFuncs = []SubstituteFunc{
|
||||
softDefault,
|
||||
@ -51,10 +63,16 @@ type SubstituteFunc func(string, Mapping) (string, bool, error)
|
||||
// SubstituteWith substitutes variables in the string with their values.
|
||||
// It accepts additional substitute function.
|
||||
func SubstituteWith(template string, mapping Mapping, pattern *regexp.Regexp, subsFuncs ...SubstituteFunc) (string, error) {
|
||||
return substituteWith(template, mapping, pattern, subsFuncs...)
|
||||
}
|
||||
|
||||
// SubstituteWith substitutes variables in the string with their values.
|
||||
// It accepts additional substitute function.
|
||||
func substituteWith(template string, mapping Mapping, pattern regexper, subsFuncs ...SubstituteFunc) (string, error) {
|
||||
var err error
|
||||
result := pattern.ReplaceAllStringFunc(template, func(substring string) string {
|
||||
matches := pattern.FindStringSubmatch(substring)
|
||||
groups := matchGroups(matches, pattern)
|
||||
groups := matchGroups(matches, defaultPattern)
|
||||
if escaped := groups["escaped"]; escaped != "" {
|
||||
return escaped
|
||||
}
|
||||
@ -93,38 +111,42 @@ func SubstituteWith(template string, mapping Mapping, pattern *regexp.Regexp, su
|
||||
|
||||
// Substitute variables in the string with their values
|
||||
func Substitute(template string, mapping Mapping) (string, error) {
|
||||
return SubstituteWith(template, mapping, defaultPattern, DefaultSubstituteFuncs...)
|
||||
return substituteWith(template, mapping, defaultPattern, DefaultSubstituteFuncs...)
|
||||
}
|
||||
|
||||
// ExtractVariables returns a map of all the variables defined in the specified
|
||||
// composefile (dict representation) and their default value if any.
|
||||
func ExtractVariables(configDict map[string]any, pattern *regexp.Regexp) map[string]string {
|
||||
return extractVariables(configDict, pattern)
|
||||
}
|
||||
|
||||
func extractVariables(configDict map[string]any, pattern regexper) map[string]string {
|
||||
if pattern == nil {
|
||||
pattern = defaultPattern
|
||||
}
|
||||
return recurseExtract(configDict, pattern)
|
||||
}
|
||||
|
||||
func recurseExtract(value any, pattern *regexp.Regexp) map[string]string {
|
||||
func recurseExtract(value any, pattern regexper) map[string]string {
|
||||
m := map[string]string{}
|
||||
|
||||
switch value := value.(type) {
|
||||
switch val := value.(type) {
|
||||
case string:
|
||||
if values, is := extractVariable(value, pattern); is {
|
||||
if values, is := extractVariable(val, pattern); is {
|
||||
for _, v := range values {
|
||||
m[v.name] = v.value
|
||||
}
|
||||
}
|
||||
case map[string]any:
|
||||
for _, elem := range value {
|
||||
for _, elem := range val {
|
||||
submap := recurseExtract(elem, pattern)
|
||||
for key, value := range submap {
|
||||
m[key] = value
|
||||
for k, v := range submap {
|
||||
m[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
case []any:
|
||||
for _, elem := range value {
|
||||
for _, elem := range val {
|
||||
if values, is := extractVariable(elem, pattern); is {
|
||||
for _, v := range values {
|
||||
m[v.name] = v.value
|
||||
@ -141,7 +163,7 @@ type extractedValue struct {
|
||||
value string
|
||||
}
|
||||
|
||||
func extractVariable(value any, pattern *regexp.Regexp) ([]extractedValue, bool) {
|
||||
func extractVariable(value any, pattern regexper) ([]extractedValue, bool) {
|
||||
sValue, ok := value.(string)
|
||||
if !ok {
|
||||
return []extractedValue{}, false
|
||||
@ -227,7 +249,7 @@ func withRequired(substitution string, mapping Mapping, sep string, valid func(s
|
||||
return value, true, nil
|
||||
}
|
||||
|
||||
func matchGroups(matches []string, pattern *regexp.Regexp) map[string]string {
|
||||
func matchGroups(matches []string, pattern regexper) map[string]string {
|
||||
groups := make(map[string]string)
|
||||
for i, name := range pattern.SubexpNames()[1:] {
|
||||
groups[name] = matches[i+1]
|
||||
|
||||
2
vendor/github.com/docker/cli/cli/compose/types/types.go
generated
vendored
2
vendor/github.com/docker/cli/cli/compose/types/types.go
generated
vendored
@ -1,5 +1,5 @@
|
||||
// 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
|
||||
//go:build go1.23
|
||||
|
||||
package types
|
||||
|
||||
|
||||
7
vendor/github.com/docker/cli/cli/config/config.go
generated
vendored
7
vendor/github.com/docker/cli/cli/config/config.go
generated
vendored
@ -58,7 +58,7 @@ func resetConfigDir() {
|
||||
// getHomeDir is a copy of [pkg/homedir.Get] to prevent adding docker/docker
|
||||
// as dependency for consumers that only need to read the config-file.
|
||||
//
|
||||
// [pkg/homedir.Get]: https://pkg.go.dev/github.com/docker/docker@v26.1.4+incompatible/pkg/homedir#Get
|
||||
// [pkg/homedir.Get]: https://pkg.go.dev/github.com/docker/docker@v28.0.3+incompatible/pkg/homedir#Get
|
||||
func getHomeDir() string {
|
||||
home, _ := os.UserHomeDir()
|
||||
if home == "" && runtime.GOOS != "windows" {
|
||||
@ -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() {
|
||||
|
||||
102
vendor/github.com/docker/cli/cli/config/configfile/file.go
generated
vendored
102
vendor/github.com/docker/cli/cli/config/configfile/file.go
generated
vendored
@ -3,12 +3,14 @@ package configfile
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/cli/cli/config/credentials"
|
||||
"github.com/docker/cli/cli/config/memorystore"
|
||||
"github.com/docker/cli/cli/config/types"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
@ -36,14 +38,41 @@ type ConfigFile struct {
|
||||
NodesFormat string `json:"nodesFormat,omitempty"`
|
||||
PruneFilters []string `json:"pruneFilters,omitempty"`
|
||||
Proxies map[string]ProxyConfig `json:"proxies,omitempty"`
|
||||
Experimental string `json:"experimental,omitempty"`
|
||||
CurrentContext string `json:"currentContext,omitempty"`
|
||||
CLIPluginsExtraDirs []string `json:"cliPluginsExtraDirs,omitempty"`
|
||||
Plugins map[string]map[string]string `json:"plugins,omitempty"`
|
||||
Aliases map[string]string `json:"aliases,omitempty"`
|
||||
Features map[string]string `json:"features,omitempty"`
|
||||
|
||||
// Deprecated: experimental CLI features are always enabled and this field is no longer used. Use [Features] instead for optional features. This field will be removed in a future release.
|
||||
Experimental string `json:"experimental,omitempty"`
|
||||
}
|
||||
|
||||
type configEnvAuth struct {
|
||||
Auth string `json:"auth"`
|
||||
}
|
||||
|
||||
type configEnv struct {
|
||||
AuthConfigs map[string]configEnvAuth `json:"auths"`
|
||||
}
|
||||
|
||||
// DockerEnvConfigKey is an environment variable that contains a JSON encoded
|
||||
// credential config. It only supports storing the credentials as a base64
|
||||
// encoded string in the format base64("username:pat").
|
||||
//
|
||||
// Adding additional fields will produce a parsing error.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// {
|
||||
// "auths": {
|
||||
// "example.test": {
|
||||
// "auth": base64-encoded-username-pat
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
const DockerEnvConfigKey = "DOCKER_AUTH_CONFIG"
|
||||
|
||||
// ProxyConfig contains proxy configuration settings
|
||||
type ProxyConfig struct {
|
||||
HTTPProxy string `json:"httpProxy,omitempty"`
|
||||
@ -150,7 +179,8 @@ func (configFile *ConfigFile) Save() (retErr error) {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
temp.Close()
|
||||
// ignore error as the file may already be closed when we reach this.
|
||||
_ = temp.Close()
|
||||
if retErr != nil {
|
||||
if err := os.Remove(temp.Name()); err != nil {
|
||||
logrus.WithError(err).WithField("file", temp.Name()).Debug("Error cleaning up temp file")
|
||||
@ -167,10 +197,16 @@ func (configFile *ConfigFile) Save() (retErr error) {
|
||||
return errors.Wrap(err, "error closing temp file")
|
||||
}
|
||||
|
||||
// Handle situation where the configfile is a symlink
|
||||
// Handle situation where the configfile is a symlink, and allow for dangling symlinks
|
||||
cfgFile := configFile.Filename
|
||||
if f, err := os.Readlink(cfgFile); err == nil {
|
||||
if f, err := filepath.EvalSymlinks(cfgFile); err == nil {
|
||||
cfgFile = f
|
||||
} else if os.IsNotExist(err) {
|
||||
// extract the path from the error if the configfile does not exist or is a dangling symlink
|
||||
var pathError *os.PathError
|
||||
if errors.As(err, &pathError) {
|
||||
cfgFile = pathError.Path
|
||||
}
|
||||
}
|
||||
|
||||
// Try copying the current config file (if any) ownership and permissions
|
||||
@ -254,10 +290,64 @@ func decodeAuth(authStr string) (string, string, error) {
|
||||
// GetCredentialsStore returns a new credentials store from the settings in the
|
||||
// configuration file
|
||||
func (configFile *ConfigFile) GetCredentialsStore(registryHostname string) credentials.Store {
|
||||
store := credentials.NewFileStore(configFile)
|
||||
|
||||
if helper := getConfiguredCredentialStore(configFile, registryHostname); helper != "" {
|
||||
return newNativeStore(configFile, helper)
|
||||
store = newNativeStore(configFile, helper)
|
||||
}
|
||||
return credentials.NewFileStore(configFile)
|
||||
|
||||
envConfig := os.Getenv(DockerEnvConfigKey)
|
||||
if envConfig == "" {
|
||||
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),
|
||||
)
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintln(os.Stderr, "Failed to create credential store from DOCKER_AUTH_CONFIG: ", err)
|
||||
return store
|
||||
}
|
||||
|
||||
return envStore
|
||||
}
|
||||
|
||||
func parseEnvConfig(v string) (map[string]types.AuthConfig, error) {
|
||||
envConfig := &configEnv{}
|
||||
decoder := json.NewDecoder(strings.NewReader(v))
|
||||
decoder.DisallowUnknownFields()
|
||||
if err := decoder.Decode(envConfig); err != nil && !errors.Is(err, io.EOF) {
|
||||
return nil, err
|
||||
}
|
||||
if decoder.More() {
|
||||
return nil, errors.New("DOCKER_AUTH_CONFIG does not support more than one JSON object")
|
||||
}
|
||||
|
||||
authConfigs := make(map[string]types.AuthConfig)
|
||||
for addr, envAuth := range envConfig.AuthConfigs {
|
||||
if envAuth.Auth == "" {
|
||||
return nil, fmt.Errorf("DOCKER_AUTH_CONFIG environment variable is missing key `auth` for %s", addr)
|
||||
}
|
||||
username, password, err := decodeAuth(envAuth.Auth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
authConfigs[addr] = types.AuthConfig{
|
||||
Username: username,
|
||||
Password: password,
|
||||
ServerAddress: addr,
|
||||
}
|
||||
}
|
||||
return authConfigs, nil
|
||||
}
|
||||
|
||||
// var for unit testing.
|
||||
|
||||
126
vendor/github.com/docker/cli/cli/config/memorystore/store.go
generated
vendored
Normal file
126
vendor/github.com/docker/cli/cli/config/memorystore/store.go
generated
vendored
Normal file
@ -0,0 +1,126 @@
|
||||
//go:build go1.23
|
||||
|
||||
package memorystore
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/docker/cli/cli/config/credentials"
|
||||
"github.com/docker/cli/cli/config/types"
|
||||
)
|
||||
|
||||
var errValueNotFound = errors.New("value not found")
|
||||
|
||||
func IsErrValueNotFound(err error) bool {
|
||||
return errors.Is(err, errValueNotFound)
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
lock sync.RWMutex
|
||||
memoryCredentials map[string]types.AuthConfig
|
||||
fallbackStore credentials.Store
|
||||
}
|
||||
|
||||
func (e *Config) Erase(serverAddress string) error {
|
||||
e.lock.Lock()
|
||||
defer e.lock.Unlock()
|
||||
delete(e.memoryCredentials, serverAddress)
|
||||
|
||||
if e.fallbackStore != nil {
|
||||
err := e.fallbackStore.Erase(serverAddress)
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintln(os.Stderr, "memorystore: ", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Config) Get(serverAddress string) (types.AuthConfig, error) {
|
||||
e.lock.RLock()
|
||||
defer e.lock.RUnlock()
|
||||
authConfig, ok := e.memoryCredentials[serverAddress]
|
||||
if !ok {
|
||||
if e.fallbackStore != nil {
|
||||
return e.fallbackStore.Get(serverAddress)
|
||||
}
|
||||
return types.AuthConfig{}, errValueNotFound
|
||||
}
|
||||
return authConfig, nil
|
||||
}
|
||||
|
||||
func (e *Config) GetAll() (map[string]types.AuthConfig, error) {
|
||||
e.lock.RLock()
|
||||
defer e.lock.RUnlock()
|
||||
creds := make(map[string]types.AuthConfig)
|
||||
|
||||
if e.fallbackStore != nil {
|
||||
fileCredentials, err := e.fallbackStore.GetAll()
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintln(os.Stderr, "memorystore: ", err)
|
||||
} else {
|
||||
creds = fileCredentials
|
||||
}
|
||||
}
|
||||
|
||||
maps.Copy(creds, e.memoryCredentials)
|
||||
return creds, nil
|
||||
}
|
||||
|
||||
func (e *Config) Store(authConfig types.AuthConfig) error {
|
||||
e.lock.Lock()
|
||||
defer e.lock.Unlock()
|
||||
e.memoryCredentials[authConfig.ServerAddress] = authConfig
|
||||
|
||||
if e.fallbackStore != nil {
|
||||
return e.fallbackStore.Store(authConfig)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WithFallbackStore sets a fallback store.
|
||||
//
|
||||
// Write operations will be performed on both the memory store and the
|
||||
// fallback store.
|
||||
//
|
||||
// Read operations will first check the memory store, and if the credential
|
||||
// is not found, it will then check the fallback store.
|
||||
//
|
||||
// Retrieving all credentials will return from both the memory store and the
|
||||
// fallback store, merging the results from both stores into a single map.
|
||||
//
|
||||
// Data stored in the memory store will take precedence over data in the
|
||||
// fallback store.
|
||||
func WithFallbackStore(store credentials.Store) Options {
|
||||
return func(s *Config) error {
|
||||
s.fallbackStore = store
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithAuthConfig allows to set the initial credentials in the memory store.
|
||||
func WithAuthConfig(config map[string]types.AuthConfig) Options {
|
||||
return func(s *Config) error {
|
||||
s.memoryCredentials = config
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
type Options func(*Config) error
|
||||
|
||||
// New creates a new in memory credential store
|
||||
func New(opts ...Options) (credentials.Store, error) {
|
||||
m := &Config{
|
||||
memoryCredentials: make(map[string]types.AuthConfig),
|
||||
}
|
||||
for _, opt := range opts {
|
||||
if err := opt(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
5
vendor/github.com/docker/cli/cli/connhelper/commandconn/commandconn.go
generated
vendored
5
vendor/github.com/docker/cli/cli/connhelper/commandconn/commandconn.go
generated
vendored
@ -28,7 +28,6 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
@ -149,7 +148,7 @@ func (c *commandConn) handleEOF(err error) error {
|
||||
c.stderrMu.Lock()
|
||||
stderr := c.stderr.String()
|
||||
c.stderrMu.Unlock()
|
||||
return errors.Errorf("command %v did not exit after %v: stderr=%q", c.cmd.Args, err, stderr)
|
||||
return fmt.Errorf("command %v did not exit after %v: stderr=%q", c.cmd.Args, err, stderr)
|
||||
}
|
||||
}
|
||||
|
||||
@ -159,7 +158,7 @@ func (c *commandConn) handleEOF(err error) error {
|
||||
c.stderrMu.Lock()
|
||||
stderr := c.stderr.String()
|
||||
c.stderrMu.Unlock()
|
||||
return errors.Errorf("command %v has exited with %v, make sure the URL is valid, and Docker 18.09 or later is installed on the remote host: stderr=%s", c.cmd.Args, werr, stderr)
|
||||
return fmt.Errorf("command %v has exited with %v, make sure the URL is valid, and Docker 18.09 or later is installed on the remote host: stderr=%s", c.cmd.Args, werr, stderr)
|
||||
}
|
||||
|
||||
func ignorableCloseError(err error) bool {
|
||||
|
||||
23
vendor/github.com/docker/cli/cli/connhelper/connhelper.go
generated
vendored
23
vendor/github.com/docker/cli/cli/connhelper/connhelper.go
generated
vendored
@ -3,13 +3,13 @@ package connhelper
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/cli/cli/connhelper/commandconn"
|
||||
"github.com/docker/cli/cli/connhelper/ssh"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// ConnectionHelper allows to connect to a remote host with custom stream provider binary.
|
||||
@ -41,20 +41,25 @@ func getConnectionHelper(daemonURL string, sshFlags []string) (*ConnectionHelper
|
||||
return nil, err
|
||||
}
|
||||
if u.Scheme == "ssh" {
|
||||
sp, err := ssh.ParseURL(daemonURL)
|
||||
sp, err := ssh.NewSpec(u)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "ssh host connection is not valid")
|
||||
return nil, fmt.Errorf("ssh host connection is not valid: %w", err)
|
||||
}
|
||||
sshFlags = addSSHTimeout(sshFlags)
|
||||
sshFlags = disablePseudoTerminalAllocation(sshFlags)
|
||||
|
||||
remoteCommand := []string{"docker", "system", "dial-stdio"}
|
||||
socketPath := sp.Path
|
||||
if strings.Trim(sp.Path, "/") != "" {
|
||||
remoteCommand = []string{"docker", "--host=unix://" + socketPath, "system", "dial-stdio"}
|
||||
}
|
||||
sshArgs, err := sp.Command(sshFlags, remoteCommand...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ConnectionHelper{
|
||||
Dialer: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
args := []string{"docker"}
|
||||
if sp.Path != "" {
|
||||
args = append(args, "--host", "unix://"+sp.Path)
|
||||
}
|
||||
args = append(args, "system", "dial-stdio")
|
||||
return commandconn.New(ctx, "ssh", append(sshFlags, sp.Args(args...)...)...)
|
||||
return commandconn.New(ctx, "ssh", sshArgs...)
|
||||
},
|
||||
Host: "http://docker.example.com",
|
||||
}, nil
|
||||
|
||||
27
vendor/github.com/docker/cli/cli/connhelper/internal/syntax/LICENSE
generated
vendored
Normal file
27
vendor/github.com/docker/cli/cli/connhelper/internal/syntax/LICENSE
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
Copyright (c) 2016, Daniel Martí. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
13
vendor/github.com/docker/cli/cli/connhelper/internal/syntax/doc.go
generated
vendored
Normal file
13
vendor/github.com/docker/cli/cli/connhelper/internal/syntax/doc.go
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
// Package syntax is a fork of [mvdan.cc/sh/v3@v3.10.0/syntax].
|
||||
//
|
||||
// Copyright (c) 2016, Daniel Martí. All rights reserved.
|
||||
//
|
||||
// It is a reduced set of the package to only provide the [Quote] function,
|
||||
// and contains the [LICENSE], [quote.go] and [parser.go] files at the given
|
||||
// revision.
|
||||
//
|
||||
// [quote.go]: https://raw.githubusercontent.com/mvdan/sh/refs/tags/v3.10.0/syntax/quote.go
|
||||
// [parser.go]: https://raw.githubusercontent.com/mvdan/sh/refs/tags/v3.10.0/syntax/parser.go
|
||||
// [LICENSE]: https://raw.githubusercontent.com/mvdan/sh/refs/tags/v3.10.0/LICENSE
|
||||
// [mvdan.cc/sh/v3@v3.10.0/syntax]: https://pkg.go.dev/mvdan.cc/sh/v3@v3.10.0/syntax
|
||||
package syntax
|
||||
95
vendor/github.com/docker/cli/cli/connhelper/internal/syntax/parser.go
generated
vendored
Normal file
95
vendor/github.com/docker/cli/cli/connhelper/internal/syntax/parser.go
generated
vendored
Normal file
@ -0,0 +1,95 @@
|
||||
// Copyright (c) 2016, Daniel Martí <mvdan@mvdan.cc>
|
||||
// See LICENSE for licensing information
|
||||
|
||||
package syntax
|
||||
|
||||
// LangVariant describes a shell language variant to use when tokenizing and
|
||||
// parsing shell code. The zero value is [LangBash].
|
||||
type LangVariant int
|
||||
|
||||
const (
|
||||
// LangBash corresponds to the GNU Bash language, as described in its
|
||||
// manual at https://www.gnu.org/software/bash/manual/bash.html.
|
||||
//
|
||||
// We currently follow Bash version 5.2.
|
||||
//
|
||||
// Its string representation is "bash".
|
||||
LangBash LangVariant = iota
|
||||
|
||||
// LangPOSIX corresponds to the POSIX Shell language, as described at
|
||||
// https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html.
|
||||
//
|
||||
// Its string representation is "posix" or "sh".
|
||||
LangPOSIX
|
||||
|
||||
// LangMirBSDKorn corresponds to the MirBSD Korn Shell, also known as
|
||||
// mksh, as described at http://www.mirbsd.org/htman/i386/man1/mksh.htm.
|
||||
// Note that it shares some features with Bash, due to the shared
|
||||
// ancestry that is ksh.
|
||||
//
|
||||
// We currently follow mksh version 59.
|
||||
//
|
||||
// Its string representation is "mksh".
|
||||
LangMirBSDKorn
|
||||
|
||||
// LangBats corresponds to the Bash Automated Testing System language,
|
||||
// as described at https://github.com/bats-core/bats-core. Note that
|
||||
// it's just a small extension of the Bash language.
|
||||
//
|
||||
// Its string representation is "bats".
|
||||
LangBats
|
||||
|
||||
// LangAuto corresponds to automatic language detection,
|
||||
// commonly used by end-user applications like shfmt,
|
||||
// which can guess a file's language variant given its filename or shebang.
|
||||
//
|
||||
// At this time, [Variant] does not support LangAuto.
|
||||
LangAuto
|
||||
)
|
||||
|
||||
func (l LangVariant) String() string {
|
||||
switch l {
|
||||
case LangBash:
|
||||
return "bash"
|
||||
case LangPOSIX:
|
||||
return "posix"
|
||||
case LangMirBSDKorn:
|
||||
return "mksh"
|
||||
case LangBats:
|
||||
return "bats"
|
||||
case LangAuto:
|
||||
return "auto"
|
||||
}
|
||||
return "unknown shell language variant"
|
||||
}
|
||||
|
||||
// IsKeyword returns true if the given word is part of the language keywords.
|
||||
func IsKeyword(word string) bool {
|
||||
// This list has been copied from the bash 5.1 source code, file y.tab.c +4460
|
||||
switch word {
|
||||
case
|
||||
"!",
|
||||
"[[", // only if COND_COMMAND is defined
|
||||
"]]", // only if COND_COMMAND is defined
|
||||
"case",
|
||||
"coproc", // only if COPROCESS_SUPPORT is defined
|
||||
"do",
|
||||
"done",
|
||||
"else",
|
||||
"esac",
|
||||
"fi",
|
||||
"for",
|
||||
"function",
|
||||
"if",
|
||||
"in",
|
||||
"select", // only if SELECT_COMMAND is defined
|
||||
"then",
|
||||
"time", // only if COMMAND_TIMING is defined
|
||||
"until",
|
||||
"while",
|
||||
"{",
|
||||
"}":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
187
vendor/github.com/docker/cli/cli/connhelper/internal/syntax/quote.go
generated
vendored
Normal file
187
vendor/github.com/docker/cli/cli/connhelper/internal/syntax/quote.go
generated
vendored
Normal file
@ -0,0 +1,187 @@
|
||||
// Copyright (c) 2021, Daniel Martí <mvdan@mvdan.cc>
|
||||
// See LICENSE for licensing information
|
||||
|
||||
package syntax
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
type QuoteError struct {
|
||||
ByteOffset int
|
||||
Message string
|
||||
}
|
||||
|
||||
func (e QuoteError) Error() string {
|
||||
return fmt.Sprintf("cannot quote character at byte %d: %s", e.ByteOffset, e.Message)
|
||||
}
|
||||
|
||||
const (
|
||||
quoteErrNull = "shell strings cannot contain null bytes"
|
||||
quoteErrPOSIX = "POSIX shell lacks escape sequences"
|
||||
quoteErrRange = "rune out of range"
|
||||
quoteErrMksh = "mksh cannot escape codepoints above 16 bits"
|
||||
)
|
||||
|
||||
// Quote returns a quoted version of the input string,
|
||||
// so that the quoted version is expanded or interpreted
|
||||
// as the original string in the given language variant.
|
||||
//
|
||||
// Quoting is necessary when using arbitrary literal strings
|
||||
// as words in a shell script or command.
|
||||
// Without quoting, one can run into syntax errors,
|
||||
// as well as the possibility of running unintended code.
|
||||
//
|
||||
// An error is returned when a string cannot be quoted for a variant.
|
||||
// For instance, POSIX lacks escape sequences for non-printable characters,
|
||||
// and no language variant can represent a string containing null bytes.
|
||||
// In such cases, the returned error type will be *QuoteError.
|
||||
//
|
||||
// The quoting strategy is chosen on a best-effort basis,
|
||||
// to minimize the amount of extra bytes necessary.
|
||||
//
|
||||
// Some strings do not require any quoting and are returned unchanged.
|
||||
// Those strings can be directly surrounded in single quotes as well.
|
||||
//
|
||||
//nolint:gocyclo // ignore "cyclomatic complexity 35 of func `Quote` is high (> 16) (gocyclo)"
|
||||
func Quote(s string, lang LangVariant) (string, error) {
|
||||
if s == "" {
|
||||
// Special case; an empty string must always be quoted,
|
||||
// as otherwise it expands to zero fields.
|
||||
return "''", nil
|
||||
}
|
||||
shellChars := false
|
||||
nonPrintable := false
|
||||
offs := 0
|
||||
for rem := s; len(rem) > 0; {
|
||||
r, size := utf8.DecodeRuneInString(rem)
|
||||
switch r {
|
||||
// Like regOps; token characters.
|
||||
case ';', '"', '\'', '(', ')', '$', '|', '&', '>', '<', '`',
|
||||
// Whitespace; might result in multiple fields.
|
||||
' ', '\t', '\r', '\n',
|
||||
// Escape sequences would be expanded.
|
||||
'\\',
|
||||
// Would start a comment unless quoted.
|
||||
'#',
|
||||
// Might result in brace expansion.
|
||||
'{',
|
||||
// Might result in tilde expansion.
|
||||
'~',
|
||||
// Might result in globbing.
|
||||
'*', '?', '[',
|
||||
// Might result in an assignment.
|
||||
'=':
|
||||
shellChars = true
|
||||
case '\x00':
|
||||
return "", &QuoteError{ByteOffset: offs, Message: quoteErrNull}
|
||||
}
|
||||
if r == utf8.RuneError || !unicode.IsPrint(r) {
|
||||
if lang == LangPOSIX {
|
||||
return "", &QuoteError{ByteOffset: offs, Message: quoteErrPOSIX}
|
||||
}
|
||||
nonPrintable = true
|
||||
}
|
||||
rem = rem[size:]
|
||||
offs += size
|
||||
}
|
||||
if !shellChars && !nonPrintable && !IsKeyword(s) {
|
||||
// Nothing to quote; avoid allocating.
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// Single quotes are usually best,
|
||||
// as they don't require any escaping of characters.
|
||||
// If we have any invalid utf8 or non-printable runes,
|
||||
// use $'' so that we can escape them.
|
||||
// Note that we can't use double quotes for those.
|
||||
var b strings.Builder
|
||||
if nonPrintable {
|
||||
b.WriteString("$'")
|
||||
lastRequoteIfHex := false
|
||||
offs = 0
|
||||
for rem := s; len(rem) > 0; {
|
||||
nextRequoteIfHex := false
|
||||
r, size := utf8.DecodeRuneInString(rem)
|
||||
switch {
|
||||
case r == '\'', r == '\\':
|
||||
b.WriteByte('\\')
|
||||
b.WriteRune(r)
|
||||
case unicode.IsPrint(r) && r != utf8.RuneError:
|
||||
if lastRequoteIfHex && isHex(r) {
|
||||
b.WriteString("'$'")
|
||||
}
|
||||
b.WriteRune(r)
|
||||
case r == '\a':
|
||||
b.WriteString(`\a`)
|
||||
case r == '\b':
|
||||
b.WriteString(`\b`)
|
||||
case r == '\f':
|
||||
b.WriteString(`\f`)
|
||||
case r == '\n':
|
||||
b.WriteString(`\n`)
|
||||
case r == '\r':
|
||||
b.WriteString(`\r`)
|
||||
case r == '\t':
|
||||
b.WriteString(`\t`)
|
||||
case r == '\v':
|
||||
b.WriteString(`\v`)
|
||||
case r < utf8.RuneSelf, r == utf8.RuneError && size == 1:
|
||||
// \xXX, fixed at two hexadecimal characters.
|
||||
fmt.Fprintf(&b, "\\x%02x", rem[0])
|
||||
// Unfortunately, mksh allows \x to consume more hex characters.
|
||||
// Ensure that we don't allow it to read more than two.
|
||||
if lang == LangMirBSDKorn {
|
||||
nextRequoteIfHex = true
|
||||
}
|
||||
case r > utf8.MaxRune:
|
||||
// Not a valid Unicode code point?
|
||||
return "", &QuoteError{ByteOffset: offs, Message: quoteErrRange}
|
||||
case lang == LangMirBSDKorn && r > 0xFFFD:
|
||||
// From the CAVEATS section in R59's man page:
|
||||
//
|
||||
// mksh currently uses OPTU-16 internally, which is the same as
|
||||
// UTF-8 and CESU-8 with 0000..FFFD being valid codepoints.
|
||||
return "", &QuoteError{ByteOffset: offs, Message: quoteErrMksh}
|
||||
case r < 0x10000:
|
||||
// \uXXXX, fixed at four hexadecimal characters.
|
||||
fmt.Fprintf(&b, "\\u%04x", r)
|
||||
default:
|
||||
// \UXXXXXXXX, fixed at eight hexadecimal characters.
|
||||
fmt.Fprintf(&b, "\\U%08x", r)
|
||||
}
|
||||
rem = rem[size:]
|
||||
lastRequoteIfHex = nextRequoteIfHex
|
||||
offs += size
|
||||
}
|
||||
b.WriteString("'")
|
||||
return b.String(), nil
|
||||
}
|
||||
|
||||
// Single quotes without any need for escaping.
|
||||
if !strings.Contains(s, "'") {
|
||||
return "'" + s + "'", nil
|
||||
}
|
||||
|
||||
// The string contains single quotes,
|
||||
// so fall back to double quotes.
|
||||
b.WriteByte('"')
|
||||
for _, r := range s {
|
||||
switch r {
|
||||
case '"', '\\', '`', '$':
|
||||
b.WriteByte('\\')
|
||||
}
|
||||
b.WriteRune(r)
|
||||
}
|
||||
b.WriteByte('"')
|
||||
return b.String(), nil
|
||||
}
|
||||
|
||||
func isHex(r rune) bool {
|
||||
return (r >= '0' && r <= '9') ||
|
||||
(r >= 'a' && r <= 'f') ||
|
||||
(r >= 'A' && r <= 'F')
|
||||
}
|
||||
150
vendor/github.com/docker/cli/cli/connhelper/ssh/ssh.go
generated
vendored
150
vendor/github.com/docker/cli/cli/connhelper/ssh/ssh.go
generated
vendored
@ -2,19 +2,48 @@
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/docker/cli/cli/connhelper/internal/syntax"
|
||||
)
|
||||
|
||||
// ParseURL parses URL
|
||||
// ParseURL creates a [Spec] from the given ssh URL. It returns an error if
|
||||
// the URL is using the wrong scheme, contains fragments, query-parameters,
|
||||
// or contains a password.
|
||||
func ParseURL(daemonURL string) (*Spec, error) {
|
||||
u, err := url.Parse(daemonURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
var urlErr *url.Error
|
||||
if errors.As(err, &urlErr) {
|
||||
err = urlErr.Unwrap()
|
||||
}
|
||||
return nil, fmt.Errorf("invalid SSH URL: %w", err)
|
||||
}
|
||||
return NewSpec(u)
|
||||
}
|
||||
|
||||
// NewSpec creates a [Spec] from the given ssh URL's properties. It returns
|
||||
// an error if the URL is using the wrong scheme, contains fragments,
|
||||
// query-parameters, or contains a password.
|
||||
func NewSpec(sshURL *url.URL) (*Spec, error) {
|
||||
s, err := newSpec(sshURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid SSH URL: %w", err)
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func newSpec(u *url.URL) (*Spec, error) {
|
||||
if u == nil {
|
||||
return nil, errors.New("URL is nil")
|
||||
}
|
||||
if u.Scheme == "" {
|
||||
return nil, errors.New("no scheme provided")
|
||||
}
|
||||
if u.Scheme != "ssh" {
|
||||
return nil, errors.Errorf("expected scheme ssh, got %q", u.Scheme)
|
||||
return nil, errors.New("incorrect scheme: " + u.Scheme)
|
||||
}
|
||||
|
||||
var sp Spec
|
||||
@ -27,17 +56,18 @@ func ParseURL(daemonURL string) (*Spec, error) {
|
||||
}
|
||||
sp.Host = u.Hostname()
|
||||
if sp.Host == "" {
|
||||
return nil, errors.Errorf("no host specified")
|
||||
return nil, errors.New("hostname is empty")
|
||||
}
|
||||
sp.Port = u.Port()
|
||||
sp.Path = u.Path
|
||||
if u.RawQuery != "" {
|
||||
return nil, errors.Errorf("extra query after the host: %q", u.RawQuery)
|
||||
return nil, fmt.Errorf("query parameters are not allowed: %q", u.RawQuery)
|
||||
}
|
||||
if u.Fragment != "" {
|
||||
return nil, errors.Errorf("extra fragment after the host: %q", u.Fragment)
|
||||
return nil, fmt.Errorf("fragments are not allowed: %q", u.Fragment)
|
||||
}
|
||||
return &sp, err
|
||||
|
||||
return &sp, nil
|
||||
}
|
||||
|
||||
// Spec of SSH URL
|
||||
@ -48,16 +78,106 @@ type Spec struct {
|
||||
Path string
|
||||
}
|
||||
|
||||
// Args returns args except "ssh" itself combined with optional additional command args
|
||||
func (sp *Spec) Args(add ...string) []string {
|
||||
// Args returns args except "ssh" itself combined with optional additional
|
||||
// command and args to be executed on the remote host. It attempts to quote
|
||||
// the given arguments to account for ssh executing the remote command in a
|
||||
// shell. It returns nil when unable to quote the remote command.
|
||||
func (sp *Spec) Args(remoteCommandAndArgs ...string) []string {
|
||||
// Format the remote command to run using the ssh connection, quoting
|
||||
// values where needed because ssh executes these in a POSIX shell.
|
||||
remoteCommand, err := quoteCommand(remoteCommandAndArgs...)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
sshArgs, err := sp.args()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
if remoteCommand != "" {
|
||||
sshArgs = append(sshArgs, remoteCommand)
|
||||
}
|
||||
return sshArgs
|
||||
}
|
||||
|
||||
func (sp *Spec) args(sshFlags ...string) ([]string, error) {
|
||||
var args []string
|
||||
if sp.Host == "" {
|
||||
return nil, errors.New("no host specified")
|
||||
}
|
||||
if sp.User != "" {
|
||||
args = append(args, "-l", sp.User)
|
||||
// Quote user, as it's obtained from the URL.
|
||||
usr, err := syntax.Quote(sp.User, syntax.LangPOSIX)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid user: %w", err)
|
||||
}
|
||||
args = append(args, "-l", usr)
|
||||
}
|
||||
if sp.Port != "" {
|
||||
args = append(args, "-p", sp.Port)
|
||||
// Quote port, as it's obtained from the URL.
|
||||
port, err := syntax.Quote(sp.Port, syntax.LangPOSIX)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid port: %w", err)
|
||||
}
|
||||
args = append(args, "-p", port)
|
||||
}
|
||||
args = append(args, "--", sp.Host)
|
||||
args = append(args, add...)
|
||||
return args
|
||||
|
||||
// We consider "sshFlags" to be "trusted", and set from code only,
|
||||
// as they are not parsed from the DOCKER_HOST URL.
|
||||
args = append(args, sshFlags...)
|
||||
|
||||
host, err := syntax.Quote(sp.Host, syntax.LangPOSIX)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid host: %w", err)
|
||||
}
|
||||
|
||||
return append(args, "--", host), nil
|
||||
}
|
||||
|
||||
// Command returns the ssh flags and arguments to execute a command
|
||||
// (remoteCommandAndArgs) on the remote host. Where needed, it quotes
|
||||
// values passed in remoteCommandAndArgs to account for ssh executing
|
||||
// the remote command in a shell. It returns an error if no remote command
|
||||
// is passed, or when unable to quote the remote command.
|
||||
//
|
||||
// Important: to preserve backward-compatibility, Command does not currently
|
||||
// perform sanitization or quoting on the sshFlags and callers are expected
|
||||
// to sanitize this argument.
|
||||
func (sp *Spec) Command(sshFlags []string, remoteCommandAndArgs ...string) ([]string, error) {
|
||||
if len(remoteCommandAndArgs) == 0 {
|
||||
return nil, errors.New("no remote command specified")
|
||||
}
|
||||
sshArgs, err := sp.args(sshFlags...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
remoteCommand, err := quoteCommand(remoteCommandAndArgs...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if remoteCommand != "" {
|
||||
sshArgs = append(sshArgs, remoteCommand)
|
||||
}
|
||||
return sshArgs, nil
|
||||
}
|
||||
|
||||
// quoteCommand returns the remote command to run using the ssh connection
|
||||
// as a single string, quoting values where needed because ssh executes
|
||||
// these in a POSIX shell.
|
||||
func quoteCommand(commandAndArgs ...string) (string, error) {
|
||||
var quotedCmd string
|
||||
for i, arg := range commandAndArgs {
|
||||
a, err := syntax.Quote(arg, syntax.LangPOSIX)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("invalid argument: %w", err)
|
||||
}
|
||||
if i == 0 {
|
||||
quotedCmd = a
|
||||
continue
|
||||
}
|
||||
quotedCmd += " " + a
|
||||
}
|
||||
// each part is quoted appropriately, so now we'll have a full
|
||||
// shell command to pass off to "ssh"
|
||||
return quotedCmd, nil
|
||||
}
|
||||
|
||||
31
vendor/github.com/docker/cli/cli/context/docker/load.go
generated
vendored
31
vendor/github.com/docker/cli/cli/context/docker/load.go
generated
vendored
@ -6,6 +6,7 @@ import (
|
||||
"encoding/pem"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/cli/cli/connhelper"
|
||||
@ -90,14 +91,19 @@ func (ep *Endpoint) ClientOpts() ([]client.Opt, error) {
|
||||
return nil, err
|
||||
}
|
||||
if helper == nil {
|
||||
tlsConfig, err := ep.tlsConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
// Check if we're connecting over a socket, because there's no
|
||||
// need to configure TLS for a socket connection.
|
||||
//
|
||||
// TODO(thaJeztah); make resolveDockerEndpoint and resolveDefaultDockerEndpoint not load TLS data,
|
||||
// and load TLS files lazily; see https://github.com/docker/cli/pull/1581
|
||||
if !isSocket(ep.Host) {
|
||||
tlsConfig, err := ep.tlsConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result = append(result, withHTTPClient(tlsConfig))
|
||||
}
|
||||
result = append(result,
|
||||
withHTTPClient(tlsConfig),
|
||||
client.WithHost(ep.Host),
|
||||
)
|
||||
result = append(result, client.WithHost(ep.Host))
|
||||
} else {
|
||||
result = append(result,
|
||||
client.WithHTTPClient(&http.Client{
|
||||
@ -116,6 +122,17 @@ func (ep *Endpoint) ClientOpts() ([]client.Opt, error) {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// isSocket checks if the given address is a Unix-socket (linux),
|
||||
// named pipe (Windows), or file-descriptor.
|
||||
func isSocket(addr string) bool {
|
||||
switch proto, _, _ := strings.Cut(addr, "://"); proto {
|
||||
case "unix", "npipe", "fd":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func withHTTPClient(tlsConfig *tls.Config) func(*client.Client) error {
|
||||
return func(c *client.Client) error {
|
||||
if tlsConfig == nil {
|
||||
|
||||
28
vendor/github.com/docker/cli/cli/context/store/errors.go
generated
vendored
Normal file
28
vendor/github.com/docker/cli/cli/context/store/errors.go
generated
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
package store
|
||||
|
||||
import cerrdefs "github.com/containerd/errdefs"
|
||||
|
||||
func invalidParameter(err error) error {
|
||||
if err == nil || cerrdefs.IsInvalidArgument(err) {
|
||||
return err
|
||||
}
|
||||
return invalidParameterErr{err}
|
||||
}
|
||||
|
||||
type invalidParameterErr struct{ error }
|
||||
|
||||
func (invalidParameterErr) InvalidParameter() {}
|
||||
|
||||
func notFound(err error) error {
|
||||
if err == nil || cerrdefs.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
return notFoundErr{err}
|
||||
}
|
||||
|
||||
type notFoundErr struct{ error }
|
||||
|
||||
func (notFoundErr) NotFound() {}
|
||||
func (e notFoundErr) Unwrap() error {
|
||||
return e.error
|
||||
}
|
||||
8
vendor/github.com/docker/cli/cli/context/store/io_utils.go
generated
vendored
8
vendor/github.com/docker/cli/cli/context/store/io_utils.go
generated
vendored
@ -5,14 +5,14 @@ import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// LimitedReader is a fork of io.LimitedReader to override Read.
|
||||
type LimitedReader struct {
|
||||
// limitedReader is a fork of [io.LimitedReader] to override Read.
|
||||
type limitedReader struct {
|
||||
R io.Reader
|
||||
N int64 // max bytes remaining
|
||||
}
|
||||
|
||||
// Read is a fork of io.LimitedReader.Read that returns an error when limit exceeded.
|
||||
func (l *LimitedReader) Read(p []byte) (n int, err error) {
|
||||
// Read is a fork of [io.LimitedReader.Read] that returns an error when limit exceeded.
|
||||
func (l *limitedReader) Read(p []byte) (n int, err error) {
|
||||
if l.N < 0 {
|
||||
return 0, errors.New("read exceeds the defined limit")
|
||||
}
|
||||
|
||||
15
vendor/github.com/docker/cli/cli/context/store/metadatastore.go
generated
vendored
15
vendor/github.com/docker/cli/cli/context/store/metadatastore.go
generated
vendored
@ -1,20 +1,19 @@
|
||||
// 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
|
||||
//go:build go1.23
|
||||
|
||||
package store
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"sort"
|
||||
|
||||
"github.com/docker/docker/errdefs"
|
||||
"github.com/docker/docker/pkg/atomicwriter"
|
||||
"github.com/fvbommel/sortorder"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/moby/sys/atomicwriter"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -64,7 +63,7 @@ func parseTypedOrMap(payload []byte, getter TypeGetter) (any, error) {
|
||||
func (s *metadataStore) get(name string) (Metadata, error) {
|
||||
m, err := s.getByID(contextdirOf(name))
|
||||
if err != nil {
|
||||
return m, errors.Wrapf(err, "context %q", name)
|
||||
return m, fmt.Errorf("context %q: %w", name, err)
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
@ -74,7 +73,7 @@ func (s *metadataStore) getByID(id contextdir) (Metadata, error) {
|
||||
bytes, err := os.ReadFile(fileName)
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return Metadata{}, errdefs.NotFound(errors.Wrap(err, "context not found"))
|
||||
return Metadata{}, notFound(fmt.Errorf("context not found: %w", err))
|
||||
}
|
||||
return Metadata{}, err
|
||||
}
|
||||
@ -99,7 +98,7 @@ func (s *metadataStore) getByID(id contextdir) (Metadata, error) {
|
||||
|
||||
func (s *metadataStore) remove(name string) error {
|
||||
if err := os.RemoveAll(s.contextDir(contextdirOf(name))); err != nil {
|
||||
return errors.Wrapf(err, "failed to remove metadata")
|
||||
return fmt.Errorf("failed to remove metadata: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -119,7 +118,7 @@ func (s *metadataStore) list() ([]Metadata, error) {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
continue
|
||||
}
|
||||
return nil, errors.Wrap(err, "failed to read metadata")
|
||||
return nil, fmt.Errorf("failed to read metadata: %w", err)
|
||||
}
|
||||
res = append(res, c)
|
||||
}
|
||||
|
||||
30
vendor/github.com/docker/cli/cli/context/store/store.go
generated
vendored
30
vendor/github.com/docker/cli/cli/context/store/store.go
generated
vendored
@ -1,5 +1,5 @@
|
||||
// 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
|
||||
//go:build go1.23
|
||||
|
||||
package store
|
||||
|
||||
@ -10,21 +10,21 @@ import (
|
||||
"bytes"
|
||||
_ "crypto/sha256" // ensure ids can be computed
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/errdefs"
|
||||
"github.com/docker/cli/internal/lazyregexp"
|
||||
"github.com/opencontainers/go-digest"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const restrictedNamePattern = "^[a-zA-Z0-9][a-zA-Z0-9_.+-]+$"
|
||||
|
||||
var restrictedNameRegEx = regexp.MustCompile(restrictedNamePattern)
|
||||
var restrictedNameRegEx = lazyregexp.New(restrictedNamePattern)
|
||||
|
||||
// Store provides a context store for easily remembering endpoints configuration
|
||||
type Store interface {
|
||||
@ -146,10 +146,10 @@ func (s *ContextStore) CreateOrUpdate(meta Metadata) error {
|
||||
// Remove deletes the context with the given name, if found.
|
||||
func (s *ContextStore) Remove(name string) error {
|
||||
if err := s.meta.remove(name); err != nil {
|
||||
return errors.Wrapf(err, "failed to remove context %s", name)
|
||||
return fmt.Errorf("failed to remove context %s: %w", name, err)
|
||||
}
|
||||
if err := s.tls.remove(name); err != nil {
|
||||
return errors.Wrapf(err, "failed to remove context %s", name)
|
||||
return fmt.Errorf("failed to remove context %s: %w", name, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -226,7 +226,7 @@ func ValidateContextName(name string) error {
|
||||
return errors.New(`"default" is a reserved context name`)
|
||||
}
|
||||
if !restrictedNameRegEx.MatchString(name) {
|
||||
return errors.Errorf("context name %q is invalid, names are validated against regexp %q", name, restrictedNamePattern)
|
||||
return fmt.Errorf("context name %q is invalid, names are validated against regexp %q", name, restrictedNamePattern)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -356,7 +356,7 @@ func isValidFilePath(p string) error {
|
||||
}
|
||||
|
||||
func importTar(name string, s Writer, reader io.Reader) error {
|
||||
tr := tar.NewReader(&LimitedReader{R: reader, N: maxAllowedFileSizeToImport})
|
||||
tr := tar.NewReader(&limitedReader{R: reader, N: maxAllowedFileSizeToImport})
|
||||
tlsData := ContextTLSData{
|
||||
Endpoints: map[string]EndpointTLSData{},
|
||||
}
|
||||
@ -374,7 +374,7 @@ func importTar(name string, s Writer, reader io.Reader) error {
|
||||
continue
|
||||
}
|
||||
if err := isValidFilePath(hdr.Name); err != nil {
|
||||
return errors.Wrap(err, hdr.Name)
|
||||
return fmt.Errorf("%s: %w", hdr.Name, err)
|
||||
}
|
||||
if hdr.Name == metaFile {
|
||||
data, err := io.ReadAll(tr)
|
||||
@ -400,13 +400,13 @@ func importTar(name string, s Writer, reader io.Reader) error {
|
||||
}
|
||||
}
|
||||
if !importedMetaFile {
|
||||
return errdefs.InvalidParameter(errors.New("invalid context: no metadata found"))
|
||||
return invalidParameter(errors.New("invalid context: no metadata found"))
|
||||
}
|
||||
return s.ResetTLSMaterial(name, &tlsData)
|
||||
}
|
||||
|
||||
func importZip(name string, s Writer, reader io.Reader) error {
|
||||
body, err := io.ReadAll(&LimitedReader{R: reader, N: maxAllowedFileSizeToImport})
|
||||
body, err := io.ReadAll(&limitedReader{R: reader, N: maxAllowedFileSizeToImport})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -426,7 +426,7 @@ func importZip(name string, s Writer, reader io.Reader) error {
|
||||
continue
|
||||
}
|
||||
if err := isValidFilePath(zf.Name); err != nil {
|
||||
return errors.Wrap(err, zf.Name)
|
||||
return fmt.Errorf("%s: %w", zf.Name, err)
|
||||
}
|
||||
if zf.Name == metaFile {
|
||||
f, err := zf.Open()
|
||||
@ -434,7 +434,7 @@ func importZip(name string, s Writer, reader io.Reader) error {
|
||||
return err
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(&LimitedReader{R: f, N: maxAllowedFileSizeToImport})
|
||||
data, err := io.ReadAll(&limitedReader{R: f, N: maxAllowedFileSizeToImport})
|
||||
defer f.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
@ -464,7 +464,7 @@ func importZip(name string, s Writer, reader io.Reader) error {
|
||||
}
|
||||
}
|
||||
if !importedMetaFile {
|
||||
return errdefs.InvalidParameter(errors.New("invalid context: no metadata found"))
|
||||
return invalidParameter(errors.New("invalid context: no metadata found"))
|
||||
}
|
||||
return s.ResetTLSMaterial(name, &tlsData)
|
||||
}
|
||||
|
||||
2
vendor/github.com/docker/cli/cli/context/store/storeconfig.go
generated
vendored
2
vendor/github.com/docker/cli/cli/context/store/storeconfig.go
generated
vendored
@ -1,5 +1,5 @@
|
||||
// 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
|
||||
//go:build go1.23
|
||||
|
||||
package store
|
||||
|
||||
|
||||
17
vendor/github.com/docker/cli/cli/context/store/tlsstore.go
generated
vendored
17
vendor/github.com/docker/cli/cli/context/store/tlsstore.go
generated
vendored
@ -1,12 +1,11 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/docker/docker/errdefs"
|
||||
"github.com/docker/docker/pkg/atomicwriter"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/moby/sys/atomicwriter"
|
||||
)
|
||||
|
||||
const tlsDir = "tls"
|
||||
@ -39,9 +38,9 @@ func (s *tlsStore) getData(name, endpointName, filename string) ([]byte, error)
|
||||
data, err := os.ReadFile(filepath.Join(s.endpointDir(name, endpointName), filename))
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil, errdefs.NotFound(errors.Errorf("TLS data for %s/%s/%s does not exist", name, endpointName, filename))
|
||||
return nil, notFound(fmt.Errorf("TLS data for %s/%s/%s does not exist", name, endpointName, filename))
|
||||
}
|
||||
return nil, errors.Wrapf(err, "failed to read TLS data for endpoint %s", endpointName)
|
||||
return nil, fmt.Errorf("failed to read TLS data for endpoint %s: %w", endpointName, err)
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
@ -49,14 +48,14 @@ func (s *tlsStore) getData(name, endpointName, filename string) ([]byte, error)
|
||||
// remove deletes all TLS data for the given context.
|
||||
func (s *tlsStore) remove(name string) error {
|
||||
if err := os.RemoveAll(s.contextDir(name)); err != nil {
|
||||
return errors.Wrapf(err, "failed to remove TLS data")
|
||||
return fmt.Errorf("failed to remove TLS data: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *tlsStore) removeEndpoint(name, endpointName string) error {
|
||||
if err := os.RemoveAll(s.endpointDir(name, endpointName)); err != nil {
|
||||
return errors.Wrapf(err, "failed to remove TLS data for endpoint %s", endpointName)
|
||||
return fmt.Errorf("failed to remove TLS data for endpoint %s: %w", endpointName, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -68,7 +67,7 @@ func (s *tlsStore) listContextData(name string) (map[string]EndpointFiles, error
|
||||
if os.IsNotExist(err) {
|
||||
return map[string]EndpointFiles{}, nil
|
||||
}
|
||||
return nil, errors.Wrapf(err, "failed to list TLS files for context %s", name)
|
||||
return nil, fmt.Errorf("failed to list TLS files for context %s: %w", name, err)
|
||||
}
|
||||
r := make(map[string]EndpointFiles)
|
||||
for _, epFS := range epFSs {
|
||||
@ -78,7 +77,7 @@ func (s *tlsStore) listContextData(name string) (map[string]EndpointFiles, error
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to list TLS files for endpoint %s", epFS.Name())
|
||||
return nil, fmt.Errorf("failed to list TLS files for endpoint %s: %w", epFS.Name(), err)
|
||||
}
|
||||
var files EndpointFiles
|
||||
for _, fs := range fss {
|
||||
|
||||
3
vendor/github.com/docker/cli/cli/debug/debug.go
generated
vendored
3
vendor/github.com/docker/cli/cli/debug/debug.go
generated
vendored
@ -33,5 +33,8 @@ func IsEnabled() bool {
|
||||
// The default is to log to the debug level which is only
|
||||
// enabled when debugging is enabled.
|
||||
var OTELErrorHandler otel.ErrorHandler = otel.ErrorHandlerFunc(func(err error) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
logrus.WithError(err).Debug("otel error")
|
||||
})
|
||||
|
||||
178
vendor/github.com/docker/cli/cli/manifest/store/store.go
generated
vendored
178
vendor/github.com/docker/cli/cli/manifest/store/store.go
generated
vendored
@ -1,178 +0,0 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/cli/cli/manifest/types"
|
||||
"github.com/docker/distribution/manifest/manifestlist"
|
||||
"github.com/opencontainers/go-digest"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Store manages local storage of image distribution manifests
|
||||
type Store interface {
|
||||
Remove(listRef reference.Reference) error
|
||||
Get(listRef reference.Reference, manifest reference.Reference) (types.ImageManifest, error)
|
||||
GetList(listRef reference.Reference) ([]types.ImageManifest, error)
|
||||
Save(listRef reference.Reference, manifest reference.Reference, image types.ImageManifest) error
|
||||
}
|
||||
|
||||
// fsStore manages manifest files stored on the local filesystem
|
||||
type fsStore struct {
|
||||
root string
|
||||
}
|
||||
|
||||
// NewStore returns a new store for a local file path
|
||||
func NewStore(root string) Store {
|
||||
return &fsStore{root: root}
|
||||
}
|
||||
|
||||
// Remove a manifest list from local storage
|
||||
func (s *fsStore) Remove(listRef reference.Reference) error {
|
||||
path := filepath.Join(s.root, makeFilesafeName(listRef.String()))
|
||||
return os.RemoveAll(path)
|
||||
}
|
||||
|
||||
// Get returns the local manifest
|
||||
func (s *fsStore) Get(listRef reference.Reference, manifest reference.Reference) (types.ImageManifest, error) {
|
||||
filename := manifestToFilename(s.root, listRef.String(), manifest.String())
|
||||
return s.getFromFilename(manifest, filename)
|
||||
}
|
||||
|
||||
func (*fsStore) getFromFilename(ref reference.Reference, filename string) (types.ImageManifest, error) {
|
||||
bytes, err := os.ReadFile(filename)
|
||||
switch {
|
||||
case os.IsNotExist(err):
|
||||
return types.ImageManifest{}, newNotFoundError(ref.String())
|
||||
case err != nil:
|
||||
return types.ImageManifest{}, err
|
||||
}
|
||||
var manifestInfo struct {
|
||||
types.ImageManifest
|
||||
|
||||
// Deprecated Fields, replaced by Descriptor
|
||||
Digest digest.Digest
|
||||
Platform *manifestlist.PlatformSpec
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(bytes, &manifestInfo); err != nil {
|
||||
return types.ImageManifest{}, err
|
||||
}
|
||||
|
||||
// Compatibility with image manifests created before
|
||||
// descriptor, newer versions omit Digest and Platform
|
||||
if manifestInfo.Digest != "" {
|
||||
mediaType, raw, err := manifestInfo.Payload()
|
||||
if err != nil {
|
||||
return types.ImageManifest{}, err
|
||||
}
|
||||
if dgst := digest.FromBytes(raw); dgst != manifestInfo.Digest {
|
||||
return types.ImageManifest{}, errors.Errorf("invalid manifest file %v: image manifest digest mismatch (%v != %v)", filename, manifestInfo.Digest, dgst)
|
||||
}
|
||||
manifestInfo.ImageManifest.Descriptor = ocispec.Descriptor{
|
||||
Digest: manifestInfo.Digest,
|
||||
Size: int64(len(raw)),
|
||||
MediaType: mediaType,
|
||||
Platform: types.OCIPlatform(manifestInfo.Platform),
|
||||
}
|
||||
}
|
||||
|
||||
return manifestInfo.ImageManifest, nil
|
||||
}
|
||||
|
||||
// GetList returns all the local manifests for a transaction
|
||||
func (s *fsStore) GetList(listRef reference.Reference) ([]types.ImageManifest, error) {
|
||||
filenames, err := s.listManifests(listRef.String())
|
||||
switch {
|
||||
case err != nil:
|
||||
return nil, err
|
||||
case filenames == nil:
|
||||
return nil, newNotFoundError(listRef.String())
|
||||
}
|
||||
|
||||
manifests := []types.ImageManifest{}
|
||||
for _, filename := range filenames {
|
||||
filename = filepath.Join(s.root, makeFilesafeName(listRef.String()), filename)
|
||||
manifest, err := s.getFromFilename(listRef, filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
manifests = append(manifests, manifest)
|
||||
}
|
||||
return manifests, nil
|
||||
}
|
||||
|
||||
// listManifests stored in a transaction
|
||||
func (s *fsStore) listManifests(transaction string) ([]string, error) {
|
||||
transactionDir := filepath.Join(s.root, makeFilesafeName(transaction))
|
||||
fileInfos, err := os.ReadDir(transactionDir)
|
||||
switch {
|
||||
case os.IsNotExist(err):
|
||||
return nil, nil
|
||||
case err != nil:
|
||||
return nil, err
|
||||
}
|
||||
|
||||
filenames := make([]string, 0, len(fileInfos))
|
||||
for _, info := range fileInfos {
|
||||
filenames = append(filenames, info.Name())
|
||||
}
|
||||
return filenames, nil
|
||||
}
|
||||
|
||||
// Save a manifest as part of a local manifest list
|
||||
func (s *fsStore) Save(listRef reference.Reference, manifest reference.Reference, image types.ImageManifest) error {
|
||||
if err := s.createManifestListDirectory(listRef.String()); err != nil {
|
||||
return err
|
||||
}
|
||||
filename := manifestToFilename(s.root, listRef.String(), manifest.String())
|
||||
bytes, err := json.Marshal(image)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(filename, bytes, 0o644)
|
||||
}
|
||||
|
||||
func (s *fsStore) createManifestListDirectory(transaction string) error {
|
||||
path := filepath.Join(s.root, makeFilesafeName(transaction))
|
||||
return os.MkdirAll(path, 0o755)
|
||||
}
|
||||
|
||||
func manifestToFilename(root, manifestList, manifest string) string {
|
||||
return filepath.Join(root, makeFilesafeName(manifestList), makeFilesafeName(manifest))
|
||||
}
|
||||
|
||||
func makeFilesafeName(ref string) string {
|
||||
fileName := strings.ReplaceAll(ref, ":", "-")
|
||||
return strings.ReplaceAll(fileName, "/", "_")
|
||||
}
|
||||
|
||||
type notFoundError struct {
|
||||
object string
|
||||
}
|
||||
|
||||
func newNotFoundError(ref string) *notFoundError {
|
||||
return ¬FoundError{object: ref}
|
||||
}
|
||||
|
||||
func (n *notFoundError) Error() string {
|
||||
return "No such manifest: " + n.object
|
||||
}
|
||||
|
||||
// NotFound interface
|
||||
func (*notFoundError) NotFound() {}
|
||||
|
||||
// IsNotFound returns true if the error is a not found error
|
||||
func IsNotFound(err error) bool {
|
||||
_, ok := err.(notFound)
|
||||
return ok
|
||||
}
|
||||
|
||||
type notFound interface {
|
||||
NotFound()
|
||||
}
|
||||
154
vendor/github.com/docker/cli/cli/manifest/types/types.go
generated
vendored
154
vendor/github.com/docker/cli/cli/manifest/types/types.go
generated
vendored
@ -1,154 +0,0 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/distribution"
|
||||
"github.com/docker/distribution/manifest/manifestlist"
|
||||
"github.com/docker/distribution/manifest/ocischema"
|
||||
"github.com/docker/distribution/manifest/schema2"
|
||||
"github.com/opencontainers/go-digest"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// ImageManifest contains info to output for a manifest object.
|
||||
type ImageManifest struct {
|
||||
Ref *SerializableNamed
|
||||
Descriptor ocispec.Descriptor
|
||||
Raw []byte `json:",omitempty"`
|
||||
|
||||
// SchemaV2Manifest is used for inspection
|
||||
SchemaV2Manifest *schema2.DeserializedManifest `json:",omitempty"`
|
||||
// OCIManifest is used for inspection
|
||||
OCIManifest *ocischema.DeserializedManifest `json:",omitempty"`
|
||||
}
|
||||
|
||||
// OCIPlatform creates an OCI platform from a manifest list platform spec
|
||||
func OCIPlatform(ps *manifestlist.PlatformSpec) *ocispec.Platform {
|
||||
if ps == nil {
|
||||
return nil
|
||||
}
|
||||
return &ocispec.Platform{
|
||||
Architecture: ps.Architecture,
|
||||
OS: ps.OS,
|
||||
OSVersion: ps.OSVersion,
|
||||
OSFeatures: ps.OSFeatures,
|
||||
Variant: ps.Variant,
|
||||
}
|
||||
}
|
||||
|
||||
// PlatformSpecFromOCI creates a platform spec from OCI platform
|
||||
func PlatformSpecFromOCI(p *ocispec.Platform) *manifestlist.PlatformSpec {
|
||||
if p == nil {
|
||||
return nil
|
||||
}
|
||||
return &manifestlist.PlatformSpec{
|
||||
Architecture: p.Architecture,
|
||||
OS: p.OS,
|
||||
OSVersion: p.OSVersion,
|
||||
OSFeatures: p.OSFeatures,
|
||||
Variant: p.Variant,
|
||||
}
|
||||
}
|
||||
|
||||
// Blobs returns the digests for all the blobs referenced by this manifest
|
||||
func (i ImageManifest) Blobs() []digest.Digest {
|
||||
var digests []digest.Digest
|
||||
switch {
|
||||
case i.SchemaV2Manifest != nil:
|
||||
refs := i.SchemaV2Manifest.References()
|
||||
digests = make([]digest.Digest, 0, len(refs))
|
||||
for _, descriptor := range refs {
|
||||
digests = append(digests, descriptor.Digest)
|
||||
}
|
||||
case i.OCIManifest != nil:
|
||||
refs := i.OCIManifest.References()
|
||||
digests = make([]digest.Digest, 0, len(refs))
|
||||
for _, descriptor := range refs {
|
||||
digests = append(digests, descriptor.Digest)
|
||||
}
|
||||
}
|
||||
return digests
|
||||
}
|
||||
|
||||
// Payload returns the media type and bytes for the manifest
|
||||
func (i ImageManifest) Payload() (string, []byte, error) {
|
||||
// TODO: If available, read content from a content store by digest
|
||||
switch {
|
||||
case i.SchemaV2Manifest != nil:
|
||||
return i.SchemaV2Manifest.Payload()
|
||||
case i.OCIManifest != nil:
|
||||
return i.OCIManifest.Payload()
|
||||
default:
|
||||
return "", nil, errors.Errorf("%s has no payload", i.Ref)
|
||||
}
|
||||
}
|
||||
|
||||
// References implements the distribution.Manifest interface. It delegates to
|
||||
// the underlying manifest.
|
||||
func (i ImageManifest) References() []distribution.Descriptor {
|
||||
switch {
|
||||
case i.SchemaV2Manifest != nil:
|
||||
return i.SchemaV2Manifest.References()
|
||||
case i.OCIManifest != nil:
|
||||
return i.OCIManifest.References()
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// NewImageManifest returns a new ImageManifest object. The values for Platform
|
||||
// are initialized from those in the image
|
||||
func NewImageManifest(ref reference.Named, desc ocispec.Descriptor, manifest *schema2.DeserializedManifest) ImageManifest {
|
||||
raw, err := manifest.MarshalJSON()
|
||||
if err != nil {
|
||||
raw = nil
|
||||
}
|
||||
|
||||
return ImageManifest{
|
||||
Ref: &SerializableNamed{Named: ref},
|
||||
Descriptor: desc,
|
||||
Raw: raw,
|
||||
SchemaV2Manifest: manifest,
|
||||
}
|
||||
}
|
||||
|
||||
// NewOCIImageManifest returns a new ImageManifest object. The values for
|
||||
// Platform are initialized from those in the image
|
||||
func NewOCIImageManifest(ref reference.Named, desc ocispec.Descriptor, manifest *ocischema.DeserializedManifest) ImageManifest {
|
||||
raw, err := manifest.MarshalJSON()
|
||||
if err != nil {
|
||||
raw = nil
|
||||
}
|
||||
|
||||
return ImageManifest{
|
||||
Ref: &SerializableNamed{Named: ref},
|
||||
Descriptor: desc,
|
||||
Raw: raw,
|
||||
OCIManifest: manifest,
|
||||
}
|
||||
}
|
||||
|
||||
// SerializableNamed is a reference.Named that can be serialized and deserialized
|
||||
// from JSON
|
||||
type SerializableNamed struct {
|
||||
reference.Named
|
||||
}
|
||||
|
||||
// UnmarshalJSON loads the Named reference from JSON bytes
|
||||
func (s *SerializableNamed) UnmarshalJSON(b []byte) error {
|
||||
var raw string
|
||||
if err := json.Unmarshal(b, &raw); err != nil {
|
||||
return errors.Wrapf(err, "invalid named reference bytes: %s", b)
|
||||
}
|
||||
var err error
|
||||
s.Named, err = reference.ParseNamed(raw)
|
||||
return err
|
||||
}
|
||||
|
||||
// MarshalJSON returns the JSON bytes representation
|
||||
func (s *SerializableNamed) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(s.String())
|
||||
}
|
||||
198
vendor/github.com/docker/cli/cli/registry/client/client.go
generated
vendored
198
vendor/github.com/docker/cli/cli/registry/client/client.go
generated
vendored
@ -1,198 +0,0 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"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"
|
||||
"github.com/opencontainers/go-digest"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// RegistryClient is a client used to communicate with a Docker distribution
|
||||
// registry
|
||||
type RegistryClient interface {
|
||||
GetManifest(ctx context.Context, ref reference.Named) (manifesttypes.ImageManifest, error)
|
||||
GetManifestList(ctx context.Context, ref reference.Named) ([]manifesttypes.ImageManifest, error)
|
||||
MountBlob(ctx context.Context, source reference.Canonical, target reference.Named) error
|
||||
PutManifest(ctx context.Context, ref reference.Named, manifest distribution.Manifest) (digest.Digest, error)
|
||||
}
|
||||
|
||||
// NewRegistryClient returns a new RegistryClient with a resolver
|
||||
func NewRegistryClient(resolver AuthConfigResolver, userAgent string, insecure bool) RegistryClient {
|
||||
return &client{
|
||||
authConfigResolver: resolver,
|
||||
insecureRegistry: insecure,
|
||||
userAgent: userAgent,
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
userAgent string
|
||||
}
|
||||
|
||||
// ErrBlobCreated returned when a blob mount request was created
|
||||
type ErrBlobCreated struct {
|
||||
From reference.Named
|
||||
Target reference.Named
|
||||
}
|
||||
|
||||
func (err ErrBlobCreated) Error() string {
|
||||
return fmt.Sprintf("blob mounted from: %v to: %v",
|
||||
err.From, err.Target)
|
||||
}
|
||||
|
||||
// ErrHTTPProto returned if attempting to use TLS with a non-TLS registry
|
||||
type ErrHTTPProto struct {
|
||||
OrigErr string
|
||||
}
|
||||
|
||||
func (err ErrHTTPProto) Error() string {
|
||||
return err.OrigErr
|
||||
}
|
||||
|
||||
var _ RegistryClient = &client{}
|
||||
|
||||
// MountBlob into the registry, so it can be referenced by a manifest
|
||||
func (c *client) MountBlob(ctx context.Context, sourceRef reference.Canonical, targetRef reference.Named) error {
|
||||
repoEndpoint, err := newDefaultRepositoryEndpoint(targetRef, c.insecureRegistry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
repoEndpoint.actions = trust.ActionsPushAndPull
|
||||
repo, err := c.getRepositoryForReference(ctx, targetRef, repoEndpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
lu, err := repo.Blobs(ctx).Create(ctx, distributionclient.WithMountFrom(sourceRef))
|
||||
switch err.(type) {
|
||||
case distribution.ErrBlobMounted:
|
||||
logrus.Debugf("mount of blob %s succeeded", sourceRef)
|
||||
return nil
|
||||
case nil:
|
||||
default:
|
||||
return errors.Wrapf(err, "failed to mount blob %s to %s", sourceRef, targetRef)
|
||||
}
|
||||
lu.Cancel(ctx)
|
||||
logrus.Debugf("mount of blob %s created", sourceRef)
|
||||
return ErrBlobCreated{From: sourceRef, Target: targetRef}
|
||||
}
|
||||
|
||||
// PutManifest sends the manifest to a registry and returns the new digest
|
||||
func (c *client) PutManifest(ctx context.Context, ref reference.Named, manifest distribution.Manifest) (digest.Digest, error) {
|
||||
repoEndpoint, err := newDefaultRepositoryEndpoint(ref, c.insecureRegistry)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
repoEndpoint.actions = trust.ActionsPushAndPull
|
||||
repo, err := c.getRepositoryForReference(ctx, ref, repoEndpoint)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
manifestService, err := repo.Manifests(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
_, opts, err := getManifestOptionsFromReference(ref)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
dgst, err := manifestService.Put(ctx, manifest, opts...)
|
||||
return dgst, errors.Wrapf(err, "failed to put manifest %s", ref)
|
||||
}
|
||||
|
||||
func (c *client) getRepositoryForReference(ctx context.Context, ref reference.Named, repoEndpoint repositoryEndpoint) (distribution.Repository, error) {
|
||||
repoName, err := reference.WithName(repoEndpoint.Name())
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to parse repo name from %s", ref)
|
||||
}
|
||||
httpTransport, err := c.getHTTPTransportForRepoEndpoint(ctx, repoEndpoint)
|
||||
if err != nil {
|
||||
if !strings.Contains(err.Error(), "server gave HTTP response to HTTPS client") {
|
||||
return nil, err
|
||||
}
|
||||
if !repoEndpoint.endpoint.TLSConfig.InsecureSkipVerify {
|
||||
return nil, ErrHTTPProto{OrigErr: err.Error()}
|
||||
}
|
||||
// --insecure was set; fall back to plain HTTP
|
||||
if url := repoEndpoint.endpoint.URL; url != nil && url.Scheme == "https" {
|
||||
url.Scheme = "http"
|
||||
httpTransport, err = c.getHTTPTransportForRepoEndpoint(ctx, repoEndpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return distributionclient.NewRepository(repoName, repoEndpoint.BaseURL(), httpTransport)
|
||||
}
|
||||
|
||||
func (c *client) getHTTPTransportForRepoEndpoint(ctx context.Context, repoEndpoint repositoryEndpoint) (http.RoundTripper, error) {
|
||||
httpTransport, err := getHTTPTransport(
|
||||
c.authConfigResolver(ctx, repoEndpoint.info.Index),
|
||||
repoEndpoint.endpoint,
|
||||
repoEndpoint.Name(),
|
||||
c.userAgent,
|
||||
repoEndpoint.actions,
|
||||
)
|
||||
return httpTransport, errors.Wrap(err, "failed to configure transport")
|
||||
}
|
||||
|
||||
// GetManifest returns an ImageManifest for the reference
|
||||
func (c *client) GetManifest(ctx context.Context, ref reference.Named) (manifesttypes.ImageManifest, error) {
|
||||
var result manifesttypes.ImageManifest
|
||||
fetch := func(ctx context.Context, repo distribution.Repository, ref reference.Named) (bool, error) {
|
||||
var err error
|
||||
result, err = fetchManifest(ctx, repo, ref)
|
||||
return result.Ref != nil, err
|
||||
}
|
||||
|
||||
err := c.iterateEndpoints(ctx, ref, fetch)
|
||||
return result, err
|
||||
}
|
||||
|
||||
// GetManifestList returns a list of ImageManifest for the reference
|
||||
func (c *client) GetManifestList(ctx context.Context, ref reference.Named) ([]manifesttypes.ImageManifest, error) {
|
||||
result := []manifesttypes.ImageManifest{}
|
||||
fetch := func(ctx context.Context, repo distribution.Repository, ref reference.Named) (bool, error) {
|
||||
var err error
|
||||
result, err = fetchList(ctx, repo, ref)
|
||||
return len(result) > 0, err
|
||||
}
|
||||
|
||||
err := c.iterateEndpoints(ctx, ref, fetch)
|
||||
return result, err
|
||||
}
|
||||
|
||||
func getManifestOptionsFromReference(ref reference.Named) (digest.Digest, []distribution.ManifestServiceOption, error) {
|
||||
if tagged, isTagged := ref.(reference.NamedTagged); isTagged {
|
||||
tag := tagged.Tag()
|
||||
return "", []distribution.ManifestServiceOption{distribution.WithTag(tag)}, nil
|
||||
}
|
||||
if digested, isDigested := ref.(reference.Canonical); isDigested {
|
||||
return digested.Digest(), []distribution.ManifestServiceOption{}, nil
|
||||
}
|
||||
return "", nil, errors.Errorf("%s no tag or digest", ref)
|
||||
}
|
||||
128
vendor/github.com/docker/cli/cli/registry/client/endpoint.go
generated
vendored
128
vendor/github.com/docker/cli/cli/registry/client/endpoint.go
generated
vendored
@ -1,128 +0,0 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"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"
|
||||
"github.com/docker/docker/registry"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type repositoryEndpoint struct {
|
||||
info *registry.RepositoryInfo
|
||||
endpoint registry.APIEndpoint
|
||||
actions []string
|
||||
}
|
||||
|
||||
// Name returns the repository name
|
||||
func (r repositoryEndpoint) Name() string {
|
||||
return reference.Path(r.info.Name)
|
||||
}
|
||||
|
||||
// BaseURL returns the endpoint url
|
||||
func (r repositoryEndpoint) BaseURL() string {
|
||||
return r.endpoint.URL.String()
|
||||
}
|
||||
|
||||
func newDefaultRepositoryEndpoint(ref reference.Named, insecure bool) (repositoryEndpoint, error) {
|
||||
repoInfo, err := registry.ParseRepositoryInfo(ref)
|
||||
if err != nil {
|
||||
return repositoryEndpoint{}, err
|
||||
}
|
||||
endpoint, err := getDefaultEndpointFromRepoInfo(repoInfo)
|
||||
if err != nil {
|
||||
return repositoryEndpoint{}, err
|
||||
}
|
||||
if insecure {
|
||||
endpoint.TLSConfig.InsecureSkipVerify = true
|
||||
}
|
||||
return repositoryEndpoint{info: repoInfo, endpoint: endpoint}, nil
|
||||
}
|
||||
|
||||
func getDefaultEndpointFromRepoInfo(repoInfo *registry.RepositoryInfo) (registry.APIEndpoint, error) {
|
||||
var err error
|
||||
|
||||
options := registry.ServiceOptions{}
|
||||
registryService, err := registry.NewService(options)
|
||||
if err != nil {
|
||||
return registry.APIEndpoint{}, err
|
||||
}
|
||||
endpoints, err := registryService.LookupPushEndpoints(reference.Domain(repoInfo.Name))
|
||||
if err != nil {
|
||||
return registry.APIEndpoint{}, err
|
||||
}
|
||||
// Default to the highest priority endpoint to return
|
||||
endpoint := endpoints[0]
|
||||
if !repoInfo.Index.Secure {
|
||||
for _, ep := range endpoints {
|
||||
if ep.URL.Scheme == "http" {
|
||||
endpoint = ep
|
||||
}
|
||||
}
|
||||
}
|
||||
return endpoint, nil
|
||||
}
|
||||
|
||||
// getHTTPTransport builds a transport for use in communicating with a registry
|
||||
func getHTTPTransport(authConfig registrytypes.AuthConfig, endpoint registry.APIEndpoint, repoName, userAgent string, actions []string) (http.RoundTripper, error) {
|
||||
// get the http transport, this will be used in a client to upload manifest
|
||||
base := &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
Dial: (&net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}).Dial,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
TLSClientConfig: endpoint.TLSConfig,
|
||||
DisableKeepAlives: true,
|
||||
}
|
||||
|
||||
modifiers := registry.Headers(userAgent, http.Header{})
|
||||
authTransport := transport.NewTransport(base, modifiers...)
|
||||
challengeManager, err := registry.PingV2Registry(endpoint.URL, authTransport)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error pinging v2 registry")
|
||||
}
|
||||
if authConfig.RegistryToken != "" {
|
||||
passThruTokenHandler := &existingTokenHandler{token: authConfig.RegistryToken}
|
||||
modifiers = append(modifiers, auth.NewAuthorizer(challengeManager, passThruTokenHandler))
|
||||
} else {
|
||||
if len(actions) == 0 {
|
||||
actions = trust.ActionsPullOnly
|
||||
}
|
||||
creds := registry.NewStaticCredentialStore(&authConfig)
|
||||
tokenHandler := auth.NewTokenHandler(authTransport, creds, repoName, actions...)
|
||||
basicHandler := auth.NewBasicHandler(creds)
|
||||
modifiers = append(modifiers, auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler))
|
||||
}
|
||||
return transport.NewTransport(base, modifiers...), nil
|
||||
}
|
||||
|
||||
// RepoNameForReference returns the repository name from a reference
|
||||
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
|
||||
}
|
||||
|
||||
type existingTokenHandler struct {
|
||||
token string
|
||||
}
|
||||
|
||||
func (th *existingTokenHandler) AuthorizeRequest(req *http.Request, _ map[string]string) error {
|
||||
req.Header.Set("Authorization", "Bearer "+th.token)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*existingTokenHandler) Scheme() string {
|
||||
return "bearer"
|
||||
}
|
||||
307
vendor/github.com/docker/cli/cli/registry/client/fetcher.go
generated
vendored
307
vendor/github.com/docker/cli/cli/registry/client/fetcher.go
generated
vendored
@ -1,307 +0,0 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/cli/cli/manifest/types"
|
||||
"github.com/docker/distribution"
|
||||
"github.com/docker/distribution/manifest/manifestlist"
|
||||
"github.com/docker/distribution/manifest/ocischema"
|
||||
"github.com/docker/distribution/manifest/schema2"
|
||||
"github.com/docker/distribution/registry/api/errcode"
|
||||
v2 "github.com/docker/distribution/registry/api/v2"
|
||||
distclient "github.com/docker/distribution/registry/client"
|
||||
"github.com/docker/docker/registry"
|
||||
"github.com/opencontainers/go-digest"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// fetchManifest pulls a manifest from a registry and returns it. An error
|
||||
// is returned if no manifest is found matching namedRef.
|
||||
func fetchManifest(ctx context.Context, repo distribution.Repository, ref reference.Named) (types.ImageManifest, error) {
|
||||
manifest, err := getManifest(ctx, repo, ref)
|
||||
if err != nil {
|
||||
return types.ImageManifest{}, err
|
||||
}
|
||||
|
||||
switch v := manifest.(type) {
|
||||
// Removed Schema 1 support
|
||||
case *schema2.DeserializedManifest:
|
||||
return pullManifestSchemaV2(ctx, ref, repo, *v)
|
||||
case *ocischema.DeserializedManifest:
|
||||
return pullManifestOCISchema(ctx, ref, repo, *v)
|
||||
case *manifestlist.DeserializedManifestList:
|
||||
return types.ImageManifest{}, errors.Errorf("%s is a manifest list", ref)
|
||||
}
|
||||
return types.ImageManifest{}, errors.Errorf("%s is not a manifest", ref)
|
||||
}
|
||||
|
||||
func fetchList(ctx context.Context, repo distribution.Repository, ref reference.Named) ([]types.ImageManifest, error) {
|
||||
manifest, err := getManifest(ctx, repo, ref)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch v := manifest.(type) {
|
||||
case *manifestlist.DeserializedManifestList:
|
||||
return pullManifestList(ctx, ref, repo, *v)
|
||||
default:
|
||||
return nil, errors.Errorf("unsupported manifest format: %v", v)
|
||||
}
|
||||
}
|
||||
|
||||
func getManifest(ctx context.Context, repo distribution.Repository, ref reference.Named) (distribution.Manifest, error) {
|
||||
manSvc, err := repo.Manifests(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dgst, opts, err := getManifestOptionsFromReference(ref)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("image manifest for %q does not exist", ref)
|
||||
}
|
||||
return manSvc.Get(ctx, dgst, opts...)
|
||||
}
|
||||
|
||||
func pullManifestSchemaV2(ctx context.Context, ref reference.Named, repo distribution.Repository, mfst schema2.DeserializedManifest) (types.ImageManifest, error) {
|
||||
manifestDesc, err := validateManifestDigest(ref, mfst)
|
||||
if err != nil {
|
||||
return types.ImageManifest{}, err
|
||||
}
|
||||
configJSON, err := pullManifestSchemaV2ImageConfig(ctx, mfst.Target().Digest, repo)
|
||||
if err != nil {
|
||||
return types.ImageManifest{}, err
|
||||
}
|
||||
|
||||
if manifestDesc.Platform == nil {
|
||||
manifestDesc.Platform = &ocispec.Platform{}
|
||||
}
|
||||
|
||||
// Fill in os and architecture fields from config JSON
|
||||
if err := json.Unmarshal(configJSON, manifestDesc.Platform); err != nil {
|
||||
return types.ImageManifest{}, err
|
||||
}
|
||||
|
||||
return types.NewImageManifest(ref, manifestDesc, &mfst), nil
|
||||
}
|
||||
|
||||
func pullManifestOCISchema(ctx context.Context, ref reference.Named, repo distribution.Repository, mfst ocischema.DeserializedManifest) (types.ImageManifest, error) {
|
||||
manifestDesc, err := validateManifestDigest(ref, mfst)
|
||||
if err != nil {
|
||||
return types.ImageManifest{}, err
|
||||
}
|
||||
configJSON, err := pullManifestSchemaV2ImageConfig(ctx, mfst.Target().Digest, repo)
|
||||
if err != nil {
|
||||
return types.ImageManifest{}, err
|
||||
}
|
||||
|
||||
if manifestDesc.Platform == nil {
|
||||
manifestDesc.Platform = &ocispec.Platform{}
|
||||
}
|
||||
|
||||
// Fill in os and architecture fields from config JSON
|
||||
if err := json.Unmarshal(configJSON, manifestDesc.Platform); err != nil {
|
||||
return types.ImageManifest{}, err
|
||||
}
|
||||
|
||||
return types.NewOCIImageManifest(ref, manifestDesc, &mfst), nil
|
||||
}
|
||||
|
||||
func pullManifestSchemaV2ImageConfig(ctx context.Context, dgst digest.Digest, repo distribution.Repository) ([]byte, error) {
|
||||
blobs := repo.Blobs(ctx)
|
||||
configJSON, err := blobs.Get(ctx, dgst)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
verifier := dgst.Verifier()
|
||||
if _, err := verifier.Write(configJSON); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !verifier.Verified() {
|
||||
return nil, errors.Errorf("image config verification failed for digest %s", dgst)
|
||||
}
|
||||
return configJSON, nil
|
||||
}
|
||||
|
||||
// validateManifestDigest computes the manifest digest, and, if pulling by
|
||||
// digest, ensures that it matches the requested digest.
|
||||
func validateManifestDigest(ref reference.Named, mfst distribution.Manifest) (ocispec.Descriptor, error) {
|
||||
mediaType, canonical, err := mfst.Payload()
|
||||
if err != nil {
|
||||
return ocispec.Descriptor{}, err
|
||||
}
|
||||
desc := ocispec.Descriptor{
|
||||
Digest: digest.FromBytes(canonical),
|
||||
Size: int64(len(canonical)),
|
||||
MediaType: mediaType,
|
||||
}
|
||||
|
||||
// If pull by digest, then verify the manifest digest.
|
||||
if digested, isDigested := ref.(reference.Canonical); isDigested && digested.Digest() != desc.Digest {
|
||||
return ocispec.Descriptor{}, errors.Errorf("manifest verification failed for digest %s", digested.Digest())
|
||||
}
|
||||
|
||||
return desc, nil
|
||||
}
|
||||
|
||||
// pullManifestList handles "manifest lists" which point to various
|
||||
// platform-specific manifests.
|
||||
func pullManifestList(ctx context.Context, ref reference.Named, repo distribution.Repository, mfstList manifestlist.DeserializedManifestList) ([]types.ImageManifest, error) {
|
||||
if _, err := validateManifestDigest(ref, mfstList); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
infos := make([]types.ImageManifest, 0, len(mfstList.Manifests))
|
||||
for _, manifestDescriptor := range mfstList.Manifests {
|
||||
manSvc, err := repo.Manifests(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
manifest, err := manSvc.Get(ctx, manifestDescriptor.Digest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
manifestRef, err := reference.WithDigest(ref, manifestDescriptor.Digest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var imageManifest types.ImageManifest
|
||||
switch v := manifest.(type) {
|
||||
case *schema2.DeserializedManifest:
|
||||
imageManifest, err = pullManifestSchemaV2(ctx, manifestRef, repo, *v)
|
||||
case *ocischema.DeserializedManifest:
|
||||
imageManifest, err = pullManifestOCISchema(ctx, manifestRef, repo, *v)
|
||||
default:
|
||||
err = errors.Errorf("unsupported manifest type: %T", manifest)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Replace platform from config
|
||||
p := manifestDescriptor.Platform
|
||||
imageManifest.Descriptor.Platform = types.OCIPlatform(&p)
|
||||
|
||||
infos = append(infos, imageManifest)
|
||||
}
|
||||
return infos, nil
|
||||
}
|
||||
|
||||
func continueOnError(err error) bool {
|
||||
switch v := err.(type) {
|
||||
case errcode.Errors:
|
||||
if len(v) == 0 {
|
||||
return true
|
||||
}
|
||||
return continueOnError(v[0])
|
||||
case errcode.Error:
|
||||
switch e := err.(errcode.Error); e.Code {
|
||||
case errcode.ErrorCodeUnauthorized, v2.ErrorCodeManifestUnknown, v2.ErrorCodeNameUnknown:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
case *distclient.UnexpectedHTTPResponseError:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *client) iterateEndpoints(ctx context.Context, namedRef reference.Named, each func(context.Context, distribution.Repository, reference.Named) (bool, error)) error {
|
||||
endpoints, err := allEndpoints(namedRef, c.insecureRegistry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
repoInfo, err := registry.ParseRepositoryInfo(namedRef)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
confirmedTLSRegistries := make(map[string]bool)
|
||||
for _, endpoint := range endpoints {
|
||||
if endpoint.URL.Scheme != "https" {
|
||||
if _, confirmedTLS := confirmedTLSRegistries[endpoint.URL.Host]; confirmedTLS {
|
||||
logrus.Debugf("skipping non-TLS endpoint %s for host/port that appears to use TLS", endpoint.URL)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if c.insecureRegistry {
|
||||
endpoint.TLSConfig.InsecureSkipVerify = true
|
||||
}
|
||||
repoEndpoint := repositoryEndpoint{endpoint: endpoint, info: repoInfo}
|
||||
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 {
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if endpoint.URL.Scheme == "http" && !c.insecureRegistry {
|
||||
logrus.Debugf("skipping non-tls registry endpoint: %s", endpoint.URL)
|
||||
continue
|
||||
}
|
||||
done, err := each(ctx, repo, namedRef)
|
||||
if err != nil {
|
||||
if continueOnError(err) {
|
||||
if endpoint.URL.Scheme == "https" {
|
||||
confirmedTLSRegistries[endpoint.URL.Host] = true
|
||||
}
|
||||
logrus.Debugf("continuing on error (%T) %s", err, err)
|
||||
continue
|
||||
}
|
||||
logrus.Debugf("not continuing on error (%T) %s", err, err)
|
||||
return err
|
||||
}
|
||||
if done {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return newNotFoundError(namedRef.String())
|
||||
}
|
||||
|
||||
// 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))
|
||||
serviceOpts.InsecureRegistries = []string{reference.Domain(namedRef)}
|
||||
}
|
||||
registryService, err := registry.NewService(serviceOpts)
|
||||
if err != nil {
|
||||
return []registry.APIEndpoint{}, err
|
||||
}
|
||||
endpoints, err := registryService.LookupPullEndpoints(reference.Domain(repoInfo.Name))
|
||||
logrus.Debugf("endpoints for %s: %v", namedRef, endpoints)
|
||||
return endpoints, err
|
||||
}
|
||||
|
||||
func newNotFoundError(ref string) *notFoundError {
|
||||
return ¬FoundError{err: errors.New("no such manifest: " + ref)}
|
||||
}
|
||||
|
||||
type notFoundError struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (n *notFoundError) Error() string {
|
||||
return n.err.Error()
|
||||
}
|
||||
|
||||
// NotFound satisfies interface github.com/docker/docker/errdefs.ErrNotFound
|
||||
func (notFoundError) NotFound() {}
|
||||
376
vendor/github.com/docker/cli/cli/trust/trust.go
generated
vendored
376
vendor/github.com/docker/cli/cli/trust/trust.go
generated
vendored
@ -1,376 +0,0 @@
|
||||
package trust
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/cli/cli/config"
|
||||
"github.com/docker/distribution/registry/client/auth"
|
||||
"github.com/docker/distribution/registry/client/auth/challenge"
|
||||
"github.com/docker/distribution/registry/client/transport"
|
||||
registrytypes "github.com/docker/docker/api/types/registry"
|
||||
"github.com/docker/docker/registry"
|
||||
"github.com/docker/go-connections/tlsconfig"
|
||||
"github.com/opencontainers/go-digest"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/theupdateframework/notary"
|
||||
"github.com/theupdateframework/notary/client"
|
||||
"github.com/theupdateframework/notary/passphrase"
|
||||
"github.com/theupdateframework/notary/storage"
|
||||
"github.com/theupdateframework/notary/trustmanager"
|
||||
"github.com/theupdateframework/notary/trustpinning"
|
||||
"github.com/theupdateframework/notary/tuf/data"
|
||||
"github.com/theupdateframework/notary/tuf/signed"
|
||||
)
|
||||
|
||||
var (
|
||||
// ReleasesRole is the role named "releases"
|
||||
ReleasesRole = data.RoleName(path.Join(data.CanonicalTargetsRole.String(), "releases"))
|
||||
// ActionsPullOnly defines the actions for read-only interactions with a Notary Repository
|
||||
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"
|
||||
)
|
||||
|
||||
// GetTrustDirectory returns the base trust directory name
|
||||
func GetTrustDirectory() string {
|
||||
return filepath.Join(config.Dir(), "trust")
|
||||
}
|
||||
|
||||
// certificateDirectory returns the directory containing
|
||||
// TLS certificates for the given server. An error is
|
||||
// returned if there was an error parsing the server string.
|
||||
func certificateDirectory(server string) (string, error) {
|
||||
u, err := url.Parse(server)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return filepath.Join(config.Dir(), "tls", u.Host), nil
|
||||
}
|
||||
|
||||
// Server returns the base URL for the trust server.
|
||||
func Server(index *registrytypes.IndexInfo) (string, error) {
|
||||
if s := os.Getenv("DOCKER_CONTENT_TRUST_SERVER"); s != "" {
|
||||
urlObj, err := url.Parse(s)
|
||||
if err != nil || urlObj.Scheme != "https" {
|
||||
return "", errors.Errorf("valid https URL required for trust server, got %s", s)
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
if index.Official {
|
||||
return NotaryServer, nil
|
||||
}
|
||||
return "https://" + index.Name, nil
|
||||
}
|
||||
|
||||
type simpleCredentialStore struct {
|
||||
auth registrytypes.AuthConfig
|
||||
}
|
||||
|
||||
func (scs simpleCredentialStore) Basic(*url.URL) (string, string) {
|
||||
return scs.auth.Username, scs.auth.Password
|
||||
}
|
||||
|
||||
func (scs simpleCredentialStore) RefreshToken(*url.URL, string) string {
|
||||
return scs.auth.IdentityToken
|
||||
}
|
||||
|
||||
func (simpleCredentialStore) SetRefreshToken(*url.URL, string, string) {}
|
||||
|
||||
// GetNotaryRepository returns a NotaryRepository which stores all the
|
||||
// information needed to operate on a notary repository.
|
||||
// It creates an HTTP transport providing authentication support.
|
||||
func GetNotaryRepository(in io.Reader, out io.Writer, userAgent string, repoInfo *registry.RepositoryInfo, authConfig *registrytypes.AuthConfig, actions ...string) (client.Repository, error) {
|
||||
server, err := Server(repoInfo.Index)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cfg := tlsconfig.ClientDefault()
|
||||
cfg.InsecureSkipVerify = !repoInfo.Index.Secure
|
||||
|
||||
// Get certificate base directory
|
||||
certDir, err := certificateDirectory(server)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
logrus.Debugf("reading certificate directory: %s", certDir)
|
||||
|
||||
if err := registry.ReadCertsDirectory(cfg, certDir); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
base := &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
Dial: (&net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}).Dial,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
TLSClientConfig: cfg,
|
||||
DisableKeepAlives: true,
|
||||
}
|
||||
|
||||
// Skip configuration headers since request is not going to Docker daemon
|
||||
modifiers := registry.Headers(userAgent, http.Header{})
|
||||
authTransport := transport.NewTransport(base, modifiers...)
|
||||
pingClient := &http.Client{
|
||||
Transport: authTransport,
|
||||
Timeout: 5 * time.Second,
|
||||
}
|
||||
endpointStr := server + "/v2/"
|
||||
req, err := http.NewRequest(http.MethodGet, endpointStr, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
challengeManager := challenge.NewSimpleManager()
|
||||
|
||||
resp, err := pingClient.Do(req)
|
||||
if err != nil {
|
||||
// Ignore error on ping to operate in offline mode
|
||||
logrus.Debugf("Error pinging notary server %q: %s", endpointStr, err)
|
||||
} else {
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Add response to the challenge manager to parse out
|
||||
// authentication header and register authentication method
|
||||
if err := challengeManager.AddResponse(resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
scope := auth.RepositoryScope{
|
||||
Repository: repoInfo.Name.Name(),
|
||||
Actions: actions,
|
||||
}
|
||||
creds := simpleCredentialStore{auth: *authConfig}
|
||||
tokenHandlerOptions := auth.TokenHandlerOptions{
|
||||
Transport: authTransport,
|
||||
Credentials: creds,
|
||||
Scopes: []auth.Scope{scope},
|
||||
ClientID: registry.AuthClientID,
|
||||
}
|
||||
tokenHandler := auth.NewTokenHandlerWithOptions(tokenHandlerOptions)
|
||||
basicHandler := auth.NewBasicHandler(creds)
|
||||
modifiers = append(modifiers, auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler))
|
||||
tr := transport.NewTransport(base, modifiers...)
|
||||
|
||||
return client.NewFileCachedRepository(
|
||||
GetTrustDirectory(),
|
||||
data.GUN(repoInfo.Name.Name()),
|
||||
server,
|
||||
tr,
|
||||
GetPassphraseRetriever(in, out),
|
||||
trustpinning.TrustPinConfig{})
|
||||
}
|
||||
|
||||
// GetPassphraseRetriever returns a passphrase retriever that utilizes Content Trust env vars
|
||||
func GetPassphraseRetriever(in io.Reader, out io.Writer) notary.PassRetriever {
|
||||
aliasMap := map[string]string{
|
||||
"root": "root",
|
||||
"snapshot": "repository",
|
||||
"targets": "repository",
|
||||
"default": "repository",
|
||||
}
|
||||
baseRetriever := passphrase.PromptRetrieverWithInOut(in, out, aliasMap)
|
||||
env := map[string]string{
|
||||
"root": os.Getenv("DOCKER_CONTENT_TRUST_ROOT_PASSPHRASE"),
|
||||
"snapshot": os.Getenv("DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"),
|
||||
"targets": os.Getenv("DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"),
|
||||
"default": os.Getenv("DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"),
|
||||
}
|
||||
|
||||
return func(keyName string, alias string, createNew bool, numAttempts int) (string, bool, error) {
|
||||
if v := env[alias]; v != "" {
|
||||
return v, numAttempts > 1, nil
|
||||
}
|
||||
// For non-root roles, we can also try the "default" alias if it is specified
|
||||
if v := env["default"]; v != "" && alias != data.CanonicalRootRole.String() {
|
||||
return v, numAttempts > 1, nil
|
||||
}
|
||||
return baseRetriever(keyName, alias, createNew, numAttempts)
|
||||
}
|
||||
}
|
||||
|
||||
// NotaryError formats an error message received from the notary service
|
||||
func NotaryError(repoName string, err error) error {
|
||||
switch err.(type) {
|
||||
case *json.SyntaxError:
|
||||
logrus.Debugf("Notary syntax error: %s", err)
|
||||
return errors.Errorf("Error: no trust data available for remote repository %s. Try running notary server and setting DOCKER_CONTENT_TRUST_SERVER to its HTTPS address?", repoName)
|
||||
case signed.ErrExpired:
|
||||
return errors.Errorf("Error: remote repository %s out-of-date: %v", repoName, err)
|
||||
case trustmanager.ErrKeyNotFound:
|
||||
return errors.Errorf("Error: signing keys for remote repository %s not found: %v", repoName, err)
|
||||
case storage.NetworkError:
|
||||
return errors.Errorf("Error: error contacting notary server: %v", err)
|
||||
case storage.ErrMetaNotFound:
|
||||
return errors.Errorf("Error: trust data missing for remote repository %s or remote repository not found: %v", repoName, err)
|
||||
case trustpinning.ErrRootRotationFail, trustpinning.ErrValidationFail, signed.ErrInvalidKeyType:
|
||||
return errors.Errorf("Warning: potential malicious behavior - trust data mismatch for remote repository %s: %v", repoName, err)
|
||||
case signed.ErrNoKeys:
|
||||
return errors.Errorf("Error: could not find signing keys for remote repository %s, or could not decrypt signing key: %v", repoName, err)
|
||||
case signed.ErrLowVersion:
|
||||
return errors.Errorf("Warning: potential malicious behavior - trust data version is lower than expected for remote repository %s: %v", repoName, err)
|
||||
case signed.ErrRoleThreshold:
|
||||
return errors.Errorf("Warning: potential malicious behavior - trust data has insufficient signatures for remote repository %s: %v", repoName, err)
|
||||
case client.ErrRepositoryNotExist:
|
||||
return errors.Errorf("Error: remote trust data does not exist for %s: %v", repoName, err)
|
||||
case signed.ErrInsufficientSignatures:
|
||||
return errors.Errorf("Error: could not produce valid signature for %s. If Yubikey was used, was touch input provided?: %v", repoName, err)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// 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) {
|
||||
var signableRoles []data.RoleName
|
||||
|
||||
// translate the full key names, which includes the GUN, into just the key IDs
|
||||
allCanonicalKeyIDs := make(map[string]struct{})
|
||||
for fullKeyID := range repo.GetCryptoService().ListAllKeys() {
|
||||
allCanonicalKeyIDs[path.Base(fullKeyID)] = struct{}{}
|
||||
}
|
||||
|
||||
allDelegationRoles, err := repo.GetDelegationRoles()
|
||||
if err != nil {
|
||||
return signableRoles, err
|
||||
}
|
||||
|
||||
// if there are no delegation roles, then just try to sign it into the targets role
|
||||
if len(allDelegationRoles) == 0 {
|
||||
signableRoles = append(signableRoles, data.CanonicalTargetsRole)
|
||||
return signableRoles, nil
|
||||
}
|
||||
|
||||
// there are delegation roles, find every delegation role we have a key for,
|
||||
// and attempt to sign in to all those roles.
|
||||
for _, delegationRole := range allDelegationRoles {
|
||||
// We do not support signing any delegation role that isn't a direct child of the targets role.
|
||||
// Also don't bother checking the keys if we can't add the target
|
||||
// to this role due to path restrictions
|
||||
if path.Dir(delegationRole.Name.String()) != data.CanonicalTargetsRole.String() || !delegationRole.CheckPaths(target.Name) {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, canonicalKeyID := range delegationRole.KeyIDs {
|
||||
if _, ok := allCanonicalKeyIDs[canonicalKeyID]; ok {
|
||||
signableRoles = append(signableRoles, delegationRole.Name)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(signableRoles) == 0 {
|
||||
return signableRoles, errors.Errorf("no valid signing keys for delegation roles")
|
||||
}
|
||||
|
||||
return signableRoles, nil
|
||||
}
|
||||
|
||||
// ImageRefAndAuth contains all reference information and the auth config for an image request
|
||||
type ImageRefAndAuth struct {
|
||||
original string
|
||||
authConfig *registrytypes.AuthConfig
|
||||
reference reference.Named
|
||||
repoInfo *registry.RepositoryInfo
|
||||
tag string
|
||||
digest digest.Digest
|
||||
}
|
||||
|
||||
// GetImageReferencesAndAuth retrieves the necessary reference and auth information for an image name
|
||||
// as an ImageRefAndAuth struct
|
||||
func GetImageReferencesAndAuth(ctx context.Context,
|
||||
authResolver func(ctx context.Context, index *registrytypes.IndexInfo) registrytypes.AuthConfig,
|
||||
imgName string,
|
||||
) (ImageRefAndAuth, error) {
|
||||
ref, err := reference.ParseNormalizedNamed(imgName)
|
||||
if err != nil {
|
||||
return ImageRefAndAuth{}, err
|
||||
}
|
||||
|
||||
// Resolve the Repository name from fqn to RepositoryInfo
|
||||
repoInfo, err := registry.ParseRepositoryInfo(ref)
|
||||
if err != nil {
|
||||
return ImageRefAndAuth{}, err
|
||||
}
|
||||
|
||||
authConfig := authResolver(ctx, repoInfo.Index)
|
||||
return ImageRefAndAuth{
|
||||
original: imgName,
|
||||
authConfig: &authConfig,
|
||||
reference: ref,
|
||||
repoInfo: repoInfo,
|
||||
tag: getTag(ref),
|
||||
digest: getDigest(ref),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getTag(ref reference.Named) string {
|
||||
switch x := ref.(type) {
|
||||
case reference.Canonical, reference.Digested:
|
||||
return ""
|
||||
case reference.NamedTagged:
|
||||
return x.Tag()
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func getDigest(ref reference.Named) digest.Digest {
|
||||
switch x := ref.(type) {
|
||||
case reference.Canonical:
|
||||
return x.Digest()
|
||||
case reference.Digested:
|
||||
return x.Digest()
|
||||
default:
|
||||
return digest.Digest("")
|
||||
}
|
||||
}
|
||||
|
||||
// AuthConfig returns the auth information (username, etc) for a given ImageRefAndAuth
|
||||
func (imgRefAuth *ImageRefAndAuth) AuthConfig() *registrytypes.AuthConfig {
|
||||
return imgRefAuth.authConfig
|
||||
}
|
||||
|
||||
// Reference returns the Image reference for a given ImageRefAndAuth
|
||||
func (imgRefAuth *ImageRefAndAuth) Reference() reference.Named {
|
||||
return imgRefAuth.reference
|
||||
}
|
||||
|
||||
// RepoInfo returns the repository information for a given ImageRefAndAuth
|
||||
func (imgRefAuth *ImageRefAndAuth) RepoInfo() *registry.RepositoryInfo {
|
||||
return imgRefAuth.repoInfo
|
||||
}
|
||||
|
||||
// Tag returns the Image tag for a given ImageRefAndAuth
|
||||
func (imgRefAuth *ImageRefAndAuth) Tag() string {
|
||||
return imgRefAuth.tag
|
||||
}
|
||||
|
||||
// Digest returns the Image digest for a given ImageRefAndAuth
|
||||
func (imgRefAuth *ImageRefAndAuth) Digest() digest.Digest {
|
||||
return imgRefAuth.digest
|
||||
}
|
||||
|
||||
// Name returns the image name used to initialize the ImageRefAndAuth
|
||||
func (imgRefAuth *ImageRefAndAuth) Name() string {
|
||||
return imgRefAuth.original
|
||||
}
|
||||
Reference in New Issue
Block a user