package manager import ( "fmt" "os" "strings" "github.com/docker/cli/cli-plugins/metadata" "github.com/spf13/cobra" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/baggage" ) const ( // resourceAttributesEnvVar is the name of the envvar that includes additional // resource attributes for OTEL as defined in the [OpenTelemetry specification]. // // [OpenTelemetry specification]: https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/#general-sdk-configuration resourceAttributesEnvVar = "OTEL_RESOURCE_ATTRIBUTES" // dockerCLIAttributePrefix is the prefix for any docker cli OTEL attributes. // // It is a copy of the const defined in [command.dockerCLIAttributePrefix]. dockerCLIAttributePrefix = "docker.cli." cobraCommandPath = attribute.Key("cobra.command_path") ) func getPluginResourceAttributes(cmd *cobra.Command, plugin Plugin) attribute.Set { commandPath := cmd.Annotations[metadata.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 { // Construct baggage members for each of the attributes. // Ignore any failures as these aren't significant and // represent an internal issue. members := make([]baggage.Member, 0, attrs.Len()) for iter := attrs.Iter(); iter.Next(); { attr := iter.Attribute() m, err := baggage.NewMemberRaw(string(attr.Key), attr.Value.AsString()) if err != nil { otel.Handle(err) continue } members = append(members, m) } // Combine plugin added resource attributes with ones found in the environment // variable. Our own attributes should be namespaced so there shouldn't be a // conflict. We do not parse the environment variable because we do not want // to handle errors in user configuration. attrsSlice := make([]string, 0, 2) if v := strings.TrimSpace(os.Getenv(resourceAttributesEnvVar)); v != "" { attrsSlice = append(attrsSlice, v) } if b, err := baggage.New(members...); err != nil { otel.Handle(err) } else if b.Len() > 0 { attrsSlice = append(attrsSlice, b.String()) } if len(attrsSlice) > 0 { env = append(env, resourceAttributesEnvVar+"="+strings.Join(attrsSlice, ",")) } } return env }