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/cmd/docker-trust/internal/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 }