diff --git a/Dockerfile b/Dockerfile index f5213cbf18c5..9ad2bc79b52c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -74,6 +74,19 @@ RUN --mount=type=bind,target=.,ro \ TARGET=/out ./scripts/build/binary && \ xx-verify $([ "$GO_LINKMODE" = "static" ] && echo "--static") /out/docker +FROM build-${BASE_VARIANT} AS build-trust +ARG GO_LINKMODE=static +ARG GO_BUILDTAGS +ARG GO_STRIP +ARG CGO_ENABLED +ARG VERSION +RUN --mount=ro --mount=type=cache,target=/root/.cache \ + xx-go --wrap && \ + TARGET=/out ./scripts/build/trust-plugin + +FROM scratch AS trust +COPY --link --from=build-trust /out/docker-trust / + FROM build-${BASE_VARIANT} AS test COPY --link --from=gotestsum /out/gotestsum /usr/bin/gotestsum ENV GO111MODULE=auto @@ -114,6 +127,7 @@ 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 --from=trust /docker-trust /usr/libexec/docker/cli-plugins/docker-trust COPY --link . . ENV DOCKER_BUILDKIT=1 ENV PATH=/go/src/github.com/docker/cli/build:$PATH diff --git a/Makefile b/Makefile index 0ec30ba6683b..f837d2a73961 100644 --- a/Makefile +++ b/Makefile @@ -69,6 +69,15 @@ dynbinary: ## build dynamically linked binary plugins: ## build example CLI plugins scripts/build/plugins +.PHONY: trust-plugin +trust-plugin: ## build docker-trust CLI plugins + scripts/build/trust-plugin + +.PHONY: install-trust-plugin +install-trust-plugin: trust-plugin +install-trust-plugin: ## install docker-trust CLI plugins + install -D -m 0755 "$$(readlink -f build/docker-trust)" /usr/libexec/docker/cli-plugins/docker-trust + .PHONY: vendor vendor: ## update vendor with go modules rm -rf vendor diff --git a/cli/command/commands/commands.go b/cli/command/commands/commands.go index d3929293999d..e96a0f1a1ed4 100644 --- a/cli/command/commands/commands.go +++ b/cli/command/commands/commands.go @@ -20,7 +20,6 @@ import ( "github.com/docker/cli/cli/command/stack" "github.com/docker/cli/cli/command/swarm" "github.com/docker/cli/cli/command/system" - "github.com/docker/cli/cli/command/trust" "github.com/docker/cli/cli/command/volume" "github.com/spf13/cobra" ) @@ -53,7 +52,6 @@ func AddCommands(cmd *cobra.Command, dockerCli command.Cli) { network.NewNetworkCommand(dockerCli), plugin.NewPluginCommand(dockerCli), system.NewSystemCommand(dockerCli), - trust.NewTrustCommand(dockerCli), volume.NewVolumeCommand(dockerCli), // orchestration (swarm) commands diff --git a/cmd/docker-trust/main.go b/cmd/docker-trust/main.go new file mode 100644 index 000000000000..bf6bc2e0115f --- /dev/null +++ b/cmd/docker-trust/main.go @@ -0,0 +1,114 @@ +package main + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "syscall" + + cerrdefs "github.com/containerd/errdefs" + "github.com/docker/cli/cli" + "github.com/docker/cli/cli-plugins/metadata" + "github.com/docker/cli/cli-plugins/plugin" + "github.com/docker/cli/cli/command" + "github.com/docker/cli/cli/version" + "github.com/docker/cli/cmd/docker-trust/trust" + "go.opentelemetry.io/otel" +) + +func runStandalone(cmd *command.DockerCli) error { + defer flushMetrics(cmd) + executable := os.Args[0] + rootCmd := trust.NewRootCmd(filepath.Base(executable), false, cmd) + return rootCmd.Execute() +} + +// flushMetrics will manually flush metrics from the configured +// meter provider. This is needed when running in standalone mode +// because the meter provider is initialized by the cli library, +// but the mechanism for forcing it to report is not presently +// exposed and not invoked when run in standalone mode. +// There are plans to fix that in the next release, but this is +// needed temporarily until the API for this is more thorough. +func flushMetrics(cmd *command.DockerCli) { + if mp, ok := cmd.MeterProvider().(command.MeterProvider); ok { + if err := mp.ForceFlush(context.Background()); err != nil { + otel.Handle(err) + } + } +} + +func runPlugin(cmd *command.DockerCli) error { + rootCmd := trust.NewRootCmd("trust", true, cmd) + return plugin.RunPlugin(cmd, rootCmd, metadata.Metadata{ + SchemaVersion: "0.1.0", + Vendor: "Docker Inc.", + Version: version.Version, + }) +} + +func run(cmd *command.DockerCli) error { + if plugin.RunningStandalone() { + return runStandalone(cmd) + } + return runPlugin(cmd) +} + +type errCtxSignalTerminated struct { + signal os.Signal +} + +func (errCtxSignalTerminated) Error() string { + return "" +} + +func main() { + cmd, err := command.NewDockerCli() + if err != nil { + _, _ = fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + + if err = run(cmd); err == nil { + return + } + + if errors.As(err, &errCtxSignalTerminated{}) { + os.Exit(getExitCode(err)) + } + + if !cerrdefs.IsCanceled(err) { + if err.Error() != "" { + _, _ = fmt.Fprintln(cmd.Err(), err) + } + os.Exit(getExitCode(err)) + } +} + +// getExitCode returns the exit-code to use for the given error. +// If err is a [cli.StatusError] and has a StatusCode set, it uses the +// status-code from it, otherwise it returns "1" for any error. +func getExitCode(err error) int { + if err == nil { + return 0 + } + + var userTerminatedErr errCtxSignalTerminated + if errors.As(err, &userTerminatedErr) { + s, ok := userTerminatedErr.signal.(syscall.Signal) + if !ok { + return 1 + } + return 128 + int(s) + } + + var stErr cli.StatusError + if errors.As(err, &stErr) && stErr.StatusCode != 0 { // FIXME(thaJeztah): StatusCode should never be used with a zero status-code. Check if we do this anywhere. + return stErr.StatusCode + } + + // No status-code provided; all errors should have a non-zero exit code. + return 1 +} diff --git a/cli/command/trust/cmd.go b/cmd/docker-trust/trust/cmd.go similarity index 100% rename from cli/command/trust/cmd.go rename to cmd/docker-trust/trust/cmd.go diff --git a/cmd/docker-trust/trust/commands.go b/cmd/docker-trust/trust/commands.go new file mode 100644 index 000000000000..a878de56b83c --- /dev/null +++ b/cmd/docker-trust/trust/commands.go @@ -0,0 +1,82 @@ +package trust + +import ( + "fmt" + + "github.com/docker/cli-docs-tool/annotation" + "github.com/docker/cli/cli" + "github.com/docker/cli/cli-plugins/plugin" + "github.com/docker/cli/cli/command" + "github.com/docker/cli/cli/debug" + cliflags "github.com/docker/cli/cli/flags" + "github.com/spf13/cobra" + "github.com/spf13/pflag" +) + +func NewRootCmd(name string, isPlugin bool, dockerCLI *command.DockerCli) *cobra.Command { + var opt rootOptions + cmd := &cobra.Command{ + Use: name, + Short: "Manage trust on Docker images", + Long: `Extended build capabilities with BuildKit`, + Annotations: map[string]string{ + annotation.CodeDelimiter: `"`, + }, + CompletionOptions: cobra.CompletionOptions{ + HiddenDefaultCmd: true, + }, + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + if opt.debug { + debug.Enable() + } + // cmd.SetContext(appcontext.Context()) + if !isPlugin { + // InstallFlags and SetDefaultOptions are necessary to match + // the plugin mode behavior to handle env vars such as + // DOCKER_TLS, DOCKER_TLS_VERIFY, ... and we also need to use a + // new flagset to avoid conflict with the global debug flag + // that we already handle in the root command otherwise it + // would panic. + nflags := pflag.NewFlagSet(cmd.DisplayName(), pflag.ContinueOnError) + options := cliflags.NewClientOptions() + options.InstallFlags(nflags) + options.SetDefaultOptions(nflags) + return dockerCLI.Initialize(options) + } + return plugin.PersistentPreRunE(cmd, args) + }, + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return cmd.Help() + } + _ = cmd.Help() + return cli.StatusError{ + StatusCode: 1, + Status: fmt.Sprintf("ERROR: unknown command: %q", args[0]), + } + }, + } + if !isPlugin { + // match plugin behavior for standalone mode + // https://github.com/docker/cli/blob/6c9eb708fa6d17765d71965f90e1c59cea686ee9/cli-plugins/plugin/plugin.go#L117-L127 + cmd.SilenceUsage = true + cmd.SilenceErrors = true + cmd.TraverseChildren = true + cmd.DisableFlagsInUseLine = true + cli.DisableFlagsInUseLine(cmd) + } + + cmd.AddCommand( + newRevokeCommand(dockerCLI), + newSignCommand(dockerCLI), + newTrustKeyCommand(dockerCLI), + newTrustSignerCommand(dockerCLI), + newInspectCommand(dockerCLI), + ) + + return cmd +} + +type rootOptions struct { + debug bool +} diff --git a/cli/command/trust/common.go b/cmd/docker-trust/trust/common.go similarity index 100% rename from cli/command/trust/common.go rename to cmd/docker-trust/trust/common.go diff --git a/cli/command/trust/common_test.go b/cmd/docker-trust/trust/common_test.go similarity index 100% rename from cli/command/trust/common_test.go rename to cmd/docker-trust/trust/common_test.go diff --git a/cli/command/trust/formatter.go b/cmd/docker-trust/trust/formatter.go similarity index 100% rename from cli/command/trust/formatter.go rename to cmd/docker-trust/trust/formatter.go diff --git a/cli/command/trust/formatter_test.go b/cmd/docker-trust/trust/formatter_test.go similarity index 100% rename from cli/command/trust/formatter_test.go rename to cmd/docker-trust/trust/formatter_test.go diff --git a/cli/command/trust/helpers.go b/cmd/docker-trust/trust/helpers.go similarity index 100% rename from cli/command/trust/helpers.go rename to cmd/docker-trust/trust/helpers.go diff --git a/cli/command/trust/inspect.go b/cmd/docker-trust/trust/inspect.go similarity index 100% rename from cli/command/trust/inspect.go rename to cmd/docker-trust/trust/inspect.go diff --git a/cli/command/trust/inspect_pretty.go b/cmd/docker-trust/trust/inspect_pretty.go similarity index 100% rename from cli/command/trust/inspect_pretty.go rename to cmd/docker-trust/trust/inspect_pretty.go diff --git a/cli/command/trust/inspect_pretty_test.go b/cmd/docker-trust/trust/inspect_pretty_test.go similarity index 100% rename from cli/command/trust/inspect_pretty_test.go rename to cmd/docker-trust/trust/inspect_pretty_test.go diff --git a/cli/command/trust/inspect_test.go b/cmd/docker-trust/trust/inspect_test.go similarity index 100% rename from cli/command/trust/inspect_test.go rename to cmd/docker-trust/trust/inspect_test.go diff --git a/cli/command/trust/key.go b/cmd/docker-trust/trust/key.go similarity index 100% rename from cli/command/trust/key.go rename to cmd/docker-trust/trust/key.go diff --git a/cli/command/trust/key_generate.go b/cmd/docker-trust/trust/key_generate.go similarity index 100% rename from cli/command/trust/key_generate.go rename to cmd/docker-trust/trust/key_generate.go diff --git a/cli/command/trust/key_generate_test.go b/cmd/docker-trust/trust/key_generate_test.go similarity index 100% rename from cli/command/trust/key_generate_test.go rename to cmd/docker-trust/trust/key_generate_test.go diff --git a/cli/command/trust/key_load.go b/cmd/docker-trust/trust/key_load.go similarity index 100% rename from cli/command/trust/key_load.go rename to cmd/docker-trust/trust/key_load.go diff --git a/cli/command/trust/key_load_test.go b/cmd/docker-trust/trust/key_load_test.go similarity index 100% rename from cli/command/trust/key_load_test.go rename to cmd/docker-trust/trust/key_load_test.go diff --git a/cli/command/trust/revoke.go b/cmd/docker-trust/trust/revoke.go similarity index 100% rename from cli/command/trust/revoke.go rename to cmd/docker-trust/trust/revoke.go diff --git a/cli/command/trust/revoke_test.go b/cmd/docker-trust/trust/revoke_test.go similarity index 100% rename from cli/command/trust/revoke_test.go rename to cmd/docker-trust/trust/revoke_test.go diff --git a/cli/command/trust/sign.go b/cmd/docker-trust/trust/sign.go similarity index 100% rename from cli/command/trust/sign.go rename to cmd/docker-trust/trust/sign.go diff --git a/cli/command/trust/sign_test.go b/cmd/docker-trust/trust/sign_test.go similarity index 100% rename from cli/command/trust/sign_test.go rename to cmd/docker-trust/trust/sign_test.go diff --git a/cli/command/trust/signer.go b/cmd/docker-trust/trust/signer.go similarity index 100% rename from cli/command/trust/signer.go rename to cmd/docker-trust/trust/signer.go diff --git a/cli/command/trust/signer_add.go b/cmd/docker-trust/trust/signer_add.go similarity index 100% rename from cli/command/trust/signer_add.go rename to cmd/docker-trust/trust/signer_add.go diff --git a/cli/command/trust/signer_add_test.go b/cmd/docker-trust/trust/signer_add_test.go similarity index 100% rename from cli/command/trust/signer_add_test.go rename to cmd/docker-trust/trust/signer_add_test.go diff --git a/cli/command/trust/signer_remove.go b/cmd/docker-trust/trust/signer_remove.go similarity index 100% rename from cli/command/trust/signer_remove.go rename to cmd/docker-trust/trust/signer_remove.go diff --git a/cli/command/trust/signer_remove_test.go b/cmd/docker-trust/trust/signer_remove_test.go similarity index 100% rename from cli/command/trust/signer_remove_test.go rename to cmd/docker-trust/trust/signer_remove_test.go diff --git a/cli/command/trust/testdata/trust-inspect-empty-repo.golden b/cmd/docker-trust/trust/testdata/trust-inspect-empty-repo.golden similarity index 100% rename from cli/command/trust/testdata/trust-inspect-empty-repo.golden rename to cmd/docker-trust/trust/testdata/trust-inspect-empty-repo.golden diff --git a/cli/command/trust/testdata/trust-inspect-full-repo-no-signers.golden b/cmd/docker-trust/trust/testdata/trust-inspect-full-repo-no-signers.golden similarity index 100% rename from cli/command/trust/testdata/trust-inspect-full-repo-no-signers.golden rename to cmd/docker-trust/trust/testdata/trust-inspect-full-repo-no-signers.golden diff --git a/cli/command/trust/testdata/trust-inspect-full-repo-with-signers.golden b/cmd/docker-trust/trust/testdata/trust-inspect-full-repo-with-signers.golden similarity index 100% rename from cli/command/trust/testdata/trust-inspect-full-repo-with-signers.golden rename to cmd/docker-trust/trust/testdata/trust-inspect-full-repo-with-signers.golden diff --git a/cli/command/trust/testdata/trust-inspect-multiple-repos-with-signers.golden b/cmd/docker-trust/trust/testdata/trust-inspect-multiple-repos-with-signers.golden similarity index 100% rename from cli/command/trust/testdata/trust-inspect-multiple-repos-with-signers.golden rename to cmd/docker-trust/trust/testdata/trust-inspect-multiple-repos-with-signers.golden diff --git a/cli/command/trust/testdata/trust-inspect-one-tag-no-signers.golden b/cmd/docker-trust/trust/testdata/trust-inspect-one-tag-no-signers.golden similarity index 100% rename from cli/command/trust/testdata/trust-inspect-one-tag-no-signers.golden rename to cmd/docker-trust/trust/testdata/trust-inspect-one-tag-no-signers.golden diff --git a/cli/command/trust/testdata/trust-inspect-pretty-full-repo-no-signers.golden b/cmd/docker-trust/trust/testdata/trust-inspect-pretty-full-repo-no-signers.golden similarity index 100% rename from cli/command/trust/testdata/trust-inspect-pretty-full-repo-no-signers.golden rename to cmd/docker-trust/trust/testdata/trust-inspect-pretty-full-repo-no-signers.golden diff --git a/cli/command/trust/testdata/trust-inspect-pretty-full-repo-with-signers.golden b/cmd/docker-trust/trust/testdata/trust-inspect-pretty-full-repo-with-signers.golden similarity index 100% rename from cli/command/trust/testdata/trust-inspect-pretty-full-repo-with-signers.golden rename to cmd/docker-trust/trust/testdata/trust-inspect-pretty-full-repo-with-signers.golden diff --git a/cli/command/trust/testdata/trust-inspect-pretty-one-tag-no-signers.golden b/cmd/docker-trust/trust/testdata/trust-inspect-pretty-one-tag-no-signers.golden similarity index 100% rename from cli/command/trust/testdata/trust-inspect-pretty-one-tag-no-signers.golden rename to cmd/docker-trust/trust/testdata/trust-inspect-pretty-one-tag-no-signers.golden diff --git a/cli/command/trust/testdata/trust-inspect-pretty-unsigned-tag-with-signers.golden b/cmd/docker-trust/trust/testdata/trust-inspect-pretty-unsigned-tag-with-signers.golden similarity index 100% rename from cli/command/trust/testdata/trust-inspect-pretty-unsigned-tag-with-signers.golden rename to cmd/docker-trust/trust/testdata/trust-inspect-pretty-unsigned-tag-with-signers.golden diff --git a/cli/command/trust/testdata/trust-inspect-uninitialized.golden b/cmd/docker-trust/trust/testdata/trust-inspect-uninitialized.golden similarity index 100% rename from cli/command/trust/testdata/trust-inspect-uninitialized.golden rename to cmd/docker-trust/trust/testdata/trust-inspect-uninitialized.golden diff --git a/cli/command/trust/testdata/trust-inspect-unsigned-tag-with-signers.golden b/cmd/docker-trust/trust/testdata/trust-inspect-unsigned-tag-with-signers.golden similarity index 100% rename from cli/command/trust/testdata/trust-inspect-unsigned-tag-with-signers.golden rename to cmd/docker-trust/trust/testdata/trust-inspect-unsigned-tag-with-signers.golden diff --git a/cli/command/trust/testdata/trust-revoke-prompt-termination.golden b/cmd/docker-trust/trust/testdata/trust-revoke-prompt-termination.golden similarity index 100% rename from cli/command/trust/testdata/trust-revoke-prompt-termination.golden rename to cmd/docker-trust/trust/testdata/trust-revoke-prompt-termination.golden diff --git a/docs/reference/commandline/docker.md b/docs/reference/commandline/docker.md index 69b1c91303be..da4c731e28e3 100644 --- a/docs/reference/commandline/docker.md +++ b/docs/reference/commandline/docker.md @@ -59,7 +59,6 @@ The base command for the Docker CLI. | [`system`](system.md) | Manage Docker | | [`tag`](tag.md) | Create a tag TARGET_IMAGE that refers to SOURCE_IMAGE | | [`top`](top.md) | Display the running processes of a container | -| [`trust`](trust.md) | Manage trust on Docker images | | [`unpause`](unpause.md) | Unpause all processes within one or more containers | | [`update`](update.md) | Update configuration of one or more containers | | [`version`](version.md) | Show the Docker version information | diff --git a/e2e/global/cli_test.go b/e2e/global/cli_test.go index f79acd1aa76b..07e37a317f16 100644 --- a/e2e/global/cli_test.go +++ b/e2e/global/cli_test.go @@ -240,7 +240,7 @@ func TestPromptExitCode(t *testing.T) { case <-writeDone: buf.Reset() assert.NilError(t, bufioWriter.Flush()) - assert.Equal(t, buf.String(), "\n", "expected a new line after the process exits from SIGINT") + assert.Equal(t, strings.HasSuffix(buf.String(), "\n"), true, "expected a new line after the process exits from SIGINT") } }) } diff --git a/scripts/build/trust-plugin b/scripts/build/trust-plugin new file mode 100755 index 000000000000..beb8487b25a5 --- /dev/null +++ b/scripts/build/trust-plugin @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +# +# Build plugins examples for the host OS/ARCH +# + +set -eu -o pipefail + +# Disable CGO - we don't need it for these plugins. +# +# Important: this must be done before sourcing "./scripts/build/.variables", +# because some other variables are conditionally set whether CGO is enabled. +export CGO_ENABLED=0 + +source ./scripts/build/.variables + +TARGET_PLUGIN="$(dirname "${TARGET}")/docker-trust-${GOOS}-${GOARCH}" +mkdir -p "$(dirname "${TARGET_PLUGIN}")" + +echo "Building $GO_LINKMODE $(basename "${TARGET_PLUGIN}")" +(set -x ; GO111MODULE=auto go build -o "${TARGET_PLUGIN}" -tags "${GO_BUILDTAGS}" -ldflags "${GO_LDFLAGS}" ${GO_BUILDMODE} "github.com/docker/cli/cmd/docker-trust") + +ln -sf "$(basename "${TARGET_PLUGIN}")" "$(dirname "${TARGET}")/docker-trust"