These options were used internally as defaults for the constructor and
only impact commands implemented in the CLI itself.
They were deprecated in 40cdfc0d81, which
was included in the 28.5.0 release, so removing it for 29.0
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
228 lines
7.6 KiB
Go
228 lines
7.6 KiB
Go
package command
|
|
|
|
import (
|
|
"context"
|
|
"encoding/csv"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/docker/cli/cli/streams"
|
|
"github.com/moby/moby/client"
|
|
"github.com/moby/term"
|
|
)
|
|
|
|
// CLIOption is a functional argument to apply options to a [DockerCli]. These
|
|
// options can be passed to [NewDockerCli] to initialize a new CLI, or
|
|
// applied with [DockerCli.Initialize] or [DockerCli.Apply].
|
|
type CLIOption func(cli *DockerCli) error
|
|
|
|
// WithStandardStreams sets a cli in, out and err streams with the standard streams.
|
|
func WithStandardStreams() CLIOption {
|
|
return func(cli *DockerCli) error {
|
|
// Set terminal emulation based on platform as required.
|
|
stdin, stdout, stderr := term.StdStreams()
|
|
cli.in = streams.NewIn(stdin)
|
|
cli.out = streams.NewOut(stdout)
|
|
cli.err = streams.NewOut(stderr)
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// WithBaseContext sets the base context of a cli. It is used to propagate
|
|
// the context from the command line to the client.
|
|
func WithBaseContext(ctx context.Context) CLIOption {
|
|
return func(cli *DockerCli) error {
|
|
cli.baseCtx = ctx
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// WithCombinedStreams uses the same stream for the output and error streams.
|
|
func WithCombinedStreams(combined io.Writer) CLIOption {
|
|
return func(cli *DockerCli) error {
|
|
s := streams.NewOut(combined)
|
|
cli.out = s
|
|
cli.err = s
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// WithInputStream sets a cli input stream.
|
|
func WithInputStream(in io.ReadCloser) CLIOption {
|
|
return func(cli *DockerCli) error {
|
|
cli.in = streams.NewIn(in)
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// WithOutputStream sets a cli output stream.
|
|
func WithOutputStream(out io.Writer) CLIOption {
|
|
return func(cli *DockerCli) error {
|
|
cli.out = streams.NewOut(out)
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// WithErrorStream sets a cli error stream.
|
|
func WithErrorStream(err io.Writer) CLIOption {
|
|
return func(cli *DockerCli) error {
|
|
cli.err = streams.NewOut(err)
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// WithDefaultContextStoreConfig configures the cli to use the default context store configuration.
|
|
func WithDefaultContextStoreConfig() CLIOption {
|
|
return func(cli *DockerCli) error {
|
|
cfg := DefaultContextStoreConfig()
|
|
cli.contextStoreConfig = &cfg
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// WithAPIClient configures the cli to use the given API client.
|
|
func WithAPIClient(c client.APIClient) CLIOption {
|
|
return func(cli *DockerCli) error {
|
|
cli.client = c
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// 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
|
|
// file.
|
|
//
|
|
// WARNING: If both config and environment-variable are set, the environment
|
|
// variable currently overrides all headers set in the configuration file.
|
|
// This behavior may change in a future update, as we are considering the
|
|
// environment variable to be appending to existing headers (and to only
|
|
// override headers with the same name).
|
|
//
|
|
// While this env-var allows for custom headers to be set, it does not allow
|
|
// for built-in headers (such as "User-Agent", if set) to be overridden.
|
|
// Also see [client.WithHTTPHeaders] and [client.WithUserAgent].
|
|
//
|
|
// This environment variable can be used in situations where headers must be
|
|
// set for a specific invocation of the CLI, but should not be set by default,
|
|
// and therefore cannot be set in the config-file.
|
|
//
|
|
// envOverrideHTTPHeaders accepts a comma-separated (CSV) list of key=value pairs,
|
|
// where key must be a non-empty, valid MIME header format. Whitespaces surrounding
|
|
// the key are trimmed, and the key is normalised. Whitespaces in values are
|
|
// preserved, but "key=value" pairs with an empty value (e.g. "key=") are ignored.
|
|
// Tuples without a "=" produce an error.
|
|
//
|
|
// It follows CSV rules for escaping, allowing "key=value" pairs to be quoted
|
|
// if they must contain commas, which allows for multiple values for a single
|
|
// header to be set. If a key is repeated in the list, later values override
|
|
// prior values.
|
|
//
|
|
// For example, the following value:
|
|
//
|
|
// one=one-value,"two=two,value","three= a value with whitespace ",four=,five=five=one,five=five-two
|
|
//
|
|
// Produces four headers (four is omitted as it has an empty value set):
|
|
//
|
|
// - one (value is "one-value")
|
|
// - two (value is "two,value")
|
|
// - three (value is " a value with whitespace ")
|
|
// - five (value is "five-two", the later value has overridden the prior value)
|
|
const envOverrideHTTPHeaders = "DOCKER_CUSTOM_HEADERS"
|
|
|
|
// withCustomHeadersFromEnv overriding custom HTTP headers to be sent by the
|
|
// client through the [envOverrideHTTPHeaders] environment-variable. This
|
|
// environment variable is the equivalent to the HttpHeaders field in the
|
|
// configuration file.
|
|
//
|
|
// WARNING: If both config and environment-variable are set, the environment-
|
|
// variable currently overrides all headers set in the configuration file.
|
|
// This behavior may change in a future update, as we are considering the
|
|
// environment-variable to be appending to existing headers (and to only
|
|
// override headers with the same name).
|
|
//
|
|
// TODO(thaJeztah): this is a client Option, and should be moved to the client. It is non-exported for that reason.
|
|
func withCustomHeadersFromEnv() (client.Opt, error) {
|
|
value := os.Getenv(envOverrideHTTPHeaders)
|
|
if value == "" {
|
|
return nil, nil
|
|
}
|
|
csvReader := csv.NewReader(strings.NewReader(value))
|
|
fields, err := csvReader.Read()
|
|
if err != nil {
|
|
return nil, invalidParameter(fmt.Errorf(
|
|
"failed to parse custom headers from %s environment variable: value must be formatted as comma-separated key=value pairs",
|
|
envOverrideHTTPHeaders,
|
|
))
|
|
}
|
|
if len(fields) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
env := map[string]string{}
|
|
for _, kv := range fields {
|
|
k, v, hasValue := strings.Cut(kv, "=")
|
|
|
|
// Only strip whitespace in keys; preserve whitespace in values.
|
|
k = strings.TrimSpace(k)
|
|
|
|
if k == "" {
|
|
return nil, invalidParameter(fmt.Errorf(
|
|
`failed to set custom headers from %s environment variable: value contains a key=value pair with an empty key: '%s'`,
|
|
envOverrideHTTPHeaders, kv,
|
|
))
|
|
}
|
|
|
|
// We don't currently allow empty key=value pairs, and produce an error.
|
|
// This is something we could allow in future (e.g. to read value
|
|
// from an environment variable with the same name). In the meantime,
|
|
// produce an error to prevent users from depending on this.
|
|
if !hasValue {
|
|
return nil, invalidParameter(fmt.Errorf(
|
|
`failed to set custom headers from %s environment variable: missing "=" in key=value pair: '%s'`,
|
|
envOverrideHTTPHeaders, kv,
|
|
))
|
|
}
|
|
|
|
env[http.CanonicalHeaderKey(k)] = v
|
|
}
|
|
|
|
if len(env) == 0 {
|
|
// We should probably not hit this case, as we don't skip values
|
|
// (only return errors), but we don't want to discard existing
|
|
// headers with an empty set.
|
|
return nil, nil
|
|
}
|
|
|
|
// TODO(thaJeztah): add a client.WithExtraHTTPHeaders() function to allow these headers to be _added_ to existing ones, instead of _replacing_
|
|
// see https://github.com/docker/cli/pull/5098#issuecomment-2147403871 (when updating, also update the WARNING in the function and env-var GoDoc)
|
|
return client.WithHTTPHeaders(env), nil
|
|
}
|
|
|
|
// WithUserAgent configures the User-Agent string for cli HTTP requests.
|
|
func WithUserAgent(userAgent string) CLIOption {
|
|
return func(cli *DockerCli) error {
|
|
if userAgent == "" {
|
|
return errors.New("user agent cannot be blank")
|
|
}
|
|
cli.userAgent = userAgent
|
|
return nil
|
|
}
|
|
}
|