diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b1f7e65a966c..3c2a4d8740e3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,20 +9,41 @@ on: push: branches: - 'master' - - '[0-9]+.[0-9]{2}' + - '[0-9]+.[0-9]+' tags: - 'v*' pull_request: jobs: + prepare: + runs-on: ubuntu-20.04 + outputs: + matrix: ${{ steps.platforms.outputs.matrix }} + steps: + - + name: Checkout + uses: actions/checkout@v3 + - + name: Create matrix + id: platforms + run: | + echo "matrix=$(docker buildx bake cross --print | jq -cr '.target."cross".platforms')" >>${GITHUB_OUTPUT} + - + name: Show matrix + run: | + echo ${{ steps.platforms.outputs.matrix }} + build: runs-on: ubuntu-20.04 + needs: + - prepare strategy: fail-fast: false matrix: target: - - cross - - dynbinary-cross + - binary + - dynbinary + platform: ${{ fromJson(needs.prepare.outputs.matrix) }} use_glibc: - "" - glibc @@ -36,37 +57,61 @@ jobs: name: Set up Docker Buildx uses: docker/setup-buildx-action@v2 - - name: Run ${{ matrix.target }} - uses: docker/bake-action@v2 + name: Build + uses: docker/bake-action@v3 with: targets: ${{ matrix.target }} + set: | + *.platform=${{ matrix.platform }} env: USE_GLIBC: ${{ matrix.use_glibc }} - - name: Flatten artifacts + name: Create tarball working-directory: ./build run: | - for dir in */; do - base=$(basename "$dir") - echo "Creating ${base}.tar.gz ..." - tar -cvzf "${base}.tar.gz" "$dir" - rm -rf "$dir" - done + mkdir /tmp/out + platform=${{ matrix.platform }} + platformPair=${platform//\//-} + tar -cvzf "/tmp/out/docker-${platformPair}.tar.gz" . if [ -z "${{ matrix.use_glibc }}" ]; then - echo "ARTIFACT_NAME=${{ matrix.target }}" >> $GITHUB_ENV + echo "ARTIFACT_NAME=${{ matrix.target }}-${platformPair}" >> $GITHUB_ENV else - echo "ARTIFACT_NAME=${{ matrix.target }}-glibc" >> $GITHUB_ENV + echo "ARTIFACT_NAME=${{ matrix.target }}-${platformPair}-glibc" >> $GITHUB_ENV fi - name: Upload artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ env.ARTIFACT_NAME }} - path: ./build/* + path: /tmp/out/* if-no-files-found: error + prepare-plugins: + runs-on: ubuntu-20.04 + outputs: + matrix: ${{ steps.platforms.outputs.matrix }} + steps: + - + name: Checkout + uses: actions/checkout@v3 + - + name: Create matrix + id: platforms + run: | + echo "matrix=$(docker buildx bake plugins-cross --print | jq -cr '.target."plugins-cross".platforms')" >>${GITHUB_OUTPUT} + - + name: Show matrix + run: | + echo ${{ steps.platforms.outputs.matrix }} + plugins: runs-on: ubuntu-20.04 + needs: + - prepare-plugins + strategy: + fail-fast: false + matrix: + platform: ${{ fromJson(needs.prepare-plugins.outputs.matrix) }} steps: - name: Checkout @@ -75,7 +120,9 @@ jobs: name: Set up Docker Buildx uses: docker/setup-buildx-action@v2 - - name: Build plugins - uses: docker/bake-action@v2 + name: Build + uses: docker/bake-action@v3 with: targets: plugins-cross + set: | + *.platform=${{ matrix.platform }} diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 4127c0c65abc..45235d29c649 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -16,6 +16,9 @@ on: jobs: codeql: runs-on: ubuntu-20.04 + env: + DISABLE_WARN_OUTSIDE_CONTAINER: '1' + steps: - name: Checkout @@ -32,6 +35,16 @@ jobs: uses: github/codeql-action/init@v2 with: languages: go + # CodeQL 2.16.4's auto-build added support for multi-module repositories, + # and is trying to be smart by searching for modules in every directory, + # including vendor directories. If no module is found, it's creating one + # which is ... not what we want, so let's give it a "go.mod". + # see: https://github.com/docker/cli/pull/4944#issuecomment-2002034698 + - + name: Create go.mod + run: | + ln -s vendor.mod go.mod + ln -s vendor.sum go.sum - name: Autobuild uses: github/codeql-action/autobuild@v2 diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 2c36dac447b0..9e9f04729d81 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -9,7 +9,7 @@ on: push: branches: - 'master' - - '[0-9]+.[0-9]{2}' + - '[0-9]+.[0-9]+' tags: - 'v*' pull_request: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 31da57a15b00..942d299b10e4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,7 +9,7 @@ on: push: branches: - 'master' - - '[0-9]+.[0-9]{2}' + - '[0-9]+.[0-9]+' tags: - 'v*' pull_request: @@ -26,7 +26,7 @@ jobs: uses: docker/setup-buildx-action@v2 - name: Test - uses: docker/bake-action@v2 + uses: docker/bake-action@v3 with: targets: test-coverage - @@ -45,7 +45,8 @@ jobs: fail-fast: false matrix: os: - - macos-11 + - macos-13 # macOS 13 on Intel + - macos-14 # macOS 14 on arm64 (Apple Silicon M1) # - windows-2022 # FIXME: some tests are failing on the Windows runner, as well as on Appveyor since June 24, 2018: https://ci.appveyor.com/project/docker/cli/history steps: - @@ -63,7 +64,7 @@ jobs: name: Set up Go uses: actions/setup-go@v3 with: - go-version: 1.19.4 + go-version: 1.22.6 - name: Test run: | @@ -77,3 +78,4 @@ jobs: with: file: /tmp/coverage.txt working-directory: ${{ env.GOPATH }}/src/github.com/docker/cli + version: v0.7.3 # Work around https://github.com/codecov/uploader/issues/1687 -- uploader v0.8.0 for mac is built for arm64, not x86_64 diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 1f8fbd739d5a..7408e2cd1dd4 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -9,7 +9,7 @@ on: push: branches: - 'master' - - '[0-9]+.[0-9]{2}' + - '[0-9]+.[0-9]+' tags: - 'v*' pull_request: @@ -29,14 +29,33 @@ jobs: - name: Checkout uses: actions/checkout@v3 - with: - fetch-depth: 0 - name: Run - uses: docker/bake-action@v2 + uses: docker/bake-action@v3 with: targets: ${{ matrix.target }} + # check that the generated Markdown and the checked-in files match + validate-md: + runs-on: ubuntu-20.04 + steps: + - + name: Checkout + uses: actions/checkout@v3 + - + name: Generate + shell: 'script --return --quiet --command "bash {0}"' + run: | + make -f docker.Makefile mddocs + - + name: Validate + run: | + if [[ $(git diff --stat) != '' ]]; then + echo 'fail: generated files do not match checked-in files' + git --no-pager diff + exit 1 + fi + validate-make: runs-on: ubuntu-20.04 strategy: @@ -49,8 +68,6 @@ jobs: - name: Checkout uses: actions/checkout@v3 - with: - fetch-depth: 0 - name: Run shell: 'script --return --quiet --command "bash {0}"' diff --git a/.golangci.yml b/.golangci.yml index 46f1f3c959ee..32ef636f69c1 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -26,27 +26,26 @@ linters: run: timeout: 5m - skip-files: - - cli/compose/schema/bindata.go - - .*generated.* linters-settings: depguard: - list-type: blacklist - include-go-root: true - packages: - # The io/ioutil package has been deprecated. - # https://go.dev/doc/go1.16#ioutil - - io/ioutil + rules: + main: + deny: + - pkg: io/ioutil + desc: The io/ioutil package has been deprecated, see https://go.dev/doc/go1.16#ioutil gocyclo: min-complexity: 16 - govet: - check-shadowing: false lll: line-length: 200 nakedret: command: nakedret pattern: ^(?P.*?\\.go):(?P\\d+)\\s*(?P.*)$ + revive: + rules: + - name: unused-parameter + severity: warning + disabled: true issues: # The default exclusion rules are a bit too permissive, so copying the relevant ones below @@ -55,6 +54,10 @@ issues: exclude: - parameter .* always receives + exclude-files: + - cli/compose/schema/bindata.go + - .*generated.* + exclude-rules: # We prefer to use an "exclude-list" so that new "default" exclusions are not # automatically inherited. We can decide whether or not to follow upstream @@ -117,7 +120,10 @@ issues: - text: "package-comments: should have a package comment" linters: - revive - + # FIXME temporarily suppress these (see https://github.com/gotestyourself/gotest.tools/issues/272) + - text: "SA1019: (assert|cmp|is)\\.ErrorType is deprecated" + linters: + - staticcheck # Exclude some linters from running on tests files. - path: _test\.go linters: diff --git a/Dockerfile b/Dockerfile index 8b0a1698ec1e..2b3392117630 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,17 +1,18 @@ # syntax=docker/dockerfile:1 ARG BASE_VARIANT=alpine -ARG GO_VERSION=1.19.5 -ARG ALPINE_VERSION=3.16 -ARG XX_VERSION=1.1.1 +ARG GO_VERSION=1.22.6 +ARG ALPINE_VERSION=3.20 +ARG XX_VERSION=1.6.1 ARG GOVERSIONINFO_VERSION=v1.3.0 -ARG GOTESTSUM_VERSION=v1.8.2 -ARG BUILDX_VERSION=0.9.1 +ARG GOTESTSUM_VERSION=v1.10.0 +ARG BUILDX_VERSION=0.11.2 +ARG COMPOSE_VERSION=v2.22.0 FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-alpine${ALPINE_VERSION} AS build-base-alpine -COPY --from=xx / / +COPY --link --from=xx / / RUN apk add --no-cache bash clang lld llvm file git WORKDIR /go/src/github.com/docker/cli @@ -21,7 +22,7 @@ ARG TARGETPLATFORM RUN xx-apk add --no-cache musl-dev gcc FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-bullseye AS build-base-bullseye -COPY --from=xx / / +COPY --link --from=xx / / RUN apt-get update && apt-get install --no-install-recommends -y bash clang lld llvm file WORKDIR /go/src/github.com/docker/cli @@ -40,13 +41,13 @@ FROM build-base-${BASE_VARIANT} AS goversioninfo ARG GOVERSIONINFO_VERSION RUN --mount=type=cache,target=/root/.cache/go-build \ --mount=type=cache,target=/go/pkg/mod \ - GOBIN=/out GO111MODULE=on go install "github.com/josephspurrier/goversioninfo/cmd/goversioninfo@${GOVERSIONINFO_VERSION}" + GOBIN=/out GO111MODULE=on CGO_ENABLED=0 go install "github.com/josephspurrier/goversioninfo/cmd/goversioninfo@${GOVERSIONINFO_VERSION}" FROM build-base-${BASE_VARIANT} AS gotestsum ARG GOTESTSUM_VERSION RUN --mount=type=cache,target=/root/.cache/go-build \ --mount=type=cache,target=/go/pkg/mod \ - GOBIN=/out GO111MODULE=on go install "gotest.tools/gotestsum@${GOTESTSUM_VERSION}" \ + GOBIN=/out GO111MODULE=on CGO_ENABLED=0 go install "gotest.tools/gotestsum@${GOTESTSUM_VERSION}" \ && /out/gotestsum --version FROM build-${BASE_VARIANT} AS build @@ -62,7 +63,7 @@ ARG CGO_ENABLED ARG VERSION # PACKAGER_NAME sets the company that produced the windows binary ARG PACKAGER_NAME -COPY --from=goversioninfo /out/goversioninfo /usr/bin/goversioninfo +COPY --link --from=goversioninfo /out/goversioninfo /usr/bin/goversioninfo # in bullseye arm64 target does not link with lld so configure it to use ld instead RUN [ ! -f /etc/alpine-release ] && xx-info is-cross && [ "$(xx-info arch)" = "arm64" ] && XX_CC_PREFER_LINKER=ld xx-clang --setup-target-triple || true RUN --mount=type=bind,target=.,ro \ @@ -76,7 +77,7 @@ RUN --mount=type=bind,target=.,ro \ xx-verify $([ "$GO_LINKMODE" = "static" ] && echo "--static") /out/docker FROM build-${BASE_VARIANT} AS test -COPY --from=gotestsum /out/gotestsum /usr/bin/gotestsum +COPY --link --from=gotestsum /out/gotestsum /usr/bin/gotestsum ENV GO111MODULE=auto RUN --mount=type=bind,target=.,rw \ --mount=type=cache,target=/root/.cache \ @@ -98,32 +99,31 @@ RUN --mount=ro --mount=type=cache,target=/root/.cache \ TARGET=/out ./scripts/build/plugins e2e/cli-plugins/plugins/* FROM build-base-alpine AS e2e-base-alpine -RUN apk add --no-cache build-base curl docker-compose openssl openssh-client +RUN apk add --no-cache build-base curl openssl openssh-client FROM build-base-bullseye AS e2e-base-bullseye RUN apt-get update && apt-get install -y build-essential curl openssl openssh-client -ARG COMPOSE_VERSION=1.29.2 -RUN curl -fsSL https://github.com/docker/compose/releases/download/${COMPOSE_VERSION}/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose && \ - chmod +x /usr/local/bin/docker-compose -FROM docker/buildx-bin:${BUILDX_VERSION} AS buildx +FROM docker/buildx-bin:${BUILDX_VERSION} AS buildx +FROM docker/compose-bin:${COMPOSE_VERSION} AS compose FROM e2e-base-${BASE_VARIANT} AS e2e ARG NOTARY_VERSION=v0.6.1 ADD --chmod=0755 https://github.com/theupdateframework/notary/releases/download/${NOTARY_VERSION}/notary-Linux-amd64 /usr/local/bin/notary -COPY e2e/testdata/notary/root-ca.cert /usr/share/ca-certificates/notary.cert +COPY --link e2e/testdata/notary/root-ca.cert /usr/share/ca-certificates/notary.cert RUN echo 'notary.cert' >> /etc/ca-certificates.conf && update-ca-certificates -COPY --from=gotestsum /out/gotestsum /usr/bin/gotestsum -COPY --from=build /out ./build/ -COPY --from=build-plugins /out ./build/ -COPY --from=buildx /buildx /usr/libexec/docker/cli-plugins/docker-buildx -COPY . . +COPY --link --from=gotestsum /out/gotestsum /usr/bin/gotestsum +COPY --link --from=build /out ./build/ +COPY --link --from=build-plugins /out ./build/ +COPY --link --from=buildx /buildx /usr/libexec/docker/cli-plugins/docker-buildx +COPY --link --from=compose /docker-compose /usr/libexec/docker/cli-plugins/docker-compose +COPY --link . . ENV DOCKER_BUILDKIT=1 ENV PATH=/go/src/github.com/docker/cli/build:$PATH CMD ./scripts/test/e2e/entry FROM build-base-${BASE_VARIANT} AS dev -COPY . . +COPY --link . . FROM scratch AS binary COPY --from=build /out . diff --git a/cli-plugins/manager/candidate_test.go b/cli-plugins/manager/candidate_test.go index cbb85dc31585..0ec7beda647c 100644 --- a/cli-plugins/manager/candidate_test.go +++ b/cli-plugins/manager/candidate_test.go @@ -74,7 +74,7 @@ func TestValidateCandidate(t *testing.T) { {name: "experimental + allowing experimental", c: &fakeCandidate{path: goodPluginPath, exec: true, meta: metaExperimental}}, } { t.Run(tc.name, func(t *testing.T) { - p, err := newPlugin(tc.c, fakeroot) + p, err := newPlugin(tc.c, fakeroot.Commands()) if tc.err != "" { assert.ErrorContains(t, err, tc.err) } else if tc.invalid != "" { diff --git a/cli-plugins/manager/cobra.go b/cli-plugins/manager/cobra.go index 71b6fe716d07..be3a05820189 100644 --- a/cli-plugins/manager/cobra.go +++ b/cli-plugins/manager/cobra.go @@ -3,6 +3,7 @@ package manager import ( "fmt" "os" + "sync" "github.com/docker/cli/cli/command" "github.com/spf13/cobra" @@ -31,64 +32,69 @@ const ( CommandAnnotationPluginInvalid = "com.docker.cli.plugin-invalid" ) +var pluginCommandStubsOnce sync.Once + // AddPluginCommandStubs adds a stub cobra.Commands for each valid and invalid // plugin. The command stubs will have several annotations added, see // `CommandAnnotationPlugin*`. -func AddPluginCommandStubs(dockerCli command.Cli, rootCmd *cobra.Command) error { - plugins, err := ListPlugins(dockerCli, rootCmd) - if err != nil { - return err - } - for _, p := range plugins { - p := p - vendor := p.Vendor - if vendor == "" { - vendor = "unknown" - } - annotations := map[string]string{ - CommandAnnotationPlugin: "true", - CommandAnnotationPluginVendor: vendor, - CommandAnnotationPluginVersion: p.Version, +func AddPluginCommandStubs(dockerCli command.Cli, rootCmd *cobra.Command) (err error) { + pluginCommandStubsOnce.Do(func() { + var plugins []Plugin + plugins, err = ListPlugins(dockerCli, rootCmd) + if err != nil { + return } - if p.Err != nil { - annotations[CommandAnnotationPluginInvalid] = p.Err.Error() - } - rootCmd.AddCommand(&cobra.Command{ - Use: p.Name, - Short: p.ShortDescription, - Run: func(_ *cobra.Command, _ []string) {}, - Annotations: annotations, - DisableFlagParsing: true, - RunE: func(cmd *cobra.Command, args []string) error { - flags := rootCmd.PersistentFlags() - flags.SetOutput(nil) - err := flags.Parse(args) - if err != nil { - return err - } - if flags.Changed("help") { - cmd.HelpFunc()(rootCmd, args) - return nil - } - return fmt.Errorf("docker: '%s' is not a docker command.\nSee 'docker --help'", cmd.Name()) - }, - ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - // Delegate completion to plugin - cargs := []string{p.Path, cobra.ShellCompRequestCmd, p.Name} - cargs = append(cargs, args...) - cargs = append(cargs, toComplete) - os.Args = cargs - runCommand, err := PluginRunCommand(dockerCli, p.Name, cmd) - if err != nil { + for _, p := range plugins { + p := p + vendor := p.Vendor + if vendor == "" { + vendor = "unknown" + } + annotations := map[string]string{ + CommandAnnotationPlugin: "true", + CommandAnnotationPluginVendor: vendor, + CommandAnnotationPluginVersion: p.Version, + } + if p.Err != nil { + annotations[CommandAnnotationPluginInvalid] = p.Err.Error() + } + rootCmd.AddCommand(&cobra.Command{ + Use: p.Name, + Short: p.ShortDescription, + Run: func(_ *cobra.Command, _ []string) {}, + Annotations: annotations, + DisableFlagParsing: true, + RunE: func(cmd *cobra.Command, args []string) error { + flags := rootCmd.PersistentFlags() + flags.SetOutput(nil) + perr := flags.Parse(args) + if perr != nil { + return err + } + if flags.Changed("help") { + cmd.HelpFunc()(rootCmd, args) + return nil + } + return fmt.Errorf("docker: '%s' is not a docker command.\nSee 'docker --help'", cmd.Name()) + }, + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + // Delegate completion to plugin + cargs := []string{p.Path, cobra.ShellCompRequestCmd, p.Name} + cargs = append(cargs, args...) + cargs = append(cargs, toComplete) + os.Args = cargs + runCommand, runErr := PluginRunCommand(dockerCli, p.Name, cmd) + if runErr != nil { + return nil, cobra.ShellCompDirectiveError + } + runErr = runCommand.Run() + if runErr == nil { + os.Exit(0) // plugin already rendered complete data + } return nil, cobra.ShellCompDirectiveError - } - err = runCommand.Run() - if err == nil { - os.Exit(0) // plugin already rendered complete data - } - return nil, cobra.ShellCompDirectiveError - }, - }) - } - return nil + }, + }) + } + }) + return err } diff --git a/cli-plugins/manager/error_test.go b/cli-plugins/manager/error_test.go index 55222d7042ea..c4cb19bd55d5 100644 --- a/cli-plugins/manager/error_test.go +++ b/cli-plugins/manager/error_test.go @@ -2,7 +2,7 @@ package manager import ( "encoding/json" - "fmt" + "errors" "testing" "gotest.tools/v3/assert" @@ -13,7 +13,7 @@ func TestPluginError(t *testing.T) { err := NewPluginError("new error") assert.Check(t, is.Error(err, "new error")) - inner := fmt.Errorf("testing") + inner := errors.New("testing") err = wrapAsPluginError(inner, "wrapping") assert.Check(t, is.Error(err, "wrapping: testing")) assert.Check(t, is.ErrorIs(err, inner)) diff --git a/cli-plugins/manager/manager.go b/cli-plugins/manager/manager.go index ff1585987897..3ce96876d176 100644 --- a/cli-plugins/manager/manager.go +++ b/cli-plugins/manager/manager.go @@ -1,15 +1,18 @@ package manager import ( + "context" "os" "path/filepath" "sort" "strings" + "sync" "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/config" "github.com/fvbommel/sortorder" "github.com/spf13/cobra" + "golang.org/x/sync/errgroup" exec "golang.org/x/sys/execabs" ) @@ -120,7 +123,7 @@ func GetPlugin(name string, dockerCli command.Cli, rootcmd *cobra.Command) (*Plu return nil, errPluginNotFound(name) } c := &candidate{paths[0]} - p, err := newPlugin(c, rootcmd) + p, err := newPlugin(c, rootcmd.Commands()) if err != nil { return nil, err } @@ -146,19 +149,32 @@ func ListPlugins(dockerCli command.Cli, rootcmd *cobra.Command) ([]Plugin, error } var plugins []Plugin + var mu sync.Mutex + eg, _ := errgroup.WithContext(context.TODO()) + cmds := rootcmd.Commands() for _, paths := range candidates { - if len(paths) == 0 { - continue - } - c := &candidate{paths[0]} - p, err := newPlugin(c, rootcmd) - if err != nil { - return nil, err - } - if !IsNotFound(p.Err) { - p.ShadowedPaths = paths[1:] - plugins = append(plugins, p) - } + func(paths []string) { + eg.Go(func() error { + if len(paths) == 0 { + return nil + } + c := &candidate{paths[0]} + p, err := newPlugin(c, cmds) + if err != nil { + return err + } + if !IsNotFound(p.Err) { + p.ShadowedPaths = paths[1:] + mu.Lock() + defer mu.Unlock() + plugins = append(plugins, p) + } + return nil + }) + }(paths) + } + if err := eg.Wait(); err != nil { + return nil, err } sort.Slice(plugins, func(i, j int) bool { @@ -199,7 +215,7 @@ func PluginRunCommand(dockerCli command.Cli, name string, rootcmd *cobra.Command } c := &candidate{path: path} - plugin, err := newPlugin(c, rootcmd) + plugin, err := newPlugin(c, rootcmd.Commands()) if err != nil { return nil, err } diff --git a/cli-plugins/manager/metadata.go b/cli-plugins/manager/metadata.go index 5c80c1db8612..5aaf5ca11a03 100644 --- a/cli-plugins/manager/metadata.go +++ b/cli-plugins/manager/metadata.go @@ -23,6 +23,7 @@ type Metadata struct { // URL is a pointer to the plugin's homepage. URL string `json:",omitempty"` // Experimental specifies whether the plugin is experimental. + // // Deprecated: experimental features are now always enabled in the CLI Experimental bool `json:",omitempty"` } diff --git a/cli-plugins/manager/plugin.go b/cli-plugins/manager/plugin.go index 341e92d7f06c..58ed6db72c1e 100644 --- a/cli-plugins/manager/plugin.go +++ b/cli-plugins/manager/plugin.go @@ -31,7 +31,7 @@ type Plugin struct { // is set, and is always a `pluginError`, but the `Plugin` is still // returned with no error. An error is only returned due to a // non-recoverable error. -func newPlugin(c Candidate, rootcmd *cobra.Command) (Plugin, error) { +func newPlugin(c Candidate, cmds []*cobra.Command) (Plugin, error) { path := c.Path() if path == "" { return Plugin{}, errors.New("plugin candidate path cannot be empty") @@ -62,22 +62,20 @@ func newPlugin(c Candidate, rootcmd *cobra.Command) (Plugin, error) { return p, nil } - if rootcmd != nil { - for _, cmd := range rootcmd.Commands() { - // Ignore conflicts with commands which are - // just plugin stubs (i.e. from a previous - // call to AddPluginCommandStubs). - if IsPluginCommand(cmd) { - continue - } - if cmd.Name() == p.Name { - p.Err = NewPluginError("plugin %q duplicates builtin command", p.Name) - return p, nil - } - if cmd.HasAlias(p.Name) { - p.Err = NewPluginError("plugin %q duplicates an alias of builtin command %q", p.Name, cmd.Name()) - return p, nil - } + for _, cmd := range cmds { + // Ignore conflicts with commands which are + // just plugin stubs (i.e. from a previous + // call to AddPluginCommandStubs). + if IsPluginCommand(cmd) { + continue + } + if cmd.Name() == p.Name { + p.Err = NewPluginError("plugin %q duplicates builtin command", p.Name) + return p, nil + } + if cmd.HasAlias(p.Name) { + p.Err = NewPluginError("plugin %q duplicates an alias of builtin command %q", p.Name, cmd.Name()) + return p, nil } } diff --git a/cli/cobra.go b/cli/cobra.go index 1b07fc56482d..6501197ddd43 100644 --- a/cli/cobra.go +++ b/cli/cobra.go @@ -204,6 +204,16 @@ func DisableFlagsInUseLine(cmd *cobra.Command) { }) } +// HasCompletionArg returns true if a cobra completion arg request is found. +func HasCompletionArg(args []string) bool { + for _, arg := range args { + if arg == cobra.ShellCompRequestCmd || arg == cobra.ShellCompNoDescRequestCmd { + return true + } + } + return false +} + var helpCommand = &cobra.Command{ Use: "help [command]", Short: "Help about the command", diff --git a/cli/command/checkpoint/client_test.go b/cli/command/checkpoint/client_test.go index c8fe190e8329..52dbc9115fac 100644 --- a/cli/command/checkpoint/client_test.go +++ b/cli/command/checkpoint/client_test.go @@ -14,21 +14,21 @@ type fakeClient struct { checkpointListFunc func(container string, options types.CheckpointListOptions) ([]types.Checkpoint, error) } -func (cli *fakeClient) CheckpointCreate(ctx context.Context, container string, options types.CheckpointCreateOptions) error { +func (cli *fakeClient) CheckpointCreate(_ context.Context, container string, options types.CheckpointCreateOptions) error { if cli.checkpointCreateFunc != nil { return cli.checkpointCreateFunc(container, options) } return nil } -func (cli *fakeClient) CheckpointDelete(ctx context.Context, container string, options types.CheckpointDeleteOptions) error { +func (cli *fakeClient) CheckpointDelete(_ context.Context, container string, options types.CheckpointDeleteOptions) error { if cli.checkpointDeleteFunc != nil { return cli.checkpointDeleteFunc(container, options) } return nil } -func (cli *fakeClient) CheckpointList(ctx context.Context, container string, options types.CheckpointListOptions) ([]types.Checkpoint, error) { +func (cli *fakeClient) CheckpointList(_ context.Context, container string, options types.CheckpointListOptions) ([]types.Checkpoint, error) { if cli.checkpointListFunc != nil { return cli.checkpointListFunc(container, options) } diff --git a/cli/command/cli.go b/cli/command/cli.go index b4fc151474c7..53a166903d52 100644 --- a/cli/command/cli.go +++ b/cli/command/cli.go @@ -164,8 +164,8 @@ func (cli *DockerCli) ContentTrustEnabled() bool { // BuildKitEnabled returns buildkit is enabled or not. func (cli *DockerCli) BuildKitEnabled() (bool, error) { - // use DOCKER_BUILDKIT env var value if set - if v, ok := os.LookupEnv("DOCKER_BUILDKIT"); ok { + // use DOCKER_BUILDKIT env var value if set and not empty + if v := os.Getenv("DOCKER_BUILDKIT"); v != "" { enabled, err := strconv.ParseBool(v) if err != nil { return false, errors.Wrap(err, "DOCKER_BUILDKIT environment variable expects boolean value") @@ -278,7 +278,7 @@ func newAPIClientFromEndpoint(ep docker.Endpoint, configFile *configfile.ConfigF func resolveDockerEndpoint(s store.Reader, contextName string) (docker.Endpoint, error) { if s == nil { - return docker.Endpoint{}, fmt.Errorf("no context store initialized") + return docker.Endpoint{}, errors.New("no context store initialized") } ctxMeta, err := s.GetMetadata(contextName) if err != nil { diff --git a/cli/command/cli_test.go b/cli/command/cli_test.go index 7a0b4e727e2e..13fb5d790449 100644 --- a/cli/command/cli_test.go +++ b/cli/command/cli_test.go @@ -192,7 +192,7 @@ func TestInitializeFromClientHangs(t *testing.T) { ts.Start() defer ts.Close() - opts := &flags.ClientOptions{Hosts: []string{fmt.Sprintf("unix://%s", socket)}} + opts := &flags.ClientOptions{Hosts: []string{"unix://" + socket}} configFile := &configfile.ConfigFile{} apiClient, err := NewAPIClientFromFlags(opts, configFile) assert.NilError(t, err) diff --git a/cli/command/completion/functions.go b/cli/command/completion/functions.go index 484ab4481200..f53ee84681e7 100644 --- a/cli/command/completion/functions.go +++ b/cli/command/completion/functions.go @@ -94,6 +94,6 @@ func NetworkNames(dockerCli command.Cli) ValidArgsFn { } // NoComplete is used for commands where there's no relevant completion -func NoComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { +func NoComplete(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) { return nil, cobra.ShellCompDirectiveNoFileComp } diff --git a/cli/command/config/client_test.go b/cli/command/config/client_test.go index 2e19b7752100..c3c938a7be76 100644 --- a/cli/command/config/client_test.go +++ b/cli/command/config/client_test.go @@ -10,34 +10,34 @@ import ( type fakeClient struct { client.Client - configCreateFunc func(swarm.ConfigSpec) (types.ConfigCreateResponse, error) - configInspectFunc func(string) (swarm.Config, []byte, error) - configListFunc func(types.ConfigListOptions) ([]swarm.Config, error) + configCreateFunc func(context.Context, swarm.ConfigSpec) (types.ConfigCreateResponse, error) + configInspectFunc func(context.Context, string) (swarm.Config, []byte, error) + configListFunc func(context.Context, types.ConfigListOptions) ([]swarm.Config, error) configRemoveFunc func(string) error } func (c *fakeClient) ConfigCreate(ctx context.Context, spec swarm.ConfigSpec) (types.ConfigCreateResponse, error) { if c.configCreateFunc != nil { - return c.configCreateFunc(spec) + return c.configCreateFunc(ctx, spec) } return types.ConfigCreateResponse{}, nil } func (c *fakeClient) ConfigInspectWithRaw(ctx context.Context, id string) (swarm.Config, []byte, error) { if c.configInspectFunc != nil { - return c.configInspectFunc(id) + return c.configInspectFunc(ctx, id) } return swarm.Config{}, nil, nil } func (c *fakeClient) ConfigList(ctx context.Context, options types.ConfigListOptions) ([]swarm.Config, error) { if c.configListFunc != nil { - return c.configListFunc(options) + return c.configListFunc(ctx, options) } return []swarm.Config{}, nil } -func (c *fakeClient) ConfigRemove(ctx context.Context, name string) error { +func (c *fakeClient) ConfigRemove(_ context.Context, name string) error { if c.configRemoveFunc != nil { return c.configRemoveFunc(name) } diff --git a/cli/command/config/create_test.go b/cli/command/config/create_test.go index 0d1f34578822..a1c5e618990d 100644 --- a/cli/command/config/create_test.go +++ b/cli/command/config/create_test.go @@ -1,6 +1,7 @@ package config import ( + "context" "io" "os" "path/filepath" @@ -22,7 +23,7 @@ const configDataFile = "config-create-with-name.golden" func TestConfigCreateErrors(t *testing.T) { testCases := []struct { args []string - configCreateFunc func(swarm.ConfigSpec) (types.ConfigCreateResponse, error) + configCreateFunc func(context.Context, swarm.ConfigSpec) (types.ConfigCreateResponse, error) expectedError string }{ { @@ -35,7 +36,7 @@ func TestConfigCreateErrors(t *testing.T) { }, { args: []string{"name", filepath.Join("testdata", configDataFile)}, - configCreateFunc: func(configSpec swarm.ConfigSpec) (types.ConfigCreateResponse, error) { + configCreateFunc: func(_ context.Context, configSpec swarm.ConfigSpec) (types.ConfigCreateResponse, error) { return types.ConfigCreateResponse{}, errors.Errorf("error creating config") }, expectedError: "error creating config", @@ -57,7 +58,7 @@ func TestConfigCreateWithName(t *testing.T) { name := "foo" var actual []byte cli := test.NewFakeCli(&fakeClient{ - configCreateFunc: func(spec swarm.ConfigSpec) (types.ConfigCreateResponse, error) { + configCreateFunc: func(_ context.Context, spec swarm.ConfigSpec) (types.ConfigCreateResponse, error) { if spec.Name != name { return types.ConfigCreateResponse{}, errors.Errorf("expected name %q, got %q", name, spec.Name) } @@ -96,7 +97,7 @@ func TestConfigCreateWithLabels(t *testing.T) { } cli := test.NewFakeCli(&fakeClient{ - configCreateFunc: func(spec swarm.ConfigSpec) (types.ConfigCreateResponse, error) { + configCreateFunc: func(_ context.Context, spec swarm.ConfigSpec) (types.ConfigCreateResponse, error) { if !reflect.DeepEqual(spec, expected) { return types.ConfigCreateResponse{}, errors.Errorf("expected %+v, got %+v", expected, spec) } @@ -122,7 +123,7 @@ func TestConfigCreateWithTemplatingDriver(t *testing.T) { name := "foo" cli := test.NewFakeCli(&fakeClient{ - configCreateFunc: func(spec swarm.ConfigSpec) (types.ConfigCreateResponse, error) { + configCreateFunc: func(_ context.Context, spec swarm.ConfigSpec) (types.ConfigCreateResponse, error) { if spec.Name != name { return types.ConfigCreateResponse{}, errors.Errorf("expected name %q, got %q", name, spec.Name) } diff --git a/cli/command/config/inspect_test.go b/cli/command/config/inspect_test.go index 523eba2acdab..a35a74d776d7 100644 --- a/cli/command/config/inspect_test.go +++ b/cli/command/config/inspect_test.go @@ -1,13 +1,14 @@ package config import ( + "context" "fmt" "io" "testing" "time" "github.com/docker/cli/internal/test" - . "github.com/docker/cli/internal/test/builders" // Import builders to get the builder function as package function + "github.com/docker/cli/internal/test/builders" "github.com/docker/docker/api/types/swarm" "github.com/pkg/errors" "gotest.tools/v3/assert" @@ -18,7 +19,7 @@ func TestConfigInspectErrors(t *testing.T) { testCases := []struct { args []string flags map[string]string - configInspectFunc func(configID string) (swarm.Config, []byte, error) + configInspectFunc func(_ context.Context, configID string) (swarm.Config, []byte, error) expectedError string }{ { @@ -26,7 +27,7 @@ func TestConfigInspectErrors(t *testing.T) { }, { args: []string{"foo"}, - configInspectFunc: func(configID string) (swarm.Config, []byte, error) { + configInspectFunc: func(_ context.Context, configID string) (swarm.Config, []byte, error) { return swarm.Config{}, nil, errors.Errorf("error while inspecting the config") }, expectedError: "error while inspecting the config", @@ -40,9 +41,9 @@ func TestConfigInspectErrors(t *testing.T) { }, { args: []string{"foo", "bar"}, - configInspectFunc: func(configID string) (swarm.Config, []byte, error) { + configInspectFunc: func(_ context.Context, configID string) (swarm.Config, []byte, error) { if configID == "foo" { - return *Config(ConfigName("foo")), nil, nil + return *builders.Config(builders.ConfigName("foo")), nil, nil } return swarm.Config{}, nil, errors.Errorf("error while inspecting the config") }, @@ -57,7 +58,7 @@ func TestConfigInspectErrors(t *testing.T) { ) cmd.SetArgs(tc.args) for key, value := range tc.flags { - cmd.Flags().Set(key, value) + assert.Check(t, cmd.Flags().Set(key, value)) } cmd.SetOut(io.Discard) assert.ErrorContains(t, cmd.Execute(), tc.expectedError) @@ -68,23 +69,23 @@ func TestConfigInspectWithoutFormat(t *testing.T) { testCases := []struct { name string args []string - configInspectFunc func(configID string) (swarm.Config, []byte, error) + configInspectFunc func(_ context.Context, configID string) (swarm.Config, []byte, error) }{ { name: "single-config", args: []string{"foo"}, - configInspectFunc: func(name string) (swarm.Config, []byte, error) { + configInspectFunc: func(_ context.Context, name string) (swarm.Config, []byte, error) { if name != "foo" { return swarm.Config{}, nil, errors.Errorf("Invalid name, expected %s, got %s", "foo", name) } - return *Config(ConfigID("ID-foo"), ConfigName("foo")), nil, nil + return *builders.Config(builders.ConfigID("ID-foo"), builders.ConfigName("foo")), nil, nil }, }, { name: "multiple-configs-with-labels", args: []string{"foo", "bar"}, - configInspectFunc: func(name string) (swarm.Config, []byte, error) { - return *Config(ConfigID("ID-"+name), ConfigName(name), ConfigLabels(map[string]string{ + configInspectFunc: func(_ context.Context, name string) (swarm.Config, []byte, error) { + return *builders.Config(builders.ConfigID("ID-"+name), builders.ConfigName(name), builders.ConfigLabels(map[string]string{ "label1": "label-foo", })), nil, nil }, @@ -100,8 +101,8 @@ func TestConfigInspectWithoutFormat(t *testing.T) { } func TestConfigInspectWithFormat(t *testing.T) { - configInspectFunc := func(name string) (swarm.Config, []byte, error) { - return *Config(ConfigName("foo"), ConfigLabels(map[string]string{ + configInspectFunc := func(_ context.Context, name string) (swarm.Config, []byte, error) { + return *builders.Config(builders.ConfigName("foo"), builders.ConfigLabels(map[string]string{ "label1": "label-foo", })), nil, nil } @@ -109,7 +110,7 @@ func TestConfigInspectWithFormat(t *testing.T) { name string format string args []string - configInspectFunc func(name string) (swarm.Config, []byte, error) + configInspectFunc func(_ context.Context, name string) (swarm.Config, []byte, error) }{ { name: "simple-template", @@ -130,7 +131,7 @@ func TestConfigInspectWithFormat(t *testing.T) { }) cmd := newConfigInspectCommand(cli) cmd.SetArgs(tc.args) - cmd.Flags().Set("format", tc.format) + assert.Check(t, cmd.Flags().Set("format", tc.format)) assert.NilError(t, cmd.Execute()) golden.Assert(t, cli.OutBuffer().String(), fmt.Sprintf("config-inspect-with-format.%s.golden", tc.name)) } @@ -139,20 +140,20 @@ func TestConfigInspectWithFormat(t *testing.T) { func TestConfigInspectPretty(t *testing.T) { testCases := []struct { name string - configInspectFunc func(string) (swarm.Config, []byte, error) + configInspectFunc func(context.Context, string) (swarm.Config, []byte, error) }{ { name: "simple", - configInspectFunc: func(id string) (swarm.Config, []byte, error) { - return *Config( - ConfigLabels(map[string]string{ + configInspectFunc: func(_ context.Context, id string) (swarm.Config, []byte, error) { + return *builders.Config( + builders.ConfigLabels(map[string]string{ "lbl1": "value1", }), - ConfigID("configID"), - ConfigName("configName"), - ConfigCreatedAt(time.Time{}), - ConfigUpdatedAt(time.Time{}), - ConfigData([]byte("payload here")), + builders.ConfigID("configID"), + builders.ConfigName("configName"), + builders.ConfigCreatedAt(time.Time{}), + builders.ConfigUpdatedAt(time.Time{}), + builders.ConfigData([]byte("payload here")), ), []byte{}, nil }, }, @@ -164,7 +165,7 @@ func TestConfigInspectPretty(t *testing.T) { cmd := newConfigInspectCommand(cli) cmd.SetArgs([]string{"configID"}) - cmd.Flags().Set("pretty", "true") + assert.Check(t, cmd.Flags().Set("pretty", "true")) assert.NilError(t, cmd.Execute()) golden.Assert(t, cli.OutBuffer().String(), fmt.Sprintf("config-inspect-pretty.%s.golden", tc.name)) } diff --git a/cli/command/config/ls_test.go b/cli/command/config/ls_test.go index 116c41bf5c76..27e76c1a95bb 100644 --- a/cli/command/config/ls_test.go +++ b/cli/command/config/ls_test.go @@ -1,13 +1,14 @@ package config import ( + "context" "io" "testing" "time" "github.com/docker/cli/cli/config/configfile" "github.com/docker/cli/internal/test" - . "github.com/docker/cli/internal/test/builders" // Import builders to get the builder function as package function + "github.com/docker/cli/internal/test/builders" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/swarm" "github.com/pkg/errors" @@ -19,7 +20,7 @@ import ( func TestConfigListErrors(t *testing.T) { testCases := []struct { args []string - configListFunc func(types.ConfigListOptions) ([]swarm.Config, error) + configListFunc func(context.Context, types.ConfigListOptions) ([]swarm.Config, error) expectedError string }{ { @@ -27,7 +28,7 @@ func TestConfigListErrors(t *testing.T) { expectedError: "accepts no argument", }, { - configListFunc: func(options types.ConfigListOptions) ([]swarm.Config, error) { + configListFunc: func(_ context.Context, options types.ConfigListOptions) ([]swarm.Config, error) { return []swarm.Config{}, errors.Errorf("error listing configs") }, expectedError: "error listing configs", @@ -47,25 +48,25 @@ func TestConfigListErrors(t *testing.T) { func TestConfigList(t *testing.T) { cli := test.NewFakeCli(&fakeClient{ - configListFunc: func(options types.ConfigListOptions) ([]swarm.Config, error) { + configListFunc: func(_ context.Context, options types.ConfigListOptions) ([]swarm.Config, error) { return []swarm.Config{ - *Config(ConfigID("ID-1-foo"), - ConfigName("1-foo"), - ConfigVersion(swarm.Version{Index: 10}), - ConfigCreatedAt(time.Now().Add(-2*time.Hour)), - ConfigUpdatedAt(time.Now().Add(-1*time.Hour)), + *builders.Config(builders.ConfigID("ID-1-foo"), + builders.ConfigName("1-foo"), + builders.ConfigVersion(swarm.Version{Index: 10}), + builders.ConfigCreatedAt(time.Now().Add(-2*time.Hour)), + builders.ConfigUpdatedAt(time.Now().Add(-1*time.Hour)), ), - *Config(ConfigID("ID-10-foo"), - ConfigName("10-foo"), - ConfigVersion(swarm.Version{Index: 11}), - ConfigCreatedAt(time.Now().Add(-2*time.Hour)), - ConfigUpdatedAt(time.Now().Add(-1*time.Hour)), + *builders.Config(builders.ConfigID("ID-10-foo"), + builders.ConfigName("10-foo"), + builders.ConfigVersion(swarm.Version{Index: 11}), + builders.ConfigCreatedAt(time.Now().Add(-2*time.Hour)), + builders.ConfigUpdatedAt(time.Now().Add(-1*time.Hour)), ), - *Config(ConfigID("ID-2-foo"), - ConfigName("2-foo"), - ConfigVersion(swarm.Version{Index: 11}), - ConfigCreatedAt(time.Now().Add(-2*time.Hour)), - ConfigUpdatedAt(time.Now().Add(-1*time.Hour)), + *builders.Config(builders.ConfigID("ID-2-foo"), + builders.ConfigName("2-foo"), + builders.ConfigVersion(swarm.Version{Index: 11}), + builders.ConfigCreatedAt(time.Now().Add(-2*time.Hour)), + builders.ConfigUpdatedAt(time.Now().Add(-1*time.Hour)), ), }, nil }, @@ -77,27 +78,27 @@ func TestConfigList(t *testing.T) { func TestConfigListWithQuietOption(t *testing.T) { cli := test.NewFakeCli(&fakeClient{ - configListFunc: func(options types.ConfigListOptions) ([]swarm.Config, error) { + configListFunc: func(_ context.Context, options types.ConfigListOptions) ([]swarm.Config, error) { return []swarm.Config{ - *Config(ConfigID("ID-foo"), ConfigName("foo")), - *Config(ConfigID("ID-bar"), ConfigName("bar"), ConfigLabels(map[string]string{ + *builders.Config(builders.ConfigID("ID-foo"), builders.ConfigName("foo")), + *builders.Config(builders.ConfigID("ID-bar"), builders.ConfigName("bar"), builders.ConfigLabels(map[string]string{ "label": "label-bar", })), }, nil }, }) cmd := newConfigListCommand(cli) - cmd.Flags().Set("quiet", "true") + assert.Check(t, cmd.Flags().Set("quiet", "true")) assert.NilError(t, cmd.Execute()) golden.Assert(t, cli.OutBuffer().String(), "config-list-with-quiet-option.golden") } func TestConfigListWithConfigFormat(t *testing.T) { cli := test.NewFakeCli(&fakeClient{ - configListFunc: func(options types.ConfigListOptions) ([]swarm.Config, error) { + configListFunc: func(_ context.Context, options types.ConfigListOptions) ([]swarm.Config, error) { return []swarm.Config{ - *Config(ConfigID("ID-foo"), ConfigName("foo")), - *Config(ConfigID("ID-bar"), ConfigName("bar"), ConfigLabels(map[string]string{ + *builders.Config(builders.ConfigID("ID-foo"), builders.ConfigName("foo")), + *builders.Config(builders.ConfigID("ID-bar"), builders.ConfigName("bar"), builders.ConfigLabels(map[string]string{ "label": "label-bar", })), }, nil @@ -113,45 +114,45 @@ func TestConfigListWithConfigFormat(t *testing.T) { func TestConfigListWithFormat(t *testing.T) { cli := test.NewFakeCli(&fakeClient{ - configListFunc: func(options types.ConfigListOptions) ([]swarm.Config, error) { + configListFunc: func(_ context.Context, options types.ConfigListOptions) ([]swarm.Config, error) { return []swarm.Config{ - *Config(ConfigID("ID-foo"), ConfigName("foo")), - *Config(ConfigID("ID-bar"), ConfigName("bar"), ConfigLabels(map[string]string{ + *builders.Config(builders.ConfigID("ID-foo"), builders.ConfigName("foo")), + *builders.Config(builders.ConfigID("ID-bar"), builders.ConfigName("bar"), builders.ConfigLabels(map[string]string{ "label": "label-bar", })), }, nil }, }) cmd := newConfigListCommand(cli) - cmd.Flags().Set("format", "{{ .Name }} {{ .Labels }}") + assert.Check(t, cmd.Flags().Set("format", "{{ .Name }} {{ .Labels }}")) assert.NilError(t, cmd.Execute()) golden.Assert(t, cli.OutBuffer().String(), "config-list-with-format.golden") } func TestConfigListWithFilter(t *testing.T) { cli := test.NewFakeCli(&fakeClient{ - configListFunc: func(options types.ConfigListOptions) ([]swarm.Config, error) { + configListFunc: func(_ context.Context, options types.ConfigListOptions) ([]swarm.Config, error) { assert.Check(t, is.Equal("foo", options.Filters.Get("name")[0])) assert.Check(t, is.Equal("lbl1=Label-bar", options.Filters.Get("label")[0])) return []swarm.Config{ - *Config(ConfigID("ID-foo"), - ConfigName("foo"), - ConfigVersion(swarm.Version{Index: 10}), - ConfigCreatedAt(time.Now().Add(-2*time.Hour)), - ConfigUpdatedAt(time.Now().Add(-1*time.Hour)), + *builders.Config(builders.ConfigID("ID-foo"), + builders.ConfigName("foo"), + builders.ConfigVersion(swarm.Version{Index: 10}), + builders.ConfigCreatedAt(time.Now().Add(-2*time.Hour)), + builders.ConfigUpdatedAt(time.Now().Add(-1*time.Hour)), ), - *Config(ConfigID("ID-bar"), - ConfigName("bar"), - ConfigVersion(swarm.Version{Index: 11}), - ConfigCreatedAt(time.Now().Add(-2*time.Hour)), - ConfigUpdatedAt(time.Now().Add(-1*time.Hour)), + *builders.Config(builders.ConfigID("ID-bar"), + builders.ConfigName("bar"), + builders.ConfigVersion(swarm.Version{Index: 11}), + builders.ConfigCreatedAt(time.Now().Add(-2*time.Hour)), + builders.ConfigUpdatedAt(time.Now().Add(-1*time.Hour)), ), }, nil }, }) cmd := newConfigListCommand(cli) - cmd.Flags().Set("filter", "name=foo") - cmd.Flags().Set("filter", "label=lbl1=Label-bar") + assert.Check(t, cmd.Flags().Set("filter", "name=foo")) + assert.Check(t, cmd.Flags().Set("filter", "label=lbl1=Label-bar")) assert.NilError(t, cmd.Execute()) golden.Assert(t, cli.OutBuffer().String(), "config-list-with-filter.golden") } diff --git a/cli/command/container/attach.go b/cli/command/container/attach.go index bd17696b8032..98441909b75c 100644 --- a/cli/command/container/attach.go +++ b/cli/command/container/attach.go @@ -2,7 +2,6 @@ package container import ( "context" - "fmt" "io" "github.com/docker/cli/cli" @@ -153,7 +152,7 @@ func getExitStatus(errC <-chan error, resultC <-chan container.WaitResponse) err select { case result := <-resultC: if result.Error != nil { - return fmt.Errorf(result.Error.Message) + return errors.New(result.Error.Message) } if result.StatusCode != 0 { return cli.StatusError{StatusCode: int(result.StatusCode)} diff --git a/cli/command/container/attach_test.go b/cli/command/container/attach_test.go index b0aaea688cb3..7c16aec778f6 100644 --- a/cli/command/container/attach_test.go +++ b/cli/command/container/attach_test.go @@ -1,7 +1,6 @@ package container import ( - "fmt" "io" "testing" @@ -79,7 +78,7 @@ func TestNewAttachCommandErrors(t *testing.T) { func TestGetExitStatus(t *testing.T) { var ( - expectedErr = fmt.Errorf("unexpected error") + expectedErr = errors.New("unexpected error") errC = make(chan error, 1) resultC = make(chan container.WaitResponse, 1) ) diff --git a/cli/command/container/client_test.go b/cli/command/container/client_test.go index e8085367ef3d..a4c1478b43f5 100644 --- a/cli/command/container/client_test.go +++ b/cli/command/container/client_test.go @@ -64,7 +64,7 @@ func (f *fakeClient) ContainerExecInspect(_ context.Context, execID string) (typ return types.ContainerExecInspect{}, nil } -func (f *fakeClient) ContainerExecStart(ctx context.Context, execID string, config types.ExecStartCheck) error { +func (f *fakeClient) ContainerExecStart(context.Context, string, types.ExecStartCheck) error { return nil } @@ -89,7 +89,7 @@ func (f *fakeClient) ContainerRemove(ctx context.Context, container string, opti return nil } -func (f *fakeClient) ImageCreate(ctx context.Context, parentReference string, options types.ImageCreateOptions) (io.ReadCloser, error) { +func (f *fakeClient) ImageCreate(_ context.Context, parentReference string, options types.ImageCreateOptions) (io.ReadCloser, error) { if f.imageCreateFunc != nil { return f.imageCreateFunc(parentReference, options) } diff --git a/cli/command/container/cp.go b/cli/command/container/cp.go index 67e5e598ea21..116d63624379 100644 --- a/cli/command/container/cp.go +++ b/cli/command/container/cp.go @@ -1,15 +1,20 @@ package container import ( + "bytes" "context" "fmt" "io" "os" + "os/signal" "path/filepath" "strings" + "sync/atomic" + "time" "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" + "github.com/docker/cli/cli/streams" "github.com/docker/docker/api/types" "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/system" @@ -48,26 +53,73 @@ type cpConfig struct { // copying files to/from a container. type copyProgressPrinter struct { io.ReadCloser - toContainer bool - total *float64 - writer io.Writer + total *int64 } +const ( + copyToContainerHeader = "Copying to container - " + copyFromContainerHeader = "Copying from container - " + copyProgressUpdateThreshold = 75 * time.Millisecond +) + func (pt *copyProgressPrinter) Read(p []byte) (int, error) { n, err := pt.ReadCloser.Read(p) - *pt.total += float64(n) + atomic.AddInt64(pt.total, int64(n)) + return n, err +} - if err == nil { - fmt.Fprint(pt.writer, aec.Restore) - fmt.Fprint(pt.writer, aec.EraseLine(aec.EraseModes.All)) - if pt.toContainer { - fmt.Fprintln(pt.writer, "Copying to container - "+units.HumanSize(*pt.total)) - } else { - fmt.Fprintln(pt.writer, "Copying from container - "+units.HumanSize(*pt.total)) - } +func copyProgress(ctx context.Context, dst io.Writer, header string, total *int64) (func(), <-chan struct{}) { + done := make(chan struct{}) + if !streams.NewOut(dst).IsTerminal() { + close(done) + return func() {}, done } - return n, err + fmt.Fprint(dst, aec.Save) + fmt.Fprint(dst, "Preparing to copy...") + + restore := func() { + fmt.Fprint(dst, aec.Restore) + fmt.Fprint(dst, aec.EraseLine(aec.EraseModes.All)) + } + + go func() { + defer close(done) + fmt.Fprint(dst, aec.Hide) + defer fmt.Fprint(dst, aec.Show) + + fmt.Fprint(dst, aec.Restore) + fmt.Fprint(dst, aec.EraseLine(aec.EraseModes.All)) + fmt.Fprint(dst, header) + + var last int64 + fmt.Fprint(dst, progressHumanSize(last)) + + buf := bytes.NewBuffer(nil) + ticker := time.NewTicker(copyProgressUpdateThreshold) + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + n := atomic.LoadInt64(total) + if n == last { + // Don't write to the terminal, if we don't need to. + continue + } + + // Write to the buffer first to avoid flickering and context switching + fmt.Fprint(buf, aec.Column(uint(len(header)+1))) + fmt.Fprint(buf, aec.EraseLine(aec.EraseModes.Tail)) + fmt.Fprint(buf, progressHumanSize(n)) + + buf.WriteTo(dst) + buf.Reset() + last += n + } + } + }() + return restore, done } // NewCopyCommand creates a new `docker cp` command @@ -113,6 +165,10 @@ func NewCopyCommand(dockerCli command.Cli) *cobra.Command { return cmd } +func progressHumanSize(n int64) string { + return units.HumanSizeWithPrecision(float64(n), 3) +} + func runCopy(dockerCli command.Cli, opts copyOptions) error { srcContainer, srcPath := splitCpArg(opts.source) destContainer, destPath := splitCpArg(opts.destination) @@ -193,6 +249,9 @@ func copyFromContainer(ctx context.Context, dockerCli command.Cli, copyConfig cp } + ctx, cancel := signal.NotifyContext(ctx, os.Interrupt) + defer cancel() + content, stat, err := client.CopyFromContainer(ctx, copyConfig.container, srcPath) if err != nil { return err @@ -211,13 +270,11 @@ func copyFromContainer(ctx context.Context, dockerCli command.Cli, copyConfig cp RebaseName: rebaseName, } - var copiedSize float64 + var copiedSize int64 if !copyConfig.quiet { content = ©ProgressPrinter{ - ReadCloser: content, - toContainer: false, - writer: dockerCli.Err(), - total: &copiedSize, + ReadCloser: content, + total: &copiedSize, } } @@ -231,12 +288,12 @@ func copyFromContainer(ctx context.Context, dockerCli command.Cli, copyConfig cp return archive.CopyTo(preArchive, srcInfo, dstPath) } - fmt.Fprint(dockerCli.Err(), aec.Save) - fmt.Fprintln(dockerCli.Err(), "Preparing to copy...") + restore, done := copyProgress(ctx, dockerCli.Err(), copyFromContainerHeader, &copiedSize) res := archive.CopyTo(preArchive, srcInfo, dstPath) - fmt.Fprint(dockerCli.Err(), aec.Restore) - fmt.Fprint(dockerCli.Err(), aec.EraseLine(aec.EraseModes.All)) - fmt.Fprintln(dockerCli.Err(), "Successfully copied", units.HumanSize(copiedSize), "to", dstPath) + cancel() + <-done + restore() + fmt.Fprintln(dockerCli.Err(), "Successfully copied", progressHumanSize(copiedSize), "to", dstPath) return res } @@ -293,7 +350,7 @@ func copyToContainer(ctx context.Context, dockerCli command.Cli, copyConfig cpCo var ( content io.ReadCloser resolvedDstPath string - copiedSize float64 + copiedSize int64 ) if srcPath == "-" { @@ -337,10 +394,8 @@ func copyToContainer(ctx context.Context, dockerCli command.Cli, copyConfig cpCo content = preparedArchive if !copyConfig.quiet { content = ©ProgressPrinter{ - ReadCloser: content, - toContainer: true, - writer: dockerCli.Err(), - total: &copiedSize, + ReadCloser: content, + total: &copiedSize, } } } @@ -354,12 +409,13 @@ func copyToContainer(ctx context.Context, dockerCli command.Cli, copyConfig cpCo return client.CopyToContainer(ctx, copyConfig.container, resolvedDstPath, content, options) } - fmt.Fprint(dockerCli.Err(), aec.Save) - fmt.Fprintln(dockerCli.Err(), "Preparing to copy...") + ctx, cancel := signal.NotifyContext(ctx, os.Interrupt) + restore, done := copyProgress(ctx, dockerCli.Err(), copyToContainerHeader, &copiedSize) res := client.CopyToContainer(ctx, copyConfig.container, resolvedDstPath, content, options) - fmt.Fprint(dockerCli.Err(), aec.Restore) - fmt.Fprint(dockerCli.Err(), aec.EraseLine(aec.EraseModes.All)) - fmt.Fprintln(dockerCli.Err(), "Successfully copied", units.HumanSize(copiedSize), "to", copyConfig.container+":"+dstInfo.Path) + cancel() + <-done + restore() + fmt.Fprintln(dockerCli.Err(), "Successfully copied", progressHumanSize(copiedSize), "to", copyConfig.container+":"+dstInfo.Path) return res } diff --git a/cli/command/container/create_test.go b/cli/command/container/create_test.go index c74b5d359eec..5da0761d54f0 100644 --- a/cli/command/container/create_test.go +++ b/cli/command/container/create_test.go @@ -3,7 +3,6 @@ package container import ( "context" "errors" - "fmt" "io" "os" "runtime" @@ -225,7 +224,7 @@ func TestNewCreateCommandWithContentTrustErrors(t *testing.T) { platform *specs.Platform, containerName string, ) (container.CreateResponse, error) { - return container.CreateResponse{}, fmt.Errorf("shouldn't try to pull image") + return container.CreateResponse{}, errors.New("shouldn't try to pull image") }, }, test.EnableContentTrust) cli.SetNotaryClient(tc.notaryFunc) diff --git a/cli/command/container/list.go b/cli/command/container/list.go index 03a4c5cc77a0..3571954b5cdb 100644 --- a/cli/command/container/list.go +++ b/cli/command/container/list.go @@ -17,14 +17,15 @@ import ( ) type psOptions struct { - quiet bool - size bool - all bool - noTrunc bool - nLatest bool - last int - format string - filter opts.FilterOpt + quiet bool + size bool + sizeChanged bool + all bool + noTrunc bool + nLatest bool + last int + format string + filter opts.FilterOpt } // NewPsCommand creates a new cobra.Command for `docker ps` @@ -36,6 +37,7 @@ func NewPsCommand(dockerCli command.Cli) *cobra.Command { Short: "List containers", Args: cli.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { + options.sizeChanged = cmd.Flags().Changed("size") return runPs(dockerCli, &options) }, Annotations: map[string]string{ @@ -78,13 +80,8 @@ func buildContainerListOptions(opts *psOptions) (*types.ContainerListOptions, er options.Limit = 1 } - if !opts.quiet && !options.Size && len(opts.format) > 0 { - // The --size option isn't set, but .Size may be used in the template. - // Parse and execute the given template to detect if the .Size field is - // used. If it is, then automatically enable the --size option. See #24696 - // - // Only requesting container size information when needed is an optimization, - // because calculating the size is a costly operation. + // always validate template when `--format` is used, for consistency + if len(opts.format) > 0 { tmpl, err := templates.NewParse("", opts.format) if err != nil { return nil, errors.Wrap(err, "failed to parse template") @@ -98,8 +95,19 @@ func buildContainerListOptions(opts *psOptions) (*types.ContainerListOptions, er return nil, errors.Wrap(err, "failed to execute template") } - if _, ok := optionsProcessor.FieldsUsed["Size"]; ok { - options.Size = true + // if `size` was not explicitly set to false (with `--size=false`) + // and `--quiet` is not set, request size if the template requires it + if !opts.quiet && !options.Size && !opts.sizeChanged { + // The --size option isn't set, but .Size may be used in the template. + // Parse and execute the given template to detect if the .Size field is + // used. If it is, then automatically enable the --size option. See #24696 + // + // Only requesting container size information when needed is an optimization, + // because calculating the size is a costly operation. + + if _, ok := optionsProcessor.FieldsUsed["Size"]; ok { + options.Size = true + } } } diff --git a/cli/command/container/list_test.go b/cli/command/container/list_test.go index d436e39a99ee..110960df1b93 100644 --- a/cli/command/container/list_test.go +++ b/cli/command/container/list_test.go @@ -1,13 +1,13 @@ package container import ( - "fmt" + "errors" "io" "testing" "github.com/docker/cli/cli/config/configfile" "github.com/docker/cli/internal/test" - . "github.com/docker/cli/internal/test/builders" // Import builders to get the builder function as package function + "github.com/docker/cli/internal/test/builders" "github.com/docker/cli/opts" "github.com/docker/docker/api/types" "gotest.tools/v3/assert" @@ -146,7 +146,7 @@ func TestContainerListErrors(t *testing.T) { }, { containerListFunc: func(_ types.ContainerListOptions) ([]types.Container, error) { - return nil, fmt.Errorf("error listing containers") + return nil, errors.New("error listing containers") }, expectedError: "error listing containers", }, @@ -159,7 +159,7 @@ func TestContainerListErrors(t *testing.T) { ) cmd.SetArgs(tc.args) for key, value := range tc.flags { - cmd.Flags().Set(key, value) + assert.Check(t, cmd.Flags().Set(key, value)) } cmd.SetOut(io.Discard) assert.ErrorContains(t, cmd.Execute(), tc.expectedError) @@ -170,11 +170,11 @@ func TestContainerListWithoutFormat(t *testing.T) { cli := test.NewFakeCli(&fakeClient{ containerListFunc: func(_ types.ContainerListOptions) ([]types.Container, error) { return []types.Container{ - *Container("c1"), - *Container("c2", WithName("foo")), - *Container("c3", WithPort(80, 80, TCP), WithPort(81, 81, TCP), WithPort(82, 82, TCP)), - *Container("c4", WithPort(81, 81, UDP)), - *Container("c5", WithPort(82, 82, IP("8.8.8.8"), TCP)), + *builders.Container("c1"), + *builders.Container("c2", builders.WithName("foo")), + *builders.Container("c3", builders.WithPort(80, 80, builders.TCP), builders.WithPort(81, 81, builders.TCP), builders.WithPort(82, 82, builders.TCP)), + *builders.Container("c4", builders.WithPort(81, 81, builders.UDP)), + *builders.Container("c5", builders.WithPort(82, 82, builders.IP("8.8.8.8"), builders.TCP)), }, nil }, }) @@ -187,13 +187,13 @@ func TestContainerListNoTrunc(t *testing.T) { cli := test.NewFakeCli(&fakeClient{ containerListFunc: func(_ types.ContainerListOptions) ([]types.Container, error) { return []types.Container{ - *Container("c1"), - *Container("c2", WithName("foo/bar")), + *builders.Container("c1"), + *builders.Container("c2", builders.WithName("foo/bar")), }, nil }, }) cmd := newListCommand(cli) - cmd.Flags().Set("no-trunc", "true") + assert.Check(t, cmd.Flags().Set("no-trunc", "true")) assert.NilError(t, cmd.Execute()) golden.Assert(t, cli.OutBuffer().String(), "container-list-without-format-no-trunc.golden") } @@ -203,13 +203,13 @@ func TestContainerListNamesMultipleTime(t *testing.T) { cli := test.NewFakeCli(&fakeClient{ containerListFunc: func(_ types.ContainerListOptions) ([]types.Container, error) { return []types.Container{ - *Container("c1"), - *Container("c2", WithName("foo/bar")), + *builders.Container("c1"), + *builders.Container("c2", builders.WithName("foo/bar")), }, nil }, }) cmd := newListCommand(cli) - cmd.Flags().Set("format", "{{.Names}} {{.Names}}") + assert.Check(t, cmd.Flags().Set("format", "{{.Names}} {{.Names}}")) assert.NilError(t, cmd.Execute()) golden.Assert(t, cli.OutBuffer().String(), "container-list-format-name-name.golden") } @@ -219,35 +219,76 @@ func TestContainerListFormatTemplateWithArg(t *testing.T) { cli := test.NewFakeCli(&fakeClient{ containerListFunc: func(_ types.ContainerListOptions) ([]types.Container, error) { return []types.Container{ - *Container("c1", WithLabel("some.label", "value")), - *Container("c2", WithName("foo/bar"), WithLabel("foo", "bar")), + *builders.Container("c1", builders.WithLabel("some.label", "value")), + *builders.Container("c2", builders.WithName("foo/bar"), builders.WithLabel("foo", "bar")), }, nil }, }) cmd := newListCommand(cli) - cmd.Flags().Set("format", `{{.Names}} {{.Label "some.label"}}`) + assert.Check(t, cmd.Flags().Set("format", `{{.Names}} {{.Label "some.label"}}`)) assert.NilError(t, cmd.Execute()) golden.Assert(t, cli.OutBuffer().String(), "container-list-format-with-arg.golden") } func TestContainerListFormatSizeSetsOption(t *testing.T) { - cli := test.NewFakeCli(&fakeClient{ - containerListFunc: func(options types.ContainerListOptions) ([]types.Container, error) { - assert.Check(t, options.Size) - return []types.Container{}, nil + tests := []struct { + doc, format, sizeFlag string + sizeExpected bool + }{ + { + doc: "detect with all fields", + format: `{{json .}}`, + sizeExpected: true, }, - }) - cmd := newListCommand(cli) - cmd.Flags().Set("format", `{{.Size}}`) - assert.NilError(t, cmd.Execute()) + { + doc: "detect with explicit field", + format: `{{.Size}}`, + sizeExpected: true, + }, + { + doc: "detect no size", + format: `{{.Names}}`, + sizeExpected: false, + }, + { + doc: "override enable", + format: `{{.Names}}`, + sizeFlag: "true", + sizeExpected: true, + }, + { + doc: "override disable", + format: `{{.Size}}`, + sizeFlag: "false", + sizeExpected: false, + }, + } + + for _, tc := range tests { + tc := tc + t.Run(tc.doc, func(t *testing.T) { + cli := test.NewFakeCli(&fakeClient{ + containerListFunc: func(options types.ContainerListOptions) ([]types.Container, error) { + assert.Check(t, is.Equal(options.Size, tc.sizeExpected)) + return []types.Container{}, nil + }, + }) + cmd := newListCommand(cli) + assert.Check(t, cmd.Flags().Set("format", tc.format)) + if tc.sizeFlag != "" { + assert.Check(t, cmd.Flags().Set("size", tc.sizeFlag)) + } + assert.NilError(t, cmd.Execute()) + }) + } } func TestContainerListWithConfigFormat(t *testing.T) { cli := test.NewFakeCli(&fakeClient{ containerListFunc: func(_ types.ContainerListOptions) ([]types.Container, error) { return []types.Container{ - *Container("c1", WithLabel("some.label", "value"), WithSize(10700000)), - *Container("c2", WithName("foo/bar"), WithLabel("foo", "bar"), WithSize(3200000)), + *builders.Container("c1", builders.WithLabel("some.label", "value"), builders.WithSize(10700000)), + *builders.Container("c2", builders.WithName("foo/bar"), builders.WithLabel("foo", "bar"), builders.WithSize(3200000)), }, nil }, }) @@ -263,8 +304,8 @@ func TestContainerListWithFormat(t *testing.T) { cli := test.NewFakeCli(&fakeClient{ containerListFunc: func(_ types.ContainerListOptions) ([]types.Container, error) { return []types.Container{ - *Container("c1", WithLabel("some.label", "value")), - *Container("c2", WithName("foo/bar"), WithLabel("foo", "bar")), + *builders.Container("c1", builders.WithLabel("some.label", "value")), + *builders.Container("c2", builders.WithName("foo/bar"), builders.WithLabel("foo", "bar")), }, nil }, }) diff --git a/cli/command/container/opts.go b/cli/command/container/opts.go index e4db20ee3e47..d59d4879bc5e 100644 --- a/cli/command/container/opts.go +++ b/cli/command/container/opts.go @@ -541,7 +541,7 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con return nil, errors.Errorf("--health-retries cannot be negative") } if copts.healthStartPeriod < 0 { - return nil, fmt.Errorf("--health-start-period cannot be negative") + return nil, errors.New("--health-start-period cannot be negative") } healthConfig = &container.HealthConfig{ diff --git a/cli/command/container/opts_test.go b/cli/command/container/opts_test.go index 6421f80a703c..17fa075d29cb 100644 --- a/cli/command/container/opts_test.go +++ b/cli/command/container/opts_test.go @@ -329,7 +329,7 @@ func TestParseHostname(t *testing.T) { hostnameWithDomain := "--hostname=hostname.domainname" hostnameWithDomainTld := "--hostname=hostname.domainname.tld" for hostname, expectedHostname := range validHostnames { - if config, _ := mustParse(t, fmt.Sprintf("--hostname=%s", hostname)); config.Hostname != expectedHostname { + if config, _ := mustParse(t, "--hostname="+hostname); config.Hostname != expectedHostname { t.Fatalf("Expected the config to have 'hostname' as %q, got %q", expectedHostname, config.Hostname) } } diff --git a/cli/command/container/prune.go b/cli/command/container/prune.go index 225ab5f2d292..868560ef711e 100644 --- a/cli/command/container/prune.go +++ b/cli/command/container/prune.go @@ -75,6 +75,6 @@ func runPrune(dockerCli command.Cli, options pruneOptions) (spaceReclaimed uint6 // RunPrune calls the Container Prune API // This returns the amount of space reclaimed and a detailed output string -func RunPrune(dockerCli command.Cli, all bool, filter opts.FilterOpt) (uint64, string, error) { +func RunPrune(dockerCli command.Cli, _ bool, filter opts.FilterOpt) (uint64, string, error) { return runPrune(dockerCli, pruneOptions{force: true, filter: filter}) } diff --git a/cli/command/container/rm_test.go b/cli/command/container/rm_test.go index 7ab83dd2c907..a124edd0bca6 100644 --- a/cli/command/container/rm_test.go +++ b/cli/command/container/rm_test.go @@ -2,7 +2,7 @@ package container import ( "context" - "fmt" + "errors" "io" "sort" "sync" @@ -37,7 +37,7 @@ func TestRemoveForce(t *testing.T) { mutex.Unlock() if container == "nosuchcontainer" { - return errdefs.NotFound(fmt.Errorf("Error: no such container: " + container)) + return errdefs.NotFound(errors.New("Error: no such container: " + container)) } return nil }, diff --git a/cli/command/container/run.go b/cli/command/container/run.go index dcf9dca9f284..fd4dccae0bbb 100644 --- a/cli/command/container/run.go +++ b/cli/command/container/run.go @@ -173,11 +173,11 @@ func runContainer(dockerCli command.Cli, opts *runOptions, copts *containerOptio dockerCli.ConfigFile().DetachKeys = opts.detachKeys } - close, err := attachContainer(ctx, dockerCli, &errCh, config, createResponse.ID) + closeFn, err := attachContainer(ctx, dockerCli, &errCh, config, createResponse.ID) if err != nil { return err } - defer close() + defer closeFn() } statusChan := waitExitOrRemoved(ctx, dockerCli, createResponse.ID, copts.autoRemove) @@ -308,7 +308,8 @@ func runStartContainerErr(err error) error { strings.Contains(trimmedErr, "no such file or directory") || strings.Contains(trimmedErr, "system cannot find the file specified") { statusError = cli.StatusError{StatusCode: 127} - } else if strings.Contains(trimmedErr, syscall.EACCES.Error()) { + } else if strings.Contains(trimmedErr, syscall.EACCES.Error()) || + strings.Contains(trimmedErr, syscall.EISDIR.Error()) { statusError = cli.StatusError{StatusCode: 126} } diff --git a/cli/command/container/run_test.go b/cli/command/container/run_test.go index 4f3acfa730d7..8df7c640b4c3 100644 --- a/cli/command/container/run_test.go +++ b/cli/command/container/run_test.go @@ -2,7 +2,6 @@ package container import ( "errors" - "fmt" "io" "testing" @@ -65,7 +64,7 @@ func TestRunCommandWithContentTrustErrors(t *testing.T) { platform *specs.Platform, containerName string, ) (container.CreateResponse, error) { - return container.CreateResponse{}, fmt.Errorf("shouldn't try to pull image") + return container.CreateResponse{}, errors.New("shouldn't try to pull image") }, }, test.EnableContentTrust) cli.SetNotaryClient(tc.notaryFunc) diff --git a/cli/command/context/create.go b/cli/command/context/create.go index d912dff375c0..757d40adef04 100644 --- a/cli/command/context/create.go +++ b/cli/command/context/create.go @@ -118,10 +118,7 @@ func createNewContext(o *CreateOptions, cli command.Cli, s store.Writer) error { if err := s.CreateOrUpdate(contextMetadata); err != nil { return err } - if err := s.ResetTLSMaterial(o.Name, &contextTLSData); err != nil { - return err - } - return nil + return s.ResetTLSMaterial(o.Name, &contextTLSData) } func checkContextNameForCreation(s store.Reader, name string) error { diff --git a/cli/command/context/options.go b/cli/command/context/options.go index 39707fe3e94f..80ddf3d6e9f6 100644 --- a/cli/command/context/options.go +++ b/cli/command/context/options.go @@ -1,7 +1,6 @@ package context import ( - "fmt" "strconv" "strings" @@ -77,7 +76,7 @@ func validateConfig(config map[string]string, allowedKeys map[string]struct{}) e var errs []string for k := range config { if _, ok := allowedKeys[k]; !ok { - errs = append(errs, fmt.Sprintf("%s: unrecognized config key", k)) + errs = append(errs, "unrecognized config key: "+k) } } if len(errs) == 0 { diff --git a/cli/command/formatter/formatter.go b/cli/command/formatter/formatter.go index 715e9ea62b80..55c5ea6e0277 100644 --- a/cli/command/formatter/formatter.go +++ b/cli/command/formatter/formatter.go @@ -19,7 +19,7 @@ const ( JSONFormatKey = "json" DefaultQuietFormat = "{{.ID}}" - jsonFormat = "{{json .}}" + JSONFormat = "{{json .}}" ) // Format is the format string rendered using the Context @@ -62,7 +62,7 @@ func (c *Context) preFormat() { case c.Format.IsTable(): c.finalFormat = c.finalFormat[len(TableFormatKey):] case c.Format.IsJSON(): - c.finalFormat = jsonFormat + c.finalFormat = JSONFormat } c.finalFormat = strings.Trim(c.finalFormat, " ") diff --git a/cli/command/formatter/image.go b/cli/command/formatter/image.go index 89553d104855..a84bd08e4125 100644 --- a/cli/command/formatter/image.go +++ b/cli/command/formatter/image.go @@ -27,6 +27,9 @@ type ImageContext struct { } func isDangling(image types.ImageSummary) bool { + if len(image.RepoTags) == 0 && len(image.RepoDigests) == 0 { + return true + } return len(image.RepoTags) == 1 && image.RepoTags[0] == ":" && len(image.RepoDigests) == 1 && image.RepoDigests[0] == "@" } diff --git a/cli/command/idresolver/client_test.go b/cli/command/idresolver/client_test.go index c53cfc6a8bd9..62049f4257db 100644 --- a/cli/command/idresolver/client_test.go +++ b/cli/command/idresolver/client_test.go @@ -14,14 +14,14 @@ type fakeClient struct { serviceInspectFunc func(string) (swarm.Service, []byte, error) } -func (cli *fakeClient) NodeInspectWithRaw(ctx context.Context, nodeID string) (swarm.Node, []byte, error) { +func (cli *fakeClient) NodeInspectWithRaw(_ context.Context, nodeID string) (swarm.Node, []byte, error) { if cli.nodeInspectFunc != nil { return cli.nodeInspectFunc(nodeID) } return swarm.Node{}, []byte{}, nil } -func (cli *fakeClient) ServiceInspectWithRaw(ctx context.Context, serviceID string, options types.ServiceInspectOptions) (swarm.Service, []byte, error) { +func (cli *fakeClient) ServiceInspectWithRaw(_ context.Context, serviceID string, _ types.ServiceInspectOptions) (swarm.Service, []byte, error) { if cli.serviceInspectFunc != nil { return cli.serviceInspectFunc(serviceID) } diff --git a/cli/command/idresolver/idresolver_test.go b/cli/command/idresolver/idresolver_test.go index a7b6fc036314..1b8810c7ae3a 100644 --- a/cli/command/idresolver/idresolver_test.go +++ b/cli/command/idresolver/idresolver_test.go @@ -4,7 +4,7 @@ import ( "context" "testing" - . "github.com/docker/cli/internal/test/builders" // Import builders to get the builder function as package function + "github.com/docker/cli/internal/test/builders" "github.com/docker/docker/api/types/swarm" "github.com/pkg/errors" "gotest.tools/v3/assert" @@ -50,7 +50,7 @@ func TestResolveWithCache(t *testing.T) { cli := &fakeClient{ nodeInspectFunc: func(nodeID string) (swarm.Node, []byte, error) { inspectCounter++ - return *Node(NodeName("node-foo")), []byte{}, nil + return *builders.Node(builders.NodeName("node-foo")), []byte{}, nil }, } @@ -82,14 +82,14 @@ func TestResolveNode(t *testing.T) { { nodeID: "nodeID", nodeInspectFunc: func(string) (swarm.Node, []byte, error) { - return *Node(NodeName("node-foo")), []byte{}, nil + return *builders.Node(builders.NodeName("node-foo")), []byte{}, nil }, expectedID: "node-foo", }, { nodeID: "nodeID", nodeInspectFunc: func(string) (swarm.Node, []byte, error) { - return *Node(NodeName(""), Hostname("node-hostname")), []byte{}, nil + return *builders.Node(builders.NodeName(""), builders.Hostname("node-hostname")), []byte{}, nil }, expectedID: "node-hostname", }, @@ -124,7 +124,7 @@ func TestResolveService(t *testing.T) { { serviceID: "serviceID", serviceInspectFunc: func(string) (swarm.Service, []byte, error) { - return *Service(ServiceName("service-foo")), []byte{}, nil + return *builders.Service(builders.ServiceName("service-foo")), []byte{}, nil }, expectedID: "service-foo", }, diff --git a/cli/command/image/build.go b/cli/command/image/build.go index 771f61ec66f0..cad1195319f6 100644 --- a/cli/command/image/build.go +++ b/cli/command/image/build.go @@ -459,7 +459,7 @@ func rewriteDockerfileFromForContentTrust(ctx context.Context, dockerfile io.Rea return nil, nil, err } - line = dockerfileFromLinePattern.ReplaceAllLiteralString(line, fmt.Sprintf("FROM %s", reference.FamiliarString(trustedRef))) + line = dockerfileFromLinePattern.ReplaceAllLiteralString(line, "FROM "+reference.FamiliarString(trustedRef)) resolvedTags = append(resolvedTags, &resolvedTag{ digestRef: trustedRef, tagRef: ref, diff --git a/cli/command/image/build/context.go b/cli/command/image/build/context.go index c4deef7e08a6..a02d23c6b7c1 100644 --- a/cli/command/image/build/context.go +++ b/cli/command/image/build/context.go @@ -223,7 +223,7 @@ func GetContextFromURL(out io.Writer, remoteURL, dockerfileName string) (io.Read progressOutput := streamformatter.NewProgressOutput(out) // Pass the response body through a progress reader. - progReader := progress.NewProgressReader(response.Body, progressOutput, response.ContentLength, "", fmt.Sprintf("Downloading build context from remote url: %s", remoteURL)) + progReader := progress.NewProgressReader(response.Body, progressOutput, response.ContentLength, "", "Downloading build context from remote url: "+remoteURL) return GetContextFromReader(ioutils.NewReadCloserWrapper(progReader, func() error { return response.Body.Close() }), dockerfileName) } @@ -231,7 +231,7 @@ func GetContextFromURL(out io.Writer, remoteURL, dockerfileName string) (io.Read // getWithStatusError does an http.Get() and returns an error if the // status code is 4xx or 5xx. func getWithStatusError(url string) (resp *http.Response, err error) { - // #nosec G107 + //nolint:gosec // Ignore G107: Potential HTTP request made with variable url if resp, err = http.Get(url); err != nil { return nil, err } diff --git a/cli/command/image/build/context_test.go b/cli/command/image/build/context_test.go index 29d48ec1d9c7..16c2f82f5e77 100644 --- a/cli/command/image/build/context_test.go +++ b/cli/command/image/build/context_test.go @@ -18,7 +18,7 @@ import ( const dockerfileContents = "FROM busybox" -func prepareEmpty(t *testing.T) string { +func prepareEmpty(_ *testing.T) string { return "" } @@ -126,7 +126,6 @@ func TestGetContextFromReaderString(t *testing.T) { tarReader := tar.NewReader(tarArchive) _, err = tarReader.Next() - if err != nil { t.Fatalf("Error when reading tar archive: %s", err) } diff --git a/cli/command/image/client_test.go b/cli/command/image/client_test.go index afae0b735dae..99244c7864f9 100644 --- a/cli/command/image/client_test.go +++ b/cli/command/image/client_test.go @@ -87,11 +87,11 @@ func (cli *fakeClient) ImageLoad(_ context.Context, input io.Reader, quiet bool) return types.ImageLoadResponse{}, nil } -func (cli *fakeClient) ImageList(ctx context.Context, options types.ImageListOptions) ([]types.ImageSummary, error) { +func (cli *fakeClient) ImageList(_ context.Context, options types.ImageListOptions) ([]types.ImageSummary, error) { if cli.imageListFunc != nil { return cli.imageListFunc(options) } - return []types.ImageSummary{{}}, nil + return []types.ImageSummary{}, nil } func (cli *fakeClient) ImageInspectWithRaw(_ context.Context, image string) (types.ImageInspect, []byte, error) { diff --git a/cli/command/image/list_test.go b/cli/command/image/list_test.go index 67d87a2d3571..fb2227b807ad 100644 --- a/cli/command/image/list_test.go +++ b/cli/command/image/list_test.go @@ -30,7 +30,7 @@ func TestNewImagesCommandErrors(t *testing.T) { name: "failed-list", expectedError: "something went wrong", imageListFunc: func(options types.ImageListOptions) ([]types.ImageSummary, error) { - return []types.ImageSummary{{}}, errors.Errorf("something went wrong") + return []types.ImageSummary{}, errors.Errorf("something went wrong") }, }, } @@ -66,7 +66,7 @@ func TestNewImagesCommandSuccess(t *testing.T) { args: []string{"image"}, imageListFunc: func(options types.ImageListOptions) ([]types.ImageSummary, error) { assert.Check(t, is.Equal("image", options.Filters.Get("reference")[0])) - return []types.ImageSummary{{}}, nil + return []types.ImageSummary{}, nil }, }, { @@ -74,7 +74,7 @@ func TestNewImagesCommandSuccess(t *testing.T) { args: []string{"--filter", "name=value"}, imageListFunc: func(options types.ImageListOptions) ([]types.ImageSummary, error) { assert.Check(t, is.Equal("value", options.Filters.Get("name")[0])) - return []types.ImageSummary{{}}, nil + return []types.ImageSummary{}, nil }, }, } diff --git a/cli/command/image/pull_test.go b/cli/command/image/pull_test.go index 3e6c55a5b45b..ca87aeb70b36 100644 --- a/cli/command/image/pull_test.go +++ b/cli/command/image/pull_test.go @@ -1,6 +1,7 @@ package image import ( + "errors" "fmt" "io" "strings" @@ -112,7 +113,7 @@ func TestNewPullCommandWithContentTrustErrors(t *testing.T) { for _, tc := range testCases { cli := test.NewFakeCli(&fakeClient{ imagePullFunc: func(ref string, options types.ImagePullOptions) (io.ReadCloser, error) { - return io.NopCloser(strings.NewReader("")), fmt.Errorf("shouldn't try to pull image") + return io.NopCloser(strings.NewReader("")), errors.New("shouldn't try to pull image") }, }, test.EnableContentTrust) cli.SetNotaryClient(tc.notaryFunc) diff --git a/cli/command/image/remove_test.go b/cli/command/image/remove_test.go index 55de3eb6a62e..7a45b614efd6 100644 --- a/cli/command/image/remove_test.go +++ b/cli/command/image/remove_test.go @@ -18,7 +18,7 @@ type notFound struct { } func (n notFound) Error() string { - return fmt.Sprintf("Error: No such image: %s", n.imageID) + return "Error: No such image: " + n.imageID } func (n notFound) NotFound() bool { diff --git a/cli/command/network/client_test.go b/cli/command/network/client_test.go index 38d942e23deb..18c91e251dfe 100644 --- a/cli/command/network/client_test.go +++ b/cli/command/network/client_test.go @@ -52,6 +52,6 @@ func (c *fakeClient) NetworkRemove(ctx context.Context, networkID string) error return nil } -func (c *fakeClient) NetworkInspectWithRaw(ctx context.Context, network string, options types.NetworkInspectOptions) (types.NetworkResource, []byte, error) { +func (c *fakeClient) NetworkInspectWithRaw(context.Context, string, types.NetworkInspectOptions) (types.NetworkResource, []byte, error) { return types.NetworkResource{}, nil, nil } diff --git a/cli/command/network/connect.go b/cli/command/network/connect.go index 75e3c0b4c893..0ff367b3159c 100644 --- a/cli/command/network/connect.go +++ b/cli/command/network/connect.go @@ -2,7 +2,7 @@ package network import ( "context" - "fmt" + "errors" "strings" "github.com/docker/cli/cli" @@ -10,6 +10,7 @@ import ( "github.com/docker/cli/cli/command/completion" "github.com/docker/cli/opts" "github.com/docker/docker/api/types/network" + "github.com/docker/docker/client" "github.com/spf13/cobra" ) @@ -36,14 +37,14 @@ func newConnectCommand(dockerCli command.Cli) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { options.network = args[0] options.container = args[1] - return runConnect(dockerCli, options) + return runConnect(cmd.Context(), dockerCli.Client(), options) }, ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if len(args) == 0 { return completion.NetworkNames(dockerCli)(cmd, args, toComplete) } - network := args[0] - return completion.ContainerNames(dockerCli, true, not(isConnected(network)))(cmd, args, toComplete) + nw := args[0] + return completion.ContainerNames(dockerCli, true, not(isConnected(nw)))(cmd, args, toComplete) }, } @@ -57,14 +58,13 @@ func newConnectCommand(dockerCli command.Cli) *cobra.Command { return cmd } -func runConnect(dockerCli command.Cli, options connectOptions) error { - client := dockerCli.Client() - +func runConnect(ctx context.Context, apiClient client.NetworkAPIClient, options connectOptions) error { driverOpts, err := convertDriverOpt(options.driverOpts) if err != nil { return err } - epConfig := &network.EndpointSettings{ + + return apiClient.NetworkConnect(ctx, options.network, options.container, &network.EndpointSettings{ IPAMConfig: &network.EndpointIPAMConfig{ IPv4Address: options.ipaddress, IPv6Address: options.ipv6address, @@ -73,9 +73,7 @@ func runConnect(dockerCli command.Cli, options connectOptions) error { Links: options.links.GetAll(), Aliases: options.aliases, DriverOpts: driverOpts, - } - - return client.NetworkConnect(context.Background(), options.network, options.container, epConfig) + }) } func convertDriverOpt(opts []string) (map[string]string, error) { @@ -85,7 +83,7 @@ func convertDriverOpt(opts []string) (map[string]string, error) { // TODO(thaJeztah): we should probably not accept whitespace here (both for key and value). k = strings.TrimSpace(k) if !ok || k == "" { - return nil, fmt.Errorf("invalid key/value pair format in driver options") + return nil, errors.New("invalid key/value pair format in driver options") } driverOpt[k] = strings.TrimSpace(v) } diff --git a/cli/command/network/list_test.go b/cli/command/network/list_test.go index b32c08f85b3f..2834aa125da5 100644 --- a/cli/command/network/list_test.go +++ b/cli/command/network/list_test.go @@ -6,7 +6,7 @@ import ( "testing" "github.com/docker/cli/internal/test" - . "github.com/docker/cli/internal/test/builders" + "github.com/docker/cli/internal/test/builders" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" "github.com/google/go-cmp/cmp" @@ -59,10 +59,10 @@ func TestNetworkList(t *testing.T) { } assert.Check(t, is.DeepEqual(expectedOpts, options, cmp.AllowUnexported(filters.Args{}))) - return []types.NetworkResource{*NetworkResource(NetworkResourceID("123454321"), - NetworkResourceName("network_1"), - NetworkResourceDriver("09.7.01"), - NetworkResourceScope("global"))}, nil + return []types.NetworkResource{*builders.NetworkResource(builders.NetworkResourceID("123454321"), + builders.NetworkResourceName("network_1"), + builders.NetworkResourceDriver("09.7.01"), + builders.NetworkResourceScope("global"))}, nil }, }, { @@ -73,9 +73,9 @@ func TestNetworkList(t *testing.T) { golden: "network-list-sort.golden", networkListFunc: func(ctx context.Context, options types.NetworkListOptions) ([]types.NetworkResource, error) { return []types.NetworkResource{ - *NetworkResource(NetworkResourceName("network-2-foo")), - *NetworkResource(NetworkResourceName("network-1-foo")), - *NetworkResource(NetworkResourceName("network-10-foo")), + *builders.NetworkResource(builders.NetworkResourceName("network-2-foo")), + *builders.NetworkResource(builders.NetworkResourceName("network-1-foo")), + *builders.NetworkResource(builders.NetworkResourceName("network-10-foo")), }, nil }, }, @@ -86,7 +86,7 @@ func TestNetworkList(t *testing.T) { cli := test.NewFakeCli(&fakeClient{networkListFunc: tc.networkListFunc}) cmd := newListCommand(cli) for key, value := range tc.flags { - cmd.Flags().Set(key, value) + assert.Check(t, cmd.Flags().Set(key, value)) } assert.NilError(t, cmd.Execute()) golden.Assert(t, cli.OutBuffer().String(), tc.golden) diff --git a/cli/command/network/prune.go b/cli/command/network/prune.go index 928edb33440d..460a5b5646c2 100644 --- a/cli/command/network/prune.go +++ b/cli/command/network/prune.go @@ -70,7 +70,7 @@ func runPrune(dockerCli command.Cli, options pruneOptions) (output string, err e // RunPrune calls the Network Prune API // This returns the amount of space reclaimed and a detailed output string -func RunPrune(dockerCli command.Cli, all bool, filter opts.FilterOpt) (uint64, string, error) { +func RunPrune(dockerCli command.Cli, _ bool, filter opts.FilterOpt) (uint64, string, error) { output, err := runPrune(dockerCli, pruneOptions{force: true, filter: filter}) return 0, output, err } diff --git a/cli/command/node/client_test.go b/cli/command/node/client_test.go index 3948c6a87103..a9cf5ed97278 100644 --- a/cli/command/node/client_test.go +++ b/cli/command/node/client_test.go @@ -20,49 +20,49 @@ type fakeClient struct { serviceInspectFunc func(ctx context.Context, serviceID string, opts types.ServiceInspectOptions) (swarm.Service, []byte, error) } -func (cli *fakeClient) NodeInspectWithRaw(ctx context.Context, ref string) (swarm.Node, []byte, error) { +func (cli *fakeClient) NodeInspectWithRaw(context.Context, string) (swarm.Node, []byte, error) { if cli.nodeInspectFunc != nil { return cli.nodeInspectFunc() } return swarm.Node{}, []byte{}, nil } -func (cli *fakeClient) NodeList(ctx context.Context, options types.NodeListOptions) ([]swarm.Node, error) { +func (cli *fakeClient) NodeList(context.Context, types.NodeListOptions) ([]swarm.Node, error) { if cli.nodeListFunc != nil { return cli.nodeListFunc() } return []swarm.Node{}, nil } -func (cli *fakeClient) NodeRemove(ctx context.Context, nodeID string, options types.NodeRemoveOptions) error { +func (cli *fakeClient) NodeRemove(context.Context, string, types.NodeRemoveOptions) error { if cli.nodeRemoveFunc != nil { return cli.nodeRemoveFunc() } return nil } -func (cli *fakeClient) NodeUpdate(ctx context.Context, nodeID string, version swarm.Version, node swarm.NodeSpec) error { +func (cli *fakeClient) NodeUpdate(_ context.Context, nodeID string, version swarm.Version, node swarm.NodeSpec) error { if cli.nodeUpdateFunc != nil { return cli.nodeUpdateFunc(nodeID, version, node) } return nil } -func (cli *fakeClient) Info(ctx context.Context) (types.Info, error) { +func (cli *fakeClient) Info(context.Context) (types.Info, error) { if cli.infoFunc != nil { return cli.infoFunc() } return types.Info{}, nil } -func (cli *fakeClient) TaskInspectWithRaw(ctx context.Context, taskID string) (swarm.Task, []byte, error) { +func (cli *fakeClient) TaskInspectWithRaw(_ context.Context, taskID string) (swarm.Task, []byte, error) { if cli.taskInspectFunc != nil { return cli.taskInspectFunc(taskID) } return swarm.Task{}, []byte{}, nil } -func (cli *fakeClient) TaskList(ctx context.Context, options types.TaskListOptions) ([]swarm.Task, error) { +func (cli *fakeClient) TaskList(_ context.Context, options types.TaskListOptions) ([]swarm.Task, error) { if cli.taskListFunc != nil { return cli.taskListFunc(options) } diff --git a/cli/command/node/demote_test.go b/cli/command/node/demote_test.go index 8c226a9f9fe9..64a11c098593 100644 --- a/cli/command/node/demote_test.go +++ b/cli/command/node/demote_test.go @@ -5,7 +5,7 @@ import ( "testing" "github.com/docker/cli/internal/test" - . "github.com/docker/cli/internal/test/builders" // Import builders to get the builder function as package functions + "github.com/docker/cli/internal/test/builders" "github.com/docker/docker/api/types/swarm" "github.com/pkg/errors" "gotest.tools/v3/assert" @@ -52,7 +52,7 @@ func TestNodeDemoteNoChange(t *testing.T) { cmd := newDemoteCommand( test.NewFakeCli(&fakeClient{ nodeInspectFunc: func() (swarm.Node, []byte, error) { - return *Node(), []byte{}, nil + return *builders.Node(), []byte{}, nil }, nodeUpdateFunc: func(nodeID string, version swarm.Version, node swarm.NodeSpec) error { if node.Role != swarm.NodeRoleWorker { @@ -69,7 +69,7 @@ func TestNodeDemoteMultipleNode(t *testing.T) { cmd := newDemoteCommand( test.NewFakeCli(&fakeClient{ nodeInspectFunc: func() (swarm.Node, []byte, error) { - return *Node(Manager()), []byte{}, nil + return *builders.Node(builders.Manager()), []byte{}, nil }, nodeUpdateFunc: func(nodeID string, version swarm.Version, node swarm.NodeSpec) error { if node.Role != swarm.NodeRoleWorker { diff --git a/cli/command/node/inspect_test.go b/cli/command/node/inspect_test.go index 2c2e33bedf2b..3e932def39c0 100644 --- a/cli/command/node/inspect_test.go +++ b/cli/command/node/inspect_test.go @@ -6,7 +6,7 @@ import ( "testing" "github.com/docker/cli/internal/test" - . "github.com/docker/cli/internal/test/builders" // Import builders to get the builder function as package functions + "github.com/docker/cli/internal/test/builders" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/swarm" "github.com/pkg/errors" @@ -71,7 +71,7 @@ func TestNodeInspectErrors(t *testing.T) { })) cmd.SetArgs(tc.args) for key, value := range tc.flags { - cmd.Flags().Set(key, value) + assert.Check(t, cmd.Flags().Set(key, value)) } cmd.SetOut(io.Discard) assert.ErrorContains(t, cmd.Execute(), tc.expectedError) @@ -86,7 +86,7 @@ func TestNodeInspectPretty(t *testing.T) { { name: "simple", nodeInspectFunc: func() (swarm.Node, []byte, error) { - return *Node(NodeLabels(map[string]string{ + return *builders.Node(builders.NodeLabels(map[string]string{ "lbl1": "value1", })), []byte{}, nil }, @@ -94,13 +94,13 @@ func TestNodeInspectPretty(t *testing.T) { { name: "manager", nodeInspectFunc: func() (swarm.Node, []byte, error) { - return *Node(Manager()), []byte{}, nil + return *builders.Node(builders.Manager()), []byte{}, nil }, }, { name: "manager-leader", nodeInspectFunc: func() (swarm.Node, []byte, error) { - return *Node(Manager(Leader())), []byte{}, nil + return *builders.Node(builders.Manager(builders.Leader())), []byte{}, nil }, }, } @@ -110,7 +110,7 @@ func TestNodeInspectPretty(t *testing.T) { }) cmd := newInspectCommand(cli) cmd.SetArgs([]string{"nodeID"}) - cmd.Flags().Set("pretty", "true") + assert.Check(t, cmd.Flags().Set("pretty", "true")) assert.NilError(t, cmd.Execute()) golden.Assert(t, cli.OutBuffer().String(), fmt.Sprintf("node-inspect-pretty.%s.golden", tc.name)) } diff --git a/cli/command/node/list_test.go b/cli/command/node/list_test.go index 6c176795fc77..bff15a82ae63 100644 --- a/cli/command/node/list_test.go +++ b/cli/command/node/list_test.go @@ -6,7 +6,7 @@ import ( "github.com/docker/cli/cli/config/configfile" "github.com/docker/cli/internal/test" - . "github.com/docker/cli/internal/test/builders" // Import builders to get the builder function as package function + "github.com/docker/cli/internal/test/builders" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/swarm" "github.com/pkg/errors" @@ -56,9 +56,9 @@ func TestNodeList(t *testing.T) { cli := test.NewFakeCli(&fakeClient{ nodeListFunc: func() ([]swarm.Node, error) { return []swarm.Node{ - *Node(NodeID("nodeID1"), Hostname("node-2-foo"), Manager(Leader()), EngineVersion(".")), - *Node(NodeID("nodeID2"), Hostname("node-10-foo"), Manager(), EngineVersion("18.03.0-ce")), - *Node(NodeID("nodeID3"), Hostname("node-1-foo")), + *builders.Node(builders.NodeID("nodeID1"), builders.Hostname("node-2-foo"), builders.Manager(builders.Leader()), builders.EngineVersion(".")), + *builders.Node(builders.NodeID("nodeID2"), builders.Hostname("node-10-foo"), builders.Manager(), builders.EngineVersion("18.03.0-ce")), + *builders.Node(builders.NodeID("nodeID3"), builders.Hostname("node-1-foo")), }, nil }, infoFunc: func() (types.Info, error) { @@ -79,12 +79,12 @@ func TestNodeListQuietShouldOnlyPrintIDs(t *testing.T) { cli := test.NewFakeCli(&fakeClient{ nodeListFunc: func() ([]swarm.Node, error) { return []swarm.Node{ - *Node(NodeID("nodeID1")), + *builders.Node(builders.NodeID("nodeID1")), }, nil }, }) cmd := newListCommand(cli) - cmd.Flags().Set("quiet", "true") + assert.Check(t, cmd.Flags().Set("quiet", "true")) assert.NilError(t, cmd.Execute()) assert.Check(t, is.Equal(cli.OutBuffer().String(), "nodeID1\n")) } @@ -93,9 +93,9 @@ func TestNodeListDefaultFormatFromConfig(t *testing.T) { cli := test.NewFakeCli(&fakeClient{ nodeListFunc: func() ([]swarm.Node, error) { return []swarm.Node{ - *Node(NodeID("nodeID1"), Hostname("nodeHostname1"), Manager(Leader())), - *Node(NodeID("nodeID2"), Hostname("nodeHostname2"), Manager()), - *Node(NodeID("nodeID3"), Hostname("nodeHostname3")), + *builders.Node(builders.NodeID("nodeID1"), builders.Hostname("nodeHostname1"), builders.Manager(builders.Leader())), + *builders.Node(builders.NodeID("nodeID2"), builders.Hostname("nodeHostname2"), builders.Manager()), + *builders.Node(builders.NodeID("nodeID3"), builders.Hostname("nodeHostname3")), }, nil }, infoFunc: func() (types.Info, error) { @@ -118,8 +118,8 @@ func TestNodeListFormat(t *testing.T) { cli := test.NewFakeCli(&fakeClient{ nodeListFunc: func() ([]swarm.Node, error) { return []swarm.Node{ - *Node(NodeID("nodeID1"), Hostname("nodeHostname1"), Manager(Leader())), - *Node(NodeID("nodeID2"), Hostname("nodeHostname2"), Manager()), + *builders.Node(builders.NodeID("nodeID1"), builders.Hostname("nodeHostname1"), builders.Manager(builders.Leader())), + *builders.Node(builders.NodeID("nodeID2"), builders.Hostname("nodeHostname2"), builders.Manager()), }, nil }, infoFunc: func() (types.Info, error) { @@ -134,7 +134,7 @@ func TestNodeListFormat(t *testing.T) { NodesFormat: "{{.ID}}: {{.Hostname}} {{.Status}}/{{.ManagerStatus}}", }) cmd := newListCommand(cli) - cmd.Flags().Set("format", "{{.Hostname}}: {{.ManagerStatus}}") + assert.Check(t, cmd.Flags().Set("format", "{{.Hostname}}: {{.ManagerStatus}}")) assert.NilError(t, cmd.Execute()) golden.Assert(t, cli.OutBuffer().String(), "node-list-format-flag.golden") } diff --git a/cli/command/node/promote_test.go b/cli/command/node/promote_test.go index ebb2890ec2ad..42b3a7d755ed 100644 --- a/cli/command/node/promote_test.go +++ b/cli/command/node/promote_test.go @@ -5,7 +5,7 @@ import ( "testing" "github.com/docker/cli/internal/test" - . "github.com/docker/cli/internal/test/builders" // Import builders to get the builder function as package function + "github.com/docker/cli/internal/test/builders" "github.com/docker/docker/api/types/swarm" "github.com/pkg/errors" "gotest.tools/v3/assert" @@ -52,7 +52,7 @@ func TestNodePromoteNoChange(t *testing.T) { cmd := newPromoteCommand( test.NewFakeCli(&fakeClient{ nodeInspectFunc: func() (swarm.Node, []byte, error) { - return *Node(Manager()), []byte{}, nil + return *builders.Node(builders.Manager()), []byte{}, nil }, nodeUpdateFunc: func(nodeID string, version swarm.Version, node swarm.NodeSpec) error { if node.Role != swarm.NodeRoleManager { @@ -69,7 +69,7 @@ func TestNodePromoteMultipleNode(t *testing.T) { cmd := newPromoteCommand( test.NewFakeCli(&fakeClient{ nodeInspectFunc: func() (swarm.Node, []byte, error) { - return *Node(), []byte{}, nil + return *builders.Node(), []byte{}, nil }, nodeUpdateFunc: func(nodeID string, version swarm.Version, node swarm.NodeSpec) error { if node.Role != swarm.NodeRoleManager { diff --git a/cli/command/node/ps_test.go b/cli/command/node/ps_test.go index 870e8eb1871f..529edf3faf9d 100644 --- a/cli/command/node/ps_test.go +++ b/cli/command/node/ps_test.go @@ -8,7 +8,7 @@ import ( "time" "github.com/docker/cli/internal/test" - . "github.com/docker/cli/internal/test/builders" // Import builders to get the builder function as package function + "github.com/docker/cli/internal/test/builders" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/swarm" "github.com/pkg/errors" @@ -57,7 +57,7 @@ func TestNodePsErrors(t *testing.T) { cmd := newPsCommand(cli) cmd.SetArgs(tc.args) for key, value := range tc.flags { - cmd.Flags().Set(key, value) + assert.Check(t, cmd.Flags().Set(key, value)) } cmd.SetOut(io.Discard) assert.Error(t, cmd.Execute(), tc.expectedError) @@ -79,11 +79,11 @@ func TestNodePs(t *testing.T) { name: "simple", args: []string{"nodeID"}, nodeInspectFunc: func() (swarm.Node, []byte, error) { - return *Node(), []byte{}, nil + return *builders.Node(), []byte{}, nil }, taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) { return []swarm.Task{ - *Task(WithStatus(Timestamp(time.Now().Add(-2*time.Hour)), PortStatus([]swarm.PortConfig{ + *builders.Task(builders.WithStatus(builders.Timestamp(time.Now().Add(-2*time.Hour)), builders.PortStatus([]swarm.PortConfig{ { TargetPort: 80, PublishedPort: 80, @@ -107,16 +107,16 @@ func TestNodePs(t *testing.T) { name: "with-errors", args: []string{"nodeID"}, nodeInspectFunc: func() (swarm.Node, []byte, error) { - return *Node(), []byte{}, nil + return *builders.Node(), []byte{}, nil }, taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) { return []swarm.Task{ - *Task(TaskID("taskID1"), TaskServiceID("failure"), - WithStatus(Timestamp(time.Now().Add(-2*time.Hour)), StatusErr("a task error"))), - *Task(TaskID("taskID2"), TaskServiceID("failure"), - WithStatus(Timestamp(time.Now().Add(-3*time.Hour)), StatusErr("a task error"))), - *Task(TaskID("taskID3"), TaskServiceID("failure"), - WithStatus(Timestamp(time.Now().Add(-4*time.Hour)), StatusErr("a task error"))), + *builders.Task(builders.TaskID("taskID1"), builders.TaskServiceID("failure"), + builders.WithStatus(builders.Timestamp(time.Now().Add(-2*time.Hour)), builders.StatusErr("a task error"))), + *builders.Task(builders.TaskID("taskID2"), builders.TaskServiceID("failure"), + builders.WithStatus(builders.Timestamp(time.Now().Add(-3*time.Hour)), builders.StatusErr("a task error"))), + *builders.Task(builders.TaskID("taskID3"), builders.TaskServiceID("failure"), + 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) { @@ -142,7 +142,7 @@ func TestNodePs(t *testing.T) { cmd := newPsCommand(cli) cmd.SetArgs(tc.args) for key, value := range tc.flags { - cmd.Flags().Set(key, value) + assert.Check(t, cmd.Flags().Set(key, value)) } assert.NilError(t, cmd.Execute()) golden.Assert(t, cli.OutBuffer().String(), fmt.Sprintf("node-ps.%s.golden", tc.name)) diff --git a/cli/command/node/update_test.go b/cli/command/node/update_test.go index 797f79f621da..0421e4757022 100644 --- a/cli/command/node/update_test.go +++ b/cli/command/node/update_test.go @@ -5,7 +5,7 @@ import ( "testing" "github.com/docker/cli/internal/test" - . "github.com/docker/cli/internal/test/builders" // Import builders to get the builder function as package function + "github.com/docker/cli/internal/test/builders" "github.com/docker/docker/api/types/swarm" "github.com/pkg/errors" "gotest.tools/v3/assert" @@ -43,7 +43,7 @@ func TestNodeUpdateErrors(t *testing.T) { { args: []string{"nodeID"}, nodeInspectFunc: func() (swarm.Node, []byte, error) { - return *Node(NodeLabels(map[string]string{ + return *builders.Node(builders.NodeLabels(map[string]string{ "key": "value", })), []byte{}, nil }, @@ -61,7 +61,7 @@ func TestNodeUpdateErrors(t *testing.T) { })) cmd.SetArgs(tc.args) for key, value := range tc.flags { - cmd.Flags().Set(key, value) + assert.Check(t, cmd.Flags().Set(key, value)) } cmd.SetOut(io.Discard) assert.ErrorContains(t, cmd.Execute(), tc.expectedError) @@ -81,7 +81,7 @@ func TestNodeUpdate(t *testing.T) { "role": "manager", }, nodeInspectFunc: func() (swarm.Node, []byte, error) { - return *Node(), []byte{}, nil + return *builders.Node(), []byte{}, nil }, nodeUpdateFunc: func(nodeID string, version swarm.Version, node swarm.NodeSpec) error { if node.Role != swarm.NodeRoleManager { @@ -96,7 +96,7 @@ func TestNodeUpdate(t *testing.T) { "availability": "drain", }, nodeInspectFunc: func() (swarm.Node, []byte, error) { - return *Node(), []byte{}, nil + return *builders.Node(), []byte{}, nil }, nodeUpdateFunc: func(nodeID string, version swarm.Version, node swarm.NodeSpec) error { if node.Availability != swarm.NodeAvailabilityDrain { @@ -111,7 +111,7 @@ func TestNodeUpdate(t *testing.T) { "label-add": "lbl", }, nodeInspectFunc: func() (swarm.Node, []byte, error) { - return *Node(), []byte{}, nil + return *builders.Node(), []byte{}, nil }, nodeUpdateFunc: func(nodeID string, version swarm.Version, node swarm.NodeSpec) error { if _, present := node.Annotations.Labels["lbl"]; !present { @@ -126,7 +126,7 @@ func TestNodeUpdate(t *testing.T) { "label-add": "key=value", }, nodeInspectFunc: func() (swarm.Node, []byte, error) { - return *Node(), []byte{}, nil + return *builders.Node(), []byte{}, nil }, nodeUpdateFunc: func(nodeID string, version swarm.Version, node swarm.NodeSpec) error { if value, present := node.Annotations.Labels["key"]; !present || value != "value" { @@ -141,7 +141,7 @@ func TestNodeUpdate(t *testing.T) { "label-rm": "key", }, nodeInspectFunc: func() (swarm.Node, []byte, error) { - return *Node(NodeLabels(map[string]string{ + return *builders.Node(builders.NodeLabels(map[string]string{ "key": "value", })), []byte{}, nil }, @@ -161,7 +161,7 @@ func TestNodeUpdate(t *testing.T) { })) cmd.SetArgs(tc.args) for key, value := range tc.flags { - cmd.Flags().Set(key, value) + assert.Check(t, cmd.Flags().Set(key, value)) } assert.NilError(t, cmd.Execute()) } diff --git a/cli/command/plugin/client_test.go b/cli/command/plugin/client_test.go index f52cefec1413..57adccf4f98e 100644 --- a/cli/command/plugin/client_test.go +++ b/cli/command/plugin/client_test.go @@ -20,42 +20,42 @@ type fakeClient struct { pluginInspectFunc func(name string) (*types.Plugin, []byte, error) } -func (c *fakeClient) PluginCreate(ctx context.Context, createContext io.Reader, createOptions types.PluginCreateOptions) error { +func (c *fakeClient) PluginCreate(_ context.Context, createContext io.Reader, createOptions types.PluginCreateOptions) error { if c.pluginCreateFunc != nil { return c.pluginCreateFunc(createContext, createOptions) } return nil } -func (c *fakeClient) PluginEnable(ctx context.Context, name string, enableOptions types.PluginEnableOptions) error { +func (c *fakeClient) PluginEnable(_ context.Context, name string, enableOptions types.PluginEnableOptions) error { if c.pluginEnableFunc != nil { return c.pluginEnableFunc(name, enableOptions) } return nil } -func (c *fakeClient) PluginDisable(context context.Context, name string, disableOptions types.PluginDisableOptions) error { +func (c *fakeClient) PluginDisable(_ context.Context, name string, disableOptions types.PluginDisableOptions) error { if c.pluginDisableFunc != nil { return c.pluginDisableFunc(name, disableOptions) } return nil } -func (c *fakeClient) PluginRemove(context context.Context, name string, removeOptions types.PluginRemoveOptions) error { +func (c *fakeClient) PluginRemove(_ context.Context, name string, removeOptions types.PluginRemoveOptions) error { if c.pluginRemoveFunc != nil { return c.pluginRemoveFunc(name, removeOptions) } return nil } -func (c *fakeClient) PluginInstall(context context.Context, name string, installOptions types.PluginInstallOptions) (io.ReadCloser, error) { +func (c *fakeClient) PluginInstall(_ context.Context, name string, installOptions types.PluginInstallOptions) (io.ReadCloser, error) { if c.pluginInstallFunc != nil { return c.pluginInstallFunc(name, installOptions) } return nil, nil } -func (c *fakeClient) PluginList(context context.Context, filter filters.Args) (types.PluginsListResponse, error) { +func (c *fakeClient) PluginList(_ context.Context, filter filters.Args) (types.PluginsListResponse, error) { if c.pluginListFunc != nil { return c.pluginListFunc(filter) } @@ -63,7 +63,7 @@ func (c *fakeClient) PluginList(context context.Context, filter filters.Args) (t return types.PluginsListResponse{}, nil } -func (c *fakeClient) PluginInspectWithRaw(ctx context.Context, name string) (*types.Plugin, []byte, error) { +func (c *fakeClient) PluginInspectWithRaw(_ context.Context, name string) (*types.Plugin, []byte, error) { if c.pluginInspectFunc != nil { return c.pluginInspectFunc(name) } @@ -71,6 +71,6 @@ func (c *fakeClient) PluginInspectWithRaw(ctx context.Context, name string) (*ty return nil, nil, nil } -func (c *fakeClient) Info(ctx context.Context) (types.Info, error) { +func (c *fakeClient) Info(context.Context) (types.Info, error) { return types.Info{}, nil } diff --git a/cli/command/plugin/create.go b/cli/command/plugin/create.go index bab0778cd39a..437c4736c287 100644 --- a/cli/command/plugin/create.go +++ b/cli/command/plugin/create.go @@ -114,7 +114,6 @@ func runCreate(dockerCli command.Cli, options pluginCreateOptions) error { createCtx, err = archive.TarWithOptions(absContextDir, &archive.TarOptions{ Compression: compression, }) - if err != nil { return err } diff --git a/cli/command/plugin/create_test.go b/cli/command/plugin/create_test.go index 37ce5e22c4bc..e05eb6085538 100644 --- a/cli/command/plugin/create_test.go +++ b/cli/command/plugin/create_test.go @@ -1,7 +1,7 @@ package plugin import ( - "fmt" + "errors" "io" "runtime" "testing" @@ -91,16 +91,14 @@ func TestCreateErrorFromDaemon(t *testing.T) { fs.WithFile("config.json", `{ "Name": "plugin-foo" }`)) defer tmpDir.Remove() - cli := test.NewFakeCli(&fakeClient{ + cmd := newCreateCommand(test.NewFakeCli(&fakeClient{ pluginCreateFunc: func(createContext io.Reader, createOptions types.PluginCreateOptions) error { - return fmt.Errorf("Error creating plugin") + return errors.New("error creating plugin") }, - }) - - cmd := newCreateCommand(cli) + })) cmd.SetArgs([]string{"plugin-foo", tmpDir.Path()}) cmd.SetOut(io.Discard) - assert.ErrorContains(t, cmd.Execute(), "Error creating plugin") + assert.ErrorContains(t, cmd.Execute(), "error creating plugin") } func TestCreatePlugin(t *testing.T) { diff --git a/cli/command/plugin/disable_test.go b/cli/command/plugin/disable_test.go index ca2e0d2cbbbe..cd78943c942d 100644 --- a/cli/command/plugin/disable_test.go +++ b/cli/command/plugin/disable_test.go @@ -1,7 +1,7 @@ package plugin import ( - "fmt" + "errors" "io" "testing" @@ -27,9 +27,9 @@ func TestPluginDisableErrors(t *testing.T) { }, { args: []string{"plugin-foo"}, - expectedError: "Error disabling plugin", + expectedError: "error disabling plugin", pluginDisableFunc: func(name string, disableOptions types.PluginDisableOptions) error { - return fmt.Errorf("Error disabling plugin") + return errors.New("error disabling plugin") }, }, } diff --git a/cli/command/plugin/enable_test.go b/cli/command/plugin/enable_test.go index 1d8840e77c16..0f220547562a 100644 --- a/cli/command/plugin/enable_test.go +++ b/cli/command/plugin/enable_test.go @@ -1,7 +1,7 @@ package plugin import ( - "fmt" + "errors" "io" "testing" @@ -29,7 +29,7 @@ func TestPluginEnableErrors(t *testing.T) { { args: []string{"plugin-foo"}, pluginEnableFunc: func(name string, options types.PluginEnableOptions) error { - return fmt.Errorf("failed to enable plugin") + return errors.New("failed to enable plugin") }, expectedError: "failed to enable plugin", }, @@ -43,10 +43,9 @@ func TestPluginEnableErrors(t *testing.T) { } for _, tc := range testCases { - cmd := newEnableCommand( - test.NewFakeCli(&fakeClient{ - pluginEnableFunc: tc.pluginEnableFunc, - })) + cmd := newEnableCommand(test.NewFakeCli(&fakeClient{ + pluginEnableFunc: tc.pluginEnableFunc, + })) cmd.SetArgs(tc.args) for key, value := range tc.flags { cmd.Flags().Set(key, value) diff --git a/cli/command/plugin/inspect_test.go b/cli/command/plugin/inspect_test.go index 4d3127660a68..f5c2ad585760 100644 --- a/cli/command/plugin/inspect_test.go +++ b/cli/command/plugin/inspect_test.go @@ -1,13 +1,13 @@ package plugin import ( + "errors" "fmt" "io" "testing" "github.com/docker/cli/internal/test" "github.com/docker/docker/api/types" - "gotest.tools/v3/assert" "gotest.tools/v3/golden" ) @@ -52,7 +52,7 @@ func TestInspectErrors(t *testing.T) { args: []string{"foo"}, expectedError: "error inspecting plugin", inspectFunc: func(name string) (*types.Plugin, []byte, error) { - return nil, nil, fmt.Errorf("error inspecting plugin") + return nil, nil, errors.New("error inspecting plugin") }, }, { diff --git a/cli/command/plugin/install_test.go b/cli/command/plugin/install_test.go index e49b582f83c8..03cad5f110e9 100644 --- a/cli/command/plugin/install_test.go +++ b/cli/command/plugin/install_test.go @@ -1,7 +1,7 @@ package plugin import ( - "fmt" + "errors" "io" "strings" "testing" @@ -38,9 +38,9 @@ func TestInstallErrors(t *testing.T) { { description: "installation error", args: []string{"foo"}, - expectedError: "Error installing plugin", + expectedError: "error installing plugin", installFunc: func(name string, options types.PluginInstallOptions) (io.ReadCloser, error) { - return nil, fmt.Errorf("Error installing plugin") + return nil, errors.New("error installing plugin") }, }, { @@ -48,7 +48,7 @@ func TestInstallErrors(t *testing.T) { args: []string{"foo"}, expectedError: "docker image pull", installFunc: func(name string, options types.PluginInstallOptions) (io.ReadCloser, error) { - return nil, fmt.Errorf("(image) when fetching") + return nil, errors.New("(image) when fetching") }, }, } @@ -92,7 +92,7 @@ func TestInstallContentTrustErrors(t *testing.T) { for _, tc := range testCases { cli := test.NewFakeCli(&fakeClient{ pluginInstallFunc: func(name string, options types.PluginInstallOptions) (io.ReadCloser, error) { - return nil, fmt.Errorf("should not try to install plugin") + return nil, errors.New("should not try to install plugin") }, }, test.EnableContentTrust) cli.SetNotaryClient(tc.notaryFunc) diff --git a/cli/command/plugin/list_test.go b/cli/command/plugin/list_test.go index ed30eb1c596f..a5e4e71d31a0 100644 --- a/cli/command/plugin/list_test.go +++ b/cli/command/plugin/list_test.go @@ -1,7 +1,7 @@ package plugin import ( - "fmt" + "errors" "io" "testing" @@ -32,7 +32,7 @@ func TestListErrors(t *testing.T) { args: []string{}, expectedError: "error listing plugins", listFunc: func(filter filters.Args) (types.PluginsListResponse, error) { - return types.PluginsListResponse{}, fmt.Errorf("error listing plugins") + return types.PluginsListResponse{}, errors.New("error listing plugins") }, }, { diff --git a/cli/command/plugin/remove_test.go b/cli/command/plugin/remove_test.go index 36cfe0ba4508..cf701cfa3b72 100644 --- a/cli/command/plugin/remove_test.go +++ b/cli/command/plugin/remove_test.go @@ -1,7 +1,7 @@ package plugin import ( - "fmt" + "errors" "io" "testing" @@ -24,9 +24,9 @@ func TestRemoveErrors(t *testing.T) { { args: []string{"plugin-foo"}, pluginRemoveFunc: func(name string, options types.PluginRemoveOptions) error { - return fmt.Errorf("Error removing plugin") + return errors.New("error removing plugin") }, - expectedError: "Error removing plugin", + expectedError: "error removing plugin", }, } diff --git a/cli/command/registry.go b/cli/command/registry.go index 94b0c4171a5b..90cc08c53ae4 100644 --- a/cli/command/registry.go +++ b/cli/command/registry.go @@ -21,8 +21,9 @@ import ( "github.com/pkg/errors" ) -// ElectAuthServer returns the default registry to use -// Deprecated: use registry.IndexServer instead +// ElectAuthServer returns the default registry to use. +// +// Deprecated: use [registry.IndexServer] instead. func ElectAuthServer(_ context.Context, _ Cli) string { return registry.IndexServer } @@ -55,9 +56,12 @@ func RegistryAuthenticationPrivilegedFunc(cli Cli, index *registrytypes.IndexInf } } -// ResolveAuthConfig is like registry.ResolveAuthConfig, but if using the -// default index, it uses the default index name for the daemon's platform, -// not the client's platform. +// ResolveAuthConfig returns auth-config for the given registry from the +// credential-store. It returns an empty AuthConfig if no credentials were +// found. +// +// It is similar to [registry.ResolveAuthConfig], but uses the credentials- +// store, instead of looking up credentials from a map. func ResolveAuthConfig(_ context.Context, cli Cli, index *registrytypes.IndexInfo) types.AuthConfig { configKey := index.Name if index.Official { diff --git a/cli/command/registry/login_test.go b/cli/command/registry/login_test.go index 2f5bc94a88bb..8a61c65fcb42 100644 --- a/cli/command/registry/login_test.go +++ b/cli/command/registry/login_test.go @@ -3,6 +3,7 @@ package registry import ( "bytes" "context" + "errors" "fmt" "testing" @@ -34,13 +35,13 @@ type fakeClient struct { client.Client } -func (c fakeClient) Info(ctx context.Context) (types.Info, error) { +func (c fakeClient) Info(context.Context) (types.Info, error) { return types.Info{}, nil } -func (c fakeClient) RegistryLogin(ctx context.Context, auth types.AuthConfig) (registrytypes.AuthenticateOKBody, error) { +func (c fakeClient) RegistryLogin(_ context.Context, auth types.AuthConfig) (registrytypes.AuthenticateOKBody, error) { if auth.Password == expiredPassword { - return registrytypes.AuthenticateOKBody{}, fmt.Errorf("Invalid Username or Password") + return registrytypes.AuthenticateOKBody{}, errors.New("Invalid Username or Password") } if auth.Password == useToken { return registrytypes.AuthenticateOKBody{ diff --git a/cli/command/registry_test.go b/cli/command/registry_test.go index b7d3e821166a..9ebe105f8339 100644 --- a/cli/command/registry_test.go +++ b/cli/command/registry_test.go @@ -3,19 +3,16 @@ package command_test import ( "bytes" "context" - "fmt" + "path/filepath" "testing" - "gotest.tools/v3/assert" - is "gotest.tools/v3/assert/cmp" - - // Prevents a circular import with "github.com/docker/cli/internal/test" - - . "github.com/docker/cli/cli/command" + "github.com/docker/cli/cli/command" configtypes "github.com/docker/cli/cli/config/types" "github.com/docker/cli/internal/test" "github.com/docker/docker/api/types" "github.com/docker/docker/client" + "gotest.tools/v3/assert" + is "gotest.tools/v3/assert/cmp" ) type fakeClient struct { @@ -47,13 +44,11 @@ func TestGetDefaultAuthConfig(t *testing.T) { testCases := []struct { checkCredStore bool inputServerAddress string - expectedErr string expectedAuthConfig types.AuthConfig }{ { checkCredStore: false, inputServerAddress: "", - expectedErr: "", expectedAuthConfig: types.AuthConfig{ ServerAddress: "", Username: "", @@ -63,38 +58,31 @@ func TestGetDefaultAuthConfig(t *testing.T) { { checkCredStore: true, inputServerAddress: testAuthConfigs[0].ServerAddress, - expectedErr: "", expectedAuthConfig: testAuthConfigs[0], }, { checkCredStore: true, inputServerAddress: testAuthConfigs[1].ServerAddress, - expectedErr: "", expectedAuthConfig: testAuthConfigs[1], }, { checkCredStore: true, - inputServerAddress: fmt.Sprintf("https://%s", testAuthConfigs[1].ServerAddress), - expectedErr: "", + inputServerAddress: "https://" + testAuthConfigs[1].ServerAddress, expectedAuthConfig: testAuthConfigs[1], }, } cli := test.NewFakeCli(&fakeClient{}) errBuf := new(bytes.Buffer) cli.SetErr(errBuf) + cli.ConfigFile().Filename = filepath.Join(t.TempDir(), "config") for _, authconfig := range testAuthConfigs { - cli.ConfigFile().GetCredentialsStore(authconfig.ServerAddress).Store(configtypes.AuthConfig(authconfig)) + assert.Check(t, cli.ConfigFile().GetCredentialsStore(authconfig.ServerAddress).Store(configtypes.AuthConfig(authconfig))) } for _, tc := range testCases { serverAddress := tc.inputServerAddress - authconfig, err := GetDefaultAuthConfig(cli, tc.checkCredStore, serverAddress, serverAddress == "https://index.docker.io/v1/") - if tc.expectedErr != "" { - assert.Check(t, err != nil) - assert.Check(t, is.Equal(tc.expectedErr, err.Error())) - } else { - assert.NilError(t, err) - assert.Check(t, is.DeepEqual(tc.expectedAuthConfig, authconfig)) - } + authconfig, err := command.GetDefaultAuthConfig(cli, tc.checkCredStore, serverAddress, serverAddress == "https://index.docker.io/v1/") + assert.NilError(t, err) + assert.Check(t, is.DeepEqual(tc.expectedAuthConfig, authconfig)) } } @@ -107,7 +95,8 @@ func TestGetDefaultAuthConfig_HelperError(t *testing.T) { expectedAuthConfig := types.AuthConfig{ ServerAddress: serverAddress, } - authconfig, err := GetDefaultAuthConfig(cli, true, serverAddress, serverAddress == "https://index.docker.io/v1/") + const isDefaultRegistry = false // registry is not "https://index.docker.io/v1/" + authconfig, err := command.GetDefaultAuthConfig(cli, true, serverAddress, isDefaultRegistry) assert.Check(t, is.DeepEqual(expectedAuthConfig, authconfig)) assert.Check(t, is.ErrorContains(err, "docker-credential-fake-does-not-exist")) } diff --git a/cli/command/secret/client_test.go b/cli/command/secret/client_test.go index ea672fa47386..efcbccfebacf 100644 --- a/cli/command/secret/client_test.go +++ b/cli/command/secret/client_test.go @@ -10,36 +10,36 @@ import ( type fakeClient struct { client.Client - secretCreateFunc func(swarm.SecretSpec) (types.SecretCreateResponse, error) - secretInspectFunc func(string) (swarm.Secret, []byte, error) - secretListFunc func(types.SecretListOptions) ([]swarm.Secret, error) - secretRemoveFunc func(string) error + secretCreateFunc func(context.Context, swarm.SecretSpec) (types.SecretCreateResponse, error) + secretInspectFunc func(context.Context, string) (swarm.Secret, []byte, error) + secretListFunc func(context.Context, types.SecretListOptions) ([]swarm.Secret, error) + secretRemoveFunc func(context.Context, string) error } func (c *fakeClient) SecretCreate(ctx context.Context, spec swarm.SecretSpec) (types.SecretCreateResponse, error) { if c.secretCreateFunc != nil { - return c.secretCreateFunc(spec) + return c.secretCreateFunc(ctx, spec) } return types.SecretCreateResponse{}, nil } func (c *fakeClient) SecretInspectWithRaw(ctx context.Context, id string) (swarm.Secret, []byte, error) { if c.secretInspectFunc != nil { - return c.secretInspectFunc(id) + return c.secretInspectFunc(ctx, id) } return swarm.Secret{}, nil, nil } func (c *fakeClient) SecretList(ctx context.Context, options types.SecretListOptions) ([]swarm.Secret, error) { if c.secretListFunc != nil { - return c.secretListFunc(options) + return c.secretListFunc(ctx, options) } return []swarm.Secret{}, nil } func (c *fakeClient) SecretRemove(ctx context.Context, name string) error { if c.secretRemoveFunc != nil { - return c.secretRemoveFunc(name) + return c.secretRemoveFunc(ctx, name) } return nil } diff --git a/cli/command/secret/create_test.go b/cli/command/secret/create_test.go index 2dd65edace43..6af3aa97f4ab 100644 --- a/cli/command/secret/create_test.go +++ b/cli/command/secret/create_test.go @@ -1,6 +1,7 @@ package secret import ( + "context" "io" "os" "path/filepath" @@ -21,7 +22,7 @@ const secretDataFile = "secret-create-with-name.golden" func TestSecretCreateErrors(t *testing.T) { testCases := []struct { args []string - secretCreateFunc func(swarm.SecretSpec) (types.SecretCreateResponse, error) + secretCreateFunc func(context.Context, swarm.SecretSpec) (types.SecretCreateResponse, error) expectedError string }{ { @@ -34,7 +35,7 @@ func TestSecretCreateErrors(t *testing.T) { }, { args: []string{"name", filepath.Join("testdata", secretDataFile)}, - secretCreateFunc: func(secretSpec swarm.SecretSpec) (types.SecretCreateResponse, error) { + secretCreateFunc: func(_ context.Context, secretSpec swarm.SecretSpec) (types.SecretCreateResponse, error) { return types.SecretCreateResponse{}, errors.Errorf("error creating secret") }, expectedError: "error creating secret", @@ -66,7 +67,7 @@ func TestSecretCreateWithName(t *testing.T) { } cli := test.NewFakeCli(&fakeClient{ - secretCreateFunc: func(spec swarm.SecretSpec) (types.SecretCreateResponse, error) { + secretCreateFunc: func(_ context.Context, spec swarm.SecretSpec) (types.SecretCreateResponse, error) { if !reflect.DeepEqual(spec, expected) { return types.SecretCreateResponse{}, errors.Errorf("expected %+v, got %+v", expected, spec) } @@ -89,7 +90,7 @@ func TestSecretCreateWithDriver(t *testing.T) { name := "foo" cli := test.NewFakeCli(&fakeClient{ - secretCreateFunc: func(spec swarm.SecretSpec) (types.SecretCreateResponse, error) { + secretCreateFunc: func(_ context.Context, spec swarm.SecretSpec) (types.SecretCreateResponse, error) { if spec.Name != name { return types.SecretCreateResponse{}, errors.Errorf("expected name %q, got %q", name, spec.Name) } @@ -118,7 +119,7 @@ func TestSecretCreateWithTemplatingDriver(t *testing.T) { name := "foo" cli := test.NewFakeCli(&fakeClient{ - secretCreateFunc: func(spec swarm.SecretSpec) (types.SecretCreateResponse, error) { + secretCreateFunc: func(_ context.Context, spec swarm.SecretSpec) (types.SecretCreateResponse, error) { if spec.Name != name { return types.SecretCreateResponse{}, errors.Errorf("expected name %q, got %q", name, spec.Name) } @@ -148,7 +149,7 @@ func TestSecretCreateWithLabels(t *testing.T) { name := "foo" cli := test.NewFakeCli(&fakeClient{ - secretCreateFunc: func(spec swarm.SecretSpec) (types.SecretCreateResponse, error) { + secretCreateFunc: func(_ context.Context, spec swarm.SecretSpec) (types.SecretCreateResponse, error) { if spec.Name != name { return types.SecretCreateResponse{}, errors.Errorf("expected name %q, got %q", name, spec.Name) } diff --git a/cli/command/secret/inspect_test.go b/cli/command/secret/inspect_test.go index e05311f2af6b..47a28cb8d010 100644 --- a/cli/command/secret/inspect_test.go +++ b/cli/command/secret/inspect_test.go @@ -1,13 +1,14 @@ package secret import ( + "context" "fmt" "io" "testing" "time" "github.com/docker/cli/internal/test" - . "github.com/docker/cli/internal/test/builders" // Import builders to get the builder function as package function + "github.com/docker/cli/internal/test/builders" "github.com/docker/docker/api/types/swarm" "github.com/pkg/errors" "gotest.tools/v3/assert" @@ -18,7 +19,7 @@ func TestSecretInspectErrors(t *testing.T) { testCases := []struct { args []string flags map[string]string - secretInspectFunc func(secretID string) (swarm.Secret, []byte, error) + secretInspectFunc func(ctx context.Context, secretID string) (swarm.Secret, []byte, error) expectedError string }{ { @@ -26,7 +27,7 @@ func TestSecretInspectErrors(t *testing.T) { }, { args: []string{"foo"}, - secretInspectFunc: func(secretID string) (swarm.Secret, []byte, error) { + secretInspectFunc: func(_ context.Context, secretID string) (swarm.Secret, []byte, error) { return swarm.Secret{}, nil, errors.Errorf("error while inspecting the secret") }, expectedError: "error while inspecting the secret", @@ -40,9 +41,9 @@ func TestSecretInspectErrors(t *testing.T) { }, { args: []string{"foo", "bar"}, - secretInspectFunc: func(secretID string) (swarm.Secret, []byte, error) { + secretInspectFunc: func(_ context.Context, secretID string) (swarm.Secret, []byte, error) { if secretID == "foo" { - return *Secret(SecretName("foo")), nil, nil + return *builders.Secret(builders.SecretName("foo")), nil, nil } return swarm.Secret{}, nil, errors.Errorf("error while inspecting the secret") }, @@ -57,7 +58,7 @@ func TestSecretInspectErrors(t *testing.T) { ) cmd.SetArgs(tc.args) for key, value := range tc.flags { - cmd.Flags().Set(key, value) + assert.Check(t, cmd.Flags().Set(key, value)) } cmd.SetOut(io.Discard) assert.ErrorContains(t, cmd.Execute(), tc.expectedError) @@ -68,23 +69,23 @@ func TestSecretInspectWithoutFormat(t *testing.T) { testCases := []struct { name string args []string - secretInspectFunc func(secretID string) (swarm.Secret, []byte, error) + secretInspectFunc func(ctx context.Context, secretID string) (swarm.Secret, []byte, error) }{ { name: "single-secret", args: []string{"foo"}, - secretInspectFunc: func(name string) (swarm.Secret, []byte, error) { + secretInspectFunc: func(_ context.Context, name string) (swarm.Secret, []byte, error) { if name != "foo" { return swarm.Secret{}, nil, errors.Errorf("Invalid name, expected %s, got %s", "foo", name) } - return *Secret(SecretID("ID-foo"), SecretName("foo")), nil, nil + return *builders.Secret(builders.SecretID("ID-foo"), builders.SecretName("foo")), nil, nil }, }, { name: "multiple-secrets-with-labels", args: []string{"foo", "bar"}, - secretInspectFunc: func(name string) (swarm.Secret, []byte, error) { - return *Secret(SecretID("ID-"+name), SecretName(name), SecretLabels(map[string]string{ + secretInspectFunc: func(_ context.Context, name string) (swarm.Secret, []byte, error) { + return *builders.Secret(builders.SecretID("ID-"+name), builders.SecretName(name), builders.SecretLabels(map[string]string{ "label1": "label-foo", })), nil, nil }, @@ -102,8 +103,8 @@ func TestSecretInspectWithoutFormat(t *testing.T) { } func TestSecretInspectWithFormat(t *testing.T) { - secretInspectFunc := func(name string) (swarm.Secret, []byte, error) { - return *Secret(SecretName("foo"), SecretLabels(map[string]string{ + secretInspectFunc := func(_ context.Context, name string) (swarm.Secret, []byte, error) { + return *builders.Secret(builders.SecretName("foo"), builders.SecretLabels(map[string]string{ "label1": "label-foo", })), nil, nil } @@ -111,7 +112,7 @@ func TestSecretInspectWithFormat(t *testing.T) { name string format string args []string - secretInspectFunc func(name string) (swarm.Secret, []byte, error) + secretInspectFunc func(_ context.Context, name string) (swarm.Secret, []byte, error) }{ { name: "simple-template", @@ -132,7 +133,7 @@ func TestSecretInspectWithFormat(t *testing.T) { }) cmd := newSecretInspectCommand(cli) cmd.SetArgs(tc.args) - cmd.Flags().Set("format", tc.format) + assert.Check(t, cmd.Flags().Set("format", tc.format)) assert.NilError(t, cmd.Execute()) golden.Assert(t, cli.OutBuffer().String(), fmt.Sprintf("secret-inspect-with-format.%s.golden", tc.name)) } @@ -141,20 +142,20 @@ func TestSecretInspectWithFormat(t *testing.T) { func TestSecretInspectPretty(t *testing.T) { testCases := []struct { name string - secretInspectFunc func(string) (swarm.Secret, []byte, error) + secretInspectFunc func(context.Context, string) (swarm.Secret, []byte, error) }{ { name: "simple", - secretInspectFunc: func(id string) (swarm.Secret, []byte, error) { - return *Secret( - SecretLabels(map[string]string{ + secretInspectFunc: func(_ context.Context, id string) (swarm.Secret, []byte, error) { + return *builders.Secret( + builders.SecretLabels(map[string]string{ "lbl1": "value1", }), - SecretID("secretID"), - SecretName("secretName"), - SecretDriver("driver"), - SecretCreatedAt(time.Time{}), - SecretUpdatedAt(time.Time{}), + builders.SecretID("secretID"), + builders.SecretName("secretName"), + builders.SecretDriver("driver"), + builders.SecretCreatedAt(time.Time{}), + builders.SecretUpdatedAt(time.Time{}), ), []byte{}, nil }, }, @@ -165,7 +166,7 @@ func TestSecretInspectPretty(t *testing.T) { }) cmd := newSecretInspectCommand(cli) cmd.SetArgs([]string{"secretID"}) - cmd.Flags().Set("pretty", "true") + assert.Check(t, cmd.Flags().Set("pretty", "true")) assert.NilError(t, cmd.Execute()) golden.Assert(t, cli.OutBuffer().String(), fmt.Sprintf("secret-inspect-pretty.%s.golden", tc.name)) } diff --git a/cli/command/secret/ls_test.go b/cli/command/secret/ls_test.go index 4c8b635fd11f..0579612b96a9 100644 --- a/cli/command/secret/ls_test.go +++ b/cli/command/secret/ls_test.go @@ -1,13 +1,14 @@ package secret import ( + "context" "io" "testing" "time" "github.com/docker/cli/cli/config/configfile" "github.com/docker/cli/internal/test" - . "github.com/docker/cli/internal/test/builders" // Import builders to get the builder function as package function + "github.com/docker/cli/internal/test/builders" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/swarm" "github.com/pkg/errors" @@ -19,7 +20,7 @@ import ( func TestSecretListErrors(t *testing.T) { testCases := []struct { args []string - secretListFunc func(types.SecretListOptions) ([]swarm.Secret, error) + secretListFunc func(context.Context, types.SecretListOptions) ([]swarm.Secret, error) expectedError string }{ { @@ -27,7 +28,7 @@ func TestSecretListErrors(t *testing.T) { expectedError: "accepts no argument", }, { - secretListFunc: func(options types.SecretListOptions) ([]swarm.Secret, error) { + secretListFunc: func(_ context.Context, options types.SecretListOptions) ([]swarm.Secret, error) { return []swarm.Secret{}, errors.Errorf("error listing secrets") }, expectedError: "error listing secrets", @@ -47,27 +48,27 @@ func TestSecretListErrors(t *testing.T) { func TestSecretList(t *testing.T) { cli := test.NewFakeCli(&fakeClient{ - secretListFunc: func(options types.SecretListOptions) ([]swarm.Secret, error) { + secretListFunc: func(_ context.Context, options types.SecretListOptions) ([]swarm.Secret, error) { return []swarm.Secret{ - *Secret(SecretID("ID-1-foo"), - SecretName("1-foo"), - SecretVersion(swarm.Version{Index: 10}), - SecretCreatedAt(time.Now().Add(-2*time.Hour)), - SecretUpdatedAt(time.Now().Add(-1*time.Hour)), + *builders.Secret(builders.SecretID("ID-1-foo"), + builders.SecretName("1-foo"), + builders.SecretVersion(swarm.Version{Index: 10}), + builders.SecretCreatedAt(time.Now().Add(-2*time.Hour)), + builders.SecretUpdatedAt(time.Now().Add(-1*time.Hour)), ), - *Secret(SecretID("ID-10-foo"), - SecretName("10-foo"), - SecretVersion(swarm.Version{Index: 11}), - SecretCreatedAt(time.Now().Add(-2*time.Hour)), - SecretUpdatedAt(time.Now().Add(-1*time.Hour)), - SecretDriver("driver"), + *builders.Secret(builders.SecretID("ID-10-foo"), + builders.SecretName("10-foo"), + builders.SecretVersion(swarm.Version{Index: 11}), + builders.SecretCreatedAt(time.Now().Add(-2*time.Hour)), + builders.SecretUpdatedAt(time.Now().Add(-1*time.Hour)), + builders.SecretDriver("driver"), ), - *Secret(SecretID("ID-2-foo"), - SecretName("2-foo"), - SecretVersion(swarm.Version{Index: 11}), - SecretCreatedAt(time.Now().Add(-2*time.Hour)), - SecretUpdatedAt(time.Now().Add(-1*time.Hour)), - SecretDriver("driver"), + *builders.Secret(builders.SecretID("ID-2-foo"), + builders.SecretName("2-foo"), + builders.SecretVersion(swarm.Version{Index: 11}), + builders.SecretCreatedAt(time.Now().Add(-2*time.Hour)), + builders.SecretUpdatedAt(time.Now().Add(-1*time.Hour)), + builders.SecretDriver("driver"), ), }, nil }, @@ -79,27 +80,27 @@ func TestSecretList(t *testing.T) { func TestSecretListWithQuietOption(t *testing.T) { cli := test.NewFakeCli(&fakeClient{ - secretListFunc: func(options types.SecretListOptions) ([]swarm.Secret, error) { + secretListFunc: func(_ context.Context, options types.SecretListOptions) ([]swarm.Secret, error) { return []swarm.Secret{ - *Secret(SecretID("ID-foo"), SecretName("foo")), - *Secret(SecretID("ID-bar"), SecretName("bar"), SecretLabels(map[string]string{ + *builders.Secret(builders.SecretID("ID-foo"), builders.SecretName("foo")), + *builders.Secret(builders.SecretID("ID-bar"), builders.SecretName("bar"), builders.SecretLabels(map[string]string{ "label": "label-bar", })), }, nil }, }) cmd := newSecretListCommand(cli) - cmd.Flags().Set("quiet", "true") + assert.Check(t, cmd.Flags().Set("quiet", "true")) assert.NilError(t, cmd.Execute()) golden.Assert(t, cli.OutBuffer().String(), "secret-list-with-quiet-option.golden") } func TestSecretListWithConfigFormat(t *testing.T) { cli := test.NewFakeCli(&fakeClient{ - secretListFunc: func(options types.SecretListOptions) ([]swarm.Secret, error) { + secretListFunc: func(_ context.Context, options types.SecretListOptions) ([]swarm.Secret, error) { return []swarm.Secret{ - *Secret(SecretID("ID-foo"), SecretName("foo")), - *Secret(SecretID("ID-bar"), SecretName("bar"), SecretLabels(map[string]string{ + *builders.Secret(builders.SecretID("ID-foo"), builders.SecretName("foo")), + *builders.Secret(builders.SecretID("ID-bar"), builders.SecretName("bar"), builders.SecretLabels(map[string]string{ "label": "label-bar", })), }, nil @@ -115,45 +116,45 @@ func TestSecretListWithConfigFormat(t *testing.T) { func TestSecretListWithFormat(t *testing.T) { cli := test.NewFakeCli(&fakeClient{ - secretListFunc: func(options types.SecretListOptions) ([]swarm.Secret, error) { + secretListFunc: func(_ context.Context, options types.SecretListOptions) ([]swarm.Secret, error) { return []swarm.Secret{ - *Secret(SecretID("ID-foo"), SecretName("foo")), - *Secret(SecretID("ID-bar"), SecretName("bar"), SecretLabels(map[string]string{ + *builders.Secret(builders.SecretID("ID-foo"), builders.SecretName("foo")), + *builders.Secret(builders.SecretID("ID-bar"), builders.SecretName("bar"), builders.SecretLabels(map[string]string{ "label": "label-bar", })), }, nil }, }) cmd := newSecretListCommand(cli) - cmd.Flags().Set("format", "{{ .Name }} {{ .Labels }}") + assert.Check(t, cmd.Flags().Set("format", "{{ .Name }} {{ .Labels }}")) assert.NilError(t, cmd.Execute()) golden.Assert(t, cli.OutBuffer().String(), "secret-list-with-format.golden") } func TestSecretListWithFilter(t *testing.T) { cli := test.NewFakeCli(&fakeClient{ - secretListFunc: func(options types.SecretListOptions) ([]swarm.Secret, error) { + secretListFunc: func(_ context.Context, options types.SecretListOptions) ([]swarm.Secret, error) { assert.Check(t, is.Equal("foo", options.Filters.Get("name")[0]), "foo") assert.Check(t, is.Equal("lbl1=Label-bar", options.Filters.Get("label")[0])) return []swarm.Secret{ - *Secret(SecretID("ID-foo"), - SecretName("foo"), - SecretVersion(swarm.Version{Index: 10}), - SecretCreatedAt(time.Now().Add(-2*time.Hour)), - SecretUpdatedAt(time.Now().Add(-1*time.Hour)), + *builders.Secret(builders.SecretID("ID-foo"), + builders.SecretName("foo"), + builders.SecretVersion(swarm.Version{Index: 10}), + builders.SecretCreatedAt(time.Now().Add(-2*time.Hour)), + builders.SecretUpdatedAt(time.Now().Add(-1*time.Hour)), ), - *Secret(SecretID("ID-bar"), - SecretName("bar"), - SecretVersion(swarm.Version{Index: 11}), - SecretCreatedAt(time.Now().Add(-2*time.Hour)), - SecretUpdatedAt(time.Now().Add(-1*time.Hour)), + *builders.Secret(builders.SecretID("ID-bar"), + builders.SecretName("bar"), + builders.SecretVersion(swarm.Version{Index: 11}), + builders.SecretCreatedAt(time.Now().Add(-2*time.Hour)), + builders.SecretUpdatedAt(time.Now().Add(-1*time.Hour)), ), }, nil }, }) cmd := newSecretListCommand(cli) - cmd.Flags().Set("filter", "name=foo") - cmd.Flags().Set("filter", "label=lbl1=Label-bar") + assert.Check(t, cmd.Flags().Set("filter", "name=foo")) + assert.Check(t, cmd.Flags().Set("filter", "label=lbl1=Label-bar")) assert.NilError(t, cmd.Execute()) golden.Assert(t, cli.OutBuffer().String(), "secret-list-with-filter.golden") } diff --git a/cli/command/secret/remove_test.go b/cli/command/secret/remove_test.go index c9f24dc22bc1..8600a98fd314 100644 --- a/cli/command/secret/remove_test.go +++ b/cli/command/secret/remove_test.go @@ -1,6 +1,7 @@ package secret import ( + "context" "io" "strings" "testing" @@ -14,7 +15,7 @@ import ( func TestSecretRemoveErrors(t *testing.T) { testCases := []struct { args []string - secretRemoveFunc func(string) error + secretRemoveFunc func(context.Context, string) error expectedError string }{ { @@ -23,7 +24,7 @@ func TestSecretRemoveErrors(t *testing.T) { }, { args: []string{"foo"}, - secretRemoveFunc: func(name string) error { + secretRemoveFunc: func(_ context.Context, name string) error { return errors.Errorf("error removing secret") }, expectedError: "error removing secret", @@ -45,7 +46,7 @@ func TestSecretRemoveWithName(t *testing.T) { names := []string{"foo", "bar"} var removedSecrets []string cli := test.NewFakeCli(&fakeClient{ - secretRemoveFunc: func(name string) error { + secretRemoveFunc: func(_ context.Context, name string) error { removedSecrets = append(removedSecrets, name) return nil }, @@ -62,7 +63,7 @@ func TestSecretRemoveContinueAfterError(t *testing.T) { var removedSecrets []string cli := test.NewFakeCli(&fakeClient{ - secretRemoveFunc: func(name string) error { + secretRemoveFunc: func(_ context.Context, name string) error { removedSecrets = append(removedSecrets, name) if name == "foo" { return errors.Errorf("error removing secret: %s", name) diff --git a/cli/command/service/client_test.go b/cli/command/service/client_test.go index d00afe2ca22a..5e81a58cc7fe 100644 --- a/cli/command/service/client_test.go +++ b/cli/command/service/client_test.go @@ -3,7 +3,7 @@ package service import ( "context" - . "github.com/docker/cli/internal/test/builders" // Import builders to get the builder function as package function + "github.com/docker/cli/internal/test/builders" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/client" @@ -39,7 +39,7 @@ func (f *fakeClient) ServiceInspectWithRaw(ctx context.Context, serviceID string return f.serviceInspectWithRawFunc(ctx, serviceID, options) } - return *Service(ServiceID(serviceID)), []byte{}, nil + return *builders.Service(builders.ServiceID(serviceID)), []byte{}, nil } func (f *fakeClient) ServiceList(ctx context.Context, options types.ServiceListOptions) ([]swarm.Service, error) { @@ -73,5 +73,5 @@ func (f *fakeClient) NetworkInspect(ctx context.Context, networkID string, optio } func newService(id string, name string) swarm.Service { - return *Service(ServiceID(id), ServiceName(name)) + return *builders.Service(builders.ServiceID(id), builders.ServiceName(name)) } diff --git a/cli/command/service/list_test.go b/cli/command/service/list_test.go index 576ecc34ee57..6a50210ec58f 100644 --- a/cli/command/service/list_test.go +++ b/cli/command/service/list_test.go @@ -8,8 +8,7 @@ import ( "testing" "github.com/docker/cli/internal/test" - // Import builders to get the builder function as package function - . "github.com/docker/cli/internal/test/builders" + "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" @@ -30,7 +29,7 @@ func TestServiceListOrder(t *testing.T) { }) cmd := newListCommand(cli) cmd.SetArgs([]string{}) - cmd.Flags().Set("format", "{{.Name}}") + assert.Check(t, cmd.Flags().Set("format", "{{.Name}}")) assert.NilError(t, cmd.Execute()) golden.Assert(t, cli.OutBuffer().String(), "service-list-sort.golden") } @@ -248,21 +247,21 @@ func generateServices(t *testing.T, opts clusterOpts) []swarm.Service { } return []swarm.Service{ - *Service( - ServiceID("replicated"), - ServiceName("01-replicated-service"), - ReplicatedService(opts.desiredTasks), - ServiceStatus(opts.desiredTasks, opts.runningTasks), + *builders.Service( + builders.ServiceID("replicated"), + builders.ServiceName("01-replicated-service"), + builders.ReplicatedService(opts.desiredTasks), + builders.ServiceStatus(opts.desiredTasks, opts.runningTasks), ), - *Service( - ServiceID("global"), - ServiceName("02-global-service"), - GlobalService(), - ServiceStatus(opts.activeNodes, globalTasks), + *builders.Service( + builders.ServiceID("global"), + builders.ServiceName("02-global-service"), + builders.GlobalService(), + builders.ServiceStatus(opts.activeNodes, globalTasks), ), - *Service( - ServiceID("none-id"), - ServiceName("03-none-service"), + *builders.Service( + builders.ServiceID("none-id"), + builders.ServiceName("03-none-service"), ), } } diff --git a/cli/command/service/logs.go b/cli/command/service/logs.go index 215bad74d9cd..90252bf0ed69 100644 --- a/cli/command/service/logs.go +++ b/cli/command/service/logs.go @@ -214,9 +214,9 @@ func (f *taskFormatter) format(ctx context.Context, logCtx logContext) (string, taskName := fmt.Sprintf("%s.%d", serviceName, task.Slot) if !f.opts.noTaskIDs { if f.opts.noTrunc { - taskName += fmt.Sprintf(".%s", task.ID) + taskName += "." + task.ID } else { - taskName += fmt.Sprintf(".%s", stringid.TruncateID(task.ID)) + taskName += "." + stringid.TruncateID(task.ID) } } diff --git a/cli/command/service/opts.go b/cli/command/service/opts.go index 97201fd432de..2149241af26b 100644 --- a/cli/command/service/opts.go +++ b/cli/command/service/opts.go @@ -1001,7 +1001,7 @@ const ( flagTTY = "tty" flagUpdateDelay = "update-delay" flagUpdateFailureAction = "update-failure-action" - flagUpdateMaxFailureRatio = "update-max-failure-ratio" + flagUpdateMaxFailureRatio = "update-max-failure-ratio" // #nosec G101 -- ignoring: Potential hardcoded credentials (gosec) flagUpdateMonitor = "update-monitor" flagUpdateOrder = "update-order" flagUpdateParallelism = "update-parallelism" diff --git a/cli/command/service/progress/progress.go b/cli/command/service/progress/progress.go index aa8a293d2182..d6f049f91b0a 100644 --- a/cli/command/service/progress/progress.go +++ b/cli/command/service/progress/progress.go @@ -414,7 +414,7 @@ type globalProgressUpdater struct { done bool } -func (u *globalProgressUpdater) update(service swarm.Service, tasks []swarm.Task, activeNodes map[string]struct{}, rollback bool) (bool, error) { +func (u *globalProgressUpdater) update(_ swarm.Service, tasks []swarm.Task, activeNodes map[string]struct{}, rollback bool) (bool, error) { tasksByNode := u.tasksByNode(tasks) // We don't have perfect knowledge of how many nodes meet the diff --git a/cli/command/service/progress/progress_test.go b/cli/command/service/progress/progress_test.go index 64cbde99cbd1..2edad317f42e 100644 --- a/cli/command/service/progress/progress_test.go +++ b/cli/command/service/progress/progress_test.go @@ -874,7 +874,7 @@ func TestGlobalJobProgressUpdaterLarge(t *testing.T) { tasks := []swarm.Task{} for nodeID := range activeNodes { tasks = append(tasks, swarm.Task{ - ID: fmt.Sprintf("task%s", nodeID), + ID: "task" + nodeID, NodeID: nodeID, DesiredState: swarm.TaskStateComplete, Status: swarm.TaskStatus{ diff --git a/cli/command/service/update_test.go b/cli/command/service/update_test.go index 91ba4ff38122..a6e0e106791a 100644 --- a/cli/command/service/update_test.go +++ b/cli/command/service/update_test.go @@ -504,23 +504,23 @@ type secretAPIClientMock struct { listResult []swarm.Secret } -func (s secretAPIClientMock) SecretList(ctx context.Context, options types.SecretListOptions) ([]swarm.Secret, error) { +func (s secretAPIClientMock) SecretList(context.Context, types.SecretListOptions) ([]swarm.Secret, error) { return s.listResult, nil } -func (s secretAPIClientMock) SecretCreate(ctx context.Context, secret swarm.SecretSpec) (types.SecretCreateResponse, error) { +func (s secretAPIClientMock) SecretCreate(context.Context, swarm.SecretSpec) (types.SecretCreateResponse, error) { return types.SecretCreateResponse{}, nil } -func (s secretAPIClientMock) SecretRemove(ctx context.Context, id string) error { +func (s secretAPIClientMock) SecretRemove(context.Context, string) error { return nil } -func (s secretAPIClientMock) SecretInspectWithRaw(ctx context.Context, name string) (swarm.Secret, []byte, error) { +func (s secretAPIClientMock) SecretInspectWithRaw(context.Context, string) (swarm.Secret, []byte, error) { return swarm.Secret{}, []byte{}, nil } -func (s secretAPIClientMock) SecretUpdate(ctx context.Context, id string, version swarm.Version, secret swarm.SecretSpec) error { +func (s secretAPIClientMock) SecretUpdate(context.Context, string, swarm.Version, swarm.SecretSpec) error { return nil } diff --git a/cli/command/stack/client_test.go b/cli/command/stack/client_test.go index a4f95cf308a1..6a2d0a900ac9 100644 --- a/cli/command/stack/client_test.go +++ b/cli/command/stack/client_test.go @@ -43,7 +43,7 @@ type fakeClient struct { configRemoveFunc func(configID string) error } -func (cli *fakeClient) ServerVersion(ctx context.Context) (types.Version, error) { +func (cli *fakeClient) ServerVersion(context.Context) (types.Version, error) { return types.Version{ Version: "docker-dev", APIVersion: api.DefaultVersion, @@ -54,7 +54,7 @@ func (cli *fakeClient) ClientVersion() string { return cli.version } -func (cli *fakeClient) ServiceList(ctx context.Context, options types.ServiceListOptions) ([]swarm.Service, error) { +func (cli *fakeClient) ServiceList(_ context.Context, options types.ServiceListOptions) ([]swarm.Service, error) { if cli.serviceListFunc != nil { return cli.serviceListFunc(options) } @@ -69,7 +69,7 @@ func (cli *fakeClient) ServiceList(ctx context.Context, options types.ServiceLis return servicesList, nil } -func (cli *fakeClient) NetworkList(ctx context.Context, options types.NetworkListOptions) ([]types.NetworkResource, error) { +func (cli *fakeClient) NetworkList(_ context.Context, options types.NetworkListOptions) ([]types.NetworkResource, error) { if cli.networkListFunc != nil { return cli.networkListFunc(options) } @@ -84,7 +84,7 @@ func (cli *fakeClient) NetworkList(ctx context.Context, options types.NetworkLis return networksList, nil } -func (cli *fakeClient) SecretList(ctx context.Context, options types.SecretListOptions) ([]swarm.Secret, error) { +func (cli *fakeClient) SecretList(_ context.Context, options types.SecretListOptions) ([]swarm.Secret, error) { if cli.secretListFunc != nil { return cli.secretListFunc(options) } @@ -99,7 +99,7 @@ func (cli *fakeClient) SecretList(ctx context.Context, options types.SecretListO return secretsList, nil } -func (cli *fakeClient) ConfigList(ctx context.Context, options types.ConfigListOptions) ([]swarm.Config, error) { +func (cli *fakeClient) ConfigList(_ context.Context, options types.ConfigListOptions) ([]swarm.Config, error) { if cli.configListFunc != nil { return cli.configListFunc(options) } @@ -114,28 +114,28 @@ func (cli *fakeClient) ConfigList(ctx context.Context, options types.ConfigListO return configsList, nil } -func (cli *fakeClient) TaskList(ctx context.Context, options types.TaskListOptions) ([]swarm.Task, error) { +func (cli *fakeClient) TaskList(_ context.Context, options types.TaskListOptions) ([]swarm.Task, error) { if cli.taskListFunc != nil { return cli.taskListFunc(options) } return []swarm.Task{}, nil } -func (cli *fakeClient) NodeList(ctx context.Context, options types.NodeListOptions) ([]swarm.Node, error) { +func (cli *fakeClient) NodeList(_ context.Context, options types.NodeListOptions) ([]swarm.Node, error) { if cli.nodeListFunc != nil { return cli.nodeListFunc(options) } return []swarm.Node{}, nil } -func (cli *fakeClient) NodeInspectWithRaw(ctx context.Context, ref string) (swarm.Node, []byte, error) { +func (cli *fakeClient) NodeInspectWithRaw(_ context.Context, ref string) (swarm.Node, []byte, error) { if cli.nodeInspectWithRaw != nil { return cli.nodeInspectWithRaw(ref) } return swarm.Node{}, nil, nil } -func (cli *fakeClient) ServiceUpdate(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (types.ServiceUpdateResponse, error) { +func (cli *fakeClient) ServiceUpdate(_ context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (types.ServiceUpdateResponse, error) { if cli.serviceUpdateFunc != nil { return cli.serviceUpdateFunc(serviceID, version, service, options) } @@ -143,7 +143,7 @@ func (cli *fakeClient) ServiceUpdate(ctx context.Context, serviceID string, vers return types.ServiceUpdateResponse{}, nil } -func (cli *fakeClient) ServiceRemove(ctx context.Context, serviceID string) error { +func (cli *fakeClient) ServiceRemove(_ context.Context, serviceID string) error { if cli.serviceRemoveFunc != nil { return cli.serviceRemoveFunc(serviceID) } @@ -152,7 +152,7 @@ func (cli *fakeClient) ServiceRemove(ctx context.Context, serviceID string) erro return nil } -func (cli *fakeClient) NetworkRemove(ctx context.Context, networkID string) error { +func (cli *fakeClient) NetworkRemove(_ context.Context, networkID string) error { if cli.networkRemoveFunc != nil { return cli.networkRemoveFunc(networkID) } @@ -161,7 +161,7 @@ func (cli *fakeClient) NetworkRemove(ctx context.Context, networkID string) erro return nil } -func (cli *fakeClient) SecretRemove(ctx context.Context, secretID string) error { +func (cli *fakeClient) SecretRemove(_ context.Context, secretID string) error { if cli.secretRemoveFunc != nil { return cli.secretRemoveFunc(secretID) } @@ -170,7 +170,7 @@ func (cli *fakeClient) SecretRemove(ctx context.Context, secretID string) error return nil } -func (cli *fakeClient) ConfigRemove(ctx context.Context, configID string) error { +func (cli *fakeClient) ConfigRemove(_ context.Context, configID string) error { if cli.configRemoveFunc != nil { return cli.configRemoveFunc(configID) } @@ -179,7 +179,7 @@ func (cli *fakeClient) ConfigRemove(ctx context.Context, configID string) error return nil } -func (cli *fakeClient) ServiceInspectWithRaw(ctx context.Context, serviceID string, opts types.ServiceInspectOptions) (swarm.Service, []byte, error) { +func (cli *fakeClient) ServiceInspectWithRaw(_ context.Context, serviceID string, _ types.ServiceInspectOptions) (swarm.Service, []byte, error) { return swarm.Service{ ID: serviceID, Spec: swarm.ServiceSpec{ diff --git a/cli/command/stack/deploy.go b/cli/command/stack/deploy.go index 1c9643e3eff3..8b977ad0a1ce 100644 --- a/cli/command/stack/deploy.go +++ b/cli/command/stack/deploy.go @@ -28,7 +28,7 @@ func newDeployCommand(dockerCli command.Cli) *cobra.Command { if err != nil { return err } - return RunDeploy(dockerCli, cmd.Flags(), config, opts) + return swarm.RunDeploy(dockerCli, opts, config) }, ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { return completeNames(dockerCli)(cmd, args, toComplete) @@ -47,7 +47,9 @@ func newDeployCommand(dockerCli command.Cli) *cobra.Command { return cmd } -// RunDeploy performs a stack deploy against the specified swarm cluster -func RunDeploy(dockerCli command.Cli, flags *pflag.FlagSet, config *composetypes.Config, opts options.Deploy) error { +// RunDeploy performs a stack deploy against the specified swarm cluster. +// +// Deprecated: use [swarm.RunDeploy] instead. +func RunDeploy(dockerCli command.Cli, _ *pflag.FlagSet, config *composetypes.Config, opts options.Deploy) error { return swarm.RunDeploy(dockerCli, opts, config) } diff --git a/cli/command/stack/list.go b/cli/command/stack/list.go index 40c1898cc6f9..fc5b03d6510c 100644 --- a/cli/command/stack/list.go +++ b/cli/command/stack/list.go @@ -23,7 +23,7 @@ func newListCommand(dockerCli command.Cli) *cobra.Command { Short: "List stacks", Args: cli.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - return RunList(cmd, dockerCli, opts) + return RunList(dockerCli, opts) }, ValidArgsFunction: completion.NoComplete, } @@ -34,24 +34,24 @@ func newListCommand(dockerCli command.Cli) *cobra.Command { } // RunList performs a stack list against the specified swarm cluster -func RunList(cmd *cobra.Command, dockerCli command.Cli, opts options.List) error { - stacks := []*formatter.Stack{} +func RunList(dockerCli command.Cli, opts options.List) error { ss, err := swarm.GetStacks(dockerCli) if err != nil { return err } + stacks := make([]*formatter.Stack, 0, len(ss)) stacks = append(stacks, ss...) return format(dockerCli, opts, stacks) } func format(dockerCli command.Cli, opts options.List, stacks []*formatter.Stack) error { - format := formatter.Format(opts.Format) - if format == "" || format == formatter.TableFormatKey { - format = formatter.SwarmStackTableFormat + fmt := formatter.Format(opts.Format) + if fmt == "" || fmt == formatter.TableFormatKey { + fmt = formatter.SwarmStackTableFormat } stackCtx := formatter.Context{ Output: dockerCli.Out(), - Format: format, + Format: fmt, } sort.Slice(stacks, func(i, j int) bool { return sortorder.NaturalLess(stacks[i].Name, stacks[j].Name) || diff --git a/cli/command/stack/list_test.go b/cli/command/stack/list_test.go index 3d18f894d907..f4c2d41b3c01 100644 --- a/cli/command/stack/list_test.go +++ b/cli/command/stack/list_test.go @@ -5,7 +5,7 @@ import ( "testing" "github.com/docker/cli/internal/test" - . "github.com/docker/cli/internal/test/builders" // Import builders to get the builder function as package function + "github.com/docker/cli/internal/test/builders" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/swarm" "github.com/pkg/errors" @@ -38,7 +38,7 @@ func TestListErrors(t *testing.T) { }, { serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) { - return []swarm.Service{*Service()}, nil + return []swarm.Service{*builders.Service()}, nil }, expectedError: "cannot get label", }, @@ -51,7 +51,7 @@ func TestListErrors(t *testing.T) { cmd.SetArgs(tc.args) cmd.SetOut(io.Discard) for key, value := range tc.flags { - cmd.Flags().Set(key, value) + assert.Check(t, cmd.Flags().Set(key, value)) } assert.ErrorContains(t, cmd.Execute(), tc.expectedError) } @@ -101,8 +101,8 @@ func TestStackList(t *testing.T) { var services []swarm.Service for _, name := range tc.serviceNames { services = append(services, - *Service( - ServiceLabels(map[string]string{ + *builders.Service( + builders.ServiceLabels(map[string]string{ "com.docker.stack.namespace": name, }), ), @@ -115,7 +115,7 @@ func TestStackList(t *testing.T) { }) cmd := newListCommand(cli) for key, value := range tc.flags { - cmd.Flags().Set(key, value) + assert.Check(t, cmd.Flags().Set(key, value)) } assert.NilError(t, cmd.Execute()) golden.Assert(t, cli.OutBuffer().String(), tc.golden) diff --git a/cli/command/stack/loader/loader.go b/cli/command/stack/loader/loader.go index 105e84e64491..39810d883285 100644 --- a/cli/command/stack/loader/loader.go +++ b/cli/command/stack/loader/loader.go @@ -5,6 +5,7 @@ import ( "io" "os" "path/filepath" + "runtime" "sort" "strings" @@ -104,9 +105,22 @@ func GetConfigDetails(composefiles []string, stdin io.Reader) (composetypes.Conf func buildEnvironment(env []string) (map[string]string, error) { result := make(map[string]string, len(env)) for _, s := range env { + if runtime.GOOS == "windows" && len(s) > 0 { + // cmd.exe can have special environment variables which names start with "=". + // They are only there for MS-DOS compatibility and we should ignore them. + // See TestBuildEnvironment for examples. + // + // https://ss64.com/nt/syntax-variables.html + // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133 + // https://github.com/docker/cli/issues/4078 + if s[0] == '=' { + continue + } + } + k, v, ok := strings.Cut(s, "=") if !ok || k == "" { - return result, errors.Errorf("unexpected environment %q", s) + return result, errors.Errorf("unexpected environment variable '%s'", s) } // value may be set, but empty if "s" is like "K=", not "K". result[k] = v diff --git a/cli/command/stack/loader/loader_test.go b/cli/command/stack/loader/loader_test.go index 6ddca65bb855..6c0da17aa339 100644 --- a/cli/command/stack/loader/loader_test.go +++ b/cli/command/stack/loader/loader_test.go @@ -3,6 +3,7 @@ package loader import ( "os" "path/filepath" + "runtime" "strings" "testing" @@ -45,3 +46,34 @@ services: assert.Check(t, is.Equal("3.0", details.ConfigFiles[0].Config["version"])) assert.Check(t, is.Len(details.Environment, len(os.Environ()))) } + +func TestBuildEnvironment(t *testing.T) { + inputEnv := []string{ + "LEGIT_VAR=LEGIT_VALUE", + "EMPTY_VARIABLE=", + } + + if runtime.GOOS == "windows" { + inputEnv = []string{ + "LEGIT_VAR=LEGIT_VALUE", + + // cmd.exe has some special environment variables which start with "=". + // These should be ignored as they're only there for MS-DOS compatibility. + "=ExitCode=00000041", + "=ExitCodeAscii=A", + `=C:=C:\some\dir`, + `=D:=D:\some\different\dir`, + `=X:=X:\`, + `=::=::\`, + + "EMPTY_VARIABLE=", + } + } + + env, err := buildEnvironment(inputEnv) + assert.NilError(t, err) + + assert.Check(t, is.Len(env, 2)) + assert.Check(t, is.Equal("LEGIT_VALUE", env["LEGIT_VAR"])) + assert.Check(t, is.Equal("", env["EMPTY_VARIABLE"])) +} diff --git a/cli/command/stack/ps.go b/cli/command/stack/ps.go index 84c250ea9174..f962b895a7f2 100644 --- a/cli/command/stack/ps.go +++ b/cli/command/stack/ps.go @@ -23,7 +23,7 @@ func newPsCommand(dockerCli command.Cli) *cobra.Command { if err := validateStackName(opts.Namespace); err != nil { return err } - return RunPs(dockerCli, cmd.Flags(), opts) + return swarm.RunPS(dockerCli, opts) }, ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { return completeNames(dockerCli)(cmd, args, toComplete) @@ -38,7 +38,9 @@ func newPsCommand(dockerCli command.Cli) *cobra.Command { return cmd } -// RunPs performs a stack ps against the specified swarm cluster -func RunPs(dockerCli command.Cli, flags *pflag.FlagSet, opts options.PS) error { +// RunPs performs a stack ps against the specified swarm cluster. +// +// Deprecated: use [swarm.RunPS] instead. +func RunPs(dockerCli command.Cli, _ *pflag.FlagSet, opts options.PS) error { return swarm.RunPS(dockerCli, opts) } diff --git a/cli/command/stack/ps_test.go b/cli/command/stack/ps_test.go index 674ab9018db6..a6b87393608a 100644 --- a/cli/command/stack/ps_test.go +++ b/cli/command/stack/ps_test.go @@ -7,7 +7,7 @@ import ( "github.com/docker/cli/cli/config/configfile" "github.com/docker/cli/internal/test" - . "github.com/docker/cli/internal/test/builders" // Import builders to get the builder function as package function + "github.com/docker/cli/internal/test/builders" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/swarm" "github.com/pkg/errors" @@ -76,7 +76,7 @@ func TestStackPs(t *testing.T) { { doc: "WithQuietOption", taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) { - return []swarm.Task{*Task(TaskID("id-foo"))}, nil + return []swarm.Task{*builders.Task(builders.TaskID("id-foo"))}, nil }, args: []string{"foo"}, flags: map[string]string{ @@ -87,7 +87,7 @@ func TestStackPs(t *testing.T) { { doc: "WithNoTruncOption", taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) { - return []swarm.Task{*Task(TaskID("xn4cypcov06f2w8gsbaf2lst3"))}, nil + return []swarm.Task{*builders.Task(builders.TaskID("xn4cypcov06f2w8gsbaf2lst3"))}, nil }, args: []string{"foo"}, flags: map[string]string{ @@ -99,12 +99,12 @@ func TestStackPs(t *testing.T) { { doc: "WithNoResolveOption", taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) { - return []swarm.Task{*Task( - TaskNodeID("id-node-foo"), + return []swarm.Task{*builders.Task( + builders.TaskNodeID("id-node-foo"), )}, nil }, nodeInspectWithRaw: func(ref string) (swarm.Node, []byte, error) { - return *Node(NodeName("node-name-bar")), nil, nil + return *builders.Node(builders.NodeName("node-name-bar")), nil, nil }, args: []string{"foo"}, flags: map[string]string{ @@ -116,7 +116,7 @@ func TestStackPs(t *testing.T) { { doc: "WithFormat", taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) { - return []swarm.Task{*Task(TaskServiceID("service-id-foo"))}, nil + return []swarm.Task{*builders.Task(builders.TaskServiceID("service-id-foo"))}, nil }, args: []string{"foo"}, flags: map[string]string{ @@ -127,7 +127,7 @@ func TestStackPs(t *testing.T) { { doc: "WithConfigFormat", taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) { - return []swarm.Task{*Task(TaskServiceID("service-id-foo"))}, nil + return []swarm.Task{*builders.Task(builders.TaskServiceID("service-id-foo"))}, nil }, config: configfile.ConfigFile{ TasksFormat: "{{ .Name }}", @@ -138,17 +138,17 @@ func TestStackPs(t *testing.T) { { doc: "WithoutFormat", taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) { - return []swarm.Task{*Task( - TaskID("id-foo"), - TaskServiceID("service-id-foo"), - TaskNodeID("id-node"), - WithTaskSpec(TaskImage("myimage:mytag")), - TaskDesiredState(swarm.TaskStateReady), - WithStatus(TaskState(swarm.TaskStateFailed), Timestamp(time.Now().Add(-2*time.Hour))), + return []swarm.Task{*builders.Task( + builders.TaskID("id-foo"), + builders.TaskServiceID("service-id-foo"), + builders.TaskNodeID("id-node"), + builders.WithTaskSpec(builders.TaskImage("myimage:mytag")), + builders.TaskDesiredState(swarm.TaskStateReady), + builders.WithStatus(builders.TaskState(swarm.TaskStateFailed), builders.Timestamp(time.Now().Add(-2*time.Hour))), )}, nil }, nodeInspectWithRaw: func(ref string) (swarm.Node, []byte, error) { - return *Node(NodeName("node-name-bar")), nil, nil + return *builders.Node(builders.NodeName("node-name-bar")), nil, nil }, args: []string{"foo"}, golden: "stack-ps-without-format.golden", @@ -166,7 +166,7 @@ func TestStackPs(t *testing.T) { cmd := newPsCommand(cli) cmd.SetArgs(tc.args) for key, value := range tc.flags { - cmd.Flags().Set(key, value) + assert.Check(t, cmd.Flags().Set(key, value)) } cmd.SetOut(io.Discard) diff --git a/cli/command/stack/remove.go b/cli/command/stack/remove.go index 9e55e65a7e95..34827666d09d 100644 --- a/cli/command/stack/remove.go +++ b/cli/command/stack/remove.go @@ -22,7 +22,7 @@ func newRemoveCommand(dockerCli command.Cli) *cobra.Command { if err := validateStackNames(opts.Namespaces); err != nil { return err } - return RunRemove(dockerCli, cmd.Flags(), opts) + return swarm.RunRemove(dockerCli, opts) }, ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { return completeNames(dockerCli)(cmd, args, toComplete) @@ -31,7 +31,9 @@ func newRemoveCommand(dockerCli command.Cli) *cobra.Command { return cmd } -// RunRemove performs a stack remove against the specified swarm cluster -func RunRemove(dockerCli command.Cli, flags *pflag.FlagSet, opts options.Remove) error { +// RunRemove performs a stack remove against the specified swarm cluster. +// +// Deprecated: use [swarm.RunRemove] instead. +func RunRemove(dockerCli command.Cli, _ *pflag.FlagSet, opts options.Remove) error { return swarm.RunRemove(dockerCli, opts) } diff --git a/cli/command/stack/services.go b/cli/command/stack/services.go index cc0bdc5e2bf2..ef1947614a96 100644 --- a/cli/command/stack/services.go +++ b/cli/command/stack/services.go @@ -30,7 +30,7 @@ func newServicesCommand(dockerCli command.Cli) *cobra.Command { if err := validateStackName(opts.Namespace); err != nil { return err } - return RunServices(dockerCli, cmd.Flags(), opts) + return RunServices(dockerCli, opts) }, ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { return completeNames(dockerCli)(cmd, args, toComplete) @@ -44,16 +44,18 @@ func newServicesCommand(dockerCli command.Cli) *cobra.Command { } // RunServices performs a stack services against the specified swarm cluster -func RunServices(dockerCli command.Cli, flags *pflag.FlagSet, opts options.Services) error { - services, err := GetServices(dockerCli, flags, opts) +func RunServices(dockerCli command.Cli, opts options.Services) error { + services, err := swarm.GetServices(dockerCli, opts) if err != nil { return err } return formatWrite(dockerCli, services, opts) } -// GetServices returns the services for the specified swarm cluster -func GetServices(dockerCli command.Cli, flags *pflag.FlagSet, opts options.Services) ([]swarmtypes.Service, error) { +// GetServices returns the services for the specified swarm cluster. +// +// Deprecated: use [swarm.GetServices] instead. +func GetServices(dockerCli command.Cli, _ *pflag.FlagSet, opts options.Services) ([]swarmtypes.Service, error) { return swarm.GetServices(dockerCli, opts) } diff --git a/cli/command/stack/services_test.go b/cli/command/stack/services_test.go index f48ee2b11ce3..2ec6431558a0 100644 --- a/cli/command/stack/services_test.go +++ b/cli/command/stack/services_test.go @@ -6,7 +6,7 @@ import ( "github.com/docker/cli/cli/config/configfile" "github.com/docker/cli/internal/test" - . "github.com/docker/cli/internal/test/builders" // Import builders to get the builder function as package function + "github.com/docker/cli/internal/test/builders" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/swarm" "github.com/pkg/errors" @@ -34,20 +34,20 @@ func TestStackServicesErrors(t *testing.T) { { args: []string{"foo"}, serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) { - return []swarm.Service{*Service(GlobalService())}, nil + return []swarm.Service{*builders.Service(builders.GlobalService())}, nil }, nodeListFunc: func(options types.NodeListOptions) ([]swarm.Node, error) { return nil, errors.Errorf("error getting nodes") }, taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) { - return []swarm.Task{*Task()}, nil + return []swarm.Task{*builders.Task()}, nil }, expectedError: "error getting nodes", }, { args: []string{"foo"}, serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) { - return []swarm.Service{*Service(GlobalService())}, nil + return []swarm.Service{*builders.Service(builders.GlobalService())}, nil }, taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) { return nil, errors.Errorf("error getting tasks") @@ -60,7 +60,7 @@ func TestStackServicesErrors(t *testing.T) { "format": "{{invalid format}}", }, serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) { - return []swarm.Service{*Service()}, nil + return []swarm.Service{*builders.Service()}, nil }, expectedError: "template parsing error", }, @@ -77,7 +77,7 @@ func TestStackServicesErrors(t *testing.T) { cmd := newServicesCommand(cli) cmd.SetArgs(tc.args) for key, value := range tc.flags { - cmd.Flags().Set(key, value) + assert.Check(t, cmd.Flags().Set(key, value)) } cmd.SetOut(io.Discard) assert.ErrorContains(t, cmd.Execute(), tc.expectedError) @@ -109,11 +109,11 @@ func TestStackServicesEmptyServiceList(t *testing.T) { func TestStackServicesWithQuietOption(t *testing.T) { cli := test.NewFakeCli(&fakeClient{ serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) { - return []swarm.Service{*Service(ServiceID("id-foo"))}, nil + return []swarm.Service{*builders.Service(builders.ServiceID("id-foo"))}, nil }, }) cmd := newServicesCommand(cli) - cmd.Flags().Set("quiet", "true") + assert.Check(t, cmd.Flags().Set("quiet", "true")) cmd.SetArgs([]string{"foo"}) assert.NilError(t, cmd.Execute()) golden.Assert(t, cli.OutBuffer().String(), "stack-services-with-quiet-option.golden") @@ -123,13 +123,13 @@ func TestStackServicesWithFormat(t *testing.T) { cli := test.NewFakeCli(&fakeClient{ serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) { return []swarm.Service{ - *Service(ServiceName("service-name-foo")), + *builders.Service(builders.ServiceName("service-name-foo")), }, nil }, }) cmd := newServicesCommand(cli) cmd.SetArgs([]string{"foo"}) - cmd.Flags().Set("format", "{{ .Name }}") + assert.Check(t, cmd.Flags().Set("format", "{{ .Name }}")) assert.NilError(t, cmd.Execute()) golden.Assert(t, cli.OutBuffer().String(), "stack-services-with-format.golden") } @@ -138,7 +138,7 @@ func TestStackServicesWithConfigFormat(t *testing.T) { cli := test.NewFakeCli(&fakeClient{ serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) { return []swarm.Service{ - *Service(ServiceName("service-name-foo")), + *builders.Service(builders.ServiceName("service-name-foo")), }, nil }, }) @@ -154,12 +154,12 @@ func TestStackServicesWithConfigFormat(t *testing.T) { func TestStackServicesWithoutFormat(t *testing.T) { cli := test.NewFakeCli(&fakeClient{ serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) { - return []swarm.Service{*Service( - ServiceName("name-foo"), - ServiceID("id-foo"), - ReplicatedService(2), - ServiceImage("busybox:latest"), - ServicePort(swarm.PortConfig{ + return []swarm.Service{*builders.Service( + builders.ServiceName("name-foo"), + builders.ServiceID("id-foo"), + builders.ReplicatedService(2), + builders.ServiceImage("busybox:latest"), + builders.ServicePort(swarm.PortConfig{ PublishMode: swarm.PortConfigPublishModeIngress, PublishedPort: 0, TargetPort: 3232, diff --git a/cli/command/stack/swarm/client_test.go b/cli/command/stack/swarm/client_test.go index 7f9375e99f89..c0fd6a83d08a 100644 --- a/cli/command/stack/swarm/client_test.go +++ b/cli/command/stack/swarm/client_test.go @@ -43,7 +43,7 @@ type fakeClient struct { configRemoveFunc func(configID string) error } -func (cli *fakeClient) ServerVersion(ctx context.Context) (types.Version, error) { +func (cli *fakeClient) ServerVersion(context.Context) (types.Version, error) { return types.Version{ Version: "docker-dev", APIVersion: api.DefaultVersion, @@ -54,7 +54,7 @@ func (cli *fakeClient) ClientVersion() string { return cli.version } -func (cli *fakeClient) ServiceList(ctx context.Context, options types.ServiceListOptions) ([]swarm.Service, error) { +func (cli *fakeClient) ServiceList(_ context.Context, options types.ServiceListOptions) ([]swarm.Service, error) { if cli.serviceListFunc != nil { return cli.serviceListFunc(options) } @@ -69,7 +69,7 @@ func (cli *fakeClient) ServiceList(ctx context.Context, options types.ServiceLis return servicesList, nil } -func (cli *fakeClient) NetworkList(ctx context.Context, options types.NetworkListOptions) ([]types.NetworkResource, error) { +func (cli *fakeClient) NetworkList(_ context.Context, options types.NetworkListOptions) ([]types.NetworkResource, error) { if cli.networkListFunc != nil { return cli.networkListFunc(options) } @@ -84,7 +84,7 @@ func (cli *fakeClient) NetworkList(ctx context.Context, options types.NetworkLis return networksList, nil } -func (cli *fakeClient) SecretList(ctx context.Context, options types.SecretListOptions) ([]swarm.Secret, error) { +func (cli *fakeClient) SecretList(_ context.Context, options types.SecretListOptions) ([]swarm.Secret, error) { if cli.secretListFunc != nil { return cli.secretListFunc(options) } @@ -99,7 +99,7 @@ func (cli *fakeClient) SecretList(ctx context.Context, options types.SecretListO return secretsList, nil } -func (cli *fakeClient) ConfigList(ctx context.Context, options types.ConfigListOptions) ([]swarm.Config, error) { +func (cli *fakeClient) ConfigList(_ context.Context, options types.ConfigListOptions) ([]swarm.Config, error) { if cli.configListFunc != nil { return cli.configListFunc(options) } @@ -114,28 +114,28 @@ func (cli *fakeClient) ConfigList(ctx context.Context, options types.ConfigListO return configsList, nil } -func (cli *fakeClient) TaskList(ctx context.Context, options types.TaskListOptions) ([]swarm.Task, error) { +func (cli *fakeClient) TaskList(_ context.Context, options types.TaskListOptions) ([]swarm.Task, error) { if cli.taskListFunc != nil { return cli.taskListFunc(options) } return []swarm.Task{}, nil } -func (cli *fakeClient) NodeList(ctx context.Context, options types.NodeListOptions) ([]swarm.Node, error) { +func (cli *fakeClient) NodeList(_ context.Context, options types.NodeListOptions) ([]swarm.Node, error) { if cli.nodeListFunc != nil { return cli.nodeListFunc(options) } return []swarm.Node{}, nil } -func (cli *fakeClient) NodeInspectWithRaw(ctx context.Context, ref string) (swarm.Node, []byte, error) { +func (cli *fakeClient) NodeInspectWithRaw(_ context.Context, ref string) (swarm.Node, []byte, error) { if cli.nodeInspectWithRaw != nil { return cli.nodeInspectWithRaw(ref) } return swarm.Node{}, nil, nil } -func (cli *fakeClient) ServiceUpdate(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (types.ServiceUpdateResponse, error) { +func (cli *fakeClient) ServiceUpdate(_ context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (types.ServiceUpdateResponse, error) { if cli.serviceUpdateFunc != nil { return cli.serviceUpdateFunc(serviceID, version, service, options) } @@ -143,7 +143,7 @@ func (cli *fakeClient) ServiceUpdate(ctx context.Context, serviceID string, vers return types.ServiceUpdateResponse{}, nil } -func (cli *fakeClient) ServiceRemove(ctx context.Context, serviceID string) error { +func (cli *fakeClient) ServiceRemove(_ context.Context, serviceID string) error { if cli.serviceRemoveFunc != nil { return cli.serviceRemoveFunc(serviceID) } @@ -152,7 +152,7 @@ func (cli *fakeClient) ServiceRemove(ctx context.Context, serviceID string) erro return nil } -func (cli *fakeClient) NetworkRemove(ctx context.Context, networkID string) error { +func (cli *fakeClient) NetworkRemove(_ context.Context, networkID string) error { if cli.networkRemoveFunc != nil { return cli.networkRemoveFunc(networkID) } @@ -161,7 +161,7 @@ func (cli *fakeClient) NetworkRemove(ctx context.Context, networkID string) erro return nil } -func (cli *fakeClient) SecretRemove(ctx context.Context, secretID string) error { +func (cli *fakeClient) SecretRemove(_ context.Context, secretID string) error { if cli.secretRemoveFunc != nil { return cli.secretRemoveFunc(secretID) } @@ -170,7 +170,7 @@ func (cli *fakeClient) SecretRemove(ctx context.Context, secretID string) error return nil } -func (cli *fakeClient) ConfigRemove(ctx context.Context, configID string) error { +func (cli *fakeClient) ConfigRemove(_ context.Context, configID string) error { if cli.configRemoveFunc != nil { return cli.configRemoveFunc(configID) } diff --git a/cli/command/stack/swarm/remove.go b/cli/command/stack/swarm/remove.go index 4dedef12f356..8fa1eaa2726b 100644 --- a/cli/command/stack/swarm/remove.go +++ b/cli/command/stack/swarm/remove.go @@ -58,7 +58,7 @@ func RunRemove(dockerCli command.Cli, opts options.Remove) error { hasError = removeNetworks(ctx, dockerCli, networks) || hasError if hasError { - errs = append(errs, fmt.Sprintf("Failed to remove some resources from stack: %s", namespace)) + errs = append(errs, "Failed to remove some resources from stack: "+namespace) } } diff --git a/cli/command/streams.go b/cli/command/streams.go index fa435e1643f2..43dc6cd00ee8 100644 --- a/cli/command/streams.go +++ b/cli/command/streams.go @@ -1,23 +1,32 @@ package command import ( + "io" + "github.com/docker/cli/cli/streams" ) // InStream is an input stream used by the DockerCli to read user input -// Deprecated: Use github.com/docker/cli/cli/streams.In instead +// +// Deprecated: Use [streams.In] instead. type InStream = streams.In // OutStream is an output stream used by the DockerCli to write normal program // output. -// Deprecated: Use github.com/docker/cli/cli/streams.Out instead +// +// Deprecated: Use [streams.Out] instead. type OutStream = streams.Out -var ( - // NewInStream returns a new InStream object from a ReadCloser - // Deprecated: Use github.com/docker/cli/cli/streams.NewIn instead - NewInStream = streams.NewIn - // NewOutStream returns a new OutStream object from a Writer - // Deprecated: Use github.com/docker/cli/cli/streams.NewOut instead - NewOutStream = streams.NewOut -) +// NewInStream returns a new [streams.In] from an [io.ReadCloser]. +// +// Deprecated: Use [streams.NewIn] instead. +func NewInStream(in io.ReadCloser) *streams.In { + return streams.NewIn(in) +} + +// NewOutStream returns a new [streams.Out] from an [io.Writer]. +// +// Deprecated: Use [streams.NewOut] instead. +func NewOutStream(out io.Writer) *streams.Out { + return streams.NewOut(out) +} diff --git a/cli/command/swarm/client_test.go b/cli/command/swarm/client_test.go index 8695c89518bf..e116ed0b16da 100644 --- a/cli/command/swarm/client_test.go +++ b/cli/command/swarm/client_test.go @@ -21,63 +21,63 @@ type fakeClient struct { swarmUnlockFunc func(req swarm.UnlockRequest) error } -func (cli *fakeClient) Info(ctx context.Context) (types.Info, error) { +func (cli *fakeClient) Info(context.Context) (types.Info, error) { if cli.infoFunc != nil { return cli.infoFunc() } return types.Info{}, nil } -func (cli *fakeClient) NodeInspectWithRaw(ctx context.Context, ref string) (swarm.Node, []byte, error) { +func (cli *fakeClient) NodeInspectWithRaw(context.Context, string) (swarm.Node, []byte, error) { if cli.nodeInspectFunc != nil { return cli.nodeInspectFunc() } return swarm.Node{}, []byte{}, nil } -func (cli *fakeClient) SwarmInit(ctx context.Context, req swarm.InitRequest) (string, error) { +func (cli *fakeClient) SwarmInit(context.Context, swarm.InitRequest) (string, error) { if cli.swarmInitFunc != nil { return cli.swarmInitFunc() } return "", nil } -func (cli *fakeClient) SwarmInspect(ctx context.Context) (swarm.Swarm, error) { +func (cli *fakeClient) SwarmInspect(context.Context) (swarm.Swarm, error) { if cli.swarmInspectFunc != nil { return cli.swarmInspectFunc() } return swarm.Swarm{}, nil } -func (cli *fakeClient) SwarmGetUnlockKey(ctx context.Context) (types.SwarmUnlockKeyResponse, error) { +func (cli *fakeClient) SwarmGetUnlockKey(context.Context) (types.SwarmUnlockKeyResponse, error) { if cli.swarmGetUnlockKeyFunc != nil { return cli.swarmGetUnlockKeyFunc() } return types.SwarmUnlockKeyResponse{}, nil } -func (cli *fakeClient) SwarmJoin(ctx context.Context, req swarm.JoinRequest) error { +func (cli *fakeClient) SwarmJoin(context.Context, swarm.JoinRequest) error { if cli.swarmJoinFunc != nil { return cli.swarmJoinFunc() } return nil } -func (cli *fakeClient) SwarmLeave(ctx context.Context, force bool) error { +func (cli *fakeClient) SwarmLeave(context.Context, bool) error { if cli.swarmLeaveFunc != nil { return cli.swarmLeaveFunc() } return nil } -func (cli *fakeClient) SwarmUpdate(ctx context.Context, version swarm.Version, swarm swarm.Spec, flags swarm.UpdateFlags) error { +func (cli *fakeClient) SwarmUpdate(_ context.Context, _ swarm.Version, swarm swarm.Spec, flags swarm.UpdateFlags) error { if cli.swarmUpdateFunc != nil { return cli.swarmUpdateFunc(swarm, flags) } return nil } -func (cli *fakeClient) SwarmUnlock(ctx context.Context, req swarm.UnlockRequest) error { +func (cli *fakeClient) SwarmUnlock(_ context.Context, req swarm.UnlockRequest) error { if cli.swarmUnlockFunc != nil { return cli.swarmUnlockFunc(req) } diff --git a/cli/command/swarm/ipnet_slice_test.go b/cli/command/swarm/ipnet_slice_test.go index 36275b989128..ef12ebba654f 100644 --- a/cli/command/swarm/ipnet_slice_test.go +++ b/cli/command/swarm/ipnet_slice_test.go @@ -10,7 +10,7 @@ import ( ) // Helper function to set static slices -func getCIDR(ip net.IP, cidr *net.IPNet, err error) net.IPNet { +func getCIDR(_ net.IP, cidr *net.IPNet, _ error) net.IPNet { return *cidr } @@ -29,7 +29,7 @@ func TestIPNets(t *testing.T) { f := setUpIPNetFlagSet(&ips) vals := []string{"192.168.1.1/24", "10.0.0.1/16", "fd00:0:0:0:0:0:0:2/64"} - arg := fmt.Sprintf("--cidrs=%s", strings.Join(vals, ",")) + arg := "--cidrs=" + strings.Join(vals, ",") err := f.Parse([]string{arg}) if err != nil { t.Fatal("expected no error; got", err) @@ -135,7 +135,7 @@ func TestIPNetBadQuoting(t *testing.T) { var cidrs []net.IPNet f := setUpIPNetFlagSet(&cidrs) - if err := f.Parse([]string{fmt.Sprintf("--cidrs=%s", strings.Join(test.FlagArg, ","))}); err != nil { + if err := f.Parse([]string{"--cidrs=" + strings.Join(test.FlagArg, ",")}); err != nil { t.Fatalf("flag parsing failed with error: %s\nparsing:\t%#v\nwant:\t\t%s", err, test.FlagArg, test.Want[i]) } diff --git a/cli/command/swarm/join_token_test.go b/cli/command/swarm/join_token_test.go index 082b54852a2e..cd5fca1cdebf 100644 --- a/cli/command/swarm/join_token_test.go +++ b/cli/command/swarm/join_token_test.go @@ -6,7 +6,7 @@ import ( "testing" "github.com/docker/cli/internal/test" - . "github.com/docker/cli/internal/test/builders" // Import builders to get the builder function as package function + "github.com/docker/cli/internal/test/builders" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/swarm" "github.com/pkg/errors" @@ -96,7 +96,7 @@ func TestSwarmJoinTokenErrors(t *testing.T) { cmd := newJoinTokenCommand(cli) cmd.SetArgs(tc.args) for key, value := range tc.flags { - cmd.Flags().Set(key, value) + assert.Check(t, cmd.Flags().Set(key, value)) } cmd.SetOut(io.Discard) assert.ErrorContains(t, cmd.Execute(), tc.expectedError) @@ -123,10 +123,10 @@ func TestSwarmJoinToken(t *testing.T) { }, nil }, nodeInspectFunc: func() (swarm.Node, []byte, error) { - return *Node(Manager()), []byte{}, nil + return *builders.Node(builders.Manager()), []byte{}, nil }, swarmInspectFunc: func() (swarm.Swarm, error) { - return *Swarm(), nil + return *builders.Swarm(), nil }, }, { @@ -140,10 +140,10 @@ func TestSwarmJoinToken(t *testing.T) { }, nil }, nodeInspectFunc: func() (swarm.Node, []byte, error) { - return *Node(Manager()), []byte{}, nil + return *builders.Node(builders.Manager()), []byte{}, nil }, swarmInspectFunc: func() (swarm.Swarm, error) { - return *Swarm(), nil + return *builders.Swarm(), nil }, }, { @@ -160,10 +160,10 @@ func TestSwarmJoinToken(t *testing.T) { }, nil }, nodeInspectFunc: func() (swarm.Node, []byte, error) { - return *Node(Manager()), []byte{}, nil + return *builders.Node(builders.Manager()), []byte{}, nil }, swarmInspectFunc: func() (swarm.Swarm, error) { - return *Swarm(), nil + return *builders.Swarm(), nil }, }, { @@ -173,10 +173,10 @@ func TestSwarmJoinToken(t *testing.T) { flagQuiet: "true", }, nodeInspectFunc: func() (swarm.Node, []byte, error) { - return *Node(Manager()), []byte{}, nil + return *builders.Node(builders.Manager()), []byte{}, nil }, swarmInspectFunc: func() (swarm.Swarm, error) { - return *Swarm(), nil + return *builders.Swarm(), nil }, }, { @@ -186,10 +186,10 @@ func TestSwarmJoinToken(t *testing.T) { flagQuiet: "true", }, nodeInspectFunc: func() (swarm.Node, []byte, error) { - return *Node(Manager()), []byte{}, nil + return *builders.Node(builders.Manager()), []byte{}, nil }, swarmInspectFunc: func() (swarm.Swarm, error) { - return *Swarm(), nil + return *builders.Swarm(), nil }, }, } @@ -202,7 +202,7 @@ func TestSwarmJoinToken(t *testing.T) { cmd := newJoinTokenCommand(cli) cmd.SetArgs(tc.args) for key, value := range tc.flags { - cmd.Flags().Set(key, value) + assert.Check(t, cmd.Flags().Set(key, value)) } assert.NilError(t, cmd.Execute()) golden.Assert(t, cli.OutBuffer().String(), fmt.Sprintf("jointoken-%s.golden", tc.name)) diff --git a/cli/command/swarm/unlock_key_test.go b/cli/command/swarm/unlock_key_test.go index 24ca164aa6cb..cfe49feb3e47 100644 --- a/cli/command/swarm/unlock_key_test.go +++ b/cli/command/swarm/unlock_key_test.go @@ -6,7 +6,7 @@ import ( "testing" "github.com/docker/cli/internal/test" - . "github.com/docker/cli/internal/test/builders" // Import builders to get the builder function as package function + "github.com/docker/cli/internal/test/builders" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/swarm" "github.com/pkg/errors" @@ -45,7 +45,7 @@ func TestSwarmUnlockKeyErrors(t *testing.T) { flagRotate: "true", }, swarmInspectFunc: func() (swarm.Swarm, error) { - return *Swarm(), nil + return *builders.Swarm(), nil }, expectedError: "cannot rotate because autolock is not turned on", }, @@ -55,7 +55,7 @@ func TestSwarmUnlockKeyErrors(t *testing.T) { flagRotate: "true", }, swarmInspectFunc: func() (swarm.Swarm, error) { - return *Swarm(Autolock()), nil + return *builders.Swarm(builders.Autolock()), nil }, swarmUpdateFunc: func(swarm swarm.Spec, flags swarm.UpdateFlags) error { return errors.Errorf("error updating the swarm") @@ -88,7 +88,7 @@ func TestSwarmUnlockKeyErrors(t *testing.T) { })) cmd.SetArgs(tc.args) for key, value := range tc.flags { - cmd.Flags().Set(key, value) + assert.Check(t, cmd.Flags().Set(key, value)) } cmd.SetOut(io.Discard) assert.ErrorContains(t, cmd.Execute(), tc.expectedError) @@ -129,7 +129,7 @@ func TestSwarmUnlockKey(t *testing.T) { flagRotate: "true", }, swarmInspectFunc: func() (swarm.Swarm, error) { - return *Swarm(Autolock()), nil + return *builders.Swarm(builders.Autolock()), nil }, swarmGetUnlockKeyFunc: func() (types.SwarmUnlockKeyResponse, error) { return types.SwarmUnlockKeyResponse{ @@ -144,7 +144,7 @@ func TestSwarmUnlockKey(t *testing.T) { flagRotate: "true", }, swarmInspectFunc: func() (swarm.Swarm, error) { - return *Swarm(Autolock()), nil + return *builders.Swarm(builders.Autolock()), nil }, swarmGetUnlockKeyFunc: func() (types.SwarmUnlockKeyResponse, error) { return types.SwarmUnlockKeyResponse{ @@ -162,7 +162,7 @@ func TestSwarmUnlockKey(t *testing.T) { cmd := newUnlockKeyCommand(cli) cmd.SetArgs(tc.args) for key, value := range tc.flags { - cmd.Flags().Set(key, value) + assert.Check(t, cmd.Flags().Set(key, value)) } assert.NilError(t, cmd.Execute()) golden.Assert(t, cli.OutBuffer().String(), fmt.Sprintf("unlockkeys-%s.golden", tc.name)) diff --git a/cli/command/swarm/update_test.go b/cli/command/swarm/update_test.go index 14605c033a6c..29432cd55b6e 100644 --- a/cli/command/swarm/update_test.go +++ b/cli/command/swarm/update_test.go @@ -7,7 +7,7 @@ import ( "time" "github.com/docker/cli/internal/test" - . "github.com/docker/cli/internal/test/builders" // Import builders to get the builder function as package function + "github.com/docker/cli/internal/test/builders" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/swarm" "github.com/pkg/errors" @@ -56,7 +56,7 @@ func TestSwarmUpdateErrors(t *testing.T) { flagAutolock: "true", }, swarmInspectFunc: func() (swarm.Swarm, error) { - return *Swarm(), nil + return *builders.Swarm(), nil }, swarmGetUnlockKeyFunc: func() (types.SwarmUnlockKeyResponse, error) { return types.SwarmUnlockKeyResponse{}, errors.Errorf("error getting unlock key") @@ -73,7 +73,7 @@ func TestSwarmUpdateErrors(t *testing.T) { })) cmd.SetArgs(tc.args) for key, value := range tc.flags { - cmd.Flags().Set(key, value) + assert.Check(t, cmd.Flags().Set(key, value)) } cmd.SetOut(io.Discard) assert.ErrorContains(t, cmd.Execute(), tc.expectedError) @@ -81,7 +81,7 @@ func TestSwarmUpdateErrors(t *testing.T) { } func TestSwarmUpdate(t *testing.T) { - swarmInfo := Swarm() + swarmInfo := builders.Swarm() swarmInfo.ClusterInfo.TLSInfo.TrustRoot = "trustroot" testCases := []struct { @@ -105,7 +105,6 @@ func TestSwarmUpdate(t *testing.T) { flagMaxSnapshots: "10", flagSnapshotInterval: "100", flagAutolock: "true", - flagQuiet: "true", }, swarmInspectFunc: func() (swarm.Swarm, error) { return *swarmInfo, nil @@ -156,7 +155,7 @@ func TestSwarmUpdate(t *testing.T) { return nil }, swarmInspectFunc: func() (swarm.Swarm, error) { - return *Swarm(), nil + return *builders.Swarm(), nil }, swarmGetUnlockKeyFunc: func() (types.SwarmUnlockKeyResponse, error) { return types.SwarmUnlockKeyResponse{ @@ -174,7 +173,7 @@ func TestSwarmUpdate(t *testing.T) { cmd := newUpdateCommand(cli) cmd.SetArgs(tc.args) for key, value := range tc.flags { - cmd.Flags().Set(key, value) + assert.Check(t, cmd.Flags().Set(key, value)) } cmd.SetOut(cli.OutBuffer()) assert.NilError(t, cmd.Execute()) diff --git a/cli/command/system/info.go b/cli/command/system/info.go index 4bd3e0356540..ecf0c47360dd 100644 --- a/cli/command/system/info.go +++ b/cli/command/system/info.go @@ -2,6 +2,7 @@ package system import ( "context" + "errors" "fmt" "io" "regexp" @@ -12,7 +13,9 @@ import ( pluginmanager "github.com/docker/cli/cli-plugins/manager" "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command/completion" + "github.com/docker/cli/cli/command/formatter" "github.com/docker/cli/cli/debug" + flagsHelper "github.com/docker/cli/cli/flags" "github.com/docker/cli/templates" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/swarm" @@ -62,10 +65,7 @@ func NewInfoCommand(dockerCli command.Cli) *cobra.Command { ValidArgsFunction: completion.NoComplete, } - flags := cmd.Flags() - - flags.StringVarP(&opts.format, "format", "f", "", "Format the output using the given Go template") - + cmd.Flags().StringVarP(&opts.format, "format", "f", "", flagsHelper.InspectFormatHelp) return cmd } @@ -168,7 +168,7 @@ func prettyPrintInfo(dockerCli command.Cli, info info) error { } if len(info.ServerErrors) > 0 || len(info.ClientErrors) > 0 { - return fmt.Errorf("errors pretty printing info") + return errors.New("errors pretty printing info") } return nil } @@ -507,6 +507,10 @@ func printServerWarningsLegacy(dockerCli command.Cli, info types.Info) { } func formatInfo(dockerCli command.Cli, info info, format string) error { + if format == formatter.JSONFormatKey { + format = formatter.JSONFormat + } + // Ensure slice/array fields render as `[]` not `null` if info.ClientInfo != nil && info.ClientInfo.Plugins == nil { info.ClientInfo.Plugins = make([]pluginmanager.Plugin, 0) diff --git a/cli/command/system/info_test.go b/cli/command/system/info_test.go index 199c93721da8..5df964006f25 100644 --- a/cli/command/system/info_test.go +++ b/cli/command/system/info_test.go @@ -396,6 +396,11 @@ func TestPrettyPrintInfo(t *testing.T) { assert.NilError(t, formatInfo(cli, tc.dockerInfo, "{{json .}}")) golden.Assert(t, cli.OutBuffer().String(), tc.jsonGolden+".json.golden") assert.Check(t, is.Equal("", cli.ErrBuffer().String())) + + cli = test.NewFakeCli(&fakeClient{}) + assert.NilError(t, formatInfo(cli, tc.dockerInfo, "json")) + golden.Assert(t, cli.OutBuffer().String(), tc.jsonGolden+".json.golden") + assert.Check(t, is.Equal("", cli.ErrBuffer().String())) } }) } diff --git a/cli/command/system/prune.go b/cli/command/system/prune.go index 51773cce3a38..aa2511f5563a 100644 --- a/cli/command/system/prune.go +++ b/cli/command/system/prune.go @@ -2,6 +2,7 @@ package system import ( "bytes" + "errors" "fmt" "sort" "text/template" @@ -71,7 +72,7 @@ Are you sure you want to continue?` func runPrune(dockerCli command.Cli, options pruneOptions) error { // TODO version this once "until" filter is supported for volumes if options.pruneVolumes && options.filter.Value().Contains("until") { - return fmt.Errorf(`ERROR: The "until" filter is not supported with "--volumes"`) + return errors.New(`ERROR: The "until" filter is not supported with "--volumes"`) } if !options.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), confirmationMessage(dockerCli, options)) { return nil @@ -96,11 +97,11 @@ func runPrune(dockerCli command.Cli, options pruneOptions) error { } spaceReclaimed += spc if output != "" { - fmt.Fprintln(dockerCli.Out(), output) + _, _ = fmt.Fprintln(dockerCli.Out(), output) } } - fmt.Fprintln(dockerCli.Out(), "Total reclaimed space:", units.HumanSize(float64(spaceReclaimed))) + _, _ = fmt.Fprintln(dockerCli.Out(), "Total reclaimed space:", units.HumanSize(float64(spaceReclaimed))) return nil } diff --git a/cli/command/system/testdata/docker-client-version.json.golden b/cli/command/system/testdata/docker-client-version.json.golden new file mode 100644 index 000000000000..3a6688548ba2 --- /dev/null +++ b/cli/command/system/testdata/docker-client-version.json.golden @@ -0,0 +1 @@ +{"Client":{"Platform":{"Name":""},"Version":"18.99.5-ce","ApiVersion":"1.38","DefaultAPIVersion":"1.38","GitCommit":"deadbeef","GoVersion":"go1.10.2","Os":"linux","Arch":"amd64","BuildTime":"Wed May 30 22:21:05 2018","Context":"my-context"},"Server":{"Platform":{"Name":"Docker Enterprise Edition (EE) 2.0"},"Components":[{"Name":"Engine","Version":"17.06.2-ee-15","Details":{"ApiVersion":"1.30","Arch":"amd64","BuildTime":"Mon Jul 9 23:38:38 2018","Experimental":"false","GitCommit":"64ddfa6","GoVersion":"go1.8.7","MinAPIVersion":"1.12","Os":"linux"}},{"Name":"Universal Control Plane","Version":"17.06.2-ee-15","Details":{"ApiVersion":"1.30","Arch":"amd64","BuildTime":"Mon Jul 2 21:24:07 UTC 2018","GitCommit":"4513922","GoVersion":"go1.9.4","MinApiVersion":"1.20","Os":"linux","Version":"3.0.3-tp2"}},{"Name":"Kubernetes","Version":"1.8+","Details":{"buildDate":"2018-04-26T16:51:21Z","compiler":"gc","gitCommit":"8d637aedf46b9c21dde723e29c645b9f27106fa5","gitTreeState":"clean","gitVersion":"v1.8.11-docker-8d637ae","goVersion":"go1.8.3","major":"1","minor":"8+","platform":"linux/amd64"}},{"Name":"Calico","Version":"v3.0.8","Details":{"cni":"v2.0.6","kube-controllers":"v2.0.5","node":"v3.0.8"}}],"Version":"","ApiVersion":"","GitCommit":"","GoVersion":"","Os":"","Arch":""}} diff --git a/cli/command/system/version.go b/cli/command/system/version.go index 3ead03215c19..712f0a934cef 100644 --- a/cli/command/system/version.go +++ b/cli/command/system/version.go @@ -11,7 +11,9 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command/completion" + "github.com/docker/cli/cli/command/formatter" "github.com/docker/cli/cli/command/formatter/tabwriter" + flagsHelper "github.com/docker/cli/cli/flags" "github.com/docker/cli/cli/version" "github.com/docker/cli/templates" "github.com/docker/docker/api/types" @@ -20,7 +22,7 @@ import ( "github.com/tonistiigi/go-rosetta" ) -var versionTemplate = `{{with .Client -}} +const defaultVersionTemplate = `{{with .Client -}} Client:{{if ne .Platform.Name ""}} {{.Platform.Name}}{{end}} Version: {{.Version}} API version: {{.APIVersion}}{{if ne .APIVersion .DefaultAPIVersion}} (downgraded from {{.DefaultAPIVersion}}){{end}} @@ -101,9 +103,7 @@ func NewVersionCommand(dockerCli command.Cli) *cobra.Command { ValidArgsFunction: completion.NoComplete, } - flags := cmd.Flags() - flags.StringVarP(&opts.format, "format", "f", "", "Format the output using the given Go template") - + cmd.Flags().StringVarP(&opts.format, "format", "f", "", flagsHelper.InspectFormatHelp) return cmd } @@ -194,8 +194,11 @@ func prettyPrintVersion(dockerCli command.Cli, vd versionInfo, tmpl *template.Te } func newVersionTemplate(templateFormat string) (*template.Template, error) { - if templateFormat == "" { - templateFormat = versionTemplate + switch templateFormat { + case "": + templateFormat = defaultVersionTemplate + case formatter.JSONFormatKey: + templateFormat = formatter.JSONFormat } tmpl := templates.New("version").Funcs(template.FuncMap{"getDetailsOrder": getDetailsOrder}) tmpl, err := tmpl.Parse(templateFormat) diff --git a/cli/command/system/version_test.go b/cli/command/system/version_test.go index 3ddf643290b2..c0cf6425975f 100644 --- a/cli/command/system/version_test.go +++ b/cli/command/system/version_test.go @@ -2,22 +2,21 @@ package system import ( "context" - "fmt" + "errors" "strings" "testing" + "github.com/docker/cli/internal/test" + "github.com/docker/docker/api/types" "gotest.tools/v3/assert" is "gotest.tools/v3/assert/cmp" "gotest.tools/v3/golden" - - "github.com/docker/cli/internal/test" - "github.com/docker/docker/api/types" ) func TestVersionWithoutServer(t *testing.T) { cli := test.NewFakeCli(&fakeClient{ serverVersion: func(ctx context.Context) (types.Version, error) { - return types.Version{}, fmt.Errorf("no server") + return types.Version{}, errors.New("no server") }, }) cmd := NewVersionCommand(cli) @@ -30,7 +29,7 @@ func TestVersionWithoutServer(t *testing.T) { assert.Assert(t, !strings.Contains(out, "Server:"), "actual: %s", out) } -func TestVersionAlign(t *testing.T) { +func TestVersionFormat(t *testing.T) { vi := versionInfo{ Client: clientVersion{ Version: "18.99.5-ce", @@ -104,10 +103,28 @@ func TestVersionAlign(t *testing.T) { }, }) - cli := test.NewFakeCli(&fakeClient{}) - tmpl, err := newVersionTemplate("") - assert.NilError(t, err) - assert.NilError(t, prettyPrintVersion(cli, vi, tmpl)) - assert.Check(t, golden.String(cli.OutBuffer().String(), "docker-client-version.golden")) - assert.Check(t, is.Equal("", cli.ErrBuffer().String())) + t.Run("default", func(t *testing.T) { + cli := test.NewFakeCli(&fakeClient{}) + tmpl, err := newVersionTemplate("") + assert.NilError(t, err) + assert.NilError(t, prettyPrintVersion(cli, vi, tmpl)) + assert.Check(t, golden.String(cli.OutBuffer().String(), "docker-client-version.golden")) + assert.Check(t, is.Equal("", cli.ErrBuffer().String())) + }) + t.Run("json", func(t *testing.T) { + cli := test.NewFakeCli(&fakeClient{}) + tmpl, err := newVersionTemplate("json") + assert.NilError(t, err) + assert.NilError(t, prettyPrintVersion(cli, vi, tmpl)) + assert.Check(t, golden.String(cli.OutBuffer().String(), "docker-client-version.json.golden")) + assert.Check(t, is.Equal("", cli.ErrBuffer().String())) + }) + t.Run("json template", func(t *testing.T) { + cli := test.NewFakeCli(&fakeClient{}) + tmpl, err := newVersionTemplate("{{json .}}") + assert.NilError(t, err) + assert.NilError(t, prettyPrintVersion(cli, vi, tmpl)) + assert.Check(t, golden.String(cli.OutBuffer().String(), "docker-client-version.json.golden")) + assert.Check(t, is.Equal("", cli.ErrBuffer().String())) + }) } diff --git a/cli/command/task/client_test.go b/cli/command/task/client_test.go index 9aa849779a35..3c5129e2617a 100644 --- a/cli/command/task/client_test.go +++ b/cli/command/task/client_test.go @@ -14,14 +14,14 @@ type fakeClient struct { serviceInspectWithRaw func(ref string, options types.ServiceInspectOptions) (swarm.Service, []byte, error) } -func (cli *fakeClient) NodeInspectWithRaw(ctx context.Context, ref string) (swarm.Node, []byte, error) { +func (cli *fakeClient) NodeInspectWithRaw(_ context.Context, ref string) (swarm.Node, []byte, error) { if cli.nodeInspectWithRaw != nil { return cli.nodeInspectWithRaw(ref) } return swarm.Node{}, nil, nil } -func (cli *fakeClient) ServiceInspectWithRaw(ctx context.Context, ref string, options types.ServiceInspectOptions) (swarm.Service, []byte, error) { +func (cli *fakeClient) ServiceInspectWithRaw(_ context.Context, ref string, options types.ServiceInspectOptions) (swarm.Service, []byte, error) { if cli.serviceInspectWithRaw != nil { return cli.serviceInspectWithRaw(ref, options) } diff --git a/cli/command/task/print_test.go b/cli/command/task/print_test.go index d3452dbf4c82..b549f678263d 100644 --- a/cli/command/task/print_test.go +++ b/cli/command/task/print_test.go @@ -8,7 +8,7 @@ import ( "github.com/docker/cli/cli/command/formatter" "github.com/docker/cli/cli/command/idresolver" "github.com/docker/cli/internal/test" - . "github.com/docker/cli/internal/test/builders" // Import builders to get the builder function as package function + "github.com/docker/cli/internal/test/builders" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/swarm" "gotest.tools/v3/assert" @@ -19,29 +19,29 @@ func TestTaskPrintSorted(t *testing.T) { apiClient := &fakeClient{ serviceInspectWithRaw: func(ref string, options types.ServiceInspectOptions) (swarm.Service, []byte, error) { if ref == "service-id-one" { - return *Service(ServiceName("service-name-1")), nil, nil + return *builders.Service(builders.ServiceName("service-name-1")), nil, nil } - return *Service(ServiceName("service-name-10")), nil, nil + return *builders.Service(builders.ServiceName("service-name-10")), nil, nil }, } cli := test.NewFakeCli(apiClient) tasks := []swarm.Task{ - *Task( - TaskID("id-foo"), - TaskServiceID("service-id-ten"), - TaskNodeID("id-node"), - WithTaskSpec(TaskImage("myimage:mytag")), - TaskDesiredState(swarm.TaskStateReady), - WithStatus(TaskState(swarm.TaskStateFailed), Timestamp(time.Now().Add(-2*time.Hour))), + *builders.Task( + builders.TaskID("id-foo"), + builders.TaskServiceID("service-id-ten"), + builders.TaskNodeID("id-node"), + builders.WithTaskSpec(builders.TaskImage("myimage:mytag")), + builders.TaskDesiredState(swarm.TaskStateReady), + builders.WithStatus(builders.TaskState(swarm.TaskStateFailed), builders.Timestamp(time.Now().Add(-2*time.Hour))), ), - *Task( - TaskID("id-bar"), - TaskServiceID("service-id-one"), - TaskNodeID("id-node"), - WithTaskSpec(TaskImage("myimage:mytag")), - TaskDesiredState(swarm.TaskStateReady), - WithStatus(TaskState(swarm.TaskStateFailed), Timestamp(time.Now().Add(-2*time.Hour))), + *builders.Task( + builders.TaskID("id-bar"), + builders.TaskServiceID("service-id-one"), + builders.TaskNodeID("id-node"), + builders.WithTaskSpec(builders.TaskImage("myimage:mytag")), + builders.TaskDesiredState(swarm.TaskStateReady), + builders.WithStatus(builders.TaskState(swarm.TaskStateFailed), builders.Timestamp(time.Now().Add(-2*time.Hour))), ), } @@ -51,25 +51,25 @@ func TestTaskPrintSorted(t *testing.T) { } func TestTaskPrintWithQuietOption(t *testing.T) { - quiet := true - trunc := false - noResolve := true + const quiet = true + const trunc = false + const noResolve = true apiClient := &fakeClient{} cli := test.NewFakeCli(apiClient) - tasks := []swarm.Task{*Task(TaskID("id-foo"))} + tasks := []swarm.Task{*builders.Task(builders.TaskID("id-foo"))} err := Print(context.Background(), cli, tasks, idresolver.New(apiClient, noResolve), trunc, quiet, formatter.TableFormatKey) assert.NilError(t, err) golden.Assert(t, cli.OutBuffer().String(), "task-print-with-quiet-option.golden") } func TestTaskPrintWithNoTruncOption(t *testing.T) { - quiet := false - trunc := false - noResolve := true + const quiet = false + const trunc = false + const noResolve = true apiClient := &fakeClient{} cli := test.NewFakeCli(apiClient) tasks := []swarm.Task{ - *Task(TaskID("id-foo-yov6omdek8fg3k5stosyp2m50")), + *builders.Task(builders.TaskID("id-foo-yov6omdek8fg3k5stosyp2m50")), } err := Print(context.Background(), cli, tasks, idresolver.New(apiClient, noResolve), trunc, quiet, "{{ .ID }}") assert.NilError(t, err) @@ -77,13 +77,13 @@ func TestTaskPrintWithNoTruncOption(t *testing.T) { } func TestTaskPrintWithGlobalService(t *testing.T) { - quiet := false - trunc := false - noResolve := true + const quiet = false + const trunc = false + const noResolve = true apiClient := &fakeClient{} cli := test.NewFakeCli(apiClient) tasks := []swarm.Task{ - *Task(TaskServiceID("service-id-foo"), TaskNodeID("node-id-bar"), TaskSlot(0)), + *builders.Task(builders.TaskServiceID("service-id-foo"), builders.TaskNodeID("node-id-bar"), builders.TaskSlot(0)), } err := Print(context.Background(), cli, tasks, idresolver.New(apiClient, noResolve), trunc, quiet, "{{ .Name }}") assert.NilError(t, err) @@ -91,13 +91,13 @@ func TestTaskPrintWithGlobalService(t *testing.T) { } func TestTaskPrintWithReplicatedService(t *testing.T) { - quiet := false - trunc := false - noResolve := true + const quiet = false + const trunc = false + const noResolve = true apiClient := &fakeClient{} cli := test.NewFakeCli(apiClient) tasks := []swarm.Task{ - *Task(TaskServiceID("service-id-foo"), TaskSlot(1)), + *builders.Task(builders.TaskServiceID("service-id-foo"), builders.TaskSlot(1)), } err := Print(context.Background(), cli, tasks, idresolver.New(apiClient, noResolve), trunc, quiet, "{{ .Name }}") assert.NilError(t, err) @@ -105,34 +105,34 @@ func TestTaskPrintWithReplicatedService(t *testing.T) { } func TestTaskPrintWithIndentation(t *testing.T) { - quiet := false - trunc := false - noResolve := false + const quiet = false + const trunc = false + const noResolve = false apiClient := &fakeClient{ serviceInspectWithRaw: func(ref string, options types.ServiceInspectOptions) (swarm.Service, []byte, error) { - return *Service(ServiceName("service-name-foo")), nil, nil + return *builders.Service(builders.ServiceName("service-name-foo")), nil, nil }, nodeInspectWithRaw: func(ref string) (swarm.Node, []byte, error) { - return *Node(NodeName("node-name-bar")), nil, nil + return *builders.Node(builders.NodeName("node-name-bar")), nil, nil }, } cli := test.NewFakeCli(apiClient) tasks := []swarm.Task{ - *Task( - TaskID("id-foo"), - TaskServiceID("service-id-foo"), - TaskNodeID("id-node"), - WithTaskSpec(TaskImage("myimage:mytag")), - TaskDesiredState(swarm.TaskStateReady), - WithStatus(TaskState(swarm.TaskStateFailed), Timestamp(time.Now().Add(-2*time.Hour))), + *builders.Task( + builders.TaskID("id-foo"), + builders.TaskServiceID("service-id-foo"), + builders.TaskNodeID("id-node"), + builders.WithTaskSpec(builders.TaskImage("myimage:mytag")), + builders.TaskDesiredState(swarm.TaskStateReady), + builders.WithStatus(builders.TaskState(swarm.TaskStateFailed), builders.Timestamp(time.Now().Add(-2*time.Hour))), ), - *Task( - TaskID("id-bar"), - TaskServiceID("service-id-foo"), - TaskNodeID("id-node"), - WithTaskSpec(TaskImage("myimage:mytag")), - TaskDesiredState(swarm.TaskStateReady), - WithStatus(TaskState(swarm.TaskStateFailed), Timestamp(time.Now().Add(-2*time.Hour))), + *builders.Task( + builders.TaskID("id-bar"), + builders.TaskServiceID("service-id-foo"), + builders.TaskNodeID("id-node"), + builders.WithTaskSpec(builders.TaskImage("myimage:mytag")), + builders.TaskDesiredState(swarm.TaskStateReady), + builders.WithStatus(builders.TaskState(swarm.TaskStateFailed), builders.Timestamp(time.Now().Add(-2*time.Hour))), ), } err := Print(context.Background(), cli, tasks, idresolver.New(apiClient, noResolve), trunc, quiet, formatter.TableFormatKey) @@ -141,20 +141,20 @@ func TestTaskPrintWithIndentation(t *testing.T) { } func TestTaskPrintWithResolution(t *testing.T) { - quiet := false - trunc := false - noResolve := false + const quiet = false + const trunc = false + const noResolve = false apiClient := &fakeClient{ serviceInspectWithRaw: func(ref string, options types.ServiceInspectOptions) (swarm.Service, []byte, error) { - return *Service(ServiceName("service-name-foo")), nil, nil + return *builders.Service(builders.ServiceName("service-name-foo")), nil, nil }, nodeInspectWithRaw: func(ref string) (swarm.Node, []byte, error) { - return *Node(NodeName("node-name-bar")), nil, nil + return *builders.Node(builders.NodeName("node-name-bar")), nil, nil }, } cli := test.NewFakeCli(apiClient) tasks := []swarm.Task{ - *Task(TaskServiceID("service-id-foo"), TaskSlot(1)), + *builders.Task(builders.TaskServiceID("service-id-foo"), builders.TaskSlot(1)), } err := Print(context.Background(), cli, tasks, idresolver.New(apiClient, noResolve), trunc, quiet, "{{ .Name }} {{ .Node }}") assert.NilError(t, err) diff --git a/cli/command/trust/inspect_pretty_test.go b/cli/command/trust/inspect_pretty_test.go index 4c6e3ffca912..2ccfd927d882 100644 --- a/cli/command/trust/inspect_pretty_test.go +++ b/cli/command/trust/inspect_pretty_test.go @@ -27,15 +27,15 @@ type fakeClient struct { apiclient.Client } -func (c *fakeClient) Info(ctx context.Context) (types.Info, error) { +func (c *fakeClient) Info(context.Context) (types.Info, error) { return types.Info{}, nil } -func (c *fakeClient) ImageInspectWithRaw(ctx context.Context, imageID string) (types.ImageInspect, []byte, error) { +func (c *fakeClient) ImageInspectWithRaw(context.Context, string) (types.ImageInspect, []byte, error) { return types.ImageInspect{}, []byte{}, nil } -func (c *fakeClient) ImagePush(ctx context.Context, image string, options types.ImagePushOptions) (io.ReadCloser, error) { +func (c *fakeClient) ImagePush(context.Context, string, types.ImagePushOptions) (io.ReadCloser, error) { return &utils.NoopCloser{Reader: bytes.NewBuffer([]byte{})}, nil } diff --git a/cli/command/trust/key_load.go b/cli/command/trust/key_load.go index bbb19e733e39..4a5bb2605578 100644 --- a/cli/command/trust/key_load.go +++ b/cli/command/trust/key_load.go @@ -109,7 +109,7 @@ func decodePrivKeyIfNecessary(privPemBytes []byte, passRet notary.PassRetriever) if _, ok := pemBlock.Headers["path"]; !ok { privKey, _, err := trustmanager.GetPasswdDecryptBytes(passRet, privPemBytes, "", "encrypted") if err != nil { - return []byte{}, fmt.Errorf("could not decrypt key") + return []byte{}, errors.New("could not decrypt key") } privPemBytes = privKey.Private() } diff --git a/cli/command/trust/revoke.go b/cli/command/trust/revoke.go index 31437b03f1c8..6aaa7d9f1069 100644 --- a/cli/command/trust/revoke.go +++ b/cli/command/trust/revoke.go @@ -42,7 +42,7 @@ func revokeTrust(cli command.Cli, remote string, options revokeOptions) error { } tag := imgRefAndAuth.Tag() if imgRefAndAuth.Tag() == "" && imgRefAndAuth.Digest() != "" { - return fmt.Errorf("cannot use a digest reference for IMAGE:TAG") + return errors.New("cannot use a digest reference for IMAGE:TAG") } if imgRefAndAuth.Tag() == "" && !options.forceYes { deleteRemote := command.PromptForConfirmation(os.Stdin, cli.Out(), fmt.Sprintf("Please confirm you would like to delete all signature data for %s?", remote)) @@ -64,7 +64,7 @@ func revokeTrust(cli command.Cli, remote string, options revokeOptions) error { if err := revokeSignature(notaryRepo, tag); err != nil { return errors.Wrapf(err, "could not remove signature for %s", remote) } - fmt.Fprintf(cli.Out(), "Successfully deleted signature for %s\n", remote) + _, _ = fmt.Fprintf(cli.Out(), "Successfully deleted signature for %s\n", remote) return nil } @@ -101,7 +101,7 @@ func revokeAllSigs(notaryRepo client.Repository) error { } if len(releasedTargetWithRoleList) == 0 { - return fmt.Errorf("no signed tags to remove") + return errors.New("no signed tags to remove") } // we need all the roles that signed each released target so we can remove from all roles. diff --git a/cli/command/trust/signer_add.go b/cli/command/trust/signer_add.go index 53c5c67cd433..c0090b508e83 100644 --- a/cli/command/trust/signer_add.go +++ b/cli/command/trust/signer_add.go @@ -53,11 +53,11 @@ func addSigner(cli command.Cli, options signerAddOptions) error { return fmt.Errorf("signer name \"%s\" must start with lowercase alphanumeric characters and can include \"-\" or \"_\" after the first character", signerName) } if signerName == "releases" { - return fmt.Errorf("releases is a reserved keyword, please use a different signer name") + return errors.New("releases is a reserved keyword, please use a different signer name") } if options.keys.Len() == 0 { - return fmt.Errorf("path to a public key must be provided using the `--key` flag") + return errors.New("path to a public key must be provided using the `--key` flag") } signerPubKeys, err := ingestPublicKeys(options.keys.GetAll()) if err != nil { diff --git a/cli/command/volume/create_test.go b/cli/command/volume/create_test.go index f5c711bf8842..a37f9655b1d0 100644 --- a/cli/command/volume/create_test.go +++ b/cli/command/volume/create_test.go @@ -50,6 +50,7 @@ func TestVolumeCreateErrors(t *testing.T) { cmd.Flags().Set(key, value) } cmd.SetOut(io.Discard) + cmd.SetErr(io.Discard) assert.ErrorContains(t, cmd.Execute(), tc.expectedError) } } diff --git a/cli/command/volume/inspect_test.go b/cli/command/volume/inspect_test.go index bf1643752417..624dc8de63b0 100644 --- a/cli/command/volume/inspect_test.go +++ b/cli/command/volume/inspect_test.go @@ -6,7 +6,7 @@ import ( "testing" "github.com/docker/cli/internal/test" - . "github.com/docker/cli/internal/test/builders" // Import builders to get the builder function as package function + "github.com/docker/cli/internal/test/builders" "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/api/types/volume" "github.com/pkg/errors" @@ -59,9 +59,10 @@ func TestVolumeInspectErrors(t *testing.T) { ) cmd.SetArgs(tc.args) for key, value := range tc.flags { - cmd.Flags().Set(key, value) + assert.Check(t, cmd.Flags().Set(key, value)) } cmd.SetOut(io.Discard) + cmd.SetErr(io.Discard) assert.ErrorContains(t, cmd.Execute(), tc.expectedError) } } @@ -79,14 +80,14 @@ func TestVolumeInspectWithoutFormat(t *testing.T) { if volumeID != "foo" { return volume.Volume{}, errors.Errorf("Invalid volumeID, expected %s, got %s", "foo", volumeID) } - return *Volume(), nil + return *builders.Volume(), nil }, }, { name: "multiple-volume-with-labels", args: []string{"foo", "bar"}, volumeInspectFunc: func(volumeID string) (volume.Volume, error) { - return *Volume(VolumeName(volumeID), VolumeLabels(map[string]string{ + return *builders.Volume(builders.VolumeName(volumeID), builders.VolumeLabels(map[string]string{ "foo": "bar", })), nil }, @@ -105,7 +106,7 @@ func TestVolumeInspectWithoutFormat(t *testing.T) { func TestVolumeInspectWithFormat(t *testing.T) { volumeInspectFunc := func(volumeID string) (volume.Volume, error) { - return *Volume(VolumeLabels(map[string]string{ + return *builders.Volume(builders.VolumeLabels(map[string]string{ "foo": "bar", })), nil } @@ -134,7 +135,7 @@ func TestVolumeInspectWithFormat(t *testing.T) { }) cmd := newInspectCommand(cli) cmd.SetArgs(tc.args) - cmd.Flags().Set("format", tc.format) + assert.Check(t, cmd.Flags().Set("format", tc.format)) assert.NilError(t, cmd.Execute()) golden.Assert(t, cli.OutBuffer().String(), fmt.Sprintf("volume-inspect-with-format.%s.golden", tc.name)) } diff --git a/cli/command/volume/list_test.go b/cli/command/volume/list_test.go index dbed5a160629..08fd4038486a 100644 --- a/cli/command/volume/list_test.go +++ b/cli/command/volume/list_test.go @@ -6,7 +6,7 @@ import ( "github.com/docker/cli/cli/config/configfile" "github.com/docker/cli/internal/test" - . "github.com/docker/cli/internal/test/builders" // Import builders to get the builder function as package function + "github.com/docker/cli/internal/test/builders" "github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/volume" "github.com/pkg/errors" @@ -40,9 +40,10 @@ func TestVolumeListErrors(t *testing.T) { ) cmd.SetArgs(tc.args) for key, value := range tc.flags { - cmd.Flags().Set(key, value) + assert.Check(t, cmd.Flags().Set(key, value)) } cmd.SetOut(io.Discard) + cmd.SetErr(io.Discard) assert.ErrorContains(t, cmd.Execute(), tc.expectedError) } } @@ -52,9 +53,9 @@ func TestVolumeListWithoutFormat(t *testing.T) { volumeListFunc: func(filter filters.Args) (volume.ListResponse, error) { return volume.ListResponse{ Volumes: []*volume.Volume{ - Volume(), - Volume(VolumeName("foo"), VolumeDriver("bar")), - Volume(VolumeName("baz"), VolumeLabels(map[string]string{ + builders.Volume(), + builders.Volume(builders.VolumeName("foo"), builders.VolumeDriver("bar")), + builders.Volume(builders.VolumeName("baz"), builders.VolumeLabels(map[string]string{ "foo": "bar", })), }, @@ -71,9 +72,9 @@ func TestVolumeListWithConfigFormat(t *testing.T) { volumeListFunc: func(filter filters.Args) (volume.ListResponse, error) { return volume.ListResponse{ Volumes: []*volume.Volume{ - Volume(), - Volume(VolumeName("foo"), VolumeDriver("bar")), - Volume(VolumeName("baz"), VolumeLabels(map[string]string{ + builders.Volume(), + builders.Volume(builders.VolumeName("foo"), builders.VolumeDriver("bar")), + builders.Volume(builders.VolumeName("baz"), builders.VolumeLabels(map[string]string{ "foo": "bar", })), }, @@ -93,9 +94,9 @@ func TestVolumeListWithFormat(t *testing.T) { volumeListFunc: func(filter filters.Args) (volume.ListResponse, error) { return volume.ListResponse{ Volumes: []*volume.Volume{ - Volume(), - Volume(VolumeName("foo"), VolumeDriver("bar")), - Volume(VolumeName("baz"), VolumeLabels(map[string]string{ + builders.Volume(), + builders.Volume(builders.VolumeName("foo"), builders.VolumeDriver("bar")), + builders.Volume(builders.VolumeName("baz"), builders.VolumeLabels(map[string]string{ "foo": "bar", })), }, @@ -103,7 +104,7 @@ func TestVolumeListWithFormat(t *testing.T) { }, }) cmd := newListCommand(cli) - cmd.Flags().Set("format", "{{ .Name }} {{ .Driver }} {{ .Labels }}") + assert.Check(t, cmd.Flags().Set("format", "{{ .Name }} {{ .Driver }} {{ .Labels }}")) assert.NilError(t, cmd.Execute()) golden.Assert(t, cli.OutBuffer().String(), "volume-list-with-format.golden") } @@ -113,15 +114,15 @@ func TestVolumeListSortOrder(t *testing.T) { volumeListFunc: func(filter filters.Args) (volume.ListResponse, error) { return volume.ListResponse{ Volumes: []*volume.Volume{ - Volume(VolumeName("volume-2-foo")), - Volume(VolumeName("volume-10-foo")), - Volume(VolumeName("volume-1-foo")), + builders.Volume(builders.VolumeName("volume-2-foo")), + builders.Volume(builders.VolumeName("volume-10-foo")), + builders.Volume(builders.VolumeName("volume-1-foo")), }, }, nil }, }) cmd := newListCommand(cli) - cmd.Flags().Set("format", "{{ .Name }}") + assert.Check(t, cmd.Flags().Set("format", "{{ .Name }}")) assert.NilError(t, cmd.Execute()) golden.Assert(t, cli.OutBuffer().String(), "volume-list-sort.golden") } @@ -222,14 +223,14 @@ func TestClusterVolumeList(t *testing.T) { }, }, }, - Volume(VolumeName("volume-local-1")), + builders.Volume(builders.VolumeName("volume-local-1")), }, }, nil }, }) cmd := newListCommand(cli) - cmd.Flags().Set("cluster", "true") + assert.Check(t, cmd.Flags().Set("cluster", "true")) assert.NilError(t, cmd.Execute()) golden.Assert(t, cli.OutBuffer().String(), "volume-cluster-volume-list.golden") } diff --git a/cli/command/volume/prune.go b/cli/command/volume/prune.go index 16fd5508988e..a85cb3883369 100644 --- a/cli/command/volume/prune.go +++ b/cli/command/volume/prune.go @@ -8,11 +8,15 @@ import ( "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command/completion" "github.com/docker/cli/opts" + "github.com/docker/docker/api/types/versions" + "github.com/docker/docker/errdefs" units "github.com/docker/go-units" + "github.com/pkg/errors" "github.com/spf13/cobra" ) type pruneOptions struct { + all bool force bool filter opts.FilterOpt } @@ -41,18 +45,37 @@ func NewPruneCommand(dockerCli command.Cli) *cobra.Command { } flags := cmd.Flags() + flags.BoolVarP(&options.all, "all", "a", false, "Remove all unused volumes, not just anonymous ones") + flags.SetAnnotation("all", "version", []string{"1.42"}) flags.BoolVarP(&options.force, "force", "f", false, "Do not prompt for confirmation") flags.Var(&options.filter, "filter", `Provide filter values (e.g. "label=