Skip to content

Commit 80b1d7c

Browse files
committed
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())
1 parent 9bc727e commit 80b1d7c

File tree

3 files changed

+139
-0
lines changed

3 files changed

+139
-0
lines changed

provisioner/terraform/otelenv.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
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+
newEnv := slices.Clone(c.Env)
47+
newEnv = append(newEnv[:i], append([]string{fmt.Sprintf("%s=%s", key, value)}, newEnv[i+1:]...)...)
48+
c.Env = newEnv
49+
return
50+
}
51+
}
52+
c.Env = append(c.Env, fmt.Sprintf("%s=%s", key, value))
53+
}
54+
55+
func (_ *envCarrier) Get(_ string) string {
56+
// Get not necessary to inject environment variables
57+
panic("Not implemented")
58+
}
59+
60+
func (_ *envCarrier) Keys() []string {
61+
// Keys not necessary to inject environment variables
62+
panic("Not implemented")
63+
}
64+
65+
// otelEnvInject will add add any necessary environment variables for the span
66+
// found in the Context. If environment variables are already present
67+
// in `environ` then they will be updated. If no variables are found the
68+
// new ones will be appended. The new environment will be returned, `environ`
69+
// will never be modified.
70+
func otelEnvInject(ctx context.Context, environ []string) []string {
71+
c := &envCarrier{Env: environ}
72+
otel.GetTextMapPropagator().Inject(ctx, c)
73+
return c.Env
74+
}

provisioner/terraform/otelenv_test.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package terraform // nolint:testpackage
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(ctx 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(ctx context.Context, traceID 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+
}

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