Merge pull request #5842 from jsternberg/otel-resource-attributes-merge
cli-plugins: merge OTEL_RESOURCE_ATTRIBUTES environment variable
This commit is contained in:
commit
fe0a8d2791
@ -2,14 +2,15 @@ package manager
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/spf13/cobra"
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/baggage"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -106,7 +107,7 @@ func AddPluginCommandStubs(dockerCli command.Cli, rootCmd *cobra.Command) (err e
|
||||
}
|
||||
|
||||
const (
|
||||
dockerCliAttributePrefix = attribute.Key("docker.cli")
|
||||
dockerCliAttributePrefix = command.DockerCliAttributePrefix
|
||||
|
||||
cobraCommandPath = attribute.Key("cobra.command_path")
|
||||
)
|
||||
@ -125,7 +126,7 @@ func getPluginResourceAttributes(cmd *cobra.Command, plugin Plugin) attribute.Se
|
||||
for iter := attrSet.Iter(); iter.Next(); {
|
||||
attr := iter.Attribute()
|
||||
kvs = append(kvs, attribute.KeyValue{
|
||||
Key: dockerCliAttributePrefix + "." + attr.Key,
|
||||
Key: dockerCliAttributePrefix + attr.Key,
|
||||
Value: attr.Value,
|
||||
})
|
||||
}
|
||||
@ -134,14 +135,37 @@ func getPluginResourceAttributes(cmd *cobra.Command, plugin Plugin) attribute.Se
|
||||
|
||||
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())
|
||||
// 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(); {
|
||||
i, v := iter.IndexedAttribute()
|
||||
attrsSlice[i] = string(v.Key) + "=" + url.PathEscape(v.Value.AsString())
|
||||
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, ","))
|
||||
}
|
||||
env = append(env, ResourceAttributesEnvvar+"="+strings.Join(attrsSlice, ","))
|
||||
}
|
||||
return env
|
||||
}
|
||||
|
26
cli-plugins/manager/cobra_test.go
Normal file
26
cli-plugins/manager/cobra_test.go
Normal file
@ -0,0 +1,26 @@
|
||||
package manager
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestPluginResourceAttributesEnvvar(t *testing.T) {
|
||||
cmd := &cobra.Command{
|
||||
Annotations: map[string]string{
|
||||
cobra.CommandDisplayNameAnnotation: "docker",
|
||||
},
|
||||
}
|
||||
|
||||
// Ensure basic usage is fine.
|
||||
env := appendPluginResourceAttributesEnvvar(nil, cmd, Plugin{Name: "compose"})
|
||||
assert.DeepEqual(t, []string{"OTEL_RESOURCE_ATTRIBUTES=docker.cli.cobra.command_path=docker%20compose"}, env)
|
||||
|
||||
// Add a user-based environment variable to OTEL_RESOURCE_ATTRIBUTES.
|
||||
t.Setenv("OTEL_RESOURCE_ATTRIBUTES", "a.b.c=foo")
|
||||
|
||||
env = appendPluginResourceAttributesEnvvar(nil, cmd, Plugin{Name: "compose"})
|
||||
assert.DeepEqual(t, []string{"OTEL_RESOURCE_ATTRIBUTES=a.b.c=foo,docker.cli.cobra.command_path=docker%20compose"}, env)
|
||||
}
|
@ -26,7 +26,7 @@ const (
|
||||
|
||||
// ResourceAttributesEnvvar is the name of the envvar that includes additional
|
||||
// resource attributes for OTEL.
|
||||
ResourceAttributesEnvvar = "OTEL_RESOURCE_ATTRIBUTES"
|
||||
ResourceAttributesEnvvar = command.ResourceAttributesEnvvar
|
||||
)
|
||||
|
||||
// errPluginNotFound is the error returned when a plugin could not be found.
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@ -292,6 +293,7 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions, ops ...CLIOption)
|
||||
if cli.enableGlobalTracer {
|
||||
cli.createGlobalTracerProvider(cli.baseCtx)
|
||||
}
|
||||
filterResourceAttributesEnvvar()
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -591,3 +593,46 @@ func DefaultContextStoreConfig() store.Config {
|
||||
defaultStoreEndpoints...,
|
||||
)
|
||||
}
|
||||
|
||||
const (
|
||||
// ResourceAttributesEnvvar is the name of the envvar that includes additional
|
||||
// resource attributes for OTEL.
|
||||
ResourceAttributesEnvvar = "OTEL_RESOURCE_ATTRIBUTES"
|
||||
|
||||
// DockerCliAttributePrefix is the prefix for any docker cli OTEL attributes.
|
||||
DockerCliAttributePrefix = "docker.cli."
|
||||
)
|
||||
|
||||
func filterResourceAttributesEnvvar() {
|
||||
if v := os.Getenv(ResourceAttributesEnvvar); v != "" {
|
||||
if filtered := filterResourceAttributes(v); filtered != "" {
|
||||
os.Setenv(ResourceAttributesEnvvar, filtered)
|
||||
} else {
|
||||
os.Unsetenv(ResourceAttributesEnvvar)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func filterResourceAttributes(s string) string {
|
||||
if trimmed := strings.TrimSpace(s); trimmed == "" {
|
||||
return trimmed
|
||||
}
|
||||
|
||||
pairs := strings.Split(s, ",")
|
||||
elems := make([]string, 0, len(pairs))
|
||||
for _, p := range pairs {
|
||||
k, _, found := strings.Cut(p, "=")
|
||||
if !found {
|
||||
// Do not interact with invalid otel resources.
|
||||
elems = append(elems, p)
|
||||
continue
|
||||
}
|
||||
|
||||
// Skip attributes that have our docker.cli prefix.
|
||||
if strings.HasPrefix(k, DockerCliAttributePrefix) {
|
||||
continue
|
||||
}
|
||||
elems = append(elems, p)
|
||||
}
|
||||
return strings.Join(elems, ",")
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user