Skip to content

Commit 12e5718

Browse files
corybspikecurtis
andauthored
feat(provisioner): propagate trace info (#17166)
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()) --------- Co-authored-by: Spike Curtis <spike@spikecurtis.com>
1 parent 9eeb506 commit 12e5718

File tree

3 files changed

+175
-0
lines changed

3 files changed

+175
-0
lines changed

provisioner/terraform/otelenv.go

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package terraform
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"slices"
7+
"strings"
8+
"unicode"
9+
10+
"go.opentelemetry.io/otel"
11+
"go.opentelemetry.io/otel/propagation"
12+
)
13+
14+
// TODO: replace this with the upstream OTEL env propagation when it is
15+
// released.
16+
17+
// envCarrier is a propagation.TextMapCarrier that is used to extract or
18+
// inject tracing environment variables. This is used with a
19+
// propagation.TextMapPropagator
20+
type envCarrier struct {
21+
Env []string
22+
}
23+
24+
var _ propagation.TextMapCarrier = (*envCarrier)(nil)
25+
26+
func toKey(key string) string {
27+
key = strings.ToUpper(key)
28+
key = strings.ReplaceAll(key, "-", "_")
29+
return strings.Map(func(r rune) rune {
30+
if unicode.IsLetter(r) || unicode.IsNumber(r) || r == '_' {
31+
return r
32+
}
33+
return -1
34+
}, key)
35+
}
36+
37+
func (c *envCarrier) Set(key, value string) {
38+
if c == nil {
39+
return
40+
}
41+
key = toKey(key)
42+
for i, e := range c.Env {
43+
if strings.HasPrefix(e, key+"=") {
44+
// don't directly update the slice so we don't modify the slice
45+
// passed in
46+
c.Env = slices.Clone(c.Env)
47+
c.Env[i] = fmt.Sprintf("%s=%s", key, value)
48+
return
49+
}
50+
}
51+
c.Env = append(c.Env, fmt.Sprintf("%s=%s", key, value))
52+
}
53+
54+
func (c *envCarrier) Get(key string) string {
55+
if c == nil {
56+
return ""
57+
}
58+
key = toKey(key)
59+
for _, e := range c.Env {
60+
if strings.HasPrefix(e, key+"=") {
61+
return strings.TrimPrefix(e, key+"=")
62+
}
63+
}
64+
return ""
65+
}
66+
67+
func (c *envCarrier) Keys() []string {
68+
if c == nil {
69+
return nil
70+
}
71+
keys := make([]string, len(c.Env))
72+
for i, e := range c.Env {
73+
k, _, _ := strings.Cut(e, "=")
74+
keys[i] = k
75+
}
76+
return keys
77+
}
78+
79+
// otelEnvInject will add add any necessary environment variables for the span
80+
// found in the Context. If environment variables are already present
81+
// in `environ` then they will be updated. If no variables are found the
82+
// new ones will be appended. The new environment will be returned, `environ`
83+
// will never be modified.
84+
func otelEnvInject(ctx context.Context, environ []string) []string {
85+
c := &envCarrier{Env: environ}
86+
otel.GetTextMapPropagator().Inject(ctx, c)
87+
return c.Env
88+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package terraform
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/stretchr/testify/require"
8+
"go.opentelemetry.io/otel"
9+
"go.opentelemetry.io/otel/propagation"
10+
sdktrace "go.opentelemetry.io/otel/sdk/trace"
11+
"go.opentelemetry.io/otel/trace"
12+
)
13+
14+
type testIDGenerator struct{}
15+
16+
var _ sdktrace.IDGenerator = (*testIDGenerator)(nil)
17+
18+
func (testIDGenerator) NewIDs(_ context.Context) (trace.TraceID, trace.SpanID) {
19+
traceID, _ := trace.TraceIDFromHex("60d19e9e9abf2197c1d6d8f93e28ee2a")
20+
spanID, _ := trace.SpanIDFromHex("a028bd951229a46f")
21+
return traceID, spanID
22+
}
23+
24+
func (testIDGenerator) NewSpanID(_ context.Context, _ trace.TraceID) trace.SpanID {
25+
spanID, _ := trace.SpanIDFromHex("a028bd951229a46f")
26+
return spanID
27+
}
28+
29+
func TestOtelEnvInject(t *testing.T) {
30+
t.Parallel()
31+
testTraceProvider := sdktrace.NewTracerProvider(
32+
sdktrace.WithSampler(sdktrace.AlwaysSample()),
33+
sdktrace.WithIDGenerator(testIDGenerator{}),
34+
)
35+
36+
tracer := testTraceProvider.Tracer("example")
37+
ctx, span := tracer.Start(context.Background(), "testing")
38+
defer span.End()
39+
40+
input := []string{"PATH=/usr/bin:/bin"}
41+
42+
otel.SetTextMapPropagator(propagation.TraceContext{})
43+
got := otelEnvInject(ctx, input)
44+
require.Equal(t, []string{
45+
"PATH=/usr/bin:/bin",
46+
"TRACEPARENT=00-60d19e9e9abf2197c1d6d8f93e28ee2a-a028bd951229a46f-01",
47+
}, got)
48+
49+
// verify we update rather than append
50+
input = []string{
51+
"PATH=/usr/bin:/bin",
52+
"TRACEPARENT=origTraceParent",
53+
"TERM=xterm",
54+
}
55+
56+
otel.SetTextMapPropagator(propagation.TraceContext{})
57+
got = otelEnvInject(ctx, input)
58+
require.Equal(t, []string{
59+
"PATH=/usr/bin:/bin",
60+
"TRACEPARENT=00-60d19e9e9abf2197c1d6d8f93e28ee2a-a028bd951229a46f-01",
61+
"TERM=xterm",
62+
}, got)
63+
}
64+
65+
func TestEnvCarrierSet(t *testing.T) {
66+
t.Parallel()
67+
c := &envCarrier{
68+
Env: []string{"PATH=/usr/bin:/bin", "TERM=xterm"},
69+
}
70+
c.Set("PATH", "/usr/local/bin")
71+
c.Set("NEWVAR", "newval")
72+
require.Equal(t, []string{
73+
"PATH=/usr/local/bin",
74+
"TERM=xterm",
75+
"NEWVAR=newval",
76+
}, c.Env)
77+
}
78+
79+
func TestEnvCarrierKeys(t *testing.T) {
80+
t.Parallel()
81+
c := &envCarrier{
82+
Env: []string{"PATH=/usr/bin:/bin", "TERM=xterm"},
83+
}
84+
require.Equal(t, []string{"PATH", "TERM"}, c.Keys())
85+
}

provisioner/terraform/provision.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ func (s *server) Plan(
156156
if err != nil {
157157
return provisionersdk.PlanErrorf("setup env: %s", err)
158158
}
159+
env = otelEnvInject(ctx, env)
159160

160161
vars, err := planVars(request)
161162
if err != nil {
@@ -208,6 +209,7 @@ func (s *server) Apply(
208209
if err != nil {
209210
return provisionersdk.ApplyErrorf("provision env: %s", err)
210211
}
212+
env = otelEnvInject(ctx, env)
211213
resp, err := e.apply(
212214
ctx, killCtx, env, sess,
213215
)

0 commit comments

Comments
 (0)
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