mirror of
https://github.com/docker/cli.git
synced 2026-08-25 02:24:25 -05:00
Compare commits
@@ -35,7 +35,7 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
-
|
||||
name: Create matrix
|
||||
id: platforms
|
||||
@@ -88,7 +88,7 @@ jobs:
|
||||
fi
|
||||
-
|
||||
name: Upload artifacts
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: ${{ env.ARTIFACT_NAME }}
|
||||
path: /tmp/out/*
|
||||
@@ -143,7 +143,7 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
-
|
||||
name: Create matrix
|
||||
id: platforms
|
||||
|
||||
@@ -46,7 +46,7 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 2
|
||||
# CodeQL 2.16.4's auto-build added support for multi-module repositories,
|
||||
@@ -63,7 +63,7 @@ jobs:
|
||||
name: Update Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: "1.25.4"
|
||||
go-version: "1.25.6"
|
||||
-
|
||||
name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v4
|
||||
|
||||
@@ -37,14 +37,14 @@ jobs:
|
||||
- alpine
|
||||
- debian
|
||||
engine-version:
|
||||
- 29-rc # latest rc
|
||||
- 28 # latest
|
||||
- 27 # latest - 1
|
||||
- rc # latest rc
|
||||
- 29 # latest
|
||||
- 28 # latest - 1
|
||||
- 25 # mirantis lts
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
-
|
||||
name: Update daemon.json
|
||||
run: |
|
||||
|
||||
@@ -60,18 +60,21 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
path: ${{ env.GOPATH }}/src/github.com/docker/cli
|
||||
-
|
||||
name: Set up Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: "1.25.4"
|
||||
go-version: "1.25.6"
|
||||
-
|
||||
name: Test
|
||||
run: |
|
||||
go test -coverprofile=/tmp/coverage.txt $(go list ./... | grep -vE '/vendor/|/e2e/|/cmd/docker-trust')
|
||||
# run in go modules mode to prevent traversing to nested modules
|
||||
ln -s vendor.mod go.mod
|
||||
ln -s vendor.sum go.sum
|
||||
go test -coverprofile=/tmp/coverage.txt $(go list ./... | grep -vE '^github.com/docker/cli/e2e/')
|
||||
go tool cover -func=/tmp/coverage.txt
|
||||
working-directory: ${{ env.GOPATH }}/src/github.com/docker/cli
|
||||
shell: bash
|
||||
|
||||
@@ -48,7 +48,7 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
-
|
||||
name: Generate
|
||||
shell: 'script --return --quiet --command "bash {0}"'
|
||||
@@ -74,7 +74,7 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
-
|
||||
name: Run
|
||||
shell: 'script --return --quiet --command "bash {0}"'
|
||||
|
||||
+3
-1
@@ -5,7 +5,7 @@ run:
|
||||
# which causes it to fallback to go1.17 semantics.
|
||||
#
|
||||
# TODO(thaJeztah): update "usetesting" settings to enable go1.24 features once our minimum version is go1.24
|
||||
go: "1.25.4"
|
||||
go: "1.25.6"
|
||||
|
||||
timeout: 5m
|
||||
|
||||
@@ -94,6 +94,8 @@ linters:
|
||||
desc: Use github.com/google/uuid instead.
|
||||
- pkg: "io/ioutil"
|
||||
desc: The io/ioutil package has been deprecated, see https://go.dev/doc/go1.16#ioutil
|
||||
- pkg: "gopkg.in/yaml.v3"
|
||||
desc: Use go.yaml.in/yaml/v3 instead.
|
||||
|
||||
forbidigo:
|
||||
forbid:
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@ ARG BASE_VARIANT=alpine
|
||||
ARG ALPINE_VERSION=3.22
|
||||
ARG BASE_DEBIAN_DISTRO=bookworm
|
||||
|
||||
ARG GO_VERSION=1.25.4
|
||||
ARG GO_VERSION=1.25.6
|
||||
|
||||
# XX_VERSION specifies the version of the xx utility to use.
|
||||
# It must be a valid tag in the docker.io/tonistiigi/xx image repository.
|
||||
|
||||
@@ -16,6 +16,5 @@ import (
|
||||
//
|
||||
// [ConfigFile.CLIPluginsExtraDirs]: https://pkg.go.dev/github.com/docker/cli@v26.1.4+incompatible/cli/config/configfile#ConfigFile.CLIPluginsExtraDirs
|
||||
var defaultSystemPluginDirs = []string{
|
||||
filepath.Join(os.Getenv("ProgramData"), "Docker", "cli-plugins"),
|
||||
filepath.Join(os.Getenv("ProgramFiles"), "Docker", "cli-plugins"),
|
||||
}
|
||||
|
||||
+2
-3
@@ -60,6 +60,7 @@ type Cli interface {
|
||||
type DockerCli struct {
|
||||
configFile *configfile.ConfigFile
|
||||
options *cliflags.ClientOptions
|
||||
clientOpts []client.Opt
|
||||
in *streams.In
|
||||
out *streams.Out
|
||||
err *streams.Out
|
||||
@@ -72,7 +73,6 @@ type DockerCli struct {
|
||||
dockerEndpoint docker.Endpoint
|
||||
contextStoreConfig *store.Config
|
||||
initTimeout time.Duration
|
||||
userAgent string
|
||||
res telemetryResource
|
||||
|
||||
// baseCtx is the base context used for internal operations. In the future
|
||||
@@ -533,8 +533,7 @@ func (cli *DockerCli) initialize() error {
|
||||
return
|
||||
}
|
||||
if cli.client == nil {
|
||||
ops := []client.Opt{client.WithUserAgent(cli.userAgent)}
|
||||
if cli.client, cli.initErr = newAPIClientFromEndpoint(cli.dockerEndpoint, cli.configFile, ops...); cli.initErr != nil {
|
||||
if cli.client, cli.initErr = newAPIClientFromEndpoint(cli.dockerEndpoint, cli.configFile, cli.clientOpts...); cli.initErr != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,6 +104,16 @@ func WithInitializeClient(makeClient func(*DockerCli) (client.APIClient, error))
|
||||
}
|
||||
}
|
||||
|
||||
// WithAPIClientOptions configures additional [client.Opt] to use when
|
||||
// initializing the API client. These options have no effect if a custom
|
||||
// client is set (through [WithAPIClient] or [WithInitializeClient]).
|
||||
func WithAPIClientOptions(c ...client.Opt) CLIOption {
|
||||
return func(cli *DockerCli) error {
|
||||
cli.clientOpts = append(cli.clientOpts, c...)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
@@ -221,7 +231,7 @@ func WithUserAgent(userAgent string) CLIOption {
|
||||
if userAgent == "" {
|
||||
return errors.New("user agent cannot be blank")
|
||||
}
|
||||
cli.userAgent = userAgent
|
||||
cli.clientOpts = append(cli.clientOpts, client.WithUserAgent(userAgent))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
+26
-2
@@ -120,8 +120,8 @@ func TestNewAPIClientFromFlagsWithCustomHeadersFromEnv(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestNewAPIClientFromFlagsWithAPIVersionFromEnv(t *testing.T) {
|
||||
const customVersion = "v3.3.3"
|
||||
const expectedVersion = "3.3.3"
|
||||
const customVersion = "v3.3"
|
||||
const expectedVersion = "3.3"
|
||||
t.Setenv("DOCKER_API_VERSION", customVersion)
|
||||
t.Setenv("DOCKER_HOST", ":2375")
|
||||
|
||||
@@ -358,6 +358,7 @@ func TestSetGoDebug(t *testing.T) {
|
||||
assert.Equal(t, "val1,val2", os.Getenv("GODEBUG"))
|
||||
})
|
||||
t.Run("GODEBUG in context metadata can set env", func(t *testing.T) {
|
||||
t.Setenv("GODEBUG", "")
|
||||
meta := store.Metadata{
|
||||
Metadata: DockerContext{
|
||||
AdditionalFields: map[string]any{
|
||||
@@ -392,3 +393,26 @@ func TestNewDockerCliWithCustomUserAgent(t *testing.T) {
|
||||
assert.NilError(t, err)
|
||||
assert.DeepEqual(t, received, "fake-agent/0.0.1")
|
||||
}
|
||||
|
||||
func TestNewDockerCliWithAPIClientOptions(t *testing.T) {
|
||||
var received string
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
received = r.UserAgent()
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
defer ts.Close()
|
||||
host := strings.Replace(ts.URL, "http://", "tcp://", 1)
|
||||
opts := &flags.ClientOptions{Hosts: []string{host}}
|
||||
|
||||
cli, err := NewDockerCli(
|
||||
WithAPIClientOptions(client.WithUserAgent("fake-agent/0.0.1")),
|
||||
)
|
||||
assert.NilError(t, err)
|
||||
cli.currentContext = DefaultContextName
|
||||
cli.options = opts
|
||||
cli.configFile = &configfile.ConfigFile{}
|
||||
|
||||
_, err = cli.Client().Ping(t.Context(), client.PingOptions{})
|
||||
assert.NilError(t, err)
|
||||
assert.DeepEqual(t, received, "fake-agent/0.0.1")
|
||||
}
|
||||
|
||||
@@ -70,6 +70,14 @@ func newAttachCommand(dockerCLI command.Cli) *cobra.Command {
|
||||
|
||||
// RunAttach executes an `attach` command
|
||||
func RunAttach(ctx context.Context, dockerCLI command.Cli, containerID string, opts *AttachOptions) error {
|
||||
detachKeys := opts.DetachKeys
|
||||
if detachKeys == "" {
|
||||
detachKeys = dockerCLI.ConfigFile().DetachKeys
|
||||
}
|
||||
if err := validateDetachKeys(detachKeys); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
apiClient := dockerCLI.Client()
|
||||
|
||||
// request channel to wait for client
|
||||
@@ -85,11 +93,6 @@ func RunAttach(ctx context.Context, dockerCLI command.Cli, containerID string, o
|
||||
return err
|
||||
}
|
||||
|
||||
detachKeys := dockerCLI.ConfigFile().DetachKeys
|
||||
if opts.DetachKeys != "" {
|
||||
detachKeys = opts.DetachKeys
|
||||
}
|
||||
|
||||
options := client.ContainerAttachOptions{
|
||||
Stream: true,
|
||||
Stdin: !opts.NoStdin && c.Config.OpenStdin,
|
||||
|
||||
@@ -27,6 +27,14 @@ func TestNewAttachCommandErrors(t *testing.T) {
|
||||
return client.ContainerInspectResult{}, errors.New("something went wrong")
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid-detach-keys",
|
||||
args: []string{"--detach-keys", "shift-b", "5cb5bb5e4a3b"},
|
||||
expectedError: "invalid detach keys (shift-b):",
|
||||
containerInspectFunc: func(containerID string) (client.ContainerInspectResult, error) {
|
||||
return client.ContainerInspectResult{}, errors.New("something went wrong")
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "client-stopped",
|
||||
args: []string{"5cb5bb5e4a3b"},
|
||||
|
||||
@@ -232,3 +232,7 @@ func (f *fakeClient) ContainerPause(ctx context.Context, containerID string, opt
|
||||
|
||||
return client.ContainerPauseResult{}, nil
|
||||
}
|
||||
|
||||
func (*fakeClient) Ping(_ context.Context, _ client.PingOptions) (client.PingResult, error) {
|
||||
return client.PingResult{}, nil
|
||||
}
|
||||
|
||||
@@ -95,14 +95,14 @@ func newCreateCommand(dockerCLI command.Cli) *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runCreate(ctx context.Context, dockerCli command.Cli, flags *pflag.FlagSet, options *createOptions, copts *containerOptions) error {
|
||||
func runCreate(ctx context.Context, dockerCLI command.Cli, flags *pflag.FlagSet, options *createOptions, copts *containerOptions) error {
|
||||
if err := validatePullOpt(options.pull); err != nil {
|
||||
return cli.StatusError{
|
||||
Status: withHelp(err, "create").Error(),
|
||||
StatusCode: 125,
|
||||
}
|
||||
}
|
||||
proxyConfig := dockerCli.ConfigFile().ParseProxyConfig(dockerCli.Client().DaemonHost(), opts.ConvertKVStringsToMapWithNil(copts.env.GetSlice()))
|
||||
proxyConfig := dockerCLI.ConfigFile().ParseProxyConfig(dockerCLI.Client().DaemonHost(), opts.ConvertKVStringsToMapWithNil(copts.env.GetSlice()))
|
||||
newEnv := make([]string, 0, len(proxyConfig))
|
||||
for k, v := range proxyConfig {
|
||||
if v == nil {
|
||||
@@ -112,24 +112,28 @@ func runCreate(ctx context.Context, dockerCli command.Cli, flags *pflag.FlagSet,
|
||||
}
|
||||
}
|
||||
copts.env = *opts.NewListOptsRef(&newEnv, nil)
|
||||
containerCfg, err := parse(flags, copts, dockerCli.ServerInfo().OSType)
|
||||
serverInfo, err := dockerCLI.Client().Ping(ctx, client.PingOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
containerCfg, err := parse(flags, copts, serverInfo.OSType)
|
||||
if err != nil {
|
||||
return cli.StatusError{
|
||||
Status: withHelp(err, "create").Error(),
|
||||
StatusCode: 125,
|
||||
}
|
||||
}
|
||||
id, err := createContainer(ctx, dockerCli, containerCfg, options)
|
||||
id, err := createContainer(ctx, dockerCLI, containerCfg, options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, _ = fmt.Fprintln(dockerCli.Out(), id)
|
||||
_, _ = fmt.Fprintln(dockerCLI.Out(), id)
|
||||
return nil
|
||||
}
|
||||
|
||||
// FIXME(thaJeztah): this is the only code-path that uses APIClient.ImageCreate. Rewrite this to use the regular "pull" code (or vice-versa).
|
||||
func pullImage(ctx context.Context, dockerCli command.Cli, img string, options *createOptions) error {
|
||||
encodedAuth, err := command.RetrieveAuthTokenFromImage(dockerCli.ConfigFile(), img)
|
||||
func pullImage(ctx context.Context, dockerCLI command.Cli, img string, options *createOptions) error {
|
||||
encodedAuth, err := command.RetrieveAuthTokenFromImage(dockerCLI.ConfigFile(), img)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -139,7 +143,7 @@ func pullImage(ctx context.Context, dockerCli command.Cli, img string, options *
|
||||
// Already validated.
|
||||
ociPlatforms = append(ociPlatforms, platforms.MustParse(options.platform))
|
||||
}
|
||||
resp, err := dockerCli.Client().ImagePull(ctx, img, client.ImagePullOptions{
|
||||
resp, err := dockerCLI.Client().ImagePull(ctx, img, client.ImagePullOptions{
|
||||
RegistryAuth: encodedAuth,
|
||||
Platforms: ociPlatforms,
|
||||
})
|
||||
@@ -150,7 +154,7 @@ func pullImage(ctx context.Context, dockerCli command.Cli, img string, options *
|
||||
_ = resp.Close()
|
||||
}()
|
||||
|
||||
out := dockerCli.Err()
|
||||
out := dockerCLI.Err()
|
||||
if options.quiet {
|
||||
out = streams.NewOut(io.Discard)
|
||||
}
|
||||
@@ -172,8 +176,8 @@ func (cid *cidFile) Close() error {
|
||||
if cid.written {
|
||||
return nil
|
||||
}
|
||||
if err := os.Remove(cid.path); err != nil {
|
||||
return fmt.Errorf("failed to remove the CID file '%s': %w", cid.path, err)
|
||||
if err := os.Remove(cid.path); err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
return fmt.Errorf("failed to remove the CID file: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -183,8 +187,8 @@ func (cid *cidFile) Write(id string) error {
|
||||
if cid.file == nil {
|
||||
return nil
|
||||
}
|
||||
if _, err := cid.file.Write([]byte(id)); err != nil {
|
||||
return fmt.Errorf("failed to write the container ID to the file: %w", err)
|
||||
if _, err := cid.file.WriteString(id); err != nil {
|
||||
return fmt.Errorf("failed to write the container ID (%s) to file: %w", id, err)
|
||||
}
|
||||
cid.written = true
|
||||
return nil
|
||||
@@ -207,7 +211,7 @@ func newCIDFile(cidPath string) (*cidFile, error) {
|
||||
}
|
||||
|
||||
//nolint:gocyclo
|
||||
func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *containerConfig, options *createOptions) (containerID string, err error) {
|
||||
func createContainer(ctx context.Context, dockerCLI command.Cli, containerCfg *containerConfig, options *createOptions) (containerID string, _ error) {
|
||||
config := containerCfg.Config
|
||||
hostConfig := containerCfg.HostConfig
|
||||
networkingConfig := containerCfg.NetworkingConfig
|
||||
@@ -216,8 +220,7 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *c
|
||||
|
||||
// TODO(thaJeztah): add a platform option-type / flag-type.
|
||||
if options.platform != "" {
|
||||
_, err = platforms.Parse(options.platform)
|
||||
if err != nil {
|
||||
if _, err := platforms.Parse(options.platform); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
@@ -243,10 +246,11 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *c
|
||||
|
||||
if options.useAPISocket {
|
||||
// We'll create two new mounts to handle this flag:
|
||||
//
|
||||
// 1. Mount the actual docker socket.
|
||||
// 2. A synthezised ~/.docker/config.json with resolved tokens.
|
||||
// 2. A synthesized ~/.docker/config.json with resolved tokens.
|
||||
|
||||
if dockerCli.ServerInfo().OSType == "windows" {
|
||||
if dockerCLI.ServerInfo().OSType == "windows" {
|
||||
return "", errors.New("flag --use-api-socket can't be used with a Windows Docker Engine")
|
||||
}
|
||||
|
||||
@@ -281,18 +285,18 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *c
|
||||
})
|
||||
*/
|
||||
|
||||
var envvarPresent bool
|
||||
for _, envvar := range containerCfg.Config.Env {
|
||||
if strings.HasPrefix(envvar, "DOCKER_CONFIG=") {
|
||||
envvarPresent = true
|
||||
var envVarPresent bool
|
||||
for _, envVar := range containerCfg.Config.Env {
|
||||
if strings.HasPrefix(envVar, "DOCKER_CONFIG=") {
|
||||
envVarPresent = true
|
||||
}
|
||||
}
|
||||
|
||||
// If the DOCKER_CONFIG env var is already present, we assume the client knows
|
||||
// what they're doing and don't inject the creds.
|
||||
if !envvarPresent {
|
||||
if !envVarPresent {
|
||||
// Resolve this here for later, ensuring we error our before we create the container.
|
||||
creds, err := readCredentials(dockerCli)
|
||||
creds, err := readCredentials(dockerCLI)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("resolving credentials failed: %w", err)
|
||||
}
|
||||
@@ -314,22 +318,15 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *c
|
||||
platform = &p
|
||||
}
|
||||
|
||||
pullAndTagImage := func() error {
|
||||
if err := pullImage(ctx, dockerCli, config.Image, options); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if options.pull == PullImageAlways {
|
||||
if err := pullAndTagImage(); err != nil {
|
||||
if err := pullImage(ctx, dockerCLI, config.Image, options); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
hostConfig.ConsoleSize[0], hostConfig.ConsoleSize[1] = dockerCli.Out().GetTtySize()
|
||||
hostConfig.ConsoleSize[0], hostConfig.ConsoleSize[1] = dockerCLI.Out().GetTtySize()
|
||||
|
||||
response, err := dockerCli.Client().ContainerCreate(ctx, client.ContainerCreateOptions{
|
||||
response, err := dockerCLI.Client().ContainerCreate(ctx, client.ContainerCreateOptions{
|
||||
Name: options.name,
|
||||
// Image: config.Image, // TODO(thaJeztah): pass image-ref separate
|
||||
Platform: platform,
|
||||
@@ -342,15 +339,15 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *c
|
||||
if errdefs.IsNotFound(err) && namedRef != nil && options.pull == PullImageMissing {
|
||||
if !options.quiet {
|
||||
// we don't want to write to stdout anything apart from container.ID
|
||||
_, _ = fmt.Fprintf(dockerCli.Err(), "Unable to find image '%s' locally\n", reference.FamiliarString(namedRef))
|
||||
_, _ = fmt.Fprintf(dockerCLI.Err(), "Unable to find image '%s' locally\n", reference.FamiliarString(namedRef))
|
||||
}
|
||||
|
||||
if err := pullAndTagImage(); err != nil {
|
||||
if err := pullImage(ctx, dockerCLI, config.Image, options); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var retryErr error
|
||||
response, retryErr = dockerCli.Client().ContainerCreate(ctx, client.ContainerCreateOptions{
|
||||
response, retryErr = dockerCLI.Client().ContainerCreate(ctx, client.ContainerCreateOptions{
|
||||
Name: options.name,
|
||||
// Image: config.Image, // TODO(thaJeztah): pass image-ref separate
|
||||
Platform: platform,
|
||||
@@ -366,24 +363,20 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *c
|
||||
}
|
||||
}
|
||||
|
||||
containerID = response.ID
|
||||
for _, w := range response.Warnings {
|
||||
_, _ = fmt.Fprintln(dockerCli.Err(), "WARNING:", w)
|
||||
}
|
||||
err = containerIDFile.Write(containerID)
|
||||
|
||||
if options.useAPISocket && len(apiSocketCreds) > 0 {
|
||||
// Create a new config file with just the auth.
|
||||
newConfig := &configfile.ConfigFile{
|
||||
if err := copyDockerConfigIntoContainer(ctx, dockerCLI.Client(), response.ID, dockerConfigPathInContainer, &configfile.ConfigFile{
|
||||
AuthConfigs: apiSocketCreds,
|
||||
}
|
||||
|
||||
if err := copyDockerConfigIntoContainer(ctx, dockerCli.Client(), containerID, dockerConfigPathInContainer, newConfig); err != nil {
|
||||
return "", fmt.Errorf("injecting docker config.json into container failed: %w", err)
|
||||
}); err != nil {
|
||||
response.Warnings = append(response.Warnings, fmt.Sprintf("injecting docker config.json into container failed: %v", err))
|
||||
}
|
||||
}
|
||||
for _, w := range response.Warnings {
|
||||
_, _ = fmt.Fprintln(dockerCLI.Err(), "WARNING:", w)
|
||||
}
|
||||
|
||||
return containerID, err
|
||||
err = containerIDFile.Write(response.ID)
|
||||
return response.ID, err
|
||||
}
|
||||
|
||||
func validatePullOpt(val string) error {
|
||||
@@ -423,6 +416,7 @@ func copyDockerConfigIntoContainer(ctx context.Context, apiClient client.APIClie
|
||||
})
|
||||
|
||||
if _, err := io.Copy(tarWriter, &configBuf); err != nil {
|
||||
_ = tarWriter.Close()
|
||||
return fmt.Errorf("writing config to tar file for config copy: %w", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
@@ -42,17 +43,31 @@ func TestNewCIDFileWhenFileAlreadyExists(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCIDFileCloseWithNoWrite(t *testing.T) {
|
||||
tempdir := fs.NewDir(t, "test-cid-file")
|
||||
defer tempdir.Remove()
|
||||
// Closing should remove the file if it was not written to.
|
||||
t.Run("closing should remove file", func(t *testing.T) {
|
||||
filename := filepath.Join(t.TempDir(), "cidfile-1")
|
||||
file, err := newCIDFile(filename)
|
||||
assert.NilError(t, err)
|
||||
assert.Check(t, is.Equal(file.path, filename))
|
||||
|
||||
path := tempdir.Join("cidfile")
|
||||
file, err := newCIDFile(path)
|
||||
assert.NilError(t, err)
|
||||
assert.Check(t, is.Equal(file.path, path))
|
||||
assert.NilError(t, file.Close())
|
||||
_, err = os.Stat(filename)
|
||||
assert.Check(t, os.IsNotExist(err))
|
||||
})
|
||||
|
||||
assert.NilError(t, file.Close())
|
||||
_, err = os.Stat(path)
|
||||
assert.Check(t, os.IsNotExist(err))
|
||||
// Closing (and removing) the file should not produce an error if the file no longer exists.
|
||||
t.Run("close should remove file", func(t *testing.T) {
|
||||
filename := filepath.Join(t.TempDir(), "cidfile-2")
|
||||
file, err := newCIDFile(filename)
|
||||
assert.NilError(t, err)
|
||||
assert.Check(t, is.Equal(file.path, filename))
|
||||
|
||||
assert.NilError(t, os.Remove(filename))
|
||||
_, err = os.Stat(filename)
|
||||
assert.Check(t, os.IsNotExist(err))
|
||||
|
||||
assert.NilError(t, file.Close())
|
||||
})
|
||||
}
|
||||
|
||||
func TestCIDFileCloseWithWrite(t *testing.T) {
|
||||
|
||||
@@ -248,5 +248,8 @@ func parseExec(execOpts ExecOptions, configFile *configfile.ConfigFile) (*client
|
||||
} else {
|
||||
execOptions.DetachKeys = configFile.DetachKeys
|
||||
}
|
||||
if err := validateDetachKeys(execOpts.DetachKeys); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return execOptions, nil
|
||||
}
|
||||
|
||||
@@ -92,21 +92,21 @@ TWO=2
|
||||
},
|
||||
{
|
||||
options: withDefaultOpts(ExecOptions{Detach: true}),
|
||||
configFile: configfile.ConfigFile{DetachKeys: "de"},
|
||||
configFile: configfile.ConfigFile{DetachKeys: "ctrl-d,e"},
|
||||
expected: client.ExecCreateOptions{
|
||||
Cmd: []string{"command"},
|
||||
DetachKeys: "de",
|
||||
DetachKeys: "ctrl-d,e",
|
||||
},
|
||||
},
|
||||
{
|
||||
options: withDefaultOpts(ExecOptions{
|
||||
Detach: true,
|
||||
DetachKeys: "ab",
|
||||
DetachKeys: "ctrl-a,b",
|
||||
}),
|
||||
configFile: configfile.ConfigFile{DetachKeys: "de"},
|
||||
configFile: configfile.ConfigFile{DetachKeys: "ctrl-d,e"},
|
||||
expected: client.ExecCreateOptions{
|
||||
Cmd: []string{"command"},
|
||||
DetachKeys: "ab",
|
||||
DetachKeys: "ctrl-a,b",
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -147,13 +147,23 @@ TWO=2
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseExecNoSuchFile(t *testing.T) {
|
||||
execOpts := withDefaultOpts(ExecOptions{})
|
||||
assert.Check(t, execOpts.EnvFile.Set("no-such-env-file"))
|
||||
execConfig, err := parseExec(execOpts, &configfile.ConfigFile{})
|
||||
assert.ErrorContains(t, err, "no-such-env-file")
|
||||
assert.Check(t, os.IsNotExist(err))
|
||||
assert.Check(t, execConfig == nil)
|
||||
func TestParseExecErrors(t *testing.T) {
|
||||
t.Run("missing env-file", func(t *testing.T) {
|
||||
execOpts := withDefaultOpts(ExecOptions{})
|
||||
assert.Check(t, execOpts.EnvFile.Set("no-such-env-file"))
|
||||
execConfig, err := parseExec(execOpts, &configfile.ConfigFile{})
|
||||
assert.ErrorContains(t, err, "no-such-env-file")
|
||||
assert.Check(t, os.IsNotExist(err))
|
||||
assert.Check(t, execConfig == nil)
|
||||
})
|
||||
t.Run("invalid detach keys", func(t *testing.T) {
|
||||
execOpts := withDefaultOpts(ExecOptions{
|
||||
DetachKeys: "shift-a",
|
||||
})
|
||||
execConfig, err := parseExec(execOpts, &configfile.ConfigFile{})
|
||||
assert.Check(t, is.ErrorContains(err, "invalid detach keys (shift-a):"))
|
||||
assert.Check(t, is.Nil(execConfig))
|
||||
})
|
||||
}
|
||||
|
||||
func TestRunExec(t *testing.T) {
|
||||
|
||||
@@ -30,6 +30,16 @@ func (r *readCloserWrapper) Close() error {
|
||||
return r.closer()
|
||||
}
|
||||
|
||||
func validateDetachKeys(keys string) error {
|
||||
if keys == "" {
|
||||
return nil
|
||||
}
|
||||
if _, err := term.ToBytes(keys); err != nil {
|
||||
return invalidParameter(fmt.Errorf("invalid detach keys (%s): %w", keys, err))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// A hijackedIOStreamer handles copying input to and output from streams to the
|
||||
// connection.
|
||||
type hijackedIOStreamer struct {
|
||||
@@ -82,13 +92,15 @@ func (h *hijackedIOStreamer) stream(ctx context.Context) error {
|
||||
}
|
||||
}
|
||||
|
||||
func (h *hijackedIOStreamer) setupInput() (restore func(), err error) {
|
||||
func (h *hijackedIOStreamer) setupInput() (restore func(), _ error) {
|
||||
if h.inputStream == nil || !h.tty {
|
||||
// No need to setup input TTY.
|
||||
// The restore func is a nop.
|
||||
return func() {}, nil
|
||||
}
|
||||
|
||||
if err := validateDetachKeys(h.detachKeys); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := setRawTerminal(h.streams); err != nil {
|
||||
return nil, fmt.Errorf("unable to set IO streams as raw terminal: %s", err)
|
||||
}
|
||||
@@ -103,11 +115,11 @@ func (h *hijackedIOStreamer) setupInput() (restore func(), err error) {
|
||||
// Use default escape keys if an invalid sequence is given.
|
||||
escapeKeys := defaultEscapeKeys
|
||||
if h.detachKeys != "" {
|
||||
customEscapeKeys, err := term.ToBytes(h.detachKeys)
|
||||
var err error
|
||||
escapeKeys, err = term.ToBytes(h.detachKeys)
|
||||
if err != nil {
|
||||
logrus.Warnf("invalid detach escape keys, using default: %s", err)
|
||||
} else {
|
||||
escapeKeys = customEscapeKeys
|
||||
restore()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -426,6 +426,9 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// short syntax ([ip:]public:private[/proto])
|
||||
//
|
||||
// TODO(thaJeztah): we need an equivalent that handles the "ip-address" part without depending on the nat package.
|
||||
ports, natPortBindings, err := nat.ParsePortSpecs(convertedOpts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -792,7 +795,7 @@ func parseNetworkOpts(copts *containerOptions) (map[string]*network.EndpointSett
|
||||
// and only a single network is specified, omit the endpoint-configuration
|
||||
// on the client (the daemon will still create it when creating the container)
|
||||
if i == 0 && len(copts.netMode.Value()) == 1 {
|
||||
if ep == nil || reflect.DeepEqual(*ep, network.EndpointSettings{}) {
|
||||
if ep == nil || reflect.ValueOf(*ep).IsZero() {
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -944,11 +947,11 @@ func parseSecurityOpts(securityOpts []string) ([]string, error) {
|
||||
if err != nil {
|
||||
return securityOpts, fmt.Errorf("opening seccomp profile (%s) failed: %w", v, err)
|
||||
}
|
||||
b := bytes.NewBuffer(nil)
|
||||
if err := json.Compact(b, f); err != nil {
|
||||
var b bytes.Buffer
|
||||
if err := json.Compact(&b, f); err != nil {
|
||||
return securityOpts, fmt.Errorf("compacting json for seccomp profile (%s) failed: %w", v, err)
|
||||
}
|
||||
securityOpts[key] = fmt.Sprintf("seccomp=%s", b.Bytes())
|
||||
securityOpts[key] = "seccomp=" + b.String()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -994,9 +997,11 @@ func parseDevice(device, serverOS string) (container.DeviceMapping, error) {
|
||||
case "linux":
|
||||
return parseLinuxDevice(device)
|
||||
case "windows":
|
||||
return parseWindowsDevice(device)
|
||||
// Windows doesn't support mapping, so passing the given value as-is.
|
||||
return container.DeviceMapping{PathOnHost: device}, nil
|
||||
default:
|
||||
return container.DeviceMapping{}, fmt.Errorf("unknown server OS: %s", serverOS)
|
||||
}
|
||||
return container.DeviceMapping{}, fmt.Errorf("unknown server OS: %s", serverOS)
|
||||
}
|
||||
|
||||
// parseLinuxDevice parses a device mapping string to a container.DeviceMapping struct
|
||||
@@ -1027,18 +1032,11 @@ func parseLinuxDevice(device string) (container.DeviceMapping, error) {
|
||||
dst = src
|
||||
}
|
||||
|
||||
deviceMapping := container.DeviceMapping{
|
||||
return container.DeviceMapping{
|
||||
PathOnHost: src,
|
||||
PathInContainer: dst,
|
||||
CgroupPermissions: permissions,
|
||||
}
|
||||
return deviceMapping, nil
|
||||
}
|
||||
|
||||
// parseWindowsDevice parses a device mapping string to a container.DeviceMapping struct
|
||||
// knowing that the target is a Windows daemon
|
||||
func parseWindowsDevice(device string) (container.DeviceMapping, error) {
|
||||
return container.DeviceMapping{PathOnHost: device}, nil
|
||||
}, nil
|
||||
}
|
||||
|
||||
// validateDeviceCgroupRule validates a device cgroup rule string format
|
||||
|
||||
@@ -84,14 +84,14 @@ func newRunCommand(dockerCLI command.Cli) *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runRun(ctx context.Context, dockerCli command.Cli, flags *pflag.FlagSet, ropts *runOptions, copts *containerOptions) error {
|
||||
func runRun(ctx context.Context, dockerCLI command.Cli, flags *pflag.FlagSet, ropts *runOptions, copts *containerOptions) error {
|
||||
if err := validatePullOpt(ropts.pull); err != nil {
|
||||
return cli.StatusError{
|
||||
Status: withHelp(err, "run").Error(),
|
||||
StatusCode: 125,
|
||||
}
|
||||
}
|
||||
proxyConfig := dockerCli.ConfigFile().ParseProxyConfig(dockerCli.Client().DaemonHost(), opts.ConvertKVStringsToMapWithNil(copts.env.GetSlice()))
|
||||
proxyConfig := dockerCLI.ConfigFile().ParseProxyConfig(dockerCLI.Client().DaemonHost(), opts.ConvertKVStringsToMapWithNil(copts.env.GetSlice()))
|
||||
newEnv := []string{}
|
||||
for k, v := range proxyConfig {
|
||||
if v == nil {
|
||||
@@ -101,7 +101,12 @@ func runRun(ctx context.Context, dockerCli command.Cli, flags *pflag.FlagSet, ro
|
||||
}
|
||||
}
|
||||
copts.env = *opts.NewListOptsRef(&newEnv, nil)
|
||||
containerCfg, err := parse(flags, copts, dockerCli.ServerInfo().OSType)
|
||||
serverInfo, err := dockerCLI.Client().Ping(ctx, client.PingOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
containerCfg, err := parse(flags, copts, serverInfo.OSType)
|
||||
// just in case the parse does not exit
|
||||
if err != nil {
|
||||
return cli.StatusError{
|
||||
@@ -109,7 +114,7 @@ func runRun(ctx context.Context, dockerCli command.Cli, flags *pflag.FlagSet, ro
|
||||
StatusCode: 125,
|
||||
}
|
||||
}
|
||||
return runContainer(ctx, dockerCli, ropts, copts, containerCfg)
|
||||
return runContainer(ctx, dockerCLI, ropts, copts, containerCfg)
|
||||
}
|
||||
|
||||
//nolint:gocyclo
|
||||
@@ -135,6 +140,14 @@ func runContainer(ctx context.Context, dockerCli command.Cli, runOpts *runOption
|
||||
config.StdinOnce = false
|
||||
}
|
||||
|
||||
detachKeys := runOpts.detachKeys
|
||||
if detachKeys == "" {
|
||||
detachKeys = dockerCli.ConfigFile().DetachKeys
|
||||
}
|
||||
if err := validateDetachKeys(runOpts.detachKeys); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
containerID, err := createContainer(ctx, dockerCli, containerCfg, &runOpts.createOptions)
|
||||
if err != nil {
|
||||
return toStatusError(err)
|
||||
@@ -167,11 +180,6 @@ func runContainer(ctx context.Context, dockerCli command.Cli, runOpts *runOption
|
||||
}()
|
||||
}
|
||||
if attach {
|
||||
detachKeys := dockerCli.ConfigFile().DetachKeys
|
||||
if runOpts.detachKeys != "" {
|
||||
detachKeys = runOpts.detachKeys
|
||||
}
|
||||
|
||||
// ctx should not be cancellable here, as this would kill the stream to the container
|
||||
// and we want to keep the stream open until the process in the container exits or until
|
||||
// the user forcefully terminates the CLI.
|
||||
|
||||
@@ -34,6 +34,11 @@ func TestRunValidateFlags(t *testing.T) {
|
||||
args: []string{"--attach", "stdin", "--detach", "myimage"},
|
||||
expectedErr: "conflicting options: cannot specify both --attach and --detach",
|
||||
},
|
||||
{
|
||||
name: "with invalid --detach-keys",
|
||||
args: []string{"--detach-keys", "shift-a", "myimage"},
|
||||
expectedErr: "invalid detach keys (shift-a):",
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cmd := newRunCommand(test.NewFakeCli(&fakeClient{}))
|
||||
|
||||
@@ -70,6 +70,14 @@ func RunStart(ctx context.Context, dockerCli command.Cli, opts *StartOptions) er
|
||||
ctx, cancelFun := context.WithCancel(ctx)
|
||||
defer cancelFun()
|
||||
|
||||
detachKeys := opts.DetachKeys
|
||||
if detachKeys == "" {
|
||||
detachKeys = dockerCli.ConfigFile().DetachKeys
|
||||
}
|
||||
if err := validateDetachKeys(detachKeys); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch {
|
||||
case opts.Attach || opts.OpenStdin:
|
||||
// We're going to attach to a container.
|
||||
@@ -93,11 +101,6 @@ func RunStart(ctx context.Context, dockerCli command.Cli, opts *StartOptions) er
|
||||
defer signal.StopCatch(sigc)
|
||||
}
|
||||
|
||||
detachKeys := dockerCli.ConfigFile().DetachKeys
|
||||
if opts.DetachKeys != "" {
|
||||
detachKeys = opts.DetachKeys
|
||||
}
|
||||
|
||||
options := client.ContainerAttachOptions{
|
||||
Stream: true,
|
||||
Stdin: opts.OpenStdin && c.Container.Config.OpenStdin,
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/internal/test"
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
)
|
||||
|
||||
func TestStartValidateFlags(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
args []string
|
||||
expectedErr string
|
||||
}{
|
||||
{
|
||||
name: "with invalid --detach-keys",
|
||||
args: []string{"--detach-keys", "shift-a", "myimage"},
|
||||
expectedErr: "invalid detach keys (shift-a):",
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cmd := newStartCommand(test.NewFakeCli(&fakeClient{}))
|
||||
cmd.SetOut(io.Discard)
|
||||
cmd.SetErr(io.Discard)
|
||||
cmd.SetArgs(tc.args)
|
||||
|
||||
err := cmd.Execute()
|
||||
if tc.expectedErr != "" {
|
||||
assert.Check(t, is.ErrorContains(err, tc.expectedErr))
|
||||
} else {
|
||||
assert.Check(t, is.Nil(err))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,10 @@
|
||||
package formatter
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
)
|
||||
|
||||
type dummy struct{}
|
||||
@@ -45,24 +47,18 @@ var dummyExpected = map[string]any{
|
||||
func TestMarshalMap(t *testing.T) {
|
||||
d := dummy{}
|
||||
m, err := marshalMap(&d)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(dummyExpected, m) {
|
||||
t.Fatalf("expected %+v, got %+v",
|
||||
dummyExpected, m)
|
||||
}
|
||||
assert.NilError(t, err)
|
||||
assert.Check(t, is.DeepEqual(m, dummyExpected))
|
||||
}
|
||||
|
||||
func TestMarshalMapBad(t *testing.T) {
|
||||
if _, err := marshalMap(nil); err == nil {
|
||||
t.Fatal("expected an error (argument is nil)")
|
||||
}
|
||||
if _, err := marshalMap(dummy{}); err == nil {
|
||||
t.Fatal("expected an error (argument is non-pointer)")
|
||||
}
|
||||
_, err := marshalMap(nil)
|
||||
assert.Check(t, is.Error(err, "expected a pointer to a struct, got invalid"), "expected an error (argument is nil)")
|
||||
|
||||
_, err = marshalMap(dummy{})
|
||||
assert.Check(t, is.Error(err, "expected a pointer to a struct, got struct"), "expected an error (argument is non-pointer)")
|
||||
|
||||
x := 42
|
||||
if _, err := marshalMap(&x); err == nil {
|
||||
t.Fatal("expected an error (argument is a pointer to non-struct)")
|
||||
}
|
||||
_, err = marshalMap(&x)
|
||||
assert.Check(t, is.Error(err, "expected a pointer to a struct, got a pointer to int"), "expected an error (argument is a pointer to non-struct)")
|
||||
}
|
||||
|
||||
@@ -480,7 +480,7 @@ func Compress(buildCtx io.ReadCloser) (io.ReadCloser, error) {
|
||||
pipeReader, pipeWriter := io.Pipe()
|
||||
|
||||
go func() {
|
||||
compressWriter, err := compression.CompressStream(pipeWriter, archive.Gzip)
|
||||
compressWriter, err := compression.CompressStream(pipeWriter, compression.Gzip)
|
||||
if err != nil {
|
||||
_ = pipeWriter.CloseWithError(err)
|
||||
}
|
||||
|
||||
+13
-13
@@ -65,7 +65,7 @@ func newImagesCommand(dockerCLI command.Cli) *cobra.Command {
|
||||
flags := cmd.Flags()
|
||||
|
||||
flags.BoolVarP(&options.quiet, "quiet", "q", false, "Only show image IDs")
|
||||
flags.BoolVarP(&options.all, "all", "a", false, "Show all images (default hides intermediate images)")
|
||||
flags.BoolVarP(&options.all, "all", "a", false, "Show all images (default hides intermediate and dangling images)")
|
||||
flags.BoolVar(&options.noTrunc, "no-trunc", false, "Don't truncate output")
|
||||
flags.BoolVar(&options.showDigests, "digests", false, "Show digests")
|
||||
flags.StringVar(&options.format, "format", "", flagsHelper.FormatHelp)
|
||||
@@ -109,29 +109,29 @@ func runImages(ctx context.Context, dockerCLI command.Cli, options imagesOptions
|
||||
|
||||
images := res.Items
|
||||
if !options.all {
|
||||
if _, ok := filters["dangling"]; !ok {
|
||||
if dangling, ok := filters["dangling"]; !ok || dangling["false"] {
|
||||
images = slices.DeleteFunc(images, isDangling)
|
||||
}
|
||||
}
|
||||
|
||||
format := options.format
|
||||
if len(format) == 0 {
|
||||
if len(dockerCLI.ConfigFile().ImagesFormat) > 0 && !options.quiet && !options.tree {
|
||||
format = dockerCLI.ConfigFile().ImagesFormat
|
||||
useTree = false
|
||||
} else {
|
||||
format = formatter.TableFormatKey
|
||||
}
|
||||
}
|
||||
|
||||
if useTree {
|
||||
return runTree(ctx, dockerCLI, treeOptions{
|
||||
images: images,
|
||||
all: options.all,
|
||||
filters: filters,
|
||||
expanded: options.tree,
|
||||
})
|
||||
}
|
||||
|
||||
format := options.format
|
||||
if len(format) == 0 {
|
||||
if len(dockerCLI.ConfigFile().ImagesFormat) > 0 && !options.quiet {
|
||||
format = dockerCLI.ConfigFile().ImagesFormat
|
||||
} else {
|
||||
format = formatter.TableFormatKey
|
||||
}
|
||||
}
|
||||
|
||||
imageCtx := formatter.ImageContext{
|
||||
Context: formatter.Context{
|
||||
Output: dockerCLI.Out(),
|
||||
@@ -176,7 +176,7 @@ func shouldUseTree(options imagesOptions) (bool, error) {
|
||||
|
||||
// isDangling is a copy of [formatter.isDangling].
|
||||
func isDangling(img image.Summary) bool {
|
||||
if len(img.RepoTags) == 0 && len(img.RepoDigests) == 0 {
|
||||
if len(img.RepoTags) == 0 {
|
||||
return true
|
||||
}
|
||||
return len(img.RepoTags) == 1 && img.RepoTags[0] == "<none>:<none>" && len(img.RepoDigests) == 1 && img.RepoDigests[0] == "<none>@<none>"
|
||||
|
||||
@@ -4,10 +4,12 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/cli/config/configfile"
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/moby/moby/api/types/image"
|
||||
"github.com/moby/moby/client"
|
||||
"gotest.tools/v3/assert"
|
||||
"gotest.tools/v3/golden"
|
||||
@@ -116,6 +118,86 @@ func TestNewListCommandAmbiguous(t *testing.T) {
|
||||
golden.Assert(t, cli.ErrBuffer().String(), "list-command-ambiguous.golden")
|
||||
}
|
||||
|
||||
func TestImagesFilterDangling(t *testing.T) {
|
||||
// Create test images with different states
|
||||
items := []image.Summary{
|
||||
{
|
||||
ID: "sha256:87428fc522803d31065e7bce3cf03fe475096631e5e07bbd7a0fde60c4cf25c7",
|
||||
RepoTags: []string{"myimage:latest"},
|
||||
RepoDigests: []string{"myimage@sha256:abc123"},
|
||||
},
|
||||
{
|
||||
ID: "sha256:0263829989b6fd954f72baaf2fc64bc2e2f01d692d4de72986ea808f6e99813f",
|
||||
RepoTags: []string{},
|
||||
RepoDigests: []string{},
|
||||
},
|
||||
{
|
||||
ID: "sha256:a3a5e715f0cc574a73c3f9bebb6bc24f32ffd5b67b387244c2c909da779a1478",
|
||||
RepoTags: []string{},
|
||||
RepoDigests: []string{"image@sha256:a3a5e715f0cc574a73c3f9bebb6bc24f32ffd5b67b387244c2c909da779a1478"},
|
||||
},
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
args []string
|
||||
imageListFunc func(options client.ImageListOptions) (client.ImageListResult, error)
|
||||
}{
|
||||
{
|
||||
name: "dangling-true",
|
||||
args: []string{"-f", "dangling=true"},
|
||||
imageListFunc: func(options client.ImageListOptions) (client.ImageListResult, error) {
|
||||
// Verify the filter is passed to the API
|
||||
assert.Check(t, options.Filters["dangling"]["true"])
|
||||
// dangling=true is handled on the server side and returns only dangling images
|
||||
return client.ImageListResult{Items: []image.Summary{items[1], items[2]}}, nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "dangling-false",
|
||||
args: []string{"-f", "dangling=false"},
|
||||
imageListFunc: func(options client.ImageListOptions) (client.ImageListResult, error) {
|
||||
// Verify the filter is passed to the API
|
||||
assert.Check(t, options.Filters["dangling"]["false"])
|
||||
// Return all images including dangling
|
||||
return client.ImageListResult{Items: slices.Clone(items)}, nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no-dangling-filter",
|
||||
args: []string{},
|
||||
imageListFunc: func(options client.ImageListOptions) (client.ImageListResult, error) {
|
||||
// Verify no dangling filter is passed to the API
|
||||
_, exists := options.Filters["dangling"]
|
||||
assert.Check(t, !exists)
|
||||
// Return all images including dangling
|
||||
return client.ImageListResult{Items: slices.Clone(items)}, nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "all-flag",
|
||||
args: []string{"--all"},
|
||||
imageListFunc: func(options client.ImageListOptions) (client.ImageListResult, error) {
|
||||
// Verify the All flag is set
|
||||
assert.Check(t, options.All)
|
||||
// Return all images including dangling
|
||||
return client.ImageListResult{Items: slices.Clone(items)}, nil
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{imageListFunc: tc.imageListFunc})
|
||||
cmd := newImagesCommand(cli)
|
||||
cmd.SetArgs(tc.args)
|
||||
err := cmd.Execute()
|
||||
assert.NilError(t, err)
|
||||
golden.Assert(t, cli.OutBuffer().String(), fmt.Sprintf("list-command-filter-dangling.%s.golden", tc.name))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func nilToEmptySlice[T any](s []T) []T {
|
||||
if s == nil {
|
||||
return []T{}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
IMAGE ID DISK USAGE CONTENT SIZE EXTRA
|
||||
myimage:latest 87428fc52280 0B 0B
|
||||
<untagged> 0263829989b6 0B 0B
|
||||
<untagged> a3a5e715f0cc 0B 0B
|
||||
@@ -0,0 +1,2 @@
|
||||
IMAGE ID DISK USAGE CONTENT SIZE EXTRA
|
||||
myimage:latest 87428fc52280 0B 0B
|
||||
@@ -0,0 +1,3 @@
|
||||
IMAGE ID DISK USAGE CONTENT SIZE EXTRA
|
||||
<untagged> 0263829989b6 0B 0B
|
||||
<untagged> a3a5e715f0cc 0B 0B
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
IMAGE ID DISK USAGE CONTENT SIZE EXTRA
|
||||
myimage:latest 87428fc52280 0B 0B
|
||||
@@ -1,2 +1 @@
|
||||
Info -> U In Use
|
||||
IMAGE ID DISK USAGE CONTENT SIZE EXTRA
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
Info -> U In Use
|
||||
IMAGE ID DISK USAGE CONTENT SIZE EXTRA
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
Info -> U In Use
|
||||
IMAGE ID DISK USAGE CONTENT SIZE EXTRA
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
Info -> U In Use
|
||||
IMAGE ID DISK USAGE CONTENT SIZE EXTRA
|
||||
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
IMAGE ID DISK USAGE CONTENT SIZE EXTRA
|
||||
multiplatform:latest aaaaaaaaaaaa 25.5 MB 20.2 MB U
|
||||
├─ linux/amd64 bbbbbbbbbbbb 12.1 MB 10.0 MB
|
||||
└─ linux/arm64 cccccccccccc 13.4 MB 10.2 MB U
|
||||
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
IMAGE ID DISK USAGE CONTENT SIZE EXTRA
|
||||
app:v1
|
||||
app:latest 101010101010 30.5 MB 25.2 MB U
|
||||
└─ linux/amd64 202020202020 15.2 MB 12.6 MB U
|
||||
|
||||
<untagged> 303030303030 12.3 MB 10.1 MB
|
||||
└─ linux/arm/v7 404040404040 6.1 MB 5.0 MB
|
||||
|
||||
base:alpine 505050505050 5.5 MB 5.5 MB
|
||||
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
IMAGE ID DISK USAGE CONTENT SIZE EXTRA
|
||||
<untagged> dddddddddddd 18.5 MB 15.2 MB
|
||||
├─ linux/amd64 eeeeeeeeeeee 9.2 MB 7.6 MB
|
||||
└─ linux/arm64 ffffffffffff 9.3 MB 7.6 MB
|
||||
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
IMAGE ID DISK USAGE CONTENT SIZE EXTRA
|
||||
a:1 111111111111 5.5 MB 2.5 MB
|
||||
<untagged> 222222222222 3.2 MB 1.6 MB
|
||||
short:v1 333333333333 7.1 MB 3.5 MB U
|
||||
+95
-47
@@ -22,9 +22,10 @@ import (
|
||||
"github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
const untaggedName = "<untagged>"
|
||||
|
||||
type treeOptions struct {
|
||||
images []imagetypes.Summary
|
||||
all bool
|
||||
filters client.Filters
|
||||
expanded bool
|
||||
}
|
||||
@@ -111,6 +112,13 @@ func runTree(ctx context.Context, dockerCLI command.Cli, opts treeOptions) (int,
|
||||
continue
|
||||
}
|
||||
|
||||
if len(sortedTags) == 0 {
|
||||
view.images = append(view.images, topImage{
|
||||
Details: topDetails,
|
||||
Children: children,
|
||||
created: img.Created,
|
||||
})
|
||||
}
|
||||
for _, tag := range sortedTags {
|
||||
view.images = append(view.images, topImage{
|
||||
Names: []string{tag},
|
||||
@@ -230,11 +238,11 @@ func printImageTree(outs command.Streams, view treeView) {
|
||||
}
|
||||
|
||||
out := tui.NewOutput(outs.Out())
|
||||
isTerm := out.IsTerminal()
|
||||
|
||||
_, width := out.GetTtySize()
|
||||
if width == 0 {
|
||||
width = 80
|
||||
}
|
||||
if width < 20 {
|
||||
limitWidth := width == 0
|
||||
if isTerm && width < 20 {
|
||||
width = 20
|
||||
}
|
||||
|
||||
@@ -243,16 +251,18 @@ func printImageTree(outs command.Streams, view treeView) {
|
||||
untaggedColor := out.Color(tui.ColorTertiary)
|
||||
titleColor := out.Color(tui.ColorTitle)
|
||||
|
||||
isTerm := out.IsTerminal()
|
||||
|
||||
out.Println(generateLegend(out, width))
|
||||
// Legend is right-aligned, so don't print it if the width is unlimited
|
||||
if !limitWidth {
|
||||
out.Println(generateLegend(out, width))
|
||||
}
|
||||
|
||||
possibleChips := getPossibleChips(view)
|
||||
columns := []imgColumn{
|
||||
{
|
||||
Title: "Image",
|
||||
Align: alignLeft,
|
||||
Width: 0,
|
||||
Title: "Image",
|
||||
Align: alignLeft,
|
||||
Width: 0,
|
||||
NoEllipsis: true,
|
||||
},
|
||||
{
|
||||
Title: "ID",
|
||||
@@ -284,7 +294,7 @@ func printImageTree(outs command.Streams, view treeView) {
|
||||
Width: func() int {
|
||||
maxChipsWidth := 0
|
||||
for _, chip := range possibleChips {
|
||||
s := chip.String(isTerm)
|
||||
s := out.Sprint(chip)
|
||||
l := tui.Width(s)
|
||||
maxChipsWidth += l
|
||||
}
|
||||
@@ -300,9 +310,9 @@ func printImageTree(outs command.Streams, view treeView) {
|
||||
var b strings.Builder
|
||||
for _, chip := range possibleChips {
|
||||
if chip.check(d) {
|
||||
b.WriteString(chip.String(isTerm))
|
||||
b.WriteString(out.Sprint(chip))
|
||||
} else {
|
||||
b.WriteString(chipPlaceholder.String(isTerm))
|
||||
b.WriteString(out.Sprint(chipPlaceholder))
|
||||
}
|
||||
}
|
||||
return b.String()
|
||||
@@ -340,25 +350,27 @@ func printImageTree(outs command.Streams, view treeView) {
|
||||
// to display their content.
|
||||
func adjustColumns(width uint, columns []imgColumn, images []topImage) []imgColumn {
|
||||
nameWidth := int(width)
|
||||
for idx, h := range columns {
|
||||
if h.Width == 0 {
|
||||
continue
|
||||
if nameWidth > 0 {
|
||||
for idx, h := range columns {
|
||||
if h.Width == 0 {
|
||||
continue
|
||||
}
|
||||
d := h.Width
|
||||
if idx > 0 {
|
||||
d += columnSpacing
|
||||
}
|
||||
// If the first column gets too short, remove remaining columns
|
||||
if nameWidth-d < 12 {
|
||||
columns = columns[:idx]
|
||||
break
|
||||
}
|
||||
nameWidth -= d
|
||||
}
|
||||
d := h.Width
|
||||
if idx > 0 {
|
||||
d += columnSpacing
|
||||
}
|
||||
// If the first column gets too short, remove remaining columns
|
||||
if nameWidth-d < 12 {
|
||||
columns = columns[:idx]
|
||||
break
|
||||
}
|
||||
nameWidth -= d
|
||||
}
|
||||
|
||||
// Try to make the first column as narrow as possible
|
||||
widest := widestFirstColumnValue(columns, images)
|
||||
if nameWidth > widest {
|
||||
if width == 0 || nameWidth > widest {
|
||||
nameWidth = widest
|
||||
}
|
||||
columns[0].Width = nameWidth
|
||||
@@ -422,17 +434,31 @@ func printChildren(out tui.Output, headers []imgColumn, img topImage, normalColo
|
||||
|
||||
func printNames(out tui.Output, headers []imgColumn, img topImage, color, untaggedColor aec.ANSI) {
|
||||
if len(img.Names) == 0 {
|
||||
_, _ = fmt.Fprint(out, headers[0].Print(untaggedColor, "<untagged>"))
|
||||
_, _ = fmt.Fprint(out, headers[0].Print(untaggedColor, untaggedName))
|
||||
}
|
||||
|
||||
for nameIdx, name := range img.Names {
|
||||
// Don't limit first names to the column width because only the last
|
||||
// name will be printed alongside other columns.
|
||||
if nameIdx < len(img.Names)-1 {
|
||||
_, fullWidth := out.GetTtySize()
|
||||
_, _ = fmt.Fprintln(out, color.Apply(tui.Ellipsis(name, int(fullWidth))))
|
||||
} else {
|
||||
_, _ = fmt.Fprint(out, headers[0].Print(color, name))
|
||||
nameWidth := tui.Width(name)
|
||||
lastName := nameIdx == len(img.Names)-1
|
||||
multiLine := nameWidth > headers[0].Width
|
||||
|
||||
_, _ = fmt.Fprint(out, headers[0].Print(color, name))
|
||||
|
||||
// Print each name on its own line, including the last,
|
||||
// unless the last name fits into the column.
|
||||
//
|
||||
// IMAGE ID ...
|
||||
// anImage 171e65262c80 ...
|
||||
// firstName
|
||||
// lastNameIsALongOne
|
||||
// eade5be814e8 ...
|
||||
// anotherLongName
|
||||
// bb747ca923a5 ...
|
||||
if !lastName || multiLine {
|
||||
_, _ = fmt.Fprintln(out)
|
||||
}
|
||||
if multiLine && lastName {
|
||||
_, _ = fmt.Fprint(out, strings.Repeat(" ", headers[0].Width))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -452,6 +478,7 @@ type imgColumn struct {
|
||||
|
||||
DetailsValue func(*imageDetails) string
|
||||
Color *aec.ANSI
|
||||
NoEllipsis bool
|
||||
}
|
||||
|
||||
func (h imgColumn) Print(clr aec.ANSI, s string) string {
|
||||
@@ -468,12 +495,16 @@ func (h imgColumn) Print(clr aec.ANSI, s string) string {
|
||||
func (h imgColumn) PrintC(clr aec.ANSI, s string) string {
|
||||
ln := tui.Width(s)
|
||||
|
||||
if ln > h.Width {
|
||||
return clr.Apply(tui.Ellipsis(s, h.Width))
|
||||
}
|
||||
|
||||
fill := h.Width - ln
|
||||
|
||||
if fill < 0 {
|
||||
if h.NoEllipsis {
|
||||
fill = 0
|
||||
} else {
|
||||
return clr.Apply(tui.Ellipsis(s, h.Width))
|
||||
}
|
||||
}
|
||||
|
||||
l := fill / 2
|
||||
r := fill - l
|
||||
|
||||
@@ -482,27 +513,44 @@ func (h imgColumn) PrintC(clr aec.ANSI, s string) string {
|
||||
|
||||
func (h imgColumn) PrintL(clr aec.ANSI, s string) string {
|
||||
ln := tui.Width(s)
|
||||
if ln > h.Width {
|
||||
return clr.Apply(tui.Ellipsis(s, h.Width))
|
||||
|
||||
fill := h.Width - ln
|
||||
|
||||
if fill < 0 {
|
||||
if h.NoEllipsis {
|
||||
fill = 0
|
||||
} else {
|
||||
return clr.Apply(tui.Ellipsis(s, h.Width))
|
||||
}
|
||||
}
|
||||
|
||||
return clr.Apply(s) + strings.Repeat(" ", h.Width-ln)
|
||||
return clr.Apply(s) + strings.Repeat(" ", fill)
|
||||
}
|
||||
|
||||
func (h imgColumn) PrintR(clr aec.ANSI, s string) string {
|
||||
ln := tui.Width(s)
|
||||
if ln > h.Width {
|
||||
return clr.Apply(tui.Ellipsis(s, h.Width))
|
||||
fill := h.Width - ln
|
||||
|
||||
if fill < 0 {
|
||||
if h.NoEllipsis {
|
||||
fill = 0
|
||||
} else {
|
||||
return clr.Apply(tui.Ellipsis(s, h.Width))
|
||||
}
|
||||
}
|
||||
|
||||
return strings.Repeat(" ", h.Width-ln) + clr.Apply(s)
|
||||
return strings.Repeat(" ", fill) + clr.Apply(s)
|
||||
}
|
||||
|
||||
// widestFirstColumnValue calculates the width needed to fully display the image names and platforms.
|
||||
func widestFirstColumnValue(headers []imgColumn, images []topImage) int {
|
||||
width := len(headers[0].Title)
|
||||
for _, img := range images {
|
||||
for _, name := range img.Names {
|
||||
names := img.Names
|
||||
if len(names) == 0 {
|
||||
names = []string{untaggedName}
|
||||
}
|
||||
for _, name := range names {
|
||||
if len(name) > width {
|
||||
width = len(name)
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package image
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/internal/test"
|
||||
"gotest.tools/v3/assert"
|
||||
"gotest.tools/v3/golden"
|
||||
)
|
||||
|
||||
func TestPrintImageTreeAnsiTty(t *testing.T) {
|
||||
@@ -15,6 +17,7 @@ func TestPrintImageTreeAnsiTty(t *testing.T) {
|
||||
stdoutTty bool
|
||||
stderrTty bool
|
||||
expectedAnsi bool
|
||||
noColorEnv bool
|
||||
}{
|
||||
{
|
||||
name: "non-terminal",
|
||||
@@ -80,6 +83,24 @@ func TestPrintImageTreeAnsiTty(t *testing.T) {
|
||||
|
||||
expectedAnsi: false,
|
||||
},
|
||||
{
|
||||
name: "no-color-env",
|
||||
stdinTty: false,
|
||||
stdoutTty: false,
|
||||
stderrTty: false,
|
||||
|
||||
noColorEnv: true,
|
||||
expectedAnsi: false,
|
||||
},
|
||||
{
|
||||
name: "no-color-env-terminal",
|
||||
stdinTty: true,
|
||||
stdoutTty: true,
|
||||
stderrTty: true,
|
||||
|
||||
noColorEnv: true,
|
||||
expectedAnsi: false,
|
||||
},
|
||||
}
|
||||
|
||||
mockView := treeView{
|
||||
@@ -115,6 +136,11 @@ func TestPrintImageTreeAnsiTty(t *testing.T) {
|
||||
cli.In().SetIsTerminal(tc.stdinTty)
|
||||
cli.Out().SetIsTerminal(tc.stdoutTty)
|
||||
cli.Err().SetIsTerminal(tc.stderrTty)
|
||||
if tc.noColorEnv {
|
||||
t.Setenv("NO_COLOR", "1")
|
||||
} else {
|
||||
t.Setenv("NO_COLOR", "")
|
||||
}
|
||||
|
||||
printImageTree(cli, mockView)
|
||||
|
||||
@@ -123,10 +149,207 @@ func TestPrintImageTreeAnsiTty(t *testing.T) {
|
||||
|
||||
hasAnsi := strings.Contains(out, "\x1b[")
|
||||
if tc.expectedAnsi {
|
||||
assert.Check(t, hasAnsi, "Output should contain ANSI escape codes")
|
||||
assert.Check(t, hasAnsi, "Output should contain ANSI escape codes, output: %s", out)
|
||||
} else {
|
||||
assert.Check(t, !hasAnsi, "Output should not contain ANSI escape codes")
|
||||
assert.Check(t, !hasAnsi, "Output should not contain ANSI escape codes, output: %s", out)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrintImageTreeGolden(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
view treeView
|
||||
expanded bool
|
||||
}{
|
||||
{
|
||||
name: "width-calculation-untagged",
|
||||
expanded: false,
|
||||
view: treeView{
|
||||
images: []topImage{
|
||||
{
|
||||
Names: []string{"a:1"},
|
||||
Details: imageDetails{
|
||||
ID: "sha256:1111111111111111111111111111111111111111111111111111111111111111",
|
||||
DiskUsage: "5.5 MB",
|
||||
InUse: false,
|
||||
ContentSize: "2.5 MB",
|
||||
},
|
||||
},
|
||||
{
|
||||
// Untagged image name is longer than "a:1"
|
||||
Names: []string{},
|
||||
Details: imageDetails{
|
||||
ID: "sha256:2222222222222222222222222222222222222222222222222222222222222222",
|
||||
DiskUsage: "3.2 MB",
|
||||
InUse: false,
|
||||
ContentSize: "1.6 MB",
|
||||
},
|
||||
},
|
||||
{
|
||||
Names: []string{"short:v1"},
|
||||
Details: imageDetails{
|
||||
ID: "sha256:3333333333333333333333333333333333333333333333333333333333333333",
|
||||
DiskUsage: "7.1 MB",
|
||||
InUse: true,
|
||||
ContentSize: "3.5 MB",
|
||||
},
|
||||
},
|
||||
},
|
||||
imageSpacing: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "expanded-view-with-platforms",
|
||||
expanded: false,
|
||||
view: treeView{
|
||||
images: []topImage{
|
||||
{
|
||||
Names: []string{"multiplatform:latest"},
|
||||
Details: imageDetails{
|
||||
ID: "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
DiskUsage: "25.5 MB",
|
||||
InUse: true,
|
||||
ContentSize: "20.2 MB",
|
||||
},
|
||||
Children: []subImage{
|
||||
{
|
||||
Platform: "linux/amd64",
|
||||
Available: true,
|
||||
Details: imageDetails{
|
||||
ID: "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
|
||||
DiskUsage: "12.1 MB",
|
||||
InUse: false,
|
||||
ContentSize: "10.0 MB",
|
||||
},
|
||||
},
|
||||
{
|
||||
Platform: "linux/arm64",
|
||||
Available: true,
|
||||
Details: imageDetails{
|
||||
ID: "sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
|
||||
DiskUsage: "13.4 MB",
|
||||
InUse: true,
|
||||
ContentSize: "10.2 MB",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
imageSpacing: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "untagged-with-platforms",
|
||||
expanded: false,
|
||||
view: treeView{
|
||||
images: []topImage{
|
||||
{
|
||||
Names: []string{},
|
||||
Details: imageDetails{
|
||||
ID: "sha256:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd",
|
||||
DiskUsage: "18.5 MB",
|
||||
InUse: false,
|
||||
ContentSize: "15.2 MB",
|
||||
},
|
||||
Children: []subImage{
|
||||
{
|
||||
Platform: "linux/amd64",
|
||||
Available: true,
|
||||
Details: imageDetails{
|
||||
ID: "sha256:eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
|
||||
DiskUsage: "9.2 MB",
|
||||
InUse: false,
|
||||
ContentSize: "7.6 MB",
|
||||
},
|
||||
},
|
||||
{
|
||||
Platform: "linux/arm64",
|
||||
Available: false,
|
||||
Details: imageDetails{
|
||||
ID: "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||
DiskUsage: "9.3 MB",
|
||||
InUse: false,
|
||||
ContentSize: "7.6 MB",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
imageSpacing: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "mixed-tagged-untagged-with-children",
|
||||
expanded: false,
|
||||
view: treeView{
|
||||
images: []topImage{
|
||||
{
|
||||
Names: []string{"app:v1", "app:latest"},
|
||||
Details: imageDetails{
|
||||
ID: "sha256:1010101010101010101010101010101010101010101010101010101010101010",
|
||||
DiskUsage: "30.5 MB",
|
||||
InUse: true,
|
||||
ContentSize: "25.2 MB",
|
||||
},
|
||||
Children: []subImage{
|
||||
{
|
||||
Platform: "linux/amd64",
|
||||
Available: true,
|
||||
Details: imageDetails{
|
||||
ID: "sha256:2020202020202020202020202020202020202020202020202020202020202020",
|
||||
DiskUsage: "15.2 MB",
|
||||
InUse: true,
|
||||
ContentSize: "12.6 MB",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Names: []string{},
|
||||
Details: imageDetails{
|
||||
ID: "sha256:3030303030303030303030303030303030303030303030303030303030303030",
|
||||
DiskUsage: "12.3 MB",
|
||||
InUse: false,
|
||||
ContentSize: "10.1 MB",
|
||||
},
|
||||
Children: []subImage{
|
||||
{
|
||||
Platform: "linux/arm/v7",
|
||||
Available: true,
|
||||
Details: imageDetails{
|
||||
ID: "sha256:4040404040404040404040404040404040404040404040404040404040404040",
|
||||
DiskUsage: "6.1 MB",
|
||||
InUse: false,
|
||||
ContentSize: "5.0 MB",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Names: []string{"base:alpine"},
|
||||
Details: imageDetails{
|
||||
ID: "sha256:5050505050505050505050505050505050505050505050505050505050505050",
|
||||
DiskUsage: "5.5 MB",
|
||||
InUse: false,
|
||||
ContentSize: "5.5 MB",
|
||||
},
|
||||
},
|
||||
},
|
||||
imageSpacing: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cli := test.NewFakeCli(nil)
|
||||
cli.Out().SetIsTerminal(false)
|
||||
|
||||
printImageTree(cli, tc.view)
|
||||
|
||||
golden.Assert(t, cli.OutBuffer().String(), fmt.Sprintf("tree-command-success.%s.golden", tc.name))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package node
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/cli/cli/command/formatter"
|
||||
@@ -170,15 +170,23 @@ func (c *nodeContext) ManagerStatus() string {
|
||||
}
|
||||
|
||||
func (c *nodeContext) TLSStatus() string {
|
||||
if c.info.Cluster == nil || reflect.DeepEqual(c.info.Cluster.TLSInfo, swarm.TLSInfo{}) || reflect.DeepEqual(c.n.Description.TLSInfo, swarm.TLSInfo{}) {
|
||||
if c.info.Cluster == nil || isEmptyTLSInfo(c.info.Cluster.TLSInfo) || isEmptyTLSInfo(c.n.Description.TLSInfo) {
|
||||
return "Unknown"
|
||||
}
|
||||
if reflect.DeepEqual(c.n.Description.TLSInfo, c.info.Cluster.TLSInfo) {
|
||||
if equalTLSInfo(c.n.Description.TLSInfo, c.info.Cluster.TLSInfo) {
|
||||
return "Ready"
|
||||
}
|
||||
return "Needs Rotation"
|
||||
}
|
||||
|
||||
func isEmptyTLSInfo(t swarm.TLSInfo) bool {
|
||||
return t.TrustRoot == "" && len(t.CertIssuerSubject) == 0 && len(t.CertIssuerPublicKey) == 0
|
||||
}
|
||||
|
||||
func equalTLSInfo(t, o swarm.TLSInfo) bool {
|
||||
return t.TrustRoot == o.TrustRoot && bytes.Equal(t.CertIssuerSubject, o.CertIssuerSubject) && bytes.Equal(t.CertIssuerPublicKey, o.CertIssuerPublicKey)
|
||||
}
|
||||
|
||||
func (c *nodeContext) EngineVersion() string {
|
||||
return c.n.Description.Engine.EngineVersion
|
||||
}
|
||||
@@ -320,8 +328,7 @@ func (ctx *nodeInspectContext) EngineVersion() string {
|
||||
}
|
||||
|
||||
func (ctx *nodeInspectContext) HasTLSInfo() bool {
|
||||
tlsInfo := ctx.Node.Description.TLSInfo
|
||||
return !reflect.DeepEqual(tlsInfo, swarm.TLSInfo{})
|
||||
return !isEmptyTLSInfo(ctx.Node.Description.TLSInfo)
|
||||
}
|
||||
|
||||
func (ctx *nodeInspectContext) TLSInfoTrustRoot() string {
|
||||
|
||||
@@ -200,7 +200,7 @@ func RetrieveAuthTokenFromImage(cfg *configfile.ConfigFile, image string) (strin
|
||||
return "", err
|
||||
}
|
||||
|
||||
encodedAuth, err := authconfig.Encode(registrytypes.AuthConfig{
|
||||
return authconfig.Encode(registrytypes.AuthConfig{
|
||||
Username: authConfig.Username,
|
||||
Password: authConfig.Password,
|
||||
ServerAddress: authConfig.ServerAddress,
|
||||
@@ -210,10 +210,6 @@ func RetrieveAuthTokenFromImage(cfg *configfile.ConfigFile, image string) (strin
|
||||
IdentityToken: authConfig.IdentityToken,
|
||||
RegistryToken: authConfig.RegistryToken,
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return encodedAuth, nil
|
||||
}
|
||||
|
||||
// getAuthConfigKey special-cases using the full index address of the official
|
||||
|
||||
@@ -197,7 +197,7 @@ func loginUser(ctx context.Context, dockerCLI command.Cli, opts loginOptions, de
|
||||
// will hit this if you attempt docker login from mintty where stdin
|
||||
// is a pipe, not a character based console.
|
||||
if (opts.user == "" || opts.password == "") && !dockerCLI.In().IsTerminal() {
|
||||
return "", errors.New("error: cannot perform an interactive login from a non TTY device")
|
||||
return "", errors.New("error: cannot perform an interactive login from a non-TTY device")
|
||||
}
|
||||
|
||||
// If we're logging into the index server and the user didn't provide a username or password, use the device flow
|
||||
|
||||
@@ -127,7 +127,7 @@ func TestRunLogin(t *testing.T) {
|
||||
input: loginOptions{
|
||||
serverAddress: "reg1",
|
||||
},
|
||||
expectedErr: "error: cannot perform an interactive login from a non TTY device",
|
||||
expectedErr: "error: cannot perform an interactive login from a non-TTY device",
|
||||
},
|
||||
{
|
||||
doc: "store valid username and password",
|
||||
@@ -334,19 +334,19 @@ func TestLoginNonInteractive(t *testing.T) {
|
||||
doc: "error - w/o user w/o pass ",
|
||||
username: false,
|
||||
password: false,
|
||||
expectedErr: "error: cannot perform an interactive login from a non TTY device",
|
||||
expectedErr: "error: cannot perform an interactive login from a non-TTY device",
|
||||
},
|
||||
{
|
||||
doc: "error - w/ user w/o pass",
|
||||
username: true,
|
||||
password: false,
|
||||
expectedErr: "error: cannot perform an interactive login from a non TTY device",
|
||||
expectedErr: "error: cannot perform an interactive login from a non-TTY device",
|
||||
},
|
||||
{
|
||||
doc: "error - w/o user w/ pass",
|
||||
username: false,
|
||||
password: true,
|
||||
expectedErr: "error: cannot perform an interactive login from a non TTY device",
|
||||
expectedErr: "error: cannot perform an interactive login from a non-TTY device",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -403,13 +403,13 @@ func TestLoginNonInteractive(t *testing.T) {
|
||||
doc: "error - w/ user w/o pass",
|
||||
username: true,
|
||||
password: false,
|
||||
expectedErr: "error: cannot perform an interactive login from a non TTY device",
|
||||
expectedErr: "error: cannot perform an interactive login from a non-TTY device",
|
||||
},
|
||||
{
|
||||
doc: "error - w/o user w/ pass",
|
||||
username: false,
|
||||
password: true,
|
||||
expectedErr: "error: cannot perform an interactive login from a non TTY device",
|
||||
expectedErr: "error: cannot perform an interactive login from a non-TTY device",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -905,9 +905,9 @@ func addServiceFlags(flags *pflag.FlagSet, options *serviceOptions, defaultFlagV
|
||||
flags.Int64Var(&options.resources.limitPids, flagLimitPids, 0, "Limit maximum number of processes (default 0 = unlimited)")
|
||||
flags.SetAnnotation(flagLimitPids, "version", []string{"1.41"})
|
||||
flags.Var(&options.resources.swapBytes, flagSwapBytes, "Swap Bytes (-1 for unlimited)")
|
||||
flags.SetAnnotation(flagLimitPids, "version", []string{"1.52"})
|
||||
flags.SetAnnotation(flagSwapBytes, "version", []string{"1.52"})
|
||||
flags.Int64Var(&options.resources.memSwappiness, flagMemSwappiness, -1, "Tune memory swappiness (0-100), -1 to reset to default")
|
||||
flags.SetAnnotation(flagLimitPids, "version", []string{"1.52"})
|
||||
flags.SetAnnotation(flagMemSwappiness, "version", []string{"1.52"})
|
||||
|
||||
flags.Var(&options.stopGrace, flagStopGracePeriod, flagDesc(flagStopGracePeriod, "Time to wait before force killing a container (ns|us|ms|s|m|h)"))
|
||||
flags.Var(&options.replicas, flagReplicas, "Number of tasks")
|
||||
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -368,23 +368,23 @@ func TestUpdateHealthcheckTable(t *testing.T) {
|
||||
err: "--no-healthcheck conflicts with --health-* options",
|
||||
},
|
||||
}
|
||||
for i, c := range testCases {
|
||||
flags := newUpdateCommand(nil).Flags()
|
||||
for _, flag := range c.flags {
|
||||
flags.Set(flag[0], flag[1])
|
||||
}
|
||||
cspec := &swarm.ContainerSpec{
|
||||
Healthcheck: c.initial,
|
||||
}
|
||||
err := updateHealthcheck(flags, cspec)
|
||||
if c.err != "" {
|
||||
assert.Error(t, err, c.err)
|
||||
} else {
|
||||
assert.NilError(t, err)
|
||||
if !reflect.DeepEqual(cspec.Healthcheck, c.expected) {
|
||||
t.Errorf("incorrect result for test %d, expected health config:\n\t%#v\ngot:\n\t%#v", i, c.expected, cspec.Healthcheck)
|
||||
for i, tc := range testCases {
|
||||
t.Run(strconv.Itoa(i), func(t *testing.T) {
|
||||
flags := newUpdateCommand(nil).Flags()
|
||||
for _, flag := range tc.flags {
|
||||
assert.Check(t, flags.Set(flag[0], flag[1]))
|
||||
}
|
||||
}
|
||||
cspec := &swarm.ContainerSpec{
|
||||
Healthcheck: tc.initial,
|
||||
}
|
||||
err := updateHealthcheck(flags, cspec)
|
||||
if tc.err != "" {
|
||||
assert.Error(t, err, tc.err)
|
||||
} else {
|
||||
assert.NilError(t, err)
|
||||
assert.Check(t, is.DeepEqual(cspec.Healthcheck, tc.expected))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
composeLoader "github.com/docker/cli/cli/compose/loader"
|
||||
composetypes "github.com/docker/cli/cli/compose/types"
|
||||
"github.com/spf13/cobra"
|
||||
"gopkg.in/yaml.v3"
|
||||
"go.yaml.in/yaml/v3"
|
||||
)
|
||||
|
||||
// configOptions holds docker stack config options
|
||||
|
||||
@@ -51,7 +51,12 @@ func newDeployCommand(dockerCLI command.Cli) *cobra.Command {
|
||||
|
||||
flags := cmd.Flags()
|
||||
flags.StringSliceVarP(&opts.composefiles, "compose-file", "c", []string{}, `Path to a Compose file, or "-" to read from stdin`)
|
||||
flags.SetAnnotation("compose-file", "version", []string{"1.25"})
|
||||
_ = flags.SetAnnotation("compose-file", "version", []string{"1.25"})
|
||||
// Provide tab-completion for filenames. On Bash, this is constrained to the
|
||||
// ".yaml" and ".yml" file-extensions, but this doesn't appear to be supported
|
||||
// by other shells.
|
||||
_ = cmd.MarkFlagFilename("compose-file", "yaml", "yml")
|
||||
|
||||
flags.BoolVar(&opts.sendRegistryAuth, "with-registry-auth", false, "Send registry authentication details to Swarm agents")
|
||||
flags.BoolVar(&opts.prune, "prune", false, "Prune services that are no longer referenced")
|
||||
flags.SetAnnotation("prune", "version", []string{"1.27"})
|
||||
|
||||
@@ -397,6 +397,13 @@ func prettyPrintServerInfo(streams command.Streams, info *dockerInfo) {
|
||||
}
|
||||
}
|
||||
|
||||
if info.NRI != nil {
|
||||
fprintln(output, " NRI:")
|
||||
for _, v := range info.NRI.Info {
|
||||
fprintf(output, " %s: %s\n", v[0], v[1])
|
||||
}
|
||||
}
|
||||
|
||||
fprintln(output)
|
||||
for _, w := range info.Warnings {
|
||||
fprintln(streams.Err(), w)
|
||||
|
||||
@@ -126,6 +126,12 @@ var sampleInfoNoSwarm = system.Info{
|
||||
{"ReloadedAt", "2025-07-16T16:59:14Z"},
|
||||
},
|
||||
},
|
||||
NRI: &system.NRIInfo{
|
||||
Info: [][2]string{
|
||||
{"plugin-path", "/usr/libexec/docker/nri-plugins"},
|
||||
{"plugin-config-path", "/etc/docker/nri/conf.d"},
|
||||
},
|
||||
},
|
||||
CDISpecDirs: []string{"/etc/cdi", "/var/run/cdi"},
|
||||
}
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"Client":{"Version":"18.99.5-ce","ApiVersion":"1.38","DefaultAPIVersion":"1.38","GitCommit":"deadbeef","GoVersion":"go1.10.2","Os":"linux","Arch":"amd64","BuildTime":"Wed May 30 22:21:05 2018","Context":"my-context"},"Server":{"Platform":{"Name":"Docker Enterprise Edition (EE) 2.0"},"Version":"","APIVersion":"","MinAPIVersion":"","Os":"","Arch":"","Experimental":false,"Components":[{"Name":"Engine","Version":"17.06.2-ee-15","Details":{"ApiVersion":"1.30","Arch":"amd64","BuildTime":"Mon Jul 9 23:38:38 2018","Experimental":"false","GitCommit":"64ddfa6","GoVersion":"go1.8.7","MinAPIVersion":"1.12","Os":"linux"}},{"Name":"Universal Control Plane","Version":"17.06.2-ee-15","Details":{"ApiVersion":"1.30","Arch":"amd64","BuildTime":"Mon Jul 2 21:24:07 UTC 2018","GitCommit":"4513922","GoVersion":"go1.9.4","MinApiVersion":"1.20","Os":"linux","Version":"3.0.3-tp2"}},{"Name":"Kubernetes","Version":"1.8+","Details":{"buildDate":"2018-04-26T16:51:21Z","compiler":"gc","gitCommit":"8d637aedf46b9c21dde723e29c645b9f27106fa5","gitTreeState":"clean","gitVersion":"v1.8.11-docker-8d637ae","goVersion":"go1.8.3","major":"1","minor":"8+","platform":"linux/amd64"}},{"Name":"Calico","Version":"v3.0.8","Details":{"cni":"v2.0.6","kube-controllers":"v2.0.5","node":"v3.0.8"}}]}}
|
||||
{"Client":{"Version":"18.99.5-ce","ApiVersion":"1.38","DefaultAPIVersion":"1.38","GitCommit":"deadbeef","GoVersion":"go1.10.2","Os":"linux","Arch":"amd64","BuildTime":"Wed May 30 22:21:05 2018","Context":"my-context"},"Server":{"Platform":{"Name":"Docker Enterprise Edition (EE) 2.0"},"Version":"18.99.5-ce","ApiVersion":"1.30","MinAPIVersion":"1.12","Os":"linux","Arch":"amd64","Components":[{"Name":"Engine","Version":"17.06.2-ee-15","Details":{"ApiVersion":"1.30","Arch":"amd64","BuildTime":"Mon Jul 9 23:38:38 2018","Experimental":"false","GitCommit":"64ddfa6","GoVersion":"go1.8.7","MinAPIVersion":"1.12","Os":"linux"}},{"Name":"Universal Control Plane","Version":"17.06.2-ee-15","Details":{"ApiVersion":"1.30","Arch":"amd64","BuildTime":"Mon Jul 2 21:24:07 UTC 2018","GitCommit":"4513922","GoVersion":"go1.9.4","MinApiVersion":"1.20","Os":"linux","Version":"3.0.3-tp2"}},{"Name":"Kubernetes","Version":"1.8+","Details":{"buildDate":"2018-04-26T16:51:21Z","compiler":"gc","gitCommit":"8d637aedf46b9c21dde723e29c645b9f27106fa5","gitTreeState":"clean","gitVersion":"v1.8.11-docker-8d637ae","goVersion":"go1.8.3","major":"1","minor":"8+","platform":"linux/amd64"}},{"Name":"Calico","Version":"v3.0.8","Details":{"cni":"v2.0.6","kube-controllers":"v2.0.5","node":"v3.0.8"}}],"GitCommit":"64ddfa6","GoVersion":"go1.8.7","KernelVersion":"v1.0.0","BuildTime":"2018-07-09T22:38:38.000000000+00:00"}}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"Client":{"Version":"18.99.5-ce","ApiVersion":"1.38","DefaultAPIVersion":"1.38","GitCommit":"deadbeef","GoVersion":"go1.10.2","Os":"linux","Arch":"amd64","BuildTime":"Wed May 30 22:21:05 2018","Context":"my-context"},"Server":{"Platform":{"Name":"Docker Enterprise Edition (EE) 2.0"},"Version":"","APIVersion":"","MinAPIVersion":"","Os":"","Arch":"","Experimental":false,"Components":[{"Name":"Engine","Version":"17.06.2-ee-15","Details":{"ApiVersion":"1.30","Arch":"amd64","BuildTime":"Mon Jul 9 23:38:38 2018","Experimental":"false","GitCommit":"64ddfa6","GoVersion":"go1.8.7","MinAPIVersion":"1.12","Os":"linux"}},{"Name":"Universal Control Plane","Version":"17.06.2-ee-15","Details":{"ApiVersion":"1.30","Arch":"amd64","BuildTime":"Mon Jul 2 21:24:07 UTC 2018","GitCommit":"4513922","GoVersion":"go1.9.4","MinApiVersion":"1.20","Os":"linux","Version":"3.0.3-tp2"}},{"Name":"Kubernetes","Version":"1.8+","Details":{"buildDate":"2018-04-26T16:51:21Z","compiler":"gc","gitCommit":"8d637aedf46b9c21dde723e29c645b9f27106fa5","gitTreeState":"clean","gitVersion":"v1.8.11-docker-8d637ae","goVersion":"go1.8.3","major":"1","minor":"8+","platform":"linux/amd64"}},{"Name":"Calico","Version":"v3.0.8","Details":{"cni":"v2.0.6","kube-controllers":"v2.0.5","node":"v3.0.8"}}]}}
|
||||
{"Client":{"Version":"18.99.5-ce","ApiVersion":"1.38","DefaultAPIVersion":"1.38","GitCommit":"deadbeef","GoVersion":"go1.10.2","Os":"linux","Arch":"amd64","BuildTime":"Wed May 30 22:21:05 2018","Context":"my-context"},"Server":{"Platform":{"Name":"Docker Enterprise Edition (EE) 2.0"},"Version":"18.99.5-ce","ApiVersion":"1.30","MinAPIVersion":"1.12","Os":"linux","Arch":"amd64","Components":[{"Name":"Engine","Version":"17.06.2-ee-15","Details":{"ApiVersion":"1.30","Arch":"amd64","BuildTime":"Mon Jul 9 23:38:38 2018","Experimental":"false","GitCommit":"64ddfa6","GoVersion":"go1.8.7","MinAPIVersion":"1.12","Os":"linux"}},{"Name":"Universal Control Plane","Version":"17.06.2-ee-15","Details":{"ApiVersion":"1.30","Arch":"amd64","BuildTime":"Mon Jul 2 21:24:07 UTC 2018","GitCommit":"4513922","GoVersion":"go1.9.4","MinApiVersion":"1.20","Os":"linux","Version":"3.0.3-tp2"}},{"Name":"Kubernetes","Version":"1.8+","Details":{"buildDate":"2018-04-26T16:51:21Z","compiler":"gc","gitCommit":"8d637aedf46b9c21dde723e29c645b9f27106fa5","gitTreeState":"clean","gitVersion":"v1.8.11-docker-8d637ae","goVersion":"go1.8.3","major":"1","minor":"8+","platform":"linux/amd64"}},{"Name":"Calico","Version":"v3.0.8","Details":{"cni":"v2.0.6","kube-controllers":"v2.0.5","node":"v3.0.8"}}],"GitCommit":"64ddfa6","GoVersion":"go1.8.7","KernelVersion":"v1.0.0","BuildTime":"2018-07-09T22:38:38.000000000+00:00"}}
|
||||
|
||||
@@ -54,4 +54,7 @@ Server:
|
||||
Base: 10.123.0.0/16, Size: 24
|
||||
Firewall Backend: nftables+firewalld
|
||||
ReloadedAt: 2025-07-16T16:59:14Z
|
||||
NRI:
|
||||
plugin-path: /usr/libexec/docker/nri-plugins
|
||||
plugin-config-path: /etc/docker/nri/conf.d
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"ID":"EKHL:QDUU:QZ7U:MKGD:VDXK:S27Q:GIPU:24B7:R7VT:DGN6:QCSF:2UBX","Containers":0,"ContainersRunning":0,"ContainersPaused":0,"ContainersStopped":0,"Images":0,"Driver":"overlay2","DriverStatus":[["Backing Filesystem","extfs"],["Supports d_type","true"],["Using metacopy","false"],["Native Overlay Diff","true"]],"Plugins":{"Volume":["local"],"Network":["bridge","host","macvlan","null","overlay"],"Authorization":null,"Log":["awslogs","fluentd","gcplogs","gelf","journald","json-file","splunk","syslog"]},"MemoryLimit":true,"SwapLimit":true,"CpuCfsPeriod":true,"CpuCfsQuota":true,"CPUShares":true,"CPUSet":true,"PidsLimit":false,"IPv4Forwarding":true,"Debug":true,"NFd":33,"OomKillDisable":true,"NGoroutines":135,"SystemTime":"2017-08-24T17:44:34.077811894Z","LoggingDriver":"json-file","CgroupDriver":"cgroupfs","NEventsListener":0,"KernelVersion":"4.4.0-87-generic","OperatingSystem":"Ubuntu 16.04.3 LTS","OSVersion":"","OSType":"linux","Architecture":"x86_64","IndexServerAddress":"https://index.docker.io/v1/","RegistryConfig":{"InsecureRegistryCIDRs":["127.0.0.0/8"],"IndexConfigs":{"docker.io":{"Name":"docker.io","Mirrors":null,"Secure":true,"Official":true}},"Mirrors":null},"NCPU":2,"MemTotal":2097356800,"GenericResources":null,"DockerRootDir":"/var/lib/docker","HttpProxy":"","HttpsProxy":"","NoProxy":"","Name":"system-sample","Labels":["provider=digitalocean"],"ExperimentalBuild":false,"ServerVersion":"17.06.1-ce","Runtimes":{"runc":{"path":"docker-runc"}},"DefaultRuntime":"runc","Swarm":{"NodeID":"","NodeAddr":"","LocalNodeState":"inactive","ControlAvailable":false,"Error":"","RemoteManagers":null},"LiveRestoreEnabled":false,"Isolation":"","InitBinary":"docker-init","ContainerdCommit":{"ID":"6e23458c129b551d5c9871e5174f6b1b7f6d1170"},"RuncCommit":{"ID":"810190ceaa507aa2727d7ae6f4790c76ec150bd2"},"InitCommit":{"ID":"949e6fa"},"SecurityOptions":["foo="],"DefaultAddressPools":[{"Base":"10.123.0.0/16","Size":24}],"FirewallBackend":{"Driver":"nftables+firewalld","Info":[["ReloadedAt","2025-07-16T16:59:14Z"]]},"CDISpecDirs":["/etc/cdi","/var/run/cdi"],"Warnings":null,"ServerErrors":["a server error occurred"],"ClientInfo":{"Debug":false,"Context":"","Plugins":[],"Warnings":null}}
|
||||
{"ID":"EKHL:QDUU:QZ7U:MKGD:VDXK:S27Q:GIPU:24B7:R7VT:DGN6:QCSF:2UBX","Containers":0,"ContainersRunning":0,"ContainersPaused":0,"ContainersStopped":0,"Images":0,"Driver":"overlay2","DriverStatus":[["Backing Filesystem","extfs"],["Supports d_type","true"],["Using metacopy","false"],["Native Overlay Diff","true"]],"Plugins":{"Volume":["local"],"Network":["bridge","host","macvlan","null","overlay"],"Authorization":null,"Log":["awslogs","fluentd","gcplogs","gelf","journald","json-file","splunk","syslog"]},"MemoryLimit":true,"SwapLimit":true,"CpuCfsPeriod":true,"CpuCfsQuota":true,"CPUShares":true,"CPUSet":true,"PidsLimit":false,"IPv4Forwarding":true,"Debug":true,"NFd":33,"OomKillDisable":true,"NGoroutines":135,"SystemTime":"2017-08-24T17:44:34.077811894Z","LoggingDriver":"json-file","CgroupDriver":"cgroupfs","NEventsListener":0,"KernelVersion":"4.4.0-87-generic","OperatingSystem":"Ubuntu 16.04.3 LTS","OSVersion":"","OSType":"linux","Architecture":"x86_64","IndexServerAddress":"https://index.docker.io/v1/","RegistryConfig":{"InsecureRegistryCIDRs":["127.0.0.0/8"],"IndexConfigs":{"docker.io":{"Name":"docker.io","Mirrors":null,"Secure":true,"Official":true}},"Mirrors":null},"NCPU":2,"MemTotal":2097356800,"GenericResources":null,"DockerRootDir":"/var/lib/docker","HttpProxy":"","HttpsProxy":"","NoProxy":"","Name":"system-sample","Labels":["provider=digitalocean"],"ExperimentalBuild":false,"ServerVersion":"17.06.1-ce","Runtimes":{"runc":{"path":"docker-runc"}},"DefaultRuntime":"runc","Swarm":{"NodeID":"","NodeAddr":"","LocalNodeState":"inactive","ControlAvailable":false,"Error":"","RemoteManagers":null},"LiveRestoreEnabled":false,"Isolation":"","InitBinary":"docker-init","ContainerdCommit":{"ID":"6e23458c129b551d5c9871e5174f6b1b7f6d1170"},"RuncCommit":{"ID":"810190ceaa507aa2727d7ae6f4790c76ec150bd2"},"InitCommit":{"ID":"949e6fa"},"SecurityOptions":["foo="],"DefaultAddressPools":[{"Base":"10.123.0.0/16","Size":24}],"FirewallBackend":{"Driver":"nftables+firewalld","Info":[["ReloadedAt","2025-07-16T16:59:14Z"]]},"CDISpecDirs":["/etc/cdi","/var/run/cdi"],"NRI":{"Info":[["plugin-path","/usr/libexec/docker/nri-plugins"],["plugin-config-path","/etc/docker/nri/conf.d"]]},"Warnings":null,"ServerErrors":["a server error occurred"],"ClientInfo":{"Debug":false,"Context":"","Plugins":[],"Warnings":null}}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"ID":"EKHL:QDUU:QZ7U:MKGD:VDXK:S27Q:GIPU:24B7:R7VT:DGN6:QCSF:2UBX","Containers":0,"ContainersRunning":0,"ContainersPaused":0,"ContainersStopped":0,"Images":0,"Driver":"overlay2","DriverStatus":[["Backing Filesystem","extfs"],["Supports d_type","true"],["Using metacopy","false"],["Native Overlay Diff","true"]],"Plugins":{"Volume":["local"],"Network":["bridge","host","macvlan","null","overlay"],"Authorization":null,"Log":["awslogs","fluentd","gcplogs","gelf","journald","json-file","splunk","syslog"]},"MemoryLimit":true,"SwapLimit":true,"CpuCfsPeriod":true,"CpuCfsQuota":true,"CPUShares":true,"CPUSet":true,"PidsLimit":false,"IPv4Forwarding":true,"Debug":true,"NFd":33,"OomKillDisable":true,"NGoroutines":135,"SystemTime":"2017-08-24T17:44:34.077811894Z","LoggingDriver":"json-file","CgroupDriver":"cgroupfs","NEventsListener":0,"KernelVersion":"4.4.0-87-generic","OperatingSystem":"Ubuntu 16.04.3 LTS","OSVersion":"","OSType":"linux","Architecture":"x86_64","IndexServerAddress":"https://index.docker.io/v1/","RegistryConfig":{"InsecureRegistryCIDRs":["127.0.0.0/8"],"IndexConfigs":{"docker.io":{"Name":"docker.io","Mirrors":null,"Secure":true,"Official":true}},"Mirrors":null},"NCPU":2,"MemTotal":2097356800,"GenericResources":null,"DockerRootDir":"/var/lib/docker","HttpProxy":"","HttpsProxy":"","NoProxy":"","Name":"system-sample","Labels":["provider=digitalocean"],"ExperimentalBuild":false,"ServerVersion":"17.06.1-ce","Runtimes":{"runc":{"path":"docker-runc"}},"DefaultRuntime":"runc","Swarm":{"NodeID":"","NodeAddr":"","LocalNodeState":"inactive","ControlAvailable":false,"Error":"","RemoteManagers":null},"LiveRestoreEnabled":false,"Isolation":"","InitBinary":"docker-init","ContainerdCommit":{"ID":"6e23458c129b551d5c9871e5174f6b1b7f6d1170"},"RuncCommit":{"ID":"810190ceaa507aa2727d7ae6f4790c76ec150bd2"},"InitCommit":{"ID":"949e6fa"},"SecurityOptions":["name=apparmor","name=seccomp,profile=default"],"DefaultAddressPools":[{"Base":"10.123.0.0/16","Size":24}],"FirewallBackend":{"Driver":"nftables+firewalld","Info":[["ReloadedAt","2025-07-16T16:59:14Z"]]},"CDISpecDirs":["/etc/cdi","/var/run/cdi"],"Warnings":["WARNING: No memory limit support","WARNING: No swap limit support","WARNING: No oom kill disable support","WARNING: No cpu cfs quota support","WARNING: No cpu cfs period support","WARNING: No cpu shares support","WARNING: No cpuset support","WARNING: IPv4 forwarding is disabled"],"ClientInfo":{"Debug":true,"Platform":{"Name":"Docker Engine - Community"},"Version":"24.0.0","Context":"default","Plugins":[],"Warnings":null}}
|
||||
{"ID":"EKHL:QDUU:QZ7U:MKGD:VDXK:S27Q:GIPU:24B7:R7VT:DGN6:QCSF:2UBX","Containers":0,"ContainersRunning":0,"ContainersPaused":0,"ContainersStopped":0,"Images":0,"Driver":"overlay2","DriverStatus":[["Backing Filesystem","extfs"],["Supports d_type","true"],["Using metacopy","false"],["Native Overlay Diff","true"]],"Plugins":{"Volume":["local"],"Network":["bridge","host","macvlan","null","overlay"],"Authorization":null,"Log":["awslogs","fluentd","gcplogs","gelf","journald","json-file","splunk","syslog"]},"MemoryLimit":true,"SwapLimit":true,"CpuCfsPeriod":true,"CpuCfsQuota":true,"CPUShares":true,"CPUSet":true,"PidsLimit":false,"IPv4Forwarding":true,"Debug":true,"NFd":33,"OomKillDisable":true,"NGoroutines":135,"SystemTime":"2017-08-24T17:44:34.077811894Z","LoggingDriver":"json-file","CgroupDriver":"cgroupfs","NEventsListener":0,"KernelVersion":"4.4.0-87-generic","OperatingSystem":"Ubuntu 16.04.3 LTS","OSVersion":"","OSType":"linux","Architecture":"x86_64","IndexServerAddress":"https://index.docker.io/v1/","RegistryConfig":{"InsecureRegistryCIDRs":["127.0.0.0/8"],"IndexConfigs":{"docker.io":{"Name":"docker.io","Mirrors":null,"Secure":true,"Official":true}},"Mirrors":null},"NCPU":2,"MemTotal":2097356800,"GenericResources":null,"DockerRootDir":"/var/lib/docker","HttpProxy":"","HttpsProxy":"","NoProxy":"","Name":"system-sample","Labels":["provider=digitalocean"],"ExperimentalBuild":false,"ServerVersion":"17.06.1-ce","Runtimes":{"runc":{"path":"docker-runc"}},"DefaultRuntime":"runc","Swarm":{"NodeID":"","NodeAddr":"","LocalNodeState":"inactive","ControlAvailable":false,"Error":"","RemoteManagers":null},"LiveRestoreEnabled":false,"Isolation":"","InitBinary":"docker-init","ContainerdCommit":{"ID":"6e23458c129b551d5c9871e5174f6b1b7f6d1170"},"RuncCommit":{"ID":"810190ceaa507aa2727d7ae6f4790c76ec150bd2"},"InitCommit":{"ID":"949e6fa"},"SecurityOptions":["name=apparmor","name=seccomp,profile=default"],"DefaultAddressPools":[{"Base":"10.123.0.0/16","Size":24}],"FirewallBackend":{"Driver":"nftables+firewalld","Info":[["ReloadedAt","2025-07-16T16:59:14Z"]]},"CDISpecDirs":["/etc/cdi","/var/run/cdi"],"NRI":{"Info":[["plugin-path","/usr/libexec/docker/nri-plugins"],["plugin-config-path","/etc/docker/nri/conf.d"]]},"Warnings":["WARNING: No memory limit support","WARNING: No swap limit support","WARNING: No oom kill disable support","WARNING: No cpu cfs quota support","WARNING: No cpu cfs period support","WARNING: No cpu shares support","WARNING: No cpuset support","WARNING: IPv4 forwarding is disabled"],"ClientInfo":{"Debug":true,"Platform":{"Name":"Docker Engine - Community"},"Version":"24.0.0","Context":"default","Plugins":[],"Warnings":null}}
|
||||
|
||||
@@ -59,4 +59,7 @@ Server:
|
||||
Base: 10.123.0.0/16, Size: 24
|
||||
Firewall Backend: nftables+firewalld
|
||||
ReloadedAt: 2025-07-16T16:59:14Z
|
||||
NRI:
|
||||
plugin-path: /usr/libexec/docker/nri-plugins
|
||||
plugin-config-path: /etc/docker/nri/conf.d
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"ID":"EKHL:QDUU:QZ7U:MKGD:VDXK:S27Q:GIPU:24B7:R7VT:DGN6:QCSF:2UBX","Containers":0,"ContainersRunning":0,"ContainersPaused":0,"ContainersStopped":0,"Images":0,"Driver":"overlay2","DriverStatus":[["Backing Filesystem","extfs"],["Supports d_type","true"],["Using metacopy","false"],["Native Overlay Diff","true"]],"Plugins":{"Volume":["local"],"Network":["bridge","host","macvlan","null","overlay"],"Authorization":null,"Log":["awslogs","fluentd","gcplogs","gelf","journald","json-file","splunk","syslog"]},"MemoryLimit":true,"SwapLimit":true,"CpuCfsPeriod":true,"CpuCfsQuota":true,"CPUShares":true,"CPUSet":true,"PidsLimit":false,"IPv4Forwarding":true,"Debug":true,"NFd":33,"OomKillDisable":true,"NGoroutines":135,"SystemTime":"2017-08-24T17:44:34.077811894Z","LoggingDriver":"json-file","CgroupDriver":"cgroupfs","NEventsListener":0,"KernelVersion":"4.4.0-87-generic","OperatingSystem":"Ubuntu 16.04.3 LTS","OSVersion":"","OSType":"linux","Architecture":"x86_64","IndexServerAddress":"https://index.docker.io/v1/","RegistryConfig":{"InsecureRegistryCIDRs":["127.0.0.0/8"],"IndexConfigs":{"docker.io":{"Name":"docker.io","Mirrors":null,"Secure":true,"Official":true}},"Mirrors":null},"NCPU":2,"MemTotal":2097356800,"GenericResources":null,"DockerRootDir":"/var/lib/docker","HttpProxy":"","HttpsProxy":"","NoProxy":"","Name":"system-sample","Labels":["provider=digitalocean"],"ExperimentalBuild":false,"ServerVersion":"17.06.1-ce","Runtimes":{"runc":{"path":"docker-runc"}},"DefaultRuntime":"runc","Swarm":{"NodeID":"","NodeAddr":"","LocalNodeState":"inactive","ControlAvailable":false,"Error":"","RemoteManagers":null},"LiveRestoreEnabled":false,"Isolation":"","InitBinary":"docker-init","ContainerdCommit":{"ID":"6e23458c129b551d5c9871e5174f6b1b7f6d1170"},"RuncCommit":{"ID":"810190ceaa507aa2727d7ae6f4790c76ec150bd2"},"InitCommit":{"ID":"949e6fa"},"SecurityOptions":["name=apparmor","name=seccomp,profile=default"],"DefaultAddressPools":[{"Base":"10.123.0.0/16","Size":24}],"FirewallBackend":{"Driver":"nftables+firewalld","Info":[["ReloadedAt","2025-07-16T16:59:14Z"]]},"CDISpecDirs":["/etc/cdi","/var/run/cdi"],"Warnings":null,"ClientInfo":{"Debug":true,"Platform":{"Name":"Docker Engine - Community"},"Version":"24.0.0","Context":"default","Plugins":[],"Warnings":null}}
|
||||
{"ID":"EKHL:QDUU:QZ7U:MKGD:VDXK:S27Q:GIPU:24B7:R7VT:DGN6:QCSF:2UBX","Containers":0,"ContainersRunning":0,"ContainersPaused":0,"ContainersStopped":0,"Images":0,"Driver":"overlay2","DriverStatus":[["Backing Filesystem","extfs"],["Supports d_type","true"],["Using metacopy","false"],["Native Overlay Diff","true"]],"Plugins":{"Volume":["local"],"Network":["bridge","host","macvlan","null","overlay"],"Authorization":null,"Log":["awslogs","fluentd","gcplogs","gelf","journald","json-file","splunk","syslog"]},"MemoryLimit":true,"SwapLimit":true,"CpuCfsPeriod":true,"CpuCfsQuota":true,"CPUShares":true,"CPUSet":true,"PidsLimit":false,"IPv4Forwarding":true,"Debug":true,"NFd":33,"OomKillDisable":true,"NGoroutines":135,"SystemTime":"2017-08-24T17:44:34.077811894Z","LoggingDriver":"json-file","CgroupDriver":"cgroupfs","NEventsListener":0,"KernelVersion":"4.4.0-87-generic","OperatingSystem":"Ubuntu 16.04.3 LTS","OSVersion":"","OSType":"linux","Architecture":"x86_64","IndexServerAddress":"https://index.docker.io/v1/","RegistryConfig":{"InsecureRegistryCIDRs":["127.0.0.0/8"],"IndexConfigs":{"docker.io":{"Name":"docker.io","Mirrors":null,"Secure":true,"Official":true}},"Mirrors":null},"NCPU":2,"MemTotal":2097356800,"GenericResources":null,"DockerRootDir":"/var/lib/docker","HttpProxy":"","HttpsProxy":"","NoProxy":"","Name":"system-sample","Labels":["provider=digitalocean"],"ExperimentalBuild":false,"ServerVersion":"17.06.1-ce","Runtimes":{"runc":{"path":"docker-runc"}},"DefaultRuntime":"runc","Swarm":{"NodeID":"","NodeAddr":"","LocalNodeState":"inactive","ControlAvailable":false,"Error":"","RemoteManagers":null},"LiveRestoreEnabled":false,"Isolation":"","InitBinary":"docker-init","ContainerdCommit":{"ID":"6e23458c129b551d5c9871e5174f6b1b7f6d1170"},"RuncCommit":{"ID":"810190ceaa507aa2727d7ae6f4790c76ec150bd2"},"InitCommit":{"ID":"949e6fa"},"SecurityOptions":["name=apparmor","name=seccomp,profile=default"],"DefaultAddressPools":[{"Base":"10.123.0.0/16","Size":24}],"FirewallBackend":{"Driver":"nftables+firewalld","Info":[["ReloadedAt","2025-07-16T16:59:14Z"]]},"CDISpecDirs":["/etc/cdi","/var/run/cdi"],"NRI":{"Info":[["plugin-path","/usr/libexec/docker/nri-plugins"],["plugin-config-path","/etc/docker/nri/conf.d"]]},"Warnings":null,"ClientInfo":{"Debug":true,"Platform":{"Name":"Docker Engine - Community"},"Version":"24.0.0","Context":"default","Plugins":[],"Warnings":null}}
|
||||
|
||||
@@ -64,4 +64,7 @@ Server:
|
||||
Base: 10.123.0.0/16, Size: 24
|
||||
Firewall Backend: nftables+firewalld
|
||||
ReloadedAt: 2025-07-16T16:59:14Z
|
||||
NRI:
|
||||
plugin-path: /usr/libexec/docker/nri-plugins
|
||||
plugin-config-path: /etc/docker/nri/conf.d
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"ID":"EKHL:QDUU:QZ7U:MKGD:VDXK:S27Q:GIPU:24B7:R7VT:DGN6:QCSF:2UBX","Containers":0,"ContainersRunning":0,"ContainersPaused":0,"ContainersStopped":0,"Images":0,"Driver":"overlay2","DriverStatus":[["Backing Filesystem","extfs"],["Supports d_type","true"],["Using metacopy","false"],["Native Overlay Diff","true"]],"Plugins":{"Volume":["local"],"Network":["bridge","host","macvlan","null","overlay"],"Authorization":null,"Log":["awslogs","fluentd","gcplogs","gelf","journald","json-file","splunk","syslog"]},"MemoryLimit":true,"SwapLimit":true,"CpuCfsPeriod":true,"CpuCfsQuota":true,"CPUShares":true,"CPUSet":true,"PidsLimit":false,"IPv4Forwarding":true,"Debug":true,"NFd":33,"OomKillDisable":true,"NGoroutines":135,"SystemTime":"2017-08-24T17:44:34.077811894Z","LoggingDriver":"json-file","CgroupDriver":"cgroupfs","NEventsListener":0,"KernelVersion":"4.4.0-87-generic","OperatingSystem":"Ubuntu 16.04.3 LTS","OSVersion":"","OSType":"linux","Architecture":"x86_64","IndexServerAddress":"https://index.docker.io/v1/","RegistryConfig":{"InsecureRegistryCIDRs":["127.0.0.0/8"],"IndexConfigs":{"docker.io":{"Name":"docker.io","Mirrors":null,"Secure":true,"Official":true}},"Mirrors":null},"NCPU":2,"MemTotal":2097356800,"GenericResources":null,"DockerRootDir":"/var/lib/docker","HttpProxy":"","HttpsProxy":"","NoProxy":"","Name":"system-sample","Labels":["provider=digitalocean"],"ExperimentalBuild":false,"ServerVersion":"17.06.1-ce","Runtimes":{"runc":{"path":"docker-runc"}},"DefaultRuntime":"runc","Swarm":{"NodeID":"","NodeAddr":"","LocalNodeState":"inactive","ControlAvailable":false,"Error":"","RemoteManagers":null},"LiveRestoreEnabled":false,"Isolation":"","InitBinary":"docker-init","ContainerdCommit":{"ID":"6e23458c129b551d5c9871e5174f6b1b7f6d1170"},"RuncCommit":{"ID":"810190ceaa507aa2727d7ae6f4790c76ec150bd2"},"InitCommit":{"ID":"949e6fa"},"SecurityOptions":["name=apparmor","name=seccomp,profile=default"],"DefaultAddressPools":[{"Base":"10.123.0.0/16","Size":24}],"FirewallBackend":{"Driver":"nftables+firewalld","Info":[["ReloadedAt","2025-07-16T16:59:14Z"]]},"CDISpecDirs":["/etc/cdi","/var/run/cdi"],"Warnings":null,"ClientInfo":{"Debug":false,"Context":"default","Plugins":[{"SchemaVersion":"0.1.0","Vendor":"ACME Corp","Version":"0.1.0","ShortDescription":"unit test is good","Name":"goodplugin","Path":"/path/to/docker-goodplugin"},{"SchemaVersion":"0.1.0","Vendor":"ACME Corp","ShortDescription":"this plugin has no version","Name":"unversionedplugin","Path":"/path/to/docker-unversionedplugin"},{"Name":"badplugin","Path":"/path/to/docker-badplugin","Err":"something wrong"}],"Warnings":null}}
|
||||
{"ID":"EKHL:QDUU:QZ7U:MKGD:VDXK:S27Q:GIPU:24B7:R7VT:DGN6:QCSF:2UBX","Containers":0,"ContainersRunning":0,"ContainersPaused":0,"ContainersStopped":0,"Images":0,"Driver":"overlay2","DriverStatus":[["Backing Filesystem","extfs"],["Supports d_type","true"],["Using metacopy","false"],["Native Overlay Diff","true"]],"Plugins":{"Volume":["local"],"Network":["bridge","host","macvlan","null","overlay"],"Authorization":null,"Log":["awslogs","fluentd","gcplogs","gelf","journald","json-file","splunk","syslog"]},"MemoryLimit":true,"SwapLimit":true,"CpuCfsPeriod":true,"CpuCfsQuota":true,"CPUShares":true,"CPUSet":true,"PidsLimit":false,"IPv4Forwarding":true,"Debug":true,"NFd":33,"OomKillDisable":true,"NGoroutines":135,"SystemTime":"2017-08-24T17:44:34.077811894Z","LoggingDriver":"json-file","CgroupDriver":"cgroupfs","NEventsListener":0,"KernelVersion":"4.4.0-87-generic","OperatingSystem":"Ubuntu 16.04.3 LTS","OSVersion":"","OSType":"linux","Architecture":"x86_64","IndexServerAddress":"https://index.docker.io/v1/","RegistryConfig":{"InsecureRegistryCIDRs":["127.0.0.0/8"],"IndexConfigs":{"docker.io":{"Name":"docker.io","Mirrors":null,"Secure":true,"Official":true}},"Mirrors":null},"NCPU":2,"MemTotal":2097356800,"GenericResources":null,"DockerRootDir":"/var/lib/docker","HttpProxy":"","HttpsProxy":"","NoProxy":"","Name":"system-sample","Labels":["provider=digitalocean"],"ExperimentalBuild":false,"ServerVersion":"17.06.1-ce","Runtimes":{"runc":{"path":"docker-runc"}},"DefaultRuntime":"runc","Swarm":{"NodeID":"","NodeAddr":"","LocalNodeState":"inactive","ControlAvailable":false,"Error":"","RemoteManagers":null},"LiveRestoreEnabled":false,"Isolation":"","InitBinary":"docker-init","ContainerdCommit":{"ID":"6e23458c129b551d5c9871e5174f6b1b7f6d1170"},"RuncCommit":{"ID":"810190ceaa507aa2727d7ae6f4790c76ec150bd2"},"InitCommit":{"ID":"949e6fa"},"SecurityOptions":["name=apparmor","name=seccomp,profile=default"],"DefaultAddressPools":[{"Base":"10.123.0.0/16","Size":24}],"FirewallBackend":{"Driver":"nftables+firewalld","Info":[["ReloadedAt","2025-07-16T16:59:14Z"]]},"CDISpecDirs":["/etc/cdi","/var/run/cdi"],"NRI":{"Info":[["plugin-path","/usr/libexec/docker/nri-plugins"],["plugin-config-path","/etc/docker/nri/conf.d"]]},"Warnings":null,"ClientInfo":{"Debug":false,"Context":"default","Plugins":[{"SchemaVersion":"0.1.0","Vendor":"ACME Corp","Version":"0.1.0","ShortDescription":"unit test is good","Name":"goodplugin","Path":"/path/to/docker-goodplugin"},{"SchemaVersion":"0.1.0","Vendor":"ACME Corp","ShortDescription":"this plugin has no version","Name":"unversionedplugin","Path":"/path/to/docker-unversionedplugin"},{"Name":"badplugin","Path":"/path/to/docker-badplugin","Err":"something wrong"}],"Warnings":null}}
|
||||
|
||||
@@ -59,4 +59,7 @@ Server:
|
||||
Base: 10.123.0.0/16, Size: 24
|
||||
Firewall Backend: nftables+firewalld
|
||||
ReloadedAt: 2025-07-16T16:59:14Z
|
||||
NRI:
|
||||
plugin-path: /usr/libexec/docker/nri-plugins
|
||||
plugin-config-path: /etc/docker/nri/conf.d
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"ID":"EKHL:QDUU:QZ7U:MKGD:VDXK:S27Q:GIPU:24B7:R7VT:DGN6:QCSF:2UBX","Containers":0,"ContainersRunning":0,"ContainersPaused":0,"ContainersStopped":0,"Images":0,"Driver":"overlay2","DriverStatus":[["Backing Filesystem","extfs"],["Supports d_type","true"],["Using metacopy","false"],["Native Overlay Diff","true"]],"Plugins":{"Volume":["local"],"Network":["bridge","host","macvlan","null","overlay"],"Authorization":null,"Log":["awslogs","fluentd","gcplogs","gelf","journald","json-file","splunk","syslog"]},"MemoryLimit":true,"SwapLimit":true,"CpuCfsPeriod":true,"CpuCfsQuota":true,"CPUShares":true,"CPUSet":true,"PidsLimit":false,"IPv4Forwarding":true,"Debug":true,"NFd":33,"OomKillDisable":true,"NGoroutines":135,"SystemTime":"2017-08-24T17:44:34.077811894Z","LoggingDriver":"json-file","CgroupDriver":"cgroupfs","NEventsListener":0,"KernelVersion":"4.4.0-87-generic","OperatingSystem":"Ubuntu 16.04.3 LTS","OSVersion":"","OSType":"linux","Architecture":"x86_64","IndexServerAddress":"https://index.docker.io/v1/","RegistryConfig":{"InsecureRegistryCIDRs":["127.0.0.0/8"],"IndexConfigs":{"docker.io":{"Name":"docker.io","Mirrors":null,"Secure":true,"Official":true}},"Mirrors":null},"NCPU":2,"MemTotal":2097356800,"GenericResources":null,"DockerRootDir":"/var/lib/docker","HttpProxy":"","HttpsProxy":"","NoProxy":"","Name":"system-sample","Labels":["provider=digitalocean"],"ExperimentalBuild":false,"ServerVersion":"17.06.1-ce","Runtimes":{"runc":{"path":"docker-runc"}},"DefaultRuntime":"runc","Swarm":{"NodeID":"","NodeAddr":"","LocalNodeState":"inactive","ControlAvailable":false,"Error":"","RemoteManagers":null},"LiveRestoreEnabled":false,"Isolation":"","InitBinary":"docker-init","ContainerdCommit":{"ID":"6e23458c129b551d5c9871e5174f6b1b7f6d1170"},"RuncCommit":{"ID":"810190ceaa507aa2727d7ae6f4790c76ec150bd2"},"InitCommit":{"ID":"949e6fa"},"SecurityOptions":["name=apparmor","name=seccomp,profile=default"],"DefaultAddressPools":[{"Base":"10.123.0.0/16","Size":24}],"FirewallBackend":{"Driver":"nftables+firewalld","Info":[["ReloadedAt","2025-07-16T16:59:14Z"]]},"CDISpecDirs":["/etc/cdi","/var/run/cdi"],"DiscoveredDevices":[{"Source":"cdi","ID":"com.example.device1"},{"Source":"cdi","ID":"nvidia.com/gpu=gpu0"}],"Warnings":null}
|
||||
{"ID":"EKHL:QDUU:QZ7U:MKGD:VDXK:S27Q:GIPU:24B7:R7VT:DGN6:QCSF:2UBX","Containers":0,"ContainersRunning":0,"ContainersPaused":0,"ContainersStopped":0,"Images":0,"Driver":"overlay2","DriverStatus":[["Backing Filesystem","extfs"],["Supports d_type","true"],["Using metacopy","false"],["Native Overlay Diff","true"]],"Plugins":{"Volume":["local"],"Network":["bridge","host","macvlan","null","overlay"],"Authorization":null,"Log":["awslogs","fluentd","gcplogs","gelf","journald","json-file","splunk","syslog"]},"MemoryLimit":true,"SwapLimit":true,"CpuCfsPeriod":true,"CpuCfsQuota":true,"CPUShares":true,"CPUSet":true,"PidsLimit":false,"IPv4Forwarding":true,"Debug":true,"NFd":33,"OomKillDisable":true,"NGoroutines":135,"SystemTime":"2017-08-24T17:44:34.077811894Z","LoggingDriver":"json-file","CgroupDriver":"cgroupfs","NEventsListener":0,"KernelVersion":"4.4.0-87-generic","OperatingSystem":"Ubuntu 16.04.3 LTS","OSVersion":"","OSType":"linux","Architecture":"x86_64","IndexServerAddress":"https://index.docker.io/v1/","RegistryConfig":{"InsecureRegistryCIDRs":["127.0.0.0/8"],"IndexConfigs":{"docker.io":{"Name":"docker.io","Mirrors":null,"Secure":true,"Official":true}},"Mirrors":null},"NCPU":2,"MemTotal":2097356800,"GenericResources":null,"DockerRootDir":"/var/lib/docker","HttpProxy":"","HttpsProxy":"","NoProxy":"","Name":"system-sample","Labels":["provider=digitalocean"],"ExperimentalBuild":false,"ServerVersion":"17.06.1-ce","Runtimes":{"runc":{"path":"docker-runc"}},"DefaultRuntime":"runc","Swarm":{"NodeID":"","NodeAddr":"","LocalNodeState":"inactive","ControlAvailable":false,"Error":"","RemoteManagers":null},"LiveRestoreEnabled":false,"Isolation":"","InitBinary":"docker-init","ContainerdCommit":{"ID":"6e23458c129b551d5c9871e5174f6b1b7f6d1170"},"RuncCommit":{"ID":"810190ceaa507aa2727d7ae6f4790c76ec150bd2"},"InitCommit":{"ID":"949e6fa"},"SecurityOptions":["name=apparmor","name=seccomp,profile=default"],"DefaultAddressPools":[{"Base":"10.123.0.0/16","Size":24}],"FirewallBackend":{"Driver":"nftables+firewalld","Info":[["ReloadedAt","2025-07-16T16:59:14Z"]]},"CDISpecDirs":["/etc/cdi","/var/run/cdi"],"DiscoveredDevices":[{"Source":"cdi","ID":"com.example.device1"},{"Source":"cdi","ID":"nvidia.com/gpu=gpu0"}],"NRI":{"Info":[["plugin-path","/usr/libexec/docker/nri-plugins"],["plugin-config-path","/etc/docker/nri/conf.d"]]},"Warnings":null}
|
||||
|
||||
@@ -56,4 +56,7 @@ Server:
|
||||
Base: 10.123.0.0/16, Size: 24
|
||||
Firewall Backend: nftables+firewalld
|
||||
ReloadedAt: 2025-07-16T16:59:14Z
|
||||
NRI:
|
||||
plugin-path: /usr/libexec/docker/nri-plugins
|
||||
plugin-config-path: /etc/docker/nri/conf.d
|
||||
|
||||
|
||||
@@ -56,4 +56,7 @@ Server:
|
||||
Base: 10.123.0.0/16, Size: 24
|
||||
Firewall Backend: nftables+firewalld
|
||||
ReloadedAt: 2025-07-16T16:59:14Z
|
||||
NRI:
|
||||
plugin-path: /usr/libexec/docker/nri-plugins
|
||||
plugin-config-path: /etc/docker/nri/conf.d
|
||||
|
||||
|
||||
@@ -80,4 +80,7 @@ Server:
|
||||
Base: 10.123.0.0/16, Size: 24
|
||||
Firewall Backend: nftables+firewalld
|
||||
ReloadedAt: 2025-07-16T16:59:14Z
|
||||
NRI:
|
||||
plugin-path: /usr/libexec/docker/nri-plugins
|
||||
plugin-config-path: /etc/docker/nri/conf.d
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"ID":"EKHL:QDUU:QZ7U:MKGD:VDXK:S27Q:GIPU:24B7:R7VT:DGN6:QCSF:2UBX","Containers":0,"ContainersRunning":0,"ContainersPaused":0,"ContainersStopped":0,"Images":0,"Driver":"overlay2","DriverStatus":[["Backing Filesystem","extfs"],["Supports d_type","true"],["Using metacopy","false"],["Native Overlay Diff","true"]],"Plugins":{"Volume":["local"],"Network":["bridge","host","macvlan","null","overlay"],"Authorization":null,"Log":["awslogs","fluentd","gcplogs","gelf","journald","json-file","splunk","syslog"]},"MemoryLimit":true,"SwapLimit":true,"CpuCfsPeriod":true,"CpuCfsQuota":true,"CPUShares":true,"CPUSet":true,"PidsLimit":false,"IPv4Forwarding":true,"Debug":true,"NFd":33,"OomKillDisable":true,"NGoroutines":135,"SystemTime":"2017-08-24T17:44:34.077811894Z","LoggingDriver":"json-file","CgroupDriver":"cgroupfs","NEventsListener":0,"KernelVersion":"4.4.0-87-generic","OperatingSystem":"Ubuntu 16.04.3 LTS","OSVersion":"","OSType":"linux","Architecture":"x86_64","IndexServerAddress":"https://index.docker.io/v1/","RegistryConfig":{"InsecureRegistryCIDRs":["127.0.0.0/8"],"IndexConfigs":{"docker.io":{"Name":"docker.io","Mirrors":null,"Secure":true,"Official":true}},"Mirrors":null},"NCPU":2,"MemTotal":2097356800,"GenericResources":null,"DockerRootDir":"/var/lib/docker","HttpProxy":"","HttpsProxy":"","NoProxy":"","Name":"system-sample","Labels":["provider=digitalocean"],"ExperimentalBuild":false,"ServerVersion":"17.06.1-ce","Runtimes":{"runc":{"path":"docker-runc"}},"DefaultRuntime":"runc","Swarm":{"NodeID":"qo2dfdig9mmxqkawulggepdih","NodeAddr":"165.227.107.89","LocalNodeState":"active","ControlAvailable":true,"Error":"","RemoteManagers":[{"NodeID":"qo2dfdig9mmxqkawulggepdih","Addr":"165.227.107.89:2377"}],"Nodes":1,"Managers":1,"Cluster":{"ID":"9vs5ygs0gguyyec4iqf2314c0","Version":{"Index":11},"CreatedAt":"2017-08-24T17:34:19.278062352Z","UpdatedAt":"2017-08-24T17:34:42.398815481Z","Spec":{"Name":"default","Labels":null,"Orchestration":{"TaskHistoryRetentionLimit":5},"Raft":{"SnapshotInterval":10000,"KeepOldSnapshots":0,"LogEntriesForSlowFollowers":500,"ElectionTick":3,"HeartbeatTick":1},"Dispatcher":{"HeartbeatPeriod":5000000000},"CAConfig":{"NodeCertExpiry":7776000000000000},"TaskDefaults":{},"EncryptionConfig":{"AutoLockManagers":true}},"TLSInfo":{"TrustRoot":"\n-----BEGIN CERTIFICATE-----\nMIIBajCCARCgAwIBAgIUaFCW5xsq8eyiJ+Pmcv3MCflMLnMwCgYIKoZIzj0EAwIw\nEzERMA8GA1UEAxMIc3dhcm0tY2EwHhcNMTcwODI0MTcyOTAwWhcNMzcwODE5MTcy\nOTAwWjATMREwDwYDVQQDEwhzd2FybS1jYTBZMBMGByqGSM49AgEGCCqGSM49AwEH\nA0IABDy7NebyUJyUjWJDBUdnZoV6GBxEGKO4TZPNDwnxDxJcUdLVaB7WGa4/DLrW\nUfsVgh1JGik2VTiLuTMA1tLlNPOjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMB\nAf8EBTADAQH/MB0GA1UdDgQWBBQl16XFtaaXiUAwEuJptJlDjfKskDAKBggqhkjO\nPQQDAgNIADBFAiEAo9fTQNM5DP9bHVcTJYfl2Cay1bFu1E+lnpmN+EYJfeACIGKH\n1pCUkZ+D0IB6CiEZGWSHyLuXPM1rlP+I5KuS7sB8\n-----END CERTIFICATE-----\n","CertIssuerSubject":"MBMxETAPBgNVBAMTCHN3YXJtLWNh","CertIssuerPublicKey":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEPLs15vJQnJSNYkMFR2dmhXoYHEQYo7hNk80PCfEPElxR0tVoHtYZrj8MutZR+xWCHUkaKTZVOIu5MwDW0uU08w=="},"RootRotationInProgress":false,"DefaultAddrPool":null,"SubnetSize":0,"DataPathPort":0}},"LiveRestoreEnabled":false,"Isolation":"","InitBinary":"docker-init","ContainerdCommit":{"ID":"6e23458c129b551d5c9871e5174f6b1b7f6d1170"},"RuncCommit":{"ID":"810190ceaa507aa2727d7ae6f4790c76ec150bd2"},"InitCommit":{"ID":"949e6fa"},"SecurityOptions":["name=apparmor","name=seccomp,profile=default"],"DefaultAddressPools":[{"Base":"10.123.0.0/16","Size":24}],"FirewallBackend":{"Driver":"nftables+firewalld","Info":[["ReloadedAt","2025-07-16T16:59:14Z"]]},"CDISpecDirs":["/etc/cdi","/var/run/cdi"],"Warnings":null,"ClientInfo":{"Debug":false,"Context":"default","Plugins":[],"Warnings":null}}
|
||||
{"ID":"EKHL:QDUU:QZ7U:MKGD:VDXK:S27Q:GIPU:24B7:R7VT:DGN6:QCSF:2UBX","Containers":0,"ContainersRunning":0,"ContainersPaused":0,"ContainersStopped":0,"Images":0,"Driver":"overlay2","DriverStatus":[["Backing Filesystem","extfs"],["Supports d_type","true"],["Using metacopy","false"],["Native Overlay Diff","true"]],"Plugins":{"Volume":["local"],"Network":["bridge","host","macvlan","null","overlay"],"Authorization":null,"Log":["awslogs","fluentd","gcplogs","gelf","journald","json-file","splunk","syslog"]},"MemoryLimit":true,"SwapLimit":true,"CpuCfsPeriod":true,"CpuCfsQuota":true,"CPUShares":true,"CPUSet":true,"PidsLimit":false,"IPv4Forwarding":true,"Debug":true,"NFd":33,"OomKillDisable":true,"NGoroutines":135,"SystemTime":"2017-08-24T17:44:34.077811894Z","LoggingDriver":"json-file","CgroupDriver":"cgroupfs","NEventsListener":0,"KernelVersion":"4.4.0-87-generic","OperatingSystem":"Ubuntu 16.04.3 LTS","OSVersion":"","OSType":"linux","Architecture":"x86_64","IndexServerAddress":"https://index.docker.io/v1/","RegistryConfig":{"InsecureRegistryCIDRs":["127.0.0.0/8"],"IndexConfigs":{"docker.io":{"Name":"docker.io","Mirrors":null,"Secure":true,"Official":true}},"Mirrors":null},"NCPU":2,"MemTotal":2097356800,"GenericResources":null,"DockerRootDir":"/var/lib/docker","HttpProxy":"","HttpsProxy":"","NoProxy":"","Name":"system-sample","Labels":["provider=digitalocean"],"ExperimentalBuild":false,"ServerVersion":"17.06.1-ce","Runtimes":{"runc":{"path":"docker-runc"}},"DefaultRuntime":"runc","Swarm":{"NodeID":"qo2dfdig9mmxqkawulggepdih","NodeAddr":"165.227.107.89","LocalNodeState":"active","ControlAvailable":true,"Error":"","RemoteManagers":[{"NodeID":"qo2dfdig9mmxqkawulggepdih","Addr":"165.227.107.89:2377"}],"Nodes":1,"Managers":1,"Cluster":{"ID":"9vs5ygs0gguyyec4iqf2314c0","Version":{"Index":11},"CreatedAt":"2017-08-24T17:34:19.278062352Z","UpdatedAt":"2017-08-24T17:34:42.398815481Z","Spec":{"Name":"default","Labels":null,"Orchestration":{"TaskHistoryRetentionLimit":5},"Raft":{"SnapshotInterval":10000,"KeepOldSnapshots":0,"LogEntriesForSlowFollowers":500,"ElectionTick":3,"HeartbeatTick":1},"Dispatcher":{"HeartbeatPeriod":5000000000},"CAConfig":{"NodeCertExpiry":7776000000000000},"TaskDefaults":{},"EncryptionConfig":{"AutoLockManagers":true}},"TLSInfo":{"TrustRoot":"\n-----BEGIN CERTIFICATE-----\nMIIBajCCARCgAwIBAgIUaFCW5xsq8eyiJ+Pmcv3MCflMLnMwCgYIKoZIzj0EAwIw\nEzERMA8GA1UEAxMIc3dhcm0tY2EwHhcNMTcwODI0MTcyOTAwWhcNMzcwODE5MTcy\nOTAwWjATMREwDwYDVQQDEwhzd2FybS1jYTBZMBMGByqGSM49AgEGCCqGSM49AwEH\nA0IABDy7NebyUJyUjWJDBUdnZoV6GBxEGKO4TZPNDwnxDxJcUdLVaB7WGa4/DLrW\nUfsVgh1JGik2VTiLuTMA1tLlNPOjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMB\nAf8EBTADAQH/MB0GA1UdDgQWBBQl16XFtaaXiUAwEuJptJlDjfKskDAKBggqhkjO\nPQQDAgNIADBFAiEAo9fTQNM5DP9bHVcTJYfl2Cay1bFu1E+lnpmN+EYJfeACIGKH\n1pCUkZ+D0IB6CiEZGWSHyLuXPM1rlP+I5KuS7sB8\n-----END CERTIFICATE-----\n","CertIssuerSubject":"MBMxETAPBgNVBAMTCHN3YXJtLWNh","CertIssuerPublicKey":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEPLs15vJQnJSNYkMFR2dmhXoYHEQYo7hNk80PCfEPElxR0tVoHtYZrj8MutZR+xWCHUkaKTZVOIu5MwDW0uU08w=="},"RootRotationInProgress":false,"DefaultAddrPool":null,"SubnetSize":0,"DataPathPort":0}},"LiveRestoreEnabled":false,"Isolation":"","InitBinary":"docker-init","ContainerdCommit":{"ID":"6e23458c129b551d5c9871e5174f6b1b7f6d1170"},"RuncCommit":{"ID":"810190ceaa507aa2727d7ae6f4790c76ec150bd2"},"InitCommit":{"ID":"949e6fa"},"SecurityOptions":["name=apparmor","name=seccomp,profile=default"],"DefaultAddressPools":[{"Base":"10.123.0.0/16","Size":24}],"FirewallBackend":{"Driver":"nftables+firewalld","Info":[["ReloadedAt","2025-07-16T16:59:14Z"]]},"CDISpecDirs":["/etc/cdi","/var/run/cdi"],"NRI":{"Info":[["plugin-path","/usr/libexec/docker/nri-plugins"],["plugin-config-path","/etc/docker/nri/conf.d"]]},"Warnings":null,"ClientInfo":{"Debug":false,"Context":"default","Plugins":[],"Warnings":null}}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"io"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
@@ -63,7 +64,7 @@ type versionOptions struct {
|
||||
// versionInfo contains version information of both the Client, and Server
|
||||
type versionInfo struct {
|
||||
Client clientVersion
|
||||
Server *client.ServerVersionResult
|
||||
Server *serverVersion
|
||||
}
|
||||
|
||||
type platformInfo struct {
|
||||
@@ -83,6 +84,26 @@ type clientVersion struct {
|
||||
Context string `json:"Context"`
|
||||
}
|
||||
|
||||
// serverVersion contains information about the Docker server host.
|
||||
// it's the client-side presentation of [client.ServerVersionResult].
|
||||
type serverVersion struct {
|
||||
Platform client.PlatformInfo `json:",omitempty"` // Platform is the platform (product name) the server is running on.
|
||||
Version string `json:"Version"` // Version is the version of the daemon.
|
||||
APIVersion string `json:"ApiVersion"` // APIVersion is the highest API version supported by the server.
|
||||
MinAPIVersion string `json:"MinAPIVersion,omitempty"` // MinAPIVersion is the minimum API version the server supports.
|
||||
Os string `json:"Os"` // Os is the operating system the server runs on.
|
||||
Arch string `json:"Arch"` // Arch is the hardware architecture the server runs on.
|
||||
Components []system.ComponentVersion `json:"Components,omitempty"` // Components contains version information for the components making up the server.
|
||||
|
||||
// The following fields are deprecated, they relate to the Engine component and are kept for backwards compatibility
|
||||
|
||||
GitCommit string `json:"GitCommit,omitempty"`
|
||||
GoVersion string `json:"GoVersion,omitempty"`
|
||||
KernelVersion string `json:"KernelVersion,omitempty"`
|
||||
Experimental bool `json:"Experimental,omitempty"`
|
||||
BuildTime string `json:"BuildTime,omitempty"`
|
||||
}
|
||||
|
||||
// newClientVersion constructs a new clientVersion. If a dockerCLI is
|
||||
// passed as argument, additional information is included (API version),
|
||||
// which may invoke an API connection. Pass nil to omit the additional
|
||||
@@ -107,6 +128,49 @@ func newClientVersion(contextName string, dockerCli command.Cli) clientVersion {
|
||||
return v
|
||||
}
|
||||
|
||||
func newServerVersion(sv client.ServerVersionResult) *serverVersion {
|
||||
out := &serverVersion{
|
||||
Platform: sv.Platform,
|
||||
Version: sv.Version,
|
||||
APIVersion: sv.APIVersion,
|
||||
MinAPIVersion: sv.MinAPIVersion,
|
||||
Os: sv.Os,
|
||||
Arch: sv.Arch,
|
||||
Experimental: sv.Experimental, //nolint:staticcheck // ignore deprecated field.
|
||||
Components: make([]system.ComponentVersion, 0, len(sv.Components)),
|
||||
}
|
||||
foundEngine := false
|
||||
for _, component := range sv.Components {
|
||||
if component.Name == "Engine" {
|
||||
foundEngine = true
|
||||
buildTime, ok := component.Details["BuildTime"]
|
||||
if ok {
|
||||
component.Details["BuildTime"] = reformatDate(buildTime)
|
||||
}
|
||||
out.GitCommit = component.Details["GitCommit"]
|
||||
out.GoVersion = component.Details["GoVersion"]
|
||||
out.KernelVersion = component.Details["KernelVersion"]
|
||||
out.Experimental = func() bool { b, _ := strconv.ParseBool(component.Details["Experimental"]); return b }()
|
||||
out.BuildTime = buildTime
|
||||
}
|
||||
out.Components = append(out.Components, component)
|
||||
}
|
||||
|
||||
if !foundEngine {
|
||||
out.Components = append(out.Components, system.ComponentVersion{
|
||||
Name: "Engine",
|
||||
Version: sv.Version,
|
||||
Details: map[string]string{
|
||||
"ApiVersion": sv.APIVersion,
|
||||
"MinAPIVersion": sv.MinAPIVersion,
|
||||
"Os": sv.Os,
|
||||
"Arch": sv.Arch,
|
||||
},
|
||||
})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// newVersionCommand creates a new cobra.Command for `docker version`
|
||||
func newVersionCommand(dockerCLI command.Cli) *cobra.Command {
|
||||
var opts versionOptions
|
||||
@@ -138,14 +202,14 @@ func reformatDate(buildTime string) string {
|
||||
}
|
||||
|
||||
func arch() string {
|
||||
arch := runtime.GOARCH
|
||||
out := runtime.GOARCH
|
||||
if rosetta.Enabled() {
|
||||
arch += " (rosetta)"
|
||||
out += " (rosetta)"
|
||||
}
|
||||
return arch
|
||||
return out
|
||||
}
|
||||
|
||||
func runVersion(ctx context.Context, dockerCli command.Cli, opts *versionOptions) error {
|
||||
func runVersion(ctx context.Context, dockerCLI command.Cli, opts *versionOptions) error {
|
||||
var err error
|
||||
tmpl, err := newVersionTemplate(opts.format)
|
||||
if err != nil {
|
||||
@@ -153,36 +217,13 @@ func runVersion(ctx context.Context, dockerCli command.Cli, opts *versionOptions
|
||||
}
|
||||
|
||||
vd := versionInfo{
|
||||
Client: newClientVersion(dockerCli.CurrentContext(), dockerCli),
|
||||
Client: newClientVersion(dockerCLI.CurrentContext(), dockerCLI),
|
||||
}
|
||||
sv, err := dockerCli.Client().ServerVersion(ctx, client.ServerVersionOptions{})
|
||||
sv, err := dockerCLI.Client().ServerVersion(ctx, client.ServerVersionOptions{})
|
||||
if err == nil {
|
||||
vd.Server = &sv
|
||||
foundEngine := false
|
||||
for _, component := range sv.Components {
|
||||
if component.Name == "Engine" {
|
||||
foundEngine = true
|
||||
buildTime, ok := component.Details["BuildTime"]
|
||||
if ok {
|
||||
component.Details["BuildTime"] = reformatDate(buildTime)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !foundEngine {
|
||||
vd.Server.Components = append(vd.Server.Components, system.ComponentVersion{
|
||||
Name: "Engine",
|
||||
Version: sv.Version,
|
||||
Details: map[string]string{
|
||||
"ApiVersion": sv.APIVersion,
|
||||
"MinAPIVersion": sv.MinAPIVersion,
|
||||
"Os": sv.Os,
|
||||
"Arch": sv.Arch,
|
||||
},
|
||||
})
|
||||
}
|
||||
vd.Server = newServerVersion(sv)
|
||||
}
|
||||
if err2 := prettyPrintVersion(dockerCli.Out(), vd, tmpl); err2 != nil && err == nil {
|
||||
if err2 := prettyPrintVersion(dockerCLI.Out(), vd, tmpl); err2 != nil && err == nil {
|
||||
err = err2
|
||||
}
|
||||
return err
|
||||
|
||||
@@ -47,8 +47,18 @@ func TestVersionFormat(t *testing.T) {
|
||||
BuildTime: "Wed May 30 22:21:05 2018",
|
||||
Context: "my-context",
|
||||
},
|
||||
Server: &client.ServerVersionResult{
|
||||
Platform: client.PlatformInfo{Name: "Docker Enterprise Edition (EE) 2.0"},
|
||||
Server: &serverVersion{
|
||||
Platform: client.PlatformInfo{Name: "Docker Enterprise Edition (EE) 2.0"},
|
||||
Version: "18.99.5-ce",
|
||||
APIVersion: "1.30",
|
||||
MinAPIVersion: "1.12",
|
||||
Os: "linux",
|
||||
Arch: "amd64",
|
||||
GitCommit: "64ddfa6",
|
||||
GoVersion: "go1.8.7",
|
||||
KernelVersion: "v1.0.0",
|
||||
Experimental: false,
|
||||
BuildTime: "2018-07-09T22:38:38.000000000+00:00",
|
||||
Components: []system.ComponentVersion{
|
||||
{
|
||||
Name: "Engine",
|
||||
|
||||
@@ -4,14 +4,15 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"io"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/cli/streams"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/spf13/cobra"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
)
|
||||
|
||||
func setupCobraCommands() (*cobra.Command, *cobra.Command, *cobra.Command) {
|
||||
@@ -139,7 +140,7 @@ func TestStdioAttributes(t *testing.T) {
|
||||
cli.Out().SetIsTerminal(tc.stdoutTty)
|
||||
actual := stdioAttributes(cli)
|
||||
|
||||
assert.Check(t, reflect.DeepEqual(actual, tc.expected))
|
||||
assert.Check(t, is.DeepEqual(actual, tc.expected, cmpopts.EquateComparable(attribute.Value{})))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -179,7 +180,7 @@ func TestAttributesFromError(t *testing.T) {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
actual := attributesFromError(tc.err)
|
||||
assert.Check(t, reflect.DeepEqual(actual, tc.expected))
|
||||
assert.Check(t, is.DeepEqual(actual, tc.expected, cmpopts.EquateComparable(attribute.Value{})))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.24
|
||||
|
||||
package volume
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"maps"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -124,10 +127,10 @@ func TestVolumeCreateWithFlags(t *testing.T) {
|
||||
if options.Driver != expectedDriver {
|
||||
return client.VolumeCreateResult{}, fmt.Errorf("expected driver %q, got %q", expectedDriver, options.Driver)
|
||||
}
|
||||
if !reflect.DeepEqual(options.DriverOpts, expectedOpts) {
|
||||
if !maps.Equal(options.DriverOpts, expectedOpts) {
|
||||
return client.VolumeCreateResult{}, fmt.Errorf("expected drivers opts %v, got %v", expectedOpts, options.DriverOpts)
|
||||
}
|
||||
if !reflect.DeepEqual(options.Labels, expectedLabels) {
|
||||
if !maps.Equal(options.Labels, expectedLabels) {
|
||||
return client.VolumeCreateResult{}, fmt.Errorf("expected labels %v, got %v", expectedLabels, options.Labels)
|
||||
}
|
||||
return client.VolumeCreateResult{
|
||||
|
||||
@@ -28,7 +28,7 @@ import (
|
||||
"github.com/moby/moby/api/types/network"
|
||||
"github.com/moby/moby/client/pkg/versions"
|
||||
"github.com/sirupsen/logrus"
|
||||
"gopkg.in/yaml.v3"
|
||||
"go.yaml.in/yaml/v3"
|
||||
)
|
||||
|
||||
// Options supported by Load
|
||||
@@ -322,26 +322,26 @@ type Transformer struct {
|
||||
|
||||
func createTransformHook(additionalTransformers ...Transformer) mapstructure.DecodeHookFuncType {
|
||||
transforms := map[reflect.Type]func(any) (any, error){
|
||||
reflect.TypeOf(types.External{}): transformExternal,
|
||||
reflect.TypeOf(types.HealthCheckTest{}): transformHealthCheckTest,
|
||||
reflect.TypeOf(types.ShellCommand{}): transformShellCommand,
|
||||
reflect.TypeOf(types.StringList{}): transformStringList,
|
||||
reflect.TypeOf(map[string]string{}): transformMapStringString,
|
||||
reflect.TypeOf(types.UlimitsConfig{}): transformUlimits,
|
||||
reflect.TypeOf(types.UnitBytes(0)): transformSize,
|
||||
reflect.TypeOf([]types.ServicePortConfig{}): transformServicePort,
|
||||
reflect.TypeOf(types.ServiceSecretConfig{}): transformStringSourceMap,
|
||||
reflect.TypeOf(types.ServiceConfigObjConfig{}): transformStringSourceMap,
|
||||
reflect.TypeOf(types.StringOrNumberList{}): transformStringOrNumberList,
|
||||
reflect.TypeOf(map[string]*types.ServiceNetworkConfig{}): transformServiceNetworkMap,
|
||||
reflect.TypeOf(types.Mapping{}): transformMappingOrListFunc("=", false),
|
||||
reflect.TypeOf(types.MappingWithEquals{}): transformMappingOrListFunc("=", true),
|
||||
reflect.TypeOf(types.Labels{}): transformMappingOrListFunc("=", false),
|
||||
reflect.TypeOf(types.MappingWithColon{}): transformMappingOrListFunc(":", false),
|
||||
reflect.TypeOf(types.HostsList{}): transformHostsList,
|
||||
reflect.TypeOf(types.ServiceVolumeConfig{}): transformServiceVolumeConfig,
|
||||
reflect.TypeOf(types.BuildConfig{}): transformBuildConfig,
|
||||
reflect.TypeOf(types.Duration(0)): transformStringToDuration,
|
||||
reflect.TypeFor[types.External](): transformExternal,
|
||||
reflect.TypeFor[types.HealthCheckTest](): transformHealthCheckTest,
|
||||
reflect.TypeFor[types.ShellCommand](): transformShellCommand,
|
||||
reflect.TypeFor[types.StringList](): transformStringList,
|
||||
reflect.TypeFor[map[string]string](): transformMapStringString,
|
||||
reflect.TypeFor[types.UlimitsConfig](): transformUlimits,
|
||||
reflect.TypeFor[types.UnitBytes](): transformSize,
|
||||
reflect.TypeFor[[]types.ServicePortConfig](): transformServicePort,
|
||||
reflect.TypeFor[types.ServiceSecretConfig](): transformStringSourceMap,
|
||||
reflect.TypeFor[types.ServiceConfigObjConfig](): transformStringSourceMap,
|
||||
reflect.TypeFor[types.StringOrNumberList](): transformStringOrNumberList,
|
||||
reflect.TypeFor[map[string]*types.ServiceNetworkConfig](): transformServiceNetworkMap,
|
||||
reflect.TypeFor[types.Mapping](): transformMappingOrListFunc("=", false),
|
||||
reflect.TypeFor[types.MappingWithEquals](): transformMappingOrListFunc("=", true),
|
||||
reflect.TypeFor[types.Labels](): transformMappingOrListFunc("=", false),
|
||||
reflect.TypeFor[types.MappingWithColon](): transformMappingOrListFunc(":", false),
|
||||
reflect.TypeFor[types.HostsList](): transformHostsList,
|
||||
reflect.TypeFor[types.ServiceVolumeConfig](): transformServiceVolumeConfig,
|
||||
reflect.TypeFor[types.BuildConfig](): transformBuildConfig,
|
||||
reflect.TypeFor[types.Duration](): transformStringToDuration,
|
||||
}
|
||||
|
||||
for _, transformer := range additionalTransformers {
|
||||
@@ -918,8 +918,9 @@ var transformStringToDuration TransformerFunc = func(value any) (any, error) {
|
||||
}
|
||||
|
||||
func toServicePortConfigs(value string) ([]any, error) {
|
||||
var portConfigs []any
|
||||
|
||||
// short syntax ([ip:]public:private[/proto])
|
||||
//
|
||||
// TODO(thaJeztah): we need an equivalent that handles the "ip-address" part without depending on the nat package.
|
||||
ports, portBindings, err := nat.ParsePortSpecs([]string{value})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -931,6 +932,7 @@ func toServicePortConfigs(value string) ([]any, error) {
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
var portConfigs []any
|
||||
for _, key := range keys {
|
||||
// Reuse ConvertPortToPortConfig so that it is consistent
|
||||
port, err := network.ParsePort(key)
|
||||
|
||||
@@ -333,7 +333,7 @@ func TestInvalidTopLevelObjectType(t *testing.T) {
|
||||
|
||||
func TestNonStringKeys(t *testing.T) {
|
||||
// FIXME(thaJeztah): opkg.in/yaml.v3, which always unmarshals to a map[string]any, so we cannot produce a customized error for invalid types.
|
||||
t.Skip("not supported by gopkg.in/yaml.v3, which always unmarshals to a map[string]any")
|
||||
t.Skip("not supported by go.yaml.in/yaml/v3, which always unmarshals to a map[string]any")
|
||||
_, err := loadYAML(`
|
||||
version: "3"
|
||||
123:
|
||||
|
||||
+10
-10
@@ -56,15 +56,15 @@ func mergeServices(base, override []types.ServiceConfig) ([]types.ServiceConfig,
|
||||
overrideServices := mapByName(override)
|
||||
specials := &specials{
|
||||
m: map[reflect.Type]func(dst, src reflect.Value) error{
|
||||
reflect.TypeOf(&types.LoggingConfig{}): safelyMerge(mergeLoggingConfig),
|
||||
reflect.TypeOf([]types.ServicePortConfig{}): mergeSlice(toServicePortConfigsMap, toServicePortConfigsSlice),
|
||||
reflect.TypeOf([]types.ServiceSecretConfig{}): mergeSlice(toServiceSecretConfigsMap, toServiceSecretConfigsSlice),
|
||||
reflect.TypeOf([]types.ServiceConfigObjConfig{}): mergeSlice(toServiceConfigObjConfigsMap, toSServiceConfigObjConfigsSlice),
|
||||
reflect.TypeOf(&types.UlimitsConfig{}): mergeUlimitsConfig,
|
||||
reflect.TypeOf([]types.ServiceVolumeConfig{}): mergeSlice(toServiceVolumeConfigsMap, toServiceVolumeConfigsSlice),
|
||||
reflect.TypeOf(types.ShellCommand{}): mergeShellCommand,
|
||||
reflect.TypeOf(&types.ServiceNetworkConfig{}): mergeServiceNetworkConfig,
|
||||
reflect.PointerTo(reflect.TypeOf(uint64(1))): mergeUint64,
|
||||
reflect.PointerTo(reflect.TypeFor[types.LoggingConfig]()): safelyMerge(mergeLoggingConfig),
|
||||
reflect.TypeFor[[]types.ServicePortConfig](): mergeSlice(toServicePortConfigsMap, toServicePortConfigsSlice),
|
||||
reflect.TypeFor[[]types.ServiceSecretConfig](): mergeSlice(toServiceSecretConfigsMap, toServiceSecretConfigsSlice),
|
||||
reflect.TypeFor[[]types.ServiceConfigObjConfig](): mergeSlice(toServiceConfigObjConfigsMap, toSServiceConfigObjConfigsSlice),
|
||||
reflect.PointerTo(reflect.TypeFor[types.UlimitsConfig]()): mergeUlimitsConfig,
|
||||
reflect.TypeFor[[]types.ServiceVolumeConfig](): mergeSlice(toServiceVolumeConfigsMap, toServiceVolumeConfigsSlice),
|
||||
reflect.TypeFor[types.ShellCommand](): mergeShellCommand,
|
||||
reflect.PointerTo(reflect.TypeFor[types.ServiceNetworkConfig]()): mergeServiceNetworkConfig,
|
||||
reflect.PointerTo(reflect.TypeFor[uint64]()): mergeUint64,
|
||||
},
|
||||
}
|
||||
for name, overrideService := range overrideServices {
|
||||
@@ -77,7 +77,7 @@ func mergeServices(base, override []types.ServiceConfig) ([]types.ServiceConfig,
|
||||
}
|
||||
baseServices[name] = overrideService
|
||||
}
|
||||
services := []types.ServiceConfig{}
|
||||
services := make([]types.ServiceConfig, 0, len(baseServices))
|
||||
for _, baseService := range baseServices {
|
||||
services = append(services, baseService)
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
"go.yaml.in/yaml/v3"
|
||||
"gotest.tools/v3/assert"
|
||||
"gotest.tools/v3/golden"
|
||||
)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package connhelper
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"gotest.tools/v3/assert"
|
||||
@@ -27,7 +26,8 @@ func TestSSHFlags(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
assert.DeepEqual(t, addSSHTimeout(tc.in), tc.out)
|
||||
result := addSSHTimeout(tc.in)
|
||||
assert.DeepEqual(t, result, tc.out)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,9 +57,7 @@ func TestDisablePseudoTerminalAllocation(t *testing.T) {
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
result := disablePseudoTerminalAllocation(tc.sshFlags)
|
||||
if !reflect.DeepEqual(result, tc.expected) {
|
||||
t.Errorf("expected %v, got %v", tc.expected, result)
|
||||
}
|
||||
assert.DeepEqual(t, result, tc.expected)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,7 +134,7 @@ func (ep *Endpoint) ClientOpts() ([]client.Opt, error) {
|
||||
}
|
||||
}
|
||||
|
||||
result = append(result, client.WithVersionFromEnv(), client.WithAPIVersionNegotiation())
|
||||
result = append(result, client.WithAPIVersionFromEnv())
|
||||
return result, nil
|
||||
}
|
||||
|
||||
|
||||
+25
-25
@@ -1,23 +1,23 @@
|
||||
module github.com/docker/cli/cmd/docker-trust
|
||||
|
||||
go 1.23.0
|
||||
go 1.24.0
|
||||
|
||||
require (
|
||||
github.com/containerd/errdefs v1.0.0
|
||||
github.com/distribution/reference v0.6.0
|
||||
github.com/docker/cli v29.0.0-rc.2+incompatible
|
||||
github.com/docker/cli v29.0.4+incompatible
|
||||
github.com/docker/cli-docs-tool v0.10.0
|
||||
github.com/docker/distribution v2.8.3+incompatible
|
||||
github.com/docker/go-connections v0.6.0
|
||||
github.com/fvbommel/sortorder v1.1.0
|
||||
github.com/moby/moby/api v1.52.0-beta.4
|
||||
github.com/moby/moby/client v0.1.0-beta.3
|
||||
github.com/moby/moby/api v1.52.0
|
||||
github.com/moby/moby/client v0.1.0
|
||||
github.com/opencontainers/go-digest v1.0.0
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/spf13/cobra v1.10.1
|
||||
github.com/spf13/pflag v1.0.10
|
||||
github.com/theupdateframework/notary v0.7.1-0.20210315103452-bf96a202a09a
|
||||
go.opentelemetry.io/otel v1.35.0
|
||||
go.opentelemetry.io/otel v1.38.0
|
||||
gotest.tools/v3 v3.5.2
|
||||
)
|
||||
|
||||
@@ -25,7 +25,7 @@ require (
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/cloudflare/cfssl v1.6.4 // indirect
|
||||
github.com/containerd/errdefs/pkg v0.3.0 // indirect
|
||||
@@ -36,12 +36,12 @@ require (
|
||||
github.com/docker/go-metrics v0.0.1 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/gorilla/mux v1.8.1 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.17 // indirect
|
||||
github.com/miekg/pkcs11 v1.1.1 // indirect
|
||||
@@ -58,23 +58,23 @@ require (
|
||||
github.com/prometheus/procfs v0.15.1 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/metric v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.35.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.5.0 // indirect
|
||||
golang.org/x/crypto v0.39.0 // indirect
|
||||
golang.org/x/net v0.39.0 // indirect
|
||||
golang.org/x/sys v0.33.0 // indirect
|
||||
golang.org/x/term v0.32.0 // indirect
|
||||
golang.org/x/text v0.26.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a // indirect
|
||||
google.golang.org/grpc v1.72.2 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.38.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.7.1 // indirect
|
||||
golang.org/x/crypto v0.45.0 // indirect
|
||||
golang.org/x/net v0.47.0 // indirect
|
||||
golang.org/x/sys v0.38.0 // indirect
|
||||
golang.org/x/term v0.37.0 // indirect
|
||||
golang.org/x/text v0.31.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 // indirect
|
||||
google.golang.org/grpc v1.75.0 // indirect
|
||||
google.golang.org/protobuf v1.36.9 // indirect
|
||||
k8s.io/klog/v2 v2.90.1 // indirect
|
||||
)
|
||||
|
||||
+52
-50
@@ -21,8 +21,8 @@ github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b h1:otBG+dV+YK+Soembj
|
||||
github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50=
|
||||
github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 h1:nvj0OLI3YqYXer/kZD8Ri1aaunCxIEsOst1BVJswV0o=
|
||||
github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
|
||||
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA=
|
||||
@@ -46,8 +46,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
|
||||
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/docker/cli v29.0.0-rc.2+incompatible h1:eT9VoGWPytEDUDQb/mF2sSOdkWShUwtAeV/RzenY/9w=
|
||||
github.com/docker/cli v29.0.0-rc.2+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/cli v29.0.4+incompatible h1:mffN/hPqaI39vx/4QiSkdldHeM0rP1ZZBIXRUOPI5+I=
|
||||
github.com/docker/cli v29.0.4+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/cli-docs-tool v0.10.0 h1:bOD6mKynPQgojQi3s2jgcUWGp/Ebqy1SeCr9VfKQLLU=
|
||||
github.com/docker/cli-docs-tool v0.10.0/go.mod h1:5EM5zPnT2E7yCLERZmrDA234Vwn09fzRHP4aX1qwp1U=
|
||||
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
@@ -79,8 +79,8 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
@@ -108,8 +108,8 @@ github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
|
||||
github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 h1:e9Rjr40Z98/clHv5Yg79Is0NtosR5LXRvdr7o/6NwbA=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1/go.mod h1:tIxuGz/9mpox++sgp9fJjHO0+q1X9/UOWd798aAm22M=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs=
|
||||
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8=
|
||||
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
@@ -154,10 +154,10 @@ github.com/mitchellh/mapstructure v1.0.0 h1:vVpGvMXJPqSDh2VYHF7gsfQj8Ncx+Xw5Y1KH
|
||||
github.com/mitchellh/mapstructure v1.0.0/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||
github.com/moby/moby/api v1.52.0-beta.4 h1:p05ztW787RcwcQx8YFcda1sb0ExqxDLHTjBQA3Jq6BY=
|
||||
github.com/moby/moby/api v1.52.0-beta.4/go.mod h1:v0K/motq8oWmx+rtApG1rBTIpQ8KUONUjpf+U73gags=
|
||||
github.com/moby/moby/client v0.1.0-beta.3 h1:y/ed2VpRmW8As56TFOaXF2zVSli38IS032zAAGt6kcI=
|
||||
github.com/moby/moby/client v0.1.0-beta.3/go.mod h1:dvvUX065yfMkFXvPk14AXVz8wmew2MdeXJ/rcYEPr7g=
|
||||
github.com/moby/moby/api v1.52.0 h1:00BtlJY4MXkkt84WhUZPRqt5TvPbgig2FZvTbe3igYg=
|
||||
github.com/moby/moby/api v1.52.0/go.mod h1:8mb+ReTlisw4pS6BRzCMts5M49W5M7bKt1cJy/YbAqc=
|
||||
github.com/moby/moby/client v0.1.0 h1:nt+hn6O9cyJQqq5UWnFGqsZRTS/JirUqzPjEl0Bdc/8=
|
||||
github.com/moby/moby/client v0.1.0/go.mod h1:O+/tw5d4a1Ha/ZA/tPxIZJapJRUS6LNZ1wiVRxYHyUE=
|
||||
github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
|
||||
github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs=
|
||||
github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
|
||||
@@ -242,8 +242,8 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/theupdateframework/notary v0.7.1-0.20210315103452-bf96a202a09a h1:tlJ7tGUHvcvL1v3yR6NcCc9nOqh2L+CG6HWrYQtwzQ0=
|
||||
github.com/theupdateframework/notary v0.7.1-0.20210315103452-bf96a202a09a/go.mod h1:Y94A6rPp2OwNfP/7vmf8O2xx2IykP8pPXQ1DLouGnEw=
|
||||
github.com/weppos/publicsuffix-go v0.15.1-0.20210511084619-b1f36a2d6c0b h1:FsyNrX12e5BkplJq7wKOLk0+C6LZ+KGXvuEcKUYm5ss=
|
||||
@@ -254,26 +254,26 @@ github.com/zmap/zlint/v3 v3.1.0 h1:WjVytZo79m/L1+/Mlphl09WBob6YTGljN5IGWZFpAv0=
|
||||
github.com/zmap/zlint/v3 v3.1.0/go.mod h1:L7t8s3sEKkb0A2BxGy1IWrxt1ZATa1R4QfJZaQOD3zU=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ=
|
||||
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
|
||||
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.35.0 h1:QcFwRrZLc82r8wODjvyCbP7Ifp3UANaBSmhDSFjnqSc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.35.0/go.mod h1:CXIWhUomyWBG/oY2/r/kLp6K/cmx9e/7DLpBuuGdLCA=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 h1:m639+BofXTvcY1q8CGs4ItwQarYtJPOWmVobfM1HpVI=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0/go.mod h1:LjReUci/F4BUyv+y4dwnq3h/26iNOeC3wAIqgvTIZVo=
|
||||
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
|
||||
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
|
||||
go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
|
||||
go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=
|
||||
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
|
||||
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
|
||||
go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4=
|
||||
go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg=
|
||||
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
|
||||
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 h1:vl9obrcoWVKp/lwl8tRE33853I8Xru9HFbw/skNeLs8=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0/go.mod h1:GAXRxmLJcVM3u22IjTg74zWBrRCKq8BnOqUVLodpcpw=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk=
|
||||
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
|
||||
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
|
||||
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
|
||||
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
|
||||
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
|
||||
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
|
||||
go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4=
|
||||
go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
@@ -281,14 +281,14 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
|
||||
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
|
||||
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
|
||||
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
|
||||
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
|
||||
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
|
||||
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -302,22 +302,24 @@ golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
|
||||
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
|
||||
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
|
||||
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
|
||||
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a h1:nwKuGPlUAt+aR+pcrkfFRrTU1BVrSmYyYMxYbUIVHr0=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a/go.mod h1:3kWAYMk1I75K4vykHtKt2ycnOgpA6974V7bREqbsenU=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ=
|
||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 h1:BIRfGDEjiHRrk0QKZe3Xv2ieMhtgRGeLcZQ0mIVn4EY=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5/go.mod h1:j3QtIyytwqGr1JUDtYXwtMXWPKsEa5LtzIFN1Wn5WvE=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 h1:eaY8u2EuxbRv7c3NiGK0/NedzVsCcV6hDuU5qPX5EGE=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5/go.mod h1:M4/wBTSeyLxupu3W3tJtOgB14jILAS/XWPSSa3TAlJc=
|
||||
google.golang.org/grpc v1.0.5/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
google.golang.org/grpc v1.72.2 h1:TdbGzwb82ty4OusHWepvFWGLgIbNo1/SUynEN0ssqv8=
|
||||
google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
|
||||
google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4=
|
||||
google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
|
||||
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
|
||||
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
|
||||
|
||||
@@ -315,7 +315,7 @@ complete -c docker -A -f -n '__fish_seen_subcommand_from history' -a '(__fish_pr
|
||||
|
||||
# images
|
||||
complete -c docker -f -n '__fish_docker_no_subcommand' -a images -d 'List images'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from images' -s a -l all -d 'Show all images (default hides intermediate images)'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from images' -s a -l all -d 'Show all images (default hides intermediate and dangling images)'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from images' -l digests -d 'Show digests'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from images' -s f -l filter -d 'Filter output based on conditions provided'
|
||||
complete -c docker -A -f -n '__fish_seen_subcommand_from images' -l format -d 'Format the output using the given Go template'
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
variable "GO_VERSION" {
|
||||
default = "1.25.4"
|
||||
default = "1.25.6"
|
||||
}
|
||||
variable "VERSION" {
|
||||
default = ""
|
||||
|
||||
+1
-1
@@ -109,7 +109,7 @@ mddocs: build_docker_image ## generate markdown files from go source
|
||||
|
||||
.PHONY: yamldocs
|
||||
yamldocs: build_docker_image ## generate documentation YAML files consumed by docs repo
|
||||
$(DOCKER_RUN) -it $(DEV_DOCKER_IMAGE_NAME) make yamldocs
|
||||
$(DOCKER_RUN) $(DEV_DOCKER_IMAGE_NAME) make yamldocs
|
||||
|
||||
.PHONY: test ## run unit and e2e tests
|
||||
test: test-unit test-e2e
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
ARG GO_VERSION=1.25.4
|
||||
ARG GO_VERSION=1.25.6
|
||||
|
||||
# ALPINE_VERSION sets the version of the alpine base image to use, including for the golang image.
|
||||
# It must be a supported tag in the docker.io/library/alpine image repository
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
ARG GO_VERSION=1.25.4
|
||||
ARG GO_VERSION=1.25.6
|
||||
|
||||
# ALPINE_VERSION sets the version of the alpine base image to use, including for the golang image.
|
||||
# It must be a supported tag in the docker.io/library/alpine image repository
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
ARG GO_VERSION=1.25.4
|
||||
ARG GO_VERSION=1.25.6
|
||||
|
||||
# ALPINE_VERSION sets the version of the alpine base image to use, including for the golang image.
|
||||
# It must be a supported tag in the docker.io/library/alpine image repository
|
||||
|
||||
@@ -1282,7 +1282,9 @@ connect to services running on the host machine.
|
||||
|
||||
It's conventional to use `host.docker.internal` as the hostname referring to
|
||||
`host-gateway`. Docker Desktop automatically resolves this hostname, see
|
||||
[Explore networking features](https://docs.docker.com/desktop/features/networking/networking-how-tos/#i-want-to-connect-from-a-container-to-a-service-on-the-host).
|
||||
[Explore networking how-tos on Docker Desktop](https://docs.docker.com/desktop/features/networking/networking-how-tos/#connect-a-container-to-a-service-on-the-host)
|
||||
and
|
||||
[Configure host gateway IP](https://docs.docker.com/reference/cli/dockerd/#configure-host-gateway-ip).
|
||||
|
||||
The following example shows how the special `host-gateway` value works. The
|
||||
example runs an HTTP server that serves a file from host to container over the
|
||||
|
||||
@@ -114,8 +114,7 @@ Options:
|
||||
|
||||
### Environment variables
|
||||
|
||||
The following list of environment variables are supported by the `docker` command
|
||||
line:
|
||||
The following environment variables control the behavior of the `docker` command-line client:
|
||||
|
||||
| Variable | Description |
|
||||
| :---------------------------- |:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
@@ -130,6 +129,8 @@ line:
|
||||
| `DOCKER_TLS` | Enable TLS for connections made by the `docker` CLI (equivalent of the `--tls` command-line option). Set to a non-empty value to enable TLS. Note that TLS is enabled automatically if any of the other TLS options are set. |
|
||||
| `DOCKER_TLS_VERIFY` | When set Docker uses TLS and verifies the remote. This variable is used both by the `docker` CLI and the [`dockerd` daemon](https://docs.docker.com/reference/cli/dockerd/) |
|
||||
| `BUILDKIT_PROGRESS` | Set type of progress output (`auto`, `plain`, `tty`, `rawjson`) when [building](https://docs.docker.com/reference/cli/docker/image/build/) with [BuildKit backend](https://docs.docker.com/build/buildkit/). Use plain to show container output (default `auto`). |
|
||||
| `NO_COLOR` | Disable any ANSI escape codes in the output in accordance with https://no-color.org/
|
||||
|
|
||||
|
||||
Because Docker is developed using Go, you can also use any environment
|
||||
variables used by the Go runtime. In particular, you may find these useful:
|
||||
@@ -317,8 +318,8 @@ be set for each environment:
|
||||
|
||||
These settings are used to configure proxy settings for containers only, and not
|
||||
used as proxy settings for the `docker` CLI or the `dockerd` daemon. Refer to the
|
||||
[environment variables](#environment-variables) and [HTTP/HTTPS proxy](https://docs.docker.com/engine/daemon/proxy/#httphttps-proxy)
|
||||
sections for configuring proxy settings for the CLI and daemon.
|
||||
[environment variables](#environment-variables) section and the [Daemon proxy configuration](https://docs.docker.com/engine/daemon/proxy/)
|
||||
guide for configuring proxy settings for the CLI and daemon.
|
||||
|
||||
> [!WARNING]
|
||||
> Proxy settings may contain sensitive information (for example, if the proxy
|
||||
|
||||
@@ -11,7 +11,7 @@ List images
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:---------------------------------------|:---------|:--------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `-a`, `--all` | `bool` | | Show all images (default hides intermediate images) |
|
||||
| `-a`, `--all` | `bool` | | Show all images (default hides intermediate and dangling images) |
|
||||
| [`--digests`](#digests) | `bool` | | Show digests |
|
||||
| [`-f`](#filter), [`--filter`](#filter) | `filter` | | Filter output based on conditions provided |
|
||||
| [`--format`](#format) | `string` | | Format output using a custom template:<br>'table': Print output in table format with column headers (default)<br>'table TEMPLATE': Print output in table format using the given Go template<br>'json': Print in JSON format<br>'TEMPLATE': Print output using the given Go template.<br>Refer to https://docs.docker.com/go/formatting/ for more information about formatting output with templates |
|
||||
@@ -32,6 +32,9 @@ decrease disk usage, and speed up `docker build` by
|
||||
allowing each step to be cached. These intermediate layers are not shown
|
||||
by default.
|
||||
|
||||
Untagged (dangling) images are also hidden by default. Use the `-a` (`--all`)
|
||||
flag to show intermediate layers and dangling images.
|
||||
|
||||
The `SIZE` is the cumulative space taken up by the image and all
|
||||
its parent images. This is also the disk space used by the contents of the
|
||||
Tar file created when you `docker save` an image.
|
||||
|
||||
@@ -11,7 +11,7 @@ List images
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:-----------------|:---------|:--------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `-a`, `--all` | `bool` | | Show all images (default hides intermediate images) |
|
||||
| `-a`, `--all` | `bool` | | Show all images (default hides intermediate and dangling images) |
|
||||
| `--digests` | `bool` | | Show digests |
|
||||
| `-f`, `--filter` | `filter` | | Filter output based on conditions provided |
|
||||
| `--format` | `string` | | Format output using a custom template:<br>'table': Print output in table format with column headers (default)<br>'table TEMPLATE': Print output in table format using the given Go template<br>'json': Print in JSON format<br>'TEMPLATE': Print output using the given Go template.<br>Refer to https://docs.docker.com/go/formatting/ for more information about formatting output with templates |
|
||||
|
||||
@@ -876,7 +876,7 @@ You can view the configured CDI specification directories using the `docker info
|
||||
|
||||
#### Disable CDI devices
|
||||
|
||||
The feature in enabled by default. To disable it, use the `cdi` options in the `deamon.json` file:
|
||||
The feature in enabled by default. To disable it, use the `cdi` options in the `daemon.json` file:
|
||||
|
||||
```json
|
||||
"features": {
|
||||
@@ -1076,11 +1076,11 @@ The following is a full example of the allowed configuration options on Linux:
|
||||
"builder": {
|
||||
"gc": {
|
||||
"enabled": true,
|
||||
"defaultKeepStorage": "10GB",
|
||||
"defaultReservedSpace": "10GB",
|
||||
"policy": [
|
||||
{ "keepStorage": "10GB", "filter": ["unused-for=2200h"] },
|
||||
{ "keepStorage": "50GB", "filter": ["unused-for=3300h"] },
|
||||
{ "keepStorage": "100GB", "all": true }
|
||||
{ "maxUsedSpace": "512MB", "keepDuration": "48h", "filter": [ "type=source.local" ] },
|
||||
{ "reservedSpace": "10GB", "maxUsedSpace": "100GB", "keepDuration": "1440h" },
|
||||
{ "reservedSpace": "50GB", "minFreeSpace": "20GB", "maxUsedSpace": "200GB", "all": true }
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
@@ -2,14 +2,14 @@ package jsonstream
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/docker/cli/cli/streams"
|
||||
"github.com/moby/moby/api/types/jsonstream"
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/moby/moby/client/pkg/progress"
|
||||
"github.com/moby/moby/client/pkg/streamformatter"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
@@ -23,21 +23,19 @@ func TestDisplay(t *testing.T) {
|
||||
})
|
||||
|
||||
go func() {
|
||||
enc := json.NewEncoder(server)
|
||||
id := test.RandomID()[:12] // short-ID
|
||||
progressOutput := streamformatter.NewJSONProgressOutput(server, true)
|
||||
for i := 0; i < 100; i++ {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
assert.NilError(t, server.Close(), "failed to close jsonmessage server")
|
||||
return
|
||||
default:
|
||||
err := enc.Encode(JSONMessage{
|
||||
Status: "Downloading",
|
||||
ID: fmt.Sprintf("id-%d", i),
|
||||
Progress: &jsonstream.Progress{
|
||||
Current: int64(i),
|
||||
Total: 100,
|
||||
Start: 0,
|
||||
},
|
||||
err := progressOutput.WriteProgress(progress.Progress{
|
||||
ID: id,
|
||||
Message: "Downloading",
|
||||
Current: int64(i),
|
||||
Total: 100,
|
||||
})
|
||||
if err != nil {
|
||||
break
|
||||
|
||||
@@ -216,7 +216,7 @@ func continueOnError(err error) bool {
|
||||
}
|
||||
|
||||
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)
|
||||
endpoints, err := allEndpoints(ctx, namedRef, c.insecureRegistry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -271,11 +271,11 @@ func (c *client) iterateEndpoints(ctx context.Context, namedRef reference.Named,
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return newNotFoundError(namedRef.String())
|
||||
return notFoundError{errors.New("no such manifest: " + namedRef.String())}
|
||||
}
|
||||
|
||||
// allEndpoints returns a list of endpoints ordered by priority (v2, http).
|
||||
func allEndpoints(namedRef reference.Named, insecure bool) ([]registry.APIEndpoint, error) {
|
||||
func allEndpoints(ctx context.Context, namedRef reference.Named, insecure bool) ([]registry.APIEndpoint, error) {
|
||||
var serviceOpts registry.ServiceOptions
|
||||
if insecure {
|
||||
logrus.Debugf("allowing insecure registry for: %s", reference.Domain(namedRef))
|
||||
@@ -285,22 +285,11 @@ func allEndpoints(namedRef reference.Named, insecure bool) ([]registry.APIEndpoi
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
endpoints, err := registryService.Endpoints(context.TODO(), reference.Domain(namedRef))
|
||||
endpoints, err := registryService.Endpoints(ctx, reference.Domain(namedRef))
|
||||
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{ error }
|
||||
|
||||
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() {}
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"github.com/docker/cli/cli/config/credentials"
|
||||
"github.com/docker/cli/cli/config/types"
|
||||
)
|
||||
|
||||
// FakeStore implements a credentials.Store that only acts as an in memory map
|
||||
type FakeStore struct {
|
||||
store map[string]types.AuthConfig
|
||||
eraseFunc func(serverAddress string) error
|
||||
getFunc func(serverAddress string) (types.AuthConfig, error)
|
||||
getAllFunc func() (map[string]types.AuthConfig, error)
|
||||
storeFunc func(authConfig types.AuthConfig) error
|
||||
}
|
||||
|
||||
// NewFakeStore creates a new file credentials store.
|
||||
func NewFakeStore() credentials.Store {
|
||||
return &FakeStore{store: map[string]types.AuthConfig{}}
|
||||
}
|
||||
|
||||
// SetStore is used to overrides Set function
|
||||
func (c *FakeStore) SetStore(store map[string]types.AuthConfig) {
|
||||
c.store = store
|
||||
}
|
||||
|
||||
// SetEraseFunc is used to overrides Erase function
|
||||
func (c *FakeStore) SetEraseFunc(eraseFunc func(string) error) {
|
||||
c.eraseFunc = eraseFunc
|
||||
}
|
||||
|
||||
// SetGetFunc is used to overrides Get function
|
||||
func (c *FakeStore) SetGetFunc(getFunc func(string) (types.AuthConfig, error)) {
|
||||
c.getFunc = getFunc
|
||||
}
|
||||
|
||||
// SetGetAllFunc is used to overrides GetAll function
|
||||
func (c *FakeStore) SetGetAllFunc(getAllFunc func() (map[string]types.AuthConfig, error)) {
|
||||
c.getAllFunc = getAllFunc
|
||||
}
|
||||
|
||||
// SetStoreFunc is used to override Store function
|
||||
func (c *FakeStore) SetStoreFunc(storeFunc func(types.AuthConfig) error) {
|
||||
c.storeFunc = storeFunc
|
||||
}
|
||||
|
||||
// Erase removes the given credentials from the map store
|
||||
func (c *FakeStore) Erase(serverAddress string) error {
|
||||
if c.eraseFunc != nil {
|
||||
return c.eraseFunc(serverAddress)
|
||||
}
|
||||
delete(c.store, serverAddress)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get retrieves credentials for a specific server from the map store.
|
||||
func (c *FakeStore) Get(serverAddress string) (types.AuthConfig, error) {
|
||||
if c.getFunc != nil {
|
||||
return c.getFunc(serverAddress)
|
||||
}
|
||||
return c.store[serverAddress], nil
|
||||
}
|
||||
|
||||
// GetAll returns the key value pairs of ServerAddress => Username
|
||||
func (c *FakeStore) GetAll() (map[string]types.AuthConfig, error) {
|
||||
if c.getAllFunc != nil {
|
||||
return c.getAllFunc()
|
||||
}
|
||||
return c.store, nil
|
||||
}
|
||||
|
||||
// Store saves the given credentials in the map store.
|
||||
func (c *FakeStore) Store(authConfig types.AuthConfig) error {
|
||||
if c.storeFunc != nil {
|
||||
return c.storeFunc(authConfig)
|
||||
}
|
||||
c.store[authConfig.ServerAddress] = authConfig
|
||||
return nil
|
||||
}
|
||||
@@ -28,7 +28,7 @@ func withHeader(header Str) noteOptions {
|
||||
}
|
||||
|
||||
func (o Output) printNoteWithOptions(format string, args []any, opts ...noteOptions) {
|
||||
if o.isTerminal {
|
||||
if !o.noColor {
|
||||
// TODO: Handle all flags
|
||||
format = strings.ReplaceAll(format, "--platform", ColorFlag.Apply("--platform"))
|
||||
}
|
||||
@@ -51,7 +51,7 @@ func (o Output) printNoteWithOptions(format string, args []any, opts ...noteOpti
|
||||
}
|
||||
|
||||
l := line
|
||||
if o.isTerminal {
|
||||
if !o.noColor {
|
||||
l = aec.Italic.Apply(l)
|
||||
}
|
||||
_, _ = fmt.Fprintln(o, l)
|
||||
|
||||
+13
-8
@@ -5,6 +5,7 @@ package tui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/docker/cli/cli/streams"
|
||||
"github.com/morikuni/aec"
|
||||
@@ -12,7 +13,7 @@ import (
|
||||
|
||||
type Output struct {
|
||||
*streams.Out
|
||||
isTerminal bool
|
||||
noColor bool
|
||||
}
|
||||
|
||||
type terminalPrintable interface {
|
||||
@@ -20,24 +21,28 @@ type terminalPrintable interface {
|
||||
}
|
||||
|
||||
func NewOutput(out *streams.Out) Output {
|
||||
noColor := !out.IsTerminal()
|
||||
if os.Getenv("NO_COLOR") != "" {
|
||||
noColor = true
|
||||
}
|
||||
return Output{
|
||||
Out: out,
|
||||
isTerminal: out.IsTerminal(),
|
||||
Out: out,
|
||||
noColor: noColor,
|
||||
}
|
||||
}
|
||||
|
||||
func (o Output) Color(clr aec.ANSI) aec.ANSI {
|
||||
if o.isTerminal {
|
||||
return clr
|
||||
if o.noColor {
|
||||
return ColorNone
|
||||
}
|
||||
return ColorNone
|
||||
return clr
|
||||
}
|
||||
|
||||
func (o Output) Sprint(all ...any) string {
|
||||
var out []any
|
||||
for _, p := range all {
|
||||
if s, ok := p.(terminalPrintable); ok {
|
||||
out = append(out, s.String(o.isTerminal))
|
||||
out = append(out, s.String(!o.noColor))
|
||||
} else {
|
||||
out = append(out, p)
|
||||
}
|
||||
@@ -47,7 +52,7 @@ func (o Output) Sprint(all ...any) string {
|
||||
|
||||
func (o Output) PrintlnWithColor(clr aec.ANSI, args ...any) {
|
||||
msg := o.Sprint(args...)
|
||||
if o.isTerminal {
|
||||
if !o.noColor {
|
||||
msg = clr.Apply(msg)
|
||||
}
|
||||
_, _ = fmt.Fprintln(o.Out, msg)
|
||||
|
||||
+189
-144
@@ -113,12 +113,11 @@ pull** IMAGE, before it starts the container from that image.
|
||||
**-a**, **--attach**=[]
|
||||
Attach to STDIN, STDOUT or STDERR.
|
||||
|
||||
In foreground mode (the default when **-d**
|
||||
is not specified), **docker run** can start the process in the container
|
||||
and attach the console to the process's standard input, output, and standard
|
||||
error. It can even pretend to be a TTY (this is what most commandline
|
||||
executables expect) and pass along signals. The **-a** option can be set for
|
||||
each of stdin, stdout, and stderr.
|
||||
In foreground mode (the default when **-d** is not specified), **docker run**
|
||||
can start the process in the container and attach the console to the process's
|
||||
standard input, output, and standard error. It can even pretend to be a TTY
|
||||
(this is what most commandline executables expect) and pass along signals.
|
||||
The **-a** option can be set for each of stdin, stdout, and stderr.
|
||||
|
||||
**--add-host**=[]
|
||||
Add a custom host-to-IP mapping (host=ip, or host:ip)
|
||||
@@ -141,32 +140,32 @@ each of stdin, stdout, and stderr.
|
||||
CPU shares (relative weight)
|
||||
|
||||
By default, all containers get the same proportion of CPU cycles. This proportion
|
||||
can be modified by changing the container's CPU share weighting relative
|
||||
to the weighting of all other running containers.
|
||||
can be modified by changing the container's CPU share weighting relative
|
||||
to the weighting of all other running containers.
|
||||
|
||||
To modify the proportion from the default of 1024, use the **-c** or **--cpu-shares**
|
||||
flag to set the weighting to 2 or higher.
|
||||
To modify the proportion from the default of 1024, use the **-c** or **--cpu-shares**
|
||||
flag to set the weighting to 2 or higher.
|
||||
|
||||
The proportion will only apply when CPU-intensive processes are running.
|
||||
When tasks in one container are idle, other containers can use the
|
||||
left-over CPU time. The actual amount of CPU time will vary depending on
|
||||
the number of containers running on the system.
|
||||
The proportion will only apply when CPU-intensive processes are running.
|
||||
When tasks in one container are idle, other containers can use the
|
||||
left-over CPU time. The actual amount of CPU time will vary depending on
|
||||
the number of containers running on the system.
|
||||
|
||||
For example, consider three containers, one has a cpu-share of 1024 and
|
||||
two others have a cpu-share setting of 512. When processes in all three
|
||||
containers attempt to use 100% of CPU, the first container would receive
|
||||
50% of the total CPU time. If you add a fourth container with a cpu-share
|
||||
of 1024, the first container only gets 33% of the CPU. The remaining containers
|
||||
receive 16.5%, 16.5% and 33% of the CPU.
|
||||
For example, consider three containers, one has a cpu-share of 1024 and
|
||||
two others have a cpu-share setting of 512. When processes in all three
|
||||
containers attempt to use 100% of CPU, the first container would receive
|
||||
50% of the total CPU time. If you add a fourth container with a cpu-share
|
||||
of 1024, the first container only gets 33% of the CPU. The remaining containers
|
||||
receive 16.5%, 16.5% and 33% of the CPU.
|
||||
|
||||
On a multi-core system, the shares of CPU time are distributed over all CPU
|
||||
cores. Even if a container is limited to less than 100% of CPU time, it can
|
||||
use 100% of each individual CPU core.
|
||||
On a multi-core system, the shares of CPU time are distributed over all CPU
|
||||
cores. Even if a container is limited to less than 100% of CPU time, it can
|
||||
use 100% of each individual CPU core.
|
||||
|
||||
For example, consider a system with more than three cores. If you start one
|
||||
container **{C0}** with **-c=512** running one process, and another container
|
||||
**{C1}** with **-c=1024** running two processes, this can result in the following
|
||||
division of CPU shares:
|
||||
For example, consider a system with more than three cores. If you start one
|
||||
container **{C0}** with **-c=512** running one process, and another container
|
||||
**{C1}** with **-c=1024** running two processes, this can result in the following
|
||||
division of CPU shares:
|
||||
|
||||
PID container CPU CPU share
|
||||
100 {C0} 0 100% of CPU0
|
||||
@@ -186,33 +185,42 @@ division of CPU shares:
|
||||
**""**: (unset) use the daemon's default configuration (**host** on cgroup v1, **private** on cgroup v2)
|
||||
|
||||
**--cgroup-parent**=""
|
||||
Path to cgroups under which the cgroup for the container will be created. If the path is not absolute, the path is considered to be relative to the cgroups path of the init process. Cgroups will be created if they do not already exist.
|
||||
Path to cgroups under which the cgroup for the container will be created.
|
||||
If the path is not absolute, the path is considered to be relative to the
|
||||
cgroups path of the init process. Cgroups will be created if they do not
|
||||
already exist.
|
||||
|
||||
**--cidfile**=""
|
||||
Write the container ID to the file
|
||||
|
||||
**--cpu-count**=*0*
|
||||
Limit the number of CPUs available for execution by the container.
|
||||
|
||||
On Windows Server containers, this is approximated as a percentage of total CPU usage.
|
||||
Limit the number of CPUs available for execution by the container.
|
||||
|
||||
On Windows Server containers, the processor resource controls are mutually exclusive, the order of precedence is CPUCount first, then CPUShares, and CPUPercent last.
|
||||
On Windows Server containers, this is approximated as a percentage of total
|
||||
CPU usage. On Windows Server containers, the processor resource controls
|
||||
are mutually exclusive, the order of precedence is CPUCount first, then
|
||||
CPUShares, and CPUPercent last.
|
||||
|
||||
**--cpu-percent**=*0*
|
||||
Limit the percentage of CPU available for execution by a container running on a Windows daemon.
|
||||
Limit the percentage of CPU available for execution by a container running
|
||||
on a Windows daemon.
|
||||
|
||||
On Windows Server containers, the processor resource controls are mutually exclusive, the order of precedence is CPUCount first, then CPUShares, and CPUPercent last.
|
||||
On Windows Server containers, the processor resource controls are mutually
|
||||
exclusive, the order of precedence is CPUCount first, then CPUShares, and
|
||||
CPUPercent last.
|
||||
|
||||
**--cpu-period**=*0*
|
||||
Limit the CPU CFS (Completely Fair Scheduler) period
|
||||
|
||||
Limit the container's CPU usage. This flag tell the kernel to restrict the container's CPU usage to the period you specify.
|
||||
Limit the container's CPU usage. This flag tell the kernel to restrict the
|
||||
container's CPU usage to the period you specify.
|
||||
|
||||
**--cpuset-cpus**=""
|
||||
CPUs in which to allow execution (0-3, 0,1)
|
||||
|
||||
**--cpuset-mems**=""
|
||||
Memory nodes (MEMs) in which to allow execution (0-3, 0,1). Only effective on NUMA systems.
|
||||
Memory nodes (MEMs) in which to allow execution (0-3, 0,1). Only effective
|
||||
on NUMA systems.
|
||||
|
||||
If you have four memory nodes on your system (0-3), use `--cpuset-mems=0,1`
|
||||
then processes in your Docker container will only use memory from the first
|
||||
@@ -222,55 +230,70 @@ two memory nodes.
|
||||
Limit the CPU CFS (Completely Fair Scheduler) quota
|
||||
|
||||
Limit the container's CPU usage. By default, containers run with the full
|
||||
CPU resource. This flag tell the kernel to restrict the container's CPU usage
|
||||
to the quota you specify.
|
||||
CPU resource. This flag tell the kernel to restrict the container's CPU
|
||||
usage to the quota you specify.
|
||||
|
||||
**--cpu-rt-period**=0
|
||||
Limit the CPU real-time period in microseconds
|
||||
|
||||
Limit the container's Real Time CPU usage. This flag tell the kernel to restrict the container's Real Time CPU usage to the period you specify.
|
||||
Limit the container's Real Time CPU usage. This flag tell the kernel to
|
||||
restrict the container's Real Time CPU usage to the period you specify.
|
||||
|
||||
**--cpu-rt-runtime**=0
|
||||
Limit the CPU real-time runtime in microseconds
|
||||
|
||||
Limit the containers Real Time CPU usage. This flag tells the kernel to limit the amount of time in a given CPU period Real Time tasks may consume. Ex:
|
||||
Period of 1,000,000us and Runtime of 950,000us means that this container could consume 95% of available CPU and leave the remaining 5% to normal priority tasks.
|
||||
Limit the containers Real Time CPU usage. This flag tells the kernel to
|
||||
limit the amount of time in a given CPU period Real Time tasks may consume.
|
||||
For example, a period of 1,000,000us and Runtime of 950,000us means that
|
||||
this container could consume 95% of available CPU and leave the remaining
|
||||
5% to normal priority tasks.
|
||||
|
||||
The sum of all runtimes across containers cannot exceed the amount allotted to the parent cgroup.
|
||||
The sum of all runtimes across containers cannot exceed the amount allotted
|
||||
to the parent cgroup.
|
||||
|
||||
**--cpus**=0.0
|
||||
Number of CPUs. The default is *0.0* which means no limit.
|
||||
|
||||
**-d**, **--detach**=*true*|*false*
|
||||
Detached mode: run the container in the background and print the new container ID. The default is *false*.
|
||||
Detached mode: run the container in the background and print the new
|
||||
container ID. The default is *false*.
|
||||
|
||||
At any time you can run **docker ps** in
|
||||
the other shell to view a list of the running containers. You can reattach to a
|
||||
detached container with **docker attach**.
|
||||
At any time you can run **docker ps** in the other shell to view a list
|
||||
of the running containers. You can reattach to a detached container with
|
||||
**docker attach**.
|
||||
|
||||
When attached in the tty mode, you can detach from the container (and leave it
|
||||
running) using a configurable key sequence. The default sequence is `CTRL-p CTRL-q`.
|
||||
You configure the key sequence using the **--detach-keys** option or a configuration file.
|
||||
See **config-json(5)** for documentation on using a configuration file.
|
||||
When attached in the tty mode, you can detach from the container (and leave
|
||||
it running) using a configurable key sequence. The default sequence is
|
||||
`CTRL-p CTRL-q`.
|
||||
You configure the key sequence using the **--detach-keys** option or a
|
||||
configuration file. See **config-json(5)** for documentation on using a
|
||||
configuration file.
|
||||
|
||||
**--detach-keys**=*key*
|
||||
Override the key sequence for detaching a container; *key* is a single character from the [a-Z] range, or **ctrl**-*value*, where *value* is one of: **a-z**, **@**, **^**, **[**, **,**, or **_**.
|
||||
Override the key sequence for detaching a container; *key* is a single
|
||||
character from the [a-Z] range, or **ctrl**-*value*, where *value* is
|
||||
one of: **a-z**, **@**, **^**, **[**, **,**, or **_**.
|
||||
|
||||
**--device**=*onhost*:*incontainer*[:*mode*]
|
||||
Add a host device *onhost* to the container under the *incontainer* name.
|
||||
Optional *mode* parameter can be used to specify device permissions, it is
|
||||
a combination of **r** (for read), **w** (for write), and **m** (for **mknod**(2)).
|
||||
Optional *mode* parameter can be used to specify device permissions, it is
|
||||
a combination of **r** (for read), **w** (for write), and **m** (for
|
||||
**mknod**(2)).
|
||||
|
||||
For example, **--device=/dev/sdc:/dev/xvdc:rwm** will give a container all
|
||||
permissions for the host device **/dev/sdc**, seen as **/dev/xvdc** inside the container.
|
||||
For example, **--device=/dev/sdc:/dev/xvdc:rwm** will give a container all
|
||||
permissions for the host device **/dev/sdc**, seen as **/dev/xvdc** inside
|
||||
the container.
|
||||
|
||||
**--device-cgroup-rule**="*type* *major*:*minor* *mode*"
|
||||
Add a rule to the cgroup allowed devices list. The rule is expected to be in the format specified in the Linux kernel documentation (Documentation/cgroup-v1/devices.txt):
|
||||
Add a rule to the cgroup allowed devices list. The rule is expected to be
|
||||
in the format specified in the Linux kernel documentation
|
||||
(Documentation/cgroup-v1/devices.txt):
|
||||
- *type*: **a** (all), **c** (char), or **b** (block);
|
||||
- *major* and *minor*: either a number, or __*__ for all;
|
||||
- *mode*: a composition of **r** (read), **w** (write), and **m** (**mknod**(2)).
|
||||
|
||||
Example: **--device-cgroup-rule "c 1:3 mr"**: allow for a character device idendified by **1:3** to be created and read.
|
||||
Example: **--device-cgroup-rule "c 1:3 mr"**: allow for a character device
|
||||
identified by **1:3** to be created and read.
|
||||
|
||||
**--device-read-bps**=[]
|
||||
Limit read rate from a device (e.g. --device-read-bps=/dev/sda:1mb)
|
||||
@@ -285,7 +308,8 @@ permissions for the host device **/dev/sdc**, seen as **/dev/xvdc** inside the c
|
||||
Limit write rate to a device (e.g. --device-write-iops=/dev/sda:1000)
|
||||
|
||||
**--dns-search**=[]
|
||||
Set custom DNS search domains (Use --dns-search=. if you don't wish to set the search domain)
|
||||
Set custom DNS search domains (Use --dns-search=. if you don't wish to set
|
||||
the search domain)
|
||||
|
||||
**--dns-option**=[]
|
||||
Set custom DNS options
|
||||
@@ -293,10 +317,10 @@ permissions for the host device **/dev/sdc**, seen as **/dev/xvdc** inside the c
|
||||
**--dns**=[]
|
||||
Set custom DNS servers
|
||||
|
||||
This option can be used to override the DNS
|
||||
configuration passed to the container. Typically this is necessary when the
|
||||
host DNS configuration is invalid for the container (e.g., 127.0.0.1). When this
|
||||
is the case the **--dns** flags is necessary for every run.
|
||||
This option can be used to override the DNS configuration passed to the
|
||||
container. Typically this is necessary when the host DNS configuration
|
||||
is invalid for the container (e.g., 127.0.0.1). When this is the case
|
||||
the **--dns** flags is necessary for every run.
|
||||
|
||||
**--domainname**=""
|
||||
Container NIS domain name
|
||||
@@ -315,24 +339,24 @@ inside of the container.
|
||||
Overwrite the default ENTRYPOINT of the image
|
||||
|
||||
This option allows you to overwrite the default entrypoint of the image that
|
||||
is set in the Dockerfile. The ENTRYPOINT of an image is similar to a COMMAND
|
||||
because it specifies what executable to run when the container starts, but it is
|
||||
(purposely) more difficult to override. The ENTRYPOINT gives a container its
|
||||
default nature or behavior, so that when you set an ENTRYPOINT you can run the
|
||||
container as if it were that binary, complete with default options, and you can
|
||||
pass in more options via the COMMAND. But, sometimes an operator may want to run
|
||||
something else inside the container, so you can override the default ENTRYPOINT
|
||||
at runtime by using a **--entrypoint** and a string to specify the new
|
||||
ENTRYPOINT.
|
||||
is set in the Dockerfile. The ENTRYPOINT of an image is similar to a COMMAND
|
||||
because it specifies what executable to run when the container starts, but it is
|
||||
(purposely) more difficult to override. The ENTRYPOINT gives a container its
|
||||
default nature or behavior, so that when you set an ENTRYPOINT you can run the
|
||||
container as if it were that binary, complete with default options, and you can
|
||||
pass in more options via the COMMAND. But, sometimes an operator may want to run
|
||||
something else inside the container, so you can override the default ENTRYPOINT
|
||||
at runtime by using a **--entrypoint** and a string to specify the new
|
||||
ENTRYPOINT.
|
||||
|
||||
**--env-file**=[]
|
||||
Read in a line delimited file of environment variables
|
||||
|
||||
**--expose**=[]
|
||||
Expose a port, or a range of ports (e.g. --expose=3300-3310) informs Docker
|
||||
that the container listens on the specified network ports at runtime. Docker
|
||||
uses this information to interconnect containers using links and to set up port
|
||||
redirection on the host system.
|
||||
Expose a port, or a range of ports (e.g. --expose=3300-3310) informs the
|
||||
docker daemon that the container listens on the specified network ports at
|
||||
runtime. The docker daemon uses this information to interconnect containers
|
||||
using links and to set up port redirection on the host system.
|
||||
|
||||
**--group-add**=[]
|
||||
Add additional groups to run as
|
||||
@@ -390,10 +414,10 @@ is `hyperv`. Linux only supports `default`.
|
||||
Kernel memory limit; *S* is an optional suffix which can be one of **b**, **k**, **m**, or **g**.
|
||||
|
||||
Constrains the kernel memory available to a container. If a limit of 0
|
||||
is specified (not using **--kernel-memory**), the container's kernel memory
|
||||
is not limited. If you specify a limit, it may be rounded up to a multiple
|
||||
of the operating system's page size and the value can be very large,
|
||||
millions of trillions.
|
||||
is specified (not using **--kernel-memory**), the container's kernel memory
|
||||
is not limited. If you specify a limit, it may be rounded up to a multiple
|
||||
of the operating system's page size and the value can be very large,
|
||||
millions of trillions.
|
||||
|
||||
**--label-file**=[]
|
||||
Read in a line delimited file of labels
|
||||
@@ -422,24 +446,27 @@ which interface and port to use.
|
||||
Memory limit; *S* is an optional suffix which can be one of **b**, **k**, **m**, or **g**.
|
||||
|
||||
Allows you to constrain the memory available to a container. If the host
|
||||
supports swap memory, then the **-m** memory setting can be larger than physical
|
||||
RAM. If a limit of 0 is specified (not using **-m**), the container's memory is
|
||||
not limited. The actual limit may be rounded up to a multiple of the operating
|
||||
system's page size (the value would be very large, that's millions of trillions).
|
||||
supports swap memory, then the **-m** memory setting can be larger than physical
|
||||
RAM. If a limit of 0 is specified (not using **-m**), the container's memory is
|
||||
not limited. The actual limit may be rounded up to a multiple of the operating
|
||||
system's page size (the value would be very large, that's millions of trillions).
|
||||
|
||||
**--memory-reservation**=*number*[*S]
|
||||
Memory soft limit; *S* is an optional suffix which can be one of **b**, **k**, **m**, or **g**.
|
||||
|
||||
After setting memory reservation, when the system detects memory contention
|
||||
or low memory, containers are forced to restrict their consumption to their
|
||||
reservation. So you should always set the value below **--memory**, otherwise the
|
||||
hard limit will take precedence. By default, memory reservation will be the same
|
||||
as memory limit.
|
||||
or low memory, containers are forced to restrict their consumption to their
|
||||
reservation. So you should always set the value below **--memory**, otherwise the
|
||||
hard limit will take precedence. By default, memory reservation will be the same
|
||||
as memory limit.
|
||||
|
||||
**--memory-swap**=*number*[*S*]
|
||||
Combined memory plus swap limit; *S* is an optional suffix which can be one of **b**, **k**, **m**, or **g**.
|
||||
Combined memory plus swap limit; *S* is an optional suffix which can be
|
||||
one of **b**, **k**, **m**, or **g**.
|
||||
|
||||
This option can only be used together with **--memory**. The argument should always be larger than that of **--memory**. Default is double the value of **--memory**. Set to **-1** to enable unlimited swap.
|
||||
This option can only be used together with **--memory**. The argument
|
||||
should always be larger than that of **--memory**. Default is double the
|
||||
value of **--memory**. Set to **-1** to enable unlimited swap.
|
||||
|
||||
**--mac-address**=""
|
||||
Container MAC address (e.g., **92:d0:c6:0a:29:33**)
|
||||
@@ -453,11 +480,11 @@ according to RFC4862.
|
||||
|
||||
Current supported mount `TYPES` are `bind`, `volume`, and `tmpfs`.
|
||||
|
||||
e.g.
|
||||
For example;
|
||||
|
||||
`type=bind,source=/path/on/host,destination=/path/in/container`
|
||||
|
||||
`type=volume,source=my-volume,destination=/path/in/container,volume-label="color=red",volume-label="shape=round"`
|
||||
`type=volume,source=my-volume,destination=/path/in/container,volume-label="color=red"`
|
||||
|
||||
`type=tmpfs,tmpfs-size=512M,destination=/path/in/container`
|
||||
|
||||
@@ -484,7 +511,9 @@ according to RFC4862.
|
||||
|
||||
* `volume-driver`: Name of the volume-driver plugin.
|
||||
* `volume-label`: Custom metadata.
|
||||
* `volume-nocopy`: `true`(default) or `false`. If set to `false`, the Engine copies existing files and directories under the mount-path into the volume, allowing the host to access them.
|
||||
* `volume-nocopy`: `true`(default) or `false`. If set to `false`, the Engine
|
||||
copies existing files and directories under the mount-path into the volume,
|
||||
allowing the host to access them.
|
||||
* `volume-opt`: specific to a given volume driver.
|
||||
|
||||
Options specific to `tmpfs`:
|
||||
@@ -497,17 +526,15 @@ according to RFC4862.
|
||||
|
||||
The operator can identify a container in three ways:
|
||||
|
||||
| Identifier type | Example value |
|
||||
|:----------------------|:-------------------------------------------------------------------|
|
||||
| UUID long identifier | "f78375b1c487e03c9438c729345e54db9d20cfa2ac1fc3494b6eb60872e74778" |
|
||||
| UUID short identifier | "f78375b1c487" |
|
||||
| Name | "evil_ptolemy" |
|
||||
- **long ID**: `f78375b1c487e03c9438c729345e54db9d20cfa2ac1fc3494b6eb60872e74778`
|
||||
- **short ID**: `f78375b1c487`
|
||||
- **Name**: `evil_ptolemy`
|
||||
|
||||
The UUID identifiers come from the Docker daemon, and if a name is not assigned
|
||||
to the container with **--name** then the daemon will also generate a random
|
||||
string name. The name is useful when defining links (see **--link**) (or any
|
||||
other place you need to identify a container). This works for both background
|
||||
and foreground Docker containers.
|
||||
The ID identifiers come from the Docker daemon, and if a name is not assigned
|
||||
to the container with **--name** then the daemon will also generate a random
|
||||
string name. The name is useful when defining links (see **--link**) (or any
|
||||
other place you need to identify a container). This works for both background
|
||||
and foreground Docker containers.
|
||||
|
||||
**--network**=*type*
|
||||
Set the Network mode for the container. Supported values are:
|
||||
@@ -555,8 +582,9 @@ Use `docker port`(1) to see the actual mapping, e.g. `docker port CONTAINER $CON
|
||||
**--pid**=""
|
||||
Set the PID mode for the container
|
||||
Default is to create a private PID namespace for the container
|
||||
'container:<name|id>': join another container's PID namespace
|
||||
'host': use the host's PID namespace for the container. Note: the host mode gives the container full access to local PID and is therefore considered insecure.
|
||||
'container:<name|id>': join another container's PID namespace
|
||||
'host': use the host's PID namespace for the container. Note: the host mode
|
||||
gives the container full access to local PID and is therefore considered insecure.
|
||||
|
||||
**--userns**=""
|
||||
Set the usernamespace mode for the container when `userns-remap` option is enabled.
|
||||
@@ -566,9 +594,11 @@ Use `docker port`(1) to see the actual mapping, e.g. `docker port CONTAINER $CON
|
||||
Tune the container's pids (process IDs) limit. Set to `-1` to have unlimited pids for the container.
|
||||
|
||||
**--uts**=*type*
|
||||
Set the UTS mode for the container. The only possible *type* is **host**, meaning to
|
||||
use the host's UTS namespace inside the container.
|
||||
Note: the host mode gives the container access to changing the host's hostname and is therefore considered insecure.
|
||||
Set the UTS mode for the container. The only possible *type* is **host**,
|
||||
meaning to use the host's UTS namespace inside the container.
|
||||
|
||||
Note: the host mode gives the container access to changing the host's hostname
|
||||
and is therefore considered insecure.
|
||||
|
||||
**--privileged** [**true**|**false**]
|
||||
Give extended privileges to this container. A "privileged" container is given access to all devices.
|
||||
@@ -588,46 +618,50 @@ its root filesystem mounted as read only prohibiting any writes.
|
||||
**--restart** *policy*
|
||||
Restart policy to apply when a container exits. Supported values are:
|
||||
|
||||
| Policy | Result |
|
||||
|:-------------------------------|:----------------------|
|
||||
| **no** | Do not automatically restart the container when it exits. |
|
||||
| **on-failure**[:_max-retries_] | Restart only if the container exits with a non-zero exit status. Optionally, limit the number of restart retries the Docker daemon attempts. |
|
||||
| **always** | Always restart the container regardless of the exit status. When you specify always, the Docker daemon will try to restart the container indefinitely. The container will also always start on daemon startup, regardless of the current state of the container. |
|
||||
| **unless-stopped** | Always restart the container regardless of the exit status, but do not start it on daemon startup if the container has been put to a stopped state before. |
|
||||
| Policy | Result |
|
||||
|:-------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **no** | Do not automatically restart the container when it exits. |
|
||||
| **on-failure**[:_max-retries_] | Restart only if the container exits with a non-zero exit status. Optionally, limit the number of restart retries the Docker daemon attempts. |
|
||||
| **always** | Always restart the container regardless of the exit status. When you specify always, the Docker daemon tries to restart the container indefinitely. The container always start on daemon startup, regardless of the current state of the container. |
|
||||
| **unless-stopped** | Always restart the container regardless of the exit status, but do not start it on daemon startup if the container has been put to a stopped state before. |
|
||||
|
||||
Default is **no**.
|
||||
|
||||
**--rm** **true**|**false**
|
||||
Automatically remove the container when it exits. The default is **false**.
|
||||
`--rm` flag can work together with `-d`, and auto-removal will be done on daemon side. Note that it's
|
||||
incompatible with any restart policy other than `none`.
|
||||
`--rm` flag can work together with `-d`, and auto-removal will be done on
|
||||
daemon side. Note that it's incompatible with any restart policy other than
|
||||
`none`.
|
||||
|
||||
**--security-opt** *value*[,...]
|
||||
Security Options for the container. The following options can be given:
|
||||
|
||||
"label=user:USER" : Set the label user for the container
|
||||
"label=role:ROLE" : Set the label role for the container
|
||||
"label=type:TYPE" : Set the label type for the container
|
||||
"label=level:LEVEL" : Set the label level for the container
|
||||
"label=disable" : Turn off label confinement for the container
|
||||
"no-new-privileges" : Disable container processes from gaining additional privileges
|
||||
|
||||
"seccomp=unconfined" : Turn off seccomp confinement for the container
|
||||
"seccomp=profile.json : White listed syscalls seccomp Json file to be used as a seccomp filter
|
||||
|
||||
"apparmor=unconfined" : Turn off apparmor confinement for the container
|
||||
"apparmor=your-profile" : Set the apparmor confinement profile for the container
|
||||
| Option | Description |
|
||||
|:-------------------------|:-----------------------------------------------------------------------|
|
||||
| "label=user:USER" | Set the label user for the container |
|
||||
| "label=role:ROLE" | Set the label role for the container |
|
||||
| "label=type:TYPE" | Set the label type for the container |
|
||||
| "label=level:LEVEL" | Set the label level for the container |
|
||||
| "label=disable" | Turn off label confinement for the container |
|
||||
| "no-new-privileges" | Disable container processes from gaining additional privileges |
|
||||
| "seccomp=unconfined" | Turn off seccomp confinement for the container |
|
||||
| "seccomp=profile.json" | White listed syscalls seccomp Json file to be used as a seccomp filter |
|
||||
| "apparmor=unconfined" | Turn off apparmor confinement for the container |
|
||||
| "apparmor=your-profile" | Set the apparmor confinement profile for the container |
|
||||
|
||||
**--storage-opt**
|
||||
Storage driver options per container
|
||||
|
||||
$ docker run -it --storage-opt size=120G fedora /bin/bash
|
||||
|
||||
This (size) will allow to set the container rootfs size to 120G at creation time.
|
||||
This option is only available for the `btrfs`, `overlay2` and `zfs` graph drivers.
|
||||
For the `btrfs` and `zfs` storage drivers, user cannot pass a size less than the Default BaseFS Size.
|
||||
For the `overlay2` storage driver, the size option is only available if the backing fs is `xfs` and mounted with the `pquota` mount option.
|
||||
Under these conditions, user can pass any size less than the backing fs size.
|
||||
This (size) will allow to set the container rootfs size to 120G at creation
|
||||
time. This option is only available for the `btrfs`, `overlay2` and `zfs`
|
||||
graph drivers.
|
||||
For the `btrfs` and `zfs` storage drivers, user cannot pass a size less than
|
||||
the Default BaseFS Size. For the `overlay2` storage driver, the size option
|
||||
is only available if the backing fs is `xfs` and mounted with the `pquota`
|
||||
mount option. Under these conditions, user can pass any size less than the
|
||||
backing fs size.
|
||||
|
||||
**--stop-signal**=""
|
||||
Signal to stop the container.
|
||||
@@ -656,16 +690,18 @@ incompatible with any restart policy other than `none`.
|
||||
|
||||
**--shm-size**=""
|
||||
Size of `/dev/shm`. The format is `<number><unit>`.
|
||||
`number` must be greater than `0`. Unit is optional and can be `b` (bytes), `k` (kilobytes), `m`(megabytes), or `g` (gigabytes).
|
||||
If you omit the unit, the system uses bytes. If you omit the size entirely, the system uses `64m`.
|
||||
`number` must be greater than `0`. Unit is optional and can be `b` (bytes),
|
||||
`k` (kilobytes), `m`(megabytes), or `g` (gigabytes). If you omit the unit,
|
||||
the system uses bytes. If you omit the size entirely, the system uses `64m`.
|
||||
|
||||
**--sysctl**=SYSCTL
|
||||
Configure namespaced kernel parameters at runtime
|
||||
|
||||
IPC Namespace - current sysctls allowed:
|
||||
|
||||
kernel.msgmax, kernel.msgmnb, kernel.msgmni, kernel.sem, kernel.shmall, kernel.shmmax, kernel.shmmni, kernel.shm_rmid_forced
|
||||
Sysctls beginning with fs.mqueue.*
|
||||
kernel.msgmax, kernel.msgmnb, kernel.msgmni, kernel.sem, kernel.shmall,
|
||||
kernel.shmmax, kernel.shmmni, kernel.shm_rmid_forced, and sysctls beginning
|
||||
with fs.mqueue.*
|
||||
|
||||
If you use the `--ipc=host` option these sysctls will not be allowed.
|
||||
|
||||
@@ -675,7 +711,8 @@ incompatible with any restart policy other than `none`.
|
||||
If you use the `--network=host` option these sysctls will not be allowed.
|
||||
|
||||
**--sig-proxy**=*true*|*false*
|
||||
Proxy received signals to the process (non-TTY mode only). SIGCHLD, SIGSTOP, and SIGKILL are not proxied. The default is *true*.
|
||||
Proxy received signals to the process (non-TTY mode only). SIGCHLD, SIGSTOP,
|
||||
and SIGKILL are not proxied. The default is *true*.
|
||||
|
||||
**--memory-swappiness**=""
|
||||
Tune a container's memory swappiness behavior. Accepts an integer between 0 and 100.
|
||||
@@ -928,7 +965,8 @@ Host shows a shared memory segment with 7 pids attached, happens to be from http
|
||||
0x01128e25 0 root 600 1000 7
|
||||
```
|
||||
|
||||
Now run a regular container, and it correctly does NOT see the shared memory segment from the host:
|
||||
Now run a regular container, and it correctly does NOT see the shared memory
|
||||
segment from the host:
|
||||
|
||||
```
|
||||
$ docker run -it shm ipcs -m
|
||||
@@ -937,7 +975,8 @@ Now run a regular container, and it correctly does NOT see the shared memory seg
|
||||
key shmid owner perms bytes nattch status
|
||||
```
|
||||
|
||||
Run a container with the new `--ipc=host` option, and it now sees the shared memory segment from the host httpd:
|
||||
Run a container with the new `--ipc=host` option, and it now sees the shared
|
||||
memory segment from the host httpd:
|
||||
|
||||
```
|
||||
$ docker run -it --ipc=host shm ipcs -m
|
||||
@@ -958,7 +997,8 @@ Start a container with a program to create a shared memory segment:
|
||||
key shmid owner perms bytes nattch status
|
||||
0x0000162e 0 root 666 27 1
|
||||
```
|
||||
Create a 2nd container correctly shows no shared memory segment from 1st container:
|
||||
Create a second container correctly shows no shared memory segment from first
|
||||
container:
|
||||
```
|
||||
$ docker run shm ipcs -m
|
||||
|
||||
@@ -966,7 +1006,8 @@ Create a 2nd container correctly shows no shared memory segment from 1st contain
|
||||
key shmid owner perms bytes nattch status
|
||||
```
|
||||
|
||||
Create a 3rd container using the new --ipc=container:CONTAINERID option, now it shows the shared memory segment from the first:
|
||||
Create a 3rd container using the new --ipc=container:CONTAINERID option,
|
||||
now it shows the shared memory segment from the first:
|
||||
|
||||
```
|
||||
$ docker run -it --ipc=container:ed735b2264ac shm ipcs -m
|
||||
@@ -1129,18 +1170,22 @@ $ docker run -d --isolation default busybox top
|
||||
|
||||
On Microsoft Windows, can take any of these values:
|
||||
|
||||
* `default`: Use the value specified by the Docker daemon's `--exec-opt` . If the `daemon` does not specify an isolation technology, Microsoft Windows uses `process` as its default value.
|
||||
* `default`: Use the value specified by the Docker daemon's `--exec-opt` . If the
|
||||
`daemon` does not specify an isolation technology, Microsoft Windows uses
|
||||
`process` as its default value.
|
||||
* `process`: Namespace isolation only.
|
||||
* `hyperv`: Hyper-V hypervisor partition-based isolation.
|
||||
|
||||
In practice, when running on Microsoft Windows without a `daemon` option set, these two commands are equivalent:
|
||||
In practice, when running on Microsoft Windows without a `daemon` option set,
|
||||
these two commands are equivalent:
|
||||
|
||||
```
|
||||
$ docker run -d --isolation default busybox top
|
||||
$ docker run -d --isolation process busybox top
|
||||
```
|
||||
|
||||
If you have set the `--exec-opt isolation=hyperv` option on the Docker `daemon`, any of these commands also result in `hyperv` isolation:
|
||||
If you have set the `--exec-opt isolation=hyperv` option on the Docker `daemon`,
|
||||
any of these commands also result in `hyperv` isolation:
|
||||
|
||||
```
|
||||
$ docker run -d --isolation default busybox top
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.24
|
||||
|
||||
package swarmopts
|
||||
|
||||
import (
|
||||
@@ -100,7 +103,9 @@ func (p *PortOpt) Set(value string) error {
|
||||
|
||||
p.ports = append(p.ports, pConfig)
|
||||
} else {
|
||||
// short syntax
|
||||
// short syntax ([ip:]public:private[/proto])
|
||||
//
|
||||
// TODO(thaJeztah): we need an equivalent that handles the "ip-address" part without depending on the nat package.
|
||||
ports, portBindingMap, err := nat.ParsePortSpecs([]string{value})
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -162,18 +167,17 @@ func ConvertPortToPortConfig(
|
||||
logrus.Warnf("ignoring IP-address (%s:%s) service will listen on '0.0.0.0'", net.JoinHostPort(binding.HostIP, binding.HostPort), portProto.String())
|
||||
}
|
||||
|
||||
startHostPort, endHostPort, err := nat.ParsePortRange(binding.HostPort)
|
||||
|
||||
pr, err := network.ParsePortRange(binding.HostPort)
|
||||
if err != nil && binding.HostPort != "" {
|
||||
return nil, fmt.Errorf("invalid hostport binding (%s) for port (%d)", binding.HostPort, portProto.Num())
|
||||
}
|
||||
|
||||
for i := startHostPort; i <= endHostPort; i++ {
|
||||
for p := range pr.All() {
|
||||
ports = append(ports, swarm.PortConfig{
|
||||
// TODO Name: ?
|
||||
Protocol: portProto.Proto(),
|
||||
TargetPort: uint32(portProto.Num()),
|
||||
PublishedPort: uint32(i),
|
||||
PublishedPort: uint32(p.Num()),
|
||||
PublishMode: swarm.PortConfigPublishModeIngress,
|
||||
})
|
||||
}
|
||||
|
||||
+20
-14
@@ -137,12 +137,14 @@ func TestPortOptValidSimpleSyntax(t *testing.T) {
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
var port PortOpt
|
||||
assert.NilError(t, port.Set(tc.value))
|
||||
assert.Check(t, is.Len(port.Value(), len(tc.expected)))
|
||||
for _, expectedPortConfig := range tc.expected {
|
||||
assertContains(t, port.Value(), expectedPortConfig)
|
||||
}
|
||||
t.Run(tc.value, func(t *testing.T) {
|
||||
var port PortOpt
|
||||
assert.NilError(t, port.Set(tc.value))
|
||||
assert.Check(t, is.Len(port.Value(), len(tc.expected)))
|
||||
for _, expectedPortConfig := range tc.expected {
|
||||
assertContains(t, port.Value(), expectedPortConfig)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,12 +230,14 @@ func TestPortOptValidComplexSyntax(t *testing.T) {
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
var port PortOpt
|
||||
assert.NilError(t, port.Set(tc.value))
|
||||
assert.Check(t, is.Len(port.Value(), len(tc.expected)))
|
||||
for _, expectedPortConfig := range tc.expected {
|
||||
assertContains(t, port.Value(), expectedPortConfig)
|
||||
}
|
||||
t.Run(tc.value, func(t *testing.T) {
|
||||
var port PortOpt
|
||||
assert.NilError(t, port.Set(tc.value))
|
||||
assert.Check(t, is.Len(port.Value(), len(tc.expected)))
|
||||
for _, expectedPortConfig := range tc.expected {
|
||||
assertContains(t, port.Value(), expectedPortConfig)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -319,8 +323,10 @@ func TestPortOptInvalidSimpleSyntax(t *testing.T) {
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
var port PortOpt
|
||||
assert.Error(t, port.Set(tc.value), tc.expectedError)
|
||||
t.Run(tc.value, func(t *testing.T) {
|
||||
var port PortOpt
|
||||
assert.Error(t, port.Set(tc.value), tc.expectedError)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+41
-1
@@ -6,6 +6,9 @@ package templates
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"text/template"
|
||||
)
|
||||
@@ -15,7 +18,7 @@ import (
|
||||
var basicFunctions = template.FuncMap{
|
||||
"json": formatJSON,
|
||||
"split": strings.Split,
|
||||
"join": strings.Join,
|
||||
"join": joinElements,
|
||||
"title": strings.Title, //nolint:nolintlint,staticcheck // strings.Title is deprecated, but we only use it for ASCII, so replacing with golang.org/x/text is out of scope
|
||||
"lower": strings.ToLower,
|
||||
"upper": strings.ToUpper,
|
||||
@@ -97,3 +100,40 @@ func formatJSON(v any) string {
|
||||
// Remove the trailing new line added by the encoder
|
||||
return strings.TrimSpace(buf.String())
|
||||
}
|
||||
|
||||
// joinElements joins a slice of items with the given separator. It uses
|
||||
// [strings.Join] if it's a slice of strings, otherwise uses [fmt.Sprint]
|
||||
// to join each item to the output.
|
||||
func joinElements(elems any, sep string) (string, error) {
|
||||
if elems == nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
if ss, ok := elems.([]string); ok {
|
||||
return strings.Join(ss, sep), nil
|
||||
}
|
||||
|
||||
switch rv := reflect.ValueOf(elems); rv.Kind() { //nolint:exhaustive // ignore: too many options to make exhaustive
|
||||
case reflect.Array, reflect.Slice:
|
||||
var b strings.Builder
|
||||
for i := range rv.Len() {
|
||||
if i > 0 {
|
||||
b.WriteString(sep)
|
||||
}
|
||||
_, _ = fmt.Fprint(&b, rv.Index(i).Interface())
|
||||
}
|
||||
return b.String(), nil
|
||||
|
||||
case reflect.Map:
|
||||
var out []string
|
||||
for _, k := range rv.MapKeys() {
|
||||
out = append(out, fmt.Sprint(rv.MapIndex(k).Interface()))
|
||||
}
|
||||
// Not ideal, but trying to keep a consistent order
|
||||
sort.Strings(out)
|
||||
return strings.Join(out, sep), nil
|
||||
|
||||
default:
|
||||
return "", fmt.Errorf("expected slice, got %T", elems)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package templates
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
"text/template"
|
||||
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
@@ -139,3 +140,92 @@ func TestHeaderFunctions(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type stringerString string
|
||||
|
||||
func (s stringerString) String() string {
|
||||
return "stringer" + string(s)
|
||||
}
|
||||
|
||||
type stringerAndError string
|
||||
|
||||
func (s stringerAndError) String() string {
|
||||
return "stringer" + string(s)
|
||||
}
|
||||
|
||||
func (s stringerAndError) Error() string {
|
||||
return "error" + string(s)
|
||||
}
|
||||
|
||||
func TestJoinElements(t *testing.T) {
|
||||
tests := []struct {
|
||||
doc string
|
||||
data any
|
||||
expOut string
|
||||
expErr string
|
||||
}{
|
||||
{
|
||||
doc: "nil",
|
||||
data: nil,
|
||||
expOut: `output: ""`,
|
||||
},
|
||||
{
|
||||
doc: "non-slice",
|
||||
data: "hello",
|
||||
expOut: `output: "`,
|
||||
expErr: `error calling join: expected slice, got string`,
|
||||
},
|
||||
{
|
||||
doc: "structs",
|
||||
data: []struct{ A, B string }{{"1", "2"}, {"3", "4"}},
|
||||
expOut: `output: "{1 2}, {3 4}"`,
|
||||
},
|
||||
{
|
||||
doc: "map with strings",
|
||||
data: map[string]string{"A": "1", "B": "2", "C": "3"},
|
||||
expOut: `output: "1, 2, 3"`,
|
||||
},
|
||||
{
|
||||
doc: "map with stringers",
|
||||
data: map[string]stringerString{"A": "1", "B": "2", "C": "3"},
|
||||
expOut: `output: "stringer1, stringer2, stringer3"`,
|
||||
},
|
||||
{
|
||||
doc: "map with errors",
|
||||
data: []stringerAndError{"1", "2", "3"},
|
||||
expOut: `output: "error1, error2, error3"`,
|
||||
},
|
||||
{
|
||||
doc: "stringers",
|
||||
data: []stringerString{"1", "2", "3"},
|
||||
expOut: `output: "stringer1, stringer2, stringer3"`,
|
||||
},
|
||||
{
|
||||
doc: "stringer with errors",
|
||||
data: []stringerAndError{"1", "2", "3"},
|
||||
expOut: `output: "error1, error2, error3"`,
|
||||
},
|
||||
{
|
||||
doc: "slice of bools",
|
||||
data: []bool{true, false, true},
|
||||
expOut: `output: "true, false, true"`,
|
||||
},
|
||||
}
|
||||
|
||||
const formatStr = `output: "{{- join . ", " -}}"`
|
||||
tmpl, err := New("my-template").Funcs(template.FuncMap{"join": joinElements}).Parse(formatStr)
|
||||
assert.NilError(t, err)
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.doc, func(t *testing.T) {
|
||||
var b bytes.Buffer
|
||||
err := tmpl.Execute(&b, tc.data)
|
||||
if tc.expErr != "" {
|
||||
assert.ErrorContains(t, err, tc.expErr)
|
||||
} else {
|
||||
assert.NilError(t, err)
|
||||
}
|
||||
assert.Equal(t, b.String(), tc.expOut)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user