| 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 | |
| } | |
| if userTerminatedErr, ok := errors.AsType[errCtxSignalTerminated](err); ok { | |
| 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 | |
| } |