Compare commits

...

29 Commits

Author SHA1 Message Date
578ccf607d Merge pull request #6170 from thaJeztah/e2e_newline_check
Some checks failed
build / bin-image (push) Has been cancelled
build / prepare-plugins (push) Has been cancelled
build / plugins (push) Has been cancelled
codeql / codeql (push) Has been cancelled
e2e / tests (alpine, 23, connhelper-ssh) (push) Has been cancelled
e2e / tests (alpine, 23, local) (push) Has been cancelled
e2e / tests (alpine, 26, connhelper-ssh) (push) Has been cancelled
e2e / tests (alpine, 26, local) (push) Has been cancelled
e2e / tests (alpine, 27, connhelper-ssh) (push) Has been cancelled
e2e / tests (alpine, 27, local) (push) Has been cancelled
e2e / tests (alpine, 28, connhelper-ssh) (push) Has been cancelled
e2e / tests (alpine, 28, local) (push) Has been cancelled
e2e / tests (debian, 23, connhelper-ssh) (push) Has been cancelled
e2e / tests (debian, 23, local) (push) Has been cancelled
e2e / tests (debian, 26, connhelper-ssh) (push) Has been cancelled
e2e / tests (debian, 26, local) (push) Has been cancelled
e2e / tests (debian, 27, connhelper-ssh) (push) Has been cancelled
e2e / tests (debian, 27, local) (push) Has been cancelled
e2e / tests (debian, 28, connhelper-ssh) (push) Has been cancelled
e2e / tests (debian, 28, local) (push) Has been cancelled
test / ctn (push) Has been cancelled
test / host (macos-13) (push) Has been cancelled
test / host (macos-14) (push) Has been cancelled
validate / validate (lint) (push) Has been cancelled
validate / validate (shellcheck) (push) Has been cancelled
validate / validate (update-authors) (push) Has been cancelled
validate / validate (validate-vendor) (push) Has been cancelled
validate / validate-md (push) Has been cancelled
validate / validate-make (manpages) (push) Has been cancelled
validate / validate-make (yamldocs) (push) Has been cancelled
e2e/global: TestPromptExitCode: check for trailing newline
2025-07-09 14:04:02 +02:00
0c5e258f8a e2e/global: TestPromptExitCode: check for trailing newline
Make the test slightly more permissive; we're looking for a trailing
newline, not necessarily an empty line.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-07-09 13:35:17 +02:00
30cad385b6 Merge pull request #6167 from vvoland/update-go
Update to go1.24.5
2025-07-09 01:23:29 +02:00
9bcc88611f update to go1.24.5
- https://github.com/golang/go/issues?q=milestone%3AGo1.24.5+label%3ACherryPickApproved
- full diff: https://github.com/golang/go/compare/go1.24.4...go1.24.5

This minor releases include 1 security fixes following the security policy:

- cmd/go: unexpected command execution in untrusted VCS repositories

    Various uses of the Go toolchain in untrusted VCS repositories can result in
    unexpected code execution. When using the Go toolchain in directories fetched
    using various VCS tools (such as directly cloning Git or Mercurial repositories)
    can cause the toolchain to execute unexpected commands, if said directory
    contains multiple VCS configuration metadata (such as a '.hg' directory in a Git
    repository). This is due to how the Go toolchain attempts to resolve which VCS
    is being used in order to embed build information in binaries and determine
    module versions.

    The toolchain will now abort attempting to resolve which VCS is being used if it
    detects multiple VCS configuration metadata in a module directory or nested VCS
    configuration metadata (such as a '.git' directoy in a parent directory and a
    '.hg' directory in a child directory). This will not prevent the toolchain from
    building modules, but will result in binaries omitting VCS related build
    information.

    If this behavior is expected by the user, the old behavior can be re-enabled by
    setting GODEBUG=allowmultiplevcs=1. This should only be done in trusted
    repositories.

    Thanks to RyotaK (https://ryotak.net) of GMO Flatt Security Inc for reporting
    this issue.

    This is CVE-2025-4674 and https://go.dev/issue/74380.

View the release notes for more information:
https://go.dev/doc/devel/release#go1.24.5

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-07-08 19:23:57 +02:00
3302212263 Merge pull request #6163 from Benehiko/env-credential-warn
registry: warn of `DOCKER_AUTH_CONFIG` usage in login and logout
2025-07-08 15:33:07 +02:00
ccd5bd8d57 registry: warn of DOCKER_AUTH_CONFIG usage in login and logout
Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com>
2025-07-08 14:07:32 +02:00
dec07e6fdf tui/note: add warning note type
Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com>
2025-07-08 14:07:22 +02:00
28f19a9d65 Merge pull request #6162 from ArthurFlag/ENGDOCS-2807-cdi-docs-update
docs: cdi isn't experimental
2025-07-07 17:53:48 +02:00
219e5ca4f2 Merge pull request #6165 from thaJeztah/bump_engine_28.3.1
vendor: github.com/docker/docker v28.3.1
2025-07-07 17:53:31 +02:00
7e040d91ef docs: cdi is not experimental anymore
Signed-off-by: ArthurFlag <arthur.flageul@docker.com>
2025-07-07 16:10:30 +02:00
76524e7d0e vendor: github.com/docker/docker v28.3.1
no changes in vendored code

full diff: https://github.com/docker/docker/compare/v28.3.0...v28.3.1

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-07-07 15:52:29 +02:00
3262107821 cli/config: export const dockerEnvConfig
Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com>
2025-07-04 14:04:38 +02:00
8403869122 Merge pull request #6158 from thaJeztah/reduce_strslice
cli/command/container: remove redundant uses of strslice.StrSlice
2025-07-02 17:42:43 +02:00
1fc7194554 Merge pull request #6159 from thaJeztah/hide_codecov
rename codecov.yml to .codecov.yml
2025-07-02 17:42:11 +02:00
fa2a7f1536 Merge pull request #6154 from thaJeztah/bump_engine
vendor: github.com/docker/docker v28.3.0
2025-07-02 17:41:49 +02:00
350b3a6e25 Merge pull request #6160 from thaJeztah/fix_otel_debug_logs
cli/debug: fix OTELErrorHandler logging messages if there's no error
2025-07-02 17:41:26 +02:00
4ea6fbf538 cli/debug: fix OTELErrorHandler logging messages if there's no error
I noticed this in a ticket in the compose issue tracker; with debug logging
enabled, the OTEL error-logger may be logging even if there's no error;

    DEBU[0000] Executing bake with args: [bake --file - --progress rawjson --metadata-file /tmp/compose-build-metadataFile-1203980021.json --allow fs.read=/home/user/dev/project --allow fs.read=/home/user/dev/project --allow fs.read=/home/user/dev/project/nginx --allow fs.read=/home/user/dev/project]
    TRAC[0000] Plugin server listening on @docker_cli_d8df486f78df3b7357995be71bf0cef6
    DEBU[0005] otel error                                    error="<nil>"
    ^CTRAC[0055] Closing plugin server
    TRAC[0055] Closing plugin server
    DEBU[0055] otel error                                    error="<nil>"
    DEBU[0055] otel error                                    error="<nil>"

Update the error-handler to not log if there's no error.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-07-02 11:59:05 +02:00
74a896f18c Merge pull request #6157 from ndeloof/use_api_socket
mount /var/run/docker.sock for --use-api-socket
2025-07-01 17:00:24 +02:00
94f097da28 rename codecov.yml to .codecov.yml
Make it a hidden file. From the [CodeCov docs][1]:

> Can I name the file .codecov.yml?
>
> Yes, you can name the file `codecov.yml` or `.codecov.yml`. However, the
> file must still be located in the repository root, `dev/`, or `.github/`
> directories

[1]: https://docs.codecov.com/docs/codecov-yaml#can-i-name-the-file-codecovyml

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-07-01 16:20:54 +02:00
e7e238eb4b cli/command/container: remove redundant uses of strslice.StrSlice
The strslice.StrSlice type is a string-slice with a custom JSON Unmarshal
function to provide backward-compatibility with older API requests (see
[moby@17d6f00] and [moby@ea4a067]).

Given that the type is assigned implicitly through the fields on HostConfig,
we can just use a regular []string instead.

[moby@17d6f00]: 17d6f00ec2
[moby@ea4a067]: ea4a06740b

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-07-01 10:09:54 +02:00
2ba7cb8b44 mount /var/run/docker.sock for --use-api-socket
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
2025-06-30 12:38:15 +02:00
52e1e4fb21 vendor: github.com/docker/docker v28.3.0
no diff; same commit: https://github.com/docker/docker/compare/v28.3.0-rc.2...v28.3.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-06-30 09:35:21 +02:00
7cbee73f19 Merge pull request #6147 from thaJeztah/connhelper_quote
cli/connhelper: quote ssh arguments to prevent shell injection
2025-06-25 17:21:12 +02:00
ae6f8d0021 Merge pull request #6149 from vvoland/gha-tags
gha/bin-image: add major and minor version image tags
2025-06-25 14:35:11 +00:00
70867e7067 gha/bin-image: add major and minor version image tags
Adding image tags that follow the semver major and minor versions (e.g., `28`
and `28.3`) for the moby-bin images.

This makes it easier for users to reference the latest build within a
major or minor version series without having to know the exact
minor/patch version.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2025-06-24 23:35:10 +02:00
88d1133224 cli/connhelper: quote ssh arguments to prevent shell injection
When connecting to a remote daemon through an ssh:// connection,
the CLI connects with the remote host using ssh, executing the
`docker system dial-stdio` command on the remote host to connect
to the daemon API's unix socket.

By default, the `docker system dial-stdio` command connects with the
daemon using the default location (/var/run/docker.sock), or the
location as configured on the remote host.

Commit 25ebf0ec9c (included in docker
CLI v24.0.0-rc.2 and higher) introduced a feature to allow the location
of the socket to be specified through the host connection string, for
example:

     DOCKER_HOST='ssh://example.test/run/custom-docker.sock'

The custom path is included as part of the ssh command executed from
the client machine to connect with the remote host. THe example above
would execute the following command from the client machine;

    ssh -o ConnectTimeout=30 -T -- example.test docker --host unix:///run/custom-docker.sock system dial-stdio

ssh executes remote commands in a shell environment, and no quoting
was in place, which allowed for a connection string to include additional
content, which would be expanded / executed on the remote machine.

For example, the following example would execute `echo hello > /hello.txt`
on the remote machine;

    export DOCKER_HOST='ssh://example.test/var/run/docker.sock $(echo hello > /hello.txt)'
    docker info
    # (output of docker info from the remote machine)

While this doesn't allow the user to do anything they're not already
able to do so (by directly using the same SSH connection), the behavior
is not expected, so this patch adds quoting to prevent such URLs from
resulting in expansion.

This patch updates the cli/connhelper and cli/connhelper/ssh package to
quote parameters used in the ssh command to prevent code execution and
expansion of variables on the remote machine. Quoting is also applied to
other parameters that are obtained from the DOCKER_HOST url, such as username
and hostname.

- The existing `Spec.Args()` method inthe cli/connhelper/ssh package now
  quotes arguments, and returns a nil slice when failing to quote. Users
  of this package should therefore check the returned arguments before
  consuming. This  method did not provide an error-return, and adding
  one would be a breaking change.
- A new `Spec.Command` method is introduced, which (unlike the `Spec.Args()`
  method) provides an error return. Users are recommended to use this new
  method instead of the `Spec.Args()` method.

Some minor additional changes in behavior are included in this patch;

- Connection URLs with a trailing slash (e.g. `ssh://example.test/`)
  would previously result in `unix:///` being used as custom socket
  path. After this patch, the trailing slash is ignored, and no custom
  socket path is used.
- Specifying a remote command is now required. When passing an empty
  remote command, `Spec.Args()` now results in a `nil` value to be
  returned (or an `no remote command specified` error when using
  `Spec.Comnmand()`.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-06-24 16:26:17 +02:00
82eda48066 cli/connhelper/internal/syntax: fix linting issues
cli/connhelper/internal/syntax/parser.go:31:2: Duplicate words (the) found (dupword)
        // Note that it shares some features with Bash, due to the the shared
        ^
    cli/connhelper/internal/syntax/quote.go:48:1: cyclomatic complexity 35 of func `Quote` is high (> 16) (gocyclo)
    func Quote(s string, lang LangVariant) (string, error) {
    ^
    cli/connhelper/internal/syntax/quote.go:103:3: shadow: declaration of "offs" shadows declaration at line 56 (govet)
            offs := 0
            ^

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-06-24 16:24:34 +02:00
52d2a9b5ae cli/connhelper/internal/syntax: remove unused code from fork
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-06-24 16:24:29 +02:00
64a9a6d0c8 cli/connhelper: add fork of mvdan.cc/sh/v3/syntax v3.10.0
This adds a local fork of the mvdan.cc/sh/v3/syntax package to provide the
Quote function without having to introduce additional (indirect) dependencies
of the mvdan.cc/sh module.

This commit does not compile as it references code not forked.

The following files were included:

- https://raw.githubusercontent.com/mvdan/sh/refs/tags/v3.10.0/syntax/quote.go
- https://raw.githubusercontent.com/mvdan/sh/refs/tags/v3.10.0/syntax/parser.go
- https://raw.githubusercontent.com/mvdan/sh/refs/tags/v3.10.0/LICENSE

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-06-24 10:02:53 +02:00
32 changed files with 727 additions and 70 deletions

View File

@ -121,6 +121,8 @@ jobs:
type=semver,pattern={{version}}
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{major}}
type=semver,pattern={{major}}.{{minor}}
-
name: Build and push image
uses: docker/bake-action@v6

View File

@ -63,7 +63,7 @@ jobs:
name: Update Go
uses: actions/setup-go@v5
with:
go-version: "1.24.4"
go-version: "1.24.5"
-
name: Initialize CodeQL
uses: github/codeql-action/init@v3

View File

@ -66,7 +66,7 @@ jobs:
name: Set up Go
uses: actions/setup-go@v5
with:
go-version: "1.24.4"
go-version: "1.24.5"
-
name: Test
run: |

View File

@ -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.24.4"
go: "1.24.5"
timeout: 5m

View File

@ -4,7 +4,7 @@ ARG BASE_VARIANT=alpine
ARG ALPINE_VERSION=3.21
ARG BASE_DEBIAN_DISTRO=bookworm
ARG GO_VERSION=1.24.4
ARG GO_VERSION=1.24.5
ARG XX_VERSION=1.6.1
ARG GOVERSIONINFO_VERSION=v1.4.1
ARG GOTESTSUM_VERSION=v1.12.0

View File

@ -248,15 +248,14 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *c
// 1. Mount the actual docker socket.
// 2. A synthezised ~/.docker/config.json with resolved tokens.
socket := dockerCli.DockerEndpoint().Host
if !strings.HasPrefix(socket, "unix://") {
return "", fmt.Errorf("flag --use-api-socket can only be used with unix sockets: docker endpoint %s incompatible", socket)
if dockerCli.ServerInfo().OSType == "windows" {
return "", errors.New("flag --use-api-socket can't be used with a Windows Docker Engine")
}
socket = strings.TrimPrefix(socket, "unix://") // should we confirm absolute path?
// hard-code engine socket path until https://github.com/moby/moby/pull/43459 gives us a discovery mechanism
containerCfg.HostConfig.Mounts = append(containerCfg.HostConfig.Mounts, mount.Mount{
Type: mount.TypeBind,
Source: socket,
Source: "/var/run/docker.sock",
Target: "/var/run/docker.sock",
BindOptions: &mount.BindOptions{},
})

View File

@ -18,7 +18,6 @@ import (
"github.com/docker/docker/api/types/container"
mounttypes "github.com/docker/docker/api/types/mount"
networktypes "github.com/docker/docker/api/types/network"
"github.com/docker/docker/api/types/strslice"
"github.com/docker/go-connections/nat"
"github.com/pkg/errors"
"github.com/spf13/pflag"
@ -400,17 +399,14 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con
tmpfs[k] = v
}
var (
runCmd strslice.StrSlice
entrypoint strslice.StrSlice
)
var runCmd, entrypoint []string
if len(copts.Args) > 0 {
runCmd = copts.Args
}
if copts.entrypoint != "" {
entrypoint = strslice.StrSlice{copts.entrypoint}
entrypoint = []string{copts.entrypoint}
} else if flags.Changed("entrypoint") {
// if `--entrypoint=` is parsed then Entrypoint is reset
entrypoint = []string{""}
@ -551,9 +547,9 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con
if haveHealthSettings {
return nil, errors.Errorf("--no-healthcheck conflicts with --health-* options")
}
healthConfig = &container.HealthConfig{Test: strslice.StrSlice{"NONE"}}
healthConfig = &container.HealthConfig{Test: []string{"NONE"}}
} else if haveHealthSettings {
var probe strslice.StrSlice
var probe []string
if copts.healthCmd != "" {
probe = []string{"CMD-SHELL", copts.healthCmd}
}
@ -675,8 +671,8 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con
UTSMode: utsMode,
UsernsMode: usernsMode,
CgroupnsMode: cgroupnsMode,
CapAdd: strslice.StrSlice(copts.capAdd.GetSlice()),
CapDrop: strslice.StrSlice(copts.capDrop.GetSlice()),
CapAdd: copts.capAdd.GetSlice(),
CapDrop: copts.capDrop.GetSlice(),
GroupAdd: copts.groupAdd.GetSlice(),
RestartPolicy: restartPolicy,
SecurityOpt: securityOpts,

View File

@ -110,6 +110,9 @@ func runLogin(ctx context.Context, dockerCLI command.Cli, opts loginOptions) err
if err := verifyLoginOptions(dockerCLI, &opts); err != nil {
return err
}
maybePrintEnvAuthWarning(dockerCLI)
var (
serverAddress string
msg string

View File

@ -36,6 +36,8 @@ func NewLogoutCommand(dockerCli command.Cli) *cobra.Command {
}
func runLogout(ctx context.Context, dockerCLI command.Cli, serverAddress string) error {
maybePrintEnvAuthWarning(dockerCLI)
var isDefaultRegistry bool
if serverAddress == "" {

View File

@ -0,0 +1,18 @@
package registry
import (
"os"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/config/configfile"
"github.com/docker/cli/internal/tui"
)
// maybePrintEnvAuthWarning if the `DOCKER_AUTH_CONFIG` environment variable is
// set this function will output a warning to stdErr
func maybePrintEnvAuthWarning(out command.Streams) {
if os.Getenv(configfile.DockerEnvConfigKey) != "" {
tui.NewOutput(out.Err()).
PrintWarning("%[1]s is set and takes precedence.\nUnset %[1]s to restore the CLI auth behaviour.\n", configfile.DockerEnvConfigKey)
}
}

View File

@ -56,7 +56,7 @@ type configEnv struct {
AuthConfigs map[string]configEnvAuth `json:"auths"`
}
// dockerEnvConfig is an environment variable that contains a JSON encoded
// DockerEnvConfigKey is an environment variable that contains a JSON encoded
// credential config. It only supports storing the credentials as a base64
// encoded string in the format base64("username:pat").
//
@ -71,7 +71,7 @@ type configEnv struct {
// }
// }
// }
const dockerEnvConfig = "DOCKER_AUTH_CONFIG"
const DockerEnvConfigKey = "DOCKER_AUTH_CONFIG"
// ProxyConfig contains proxy configuration settings
type ProxyConfig struct {
@ -296,7 +296,7 @@ func (configFile *ConfigFile) GetCredentialsStore(registryHostname string) crede
store = newNativeStore(configFile, helper)
}
envConfig := os.Getenv(dockerEnvConfig)
envConfig := os.Getenv(DockerEnvConfigKey)
if envConfig == "" {
return store
}

View File

@ -47,14 +47,19 @@ func getConnectionHelper(daemonURL string, sshFlags []string) (*ConnectionHelper
}
sshFlags = addSSHTimeout(sshFlags)
sshFlags = disablePseudoTerminalAllocation(sshFlags)
remoteCommand := []string{"docker", "system", "dial-stdio"}
socketPath := sp.Path
if strings.Trim(sp.Path, "/") != "" {
remoteCommand = []string{"docker", "--host=unix://" + socketPath, "system", "dial-stdio"}
}
sshArgs, err := sp.Command(sshFlags, remoteCommand...)
if err != nil {
return nil, err
}
return &ConnectionHelper{
Dialer: func(ctx context.Context, network, addr string) (net.Conn, error) {
args := []string{"docker"}
if sp.Path != "" {
args = append(args, "--host", "unix://"+sp.Path)
}
args = append(args, "system", "dial-stdio")
return commandconn.New(ctx, "ssh", append(sshFlags, sp.Args(args...)...)...)
return commandconn.New(ctx, "ssh", sshArgs...)
},
Host: "http://docker.example.com",
}, nil

View File

@ -0,0 +1,27 @@
Copyright (c) 2016, Daniel Martí. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,13 @@
// Package syntax is a fork of [mvdan.cc/sh/v3@v3.10.0/syntax].
//
// Copyright (c) 2016, Daniel Martí. All rights reserved.
//
// It is a reduced set of the package to only provide the [Quote] function,
// and contains the [LICENSE], [quote.go] and [parser.go] files at the given
// revision.
//
// [quote.go]: https://raw.githubusercontent.com/mvdan/sh/refs/tags/v3.10.0/syntax/quote.go
// [parser.go]: https://raw.githubusercontent.com/mvdan/sh/refs/tags/v3.10.0/syntax/parser.go
// [LICENSE]: https://raw.githubusercontent.com/mvdan/sh/refs/tags/v3.10.0/LICENSE
// [mvdan.cc/sh/v3@v3.10.0/syntax]: https://pkg.go.dev/mvdan.cc/sh/v3@v3.10.0/syntax
package syntax

View File

@ -0,0 +1,95 @@
// Copyright (c) 2016, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information
package syntax
// LangVariant describes a shell language variant to use when tokenizing and
// parsing shell code. The zero value is [LangBash].
type LangVariant int
const (
// LangBash corresponds to the GNU Bash language, as described in its
// manual at https://www.gnu.org/software/bash/manual/bash.html.
//
// We currently follow Bash version 5.2.
//
// Its string representation is "bash".
LangBash LangVariant = iota
// LangPOSIX corresponds to the POSIX Shell language, as described at
// https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html.
//
// Its string representation is "posix" or "sh".
LangPOSIX
// LangMirBSDKorn corresponds to the MirBSD Korn Shell, also known as
// mksh, as described at http://www.mirbsd.org/htman/i386/man1/mksh.htm.
// Note that it shares some features with Bash, due to the shared
// ancestry that is ksh.
//
// We currently follow mksh version 59.
//
// Its string representation is "mksh".
LangMirBSDKorn
// LangBats corresponds to the Bash Automated Testing System language,
// as described at https://github.com/bats-core/bats-core. Note that
// it's just a small extension of the Bash language.
//
// Its string representation is "bats".
LangBats
// LangAuto corresponds to automatic language detection,
// commonly used by end-user applications like shfmt,
// which can guess a file's language variant given its filename or shebang.
//
// At this time, [Variant] does not support LangAuto.
LangAuto
)
func (l LangVariant) String() string {
switch l {
case LangBash:
return "bash"
case LangPOSIX:
return "posix"
case LangMirBSDKorn:
return "mksh"
case LangBats:
return "bats"
case LangAuto:
return "auto"
}
return "unknown shell language variant"
}
// IsKeyword returns true if the given word is part of the language keywords.
func IsKeyword(word string) bool {
// This list has been copied from the bash 5.1 source code, file y.tab.c +4460
switch word {
case
"!",
"[[", // only if COND_COMMAND is defined
"]]", // only if COND_COMMAND is defined
"case",
"coproc", // only if COPROCESS_SUPPORT is defined
"do",
"done",
"else",
"esac",
"fi",
"for",
"function",
"if",
"in",
"select", // only if SELECT_COMMAND is defined
"then",
"time", // only if COMMAND_TIMING is defined
"until",
"while",
"{",
"}":
return true
}
return false
}

View File

@ -0,0 +1,187 @@
// Copyright (c) 2021, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information
package syntax
import (
"fmt"
"strings"
"unicode"
"unicode/utf8"
)
type QuoteError struct {
ByteOffset int
Message string
}
func (e QuoteError) Error() string {
return fmt.Sprintf("cannot quote character at byte %d: %s", e.ByteOffset, e.Message)
}
const (
quoteErrNull = "shell strings cannot contain null bytes"
quoteErrPOSIX = "POSIX shell lacks escape sequences"
quoteErrRange = "rune out of range"
quoteErrMksh = "mksh cannot escape codepoints above 16 bits"
)
// Quote returns a quoted version of the input string,
// so that the quoted version is expanded or interpreted
// as the original string in the given language variant.
//
// Quoting is necessary when using arbitrary literal strings
// as words in a shell script or command.
// Without quoting, one can run into syntax errors,
// as well as the possibility of running unintended code.
//
// An error is returned when a string cannot be quoted for a variant.
// For instance, POSIX lacks escape sequences for non-printable characters,
// and no language variant can represent a string containing null bytes.
// In such cases, the returned error type will be *QuoteError.
//
// The quoting strategy is chosen on a best-effort basis,
// to minimize the amount of extra bytes necessary.
//
// Some strings do not require any quoting and are returned unchanged.
// Those strings can be directly surrounded in single quotes as well.
//
//nolint:gocyclo // ignore "cyclomatic complexity 35 of func `Quote` is high (> 16) (gocyclo)"
func Quote(s string, lang LangVariant) (string, error) {
if s == "" {
// Special case; an empty string must always be quoted,
// as otherwise it expands to zero fields.
return "''", nil
}
shellChars := false
nonPrintable := false
offs := 0
for rem := s; len(rem) > 0; {
r, size := utf8.DecodeRuneInString(rem)
switch r {
// Like regOps; token characters.
case ';', '"', '\'', '(', ')', '$', '|', '&', '>', '<', '`',
// Whitespace; might result in multiple fields.
' ', '\t', '\r', '\n',
// Escape sequences would be expanded.
'\\',
// Would start a comment unless quoted.
'#',
// Might result in brace expansion.
'{',
// Might result in tilde expansion.
'~',
// Might result in globbing.
'*', '?', '[',
// Might result in an assignment.
'=':
shellChars = true
case '\x00':
return "", &QuoteError{ByteOffset: offs, Message: quoteErrNull}
}
if r == utf8.RuneError || !unicode.IsPrint(r) {
if lang == LangPOSIX {
return "", &QuoteError{ByteOffset: offs, Message: quoteErrPOSIX}
}
nonPrintable = true
}
rem = rem[size:]
offs += size
}
if !shellChars && !nonPrintable && !IsKeyword(s) {
// Nothing to quote; avoid allocating.
return s, nil
}
// Single quotes are usually best,
// as they don't require any escaping of characters.
// If we have any invalid utf8 or non-printable runes,
// use $'' so that we can escape them.
// Note that we can't use double quotes for those.
var b strings.Builder
if nonPrintable {
b.WriteString("$'")
lastRequoteIfHex := false
offs = 0
for rem := s; len(rem) > 0; {
nextRequoteIfHex := false
r, size := utf8.DecodeRuneInString(rem)
switch {
case r == '\'', r == '\\':
b.WriteByte('\\')
b.WriteRune(r)
case unicode.IsPrint(r) && r != utf8.RuneError:
if lastRequoteIfHex && isHex(r) {
b.WriteString("'$'")
}
b.WriteRune(r)
case r == '\a':
b.WriteString(`\a`)
case r == '\b':
b.WriteString(`\b`)
case r == '\f':
b.WriteString(`\f`)
case r == '\n':
b.WriteString(`\n`)
case r == '\r':
b.WriteString(`\r`)
case r == '\t':
b.WriteString(`\t`)
case r == '\v':
b.WriteString(`\v`)
case r < utf8.RuneSelf, r == utf8.RuneError && size == 1:
// \xXX, fixed at two hexadecimal characters.
fmt.Fprintf(&b, "\\x%02x", rem[0])
// Unfortunately, mksh allows \x to consume more hex characters.
// Ensure that we don't allow it to read more than two.
if lang == LangMirBSDKorn {
nextRequoteIfHex = true
}
case r > utf8.MaxRune:
// Not a valid Unicode code point?
return "", &QuoteError{ByteOffset: offs, Message: quoteErrRange}
case lang == LangMirBSDKorn && r > 0xFFFD:
// From the CAVEATS section in R59's man page:
//
// mksh currently uses OPTU-16 internally, which is the same as
// UTF-8 and CESU-8 with 0000..FFFD being valid codepoints.
return "", &QuoteError{ByteOffset: offs, Message: quoteErrMksh}
case r < 0x10000:
// \uXXXX, fixed at four hexadecimal characters.
fmt.Fprintf(&b, "\\u%04x", r)
default:
// \UXXXXXXXX, fixed at eight hexadecimal characters.
fmt.Fprintf(&b, "\\U%08x", r)
}
rem = rem[size:]
lastRequoteIfHex = nextRequoteIfHex
offs += size
}
b.WriteString("'")
return b.String(), nil
}
// Single quotes without any need for escaping.
if !strings.Contains(s, "'") {
return "'" + s + "'", nil
}
// The string contains single quotes,
// so fall back to double quotes.
b.WriteByte('"')
for _, r := range s {
switch r {
case '"', '\\', '`', '$':
b.WriteByte('\\')
}
b.WriteRune(r)
}
b.WriteByte('"')
return b.String(), nil
}
func isHex(r rune) bool {
return (r >= '0' && r <= '9') ||
(r >= 'a' && r <= 'f') ||
(r >= 'A' && r <= 'F')
}

View File

@ -5,6 +5,8 @@ import (
"errors"
"fmt"
"net/url"
"github.com/docker/cli/cli/connhelper/internal/syntax"
)
// ParseURL creates a [Spec] from the given ssh URL. It returns an error if
@ -76,16 +78,106 @@ type Spec struct {
Path string
}
// Args returns args except "ssh" itself combined with optional additional command args
func (sp *Spec) Args(add ...string) []string {
// Args returns args except "ssh" itself combined with optional additional
// command and args to be executed on the remote host. It attempts to quote
// the given arguments to account for ssh executing the remote command in a
// shell. It returns nil when unable to quote the remote command.
func (sp *Spec) Args(remoteCommandAndArgs ...string) []string {
// Format the remote command to run using the ssh connection, quoting
// values where needed because ssh executes these in a POSIX shell.
remoteCommand, err := quoteCommand(remoteCommandAndArgs...)
if err != nil {
return nil
}
sshArgs, err := sp.args()
if err != nil {
return nil
}
if remoteCommand != "" {
sshArgs = append(sshArgs, remoteCommand)
}
return sshArgs
}
func (sp *Spec) args(sshFlags ...string) ([]string, error) {
var args []string
if sp.Host == "" {
return nil, errors.New("no host specified")
}
if sp.User != "" {
args = append(args, "-l", sp.User)
// Quote user, as it's obtained from the URL.
usr, err := syntax.Quote(sp.User, syntax.LangPOSIX)
if err != nil {
return nil, fmt.Errorf("invalid user: %w", err)
}
args = append(args, "-l", usr)
}
if sp.Port != "" {
args = append(args, "-p", sp.Port)
// Quote port, as it's obtained from the URL.
port, err := syntax.Quote(sp.Port, syntax.LangPOSIX)
if err != nil {
return nil, fmt.Errorf("invalid port: %w", err)
}
args = append(args, "-p", port)
}
args = append(args, "--", sp.Host)
args = append(args, add...)
return args
// We consider "sshFlags" to be "trusted", and set from code only,
// as they are not parsed from the DOCKER_HOST URL.
args = append(args, sshFlags...)
host, err := syntax.Quote(sp.Host, syntax.LangPOSIX)
if err != nil {
return nil, fmt.Errorf("invalid host: %w", err)
}
return append(args, "--", host), nil
}
// Command returns the ssh flags and arguments to execute a command
// (remoteCommandAndArgs) on the remote host. Where needed, it quotes
// values passed in remoteCommandAndArgs to account for ssh executing
// the remote command in a shell. It returns an error if no remote command
// is passed, or when unable to quote the remote command.
//
// Important: to preserve backward-compatibility, Command does not currently
// perform sanitization or quoting on the sshFlags and callers are expected
// to sanitize this argument.
func (sp *Spec) Command(sshFlags []string, remoteCommandAndArgs ...string) ([]string, error) {
if len(remoteCommandAndArgs) == 0 {
return nil, errors.New("no remote command specified")
}
sshArgs, err := sp.args(sshFlags...)
if err != nil {
return nil, err
}
remoteCommand, err := quoteCommand(remoteCommandAndArgs...)
if err != nil {
return nil, err
}
if remoteCommand != "" {
sshArgs = append(sshArgs, remoteCommand)
}
return sshArgs, nil
}
// quoteCommand returns the remote command to run using the ssh connection
// as a single string, quoting values where needed because ssh executes
// these in a POSIX shell.
func quoteCommand(commandAndArgs ...string) (string, error) {
var quotedCmd string
for i, arg := range commandAndArgs {
a, err := syntax.Quote(arg, syntax.LangPOSIX)
if err != nil {
return "", fmt.Errorf("invalid argument: %w", err)
}
if i == 0 {
quotedCmd = a
continue
}
quotedCmd += " " + a
}
// each part is quoted appropriately, so now we'll have a full
// shell command to pass off to "ssh"
return quotedCmd, nil
}

View File

@ -1,6 +1,7 @@
package ssh
import (
"strings"
"testing"
"gotest.tools/v3/assert"
@ -26,6 +27,28 @@ func TestParseURL(t *testing.T) {
Host: "example.com",
},
},
{
doc: "bare ssh URL with trailing slash",
url: "ssh://example.com/",
expectedArgs: []string{
"--", "example.com",
},
expectedSpec: Spec{
Host: "example.com",
Path: "/",
},
},
{
doc: "bare ssh URL with trailing slashes",
url: "ssh://example.com//",
expectedArgs: []string{
"--", "example.com",
},
expectedSpec: Spec{
Host: "example.com",
Path: "//",
},
},
{
doc: "bare ssh URL and remote command",
url: "ssh://example.com",
@ -34,7 +57,7 @@ func TestParseURL(t *testing.T) {
},
expectedArgs: []string{
"--", "example.com",
"docker", "system", "dial-stdio",
`docker system dial-stdio`,
},
expectedSpec: Spec{
Host: "example.com",
@ -48,7 +71,7 @@ func TestParseURL(t *testing.T) {
},
expectedArgs: []string{
"--", "example.com",
"docker", "--host", "unix:///var/run/docker.sock", "system", "dial-stdio",
`docker --host unix:///var/run/docker.sock system dial-stdio`,
},
expectedSpec: Spec{
Host: "example.com",
@ -84,6 +107,25 @@ func TestParseURL(t *testing.T) {
Path: "/var/run/docker.sock",
},
},
{
// This test is only to verify the behavior of ParseURL to
// pass through the Path as-is. Neither Spec.Args, nor
// Spec.Command use the Path field directly, and it should
// likely be deprecated.
doc: "bad path",
url: `ssh://example.com/var/run/docker.sock '$(echo hello > /hello.txt)'`,
remoteCommand: []string{
"docker", "--host", `unix:///var/run/docker.sock '$(echo hello > /hello.txt)'`, "system", "dial-stdio",
},
expectedArgs: []string{
"--", "example.com",
`docker --host "unix:///var/run/docker.sock '\$(echo hello > /hello.txt)'" system dial-stdio`,
},
expectedSpec: Spec{
Host: "example.com",
Path: `/var/run/docker.sock '$(echo hello > /hello.txt)'`,
},
},
{
doc: "malformed URL",
url: "malformed %%url",
@ -123,6 +165,21 @@ func TestParseURL(t *testing.T) {
url: "https://example.com",
expectedError: `invalid SSH URL: incorrect scheme: https`,
},
{
doc: "invalid URL with NUL character",
url: "ssh://example.com/var/run/\x00docker.sock",
expectedError: `invalid SSH URL: net/url: invalid control character in URL`,
},
{
doc: "invalid URL with newline character",
url: "ssh://example.com/var/run/docker.sock\n",
expectedError: `invalid SSH URL: net/url: invalid control character in URL`,
},
{
doc: "invalid URL with control character",
url: "ssh://example.com/var/run/\x1bdocker.sock",
expectedError: `invalid SSH URL: net/url: invalid control character in URL`,
},
}
for _, tc := range testCases {
t.Run(tc.doc, func(t *testing.T) {
@ -139,3 +196,122 @@ func TestParseURL(t *testing.T) {
})
}
}
func TestCommand(t *testing.T) {
testCases := []struct {
doc string
url string
sshFlags []string
customCmd []string
expectedCmd []string
expectedError string
}{
{
doc: "bare ssh URL",
url: "ssh://example.com",
expectedCmd: []string{
"--", "example.com",
"docker system dial-stdio",
},
},
{
doc: "bare ssh URL with trailing slash",
url: "ssh://example.com/",
expectedCmd: []string{
"--", "example.com",
"docker system dial-stdio",
},
},
{
doc: "bare ssh URL with custom ssh flags",
url: "ssh://example.com",
sshFlags: []string{"-T", "-o", "ConnectTimeout=30", "-oStrictHostKeyChecking=no"},
expectedCmd: []string{
"-T",
"-o", "ConnectTimeout=30",
"-oStrictHostKeyChecking=no",
"--", "example.com",
"docker system dial-stdio",
},
},
{
doc: "ssh URL with all options",
url: "ssh://me@example.com:10022/var/run/docker.sock",
sshFlags: []string{"-T", "-o ConnectTimeout=30"},
expectedCmd: []string{
"-l", "me",
"-p", "10022",
"-T",
"-o ConnectTimeout=30",
"--", "example.com",
"docker '--host=unix:///var/run/docker.sock' system dial-stdio",
},
},
{
doc: "bad ssh flags",
url: "ssh://example.com",
sshFlags: []string{"-T", "-o", `ConnectTimeout=30 $(echo hi > /hi.txt)`},
expectedCmd: []string{
"-T",
"-o", `ConnectTimeout=30 $(echo hi > /hi.txt)`,
"--", "example.com",
"docker system dial-stdio",
},
},
{
doc: "bad username",
url: `ssh://$(shutdown)me@example.com`,
expectedCmd: []string{
"-l", `'$(shutdown)me'`,
"--", "example.com",
"docker system dial-stdio",
},
},
{
doc: "bad hostname",
url: `ssh://$(shutdown)example.com`,
expectedCmd: []string{
"--", `'$(shutdown)example.com'`,
"docker system dial-stdio",
},
},
{
doc: "bad path",
url: `ssh://example.com/var/run/docker.sock '$(echo hello > /hello.txt)'`,
expectedCmd: []string{
"--", "example.com",
`docker "--host=unix:///var/run/docker.sock '\$(echo hello > /hello.txt)'" system dial-stdio`,
},
},
{
doc: "missing command",
url: "ssh://example.com",
customCmd: []string{},
expectedError: "no remote command specified",
},
}
for _, tc := range testCases {
t.Run(tc.doc, func(t *testing.T) {
sp, err := ParseURL(tc.url)
assert.NilError(t, err)
var commandAndArgs []string
if tc.customCmd == nil {
socketPath := sp.Path
commandAndArgs = []string{"docker", "system", "dial-stdio"}
if strings.Trim(socketPath, "/") != "" {
commandAndArgs = []string{"docker", "--host=unix://" + socketPath, "system", "dial-stdio"}
}
}
actualCmd, err := sp.Command(tc.sshFlags, commandAndArgs...)
if tc.expectedError == "" {
assert.NilError(t, err)
assert.Check(t, is.DeepEqual(actualCmd, tc.expectedCmd), "%+#v", actualCmd)
} else {
assert.Check(t, is.Error(err, tc.expectedError))
assert.Check(t, is.Nil(actualCmd))
}
})
}
}

View File

@ -33,5 +33,8 @@ func IsEnabled() bool {
// The default is to log to the debug level which is only
// enabled when debugging is enabled.
var OTELErrorHandler otel.ErrorHandler = otel.ErrorHandlerFunc(func(err error) {
if err == nil {
return
}
logrus.WithError(err).Debug("otel error")
})

View File

@ -1,5 +1,5 @@
variable "GO_VERSION" {
default = "1.24.4"
default = "1.24.5"
}
variable "VERSION" {
default = ""

View File

@ -1,6 +1,6 @@
# syntax=docker/dockerfile:1
ARG GO_VERSION=1.24.4
ARG GO_VERSION=1.24.5
ARG ALPINE_VERSION=3.21
# BUILDX_VERSION sets the version of buildx to install in the dev container.

View File

@ -1,6 +1,6 @@
# syntax=docker/dockerfile:1
ARG GO_VERSION=1.24.4
ARG GO_VERSION=1.24.5
ARG ALPINE_VERSION=3.21
ARG GOLANGCI_LINT_VERSION=v2.1.5

View File

@ -1,6 +1,6 @@
# syntax=docker/dockerfile:1
ARG GO_VERSION=1.24.4
ARG GO_VERSION=1.24.5
ARG ALPINE_VERSION=3.21
ARG MODOUTDATED_VERSION=v0.8.0

View File

@ -937,15 +937,14 @@ PS C:\> docker run --device=class/86E0D1E0-8089-11D0-9CE4-08003E301F73 mcr.micro
#### CDI devices
> [!NOTE]
> The CDI feature is experimental, and potentially subject to change.
> CDI is currently only supported for Linux containers.
[Container Device Interface
(CDI)](https://github.com/cncf-tags/container-device-interface/blob/main/SPEC.md)
is a standardized mechanism for container runtimes to create containers which
are able to interact with third party devices.
CDI is currently only supported for Linux containers and is enabled by default
since Docker Engine 28.3.0.
With CDI, device configurations are declaratively defined using a JSON or YAML
file. In addition to enabling the container to interact with the device node,
it also lets you specify additional configuration for the device, such as

View File

@ -840,42 +840,49 @@ $ docker run -it --add-host host.docker.internal:host-gateway \
PING host.docker.internal (2001:db8::1111): 56 data bytes
```
### Enable CDI devices
> [!NOTE]
> This is experimental feature and as such doesn't represent a stable API.
>
> This feature isn't enabled by default. To this feature, set `features.cdi` to
> `true` in the `daemon.json` configuration file.
### Configure CDI devices
Container Device Interface (CDI) is a
[standardized](https://github.com/cncf-tags/container-device-interface/blob/main/SPEC.md)
mechanism for container runtimes to create containers which are able to
interact with third party devices.
CDI is currently only supported for Linux containers and is enabled by default
since Docker Engine 28.3.0.
The Docker daemon supports running containers with CDI devices if the requested
device specifications are available on the filesystem of the daemon.
The default specification directors are:
The default specification directories are:
- `/etc/cdi/` for static CDI Specs
- `/var/run/cdi` for generated CDI Specs
Alternatively, you can set custom locations for CDI specifications using the
#### Set custom locations
To set custom locations for CDI specifications, use the
`cdi-spec-dirs` option in the `daemon.json` configuration file, or the
`--cdi-spec-dir` flag for the `dockerd` CLI.
`--cdi-spec-dir` flag for the `dockerd` CLI:
```json
{
"features": {
"cdi": true
},
"cdi-spec-dirs": ["/etc/cdi/", "/var/run/cdi"]
}
```
When CDI is enabled for a daemon, you can view the configured CDI specification
directories using the `docker info` command.
You can view the configured CDI specification directories using the `docker info` command.
#### Disable CDI devices
The feature in enabled by default. To disable it, use the `cdi` options in the `deamon.json` file:
```json
"features": {
"cdi": false
},
```
To check the status of the CDI devices, run `docker info`.
#### Daemon logging format {#log-format}

View File

@ -240,7 +240,7 @@ func TestPromptExitCode(t *testing.T) {
case <-writeDone:
buf.Reset()
assert.NilError(t, bufioWriter.Flush())
assert.Equal(t, buf.String(), "\n", "expected a new line after the process exits from SIGINT")
assert.Assert(t, strings.HasSuffix(buf.String(), "\n"), "expected a new line after the process exits from SIGINT")
}
})
}

View File

@ -1,6 +1,6 @@
# syntax=docker/dockerfile:1
ARG GO_VERSION=1.24.4
ARG GO_VERSION=1.24.5
FROM golang:${GO_VERSION}-alpine AS generated
ENV GOTOOLCHAIN=local

View File

@ -15,19 +15,39 @@ var InfoHeader = Str{
Fancy: aec.Bold.Apply(aec.LightCyanB.Apply(aec.BlackF.Apply("i")) + " " + aec.LightCyanF.Apply("Info → ")),
}
func (o Output) PrintNote(format string, args ...any) {
type options struct {
header Str
}
type noteOptions func(o *options)
func withHeader(header Str) noteOptions {
return func(o *options) {
o.header = header
}
}
func (o Output) printNoteWithOptions(format string, args []any, opts ...noteOptions) {
if o.isTerminal {
// TODO: Handle all flags
format = strings.ReplaceAll(format, "--platform", ColorFlag.Apply("--platform"))
}
header := o.Sprint(InfoHeader)
opt := &options{
header: InfoHeader,
}
_, _ = fmt.Fprint(o, "\n", header)
for _, override := range opts {
override(opt)
}
h := o.Sprint(opt.header)
_, _ = fmt.Fprint(o, "\n", h)
s := fmt.Sprintf(format, args...)
for idx, line := range strings.Split(s, "\n") {
if idx > 0 {
_, _ = fmt.Fprint(o, strings.Repeat(" ", Width(header)))
_, _ = fmt.Fprint(o, strings.Repeat(" ", Width(h)))
}
l := line
@ -37,3 +57,16 @@ func (o Output) PrintNote(format string, args ...any) {
_, _ = fmt.Fprintln(o, l)
}
}
func (o Output) PrintNote(format string, args ...any) {
o.printNoteWithOptions(format, args, withHeader(InfoHeader))
}
var warningHeader = Str{
Plain: " Warn -> ",
Fancy: aec.Bold.Apply(aec.LightYellowB.Apply(aec.BlackF.Apply("w")) + " " + ColorWarning.Apply("Warn → ")),
}
func (o Output) PrintWarning(format string, args ...any) {
o.printNoteWithOptions(format, args, withHeader(warningHeader))
}

View File

@ -15,7 +15,7 @@ require (
github.com/distribution/reference v0.6.0
github.com/docker/cli-docs-tool v0.10.0
github.com/docker/distribution v2.8.3+incompatible
github.com/docker/docker v28.3.0-rc.2+incompatible
github.com/docker/docker v28.3.1+incompatible
github.com/docker/docker-credential-helpers v0.9.3
github.com/docker/go-connections v0.5.0
github.com/docker/go-units v0.5.0

View File

@ -57,8 +57,8 @@ github.com/docker/cli-docs-tool v0.10.0/go.mod h1:5EM5zPnT2E7yCLERZmrDA234Vwn09f
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v28.3.0-rc.2+incompatible h1:4qcy6nOwGYCeEyFWs0SrH6FC1Hn6pp0z28JYNCILw8w=
github.com/docker/docker v28.3.0-rc.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v28.3.1+incompatible h1:20+BmuA9FXlCX4ByQ0vYJcUEnOmRM6XljDnFWR+jCyY=
github.com/docker/docker v28.3.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8=
github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo=
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0=

2
vendor/modules.txt vendored
View File

@ -65,7 +65,7 @@ github.com/docker/distribution/registry/client/transport
github.com/docker/distribution/registry/storage/cache
github.com/docker/distribution/registry/storage/cache/memory
github.com/docker/distribution/uuid
# github.com/docker/docker v28.3.0-rc.2+incompatible
# github.com/docker/docker v28.3.1+incompatible
## explicit
github.com/docker/docker/api
github.com/docker/docker/api/types