Compare commits
137 Commits
v28.2.0-rc
...
v28.3.2
| Author | SHA1 | Date | |
|---|---|---|---|
| 578ccf607d | |||
| 0c5e258f8a | |||
| 30cad385b6 | |||
| 9bcc88611f | |||
| 3302212263 | |||
| ccd5bd8d57 | |||
| dec07e6fdf | |||
| 28f19a9d65 | |||
| 219e5ca4f2 | |||
| 7e040d91ef | |||
| 76524e7d0e | |||
| 3262107821 | |||
| 8403869122 | |||
| 1fc7194554 | |||
| fa2a7f1536 | |||
| 350b3a6e25 | |||
| 4ea6fbf538 | |||
| 74a896f18c | |||
| 94f097da28 | |||
| e7e238eb4b | |||
| 2ba7cb8b44 | |||
| 52e1e4fb21 | |||
| 7cbee73f19 | |||
| ae6f8d0021 | |||
| 70867e7067 | |||
| 38b7060a21 | |||
| 2d46d162c1 | |||
| 88d1133224 | |||
| 82eda48066 | |||
| 52d2a9b5ae | |||
| 64a9a6d0c8 | |||
| f03fb6c40b | |||
| 5bb0d7f70c | |||
| 575d4af72f | |||
| 4b202b9e2b | |||
| 80d1959ee3 | |||
| 19a5c5c714 | |||
| c88268681e | |||
| 747cb4448f | |||
| 23fe9ec244 | |||
| 51025e12e5 | |||
| 9b83d5bbf9 | |||
| ab2d683f61 | |||
| 3664c08b73 | |||
| cccf6d8cc4 | |||
| 50da0ad9df | |||
| dbb5872b69 | |||
| e2632c5c4f | |||
| f53bb8882f | |||
| 4cb0695b49 | |||
| 3ce5130af6 | |||
| 99d4d1f386 | |||
| 398fa5aa70 | |||
| e225d51919 | |||
| 9cc2a2bf2d | |||
| 181563ee99 | |||
| 082d23d12d | |||
| 59e34093bc | |||
| a76643bca3 | |||
| f6985b7a27 | |||
| bab8478ef3 | |||
| 9f82d4a791 | |||
| 8b8f558b83 | |||
| 5487986681 | |||
| b9c563a581 | |||
| fe7fc2ff7f | |||
| 9e506545fd | |||
| 3c1bbfd82f | |||
| 0bfd4c9f29 | |||
| d8f09a1b75 | |||
| 473b248260 | |||
| b2d63d17af | |||
| e6534b4eb7 | |||
| 5c3128e95e | |||
| 879ac3f88f | |||
| 92fa1e1fc9 | |||
| 4bec3a6795 | |||
| a007d1ae24 | |||
| bbfbd54f4d | |||
| 2d21e1f7a5 | |||
| bc9be0bdea | |||
| 3fe7dc5cb4 | |||
| 9eae2a8976 | |||
| a29e53dab7 | |||
| da0c976fb0 | |||
| 17dc2880fa | |||
| bb0ca9f9ef | |||
| f567263802 | |||
| 7775f01caa | |||
| 908ff04d6e | |||
| 519bc2daa1 | |||
| b2c4452acb | |||
| 3e287df661 | |||
| f1fb3e3011 | |||
| 9c8666c106 | |||
| 21d0466ff1 | |||
| 1d3b933ac3 | |||
| 649d088ddf | |||
| e135563c1f | |||
| 026ae7a11f | |||
| 681c705be6 | |||
| bdd994b79a | |||
| 1015d15621 | |||
| ae922ec177 | |||
| 881c68f690 | |||
| 761285bfee | |||
| f23ec25a12 | |||
| 565b0a2822 | |||
| 4be9afb801 | |||
| f05025caf9 | |||
| 32a8f4c420 | |||
| 68ef98d801 | |||
| 63f2984336 | |||
| 0ee20b8543 | |||
| c07cd8aaad | |||
| bf2eea31b5 | |||
| 6e4315f599 | |||
| 97e060e7b1 | |||
| 67c0be4b05 | |||
| 7aa6c79c0a | |||
| af090512a6 | |||
| 067587bf15 | |||
| ed5d9757c9 | |||
| 89f38282fd | |||
| 9d027dff40 | |||
| d1e9946ab8 | |||
| 615ffee13b | |||
| c1313a92a0 | |||
| 18e911c958 | |||
| d65f0c9bbf | |||
| b64d9b3b19 | |||
| 062ad57ce2 | |||
| 915b3fe992 | |||
| 2ef9ab4494 | |||
| d14b7e8d09 | |||
| 6c2d023d87 | |||
| a0385bf042 |
3
.github/workflows/build.yml
vendored
3
.github/workflows/build.yml
vendored
@ -121,7 +121,8 @@ jobs:
|
||||
type=semver,pattern={{version}}
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
type=sha
|
||||
type=semver,pattern={{major}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
-
|
||||
name: Build and push image
|
||||
uses: docker/bake-action@v6
|
||||
|
||||
2
.github/workflows/codeql.yml
vendored
2
.github/workflows/codeql.yml
vendored
@ -63,7 +63,7 @@ jobs:
|
||||
name: Update Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.24.3"
|
||||
go-version: "1.24.5"
|
||||
-
|
||||
name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
|
||||
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@ -66,7 +66,7 @@ jobs:
|
||||
name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.24.3"
|
||||
go-version: "1.24.5"
|
||||
-
|
||||
name: Test
|
||||
run: |
|
||||
|
||||
@ -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.3"
|
||||
go: "1.24.5"
|
||||
|
||||
timeout: 5m
|
||||
|
||||
@ -26,39 +26,49 @@ formatters:
|
||||
|
||||
linters:
|
||||
enable:
|
||||
- asasalint # Detects "[]any" used as argument for variadic "func(...any)".
|
||||
- bodyclose
|
||||
- copyloopvar # Detects places where loop variables are copied.
|
||||
- copyloopvar # Detects places where loop variables are copied.
|
||||
- depguard
|
||||
- dogsled
|
||||
- dupword # Detects duplicate words.
|
||||
- durationcheck
|
||||
- dogsled # Detects assignments with too many blank identifiers.
|
||||
- dupword # Detects duplicate words.
|
||||
- durationcheck # Detect cases where two time.Duration values are being multiplied in possibly erroneous ways.
|
||||
- errcheck
|
||||
- errchkjson
|
||||
- errchkjson # Detects unsupported types passed to json encoding functions and reports if checks for the returned error can be omitted.
|
||||
- exhaustive # Detects missing options in enum switch statements.
|
||||
- exptostd # Detects functions from golang.org/x/exp/ that can be replaced by std functions.
|
||||
- fatcontext # Detects nested contexts in loops and function literals.
|
||||
- forbidigo
|
||||
- gocritic # Metalinter; detects bugs, performance, and styling issues.
|
||||
- gocheckcompilerdirectives # Detects invalid go compiler directive comments (//go:).
|
||||
- gocritic # Metalinter; detects bugs, performance, and styling issues.
|
||||
- gocyclo
|
||||
- gosec # Detects security problems.
|
||||
- gosec # Detects security problems.
|
||||
- govet
|
||||
- iface # Detects incorrect use of interfaces. Currently only used for "identical" interfaces in the same package.
|
||||
- importas # Enforces consistent import aliases.
|
||||
- ineffassign
|
||||
- importas # Enforces consistent import aliases.
|
||||
- misspell # Detects commonly misspelled English words in comments.
|
||||
- nakedret # Detects uses of naked returns.
|
||||
- nilerr # Detects code that returns nil even if it checks that the error is not nil.
|
||||
- nolintlint # Detects ill-formed or insufficient nolint directives.
|
||||
- perfsprint # Detects fmt.Sprintf uses that can be replaced with a faster alternative.
|
||||
- prealloc # Detects slice declarations that could potentially be pre-allocated.
|
||||
- predeclared # Detects code that shadows one of Go's predeclared identifiers
|
||||
- reassign
|
||||
- revive # Metalinter; drop-in replacement for golint.
|
||||
- makezero # Finds slice declarations with non-zero initial length.
|
||||
- mirror # Detects wrong mirror patterns of bytes/strings usage.
|
||||
- misspell # Detects commonly misspelled English words in comments.
|
||||
- nakedret # Detects uses of naked returns.
|
||||
- nilnesserr # Detects returning nil errors. It combines the features of nilness and nilerr,
|
||||
- nosprintfhostport # Detects misuse of Sprintf to construct a host with port in a URL.
|
||||
- nolintlint # Detects ill-formed or insufficient nolint directives.
|
||||
- perfsprint # Detects fmt.Sprintf uses that can be replaced with a faster alternative.
|
||||
- prealloc # Detects slice declarations that could potentially be pre-allocated.
|
||||
- predeclared # Detects code that shadows one of Go's predeclared identifiers
|
||||
- reassign # Detects reassigning a top-level variable in another package.
|
||||
- revive # Metalinter; drop-in replacement for golint.
|
||||
- spancheck # Detects mistakes with OpenTelemetry/Census spans.
|
||||
- staticcheck
|
||||
- thelper # Detects test helpers without t.Helper().
|
||||
- tparallel # Detects inappropriate usage of t.Parallel().
|
||||
- unconvert # Detects unnecessary type conversions.
|
||||
- thelper # Detects test helpers without t.Helper().
|
||||
- tparallel # Detects inappropriate usage of t.Parallel().
|
||||
- unconvert # Detects unnecessary type conversions.
|
||||
- unparam
|
||||
- unused
|
||||
- usestdlibvars
|
||||
- usetesting # Reports uses of functions with replacement inside the testing package.
|
||||
- wastedassign
|
||||
- usestdlibvars # Detects the possibility to use variables/constants from the Go standard library.
|
||||
- usetesting # Reports uses of functions with replacement inside the testing package.
|
||||
- wastedassign # Detects wasted assignment statements.
|
||||
|
||||
disable:
|
||||
- errcheck
|
||||
@ -132,9 +142,7 @@ linters:
|
||||
staticcheck:
|
||||
checks:
|
||||
- all
|
||||
- -QF1008 # Omit embedded fields from selector expression
|
||||
- -ST1020 # The documentation of an exported function should start with the function’s name
|
||||
- -ST1022 # The documentation of an exported variable or constant should start with variable’s name
|
||||
- -QF1008 # Omit embedded fields from selector expression; https://staticcheck.dev/docs/checks/#QF1008
|
||||
|
||||
revive:
|
||||
rules:
|
||||
@ -159,8 +167,8 @@ linters:
|
||||
# (unlike the "include" option), the "exclude" option does not take exclusion
|
||||
# ID's.
|
||||
#
|
||||
# These exclusion patterns are copied from the default excluses at:
|
||||
# https://github.com/golangci/golangci-lint/blob/v1.44.0/pkg/config/issues.go#L10-L104
|
||||
# These exclusion patterns are copied from the default excludes at:
|
||||
# https://github.com/golangci/golangci-lint/blob/v1.61.0/pkg/config/issues.go#L11-L104
|
||||
#
|
||||
# The default list of exclusions can be found at:
|
||||
# https://golangci-lint.run/usage/false-positives/#default-exclusions
|
||||
@ -207,6 +215,12 @@ linters:
|
||||
linters:
|
||||
- govet
|
||||
|
||||
# Ignore for cli/command/formatter/tabwriter, which is forked from go stdlib, so we want to align with it.
|
||||
- text: '^(ST1020|ST1022): comment on exported'
|
||||
path: "cli/command/formatter/tabwriter"
|
||||
linters:
|
||||
- staticcheck
|
||||
|
||||
# Log a warning if an exclusion rule is unused.
|
||||
# Default: false
|
||||
warn-unused: true
|
||||
|
||||
@ -4,7 +4,7 @@ ARG BASE_VARIANT=alpine
|
||||
ARG ALPINE_VERSION=3.21
|
||||
ARG BASE_DEBIAN_DISTRO=bookworm
|
||||
|
||||
ARG GO_VERSION=1.24.3
|
||||
ARG GO_VERSION=1.24.5
|
||||
ARG XX_VERSION=1.6.1
|
||||
ARG GOVERSIONINFO_VERSION=v1.4.1
|
||||
ARG GOTESTSUM_VERSION=v1.12.0
|
||||
@ -12,8 +12,8 @@ ARG GOTESTSUM_VERSION=v1.12.0
|
||||
# BUILDX_VERSION sets the version of buildx to use for the e2e tests.
|
||||
# It must be a tag in the docker.io/docker/buildx-bin image repository
|
||||
# on Docker Hub.
|
||||
ARG BUILDX_VERSION=0.23.0
|
||||
ARG COMPOSE_VERSION=v2.35.1
|
||||
ARG BUILDX_VERSION=0.24.0
|
||||
ARG COMPOSE_VERSION=v2.36.2
|
||||
|
||||
FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx
|
||||
|
||||
|
||||
@ -81,7 +81,7 @@ func addPluginCandidatesFromDir(res map[string][]string, d string) {
|
||||
return
|
||||
}
|
||||
for _, dentry := range dentries {
|
||||
switch dentry.Type() & os.ModeType {
|
||||
switch dentry.Type() & os.ModeType { //nolint:exhaustive,nolintlint // no need to include all possible file-modes in this list
|
||||
case 0, os.ModeSymlink:
|
||||
// Regular file or symlink, keep going
|
||||
default:
|
||||
|
||||
@ -28,7 +28,7 @@ func NewContainerCommand(dockerCli command.Cli) *cobra.Command {
|
||||
NewPortCommand(dockerCli),
|
||||
NewRenameCommand(dockerCli),
|
||||
NewRestartCommand(dockerCli),
|
||||
NewRmCommand(dockerCli),
|
||||
newRemoveCommand(dockerCli),
|
||||
NewRunCommand(dockerCli),
|
||||
NewStartCommand(dockerCli),
|
||||
NewStatsCommand(dockerCli),
|
||||
|
||||
@ -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{},
|
||||
})
|
||||
|
||||
@ -99,7 +99,7 @@ func RunExec(ctx context.Context, dockerCLI command.Cli, containerIDorName strin
|
||||
if _, err := apiClient.ContainerInspect(ctx, containerIDorName); err != nil {
|
||||
return err
|
||||
}
|
||||
if !execOptions.Detach {
|
||||
if !options.Detach {
|
||||
if err := dockerCLI.In().CheckTty(execOptions.AttachStdin, execOptions.Tty); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -117,9 +117,9 @@ func RunExec(ctx context.Context, dockerCLI command.Cli, containerIDorName strin
|
||||
return errors.New("exec ID empty")
|
||||
}
|
||||
|
||||
if execOptions.Detach {
|
||||
if options.Detach {
|
||||
return apiClient.ContainerExecStart(ctx, execID, container.ExecStartOptions{
|
||||
Detach: execOptions.Detach,
|
||||
Detach: options.Detach,
|
||||
Tty: execOptions.Tty,
|
||||
ConsoleSize: execOptions.ConsoleSize,
|
||||
})
|
||||
@ -223,7 +223,6 @@ func parseExec(execOpts ExecOptions, configFile *configfile.ConfigFile) (*contai
|
||||
Privileged: execOpts.Privileged,
|
||||
Tty: execOpts.TTY,
|
||||
Cmd: execOpts.Command,
|
||||
Detach: execOpts.Detach,
|
||||
WorkingDir: execOpts.Workdir,
|
||||
}
|
||||
|
||||
|
||||
@ -5,6 +5,7 @@ import (
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
@ -75,8 +76,7 @@ TWO=2
|
||||
{
|
||||
options: withDefaultOpts(ExecOptions{Detach: true}),
|
||||
expected: container.ExecOptions{
|
||||
Detach: true,
|
||||
Cmd: []string{"command"},
|
||||
Cmd: []string{"command"},
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -86,9 +86,8 @@ TWO=2
|
||||
Detach: true,
|
||||
}),
|
||||
expected: container.ExecOptions{
|
||||
Detach: true,
|
||||
Tty: true,
|
||||
Cmd: []string{"command"},
|
||||
Tty: true,
|
||||
Cmd: []string{"command"},
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -97,7 +96,6 @@ TWO=2
|
||||
expected: container.ExecOptions{
|
||||
Cmd: []string{"command"},
|
||||
DetachKeys: "de",
|
||||
Detach: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -109,7 +107,6 @@ TWO=2
|
||||
expected: container.ExecOptions{
|
||||
Cmd: []string{"command"},
|
||||
DetachKeys: "ab",
|
||||
Detach: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -141,10 +138,12 @@ TWO=2
|
||||
},
|
||||
}
|
||||
|
||||
for _, testcase := range testcases {
|
||||
execConfig, err := parseExec(testcase.options, &testcase.configFile)
|
||||
assert.NilError(t, err)
|
||||
assert.Check(t, is.DeepEqual(testcase.expected, *execConfig))
|
||||
for i, testcase := range testcases {
|
||||
t.Run("test "+strconv.Itoa(i+1), func(t *testing.T) {
|
||||
execConfig, err := parseExec(testcase.options, &testcase.configFile)
|
||||
assert.NilError(t, err)
|
||||
assert.Check(t, is.DeepEqual(testcase.expected, *execConfig))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -148,7 +148,7 @@ container2 -- --
|
||||
`,
|
||||
},
|
||||
}
|
||||
stats := []StatsEntry{
|
||||
entries := []StatsEntry{
|
||||
{
|
||||
Container: "container1",
|
||||
CPUPercentage: 20,
|
||||
@ -181,7 +181,7 @@ container2 -- --
|
||||
t.Run(string(tc.context.Format), func(t *testing.T) {
|
||||
var out bytes.Buffer
|
||||
tc.context.Output = &out
|
||||
err := statsFormatWrite(tc.context, stats, "windows", false)
|
||||
err := statsFormatWrite(tc.context, entries, "windows", false)
|
||||
if err != nil {
|
||||
assert.Error(t, err, tc.expected)
|
||||
} else {
|
||||
@ -273,45 +273,46 @@ func TestContainerStatsContextWriteWithNoStatsWindows(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestContainerStatsContextWriteTrunc(t *testing.T) {
|
||||
var out bytes.Buffer
|
||||
|
||||
contexts := []struct {
|
||||
tests := []struct {
|
||||
doc string
|
||||
context formatter.Context
|
||||
trunc bool
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
formatter.Context{
|
||||
doc: "non-truncated",
|
||||
context: formatter.Context{
|
||||
Format: "{{.ID}}",
|
||||
Output: &out,
|
||||
},
|
||||
false,
|
||||
"b95a83497c9161c9b444e3d70e1a9dfba0c1840d41720e146a95a08ebf938afc\n",
|
||||
expected: "b95a83497c9161c9b444e3d70e1a9dfba0c1840d41720e146a95a08ebf938afc\n",
|
||||
},
|
||||
{
|
||||
formatter.Context{
|
||||
doc: "truncated",
|
||||
context: formatter.Context{
|
||||
Format: "{{.ID}}",
|
||||
Output: &out,
|
||||
},
|
||||
true,
|
||||
"b95a83497c91\n",
|
||||
trunc: true,
|
||||
expected: "b95a83497c91\n",
|
||||
},
|
||||
}
|
||||
|
||||
for _, context := range contexts {
|
||||
statsFormatWrite(context.context, []StatsEntry{{ID: "b95a83497c9161c9b444e3d70e1a9dfba0c1840d41720e146a95a08ebf938afc"}}, "linux", context.trunc)
|
||||
assert.Check(t, is.Equal(context.expected, out.String()))
|
||||
// Clean buffer
|
||||
out.Reset()
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.doc, func(t *testing.T) {
|
||||
var out bytes.Buffer
|
||||
tc.context.Output = &out
|
||||
err := statsFormatWrite(tc.context, []StatsEntry{{ID: "b95a83497c9161c9b444e3d70e1a9dfba0c1840d41720e146a95a08ebf938afc"}}, "linux", tc.trunc)
|
||||
assert.NilError(t, err)
|
||||
assert.Check(t, is.Equal(tc.expected, out.String()))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkStatsFormat(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
stats := genStats()
|
||||
entries := genStats()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
for _, s := range stats {
|
||||
for _, s := range entries {
|
||||
_ = s.CPUPerc()
|
||||
_ = s.MemUsage()
|
||||
_ = s.MemPerc()
|
||||
@ -334,9 +335,9 @@ func genStats() []statsContext {
|
||||
NetworkTx: 987.654321,
|
||||
PidsCurrent: 123456789,
|
||||
}}
|
||||
stats := make([]statsContext, 100)
|
||||
for i := 0; i < 100; i++ {
|
||||
stats = append(stats, entry)
|
||||
entries := make([]statsContext, 0, 100)
|
||||
for range 100 {
|
||||
entries = append(entries, entry)
|
||||
}
|
||||
return stats
|
||||
return entries
|
||||
}
|
||||
|
||||
@ -84,12 +84,9 @@ func (h *hijackedIOStreamer) setupInput() (restore func(), err error) {
|
||||
|
||||
// Use sync.Once so we may call restore multiple times but ensure we
|
||||
// only restore the terminal once.
|
||||
var restoreOnce sync.Once
|
||||
restore = func() {
|
||||
restoreOnce.Do(func() {
|
||||
_ = restoreTerminal(h.streams, h.inputStream)
|
||||
})
|
||||
}
|
||||
restore = sync.OnceFunc(func() {
|
||||
_ = restoreTerminal(h.streams, h.inputStream)
|
||||
})
|
||||
|
||||
// Wrap the input to detect detach escape sequence.
|
||||
// Use default escape keys if an invalid sequence is given.
|
||||
|
||||
@ -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"
|
||||
@ -375,7 +374,7 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con
|
||||
|
||||
if parsed.Type == string(mounttypes.TypeBind) {
|
||||
if hostPart, targetPath, ok := strings.Cut(bind, ":"); ok {
|
||||
if strings.HasPrefix(hostPart, "."+string(filepath.Separator)) || hostPart == "." {
|
||||
if !filepath.IsAbs(hostPart) && strings.HasPrefix(hostPart, ".") {
|
||||
if absHostPart, err := filepath.Abs(hostPart); err == nil {
|
||||
hostPart = absHostPart
|
||||
}
|
||||
@ -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,
|
||||
|
||||
@ -27,10 +27,9 @@ func NewRmCommand(dockerCli command.Cli) *cobra.Command {
|
||||
var opts rmOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "rm [OPTIONS] CONTAINER [CONTAINER...]",
|
||||
Aliases: []string{"remove"},
|
||||
Short: "Remove one or more containers",
|
||||
Args: cli.RequiresMinArgs(1),
|
||||
Use: "rm [OPTIONS] CONTAINER [CONTAINER...]",
|
||||
Short: "Remove one or more containers",
|
||||
Args: cli.RequiresMinArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
opts.containers = args
|
||||
return runRm(cmd.Context(), dockerCli, &opts)
|
||||
@ -50,6 +49,15 @@ func NewRmCommand(dockerCli command.Cli) *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
// newRemoveCommand adds subcommands for "docker container"; unlike the
|
||||
// top-level "docker rm", it also adds a "remove" alias to support
|
||||
// "docker container remove" in addition to "docker container rm".
|
||||
func newRemoveCommand(dockerCli command.Cli) *cobra.Command {
|
||||
cmd := *NewRmCommand(dockerCli)
|
||||
cmd.Aliases = []string{"rm", "remove"}
|
||||
return &cmd
|
||||
}
|
||||
|
||||
func runRm(ctx context.Context, dockerCLI command.Cli, opts *rmOptions) error {
|
||||
apiClient := dockerCLI.Client()
|
||||
errChan := parallelOperation(ctx, opts.containers, func(ctx context.Context, ctrID string) error {
|
||||
|
||||
@ -75,8 +75,8 @@ func legacyWaitExitOrRemoved(ctx context.Context, apiClient client.APIClient, co
|
||||
|
||||
eventProcessor := func(e events.Message) bool {
|
||||
stopProcessing := false
|
||||
switch e.Status {
|
||||
case "die":
|
||||
switch e.Action { //nolint:exhaustive // TODO(thaJeztah): make exhaustive
|
||||
case events.ActionDie:
|
||||
if v, ok := e.Actor.Attributes["exitCode"]; ok {
|
||||
code, cerr := strconv.Atoi(v)
|
||||
if cerr != nil {
|
||||
@ -98,10 +98,10 @@ func legacyWaitExitOrRemoved(ctx context.Context, apiClient client.APIClient, co
|
||||
}
|
||||
}()
|
||||
}
|
||||
case "detach":
|
||||
case events.ActionDetach:
|
||||
exitCode = 0
|
||||
stopProcessing = true
|
||||
case "destroy":
|
||||
case events.ActionDestroy:
|
||||
stopProcessing = true
|
||||
}
|
||||
return stopProcessing
|
||||
|
||||
@ -11,10 +11,12 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/containerd/platforms"
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"github.com/docker/go-units"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -26,8 +28,18 @@ const (
|
||||
mountsHeader = "MOUNTS"
|
||||
localVolumes = "LOCAL VOLUMES"
|
||||
networksHeader = "NETWORKS"
|
||||
platformHeader = "PLATFORM"
|
||||
)
|
||||
|
||||
// Platform wraps a [ocispec.Platform] to implement the stringer interface.
|
||||
type Platform struct {
|
||||
ocispec.Platform
|
||||
}
|
||||
|
||||
func (p Platform) String() string {
|
||||
return platforms.FormatAll(p.Platform)
|
||||
}
|
||||
|
||||
// NewContainerFormat returns a Format for rendering using a Context
|
||||
func NewContainerFormat(source string, quiet bool, size bool) Format {
|
||||
switch source {
|
||||
@ -109,6 +121,7 @@ func NewContainerContext() *ContainerContext {
|
||||
"Mounts": mountsHeader,
|
||||
"LocalVolumes": localVolumes,
|
||||
"Networks": networksHeader,
|
||||
"Platform": platformHeader,
|
||||
}
|
||||
return &containerCtx
|
||||
}
|
||||
@ -208,6 +221,16 @@ func (c *ContainerContext) RunningFor() string {
|
||||
return units.HumanDuration(time.Now().UTC().Sub(createdAt)) + " ago"
|
||||
}
|
||||
|
||||
// Platform returns a human-readable representation of the container's
|
||||
// platform if it is available.
|
||||
func (c *ContainerContext) Platform() *Platform {
|
||||
p := c.c.ImageManifestDescriptor
|
||||
if p == nil || p.Platform == nil {
|
||||
return nil
|
||||
}
|
||||
return &Platform{*p.Platform}
|
||||
}
|
||||
|
||||
// Ports returns a comma-separated string representing open ports of the container
|
||||
// e.g. "0.0.0.0:80->9090/tcp, 9988/tcp"
|
||||
// it's used by command 'docker ps'
|
||||
|
||||
@ -14,6 +14,7 @@ import (
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
"gotest.tools/v3/golden"
|
||||
@ -425,13 +426,36 @@ func TestContainerContextWriteWithNoContainers(t *testing.T) {
|
||||
func TestContainerContextWriteJSON(t *testing.T) {
|
||||
unix := time.Now().Add(-65 * time.Second).Unix()
|
||||
containers := []container.Summary{
|
||||
{ID: "containerID1", Names: []string{"/foobar_baz"}, Image: "ubuntu", Created: unix, State: container.StateRunning},
|
||||
{ID: "containerID2", Names: []string{"/foobar_bar"}, Image: "ubuntu", Created: unix, State: container.StateRunning},
|
||||
{
|
||||
ID: "containerID1",
|
||||
Names: []string{"/foobar_baz"},
|
||||
Image: "ubuntu",
|
||||
Created: unix,
|
||||
State: container.StateRunning,
|
||||
},
|
||||
{
|
||||
ID: "containerID2",
|
||||
Names: []string{"/foobar_bar"},
|
||||
Image: "ubuntu",
|
||||
Created: unix,
|
||||
State: container.StateRunning,
|
||||
|
||||
ImageManifestDescriptor: &ocispec.Descriptor{Platform: &ocispec.Platform{Architecture: "amd64", OS: "linux"}},
|
||||
},
|
||||
{
|
||||
ID: "containerID3",
|
||||
Names: []string{"/foobar_bar"},
|
||||
Image: "ubuntu",
|
||||
Created: unix,
|
||||
State: container.StateRunning,
|
||||
|
||||
ImageManifestDescriptor: &ocispec.Descriptor{Platform: &ocispec.Platform{}},
|
||||
},
|
||||
}
|
||||
expectedCreated := time.Unix(unix, 0).String()
|
||||
expectedJSONs := []map[string]any{
|
||||
{
|
||||
"Command": "\"\"",
|
||||
"Command": `""`,
|
||||
"CreatedAt": expectedCreated,
|
||||
"ID": "containerID1",
|
||||
"Image": "ubuntu",
|
||||
@ -440,6 +464,7 @@ func TestContainerContextWriteJSON(t *testing.T) {
|
||||
"Mounts": "",
|
||||
"Names": "foobar_baz",
|
||||
"Networks": "",
|
||||
"Platform": nil,
|
||||
"Ports": "",
|
||||
"RunningFor": "About a minute ago",
|
||||
"Size": "0B",
|
||||
@ -447,7 +472,7 @@ func TestContainerContextWriteJSON(t *testing.T) {
|
||||
"Status": "",
|
||||
},
|
||||
{
|
||||
"Command": "\"\"",
|
||||
"Command": `""`,
|
||||
"CreatedAt": expectedCreated,
|
||||
"ID": "containerID2",
|
||||
"Image": "ubuntu",
|
||||
@ -456,6 +481,24 @@ func TestContainerContextWriteJSON(t *testing.T) {
|
||||
"Mounts": "",
|
||||
"Names": "foobar_bar",
|
||||
"Networks": "",
|
||||
"Platform": map[string]any{"architecture": "amd64", "os": "linux"},
|
||||
"Ports": "",
|
||||
"RunningFor": "About a minute ago",
|
||||
"Size": "0B",
|
||||
"State": "running",
|
||||
"Status": "",
|
||||
},
|
||||
{
|
||||
"Command": `""`,
|
||||
"CreatedAt": expectedCreated,
|
||||
"ID": "containerID3",
|
||||
"Image": "ubuntu",
|
||||
"Labels": "",
|
||||
"LocalVolumes": "0",
|
||||
"Mounts": "",
|
||||
"Names": "foobar_bar",
|
||||
"Networks": "",
|
||||
"Platform": map[string]any{"architecture": "", "os": ""},
|
||||
"Ports": "",
|
||||
"RunningFor": "About a minute ago",
|
||||
"Size": "0B",
|
||||
|
||||
@ -20,6 +20,8 @@ func charWidth(r rune) int {
|
||||
switch width.LookupRune(r).Kind() {
|
||||
case width.EastAsianWide, width.EastAsianFullwidth:
|
||||
return 2
|
||||
case width.Neutral, width.EastAsianAmbiguous, width.EastAsianNarrow, width.EastAsianHalfwidth:
|
||||
return 1
|
||||
default:
|
||||
return 1
|
||||
}
|
||||
|
||||
@ -3,7 +3,6 @@ package idresolver
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/client"
|
||||
)
|
||||
@ -21,7 +20,7 @@ func (cli *fakeClient) NodeInspectWithRaw(_ context.Context, nodeID string) (swa
|
||||
return swarm.Node{}, []byte{}, nil
|
||||
}
|
||||
|
||||
func (cli *fakeClient) ServiceInspectWithRaw(_ context.Context, serviceID string, _ types.ServiceInspectOptions) (swarm.Service, []byte, error) {
|
||||
func (cli *fakeClient) ServiceInspectWithRaw(_ context.Context, serviceID string, _ swarm.ServiceInspectOptions) (swarm.Service, []byte, error) {
|
||||
if cli.serviceInspectFunc != nil {
|
||||
return cli.serviceInspectFunc(serviceID)
|
||||
}
|
||||
|
||||
@ -6,7 +6,6 @@ package idresolver
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/pkg/errors"
|
||||
@ -44,7 +43,7 @@ func (r *IDResolver) get(ctx context.Context, t any, id string) (string, error)
|
||||
}
|
||||
return id, nil
|
||||
case swarm.Service:
|
||||
service, _, err := r.client.ServiceInspectWithRaw(ctx, id, types.ServiceInspectOptions{})
|
||||
service, _, err := r.client.ServiceInspectWithRaw(ctx, id, swarm.ServiceInspectOptions{})
|
||||
if err != nil {
|
||||
// TODO(thaJeztah): should error-handling be more specific, or is it ok to ignore any error?
|
||||
return id, nil //nolint:nilerr // ignore nil-error being returned, as this is a best-effort.
|
||||
|
||||
@ -74,6 +74,8 @@ Image index won't be pushed, meaning that other manifests, including attestation
|
||||
}
|
||||
|
||||
// runPush performs a push against the engine based on the specified options.
|
||||
//
|
||||
//nolint:gocyclo // ignore cyclomatic complexity 17 of func `runPush` is high (> 16) for now.
|
||||
func runPush(ctx context.Context, dockerCli command.Cli, opts pushOptions) error {
|
||||
var platform *ocispec.Platform
|
||||
out := tui.NewOutput(dockerCli.Out())
|
||||
@ -113,7 +115,10 @@ To push the complete multi-platform image, remove the --platform flag.
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
requestPrivilege := command.RegistryAuthenticationPrivilegedFunc(dockerCli, repoInfo.Index, "push")
|
||||
var requestPrivilege registrytypes.RequestAuthConfig
|
||||
if dockerCli.In().IsTerminal() {
|
||||
requestPrivilege = command.RegistryAuthenticationPrivilegedFunc(dockerCli, repoInfo.Index, "push")
|
||||
}
|
||||
options := image.PushOptions{
|
||||
All: opts.all,
|
||||
RegistryAuth: encodedAuth,
|
||||
|
||||
@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
cerrdefs "github.com/containerd/errdefs"
|
||||
"github.com/containerd/platforms"
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
@ -14,22 +15,23 @@ import (
|
||||
)
|
||||
|
||||
type removeOptions struct {
|
||||
force bool
|
||||
noPrune bool
|
||||
force bool
|
||||
noPrune bool
|
||||
platforms []string
|
||||
}
|
||||
|
||||
// NewRemoveCommand creates a new `docker remove` command
|
||||
func NewRemoveCommand(dockerCli command.Cli) *cobra.Command {
|
||||
var opts removeOptions
|
||||
func NewRemoveCommand(dockerCLI command.Cli) *cobra.Command {
|
||||
var options removeOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "rmi [OPTIONS] IMAGE [IMAGE...]",
|
||||
Short: "Remove one or more images",
|
||||
Args: cli.RequiresMinArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runRemove(cmd.Context(), dockerCli, opts, args)
|
||||
return runRemove(cmd.Context(), dockerCLI, options, args)
|
||||
},
|
||||
ValidArgsFunction: completion.ImageNames(dockerCli, -1),
|
||||
ValidArgsFunction: completion.ImageNames(dockerCLI, -1),
|
||||
Annotations: map[string]string{
|
||||
"aliases": "docker image rm, docker image remove, docker rmi",
|
||||
},
|
||||
@ -37,9 +39,14 @@ func NewRemoveCommand(dockerCli command.Cli) *cobra.Command {
|
||||
|
||||
flags := cmd.Flags()
|
||||
|
||||
flags.BoolVarP(&opts.force, "force", "f", false, "Force removal of the image")
|
||||
flags.BoolVar(&opts.noPrune, "no-prune", false, "Do not delete untagged parents")
|
||||
flags.BoolVarP(&options.force, "force", "f", false, "Force removal of the image")
|
||||
flags.BoolVar(&options.noPrune, "no-prune", false, "Do not delete untagged parents")
|
||||
|
||||
// TODO(thaJeztah): create a "platforms" option for this (including validation / parsing).
|
||||
flags.StringSliceVar(&options.platforms, "platform", nil, `Remove only the given platform variant. Formatted as "os[/arch[/variant]]" (e.g., "linux/amd64")`)
|
||||
_ = flags.SetAnnotation("platform", "version", []string{"1.50"})
|
||||
|
||||
_ = cmd.RegisterFlagCompletionFunc("platform", completion.Platforms)
|
||||
return cmd
|
||||
}
|
||||
|
||||
@ -58,6 +65,14 @@ func runRemove(ctx context.Context, dockerCLI command.Cli, opts removeOptions, i
|
||||
PruneChildren: !opts.noPrune,
|
||||
}
|
||||
|
||||
for _, v := range opts.platforms {
|
||||
p, err := platforms.Parse(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
options.Platforms = append(options.Platforms, p)
|
||||
}
|
||||
|
||||
// TODO(thaJeztah): this logic can likely be simplified: do we want to print "not found" errors at all when using "force"?
|
||||
fatalErr := false
|
||||
var errs []error
|
||||
|
||||
@ -170,10 +170,16 @@ func getPossibleChips(view treeView) (chips []imageChip) {
|
||||
|
||||
var possible []imageChip
|
||||
for _, img := range view.images {
|
||||
details := []imageDetails{img.Details}
|
||||
|
||||
for _, c := range img.Children {
|
||||
details = append(details, c.Details)
|
||||
}
|
||||
|
||||
for _, d := range details {
|
||||
for idx := len(remaining) - 1; idx >= 0; idx-- {
|
||||
chip := remaining[idx]
|
||||
if chip.check(&c.Details) {
|
||||
if chip.check(&d) {
|
||||
possible = append(possible, chip)
|
||||
remaining = append(remaining[:idx], remaining[idx+1:]...)
|
||||
}
|
||||
|
||||
@ -149,7 +149,10 @@ func imagePullPrivileged(ctx context.Context, cli command.Cli, imgRefAndAuth tru
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
requestPrivilege := command.RegistryAuthenticationPrivilegedFunc(cli, imgRefAndAuth.RepoInfo().Index, "pull")
|
||||
var requestPrivilege registrytypes.RequestAuthConfig
|
||||
if cli.In().IsTerminal() {
|
||||
requestPrivilege = command.RegistryAuthenticationPrivilegedFunc(cli, imgRefAndAuth.RepoInfo().Index, "pull")
|
||||
}
|
||||
responseBody, err := cli.Client().ImagePull(ctx, reference.FamiliarString(imgRefAndAuth.Reference()), image.PullOptions{
|
||||
RegistryAuth: encodedAuth,
|
||||
PrivilegeFunc: requestPrivilege,
|
||||
|
||||
@ -3,7 +3,6 @@ package node
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/api/types/system"
|
||||
"github.com/docker/docker/client"
|
||||
@ -17,8 +16,8 @@ type fakeClient struct {
|
||||
nodeRemoveFunc func() error
|
||||
nodeUpdateFunc func(nodeID string, version swarm.Version, node swarm.NodeSpec) error
|
||||
taskInspectFunc func(taskID string) (swarm.Task, []byte, error)
|
||||
taskListFunc func(options types.TaskListOptions) ([]swarm.Task, error)
|
||||
serviceInspectFunc func(ctx context.Context, serviceID string, opts types.ServiceInspectOptions) (swarm.Service, []byte, error)
|
||||
taskListFunc func(options swarm.TaskListOptions) ([]swarm.Task, error)
|
||||
serviceInspectFunc func(ctx context.Context, serviceID string, opts swarm.ServiceInspectOptions) (swarm.Service, []byte, error)
|
||||
}
|
||||
|
||||
func (cli *fakeClient) NodeInspectWithRaw(context.Context, string) (swarm.Node, []byte, error) {
|
||||
@ -28,14 +27,14 @@ func (cli *fakeClient) NodeInspectWithRaw(context.Context, string) (swarm.Node,
|
||||
return swarm.Node{}, []byte{}, nil
|
||||
}
|
||||
|
||||
func (cli *fakeClient) NodeList(context.Context, types.NodeListOptions) ([]swarm.Node, error) {
|
||||
func (cli *fakeClient) NodeList(context.Context, swarm.NodeListOptions) ([]swarm.Node, error) {
|
||||
if cli.nodeListFunc != nil {
|
||||
return cli.nodeListFunc()
|
||||
}
|
||||
return []swarm.Node{}, nil
|
||||
}
|
||||
|
||||
func (cli *fakeClient) NodeRemove(context.Context, string, types.NodeRemoveOptions) error {
|
||||
func (cli *fakeClient) NodeRemove(context.Context, string, swarm.NodeRemoveOptions) error {
|
||||
if cli.nodeRemoveFunc != nil {
|
||||
return cli.nodeRemoveFunc()
|
||||
}
|
||||
@ -63,14 +62,14 @@ func (cli *fakeClient) TaskInspectWithRaw(_ context.Context, taskID string) (swa
|
||||
return swarm.Task{}, []byte{}, nil
|
||||
}
|
||||
|
||||
func (cli *fakeClient) TaskList(_ context.Context, options types.TaskListOptions) ([]swarm.Task, error) {
|
||||
func (cli *fakeClient) TaskList(_ context.Context, options swarm.TaskListOptions) ([]swarm.Task, error) {
|
||||
if cli.taskListFunc != nil {
|
||||
return cli.taskListFunc(options)
|
||||
}
|
||||
return []swarm.Task{}, nil
|
||||
}
|
||||
|
||||
func (cli *fakeClient) ServiceInspectWithRaw(ctx context.Context, serviceID string, opts types.ServiceInspectOptions) (swarm.Service, []byte, error) {
|
||||
func (cli *fakeClient) ServiceInspectWithRaw(ctx context.Context, serviceID string, opts swarm.ServiceInspectOptions) (swarm.Service, []byte, error) {
|
||||
if cli.serviceInspectFunc != nil {
|
||||
return cli.serviceInspectFunc(ctx, serviceID, opts)
|
||||
}
|
||||
|
||||
@ -6,7 +6,7 @@ import (
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@ -48,7 +48,7 @@ func Reference(ctx context.Context, apiClient client.APIClient, ref string) (str
|
||||
// If there's no node ID in /info, the node probably
|
||||
// isn't a manager. Call a swarm-specific endpoint to
|
||||
// get a more specific error message.
|
||||
_, err = apiClient.NodeList(ctx, types.NodeListOptions{})
|
||||
_, err = apiClient.NodeList(ctx, swarm.NodeListOptions{})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
@ -4,7 +4,7 @@ import (
|
||||
"os"
|
||||
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@ -17,7 +17,7 @@ func completeNodeNames(dockerCLI completion.APIClientProvider) cobra.CompletionF
|
||||
// https://github.com/docker/cli/blob/f9ced58158d5e0b358052432244b483774a1983d/contrib/completion/bash/docker#L41-L43
|
||||
showIDs := os.Getenv("DOCKER_COMPLETION_SHOW_NODE_IDS") == "yes"
|
||||
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
list, err := dockerCLI.Client().NodeList(cmd.Context(), types.NodeListOptions{})
|
||||
list, err := dockerCLI.Client().NodeList(cmd.Context(), swarm.NodeListOptions{})
|
||||
if err != nil {
|
||||
return nil, cobra.ShellCompDirectiveError
|
||||
}
|
||||
|
||||
@ -10,7 +10,7 @@ import (
|
||||
"github.com/docker/cli/cli/command/formatter"
|
||||
flagsHelper "github.com/docker/cli/cli/flags"
|
||||
"github.com/docker/cli/opts"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/api/types/system"
|
||||
"github.com/fvbommel/sortorder"
|
||||
"github.com/spf13/cobra"
|
||||
@ -55,7 +55,7 @@ func runList(ctx context.Context, dockerCli command.Cli, options listOptions) er
|
||||
|
||||
nodes, err := client.NodeList(
|
||||
ctx,
|
||||
types.NodeListOptions{Filters: options.filter.Value()})
|
||||
swarm.NodeListOptions{Filters: options.filter.Value()})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -10,7 +10,6 @@ import (
|
||||
"github.com/docker/cli/cli/command/idresolver"
|
||||
"github.com/docker/cli/cli/command/task"
|
||||
"github.com/docker/cli/opts"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
@ -84,7 +83,7 @@ func runPs(ctx context.Context, dockerCli command.Cli, options psOptions) error
|
||||
filter := options.filter.Value()
|
||||
filter.Add("node", node.ID)
|
||||
|
||||
nodeTasks, err := client.TaskList(ctx, types.TaskListOptions{Filters: filter})
|
||||
nodeTasks, err := client.TaskList(ctx, swarm.TaskListOptions{Filters: filter})
|
||||
if err != nil {
|
||||
errs = append(errs, err.Error())
|
||||
continue
|
||||
|
||||
@ -10,7 +10,6 @@ import (
|
||||
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/docker/cli/internal/test/builders"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/api/types/system"
|
||||
"gotest.tools/v3/assert"
|
||||
@ -23,7 +22,7 @@ func TestNodePsErrors(t *testing.T) {
|
||||
flags map[string]string
|
||||
infoFunc func() (system.Info, error)
|
||||
nodeInspectFunc func() (swarm.Node, []byte, error)
|
||||
taskListFunc func(options types.TaskListOptions) ([]swarm.Task, error)
|
||||
taskListFunc func(options swarm.TaskListOptions) ([]swarm.Task, error)
|
||||
taskInspectFunc func(taskID string) (swarm.Task, []byte, error)
|
||||
expectedError string
|
||||
}{
|
||||
@ -42,7 +41,7 @@ func TestNodePsErrors(t *testing.T) {
|
||||
},
|
||||
{
|
||||
args: []string{"nodeID"},
|
||||
taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) {
|
||||
taskListFunc: func(options swarm.TaskListOptions) ([]swarm.Task, error) {
|
||||
return []swarm.Task{}, errors.New("error returning the task list")
|
||||
},
|
||||
expectedError: "error returning the task list",
|
||||
@ -73,9 +72,9 @@ func TestNodePs(t *testing.T) {
|
||||
flags map[string]string
|
||||
infoFunc func() (system.Info, error)
|
||||
nodeInspectFunc func() (swarm.Node, []byte, error)
|
||||
taskListFunc func(options types.TaskListOptions) ([]swarm.Task, error)
|
||||
taskListFunc func(options swarm.TaskListOptions) ([]swarm.Task, error)
|
||||
taskInspectFunc func(taskID string) (swarm.Task, []byte, error)
|
||||
serviceInspectFunc func(ctx context.Context, serviceID string, opts types.ServiceInspectOptions) (swarm.Service, []byte, error)
|
||||
serviceInspectFunc func(ctx context.Context, serviceID string, opts swarm.ServiceInspectOptions) (swarm.Service, []byte, error)
|
||||
}{
|
||||
{
|
||||
name: "simple",
|
||||
@ -83,7 +82,7 @@ func TestNodePs(t *testing.T) {
|
||||
nodeInspectFunc: func() (swarm.Node, []byte, error) {
|
||||
return *builders.Node(), []byte{}, nil
|
||||
},
|
||||
taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) {
|
||||
taskListFunc: func(options swarm.TaskListOptions) ([]swarm.Task, error) {
|
||||
return []swarm.Task{
|
||||
*builders.Task(builders.WithStatus(builders.Timestamp(time.Now().Add(-2*time.Hour)), builders.PortStatus([]swarm.PortConfig{
|
||||
{
|
||||
@ -94,7 +93,7 @@ func TestNodePs(t *testing.T) {
|
||||
}))),
|
||||
}, nil
|
||||
},
|
||||
serviceInspectFunc: func(ctx context.Context, serviceID string, opts types.ServiceInspectOptions) (swarm.Service, []byte, error) {
|
||||
serviceInspectFunc: func(ctx context.Context, serviceID string, opts swarm.ServiceInspectOptions) (swarm.Service, []byte, error) {
|
||||
return swarm.Service{
|
||||
ID: serviceID,
|
||||
Spec: swarm.ServiceSpec{
|
||||
@ -111,7 +110,7 @@ func TestNodePs(t *testing.T) {
|
||||
nodeInspectFunc: func() (swarm.Node, []byte, error) {
|
||||
return *builders.Node(), []byte{}, nil
|
||||
},
|
||||
taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) {
|
||||
taskListFunc: func(options swarm.TaskListOptions) ([]swarm.Task, error) {
|
||||
return []swarm.Task{
|
||||
*builders.Task(builders.TaskID("taskID1"), builders.TaskServiceID("failure"),
|
||||
builders.WithStatus(builders.Timestamp(time.Now().Add(-2*time.Hour)), builders.StatusErr("a task error"))),
|
||||
@ -121,7 +120,7 @@ func TestNodePs(t *testing.T) {
|
||||
builders.WithStatus(builders.Timestamp(time.Now().Add(-4*time.Hour)), builders.StatusErr("a task error"))),
|
||||
}, nil
|
||||
},
|
||||
serviceInspectFunc: func(ctx context.Context, serviceID string, opts types.ServiceInspectOptions) (swarm.Service, []byte, error) {
|
||||
serviceInspectFunc: func(ctx context.Context, serviceID string, opts swarm.ServiceInspectOptions) (swarm.Service, []byte, error) {
|
||||
return swarm.Service{
|
||||
ID: serviceID,
|
||||
Spec: swarm.ServiceSpec{
|
||||
|
||||
@ -7,7 +7,7 @@ import (
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@ -38,7 +38,7 @@ func runRemove(ctx context.Context, dockerCLI command.Cli, nodeIDs []string, opt
|
||||
|
||||
var errs []error
|
||||
for _, id := range nodeIDs {
|
||||
if err := apiClient.NodeRemove(ctx, id, types.NodeRemoveOptions{Force: opts.force}); err != nil {
|
||||
if err := apiClient.NodeRemove(ctx, id, swarm.NodeRemoveOptions{Force: opts.force}); err != nil {
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
}
|
||||
|
||||
@ -90,13 +90,18 @@ func buildPullConfig(ctx context.Context, dockerCli command.Cli, opts pluginOpti
|
||||
return types.PluginInstallOptions{}, err
|
||||
}
|
||||
|
||||
var requestPrivilege registrytypes.RequestAuthConfig
|
||||
if dockerCli.In().IsTerminal() {
|
||||
requestPrivilege = command.RegistryAuthenticationPrivilegedFunc(dockerCli, repoInfo.Index, cmdName)
|
||||
}
|
||||
|
||||
options := types.PluginInstallOptions{
|
||||
RegistryAuth: encodedAuth,
|
||||
RemoteRef: remote,
|
||||
Disabled: opts.disable,
|
||||
AcceptAllPermissions: opts.grantPerms,
|
||||
AcceptPermissionsFunc: acceptPrivileges(dockerCli, opts.remote),
|
||||
PrivilegeFunc: command.RegistryAuthenticationPrivilegedFunc(dockerCli, repoInfo.Index, cmdName),
|
||||
PrivilegeFunc: requestPrivilege,
|
||||
Args: opts.args,
|
||||
}
|
||||
return options, nil
|
||||
|
||||
@ -35,7 +35,7 @@ const (
|
||||
const authConfigKey = "https://index.docker.io/v1/"
|
||||
|
||||
// RegistryAuthenticationPrivilegedFunc returns a RequestPrivilegeFunc from the specified registry index info
|
||||
// for the given command.
|
||||
// for the given command to prompt the user for username and password.
|
||||
func RegistryAuthenticationPrivilegedFunc(cli Cli, index *registrytypes.IndexInfo, cmdName string) registrytypes.RequestAuthConfig {
|
||||
configKey := getAuthConfigKey(index.Name)
|
||||
isDefaultRegistry := configKey == authConfigKey || index.Official
|
||||
@ -43,7 +43,7 @@ func RegistryAuthenticationPrivilegedFunc(cli Cli, index *registrytypes.IndexInf
|
||||
_, _ = fmt.Fprintf(cli.Out(), "\nLogin prior to %s:\n", cmdName)
|
||||
authConfig, err := GetDefaultAuthConfig(cli.ConfigFile(), true, configKey, isDefaultRegistry)
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(cli.Err(), "Unable to retrieve stored credentials for %s, error: %s.\n", authConfigKey, err)
|
||||
_, _ = fmt.Fprintf(cli.Err(), "Unable to retrieve stored credentials for %s, error: %s.\n", configKey, err)
|
||||
}
|
||||
|
||||
select {
|
||||
@ -52,7 +52,7 @@ func RegistryAuthenticationPrivilegedFunc(cli Cli, index *registrytypes.IndexInf
|
||||
default:
|
||||
}
|
||||
|
||||
authConfig, err = PromptUserForCredentials(ctx, cli, "", "", authConfig.Username, authConfigKey)
|
||||
authConfig, err = PromptUserForCredentials(ctx, cli, "", "", authConfig.Username, configKey)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
@ -42,7 +42,6 @@ func SearchWrite(ctx formatter.Context, results []registrytypes.SearchResult) er
|
||||
"Description": formatter.DescriptionHeader,
|
||||
"StarCount": starsHeader,
|
||||
"IsOfficial": officialHeader,
|
||||
"IsAutomated": automatedHeader,
|
||||
}
|
||||
return ctx.Write(&searchCtx, render)
|
||||
}
|
||||
@ -92,10 +91,3 @@ func (c *searchContext) formatBool(value bool) string {
|
||||
func (c *searchContext) IsOfficial() string {
|
||||
return c.formatBool(c.s.IsOfficial)
|
||||
}
|
||||
|
||||
// IsAutomated formats the IsAutomated field for printing.
|
||||
//
|
||||
// Deprecated: the "is_automated" field is deprecated and will always be "false" in the future.
|
||||
func (c *searchContext) IsAutomated() string {
|
||||
return c.formatBool(c.s.IsAutomated) //nolint:nolintlint,staticcheck // ignore SA1019 (IsAutomated is deprecated).
|
||||
}
|
||||
|
||||
@ -45,19 +45,6 @@ func TestSearchContext(t *testing.T) {
|
||||
},
|
||||
call: ctx.IsOfficial,
|
||||
},
|
||||
{
|
||||
searchCtx: searchContext{
|
||||
s: registrytypes.SearchResult{IsAutomated: true}, //nolint:nolintlint,staticcheck // ignore SA1019 (IsAutomated is deprecated).
|
||||
},
|
||||
expValue: "[OK]",
|
||||
call: ctx.IsAutomated, //nolint:nolintlint,staticcheck // ignore SA1019 (IsAutomated is deprecated).
|
||||
},
|
||||
{
|
||||
searchCtx: searchContext{
|
||||
s: registrytypes.SearchResult{},
|
||||
},
|
||||
call: ctx.IsAutomated, //nolint:nolintlint,staticcheck // ignore SA1019 (IsAutomated is deprecated).
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
@ -157,8 +144,8 @@ func TestSearchContextWrite(t *testing.T) {
|
||||
{
|
||||
doc: "JSON format",
|
||||
format: "{{json .}}",
|
||||
expected: `{"Description":"Official build","IsAutomated":"false","IsOfficial":"true","Name":"result1","StarCount":"5000"}
|
||||
{"Description":"Not official","IsAutomated":"true","IsOfficial":"false","Name":"result2","StarCount":"5"}
|
||||
expected: `{"Description":"Official build","IsOfficial":"true","Name":"result1","StarCount":"5000"}
|
||||
{"Description":"Not official","IsOfficial":"false","Name":"result2","StarCount":"5"}
|
||||
`,
|
||||
},
|
||||
{
|
||||
@ -199,7 +186,7 @@ result2 5
|
||||
|
||||
results := []registrytypes.SearchResult{
|
||||
{Name: "result1", Description: "Official build", StarCount: 5000, IsOfficial: true},
|
||||
{Name: "result2", Description: "Not official", StarCount: 5, IsAutomated: true},
|
||||
{Name: "result2", Description: "Not official", StarCount: 5},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 == "" {
|
||||
|
||||
@ -63,7 +63,10 @@ func runSearch(ctx context.Context, dockerCli command.Cli, options searchOptions
|
||||
return err
|
||||
}
|
||||
|
||||
requestPrivilege := command.RegistryAuthenticationPrivilegedFunc(dockerCli, indexInfo, "search")
|
||||
var requestPrivilege registrytypes.RequestAuthConfig
|
||||
if dockerCli.In().IsTerminal() {
|
||||
requestPrivilege = command.RegistryAuthenticationPrivilegedFunc(dockerCli, indexInfo, "search")
|
||||
}
|
||||
results, err := dockerCli.Client().ImageSearch(ctx, options.term, registrytypes.SearchOptions{
|
||||
RegistryAuth: encodedAuth,
|
||||
PrivilegeFunc: requestPrivilege,
|
||||
|
||||
18
cli/command/registry/warning.go
Normal file
18
cli/command/registry/warning.go
Normal 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)
|
||||
}
|
||||
}
|
||||
@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/docker/cli/internal/test/builders"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/api/types/system"
|
||||
@ -13,30 +12,30 @@ import (
|
||||
|
||||
type fakeClient struct {
|
||||
client.Client
|
||||
serviceInspectWithRawFunc func(ctx context.Context, serviceID string, options types.ServiceInspectOptions) (swarm.Service, []byte, error)
|
||||
serviceUpdateFunc func(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (swarm.ServiceUpdateResponse, error)
|
||||
serviceListFunc func(context.Context, types.ServiceListOptions) ([]swarm.Service, error)
|
||||
taskListFunc func(context.Context, types.TaskListOptions) ([]swarm.Task, error)
|
||||
serviceInspectWithRawFunc func(ctx context.Context, serviceID string, options swarm.ServiceInspectOptions) (swarm.Service, []byte, error)
|
||||
serviceUpdateFunc func(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options swarm.ServiceUpdateOptions) (swarm.ServiceUpdateResponse, error)
|
||||
serviceListFunc func(context.Context, swarm.ServiceListOptions) ([]swarm.Service, error)
|
||||
taskListFunc func(context.Context, swarm.TaskListOptions) ([]swarm.Task, error)
|
||||
infoFunc func(ctx context.Context) (system.Info, error)
|
||||
networkInspectFunc func(ctx context.Context, networkID string, options network.InspectOptions) (network.Inspect, error)
|
||||
nodeListFunc func(ctx context.Context, options types.NodeListOptions) ([]swarm.Node, error)
|
||||
nodeListFunc func(ctx context.Context, options swarm.NodeListOptions) ([]swarm.Node, error)
|
||||
}
|
||||
|
||||
func (f *fakeClient) NodeList(ctx context.Context, options types.NodeListOptions) ([]swarm.Node, error) {
|
||||
func (f *fakeClient) NodeList(ctx context.Context, options swarm.NodeListOptions) ([]swarm.Node, error) {
|
||||
if f.nodeListFunc != nil {
|
||||
return f.nodeListFunc(ctx, options)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (f *fakeClient) TaskList(ctx context.Context, options types.TaskListOptions) ([]swarm.Task, error) {
|
||||
func (f *fakeClient) TaskList(ctx context.Context, options swarm.TaskListOptions) ([]swarm.Task, error) {
|
||||
if f.taskListFunc != nil {
|
||||
return f.taskListFunc(ctx, options)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (f *fakeClient) ServiceInspectWithRaw(ctx context.Context, serviceID string, options types.ServiceInspectOptions) (swarm.Service, []byte, error) {
|
||||
func (f *fakeClient) ServiceInspectWithRaw(ctx context.Context, serviceID string, options swarm.ServiceInspectOptions) (swarm.Service, []byte, error) {
|
||||
if f.serviceInspectWithRawFunc != nil {
|
||||
return f.serviceInspectWithRawFunc(ctx, serviceID, options)
|
||||
}
|
||||
@ -44,7 +43,7 @@ func (f *fakeClient) ServiceInspectWithRaw(ctx context.Context, serviceID string
|
||||
return *builders.Service(builders.ServiceID(serviceID)), []byte{}, nil
|
||||
}
|
||||
|
||||
func (f *fakeClient) ServiceList(ctx context.Context, options types.ServiceListOptions) ([]swarm.Service, error) {
|
||||
func (f *fakeClient) ServiceList(ctx context.Context, options swarm.ServiceListOptions) ([]swarm.Service, error) {
|
||||
if f.serviceListFunc != nil {
|
||||
return f.serviceListFunc(ctx, options)
|
||||
}
|
||||
@ -52,7 +51,7 @@ func (f *fakeClient) ServiceList(ctx context.Context, options types.ServiceListO
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (f *fakeClient) ServiceUpdate(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (swarm.ServiceUpdateResponse, error) {
|
||||
func (f *fakeClient) ServiceUpdate(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options swarm.ServiceUpdateOptions) (swarm.ServiceUpdateResponse, error) {
|
||||
if f.serviceUpdateFunc != nil {
|
||||
return f.serviceUpdateFunc(ctx, serviceID, version, service, options)
|
||||
}
|
||||
|
||||
@ -4,7 +4,7 @@ import (
|
||||
"os"
|
||||
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@ -15,7 +15,7 @@ func completeServiceNames(dockerCLI completion.APIClientProvider) cobra.Completi
|
||||
// https://github.com/docker/cli/blob/f9ced58158d5e0b358052432244b483774a1983d/contrib/completion/bash/docker#L41-L43
|
||||
showIDs := os.Getenv("DOCKER_COMPLETION_SHOW_SERVICE_IDS") == "yes"
|
||||
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
list, err := dockerCLI.Client().ServiceList(cmd.Context(), types.ServiceListOptions{})
|
||||
list, err := dockerCLI.Client().ServiceList(cmd.Context(), swarm.ServiceListOptions{})
|
||||
if err != nil {
|
||||
return nil, cobra.ShellCompDirectiveError
|
||||
}
|
||||
|
||||
@ -8,7 +8,6 @@ import (
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
cliopts "github.com/docker/cli/opts"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/api/types/versions"
|
||||
"github.com/docker/docker/client"
|
||||
@ -102,7 +101,7 @@ func newCreateCommand(dockerCLI command.Cli) *cobra.Command {
|
||||
|
||||
func runCreate(ctx context.Context, dockerCLI command.Cli, flags *pflag.FlagSet, opts *serviceOptions) error {
|
||||
apiClient := dockerCLI.Client()
|
||||
createOpts := types.ServiceCreateOptions{}
|
||||
createOpts := swarm.ServiceCreateOptions{}
|
||||
|
||||
service, err := opts.ToService(ctx, apiClient, flags)
|
||||
if err != nil {
|
||||
|
||||
@ -13,8 +13,8 @@ import (
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/docker/cli/cli/command/formatter"
|
||||
flagsHelper "github.com/docker/cli/cli/flags"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
@ -66,7 +66,7 @@ func runInspect(ctx context.Context, dockerCli command.Cli, opts inspectOptions)
|
||||
|
||||
getRef := func(ref string) (any, []byte, error) {
|
||||
// Service inspect shows defaults values in empty fields.
|
||||
service, _, err := client.ServiceInspectWithRaw(ctx, ref, types.ServiceInspectOptions{InsertDefaults: true})
|
||||
service, _, err := client.ServiceInspectWithRaw(ctx, ref, swarm.ServiceInspectOptions{InsertDefaults: true})
|
||||
if err == nil || !cerrdefs.IsNotFound(err) {
|
||||
return service, nil, err
|
||||
}
|
||||
|
||||
@ -9,7 +9,6 @@ import (
|
||||
"github.com/docker/cli/cli/command/formatter"
|
||||
flagsHelper "github.com/docker/cli/cli/flags"
|
||||
"github.com/docker/cli/opts"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/client"
|
||||
@ -57,7 +56,7 @@ func runList(ctx context.Context, dockerCLI command.Cli, options listOptions) er
|
||||
err error
|
||||
)
|
||||
|
||||
listOpts := types.ServiceListOptions{
|
||||
listOpts := swarm.ServiceListOptions{
|
||||
Filters: options.filter.Value(),
|
||||
// When not running "quiet", also get service status (number of running
|
||||
// and desired tasks). Note that this is only supported on API v1.41 and
|
||||
@ -147,7 +146,7 @@ func AppendServiceStatus(ctx context.Context, c client.APIClient, services []swa
|
||||
return services, nil
|
||||
}
|
||||
|
||||
tasks, err := c.TaskList(ctx, types.TaskListOptions{Filters: taskFilter})
|
||||
tasks, err := c.TaskList(ctx, swarm.TaskListOptions{Filters: taskFilter})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -184,7 +183,7 @@ func AppendServiceStatus(ctx context.Context, c client.APIClient, services []swa
|
||||
}
|
||||
|
||||
func getActiveNodes(ctx context.Context, c client.NodeAPIClient) (map[string]struct{}, error) {
|
||||
nodes, err := c.NodeList(ctx, types.NodeListOptions{})
|
||||
nodes, err := c.NodeList(ctx, swarm.NodeListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@ -9,7 +9,6 @@ import (
|
||||
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/docker/cli/internal/test/builders"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/api/types/versions"
|
||||
"gotest.tools/v3/assert"
|
||||
@ -19,7 +18,7 @@ import (
|
||||
|
||||
func TestServiceListOrder(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
serviceListFunc: func(ctx context.Context, options types.ServiceListOptions) ([]swarm.Service, error) {
|
||||
serviceListFunc: func(ctx context.Context, options swarm.ServiceListOptions) ([]swarm.Service, error) {
|
||||
return []swarm.Service{
|
||||
newService("a57dbe8", "service-1-foo"),
|
||||
newService("a57dbdd", "service-10-foo"),
|
||||
@ -173,7 +172,7 @@ func TestServiceListServiceStatus(t *testing.T) {
|
||||
tc.cluster = generateCluster(t, tc.opts)
|
||||
}
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
serviceListFunc: func(ctx context.Context, options types.ServiceListOptions) ([]swarm.Service, error) {
|
||||
serviceListFunc: func(ctx context.Context, options swarm.ServiceListOptions) ([]swarm.Service, error) {
|
||||
if !options.Status || versions.LessThan(tc.opts.apiVersion, "1.41") {
|
||||
// Don't return "ServiceStatus" if not requested, or on older API versions
|
||||
for i := range tc.cluster.services {
|
||||
@ -182,10 +181,10 @@ func TestServiceListServiceStatus(t *testing.T) {
|
||||
}
|
||||
return tc.cluster.services, nil
|
||||
},
|
||||
taskListFunc: func(context.Context, types.TaskListOptions) ([]swarm.Task, error) {
|
||||
taskListFunc: func(context.Context, swarm.TaskListOptions) ([]swarm.Task, error) {
|
||||
return tc.cluster.tasks, nil
|
||||
},
|
||||
nodeListFunc: func(ctx context.Context, options types.NodeListOptions) ([]swarm.Node, error) {
|
||||
nodeListFunc: func(ctx context.Context, options swarm.NodeListOptions) ([]swarm.Node, error) {
|
||||
return tc.cluster.nodes, nil
|
||||
},
|
||||
})
|
||||
|
||||
@ -15,7 +15,6 @@ import (
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/docker/cli/cli/command/idresolver"
|
||||
"github.com/docker/cli/internal/logdetails"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/client"
|
||||
@ -91,7 +90,7 @@ func runLogs(ctx context.Context, dockerCli command.Cli, opts *logsOptions) erro
|
||||
logfunc func(context.Context, string, container.LogsOptions) (io.ReadCloser, error)
|
||||
)
|
||||
|
||||
service, _, err := apiClient.ServiceInspectWithRaw(ctx, opts.target, types.ServiceInspectOptions{})
|
||||
service, _, err := apiClient.ServiceInspectWithRaw(ctx, opts.target, swarm.ServiceInspectOptions{})
|
||||
if err != nil {
|
||||
// if it's any error other than service not found, it's Real
|
||||
if !cerrdefs.IsNotFound(err) {
|
||||
|
||||
@ -11,7 +11,6 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/client"
|
||||
@ -89,7 +88,7 @@ func ServiceProgress(ctx context.Context, apiClient client.APIClient, serviceID
|
||||
)
|
||||
|
||||
for {
|
||||
service, _, err := apiClient.ServiceInspectWithRaw(ctx, serviceID, types.ServiceInspectOptions{})
|
||||
service, _, err := apiClient.ServiceInspectWithRaw(ctx, serviceID, swarm.ServiceInspectOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -143,7 +142,7 @@ func ServiceProgress(ctx context.Context, apiClient client.APIClient, serviceID
|
||||
return nil
|
||||
}
|
||||
|
||||
tasks, err := apiClient.TaskList(ctx, types.TaskListOptions{Filters: filters.NewArgs(
|
||||
tasks, err := apiClient.TaskList(ctx, swarm.TaskListOptions{Filters: filters.NewArgs(
|
||||
filters.KeyValuePair{Key: "service", Value: service.ID},
|
||||
filters.KeyValuePair{Key: "_up-to-date", Value: "true"},
|
||||
)})
|
||||
@ -217,7 +216,7 @@ func ServiceProgress(ctx context.Context, apiClient client.APIClient, serviceID
|
||||
//
|
||||
// TODO(thaJeztah): this should really be a filter on [apiClient.NodeList] instead of being filtered on the client side.
|
||||
func getActiveNodes(ctx context.Context, apiClient client.NodeAPIClient) (map[string]struct{}, error) {
|
||||
nodes, err := apiClient.NodeList(ctx, types.NodeListOptions{})
|
||||
nodes, err := apiClient.NodeList(ctx, swarm.NodeListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@ -11,8 +11,8 @@ import (
|
||||
"github.com/docker/cli/cli/command/node"
|
||||
"github.com/docker/cli/cli/command/task"
|
||||
"github.com/docker/cli/opts"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
@ -68,7 +68,7 @@ func runPS(ctx context.Context, dockerCli command.Cli, options psOptions) error
|
||||
return err
|
||||
}
|
||||
|
||||
tasks, err := apiClient.TaskList(ctx, types.TaskListOptions{Filters: filter})
|
||||
tasks, err := apiClient.TaskList(ctx, swarm.TaskListOptions{Filters: filter})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -98,11 +98,11 @@ func createFilter(ctx context.Context, apiClient client.APIClient, options psOpt
|
||||
serviceIDFilter.Add("id", service)
|
||||
serviceNameFilter.Add("name", service)
|
||||
}
|
||||
serviceByIDList, err := apiClient.ServiceList(ctx, types.ServiceListOptions{Filters: serviceIDFilter})
|
||||
serviceByIDList, err := apiClient.ServiceList(ctx, swarm.ServiceListOptions{Filters: serviceIDFilter})
|
||||
if err != nil {
|
||||
return filter, nil, err
|
||||
}
|
||||
serviceByNameList, err := apiClient.ServiceList(ctx, types.ServiceListOptions{Filters: serviceNameFilter})
|
||||
serviceByNameList, err := apiClient.ServiceList(ctx, swarm.ServiceListOptions{Filters: serviceNameFilter})
|
||||
if err != nil {
|
||||
return filter, nil, err
|
||||
}
|
||||
|
||||
@ -6,7 +6,6 @@ import (
|
||||
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/docker/cli/opts"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/api/types/system"
|
||||
@ -17,7 +16,7 @@ import (
|
||||
|
||||
func TestCreateFilter(t *testing.T) {
|
||||
client := &fakeClient{
|
||||
serviceListFunc: func(ctx context.Context, options types.ServiceListOptions) ([]swarm.Service, error) {
|
||||
serviceListFunc: func(ctx context.Context, options swarm.ServiceListOptions) ([]swarm.Service, error) {
|
||||
return []swarm.Service{
|
||||
{ID: "idmatch"},
|
||||
{ID: "idprefixmatch"},
|
||||
@ -49,7 +48,7 @@ func TestCreateFilter(t *testing.T) {
|
||||
|
||||
func TestCreateFilterWithAmbiguousIDPrefixError(t *testing.T) {
|
||||
client := &fakeClient{
|
||||
serviceListFunc: func(ctx context.Context, options types.ServiceListOptions) ([]swarm.Service, error) {
|
||||
serviceListFunc: func(ctx context.Context, options swarm.ServiceListOptions) ([]swarm.Service, error) {
|
||||
return []swarm.Service{
|
||||
{ID: "aaaone"},
|
||||
{ID: "aaatwo"},
|
||||
@ -76,7 +75,7 @@ func TestCreateFilterNoneFound(t *testing.T) {
|
||||
|
||||
func TestRunPSWarnsOnNotFound(t *testing.T) {
|
||||
client := &fakeClient{
|
||||
serviceListFunc: func(ctx context.Context, options types.ServiceListOptions) ([]swarm.Service, error) {
|
||||
serviceListFunc: func(ctx context.Context, options swarm.ServiceListOptions) ([]swarm.Service, error) {
|
||||
return []swarm.Service{
|
||||
{ID: "foo"},
|
||||
}, nil
|
||||
@ -97,10 +96,10 @@ func TestRunPSWarnsOnNotFound(t *testing.T) {
|
||||
|
||||
func TestRunPSQuiet(t *testing.T) {
|
||||
client := &fakeClient{
|
||||
serviceListFunc: func(ctx context.Context, options types.ServiceListOptions) ([]swarm.Service, error) {
|
||||
serviceListFunc: func(ctx context.Context, options swarm.ServiceListOptions) ([]swarm.Service, error) {
|
||||
return []swarm.Service{{ID: "foo"}}, nil
|
||||
},
|
||||
taskListFunc: func(ctx context.Context, options types.TaskListOptions) ([]swarm.Task, error) {
|
||||
taskListFunc: func(ctx context.Context, options swarm.TaskListOptions) ([]swarm.Task, error) {
|
||||
return []swarm.Task{{ID: "sxabyp0obqokwekpun4rjo0b3"}}, nil
|
||||
},
|
||||
}
|
||||
|
||||
@ -7,7 +7,7 @@ import (
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/api/types/versions"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
@ -43,12 +43,12 @@ func newRollbackCommand(dockerCli command.Cli) *cobra.Command {
|
||||
func runRollback(ctx context.Context, dockerCLI command.Cli, options *serviceOptions, serviceID string) error {
|
||||
apiClient := dockerCLI.Client()
|
||||
|
||||
service, _, err := apiClient.ServiceInspectWithRaw(ctx, serviceID, types.ServiceInspectOptions{})
|
||||
service, _, err := apiClient.ServiceInspectWithRaw(ctx, serviceID, swarm.ServiceInspectOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
response, err := apiClient.ServiceUpdate(ctx, service.ID, service.Version, service.Spec, types.ServiceUpdateOptions{
|
||||
response, err := apiClient.ServiceUpdate(ctx, service.ID, service.Version, service.Spec, swarm.ServiceUpdateOptions{
|
||||
Rollback: "previous", // TODO(thaJeztah): this should have a const defined
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
@ -8,7 +8,6 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
@ -18,7 +17,7 @@ func TestRollback(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
args []string
|
||||
serviceUpdateFunc func(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (swarm.ServiceUpdateResponse, error)
|
||||
serviceUpdateFunc func(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options swarm.ServiceUpdateOptions) (swarm.ServiceUpdateResponse, error)
|
||||
expectedDockerCliErr string
|
||||
}{
|
||||
{
|
||||
@ -28,7 +27,7 @@ func TestRollback(t *testing.T) {
|
||||
{
|
||||
name: "rollback-service-with-warnings",
|
||||
args: []string{"service-id"},
|
||||
serviceUpdateFunc: func(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (swarm.ServiceUpdateResponse, error) {
|
||||
serviceUpdateFunc: func(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options swarm.ServiceUpdateOptions) (swarm.ServiceUpdateResponse, error) {
|
||||
response := swarm.ServiceUpdateResponse{}
|
||||
|
||||
response.Warnings = []string{
|
||||
@ -59,8 +58,8 @@ func TestRollbackWithErrors(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
args []string
|
||||
serviceInspectWithRawFunc func(ctx context.Context, serviceID string, options types.ServiceInspectOptions) (swarm.Service, []byte, error)
|
||||
serviceUpdateFunc func(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (swarm.ServiceUpdateResponse, error)
|
||||
serviceInspectWithRawFunc func(ctx context.Context, serviceID string, options swarm.ServiceInspectOptions) (swarm.Service, []byte, error)
|
||||
serviceUpdateFunc func(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options swarm.ServiceUpdateOptions) (swarm.ServiceUpdateResponse, error)
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
@ -75,7 +74,7 @@ func TestRollbackWithErrors(t *testing.T) {
|
||||
{
|
||||
name: "service-does-not-exists",
|
||||
args: []string{"service-id"},
|
||||
serviceInspectWithRawFunc: func(ctx context.Context, serviceID string, options types.ServiceInspectOptions) (swarm.Service, []byte, error) {
|
||||
serviceInspectWithRawFunc: func(ctx context.Context, serviceID string, options swarm.ServiceInspectOptions) (swarm.Service, []byte, error) {
|
||||
return swarm.Service{}, []byte{}, fmt.Errorf("no such services: %s", serviceID)
|
||||
},
|
||||
expectedError: "no such services: service-id",
|
||||
@ -83,7 +82,7 @@ func TestRollbackWithErrors(t *testing.T) {
|
||||
{
|
||||
name: "service-update-failed",
|
||||
args: []string{"service-id"},
|
||||
serviceUpdateFunc: func(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (swarm.ServiceUpdateResponse, error) {
|
||||
serviceUpdateFunc: func(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options swarm.ServiceUpdateOptions) (swarm.ServiceUpdateResponse, error) {
|
||||
return swarm.ServiceUpdateResponse{}, fmt.Errorf("no such services: %s", serviceID)
|
||||
},
|
||||
expectedError: "no such services: service-id",
|
||||
|
||||
@ -9,7 +9,7 @@ import (
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/api/types/versions"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/spf13/cobra"
|
||||
@ -94,7 +94,7 @@ func runScale(ctx context.Context, dockerCLI command.Cli, options *scaleOptions,
|
||||
}
|
||||
|
||||
func runServiceScale(ctx context.Context, apiClient client.ServiceAPIClient, serviceID string, scale uint64) (warnings []string, _ error) {
|
||||
service, _, err := apiClient.ServiceInspectWithRaw(ctx, serviceID, types.ServiceInspectOptions{})
|
||||
service, _, err := apiClient.ServiceInspectWithRaw(ctx, serviceID, swarm.ServiceInspectOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -109,7 +109,7 @@ func runServiceScale(ctx context.Context, apiClient client.ServiceAPIClient, ser
|
||||
return nil, errors.New("scale can only be used with replicated or replicated-job mode")
|
||||
}
|
||||
|
||||
response, err := apiClient.ServiceUpdate(ctx, service.ID, service.Version, service.Spec, types.ServiceUpdateOptions{})
|
||||
response, err := apiClient.ServiceUpdate(ctx, service.ID, service.Version, service.Spec, swarm.ServiceUpdateOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@ -12,7 +12,6 @@ import (
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/docker/cli/opts"
|
||||
"github.com/docker/cli/opts/swarmopts"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
mounttypes "github.com/docker/docker/api/types/mount"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
@ -156,7 +155,7 @@ func newListOptsVarWithValidator(validator opts.ValidatorFctType) *opts.ListOpts
|
||||
func runUpdate(ctx context.Context, dockerCLI command.Cli, flags *pflag.FlagSet, options *serviceOptions, serviceID string) error {
|
||||
apiClient := dockerCLI.Client()
|
||||
|
||||
service, _, err := apiClient.ServiceInspectWithRaw(ctx, serviceID, types.ServiceInspectOptions{})
|
||||
service, _, err := apiClient.ServiceInspectWithRaw(ctx, serviceID, swarm.ServiceInspectOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -201,7 +200,7 @@ func runUpdate(ctx context.Context, dockerCLI command.Cli, flags *pflag.FlagSet,
|
||||
}
|
||||
}
|
||||
|
||||
updateOpts := types.ServiceUpdateOptions{}
|
||||
updateOpts := swarm.ServiceUpdateOptions{}
|
||||
if serverSideRollback {
|
||||
updateOpts.Rollback = "previous"
|
||||
}
|
||||
@ -255,9 +254,9 @@ func runUpdate(ctx context.Context, dockerCLI command.Cli, flags *pflag.FlagSet,
|
||||
}
|
||||
updateOpts.EncodedRegistryAuth = encodedAuth
|
||||
case clientSideRollback:
|
||||
updateOpts.RegistryAuthFrom = types.RegistryAuthFromPreviousSpec
|
||||
updateOpts.RegistryAuthFrom = swarm.RegistryAuthFromPreviousSpec
|
||||
default:
|
||||
updateOpts.RegistryAuthFrom = types.RegistryAuthFromSpec
|
||||
updateOpts.RegistryAuthFrom = swarm.RegistryAuthFromSpec
|
||||
}
|
||||
|
||||
response, err := apiClient.ServiceUpdate(ctx, service.ID, service.Version, *spec, updateOpts)
|
||||
|
||||
@ -28,15 +28,15 @@ type fakeClient struct {
|
||||
removedSecrets []string
|
||||
removedConfigs []string
|
||||
|
||||
serviceListFunc func(options types.ServiceListOptions) ([]swarm.Service, error)
|
||||
serviceListFunc func(options swarm.ServiceListOptions) ([]swarm.Service, error)
|
||||
networkListFunc func(options network.ListOptions) ([]network.Summary, error)
|
||||
secretListFunc func(options swarm.SecretListOptions) ([]swarm.Secret, error)
|
||||
configListFunc func(options swarm.ConfigListOptions) ([]swarm.Config, error)
|
||||
nodeListFunc func(options types.NodeListOptions) ([]swarm.Node, error)
|
||||
taskListFunc func(options types.TaskListOptions) ([]swarm.Task, error)
|
||||
nodeListFunc func(options swarm.NodeListOptions) ([]swarm.Node, error)
|
||||
taskListFunc func(options swarm.TaskListOptions) ([]swarm.Task, error)
|
||||
nodeInspectWithRaw func(ref string) (swarm.Node, []byte, error)
|
||||
|
||||
serviceUpdateFunc func(serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (swarm.ServiceUpdateResponse, error)
|
||||
serviceUpdateFunc func(serviceID string, version swarm.Version, service swarm.ServiceSpec, options swarm.ServiceUpdateOptions) (swarm.ServiceUpdateResponse, error)
|
||||
|
||||
serviceRemoveFunc func(serviceID string) error
|
||||
networkRemoveFunc func(networkID string) error
|
||||
@ -55,7 +55,7 @@ func (cli *fakeClient) ClientVersion() string {
|
||||
return cli.version
|
||||
}
|
||||
|
||||
func (cli *fakeClient) ServiceList(_ context.Context, options types.ServiceListOptions) ([]swarm.Service, error) {
|
||||
func (cli *fakeClient) ServiceList(_ context.Context, options swarm.ServiceListOptions) ([]swarm.Service, error) {
|
||||
if cli.serviceListFunc != nil {
|
||||
return cli.serviceListFunc(options)
|
||||
}
|
||||
@ -115,14 +115,14 @@ func (cli *fakeClient) ConfigList(_ context.Context, options swarm.ConfigListOpt
|
||||
return configsList, nil
|
||||
}
|
||||
|
||||
func (cli *fakeClient) TaskList(_ context.Context, options types.TaskListOptions) ([]swarm.Task, error) {
|
||||
func (cli *fakeClient) TaskList(_ context.Context, options swarm.TaskListOptions) ([]swarm.Task, error) {
|
||||
if cli.taskListFunc != nil {
|
||||
return cli.taskListFunc(options)
|
||||
}
|
||||
return []swarm.Task{}, nil
|
||||
}
|
||||
|
||||
func (cli *fakeClient) NodeList(_ context.Context, options types.NodeListOptions) ([]swarm.Node, error) {
|
||||
func (cli *fakeClient) NodeList(_ context.Context, options swarm.NodeListOptions) ([]swarm.Node, error) {
|
||||
if cli.nodeListFunc != nil {
|
||||
return cli.nodeListFunc(options)
|
||||
}
|
||||
@ -136,7 +136,7 @@ func (cli *fakeClient) NodeInspectWithRaw(_ context.Context, ref string) (swarm.
|
||||
return swarm.Node{}, nil, nil
|
||||
}
|
||||
|
||||
func (cli *fakeClient) ServiceUpdate(_ context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (swarm.ServiceUpdateResponse, error) {
|
||||
func (cli *fakeClient) ServiceUpdate(_ context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options swarm.ServiceUpdateOptions) (swarm.ServiceUpdateResponse, error) {
|
||||
if cli.serviceUpdateFunc != nil {
|
||||
return cli.serviceUpdateFunc(serviceID, version, service, options)
|
||||
}
|
||||
@ -180,7 +180,7 @@ func (cli *fakeClient) ConfigRemove(_ context.Context, configID string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*fakeClient) ServiceInspectWithRaw(_ context.Context, serviceID string, _ types.ServiceInspectOptions) (swarm.Service, []byte, error) {
|
||||
func (*fakeClient) ServiceInspectWithRaw(_ context.Context, serviceID string, _ swarm.ServiceInspectOptions) (swarm.Service, []byte, error) {
|
||||
return swarm.Service{
|
||||
ID: serviceID,
|
||||
Spec: swarm.ServiceSpec{
|
||||
|
||||
@ -7,7 +7,6 @@ import (
|
||||
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/docker/cli/internal/test/builders"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"gotest.tools/v3/assert"
|
||||
"gotest.tools/v3/golden"
|
||||
@ -17,7 +16,7 @@ func TestListErrors(t *testing.T) {
|
||||
testCases := []struct {
|
||||
args []string
|
||||
flags map[string]string
|
||||
serviceListFunc func(options types.ServiceListOptions) ([]swarm.Service, error)
|
||||
serviceListFunc func(options swarm.ServiceListOptions) ([]swarm.Service, error)
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
@ -33,14 +32,14 @@ func TestListErrors(t *testing.T) {
|
||||
},
|
||||
{
|
||||
args: []string{},
|
||||
serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) {
|
||||
serviceListFunc: func(options swarm.ServiceListOptions) ([]swarm.Service, error) {
|
||||
return []swarm.Service{}, errors.New("error getting services")
|
||||
},
|
||||
expectedError: "error getting services",
|
||||
},
|
||||
{
|
||||
args: []string{},
|
||||
serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) {
|
||||
serviceListFunc: func(options swarm.ServiceListOptions) ([]swarm.Service, error) {
|
||||
return []swarm.Service{*builders.Service()}, nil
|
||||
},
|
||||
expectedError: "cannot get label",
|
||||
@ -115,7 +114,7 @@ func TestStackList(t *testing.T) {
|
||||
)
|
||||
}
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) {
|
||||
serviceListFunc: func(options swarm.ServiceListOptions) ([]swarm.Service, error) {
|
||||
return services, nil
|
||||
},
|
||||
})
|
||||
|
||||
@ -9,7 +9,6 @@ import (
|
||||
"github.com/docker/cli/cli/config/configfile"
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/docker/cli/internal/test/builders"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
@ -19,7 +18,7 @@ import (
|
||||
func TestStackPsErrors(t *testing.T) {
|
||||
testCases := []struct {
|
||||
args []string
|
||||
taskListFunc func(options types.TaskListOptions) ([]swarm.Task, error)
|
||||
taskListFunc func(options swarm.TaskListOptions) ([]swarm.Task, error)
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
@ -32,7 +31,7 @@ func TestStackPsErrors(t *testing.T) {
|
||||
},
|
||||
{
|
||||
args: []string{"foo"},
|
||||
taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) {
|
||||
taskListFunc: func(options swarm.TaskListOptions) ([]swarm.Task, error) {
|
||||
return nil, errors.New("error getting tasks")
|
||||
},
|
||||
expectedError: "error getting tasks",
|
||||
@ -55,7 +54,7 @@ func TestStackPsErrors(t *testing.T) {
|
||||
func TestStackPs(t *testing.T) {
|
||||
testCases := []struct {
|
||||
doc string
|
||||
taskListFunc func(types.TaskListOptions) ([]swarm.Task, error)
|
||||
taskListFunc func(swarm.TaskListOptions) ([]swarm.Task, error)
|
||||
nodeInspectWithRaw func(string) (swarm.Node, []byte, error)
|
||||
config configfile.ConfigFile
|
||||
args []string
|
||||
@ -70,7 +69,7 @@ func TestStackPs(t *testing.T) {
|
||||
},
|
||||
{
|
||||
doc: "WithEmptyStack",
|
||||
taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) {
|
||||
taskListFunc: func(options swarm.TaskListOptions) ([]swarm.Task, error) {
|
||||
return []swarm.Task{}, nil
|
||||
},
|
||||
args: []string{"foo"},
|
||||
@ -78,7 +77,7 @@ func TestStackPs(t *testing.T) {
|
||||
},
|
||||
{
|
||||
doc: "WithQuietOption",
|
||||
taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) {
|
||||
taskListFunc: func(options swarm.TaskListOptions) ([]swarm.Task, error) {
|
||||
return []swarm.Task{*builders.Task(builders.TaskID("id-foo"))}, nil
|
||||
},
|
||||
args: []string{"foo"},
|
||||
@ -89,7 +88,7 @@ func TestStackPs(t *testing.T) {
|
||||
},
|
||||
{
|
||||
doc: "WithNoTruncOption",
|
||||
taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) {
|
||||
taskListFunc: func(options swarm.TaskListOptions) ([]swarm.Task, error) {
|
||||
return []swarm.Task{*builders.Task(builders.TaskID("xn4cypcov06f2w8gsbaf2lst3"))}, nil
|
||||
},
|
||||
args: []string{"foo"},
|
||||
@ -101,7 +100,7 @@ func TestStackPs(t *testing.T) {
|
||||
},
|
||||
{
|
||||
doc: "WithNoResolveOption",
|
||||
taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) {
|
||||
taskListFunc: func(options swarm.TaskListOptions) ([]swarm.Task, error) {
|
||||
return []swarm.Task{*builders.Task(
|
||||
builders.TaskNodeID("id-node-foo"),
|
||||
)}, nil
|
||||
@ -118,7 +117,7 @@ func TestStackPs(t *testing.T) {
|
||||
},
|
||||
{
|
||||
doc: "WithFormat",
|
||||
taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) {
|
||||
taskListFunc: func(options swarm.TaskListOptions) ([]swarm.Task, error) {
|
||||
return []swarm.Task{*builders.Task(builders.TaskServiceID("service-id-foo"))}, nil
|
||||
},
|
||||
args: []string{"foo"},
|
||||
@ -129,7 +128,7 @@ func TestStackPs(t *testing.T) {
|
||||
},
|
||||
{
|
||||
doc: "WithConfigFormat",
|
||||
taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) {
|
||||
taskListFunc: func(options swarm.TaskListOptions) ([]swarm.Task, error) {
|
||||
return []swarm.Task{*builders.Task(builders.TaskServiceID("service-id-foo"))}, nil
|
||||
},
|
||||
config: configfile.ConfigFile{
|
||||
@ -140,7 +139,7 @@ func TestStackPs(t *testing.T) {
|
||||
},
|
||||
{
|
||||
doc: "WithoutFormat",
|
||||
taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) {
|
||||
taskListFunc: func(options swarm.TaskListOptions) ([]swarm.Task, error) {
|
||||
return []swarm.Task{*builders.Task(
|
||||
builders.TaskID("id-foo"),
|
||||
builders.TaskServiceID("service-id-foo"),
|
||||
|
||||
@ -8,7 +8,6 @@ import (
|
||||
"github.com/docker/cli/cli/config/configfile"
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/docker/cli/internal/test/builders"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
@ -19,37 +18,37 @@ func TestStackServicesErrors(t *testing.T) {
|
||||
testCases := []struct {
|
||||
args []string
|
||||
flags map[string]string
|
||||
serviceListFunc func(options types.ServiceListOptions) ([]swarm.Service, error)
|
||||
nodeListFunc func(options types.NodeListOptions) ([]swarm.Node, error)
|
||||
taskListFunc func(options types.TaskListOptions) ([]swarm.Task, error)
|
||||
serviceListFunc func(options swarm.ServiceListOptions) ([]swarm.Service, error)
|
||||
nodeListFunc func(options swarm.NodeListOptions) ([]swarm.Node, error)
|
||||
taskListFunc func(options swarm.TaskListOptions) ([]swarm.Task, error)
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
args: []string{"foo"},
|
||||
serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) {
|
||||
serviceListFunc: func(options swarm.ServiceListOptions) ([]swarm.Service, error) {
|
||||
return nil, errors.New("error getting services")
|
||||
},
|
||||
expectedError: "error getting services",
|
||||
},
|
||||
{
|
||||
args: []string{"foo"},
|
||||
serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) {
|
||||
serviceListFunc: func(options swarm.ServiceListOptions) ([]swarm.Service, error) {
|
||||
return []swarm.Service{*builders.Service(builders.GlobalService())}, nil
|
||||
},
|
||||
nodeListFunc: func(options types.NodeListOptions) ([]swarm.Node, error) {
|
||||
nodeListFunc: func(options swarm.NodeListOptions) ([]swarm.Node, error) {
|
||||
return nil, errors.New("error getting nodes")
|
||||
},
|
||||
taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) {
|
||||
taskListFunc: func(options swarm.TaskListOptions) ([]swarm.Task, error) {
|
||||
return []swarm.Task{*builders.Task()}, nil
|
||||
},
|
||||
expectedError: "error getting nodes",
|
||||
},
|
||||
{
|
||||
args: []string{"foo"},
|
||||
serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) {
|
||||
serviceListFunc: func(options swarm.ServiceListOptions) ([]swarm.Service, error) {
|
||||
return []swarm.Service{*builders.Service(builders.GlobalService())}, nil
|
||||
},
|
||||
taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) {
|
||||
taskListFunc: func(options swarm.TaskListOptions) ([]swarm.Task, error) {
|
||||
return nil, errors.New("error getting tasks")
|
||||
},
|
||||
expectedError: "error getting tasks",
|
||||
@ -59,7 +58,7 @@ func TestStackServicesErrors(t *testing.T) {
|
||||
flags: map[string]string{
|
||||
"format": "{{invalid format}}",
|
||||
},
|
||||
serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) {
|
||||
serviceListFunc: func(options swarm.ServiceListOptions) ([]swarm.Service, error) {
|
||||
return []swarm.Service{*builders.Service()}, nil
|
||||
},
|
||||
expectedError: "template parsing error",
|
||||
@ -96,7 +95,7 @@ func TestRunServicesWithEmptyName(t *testing.T) {
|
||||
|
||||
func TestStackServicesEmptyServiceList(t *testing.T) {
|
||||
fakeCli := test.NewFakeCli(&fakeClient{
|
||||
serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) {
|
||||
serviceListFunc: func(options swarm.ServiceListOptions) ([]swarm.Service, error) {
|
||||
return []swarm.Service{}, nil
|
||||
},
|
||||
})
|
||||
@ -109,7 +108,7 @@ func TestStackServicesEmptyServiceList(t *testing.T) {
|
||||
|
||||
func TestStackServicesWithQuietOption(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) {
|
||||
serviceListFunc: func(options swarm.ServiceListOptions) ([]swarm.Service, error) {
|
||||
return []swarm.Service{*builders.Service(builders.ServiceID("id-foo"))}, nil
|
||||
},
|
||||
})
|
||||
@ -122,7 +121,7 @@ func TestStackServicesWithQuietOption(t *testing.T) {
|
||||
|
||||
func TestStackServicesWithFormat(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) {
|
||||
serviceListFunc: func(options swarm.ServiceListOptions) ([]swarm.Service, error) {
|
||||
return []swarm.Service{
|
||||
*builders.Service(builders.ServiceName("service-name-foo")),
|
||||
}, nil
|
||||
@ -137,7 +136,7 @@ func TestStackServicesWithFormat(t *testing.T) {
|
||||
|
||||
func TestStackServicesWithConfigFormat(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) {
|
||||
serviceListFunc: func(options swarm.ServiceListOptions) ([]swarm.Service, error) {
|
||||
return []swarm.Service{
|
||||
*builders.Service(builders.ServiceName("service-name-foo")),
|
||||
}, nil
|
||||
@ -154,7 +153,7 @@ func TestStackServicesWithConfigFormat(t *testing.T) {
|
||||
|
||||
func TestStackServicesWithoutFormat(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) {
|
||||
serviceListFunc: func(options swarm.ServiceListOptions) ([]swarm.Service, error) {
|
||||
return []swarm.Service{*builders.Service(
|
||||
builders.ServiceName("name-foo"),
|
||||
builders.ServiceID("id-foo"),
|
||||
|
||||
@ -28,15 +28,15 @@ type fakeClient struct {
|
||||
removedSecrets []string
|
||||
removedConfigs []string
|
||||
|
||||
serviceListFunc func(options types.ServiceListOptions) ([]swarm.Service, error)
|
||||
serviceListFunc func(options swarm.ServiceListOptions) ([]swarm.Service, error)
|
||||
networkListFunc func(options network.ListOptions) ([]network.Summary, error)
|
||||
secretListFunc func(options swarm.SecretListOptions) ([]swarm.Secret, error)
|
||||
configListFunc func(options swarm.ConfigListOptions) ([]swarm.Config, error)
|
||||
nodeListFunc func(options types.NodeListOptions) ([]swarm.Node, error)
|
||||
taskListFunc func(options types.TaskListOptions) ([]swarm.Task, error)
|
||||
nodeListFunc func(options swarm.NodeListOptions) ([]swarm.Node, error)
|
||||
taskListFunc func(options swarm.TaskListOptions) ([]swarm.Task, error)
|
||||
nodeInspectWithRaw func(ref string) (swarm.Node, []byte, error)
|
||||
|
||||
serviceUpdateFunc func(serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (swarm.ServiceUpdateResponse, error)
|
||||
serviceUpdateFunc func(serviceID string, version swarm.Version, service swarm.ServiceSpec, options swarm.ServiceUpdateOptions) (swarm.ServiceUpdateResponse, error)
|
||||
|
||||
serviceRemoveFunc func(serviceID string) error
|
||||
networkRemoveFunc func(networkID string) error
|
||||
@ -55,7 +55,7 @@ func (cli *fakeClient) ClientVersion() string {
|
||||
return cli.version
|
||||
}
|
||||
|
||||
func (cli *fakeClient) ServiceList(_ context.Context, options types.ServiceListOptions) ([]swarm.Service, error) {
|
||||
func (cli *fakeClient) ServiceList(_ context.Context, options swarm.ServiceListOptions) ([]swarm.Service, error) {
|
||||
if cli.serviceListFunc != nil {
|
||||
return cli.serviceListFunc(options)
|
||||
}
|
||||
@ -115,14 +115,14 @@ func (cli *fakeClient) ConfigList(_ context.Context, options swarm.ConfigListOpt
|
||||
return configsList, nil
|
||||
}
|
||||
|
||||
func (cli *fakeClient) TaskList(_ context.Context, options types.TaskListOptions) ([]swarm.Task, error) {
|
||||
func (cli *fakeClient) TaskList(_ context.Context, options swarm.TaskListOptions) ([]swarm.Task, error) {
|
||||
if cli.taskListFunc != nil {
|
||||
return cli.taskListFunc(options)
|
||||
}
|
||||
return []swarm.Task{}, nil
|
||||
}
|
||||
|
||||
func (cli *fakeClient) NodeList(_ context.Context, options types.NodeListOptions) ([]swarm.Node, error) {
|
||||
func (cli *fakeClient) NodeList(_ context.Context, options swarm.NodeListOptions) ([]swarm.Node, error) {
|
||||
if cli.nodeListFunc != nil {
|
||||
return cli.nodeListFunc(options)
|
||||
}
|
||||
@ -136,7 +136,7 @@ func (cli *fakeClient) NodeInspectWithRaw(_ context.Context, ref string) (swarm.
|
||||
return swarm.Node{}, nil, nil
|
||||
}
|
||||
|
||||
func (cli *fakeClient) ServiceUpdate(_ context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (swarm.ServiceUpdateResponse, error) {
|
||||
func (cli *fakeClient) ServiceUpdate(_ context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options swarm.ServiceUpdateOptions) (swarm.ServiceUpdateResponse, error) {
|
||||
if cli.serviceUpdateFunc != nil {
|
||||
return cli.serviceUpdateFunc(serviceID, version, service, options)
|
||||
}
|
||||
|
||||
@ -5,7 +5,6 @@ import (
|
||||
|
||||
"github.com/docker/cli/cli/compose/convert"
|
||||
"github.com/docker/cli/opts"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
@ -31,7 +30,7 @@ func getAllStacksFilter() filters.Args {
|
||||
}
|
||||
|
||||
func getStackServices(ctx context.Context, apiclient client.APIClient, namespace string) ([]swarm.Service, error) {
|
||||
return apiclient.ServiceList(ctx, types.ServiceListOptions{Filters: getStackFilter(namespace)})
|
||||
return apiclient.ServiceList(ctx, swarm.ServiceListOptions{Filters: getStackFilter(namespace)})
|
||||
}
|
||||
|
||||
func getStackNetworks(ctx context.Context, apiclient client.APIClient, namespace string) ([]network.Summary, error) {
|
||||
@ -47,5 +46,5 @@ func getStackConfigs(ctx context.Context, apiclient client.APIClient, namespace
|
||||
}
|
||||
|
||||
func getStackTasks(ctx context.Context, apiclient client.APIClient, namespace string) ([]swarm.Task, error) {
|
||||
return apiclient.TaskList(ctx, types.TaskListOptions{Filters: getStackFilter(namespace)})
|
||||
return apiclient.TaskList(ctx, swarm.TaskListOptions{Filters: getStackFilter(namespace)})
|
||||
}
|
||||
|
||||
@ -11,7 +11,6 @@ import (
|
||||
"github.com/docker/cli/cli/command/stack/options"
|
||||
"github.com/docker/cli/cli/compose/convert"
|
||||
composetypes "github.com/docker/cli/cli/compose/types"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
@ -221,7 +220,7 @@ func deployServices(ctx context.Context, dockerCLI command.Cli, services map[str
|
||||
if service, exists := existingServiceMap[name]; exists {
|
||||
_, _ = fmt.Fprintf(out, "Updating service %s (id: %s)\n", name, service.ID)
|
||||
|
||||
updateOpts := types.ServiceUpdateOptions{EncodedRegistryAuth: encodedAuth}
|
||||
updateOpts := swarm.ServiceUpdateOptions{EncodedRegistryAuth: encodedAuth}
|
||||
|
||||
switch resolveImage {
|
||||
case ResolveImageAlways:
|
||||
@ -266,7 +265,7 @@ func deployServices(ctx context.Context, dockerCLI command.Cli, services map[str
|
||||
} else {
|
||||
_, _ = fmt.Fprintln(out, "Creating service", name)
|
||||
|
||||
createOpts := types.ServiceCreateOptions{EncodedRegistryAuth: encodedAuth}
|
||||
createOpts := swarm.ServiceCreateOptions{EncodedRegistryAuth: encodedAuth}
|
||||
|
||||
// query registry if flag disabling it was not set
|
||||
if resolveImage == ResolveImageAlways || resolveImage == ResolveImageChanged {
|
||||
|
||||
@ -6,7 +6,6 @@ import (
|
||||
|
||||
"github.com/docker/cli/cli/compose/convert"
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
@ -33,12 +32,12 @@ func TestServiceUpdateResolveImageChanged(t *testing.T) {
|
||||
namespace := convert.NewNamespace("mystack")
|
||||
|
||||
var (
|
||||
receivedOptions types.ServiceUpdateOptions
|
||||
receivedOptions swarm.ServiceUpdateOptions
|
||||
receivedService swarm.ServiceSpec
|
||||
)
|
||||
|
||||
client := test.NewFakeCli(&fakeClient{
|
||||
serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) {
|
||||
serviceListFunc: func(options swarm.ServiceListOptions) ([]swarm.Service, error) {
|
||||
return []swarm.Service{
|
||||
{
|
||||
Spec: swarm.ServiceSpec{
|
||||
@ -56,7 +55,7 @@ func TestServiceUpdateResolveImageChanged(t *testing.T) {
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
serviceUpdateFunc: func(serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (swarm.ServiceUpdateResponse, error) {
|
||||
serviceUpdateFunc: func(serviceID string, version swarm.Version, service swarm.ServiceSpec, options swarm.ServiceUpdateOptions) (swarm.ServiceUpdateResponse, error) {
|
||||
receivedOptions = options
|
||||
receivedService = service
|
||||
return swarm.ServiceUpdateResponse{}, nil
|
||||
@ -105,7 +104,7 @@ func TestServiceUpdateResolveImageChanged(t *testing.T) {
|
||||
assert.Check(t, is.Equal(receivedService.TaskTemplate.ForceUpdate, tc.expectedForceUpdate))
|
||||
|
||||
receivedService = swarm.ServiceSpec{}
|
||||
receivedOptions = types.ServiceUpdateOptions{}
|
||||
receivedOptions = swarm.ServiceUpdateOptions{}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,7 +5,7 @@ import (
|
||||
|
||||
"github.com/docker/cli/cli/command/stack/formatter"
|
||||
"github.com/docker/cli/cli/compose/convert"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
@ -14,7 +14,7 @@ import (
|
||||
func GetStacks(ctx context.Context, apiClient client.ServiceAPIClient) ([]*formatter.Stack, error) {
|
||||
services, err := apiClient.ServiceList(
|
||||
ctx,
|
||||
types.ServiceListOptions{Filters: getAllStacksFilter()})
|
||||
swarm.ServiceListOptions{Filters: getAllStacksFilter()})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@ -8,7 +8,7 @@ import (
|
||||
"github.com/docker/cli/cli/command/idresolver"
|
||||
"github.com/docker/cli/cli/command/stack/options"
|
||||
"github.com/docker/cli/cli/command/task"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
)
|
||||
|
||||
// RunPS is the swarm implementation of docker stack ps
|
||||
@ -16,7 +16,7 @@ func RunPS(ctx context.Context, dockerCli command.Cli, opts options.PS) error {
|
||||
filter := getStackFilterFromOpt(opts.Namespace, opts.Filter)
|
||||
|
||||
client := dockerCli.Client()
|
||||
tasks, err := client.TaskList(ctx, types.TaskListOptions{Filters: filter})
|
||||
tasks, err := client.TaskList(ctx, swarm.TaskListOptions{Filters: filter})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -6,7 +6,6 @@ import (
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/service"
|
||||
"github.com/docker/cli/cli/command/stack/options"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
)
|
||||
|
||||
@ -17,7 +16,7 @@ func GetServices(ctx context.Context, dockerCli command.Cli, opts options.Servic
|
||||
client = dockerCli.Client()
|
||||
)
|
||||
|
||||
listOpts := types.ServiceListOptions{
|
||||
listOpts := swarm.ServiceListOptions{
|
||||
Filters: getStackFilterFromOpt(opts.Namespace, opts.Filter),
|
||||
// When not running "quiet", also get service status (number of running
|
||||
// and desired tasks). Note that this is only supported on API v1.41 and
|
||||
|
||||
@ -96,7 +96,7 @@ func runCA(ctx context.Context, dockerCli command.Cli, flags *pflag.FlagSet, opt
|
||||
func updateSwarmSpec(spec *swarm.Spec, flags *pflag.FlagSet, opts caOptions) {
|
||||
caCert := opts.rootCACert.Contents()
|
||||
caKey := opts.rootCAKey.Contents()
|
||||
opts.mergeSwarmSpecCAFlags(spec, flags, caCert)
|
||||
opts.mergeSwarmSpecCAFlags(spec, flags, &caCert)
|
||||
|
||||
spec.CAConfig.SigningCACert = caCert
|
||||
spec.CAConfig.SigningCAKey = caKey
|
||||
|
||||
@ -69,7 +69,7 @@ func writeFile(data string) (string, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
_, err = tmpfile.Write([]byte(data))
|
||||
_, err = tmpfile.WriteString(data)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
@ -3,7 +3,6 @@ package swarm
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/api/types/system"
|
||||
"github.com/docker/docker/client"
|
||||
@ -12,10 +11,10 @@ import (
|
||||
type fakeClient struct {
|
||||
client.Client
|
||||
infoFunc func() (system.Info, error)
|
||||
swarmInitFunc func() (string, error)
|
||||
swarmInitFunc func(req swarm.InitRequest) (string, error)
|
||||
swarmInspectFunc func() (swarm.Swarm, error)
|
||||
nodeInspectFunc func() (swarm.Node, []byte, error)
|
||||
swarmGetUnlockKeyFunc func() (types.SwarmUnlockKeyResponse, error)
|
||||
swarmGetUnlockKeyFunc func() (swarm.UnlockKeyResponse, error)
|
||||
swarmJoinFunc func() error
|
||||
swarmLeaveFunc func() error
|
||||
swarmUpdateFunc func(swarm swarm.Spec, flags swarm.UpdateFlags) error
|
||||
@ -36,9 +35,9 @@ func (cli *fakeClient) NodeInspectWithRaw(context.Context, string) (swarm.Node,
|
||||
return swarm.Node{}, []byte{}, nil
|
||||
}
|
||||
|
||||
func (cli *fakeClient) SwarmInit(context.Context, swarm.InitRequest) (string, error) {
|
||||
func (cli *fakeClient) SwarmInit(_ context.Context, req swarm.InitRequest) (string, error) {
|
||||
if cli.swarmInitFunc != nil {
|
||||
return cli.swarmInitFunc()
|
||||
return cli.swarmInitFunc(req)
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
@ -50,11 +49,11 @@ func (cli *fakeClient) SwarmInspect(context.Context) (swarm.Swarm, error) {
|
||||
return swarm.Swarm{}, nil
|
||||
}
|
||||
|
||||
func (cli *fakeClient) SwarmGetUnlockKey(context.Context) (types.SwarmUnlockKeyResponse, error) {
|
||||
func (cli *fakeClient) SwarmGetUnlockKey(context.Context) (swarm.UnlockKeyResponse, error) {
|
||||
if cli.swarmGetUnlockKeyFunc != nil {
|
||||
return cli.swarmGetUnlockKeyFunc()
|
||||
}
|
||||
return types.SwarmUnlockKeyResponse{}, nil
|
||||
return swarm.UnlockKeyResponse{}, nil
|
||||
}
|
||||
|
||||
func (cli *fakeClient) SwarmJoin(context.Context, swarm.JoinRequest) error {
|
||||
|
||||
@ -4,12 +4,14 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
"gotest.tools/v3/golden"
|
||||
)
|
||||
|
||||
@ -17,22 +19,22 @@ func TestSwarmInitErrorOnAPIFailure(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
flags map[string]string
|
||||
swarmInitFunc func() (string, error)
|
||||
swarmInitFunc func(swarm.InitRequest) (string, error)
|
||||
swarmInspectFunc func() (swarm.Swarm, error)
|
||||
swarmGetUnlockKeyFunc func() (types.SwarmUnlockKeyResponse, error)
|
||||
swarmGetUnlockKeyFunc func() (swarm.UnlockKeyResponse, error)
|
||||
nodeInspectFunc func() (swarm.Node, []byte, error)
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
name: "init-failed",
|
||||
swarmInitFunc: func() (string, error) {
|
||||
swarmInitFunc: func(swarm.InitRequest) (string, error) {
|
||||
return "", errors.New("error initializing the swarm")
|
||||
},
|
||||
expectedError: "error initializing the swarm",
|
||||
},
|
||||
{
|
||||
name: "init-failed-with-ip-choice",
|
||||
swarmInitFunc: func() (string, error) {
|
||||
swarmInitFunc: func(swarm.InitRequest) (string, error) {
|
||||
return "", errors.New("could not choose an IP address to advertise")
|
||||
},
|
||||
expectedError: "could not choose an IP address to advertise - specify one with --advertise-addr",
|
||||
@ -56,8 +58,8 @@ func TestSwarmInitErrorOnAPIFailure(t *testing.T) {
|
||||
flags: map[string]string{
|
||||
flagAutolock: "true",
|
||||
},
|
||||
swarmGetUnlockKeyFunc: func() (types.SwarmUnlockKeyResponse, error) {
|
||||
return types.SwarmUnlockKeyResponse{}, errors.New("error getting swarm unlock key")
|
||||
swarmGetUnlockKeyFunc: func() (swarm.UnlockKeyResponse, error) {
|
||||
return swarm.UnlockKeyResponse{}, errors.New("error getting swarm unlock key")
|
||||
},
|
||||
expectedError: "could not fetch unlock key: error getting swarm unlock key",
|
||||
},
|
||||
@ -86,14 +88,14 @@ func TestSwarmInit(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
flags map[string]string
|
||||
swarmInitFunc func() (string, error)
|
||||
swarmInitFunc func(req swarm.InitRequest) (string, error)
|
||||
swarmInspectFunc func() (swarm.Swarm, error)
|
||||
swarmGetUnlockKeyFunc func() (types.SwarmUnlockKeyResponse, error)
|
||||
swarmGetUnlockKeyFunc func() (swarm.UnlockKeyResponse, error)
|
||||
nodeInspectFunc func() (swarm.Node, []byte, error)
|
||||
}{
|
||||
{
|
||||
name: "init",
|
||||
swarmInitFunc: func() (string, error) {
|
||||
swarmInitFunc: func(swarm.InitRequest) (string, error) {
|
||||
return "nodeID", nil
|
||||
},
|
||||
},
|
||||
@ -102,11 +104,11 @@ func TestSwarmInit(t *testing.T) {
|
||||
flags: map[string]string{
|
||||
flagAutolock: "true",
|
||||
},
|
||||
swarmInitFunc: func() (string, error) {
|
||||
swarmInitFunc: func(swarm.InitRequest) (string, error) {
|
||||
return "nodeID", nil
|
||||
},
|
||||
swarmGetUnlockKeyFunc: func() (types.SwarmUnlockKeyResponse, error) {
|
||||
return types.SwarmUnlockKeyResponse{
|
||||
swarmGetUnlockKeyFunc: func() (swarm.UnlockKeyResponse, error) {
|
||||
return swarm.UnlockKeyResponse{
|
||||
UnlockKey: "unlock-key",
|
||||
}, nil
|
||||
},
|
||||
@ -132,3 +134,28 @@ func TestSwarmInit(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSwarmInitWithExternalCA(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
swarmInitFunc: func(req swarm.InitRequest) (string, error) {
|
||||
if assert.Check(t, is.Len(req.Spec.CAConfig.ExternalCAs, 1)) {
|
||||
assert.Equal(t, req.Spec.CAConfig.ExternalCAs[0].CACert, cert)
|
||||
assert.Equal(t, req.Spec.CAConfig.ExternalCAs[0].Protocol, swarm.ExternalCAProtocolCFSSL)
|
||||
assert.Equal(t, req.Spec.CAConfig.ExternalCAs[0].URL, "https://example.com")
|
||||
}
|
||||
return "nodeID", nil
|
||||
},
|
||||
})
|
||||
|
||||
tempDir := t.TempDir()
|
||||
certFile := filepath.Join(tempDir, "cert.pem")
|
||||
err := os.WriteFile(certFile, []byte(cert), 0o644)
|
||||
assert.NilError(t, err)
|
||||
|
||||
cmd := newInitCommand(cli)
|
||||
cmd.SetArgs([]string{})
|
||||
cmd.SetOut(io.Discard)
|
||||
cmd.SetErr(io.Discard)
|
||||
assert.NilError(t, cmd.Flags().Set(flagExternalCA, "protocol=cfssl,url=https://example.com,cacert="+certFile))
|
||||
assert.NilError(t, cmd.Execute())
|
||||
}
|
||||
|
||||
@ -231,7 +231,7 @@ func addSwarmFlags(flags *pflag.FlagSet, options *swarmOptions) {
|
||||
addSwarmCAFlags(flags, &options.swarmCAOptions)
|
||||
}
|
||||
|
||||
func (o *swarmOptions) mergeSwarmSpec(spec *swarm.Spec, flags *pflag.FlagSet, caCert string) {
|
||||
func (o *swarmOptions) mergeSwarmSpec(spec *swarm.Spec, flags *pflag.FlagSet, caCert *string) {
|
||||
if flags.Changed(flagTaskHistoryLimit) {
|
||||
spec.Orchestration.TaskHistoryRetentionLimit = &o.taskHistoryLimit
|
||||
}
|
||||
@ -255,20 +255,24 @@ type swarmCAOptions struct {
|
||||
externalCA ExternalCAOption
|
||||
}
|
||||
|
||||
func (o *swarmCAOptions) mergeSwarmSpecCAFlags(spec *swarm.Spec, flags *pflag.FlagSet, caCert string) {
|
||||
func (o *swarmCAOptions) mergeSwarmSpecCAFlags(spec *swarm.Spec, flags *pflag.FlagSet, caCert *string) {
|
||||
if flags.Changed(flagCertExpiry) {
|
||||
spec.CAConfig.NodeCertExpiry = o.nodeCertExpiry
|
||||
}
|
||||
if flags.Changed(flagExternalCA) {
|
||||
spec.CAConfig.ExternalCAs = o.externalCA.Value()
|
||||
for _, ca := range spec.CAConfig.ExternalCAs {
|
||||
ca.CACert = caCert
|
||||
if caCert != nil {
|
||||
for _, ca := range spec.CAConfig.ExternalCAs {
|
||||
if ca.CACert == "" {
|
||||
ca.CACert = *caCert
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (o *swarmOptions) ToSpec(flags *pflag.FlagSet) swarm.Spec {
|
||||
var spec swarm.Spec
|
||||
o.mergeSwarmSpec(&spec, flags, "")
|
||||
o.mergeSwarmSpec(&spec, flags, nil)
|
||||
return spec
|
||||
}
|
||||
|
||||
@ -8,7 +8,6 @@ import (
|
||||
"os/signal"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/docker/docker/pkg/progress"
|
||||
@ -52,7 +51,7 @@ func RootRotationProgress(ctx context.Context, dclient client.APIClient, progres
|
||||
return nil
|
||||
}
|
||||
|
||||
nodes, err := dclient.NodeList(ctx, types.NodeListOptions{})
|
||||
nodes, err := dclient.NodeList(ctx, swarm.NodeListOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -50,7 +50,7 @@ func runUnlock(ctx context.Context, dockerCli command.Cli) error {
|
||||
return errors.New("Error: This node is not part of a swarm")
|
||||
case swarm.LocalNodeStateLocked:
|
||||
break
|
||||
default:
|
||||
case swarm.LocalNodeStatePending, swarm.LocalNodeStateActive, swarm.LocalNodeStateError:
|
||||
return errors.New("Error: swarm is not locked")
|
||||
}
|
||||
|
||||
@ -58,11 +58,10 @@ func runUnlock(ctx context.Context, dockerCli command.Cli) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req := swarm.UnlockRequest{
|
||||
UnlockKey: key,
|
||||
}
|
||||
|
||||
return client.SwarmUnlock(ctx, req)
|
||||
return client.SwarmUnlock(ctx, swarm.UnlockRequest{
|
||||
UnlockKey: key,
|
||||
})
|
||||
}
|
||||
|
||||
func readKey(in *streams.In, prompt string) (string, error) {
|
||||
|
||||
@ -8,7 +8,6 @@ import (
|
||||
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/docker/cli/internal/test/builders"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"gotest.tools/v3/assert"
|
||||
"gotest.tools/v3/golden"
|
||||
@ -21,7 +20,7 @@ func TestSwarmUnlockKeyErrors(t *testing.T) {
|
||||
flags map[string]string
|
||||
swarmInspectFunc func() (swarm.Swarm, error)
|
||||
swarmUpdateFunc func(swarm swarm.Spec, flags swarm.UpdateFlags) error
|
||||
swarmGetUnlockKeyFunc func() (types.SwarmUnlockKeyResponse, error)
|
||||
swarmGetUnlockKeyFunc func() (swarm.UnlockKeyResponse, error)
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
@ -64,17 +63,15 @@ func TestSwarmUnlockKeyErrors(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "swarm-get-unlock-key-failed",
|
||||
swarmGetUnlockKeyFunc: func() (types.SwarmUnlockKeyResponse, error) {
|
||||
return types.SwarmUnlockKeyResponse{}, errors.New("error getting unlock key")
|
||||
swarmGetUnlockKeyFunc: func() (swarm.UnlockKeyResponse, error) {
|
||||
return swarm.UnlockKeyResponse{}, errors.New("error getting unlock key")
|
||||
},
|
||||
expectedError: "error getting unlock key",
|
||||
},
|
||||
{
|
||||
name: "swarm-no-unlock-key-failed",
|
||||
swarmGetUnlockKeyFunc: func() (types.SwarmUnlockKeyResponse, error) {
|
||||
return types.SwarmUnlockKeyResponse{
|
||||
UnlockKey: "",
|
||||
}, nil
|
||||
swarmGetUnlockKeyFunc: func() (swarm.UnlockKeyResponse, error) {
|
||||
return swarm.UnlockKeyResponse{}, nil
|
||||
},
|
||||
expectedError: "no unlock key is set",
|
||||
},
|
||||
@ -108,12 +105,12 @@ func TestSwarmUnlockKey(t *testing.T) {
|
||||
flags map[string]string
|
||||
swarmInspectFunc func() (swarm.Swarm, error)
|
||||
swarmUpdateFunc func(swarm swarm.Spec, flags swarm.UpdateFlags) error
|
||||
swarmGetUnlockKeyFunc func() (types.SwarmUnlockKeyResponse, error)
|
||||
swarmGetUnlockKeyFunc func() (swarm.UnlockKeyResponse, error)
|
||||
}{
|
||||
{
|
||||
name: "unlock-key",
|
||||
swarmGetUnlockKeyFunc: func() (types.SwarmUnlockKeyResponse, error) {
|
||||
return types.SwarmUnlockKeyResponse{
|
||||
swarmGetUnlockKeyFunc: func() (swarm.UnlockKeyResponse, error) {
|
||||
return swarm.UnlockKeyResponse{
|
||||
UnlockKey: "unlock-key",
|
||||
}, nil
|
||||
},
|
||||
@ -123,8 +120,8 @@ func TestSwarmUnlockKey(t *testing.T) {
|
||||
flags: map[string]string{
|
||||
flagQuiet: "true",
|
||||
},
|
||||
swarmGetUnlockKeyFunc: func() (types.SwarmUnlockKeyResponse, error) {
|
||||
return types.SwarmUnlockKeyResponse{
|
||||
swarmGetUnlockKeyFunc: func() (swarm.UnlockKeyResponse, error) {
|
||||
return swarm.UnlockKeyResponse{
|
||||
UnlockKey: "unlock-key",
|
||||
}, nil
|
||||
},
|
||||
@ -137,8 +134,8 @@ func TestSwarmUnlockKey(t *testing.T) {
|
||||
swarmInspectFunc: func() (swarm.Swarm, error) {
|
||||
return *builders.Swarm(builders.Autolock()), nil
|
||||
},
|
||||
swarmGetUnlockKeyFunc: func() (types.SwarmUnlockKeyResponse, error) {
|
||||
return types.SwarmUnlockKeyResponse{
|
||||
swarmGetUnlockKeyFunc: func() (swarm.UnlockKeyResponse, error) {
|
||||
return swarm.UnlockKeyResponse{
|
||||
UnlockKey: "unlock-key",
|
||||
}, nil
|
||||
},
|
||||
@ -152,8 +149,8 @@ func TestSwarmUnlockKey(t *testing.T) {
|
||||
swarmInspectFunc: func() (swarm.Swarm, error) {
|
||||
return *builders.Swarm(builders.Autolock()), nil
|
||||
},
|
||||
swarmGetUnlockKeyFunc: func() (types.SwarmUnlockKeyResponse, error) {
|
||||
return types.SwarmUnlockKeyResponse{
|
||||
swarmGetUnlockKeyFunc: func() (swarm.UnlockKeyResponse, error) {
|
||||
return swarm.UnlockKeyResponse{
|
||||
UnlockKey: "unlock-key",
|
||||
}, nil
|
||||
},
|
||||
|
||||
@ -53,7 +53,7 @@ func runUpdate(ctx context.Context, dockerCli command.Cli, flags *pflag.FlagSet,
|
||||
|
||||
prevAutoLock := swarmInspect.Spec.EncryptionConfig.AutoLockManagers
|
||||
|
||||
opts.mergeSwarmSpec(&swarmInspect.Spec, flags, swarmInspect.ClusterInfo.TLSInfo.TrustRoot)
|
||||
opts.mergeSwarmSpec(&swarmInspect.Spec, flags, &swarmInspect.ClusterInfo.TLSInfo.TrustRoot)
|
||||
|
||||
curAutoLock := swarmInspect.Spec.EncryptionConfig.AutoLockManagers
|
||||
|
||||
|
||||
@ -9,7 +9,6 @@ import (
|
||||
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/docker/cli/internal/test/builders"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"gotest.tools/v3/assert"
|
||||
"gotest.tools/v3/golden"
|
||||
@ -22,7 +21,7 @@ func TestSwarmUpdateErrors(t *testing.T) {
|
||||
flags map[string]string
|
||||
swarmInspectFunc func() (swarm.Swarm, error)
|
||||
swarmUpdateFunc func(swarm swarm.Spec, flags swarm.UpdateFlags) error
|
||||
swarmGetUnlockKeyFunc func() (types.SwarmUnlockKeyResponse, error)
|
||||
swarmGetUnlockKeyFunc func() (swarm.UnlockKeyResponse, error)
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
@ -58,8 +57,8 @@ func TestSwarmUpdateErrors(t *testing.T) {
|
||||
swarmInspectFunc: func() (swarm.Swarm, error) {
|
||||
return *builders.Swarm(), nil
|
||||
},
|
||||
swarmGetUnlockKeyFunc: func() (types.SwarmUnlockKeyResponse, error) {
|
||||
return types.SwarmUnlockKeyResponse{}, errors.New("error getting unlock key")
|
||||
swarmGetUnlockKeyFunc: func() (swarm.UnlockKeyResponse, error) {
|
||||
return swarm.UnlockKeyResponse{}, errors.New("error getting unlock key")
|
||||
},
|
||||
expectedError: "error getting unlock key",
|
||||
},
|
||||
@ -97,7 +96,7 @@ func TestSwarmUpdate(t *testing.T) {
|
||||
flags map[string]string
|
||||
swarmInspectFunc func() (swarm.Swarm, error)
|
||||
swarmUpdateFunc func(swarm swarm.Spec, flags swarm.UpdateFlags) error
|
||||
swarmGetUnlockKeyFunc func() (types.SwarmUnlockKeyResponse, error)
|
||||
swarmGetUnlockKeyFunc func() (swarm.UnlockKeyResponse, error)
|
||||
}{
|
||||
{
|
||||
name: "noargs",
|
||||
@ -164,8 +163,8 @@ func TestSwarmUpdate(t *testing.T) {
|
||||
swarmInspectFunc: func() (swarm.Swarm, error) {
|
||||
return *builders.Swarm(), nil
|
||||
},
|
||||
swarmGetUnlockKeyFunc: func() (types.SwarmUnlockKeyResponse, error) {
|
||||
return types.SwarmUnlockKeyResponse{
|
||||
swarmGetUnlockKeyFunc: func() (swarm.UnlockKeyResponse, error) {
|
||||
return swarm.UnlockKeyResponse{
|
||||
UnlockKey: "unlock-key",
|
||||
}, nil
|
||||
},
|
||||
|
||||
@ -26,7 +26,7 @@ type fakeClient struct {
|
||||
infoFunc func(ctx context.Context) (system.Info, error)
|
||||
networkListFunc func(ctx context.Context, options network.ListOptions) ([]network.Summary, error)
|
||||
networkPruneFunc func(ctx context.Context, pruneFilter filters.Args) (network.PruneReport, error)
|
||||
nodeListFunc func(ctx context.Context, options types.NodeListOptions) ([]swarm.Node, error)
|
||||
nodeListFunc func(ctx context.Context, options swarm.NodeListOptions) ([]swarm.Node, error)
|
||||
serverVersion func(ctx context.Context) (types.Version, error)
|
||||
volumeListFunc func(ctx context.Context, options volume.ListOptions) (volume.ListResponse, error)
|
||||
}
|
||||
@ -81,7 +81,7 @@ func (cli *fakeClient) NetworksPrune(ctx context.Context, pruneFilter filters.Ar
|
||||
return network.PruneReport{}, nil
|
||||
}
|
||||
|
||||
func (cli *fakeClient) NodeList(ctx context.Context, options types.NodeListOptions) ([]swarm.Node, error) {
|
||||
func (cli *fakeClient) NodeList(ctx context.Context, options swarm.NodeListOptions) ([]swarm.Node, error) {
|
||||
if cli.nodeListFunc != nil {
|
||||
return cli.nodeListFunc(ctx, options)
|
||||
}
|
||||
|
||||
@ -4,10 +4,10 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/events"
|
||||
"github.com/docker/docker/api/types/image"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/api/types/volume"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@ -211,7 +211,7 @@ func networkNames(dockerCLI completion.APIClientProvider, cmd *cobra.Command) []
|
||||
// nodeNames contacts the API to get a list of node names.
|
||||
// In case of an error, an empty list is returned.
|
||||
func nodeNames(dockerCLI completion.APIClientProvider, cmd *cobra.Command) []string {
|
||||
list, err := dockerCLI.Client().NodeList(cmd.Context(), types.NodeListOptions{})
|
||||
list, err := dockerCLI.Client().NodeList(cmd.Context(), swarm.NodeListOptions{})
|
||||
if err != nil {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
@ -8,7 +8,6 @@ import (
|
||||
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/docker/cli/internal/test/builders"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/image"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
@ -111,7 +110,7 @@ func TestCompleteEventFilter(t *testing.T) {
|
||||
},
|
||||
{
|
||||
client: &fakeClient{
|
||||
nodeListFunc: func(_ context.Context, _ types.NodeListOptions) ([]swarm.Node, error) {
|
||||
nodeListFunc: func(_ context.Context, _ swarm.NodeListOptions) ([]swarm.Node, error) {
|
||||
return []swarm.Node{
|
||||
*builders.Node(builders.Hostname("n1")),
|
||||
}, nil
|
||||
@ -122,7 +121,7 @@ func TestCompleteEventFilter(t *testing.T) {
|
||||
},
|
||||
{
|
||||
client: &fakeClient{
|
||||
nodeListFunc: func(_ context.Context, _ types.NodeListOptions) ([]swarm.Node, error) {
|
||||
nodeListFunc: func(_ context.Context, _ swarm.NodeListOptions) ([]swarm.Node, error) {
|
||||
return []swarm.Node{}, errors.New("API error")
|
||||
},
|
||||
},
|
||||
|
||||
@ -15,9 +15,9 @@ import (
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/docker/cli/cli/command/inspect"
|
||||
flagsHelper "github.com/docker/cli/cli/flags"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/image"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
@ -137,7 +137,7 @@ func inspectNode(ctx context.Context, dockerCli command.Cli) inspect.GetRefFunc
|
||||
func inspectService(ctx context.Context, dockerCli command.Cli) inspect.GetRefFunc {
|
||||
return func(ref string) (any, []byte, error) {
|
||||
// Service inspect shows defaults values in empty fields.
|
||||
return dockerCli.Client().ServiceInspectWithRaw(ctx, ref, types.ServiceInspectOptions{InsertDefaults: true})
|
||||
return dockerCli.Client().ServiceInspectWithRaw(ctx, ref, swarm.ServiceInspectOptions{InsertDefaults: true})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -3,7 +3,6 @@ package task
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/client"
|
||||
)
|
||||
@ -11,7 +10,7 @@ import (
|
||||
type fakeClient struct {
|
||||
client.APIClient
|
||||
nodeInspectWithRaw func(ref string) (swarm.Node, []byte, error)
|
||||
serviceInspectWithRaw func(ref string, options types.ServiceInspectOptions) (swarm.Service, []byte, error)
|
||||
serviceInspectWithRaw func(ref string, options swarm.ServiceInspectOptions) (swarm.Service, []byte, error)
|
||||
}
|
||||
|
||||
func (cli *fakeClient) NodeInspectWithRaw(_ context.Context, ref string) (swarm.Node, []byte, error) {
|
||||
@ -21,7 +20,7 @@ func (cli *fakeClient) NodeInspectWithRaw(_ context.Context, ref string) (swarm.
|
||||
return swarm.Node{}, nil, nil
|
||||
}
|
||||
|
||||
func (cli *fakeClient) ServiceInspectWithRaw(_ context.Context, ref string, options types.ServiceInspectOptions) (swarm.Service, []byte, error) {
|
||||
func (cli *fakeClient) ServiceInspectWithRaw(_ context.Context, ref string, options swarm.ServiceInspectOptions) (swarm.Service, []byte, error) {
|
||||
if cli.serviceInspectWithRaw != nil {
|
||||
return cli.serviceInspectWithRaw(ref, options)
|
||||
}
|
||||
|
||||
@ -9,7 +9,6 @@ import (
|
||||
"github.com/docker/cli/cli/command/idresolver"
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/docker/cli/internal/test/builders"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"gotest.tools/v3/assert"
|
||||
"gotest.tools/v3/golden"
|
||||
@ -17,7 +16,7 @@ import (
|
||||
|
||||
func TestTaskPrintSorted(t *testing.T) {
|
||||
apiClient := &fakeClient{
|
||||
serviceInspectWithRaw: func(ref string, options types.ServiceInspectOptions) (swarm.Service, []byte, error) {
|
||||
serviceInspectWithRaw: func(ref string, options swarm.ServiceInspectOptions) (swarm.Service, []byte, error) {
|
||||
if ref == "service-id-one" {
|
||||
return *builders.Service(builders.ServiceName("service-name-1")), nil, nil
|
||||
}
|
||||
@ -109,7 +108,7 @@ func TestTaskPrintWithIndentation(t *testing.T) {
|
||||
const trunc = false
|
||||
const noResolve = false
|
||||
apiClient := &fakeClient{
|
||||
serviceInspectWithRaw: func(ref string, options types.ServiceInspectOptions) (swarm.Service, []byte, error) {
|
||||
serviceInspectWithRaw: func(ref string, options swarm.ServiceInspectOptions) (swarm.Service, []byte, error) {
|
||||
return *builders.Service(builders.ServiceName("service-name-foo")), nil, nil
|
||||
},
|
||||
nodeInspectWithRaw: func(ref string) (swarm.Node, []byte, error) {
|
||||
@ -145,7 +144,7 @@ func TestTaskPrintWithResolution(t *testing.T) {
|
||||
const trunc = false
|
||||
const noResolve = false
|
||||
apiClient := &fakeClient{
|
||||
serviceInspectWithRaw: func(ref string, options types.ServiceInspectOptions) (swarm.Service, []byte, error) {
|
||||
serviceInspectWithRaw: func(ref string, options swarm.ServiceInspectOptions) (swarm.Service, []byte, error) {
|
||||
return *builders.Service(builders.ServiceName("service-name-foo")), nil, nil
|
||||
},
|
||||
nodeInspectWithRaw: func(ref string) (swarm.Node, []byte, error) {
|
||||
|
||||
@ -82,7 +82,10 @@ func runSignImage(ctx context.Context, dockerCLI command.Cli, options signOption
|
||||
return trust.NotaryError(imgRefAndAuth.RepoInfo().Name.Name(), err)
|
||||
}
|
||||
}
|
||||
requestPrivilege := command.RegistryAuthenticationPrivilegedFunc(dockerCLI, imgRefAndAuth.RepoInfo().Index, "push")
|
||||
var requestPrivilege registrytypes.RequestAuthConfig
|
||||
if dockerCLI.In().IsTerminal() {
|
||||
requestPrivilege = command.RegistryAuthenticationPrivilegedFunc(dockerCLI, imgRefAndAuth.RepoInfo().Index, "push")
|
||||
}
|
||||
target, err := createTarget(notaryRepo, imgRefAndAuth.Tag())
|
||||
if err != nil || options.local {
|
||||
switch err := err.(type) {
|
||||
|
||||
@ -3,12 +3,14 @@ package configfile
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/cli/cli/config/credentials"
|
||||
"github.com/docker/cli/cli/config/memorystore"
|
||||
"github.com/docker/cli/cli/config/types"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
@ -46,6 +48,31 @@ type ConfigFile struct {
|
||||
Experimental string `json:"experimental,omitempty"`
|
||||
}
|
||||
|
||||
type configEnvAuth struct {
|
||||
Auth string `json:"auth"`
|
||||
}
|
||||
|
||||
type configEnv struct {
|
||||
AuthConfigs map[string]configEnvAuth `json:"auths"`
|
||||
}
|
||||
|
||||
// DockerEnvConfigKey is an environment variable that contains a JSON encoded
|
||||
// credential config. It only supports storing the credentials as a base64
|
||||
// encoded string in the format base64("username:pat").
|
||||
//
|
||||
// Adding additional fields will produce a parsing error.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// {
|
||||
// "auths": {
|
||||
// "example.test": {
|
||||
// "auth": base64-encoded-username-pat
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
const DockerEnvConfigKey = "DOCKER_AUTH_CONFIG"
|
||||
|
||||
// ProxyConfig contains proxy configuration settings
|
||||
type ProxyConfig struct {
|
||||
HTTPProxy string `json:"httpProxy,omitempty"`
|
||||
@ -152,7 +179,8 @@ func (configFile *ConfigFile) Save() (retErr error) {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
temp.Close()
|
||||
// ignore error as the file may already be closed when we reach this.
|
||||
_ = temp.Close()
|
||||
if retErr != nil {
|
||||
if err := os.Remove(temp.Name()); err != nil {
|
||||
logrus.WithError(err).WithField("file", temp.Name()).Debug("Error cleaning up temp file")
|
||||
@ -169,10 +197,16 @@ func (configFile *ConfigFile) Save() (retErr error) {
|
||||
return errors.Wrap(err, "error closing temp file")
|
||||
}
|
||||
|
||||
// Handle situation where the configfile is a symlink
|
||||
// Handle situation where the configfile is a symlink, and allow for dangling symlinks
|
||||
cfgFile := configFile.Filename
|
||||
if f, err := os.Readlink(cfgFile); err == nil {
|
||||
if f, err := filepath.EvalSymlinks(cfgFile); err == nil {
|
||||
cfgFile = f
|
||||
} else if os.IsNotExist(err) {
|
||||
// extract the path from the error if the configfile does not exist or is a dangling symlink
|
||||
var pathError *os.PathError
|
||||
if errors.As(err, &pathError) {
|
||||
cfgFile = pathError.Path
|
||||
}
|
||||
}
|
||||
|
||||
// Try copying the current config file (if any) ownership and permissions
|
||||
@ -256,10 +290,64 @@ func decodeAuth(authStr string) (string, string, error) {
|
||||
// GetCredentialsStore returns a new credentials store from the settings in the
|
||||
// configuration file
|
||||
func (configFile *ConfigFile) GetCredentialsStore(registryHostname string) credentials.Store {
|
||||
store := credentials.NewFileStore(configFile)
|
||||
|
||||
if helper := getConfiguredCredentialStore(configFile, registryHostname); helper != "" {
|
||||
return newNativeStore(configFile, helper)
|
||||
store = newNativeStore(configFile, helper)
|
||||
}
|
||||
return credentials.NewFileStore(configFile)
|
||||
|
||||
envConfig := os.Getenv(DockerEnvConfigKey)
|
||||
if envConfig == "" {
|
||||
return store
|
||||
}
|
||||
|
||||
authConfig, err := parseEnvConfig(envConfig)
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintln(os.Stderr, "Failed to create credential store from DOCKER_AUTH_CONFIG: ", err)
|
||||
return store
|
||||
}
|
||||
|
||||
// use DOCKER_AUTH_CONFIG if set
|
||||
// it uses the native or file store as a fallback to fetch and store credentials
|
||||
envStore, err := memorystore.New(
|
||||
memorystore.WithAuthConfig(authConfig),
|
||||
memorystore.WithFallbackStore(store),
|
||||
)
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintln(os.Stderr, "Failed to create credential store from DOCKER_AUTH_CONFIG: ", err)
|
||||
return store
|
||||
}
|
||||
|
||||
return envStore
|
||||
}
|
||||
|
||||
func parseEnvConfig(v string) (map[string]types.AuthConfig, error) {
|
||||
envConfig := &configEnv{}
|
||||
decoder := json.NewDecoder(strings.NewReader(v))
|
||||
decoder.DisallowUnknownFields()
|
||||
if err := decoder.Decode(envConfig); err != nil && !errors.Is(err, io.EOF) {
|
||||
return nil, err
|
||||
}
|
||||
if decoder.More() {
|
||||
return nil, errors.New("DOCKER_AUTH_CONFIG does not support more than one JSON object")
|
||||
}
|
||||
|
||||
authConfigs := make(map[string]types.AuthConfig)
|
||||
for addr, envAuth := range envConfig.AuthConfigs {
|
||||
if envAuth.Auth == "" {
|
||||
return nil, fmt.Errorf("DOCKER_AUTH_CONFIG environment variable is missing key `auth` for %s", addr)
|
||||
}
|
||||
username, password, err := decodeAuth(envAuth.Auth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
authConfigs[addr] = types.AuthConfig{
|
||||
Username: username,
|
||||
Password: password,
|
||||
ServerAddress: addr,
|
||||
}
|
||||
}
|
||||
return authConfigs, nil
|
||||
}
|
||||
|
||||
// var for unit testing.
|
||||
|
||||
@ -481,6 +481,133 @@ func TestLoadFromReaderWithUsernamePassword(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
const envTestUserPassConfig = `{
|
||||
"auths": {
|
||||
"env.example.test": {
|
||||
"username": "env_user",
|
||||
"password": "env_pass",
|
||||
"serveraddress": "env.example.test"
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
const envTestAuthConfig = `{
|
||||
"auths": {
|
||||
"env.example.test": {
|
||||
"auth": "ZW52X3VzZXI6ZW52X3Bhc3M="
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
func TestGetAllCredentialsFromEnvironment(t *testing.T) {
|
||||
t.Run("can parse DOCKER_AUTH_CONFIG auth field", func(t *testing.T) {
|
||||
config := &ConfigFile{}
|
||||
|
||||
t.Setenv("DOCKER_AUTH_CONFIG", envTestAuthConfig)
|
||||
|
||||
authConfigs, err := config.GetAllCredentials()
|
||||
assert.NilError(t, err)
|
||||
|
||||
expected := map[string]types.AuthConfig{
|
||||
"env.example.test": {
|
||||
Username: "env_user",
|
||||
Password: "env_pass",
|
||||
ServerAddress: "env.example.test",
|
||||
},
|
||||
}
|
||||
assert.Check(t, is.DeepEqual(authConfigs, expected))
|
||||
})
|
||||
|
||||
t.Run("malformed DOCKER_AUTH_CONFIG should fallback to underlying store", func(t *testing.T) {
|
||||
fallbackStore := map[string]types.AuthConfig{
|
||||
"fallback.example.test": {
|
||||
Username: "fallback_user",
|
||||
Password: "fallback_pass",
|
||||
ServerAddress: "fallback.example.test",
|
||||
},
|
||||
}
|
||||
config := &ConfigFile{
|
||||
AuthConfigs: fallbackStore,
|
||||
}
|
||||
|
||||
t.Setenv("DOCKER_AUTH_CONFIG", envTestUserPassConfig)
|
||||
|
||||
authConfigs, err := config.GetAllCredentials()
|
||||
assert.NilError(t, err)
|
||||
|
||||
expected := fallbackStore
|
||||
assert.Check(t, is.DeepEqual(authConfigs, expected))
|
||||
})
|
||||
|
||||
t.Run("can fetch credentials from DOCKER_AUTH_CONFIG and underlying store", func(t *testing.T) {
|
||||
configFile := New("filename")
|
||||
exampleAuth := types.AuthConfig{
|
||||
Username: "user",
|
||||
Password: "pass",
|
||||
}
|
||||
configFile.AuthConfigs["foo.example.test"] = exampleAuth
|
||||
|
||||
t.Setenv("DOCKER_AUTH_CONFIG", envTestAuthConfig)
|
||||
|
||||
authConfigs, err := configFile.GetAllCredentials()
|
||||
assert.NilError(t, err)
|
||||
|
||||
expected := map[string]types.AuthConfig{
|
||||
"foo.example.test": exampleAuth,
|
||||
"env.example.test": {
|
||||
Username: "env_user",
|
||||
Password: "env_pass",
|
||||
ServerAddress: "env.example.test",
|
||||
},
|
||||
}
|
||||
assert.Check(t, is.DeepEqual(authConfigs, expected))
|
||||
|
||||
fooConfig, err := configFile.GetAuthConfig("foo.example.test")
|
||||
assert.NilError(t, err)
|
||||
expectedAuth := expected["foo.example.test"]
|
||||
assert.Check(t, is.DeepEqual(fooConfig, expectedAuth))
|
||||
|
||||
envConfig, err := configFile.GetAuthConfig("env.example.test")
|
||||
assert.NilError(t, err)
|
||||
expectedAuth = expected["env.example.test"]
|
||||
assert.Check(t, is.DeepEqual(envConfig, expectedAuth))
|
||||
})
|
||||
|
||||
t.Run("env is ignored when empty", func(t *testing.T) {
|
||||
configFile := New("filename")
|
||||
|
||||
t.Setenv("DOCKER_AUTH_CONFIG", "")
|
||||
|
||||
authConfigs, err := configFile.GetAllCredentials()
|
||||
assert.NilError(t, err)
|
||||
assert.Check(t, is.Len(authConfigs, 0))
|
||||
})
|
||||
}
|
||||
|
||||
func TestParseEnvConfig(t *testing.T) {
|
||||
t.Run("should error on unexpected fields", func(t *testing.T) {
|
||||
_, err := parseEnvConfig(envTestUserPassConfig)
|
||||
assert.ErrorContains(t, err, "json: unknown field \"username\"")
|
||||
})
|
||||
t.Run("should be able to load env credentials", func(t *testing.T) {
|
||||
got, err := parseEnvConfig(envTestAuthConfig)
|
||||
assert.NilError(t, err)
|
||||
expected := map[string]types.AuthConfig{
|
||||
"env.example.test": {
|
||||
Username: "env_user",
|
||||
Password: "env_pass",
|
||||
ServerAddress: "env.example.test",
|
||||
},
|
||||
}
|
||||
assert.NilError(t, err)
|
||||
assert.Check(t, is.DeepEqual(got, expected))
|
||||
})
|
||||
t.Run("should not support multiple JSON objects", func(t *testing.T) {
|
||||
_, err := parseEnvConfig(`{"auths":{"env.example.test":{"auth":"something"}}}{}`)
|
||||
assert.ErrorContains(t, err, "does not support more than one JSON object")
|
||||
})
|
||||
}
|
||||
|
||||
func TestSave(t *testing.T) {
|
||||
configFile := New("test-save")
|
||||
defer os.Remove("test-save")
|
||||
@ -538,6 +665,34 @@ func TestSaveWithSymlink(t *testing.T) {
|
||||
assert.Check(t, is.Equal(string(cfg), "{\n \"auths\": {}\n}"))
|
||||
}
|
||||
|
||||
func TestSaveWithRelativeSymlink(t *testing.T) {
|
||||
dir := fs.NewDir(t, t.Name(), fs.WithFile("real-config.json", `{}`))
|
||||
defer dir.Remove()
|
||||
|
||||
symLink := dir.Join("config.json")
|
||||
relativeRealFile := "real-config.json"
|
||||
realFile := dir.Join(relativeRealFile)
|
||||
err := os.Symlink(relativeRealFile, symLink)
|
||||
assert.NilError(t, err)
|
||||
|
||||
configFile := New(symLink)
|
||||
|
||||
err = configFile.Save()
|
||||
assert.NilError(t, err)
|
||||
|
||||
fi, err := os.Lstat(symLink)
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, fi.Mode()&os.ModeSymlink != 0, "expected %s to be a symlink", symLink)
|
||||
|
||||
cfg, err := os.ReadFile(symLink)
|
||||
assert.NilError(t, err)
|
||||
assert.Check(t, is.Equal(string(cfg), "{\n \"auths\": {}\n}"))
|
||||
|
||||
cfg, err = os.ReadFile(realFile)
|
||||
assert.NilError(t, err)
|
||||
assert.Check(t, is.Equal(string(cfg), "{\n \"auths\": {}\n}"))
|
||||
}
|
||||
|
||||
func TestPluginConfig(t *testing.T) {
|
||||
configFile := New("test-plugin")
|
||||
defer os.Remove("test-plugin")
|
||||
|
||||
126
cli/config/memorystore/store.go
Normal file
126
cli/config/memorystore/store.go
Normal file
@ -0,0 +1,126 @@
|
||||
//go:build go1.23
|
||||
|
||||
package memorystore
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/docker/cli/cli/config/credentials"
|
||||
"github.com/docker/cli/cli/config/types"
|
||||
)
|
||||
|
||||
var errValueNotFound = errors.New("value not found")
|
||||
|
||||
func IsErrValueNotFound(err error) bool {
|
||||
return errors.Is(err, errValueNotFound)
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
lock sync.RWMutex
|
||||
memoryCredentials map[string]types.AuthConfig
|
||||
fallbackStore credentials.Store
|
||||
}
|
||||
|
||||
func (e *Config) Erase(serverAddress string) error {
|
||||
e.lock.Lock()
|
||||
defer e.lock.Unlock()
|
||||
delete(e.memoryCredentials, serverAddress)
|
||||
|
||||
if e.fallbackStore != nil {
|
||||
err := e.fallbackStore.Erase(serverAddress)
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintln(os.Stderr, "memorystore: ", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Config) Get(serverAddress string) (types.AuthConfig, error) {
|
||||
e.lock.RLock()
|
||||
defer e.lock.RUnlock()
|
||||
authConfig, ok := e.memoryCredentials[serverAddress]
|
||||
if !ok {
|
||||
if e.fallbackStore != nil {
|
||||
return e.fallbackStore.Get(serverAddress)
|
||||
}
|
||||
return types.AuthConfig{}, errValueNotFound
|
||||
}
|
||||
return authConfig, nil
|
||||
}
|
||||
|
||||
func (e *Config) GetAll() (map[string]types.AuthConfig, error) {
|
||||
e.lock.RLock()
|
||||
defer e.lock.RUnlock()
|
||||
creds := make(map[string]types.AuthConfig)
|
||||
|
||||
if e.fallbackStore != nil {
|
||||
fileCredentials, err := e.fallbackStore.GetAll()
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintln(os.Stderr, "memorystore: ", err)
|
||||
} else {
|
||||
creds = fileCredentials
|
||||
}
|
||||
}
|
||||
|
||||
maps.Copy(creds, e.memoryCredentials)
|
||||
return creds, nil
|
||||
}
|
||||
|
||||
func (e *Config) Store(authConfig types.AuthConfig) error {
|
||||
e.lock.Lock()
|
||||
defer e.lock.Unlock()
|
||||
e.memoryCredentials[authConfig.ServerAddress] = authConfig
|
||||
|
||||
if e.fallbackStore != nil {
|
||||
return e.fallbackStore.Store(authConfig)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WithFallbackStore sets a fallback store.
|
||||
//
|
||||
// Write operations will be performed on both the memory store and the
|
||||
// fallback store.
|
||||
//
|
||||
// Read operations will first check the memory store, and if the credential
|
||||
// is not found, it will then check the fallback store.
|
||||
//
|
||||
// Retrieving all credentials will return from both the memory store and the
|
||||
// fallback store, merging the results from both stores into a single map.
|
||||
//
|
||||
// Data stored in the memory store will take precedence over data in the
|
||||
// fallback store.
|
||||
func WithFallbackStore(store credentials.Store) Options {
|
||||
return func(s *Config) error {
|
||||
s.fallbackStore = store
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithAuthConfig allows to set the initial credentials in the memory store.
|
||||
func WithAuthConfig(config map[string]types.AuthConfig) Options {
|
||||
return func(s *Config) error {
|
||||
s.memoryCredentials = config
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
type Options func(*Config) error
|
||||
|
||||
// New creates a new in memory credential store
|
||||
func New(opts ...Options) (credentials.Store, error) {
|
||||
m := &Config{
|
||||
memoryCredentials: make(map[string]types.AuthConfig),
|
||||
}
|
||||
for _, opt := range opts {
|
||||
if err := opt(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
131
cli/config/memorystore/store_test.go
Normal file
131
cli/config/memorystore/store_test.go
Normal file
@ -0,0 +1,131 @@
|
||||
package memorystore
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/cli/config/types"
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
)
|
||||
|
||||
func TestMemoryStore(t *testing.T) {
|
||||
config := map[string]types.AuthConfig{
|
||||
"https://example.test": {
|
||||
Username: "something-something",
|
||||
ServerAddress: "https://example.test",
|
||||
Auth: "super_secret_token",
|
||||
},
|
||||
}
|
||||
|
||||
fallbackConfig := map[string]types.AuthConfig{
|
||||
"https://only-in-file.example.test": {
|
||||
Username: "something-something",
|
||||
ServerAddress: "https://only-in-file.example.test",
|
||||
Auth: "super_secret_token",
|
||||
},
|
||||
}
|
||||
|
||||
fallbackStore, err := New(WithAuthConfig(fallbackConfig))
|
||||
assert.NilError(t, err)
|
||||
|
||||
memoryStore, err := New(WithAuthConfig(config), WithFallbackStore(fallbackStore))
|
||||
assert.NilError(t, err)
|
||||
|
||||
t.Run("get credentials from memory store", func(t *testing.T) {
|
||||
c, err := memoryStore.Get("https://example.test")
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, c, config["https://example.test"])
|
||||
})
|
||||
|
||||
t.Run("get credentials from fallback store", func(t *testing.T) {
|
||||
c, err := memoryStore.Get("https://only-in-file.example.test")
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, c, fallbackConfig["https://only-in-file.example.test"])
|
||||
})
|
||||
|
||||
t.Run("storing credentials in memory store should also be in defined fallback store", func(t *testing.T) {
|
||||
err := memoryStore.Store(types.AuthConfig{
|
||||
Username: "not-in-store",
|
||||
ServerAddress: "https://not-in-store.example.test",
|
||||
Auth: "not-in-store_token",
|
||||
})
|
||||
assert.NilError(t, err)
|
||||
c, err := memoryStore.Get("https://not-in-store.example.test")
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, c.Username, "not-in-store")
|
||||
assert.Equal(t, c.ServerAddress, "https://not-in-store.example.test")
|
||||
assert.Equal(t, c.Auth, "not-in-store_token")
|
||||
|
||||
cc, err := fallbackStore.Get("https://not-in-store.example.test")
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, cc.Username, "not-in-store")
|
||||
assert.Equal(t, cc.ServerAddress, "https://not-in-store.example.test")
|
||||
assert.Equal(t, cc.Auth, "not-in-store_token")
|
||||
})
|
||||
|
||||
t.Run("delete credentials should remove credentials from memory store and fallback store", func(t *testing.T) {
|
||||
err := memoryStore.Store(types.AuthConfig{
|
||||
Username: "a-new-credential",
|
||||
ServerAddress: "https://a-new-credential.example.test",
|
||||
Auth: "a-new-credential_token",
|
||||
})
|
||||
assert.NilError(t, err)
|
||||
err = memoryStore.Erase("https://a-new-credential.example.test")
|
||||
assert.NilError(t, err)
|
||||
_, err = memoryStore.Get("https://a-new-credential.example.test")
|
||||
assert.Check(t, is.ErrorIs(err, errValueNotFound))
|
||||
_, err = fallbackStore.Get("https://a-new-credential.example.test")
|
||||
assert.Check(t, is.ErrorIs(err, errValueNotFound))
|
||||
})
|
||||
}
|
||||
|
||||
func TestMemoryStoreWithoutFallback(t *testing.T) {
|
||||
config := map[string]types.AuthConfig{
|
||||
"https://example.test": {
|
||||
Username: "something-something",
|
||||
ServerAddress: "https://example.test",
|
||||
Auth: "super_secret_token",
|
||||
},
|
||||
}
|
||||
|
||||
memoryStore, err := New(WithAuthConfig(config))
|
||||
assert.NilError(t, err)
|
||||
|
||||
t.Run("get credentials from memory store without fallback", func(t *testing.T) {
|
||||
c, err := memoryStore.Get("https://example.test")
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, c, config["https://example.test"])
|
||||
})
|
||||
|
||||
t.Run("get non-existing credentials from memory store should error", func(t *testing.T) {
|
||||
_, err := memoryStore.Get("https://not-in-store.example.test")
|
||||
assert.Check(t, is.ErrorIs(err, errValueNotFound))
|
||||
})
|
||||
|
||||
t.Run("case store credentials", func(t *testing.T) {
|
||||
err := memoryStore.Store(types.AuthConfig{
|
||||
Username: "not-in-store",
|
||||
ServerAddress: "https://not-in-store.example.test",
|
||||
Auth: "not-in-store_token",
|
||||
})
|
||||
assert.NilError(t, err)
|
||||
c, err := memoryStore.Get("https://not-in-store.example.test")
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, c.Username, "not-in-store")
|
||||
assert.Equal(t, c.ServerAddress, "https://not-in-store.example.test")
|
||||
assert.Equal(t, c.Auth, "not-in-store_token")
|
||||
})
|
||||
|
||||
t.Run("delete credentials should remove credentials from memory store", func(t *testing.T) {
|
||||
err := memoryStore.Store(types.AuthConfig{
|
||||
Username: "a-new-credential",
|
||||
ServerAddress: "https://a-new-credential.example.test",
|
||||
Auth: "a-new-credential_token",
|
||||
})
|
||||
assert.NilError(t, err)
|
||||
err = memoryStore.Erase("https://a-new-credential.example.test")
|
||||
assert.NilError(t, err)
|
||||
_, err = memoryStore.Get("https://a-new-credential.example.test")
|
||||
assert.Check(t, is.ErrorIs(err, errValueNotFound))
|
||||
})
|
||||
}
|
||||
@ -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
|
||||
|
||||
27
cli/connhelper/internal/syntax/LICENSE
Normal file
27
cli/connhelper/internal/syntax/LICENSE
Normal file
@ -0,0 +1,27 @@
|
||||
Copyright (c) 2016, Daniel Martí. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
13
cli/connhelper/internal/syntax/doc.go
Normal file
13
cli/connhelper/internal/syntax/doc.go
Normal file
@ -0,0 +1,13 @@
|
||||
// Package syntax is a fork of [mvdan.cc/sh/v3@v3.10.0/syntax].
|
||||
//
|
||||
// Copyright (c) 2016, Daniel Martí. All rights reserved.
|
||||
//
|
||||
// It is a reduced set of the package to only provide the [Quote] function,
|
||||
// and contains the [LICENSE], [quote.go] and [parser.go] files at the given
|
||||
// revision.
|
||||
//
|
||||
// [quote.go]: https://raw.githubusercontent.com/mvdan/sh/refs/tags/v3.10.0/syntax/quote.go
|
||||
// [parser.go]: https://raw.githubusercontent.com/mvdan/sh/refs/tags/v3.10.0/syntax/parser.go
|
||||
// [LICENSE]: https://raw.githubusercontent.com/mvdan/sh/refs/tags/v3.10.0/LICENSE
|
||||
// [mvdan.cc/sh/v3@v3.10.0/syntax]: https://pkg.go.dev/mvdan.cc/sh/v3@v3.10.0/syntax
|
||||
package syntax
|
||||
95
cli/connhelper/internal/syntax/parser.go
Normal file
95
cli/connhelper/internal/syntax/parser.go
Normal file
@ -0,0 +1,95 @@
|
||||
// Copyright (c) 2016, Daniel Martí <mvdan@mvdan.cc>
|
||||
// See LICENSE for licensing information
|
||||
|
||||
package syntax
|
||||
|
||||
// LangVariant describes a shell language variant to use when tokenizing and
|
||||
// parsing shell code. The zero value is [LangBash].
|
||||
type LangVariant int
|
||||
|
||||
const (
|
||||
// LangBash corresponds to the GNU Bash language, as described in its
|
||||
// manual at https://www.gnu.org/software/bash/manual/bash.html.
|
||||
//
|
||||
// We currently follow Bash version 5.2.
|
||||
//
|
||||
// Its string representation is "bash".
|
||||
LangBash LangVariant = iota
|
||||
|
||||
// LangPOSIX corresponds to the POSIX Shell language, as described at
|
||||
// https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html.
|
||||
//
|
||||
// Its string representation is "posix" or "sh".
|
||||
LangPOSIX
|
||||
|
||||
// LangMirBSDKorn corresponds to the MirBSD Korn Shell, also known as
|
||||
// mksh, as described at http://www.mirbsd.org/htman/i386/man1/mksh.htm.
|
||||
// Note that it shares some features with Bash, due to the shared
|
||||
// ancestry that is ksh.
|
||||
//
|
||||
// We currently follow mksh version 59.
|
||||
//
|
||||
// Its string representation is "mksh".
|
||||
LangMirBSDKorn
|
||||
|
||||
// LangBats corresponds to the Bash Automated Testing System language,
|
||||
// as described at https://github.com/bats-core/bats-core. Note that
|
||||
// it's just a small extension of the Bash language.
|
||||
//
|
||||
// Its string representation is "bats".
|
||||
LangBats
|
||||
|
||||
// LangAuto corresponds to automatic language detection,
|
||||
// commonly used by end-user applications like shfmt,
|
||||
// which can guess a file's language variant given its filename or shebang.
|
||||
//
|
||||
// At this time, [Variant] does not support LangAuto.
|
||||
LangAuto
|
||||
)
|
||||
|
||||
func (l LangVariant) String() string {
|
||||
switch l {
|
||||
case LangBash:
|
||||
return "bash"
|
||||
case LangPOSIX:
|
||||
return "posix"
|
||||
case LangMirBSDKorn:
|
||||
return "mksh"
|
||||
case LangBats:
|
||||
return "bats"
|
||||
case LangAuto:
|
||||
return "auto"
|
||||
}
|
||||
return "unknown shell language variant"
|
||||
}
|
||||
|
||||
// IsKeyword returns true if the given word is part of the language keywords.
|
||||
func IsKeyword(word string) bool {
|
||||
// This list has been copied from the bash 5.1 source code, file y.tab.c +4460
|
||||
switch word {
|
||||
case
|
||||
"!",
|
||||
"[[", // only if COND_COMMAND is defined
|
||||
"]]", // only if COND_COMMAND is defined
|
||||
"case",
|
||||
"coproc", // only if COPROCESS_SUPPORT is defined
|
||||
"do",
|
||||
"done",
|
||||
"else",
|
||||
"esac",
|
||||
"fi",
|
||||
"for",
|
||||
"function",
|
||||
"if",
|
||||
"in",
|
||||
"select", // only if SELECT_COMMAND is defined
|
||||
"then",
|
||||
"time", // only if COMMAND_TIMING is defined
|
||||
"until",
|
||||
"while",
|
||||
"{",
|
||||
"}":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
187
cli/connhelper/internal/syntax/quote.go
Normal file
187
cli/connhelper/internal/syntax/quote.go
Normal file
@ -0,0 +1,187 @@
|
||||
// Copyright (c) 2021, Daniel Martí <mvdan@mvdan.cc>
|
||||
// See LICENSE for licensing information
|
||||
|
||||
package syntax
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
type QuoteError struct {
|
||||
ByteOffset int
|
||||
Message string
|
||||
}
|
||||
|
||||
func (e QuoteError) Error() string {
|
||||
return fmt.Sprintf("cannot quote character at byte %d: %s", e.ByteOffset, e.Message)
|
||||
}
|
||||
|
||||
const (
|
||||
quoteErrNull = "shell strings cannot contain null bytes"
|
||||
quoteErrPOSIX = "POSIX shell lacks escape sequences"
|
||||
quoteErrRange = "rune out of range"
|
||||
quoteErrMksh = "mksh cannot escape codepoints above 16 bits"
|
||||
)
|
||||
|
||||
// Quote returns a quoted version of the input string,
|
||||
// so that the quoted version is expanded or interpreted
|
||||
// as the original string in the given language variant.
|
||||
//
|
||||
// Quoting is necessary when using arbitrary literal strings
|
||||
// as words in a shell script or command.
|
||||
// Without quoting, one can run into syntax errors,
|
||||
// as well as the possibility of running unintended code.
|
||||
//
|
||||
// An error is returned when a string cannot be quoted for a variant.
|
||||
// For instance, POSIX lacks escape sequences for non-printable characters,
|
||||
// and no language variant can represent a string containing null bytes.
|
||||
// In such cases, the returned error type will be *QuoteError.
|
||||
//
|
||||
// The quoting strategy is chosen on a best-effort basis,
|
||||
// to minimize the amount of extra bytes necessary.
|
||||
//
|
||||
// Some strings do not require any quoting and are returned unchanged.
|
||||
// Those strings can be directly surrounded in single quotes as well.
|
||||
//
|
||||
//nolint:gocyclo // ignore "cyclomatic complexity 35 of func `Quote` is high (> 16) (gocyclo)"
|
||||
func Quote(s string, lang LangVariant) (string, error) {
|
||||
if s == "" {
|
||||
// Special case; an empty string must always be quoted,
|
||||
// as otherwise it expands to zero fields.
|
||||
return "''", nil
|
||||
}
|
||||
shellChars := false
|
||||
nonPrintable := false
|
||||
offs := 0
|
||||
for rem := s; len(rem) > 0; {
|
||||
r, size := utf8.DecodeRuneInString(rem)
|
||||
switch r {
|
||||
// Like regOps; token characters.
|
||||
case ';', '"', '\'', '(', ')', '$', '|', '&', '>', '<', '`',
|
||||
// Whitespace; might result in multiple fields.
|
||||
' ', '\t', '\r', '\n',
|
||||
// Escape sequences would be expanded.
|
||||
'\\',
|
||||
// Would start a comment unless quoted.
|
||||
'#',
|
||||
// Might result in brace expansion.
|
||||
'{',
|
||||
// Might result in tilde expansion.
|
||||
'~',
|
||||
// Might result in globbing.
|
||||
'*', '?', '[',
|
||||
// Might result in an assignment.
|
||||
'=':
|
||||
shellChars = true
|
||||
case '\x00':
|
||||
return "", &QuoteError{ByteOffset: offs, Message: quoteErrNull}
|
||||
}
|
||||
if r == utf8.RuneError || !unicode.IsPrint(r) {
|
||||
if lang == LangPOSIX {
|
||||
return "", &QuoteError{ByteOffset: offs, Message: quoteErrPOSIX}
|
||||
}
|
||||
nonPrintable = true
|
||||
}
|
||||
rem = rem[size:]
|
||||
offs += size
|
||||
}
|
||||
if !shellChars && !nonPrintable && !IsKeyword(s) {
|
||||
// Nothing to quote; avoid allocating.
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// Single quotes are usually best,
|
||||
// as they don't require any escaping of characters.
|
||||
// If we have any invalid utf8 or non-printable runes,
|
||||
// use $'' so that we can escape them.
|
||||
// Note that we can't use double quotes for those.
|
||||
var b strings.Builder
|
||||
if nonPrintable {
|
||||
b.WriteString("$'")
|
||||
lastRequoteIfHex := false
|
||||
offs = 0
|
||||
for rem := s; len(rem) > 0; {
|
||||
nextRequoteIfHex := false
|
||||
r, size := utf8.DecodeRuneInString(rem)
|
||||
switch {
|
||||
case r == '\'', r == '\\':
|
||||
b.WriteByte('\\')
|
||||
b.WriteRune(r)
|
||||
case unicode.IsPrint(r) && r != utf8.RuneError:
|
||||
if lastRequoteIfHex && isHex(r) {
|
||||
b.WriteString("'$'")
|
||||
}
|
||||
b.WriteRune(r)
|
||||
case r == '\a':
|
||||
b.WriteString(`\a`)
|
||||
case r == '\b':
|
||||
b.WriteString(`\b`)
|
||||
case r == '\f':
|
||||
b.WriteString(`\f`)
|
||||
case r == '\n':
|
||||
b.WriteString(`\n`)
|
||||
case r == '\r':
|
||||
b.WriteString(`\r`)
|
||||
case r == '\t':
|
||||
b.WriteString(`\t`)
|
||||
case r == '\v':
|
||||
b.WriteString(`\v`)
|
||||
case r < utf8.RuneSelf, r == utf8.RuneError && size == 1:
|
||||
// \xXX, fixed at two hexadecimal characters.
|
||||
fmt.Fprintf(&b, "\\x%02x", rem[0])
|
||||
// Unfortunately, mksh allows \x to consume more hex characters.
|
||||
// Ensure that we don't allow it to read more than two.
|
||||
if lang == LangMirBSDKorn {
|
||||
nextRequoteIfHex = true
|
||||
}
|
||||
case r > utf8.MaxRune:
|
||||
// Not a valid Unicode code point?
|
||||
return "", &QuoteError{ByteOffset: offs, Message: quoteErrRange}
|
||||
case lang == LangMirBSDKorn && r > 0xFFFD:
|
||||
// From the CAVEATS section in R59's man page:
|
||||
//
|
||||
// mksh currently uses OPTU-16 internally, which is the same as
|
||||
// UTF-8 and CESU-8 with 0000..FFFD being valid codepoints.
|
||||
return "", &QuoteError{ByteOffset: offs, Message: quoteErrMksh}
|
||||
case r < 0x10000:
|
||||
// \uXXXX, fixed at four hexadecimal characters.
|
||||
fmt.Fprintf(&b, "\\u%04x", r)
|
||||
default:
|
||||
// \UXXXXXXXX, fixed at eight hexadecimal characters.
|
||||
fmt.Fprintf(&b, "\\U%08x", r)
|
||||
}
|
||||
rem = rem[size:]
|
||||
lastRequoteIfHex = nextRequoteIfHex
|
||||
offs += size
|
||||
}
|
||||
b.WriteString("'")
|
||||
return b.String(), nil
|
||||
}
|
||||
|
||||
// Single quotes without any need for escaping.
|
||||
if !strings.Contains(s, "'") {
|
||||
return "'" + s + "'", nil
|
||||
}
|
||||
|
||||
// The string contains single quotes,
|
||||
// so fall back to double quotes.
|
||||
b.WriteByte('"')
|
||||
for _, r := range s {
|
||||
switch r {
|
||||
case '"', '\\', '`', '$':
|
||||
b.WriteByte('\\')
|
||||
}
|
||||
b.WriteRune(r)
|
||||
}
|
||||
b.WriteByte('"')
|
||||
return b.String(), nil
|
||||
}
|
||||
|
||||
func isHex(r rune) bool {
|
||||
return (r >= '0' && r <= '9') ||
|
||||
(r >= 'a' && r <= 'f') ||
|
||||
(r >= 'A' && r <= 'F')
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -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")
|
||||
})
|
||||
|
||||
@ -302,12 +302,12 @@ func tryPluginRun(ctx context.Context, dockerCli command.Cli, cmd *cobra.Command
|
||||
srv, err := socket.NewPluginServer(nil)
|
||||
if err == nil {
|
||||
plugincmd.Env = append(plugincmd.Env, socket.EnvKey+"="+srv.Addr().String())
|
||||
defer func() {
|
||||
// Close the server when plugin execution is over, so that in case
|
||||
// it's still open, any sockets on the filesystem are cleaned up.
|
||||
_ = srv.Close()
|
||||
}()
|
||||
}
|
||||
defer func() {
|
||||
// Close the server when plugin execution is over, so that in case
|
||||
// it's still open, any sockets on the filesystem are cleaned up.
|
||||
_ = srv.Close()
|
||||
}()
|
||||
|
||||
// Set additional environment variables specified by the caller.
|
||||
plugincmd.Env = append(plugincmd.Env, envs...)
|
||||
@ -334,7 +334,9 @@ func tryPluginRun(ctx context.Context, dockerCli command.Cli, cmd *cobra.Command
|
||||
//
|
||||
// Repeated invocations will result in EINVAL,
|
||||
// or EBADF; but that is fine for our purposes.
|
||||
_ = srv.Close()
|
||||
if srv != nil {
|
||||
_ = srv.Close()
|
||||
}
|
||||
|
||||
// force the process to terminate if it hasn't already
|
||||
if force {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
variable "GO_VERSION" {
|
||||
default = "1.24.3"
|
||||
default = "1.24.5"
|
||||
}
|
||||
variable "VERSION" {
|
||||
default = ""
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
ARG GO_VERSION=1.24.3
|
||||
ARG GO_VERSION=1.24.5
|
||||
ARG ALPINE_VERSION=3.21
|
||||
|
||||
# BUILDX_VERSION sets the version of buildx to install in the dev container.
|
||||
# It must be a valid tag in the docker.io/docker/buildx-bin image repository
|
||||
# on Docker Hub.
|
||||
ARG BUILDX_VERSION=0.23.0
|
||||
ARG BUILDX_VERSION=0.24.0
|
||||
FROM docker/buildx-bin:${BUILDX_VERSION} AS buildx
|
||||
|
||||
FROM golang:${GO_VERSION}-alpine${ALPINE_VERSION} AS golang
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
ARG GO_VERSION=1.24.3
|
||||
ARG GO_VERSION=1.24.5
|
||||
ARG ALPINE_VERSION=3.21
|
||||
ARG GOLANGCI_LINT_VERSION=v2.1.2
|
||||
ARG GOLANGCI_LINT_VERSION=v2.1.5
|
||||
|
||||
FROM golangci/golangci-lint:${GOLANGCI_LINT_VERSION}-alpine AS golangci-lint
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
ARG GO_VERSION=1.24.3
|
||||
ARG GO_VERSION=1.24.5
|
||||
ARG ALPINE_VERSION=3.21
|
||||
ARG MODOUTDATED_VERSION=v0.8.0
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user