Skip to content

Commit 4459db5

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 4459db5

File tree

3 files changed

+137
-0
lines changed

3 files changed

+137
-0
lines changed

provisioner/terraform/otelenv.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package terraform
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"strings"
7+
"unicode"
8+
9+
"go.opentelemetry.io/otel"
10+
"go.opentelemetry.io/otel/propagation"
11+
)
12+
13+
// TODO: replace this with the upstream OTEL env propagation when it is
14+
// released.
15+
16+
// envCarrier is a propagation.TextMapCarrier that is used to extract or
17+
// inject tracing environment variables. This is used with a
18+
// propagation.TextMapPropagator
19+
type envCarrier struct {
20+
Env []string
21+
}
22+
23+
var _ propagation.TextMapCarrier = (*envCarrier)(nil)
24+
25+
func toKey(key string) string {
26+
key = strings.ToUpper(key)
27+
key = strings.ReplaceAll(key, "-", "_")
28+
return strings.Map(func(r rune) rune {
29+
if unicode.IsLetter(r) || unicode.IsNumber(r) || r == '_' {
30+
return r
31+
}
32+
return -1
33+
}, key)
34+
}
35+
36+
func (c *envCarrier) Set(key, value string) {
37+
if c == nil {
38+
return
39+
}
40+
key = toKey(key)
41+
for i, e := range c.Env {
42+
if strings.HasPrefix(e, key+"=") {
43+
// don't directly update the slice so we don't modify the slice
44+
// passed in
45+
newEnv := make([]string, len(c.Env))
46+
copy(newEnv, c.Env)
47+
c.Env = append(newEnv[:i], append([]string{fmt.Sprintf("%s=%s", key, value)}, newEnv[i+1:]...)...)
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+
// Get not necessary to inject environment variables
56+
panic("Not implemented")
57+
}
58+
59+
func (c *envCarrier) Keys() []string {
60+
// Keys not necessary to inject environment variables
61+
panic("Not implemented")
62+
}
63+
64+
// otelEnvInject will add add any necessary environment variables for the span
65+
// found in the Context. If environment variables are already present
66+
// in `environ` then they will be updated. If no variables are found the
67+
// new ones will be appended. The new environment will be returned, `environ`
68+
// will never be modified.
69+
func otelEnvInject(ctx context.Context, environ []string) []string {
70+
c := &envCarrier{Env: environ}
71+
otel.GetTextMapPropagator().Inject(ctx, c)
72+
return c.Env
73+
}

provisioner/terraform/otelenv_test.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
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 (g *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 (g *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+
testTraceProvider := sdktrace.NewTracerProvider(
31+
sdktrace.WithSampler(sdktrace.AlwaysSample()),
32+
sdktrace.WithIDGenerator(&testIDGenerator{}),
33+
)
34+
35+
tracer := testTraceProvider.Tracer("example")
36+
ctx, span := tracer.Start(context.Background(), "testing")
37+
defer span.End()
38+
39+
input := []string{"PATH=/usr/bin:/bin"}
40+
41+
otel.SetTextMapPropagator(propagation.TraceContext{})
42+
got := otelEnvInject(ctx, input)
43+
require.Equal(t, []string{
44+
"PATH=/usr/bin:/bin",
45+
"TRACEPARENT=00-60d19e9e9abf2197c1d6d8f93e28ee2a-a028bd951229a46f-01",
46+
}, got)
47+
48+
// verify we update rather than append
49+
input = []string{
50+
"PATH=/usr/bin:/bin",
51+
"TRACEPARENT=origTraceParent",
52+
"TERM=xterm",
53+
}
54+
55+
otel.SetTextMapPropagator(propagation.TraceContext{})
56+
got = otelEnvInject(ctx, input)
57+
require.Equal(t, []string{
58+
"PATH=/usr/bin:/bin",
59+
"TRACEPARENT=00-60d19e9e9abf2197c1d6d8f93e28ee2a-a028bd951229a46f-01",
60+
"TERM=xterm",
61+
}, got)
62+
}

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