From e5a288caaa74a18d848c24cd37d65f8450c15077 Mon Sep 17 00:00:00 2001 From: Cory Bennett Date: Fri, 28 Mar 2025 15:53:19 -0700 Subject: [PATCH 1/4] feat(provisioner): propagate trace info If tracing is enabled, propagate the trace information to the terraform provisioner via environment variables. This sets the `TRACEPARENT` environment variable using the default W3C trace propagators. Users can choose to continue the trace by adding new spans in the provisioner by reading from the environment like: ctx := env.ContextWithRemoteSpanContext(context.Background(), os.Environ()) --- provisioner/terraform/otelenv.go | 74 +++++++++++++++++++++++++++ provisioner/terraform/otelenv_test.go | 63 +++++++++++++++++++++++ provisioner/terraform/provision.go | 2 + 3 files changed, 139 insertions(+) create mode 100644 provisioner/terraform/otelenv.go create mode 100644 provisioner/terraform/otelenv_test.go diff --git a/provisioner/terraform/otelenv.go b/provisioner/terraform/otelenv.go new file mode 100644 index 0000000000000..1402f169183cc --- /dev/null +++ b/provisioner/terraform/otelenv.go @@ -0,0 +1,74 @@ +package terraform + +import ( + "context" + "fmt" + "slices" + "strings" + "unicode" + + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/propagation" +) + +// TODO: replace this with the upstream OTEL env propagation when it is +// released. + +// envCarrier is a propagation.TextMapCarrier that is used to extract or +// inject tracing environment variables. This is used with a +// propagation.TextMapPropagator +type envCarrier struct { + Env []string +} + +var _ propagation.TextMapCarrier = (*envCarrier)(nil) + +func toKey(key string) string { + key = strings.ToUpper(key) + key = strings.ReplaceAll(key, "-", "_") + return strings.Map(func(r rune) rune { + if unicode.IsLetter(r) || unicode.IsNumber(r) || r == '_' { + return r + } + return -1 + }, key) +} + +func (c *envCarrier) Set(key, value string) { + if c == nil { + return + } + key = toKey(key) + for i, e := range c.Env { + if strings.HasPrefix(e, key+"=") { + // don't directly update the slice so we don't modify the slice + // passed in + newEnv := slices.Clone(c.Env) + newEnv = append(newEnv[:i], append([]string{fmt.Sprintf("%s=%s", key, value)}, newEnv[i+1:]...)...) + c.Env = newEnv + return + } + } + c.Env = append(c.Env, fmt.Sprintf("%s=%s", key, value)) +} + +func (*envCarrier) Get(_ string) string { + // Get not necessary to inject environment variables + panic("Not implemented") +} + +func (*envCarrier) Keys() []string { + // Keys not necessary to inject environment variables + panic("Not implemented") +} + +// otelEnvInject will add add any necessary environment variables for the span +// found in the Context. If environment variables are already present +// in `environ` then they will be updated. If no variables are found the +// new ones will be appended. The new environment will be returned, `environ` +// will never be modified. +func otelEnvInject(ctx context.Context, environ []string) []string { + c := &envCarrier{Env: environ} + otel.GetTextMapPropagator().Inject(ctx, c) + return c.Env +} diff --git a/provisioner/terraform/otelenv_test.go b/provisioner/terraform/otelenv_test.go new file mode 100644 index 0000000000000..ef900c0b2d26c --- /dev/null +++ b/provisioner/terraform/otelenv_test.go @@ -0,0 +1,63 @@ +package terraform // nolint:testpackage + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/propagation" + sdktrace "go.opentelemetry.io/otel/sdk/trace" + "go.opentelemetry.io/otel/trace" +) + +type testIDGenerator struct{} + +var _ sdktrace.IDGenerator = (*testIDGenerator)(nil) + +func (testIDGenerator) NewIDs(ctx context.Context) (trace.TraceID, trace.SpanID) { + traceID, _ := trace.TraceIDFromHex("60d19e9e9abf2197c1d6d8f93e28ee2a") + spanID, _ := trace.SpanIDFromHex("a028bd951229a46f") + return traceID, spanID +} + +func (testIDGenerator) NewSpanID(ctx context.Context, traceID trace.TraceID) trace.SpanID { + spanID, _ := trace.SpanIDFromHex("a028bd951229a46f") + return spanID +} + +func TestOtelEnvInject(t *testing.T) { + t.Parallel() + testTraceProvider := sdktrace.NewTracerProvider( + sdktrace.WithSampler(sdktrace.AlwaysSample()), + sdktrace.WithIDGenerator(testIDGenerator{}), + ) + + tracer := testTraceProvider.Tracer("example") + ctx, span := tracer.Start(context.Background(), "testing") + defer span.End() + + input := []string{"PATH=/usr/bin:/bin"} + + otel.SetTextMapPropagator(propagation.TraceContext{}) + got := otelEnvInject(ctx, input) + require.Equal(t, []string{ + "PATH=/usr/bin:/bin", + "TRACEPARENT=00-60d19e9e9abf2197c1d6d8f93e28ee2a-a028bd951229a46f-01", + }, got) + + // verify we update rather than append + input = []string{ + "PATH=/usr/bin:/bin", + "TRACEPARENT=origTraceParent", + "TERM=xterm", + } + + otel.SetTextMapPropagator(propagation.TraceContext{}) + got = otelEnvInject(ctx, input) + require.Equal(t, []string{ + "PATH=/usr/bin:/bin", + "TRACEPARENT=00-60d19e9e9abf2197c1d6d8f93e28ee2a-a028bd951229a46f-01", + "TERM=xterm", + }, got) +} diff --git a/provisioner/terraform/provision.go b/provisioner/terraform/provision.go index 78068fc43c819..171deb35c4bbc 100644 --- a/provisioner/terraform/provision.go +++ b/provisioner/terraform/provision.go @@ -156,6 +156,7 @@ func (s *server) Plan( if err != nil { return provisionersdk.PlanErrorf("setup env: %s", err) } + env = otelEnvInject(ctx, env) vars, err := planVars(request) if err != nil { @@ -208,6 +209,7 @@ func (s *server) Apply( if err != nil { return provisionersdk.ApplyErrorf("provision env: %s", err) } + env = otelEnvInject(ctx, env) resp, err := e.apply( ctx, killCtx, env, sess, ) From fbab87757fd043cf94b4dc472093ec031a05f52b Mon Sep 17 00:00:00 2001 From: coryb Date: Mon, 7 Apr 2025 07:49:24 -0700 Subject: [PATCH 2/4] Update provisioner/terraform/otelenv_test.go Co-authored-by: Spike Curtis --- provisioner/terraform/otelenv_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/provisioner/terraform/otelenv_test.go b/provisioner/terraform/otelenv_test.go index ef900c0b2d26c..f5f9249263cee 100644 --- a/provisioner/terraform/otelenv_test.go +++ b/provisioner/terraform/otelenv_test.go @@ -21,7 +21,7 @@ func (testIDGenerator) NewIDs(ctx context.Context) (trace.TraceID, trace.SpanID) return traceID, spanID } -func (testIDGenerator) NewSpanID(ctx context.Context, traceID trace.TraceID) trace.SpanID { +func (testIDGenerator) NewSpanID(_ context.Context, _ trace.TraceID) trace.SpanID { spanID, _ := trace.SpanIDFromHex("a028bd951229a46f") return spanID } From 0a48cc5d37487cb9c1df6309a5445ad396f715c8 Mon Sep 17 00:00:00 2001 From: coryb Date: Mon, 7 Apr 2025 07:49:35 -0700 Subject: [PATCH 3/4] Update provisioner/terraform/otelenv_test.go Co-authored-by: Spike Curtis --- provisioner/terraform/otelenv_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/provisioner/terraform/otelenv_test.go b/provisioner/terraform/otelenv_test.go index f5f9249263cee..521856f1c3a91 100644 --- a/provisioner/terraform/otelenv_test.go +++ b/provisioner/terraform/otelenv_test.go @@ -15,7 +15,7 @@ type testIDGenerator struct{} var _ sdktrace.IDGenerator = (*testIDGenerator)(nil) -func (testIDGenerator) NewIDs(ctx context.Context) (trace.TraceID, trace.SpanID) { +func (testIDGenerator) NewIDs(_ context.Context) (trace.TraceID, trace.SpanID) { traceID, _ := trace.TraceIDFromHex("60d19e9e9abf2197c1d6d8f93e28ee2a") spanID, _ := trace.SpanIDFromHex("a028bd951229a46f") return traceID, spanID From 228410861961be1768ab3dcf78c4e0f9ebde2f04 Mon Sep 17 00:00:00 2001 From: Cory Bennett Date: Mon, 7 Apr 2025 16:51:08 +0000 Subject: [PATCH 4/4] update for feedback, remove unimplemented panics Simplified the slice copy/update logic. Removed the panics for non required interface functions, the impl was trivial, added simple tests to ensure they work as expected. --- provisioner/terraform/otelenv.go | 32 +++++++++++++------ ...elenv_test.go => otelenv_internal_test.go} | 24 +++++++++++++- 2 files changed, 46 insertions(+), 10 deletions(-) rename provisioner/terraform/{otelenv_test.go => otelenv_internal_test.go} (77%) diff --git a/provisioner/terraform/otelenv.go b/provisioner/terraform/otelenv.go index 1402f169183cc..681df25490854 100644 --- a/provisioner/terraform/otelenv.go +++ b/provisioner/terraform/otelenv.go @@ -43,23 +43,37 @@ func (c *envCarrier) Set(key, value string) { if strings.HasPrefix(e, key+"=") { // don't directly update the slice so we don't modify the slice // passed in - newEnv := slices.Clone(c.Env) - newEnv = append(newEnv[:i], append([]string{fmt.Sprintf("%s=%s", key, value)}, newEnv[i+1:]...)...) - c.Env = newEnv + c.Env = slices.Clone(c.Env) + c.Env[i] = fmt.Sprintf("%s=%s", key, value) return } } c.Env = append(c.Env, fmt.Sprintf("%s=%s", key, value)) } -func (*envCarrier) Get(_ string) string { - // Get not necessary to inject environment variables - panic("Not implemented") +func (c *envCarrier) Get(key string) string { + if c == nil { + return "" + } + key = toKey(key) + for _, e := range c.Env { + if strings.HasPrefix(e, key+"=") { + return strings.TrimPrefix(e, key+"=") + } + } + return "" } -func (*envCarrier) Keys() []string { - // Keys not necessary to inject environment variables - panic("Not implemented") +func (c *envCarrier) Keys() []string { + if c == nil { + return nil + } + keys := make([]string, len(c.Env)) + for i, e := range c.Env { + k, _, _ := strings.Cut(e, "=") + keys[i] = k + } + return keys } // otelEnvInject will add add any necessary environment variables for the span diff --git a/provisioner/terraform/otelenv_test.go b/provisioner/terraform/otelenv_internal_test.go similarity index 77% rename from provisioner/terraform/otelenv_test.go rename to provisioner/terraform/otelenv_internal_test.go index 521856f1c3a91..57be6e4cd0cc6 100644 --- a/provisioner/terraform/otelenv_test.go +++ b/provisioner/terraform/otelenv_internal_test.go @@ -1,4 +1,4 @@ -package terraform // nolint:testpackage +package terraform import ( "context" @@ -61,3 +61,25 @@ func TestOtelEnvInject(t *testing.T) { "TERM=xterm", }, got) } + +func TestEnvCarrierSet(t *testing.T) { + t.Parallel() + c := &envCarrier{ + Env: []string{"PATH=/usr/bin:/bin", "TERM=xterm"}, + } + c.Set("PATH", "/usr/local/bin") + c.Set("NEWVAR", "newval") + require.Equal(t, []string{ + "PATH=/usr/local/bin", + "TERM=xterm", + "NEWVAR=newval", + }, c.Env) +} + +func TestEnvCarrierKeys(t *testing.T) { + t.Parallel() + c := &envCarrier{ + Env: []string{"PATH=/usr/bin:/bin", "TERM=xterm"}, + } + require.Equal(t, []string{"PATH", "TERM"}, c.Keys()) +} pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy