Skip to content

implement docker trust as plugin #6121

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
9 changes: 9 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 0 additions & 2 deletions cli/command/commands/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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
Expand Down
114 changes: 114 additions & 0 deletions cmd/docker-trust/main.go
Original file line number Diff line number Diff line change
@@ -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
}
File renamed without changes.
82 changes: 82 additions & 0 deletions cmd/docker-trust/trust/commands.go
Original file line number Diff line number Diff line change
@@ -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
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
1 change: 0 additions & 1 deletion docs/reference/commandline/docker.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down
2 changes: 1 addition & 1 deletion e2e/global/cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
})
}
Expand Down
22 changes: 22 additions & 0 deletions scripts/build/trust-plugin
Original file line number Diff line number Diff line change
@@ -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"
Loading