diff --git a/cli-plugins/manager/cobra.go b/cli-plugins/manager/cobra.go index be3a058201..feff8a8fd6 100644 --- a/cli-plugins/manager/cobra.go +++ b/cli-plugins/manager/cobra.go @@ -2,11 +2,14 @@ package manager import ( "fmt" + "net/url" "os" + "strings" "sync" "github.com/docker/cli/cli/command" "github.com/spf13/cobra" + "go.opentelemetry.io/otel/attribute" ) const ( @@ -30,6 +33,10 @@ const ( // is, one which failed it's candidate test) and contains the // reason for the failure. CommandAnnotationPluginInvalid = "com.docker.cli.plugin-invalid" + + // CommandAnnotationPluginCommandPath is added to overwrite the + // command path for a plugin invocation. + CommandAnnotationPluginCommandPath = "com.docker.cli.plugin.command_path" ) var pluginCommandStubsOnce sync.Once @@ -98,3 +105,44 @@ func AddPluginCommandStubs(dockerCli command.Cli, rootCmd *cobra.Command) (err e }) return err } + +const ( + dockerCliAttributePrefix = attribute.Key("docker.cli") + + cobraCommandPath = attribute.Key("cobra.command_path") +) + +func getPluginResourceAttributes(cmd *cobra.Command, plugin Plugin) attribute.Set { + commandPath := cmd.Annotations[CommandAnnotationPluginCommandPath] + if commandPath == "" { + commandPath = fmt.Sprintf("%s %s", cmd.CommandPath(), plugin.Name) + } + + attrSet := attribute.NewSet( + cobraCommandPath.String(commandPath), + ) + + kvs := make([]attribute.KeyValue, 0, attrSet.Len()) + for iter := attrSet.Iter(); iter.Next(); { + attr := iter.Attribute() + kvs = append(kvs, attribute.KeyValue{ + Key: dockerCliAttributePrefix + "." + attr.Key, + Value: attr.Value, + }) + } + return attribute.NewSet(kvs...) +} + +func appendPluginResourceAttributesEnvvar(env []string, cmd *cobra.Command, plugin Plugin) []string { + if attrs := getPluginResourceAttributes(cmd, plugin); attrs.Len() > 0 { + // values in environment variables need to be in baggage format + // otel/baggage package can be used after update to v1.22, currently it encodes incorrectly + attrsSlice := make([]string, attrs.Len()) + for iter := attrs.Iter(); iter.Next(); { + i, v := iter.IndexedAttribute() + attrsSlice[i] = string(v.Key) + "=" + url.PathEscape(v.Value.AsString()) + } + env = append(env, ResourceAttributesEnvvar+"="+strings.Join(attrsSlice, ",")) + } + return env +} diff --git a/cli-plugins/manager/manager.go b/cli-plugins/manager/manager.go index d23934940d..d3b7b7b526 100644 --- a/cli-plugins/manager/manager.go +++ b/cli-plugins/manager/manager.go @@ -17,11 +17,17 @@ import ( "golang.org/x/sync/errgroup" ) -// ReexecEnvvar is the name of an ennvar which is set to the command -// used to originally invoke the docker CLI when executing a -// plugin. Assuming $PATH and $CWD remain unchanged this should allow -// the plugin to re-execute the original CLI. -const ReexecEnvvar = "DOCKER_CLI_PLUGIN_ORIGINAL_CLI_COMMAND" +const ( + // ReexecEnvvar is the name of an ennvar which is set to the command + // used to originally invoke the docker CLI when executing a + // plugin. Assuming $PATH and $CWD remain unchanged this should allow + // the plugin to re-execute the original CLI. + ReexecEnvvar = "DOCKER_CLI_PLUGIN_ORIGINAL_CLI_COMMAND" + + // ResourceAttributesEnvvar is the name of the envvar that includes additional + // resource attributes for OTEL. + ResourceAttributesEnvvar = "OTEL_RESOURCE_ATTRIBUTES" +) // errPluginNotFound is the error returned when a plugin could not be found. type errPluginNotFound string @@ -236,6 +242,7 @@ func PluginRunCommand(dockerCli command.Cli, name string, rootcmd *cobra.Command cmd.Env = os.Environ() cmd.Env = append(cmd.Env, ReexecEnvvar+"="+os.Args[0]) + cmd.Env = appendPluginResourceAttributesEnvvar(cmd.Env, rootcmd, plugin) return cmd, nil } diff --git a/cmd/docker/builder.go b/cmd/docker/builder.go index 7807f80b1a..ad28d655cc 100644 --- a/cmd/docker/builder.go +++ b/cmd/docker/builder.go @@ -69,7 +69,7 @@ func processBuilder(dockerCli command.Cli, cmd *cobra.Command, args, osargs []st } // is this a build that should be forwarded to the builder? - fwargs, fwosargs, forwarded := forwardBuilder(builderAlias, args, osargs) + fwargs, fwosargs, alias, forwarded := forwardBuilder(builderAlias, args, osargs) if !forwarded { return args, osargs, nil, nil } @@ -116,10 +116,13 @@ func processBuilder(dockerCli command.Cli, cmd *cobra.Command, args, osargs []st envs = append([]string{"BUILDX_BUILDER=" + dockerCli.CurrentContext()}, envs...) } + // overwrite the command path for this plugin using the alias name. + cmd.Annotations[pluginmanager.CommandAnnotationPluginCommandPath] = fmt.Sprintf("%s %s", cmd.CommandPath(), alias) + return fwargs, fwosargs, envs, nil } -func forwardBuilder(alias string, args, osargs []string) ([]string, []string, bool) { +func forwardBuilder(alias string, args, osargs []string) ([]string, []string, string, bool) { aliases := [][2][]string{ { {"builder"}, @@ -137,10 +140,10 @@ func forwardBuilder(alias string, args, osargs []string) ([]string, []string, bo for _, al := range aliases { if fwargs, changed := command.StringSliceReplaceAt(args, al[0], al[1], 0); changed { fwosargs, _ := command.StringSliceReplaceAt(osargs, al[0], al[1], -1) - return fwargs, fwosargs, true + return fwargs, fwosargs, al[0][0], true } } - return args, osargs, false + return args, osargs, "", false } // hasBuilderName checks if a builder name is defined in args or env vars diff --git a/vendor.mod b/vendor.mod index fa684ca784..33ed56e342 100644 --- a/vendor.mod +++ b/vendor.mod @@ -37,6 +37,7 @@ require ( github.com/theupdateframework/notary v0.7.1-0.20210315103452-bf96a202a09a github.com/tonistiigi/go-rosetta v0.0.0-20200727161949-f79598599c5d github.com/xeipuuv/gojsonschema v1.2.0 + go.opentelemetry.io/otel v1.21.0 golang.org/x/sync v0.6.0 golang.org/x/sys v0.16.0 golang.org/x/term v0.15.0 @@ -77,7 +78,6 @@ require ( github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect go.etcd.io/etcd/raft/v3 v3.5.6 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 // indirect - go.opentelemetry.io/otel v1.21.0 // indirect go.opentelemetry.io/otel/metric v1.21.0 // indirect go.opentelemetry.io/otel/trace v1.21.0 // indirect golang.org/x/crypto v0.17.0 // indirect