Skip to content

Commit 1779270

Browse files
DrJosh9000goodspark
andcommitted
Support for multiple trace context encodings
This is part merge-conflict-resolution, part refinement of #1775. The main differences to #1775 are: specifying the encoding with a string flag rather than bool, represented internally with a `Codec` interface, and some tweaks to the tests. Co-authored-by: Sam Park <[email protected]>
1 parent 31ab702 commit 1779270

File tree

12 files changed

+223
-35
lines changed

12 files changed

+223
-35
lines changed

agent/agent_configuration.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,5 +61,6 @@ type AgentConfiguration struct {
6161
AcquireJob string
6262
TracingBackend string
6363
TracingServiceName string
64+
TraceContextEncoding string
6465
DisableWarningsFor []string
6566
}

agent/job_runner.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,7 @@ func (r *JobRunner) createEnvironment(ctx context.Context) ([]string, error) {
502502
env["BUILDKITE_STRICT_SINGLE_HOOKS"] = fmt.Sprintf("%t", r.conf.AgentConfiguration.StrictSingleHooks)
503503
env["BUILDKITE_CANCEL_GRACE_PERIOD"] = strconv.Itoa(r.conf.AgentConfiguration.CancelGracePeriod)
504504
env["BUILDKITE_SIGNAL_GRACE_PERIOD_SECONDS"] = strconv.Itoa(int(r.conf.AgentConfiguration.SignalGracePeriod / time.Second))
505+
env["BUILDKITE_TRACE_CONTEXT_ENCODING"] = r.conf.AgentConfiguration.TraceContextEncoding
505506

506507
if r.conf.KubernetesExec {
507508
env["BUILDKITE_KUBERNETES_EXEC"] = "true"

clicommand/agent_start.go

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -161,13 +161,14 @@ type AgentStartConfig struct {
161161
TracingServiceName string `cli:"tracing-service-name"`
162162

163163
// Global flags
164-
Debug bool `cli:"debug"`
165-
LogLevel string `cli:"log-level"`
166-
NoColor bool `cli:"no-color"`
167-
Experiments []string `cli:"experiment" normalize:"list"`
168-
Profile string `cli:"profile"`
169-
StrictSingleHooks bool `cli:"strict-single-hooks"`
170-
KubernetesExec bool `cli:"kubernetes-exec"`
164+
Debug bool `cli:"debug"`
165+
LogLevel string `cli:"log-level"`
166+
NoColor bool `cli:"no-color"`
167+
Experiments []string `cli:"experiment" normalize:"list"`
168+
Profile string `cli:"profile"`
169+
StrictSingleHooks bool `cli:"strict-single-hooks"`
170+
KubernetesExec bool `cli:"kubernetes-exec"`
171+
TraceContextEncoding string `cli:"trace-context-encoding"`
171172

172173
// API config
173174
DebugHTTP bool `cli:"debug-http"`
@@ -689,6 +690,7 @@ var AgentStartCommand = cli.Command{
689690
RedactedVars,
690691
StrictSingleHooksFlag,
691692
KubernetesExecFlag,
693+
TraceContextEncodingFlag,
692694

693695
// Deprecated flags which will be removed in v4
694696
cli.StringSliceFlag{
@@ -849,6 +851,10 @@ var AgentStartCommand = cli.Command{
849851
return err
850852
}
851853

854+
if _, err := tracetools.ParseEncoding(cfg.TraceContextEncoding); err != nil {
855+
return fmt.Errorf("while parsing trace context encoding: %v", err)
856+
}
857+
852858
mc := metrics.NewCollector(l, metrics.CollectorConfig{
853859
Datadog: cfg.MetricsDatadog,
854860
DatadogHost: cfg.MetricsDatadogHost,
@@ -946,6 +952,7 @@ var AgentStartCommand = cli.Command{
946952
AcquireJob: cfg.AcquireJob,
947953
TracingBackend: cfg.TracingBackend,
948954
TracingServiceName: cfg.TracingServiceName,
955+
TraceContextEncoding: cfg.TraceContextEncoding,
949956
VerificationFailureBehaviour: cfg.VerificationFailureBehavior,
950957
KubernetesExec: cfg.KubernetesExec,
951958

clicommand/bootstrap.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111

1212
"github.com/buildkite/agent/v3/internal/job"
1313
"github.com/buildkite/agent/v3/process"
14+
"github.com/buildkite/agent/v3/tracetools"
1415
"github.com/urfave/cli"
1516
)
1617

@@ -95,6 +96,7 @@ type BootstrapConfig struct {
9596
RedactedVars []string `cli:"redacted-vars" normalize:"list"`
9697
TracingBackend string `cli:"tracing-backend"`
9798
TracingServiceName string `cli:"tracing-service-name"`
99+
TraceContextEncoding string `cli:"trace-context-encoding"`
98100
NoJobAPI bool `cli:"no-job-api"`
99101
DisableWarningsFor []string `cli:"disable-warnings-for" normalize:"list"`
100102
KubernetesExec bool `cli:"kubernetes-exec"`
@@ -382,6 +384,7 @@ var BootstrapCommand = cli.Command{
382384
RedactedVars,
383385
StrictSingleHooksFlag,
384386
KubernetesExecFlag,
387+
TraceContextEncodingFlag,
385388
},
386389
Action: func(c *cli.Context) error {
387390
ctx := context.Background()
@@ -414,6 +417,11 @@ var BootstrapCommand = cli.Command{
414417
return err
415418
}
416419

420+
traceContextCodec, err := tracetools.ParseEncoding(cfg.TraceContextEncoding)
421+
if err != nil {
422+
return fmt.Errorf("while parsing trace context encoding: %v", err)
423+
}
424+
417425
// Configure the bootstraper
418426
bootstrap := job.New(job.ExecutorConfig{
419427
AgentName: cfg.AgentName,
@@ -464,6 +472,7 @@ var BootstrapCommand = cli.Command{
464472
Tag: cfg.Tag,
465473
TracingBackend: cfg.TracingBackend,
466474
TracingServiceName: cfg.TracingServiceName,
475+
TraceContextCodec: traceContextCodec,
467476
JobAPI: !cfg.NoJobAPI,
468477
DisabledWarnings: cfg.DisableWarningsFor,
469478
KubernetesExec: cfg.KubernetesExec,

clicommand/global.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,13 @@ var (
117117
"*_CONNECTION_STRING",
118118
},
119119
}
120+
121+
TraceContextEncodingFlag = cli.StringFlag{
122+
Name: "trace-context-encoding",
123+
Usage: "Sets the inner encoding for BUILDKITE_TRACE_CONTEXT. Must be either json or gob",
124+
Value: "gob",
125+
EnvVar: "BUILDKITE_TRACE_CONTEXT_ENCODING",
126+
}
120127
)
121128

122129
func globalFlags() []cli.Flag {

internal/job/config.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88

99
"github.com/buildkite/agent/v3/env"
1010
"github.com/buildkite/agent/v3/process"
11+
"github.com/buildkite/agent/v3/tracetools"
1112
)
1213

1314
// Config provides the configuration for the job executor. Some of the keys are
@@ -165,6 +166,9 @@ type ExecutorConfig struct {
165166
// Service name to use when reporting traces.
166167
TracingServiceName string
167168

169+
// Encoding (within base64) for the trace context environment variable.
170+
TraceContextCodec tracetools.Codec
171+
168172
// Whether to start the JobAPI
169173
JobAPI bool
170174

internal/job/executor.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ func (e *Executor) Run(ctx context.Context) (exitCode int) {
8989
var err error
9090
logger := shell.StderrLogger
9191
logger.DisabledWarningIDs = e.DisabledWarnings
92-
e.shell, err = shell.New(shell.WithLogger(logger))
92+
e.shell, err = shell.New(shell.WithLogger(logger), shell.WithTraceContextCodec(e.TraceContextCodec))
9393
if err != nil {
9494
fmt.Printf("Error creating shell: %v", err)
9595
return 1

internal/job/shell/shell.go

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ type Shell struct {
7272

7373
// Amount of time to wait between sending the InterruptSignal and SIGKILL
7474
SignalGracePeriod time.Duration
75+
76+
// How to encode trace contexts.
77+
traceContextCodec tracetools.Codec
7578
}
7679

7780
type newShellOpt func(*Shell)
@@ -82,6 +85,12 @@ func WithLogger(l Logger) newShellOpt {
8285
}
8386
}
8487

88+
func WithTraceContextCodec(c tracetools.Codec) newShellOpt {
89+
return func(s *Shell) {
90+
s.traceContextCodec = c
91+
}
92+
}
93+
8594
// New returns a new Shell
8695
func New(opts ...newShellOpt) (*Shell, error) {
8796
wd, err := os.Getwd()
@@ -90,10 +99,11 @@ func New(opts ...newShellOpt) (*Shell, error) {
9099
}
91100

92101
shell := &Shell{
93-
Logger: StderrLogger,
94-
Env: env.FromSlice(os.Environ()),
95-
Writer: os.Stdout,
96-
wd: wd,
102+
Logger: StderrLogger,
103+
Env: env.FromSlice(os.Environ()),
104+
Writer: os.Stdout,
105+
wd: wd,
106+
traceContextCodec: tracetools.CodecGob{},
97107
}
98108

99109
for _, opt := range opts {
@@ -119,6 +129,7 @@ func (s *Shell) WithStdin(r io.Reader) *Shell {
119129
wd: s.wd,
120130
InterruptSignal: s.InterruptSignal,
121131
SignalGracePeriod: s.SignalGracePeriod,
132+
traceContextCodec: s.traceContextCodec,
122133
}
123134
}
124135

@@ -373,7 +384,7 @@ func (s *Shell) injectTraceCtx(ctx context.Context, env *env.Environment) {
373384
if span == nil {
374385
return
375386
}
376-
if err := tracetools.EncodeTraceContext(span, env.Dump()); err != nil {
387+
if err := tracetools.EncodeTraceContext(span, env.Dump(), s.traceContextCodec); err != nil {
377388
if s.Debug {
378389
s.Logger.Warningf("Failed to encode trace context: %v", err)
379390
}

internal/job/tracing.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ func (e *Executor) startTracingDatadog(ctx context.Context) (tracetools.Span, co
100100
// extractTraceCtx pulls encoded distributed tracing information from the env vars.
101101
// Note: This should match the injectTraceCtx code in shell.
102102
func (e *Executor) extractDDTraceCtx() opentracing.SpanContext {
103-
sctx, err := tracetools.DecodeTraceContext(e.shell.Env.Dump())
103+
sctx, err := tracetools.DecodeTraceContext(e.shell.Env.Dump(), e.ExecutorConfig.TraceContextCodec)
104104
if err != nil {
105105
// Return nil so a new span will be created
106106
return nil

tracetools/propagate.go

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import (
44
"bytes"
55
"encoding/base64"
66
"encoding/gob"
7+
"encoding/json"
8+
"fmt"
9+
"io"
710

811
"github.com/opentracing/opentracing-go"
912
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer"
@@ -15,14 +18,14 @@ const EnvVarTraceContextKey = "BUILDKITE_TRACE_CONTEXT"
1518

1619
// EncodeTraceContext will serialize and encode tracing data into a string and place
1720
// it into the given env vars map.
18-
func EncodeTraceContext(span opentracing.Span, env map[string]string) error {
21+
func EncodeTraceContext(span opentracing.Span, env map[string]string, codec Codec) error {
1922
textmap := tracer.TextMapCarrier{}
2023
if err := span.Tracer().Inject(span.Context(), opentracing.TextMap, &textmap); err != nil {
2124
return err
2225
}
2326

24-
buf := bytes.NewBuffer([]byte{})
25-
enc := gob.NewEncoder(buf)
27+
buf := bytes.NewBuffer(nil)
28+
enc := codec.NewEncoder(buf)
2629
if err := enc.Encode(textmap); err != nil {
2730
return err
2831
}
@@ -33,7 +36,7 @@ func EncodeTraceContext(span opentracing.Span, env map[string]string) error {
3336

3437
// DecodeTraceContext will decode, deserialize, and extract the tracing data from the
3538
// given env var map.
36-
func DecodeTraceContext(env map[string]string) (opentracing.SpanContext, error) {
39+
func DecodeTraceContext(env map[string]string, codec Codec) (opentracing.SpanContext, error) {
3740
s, has := env[EnvVarTraceContextKey]
3841
if !has {
3942
return nil, opentracing.ErrSpanContextNotFound
@@ -44,12 +47,49 @@ func DecodeTraceContext(env map[string]string) (opentracing.SpanContext, error)
4447
return nil, err
4548
}
4649

47-
buf := bytes.NewBuffer(contextBytes)
48-
dec := gob.NewDecoder(buf)
50+
dec := codec.NewDecoder(bytes.NewReader(contextBytes))
4951
textmap := opentracing.TextMapCarrier{}
5052
if err := dec.Decode(&textmap); err != nil {
5153
return nil, err
5254
}
5355

5456
return opentracing.GlobalTracer().Extract(opentracing.TextMap, textmap)
5557
}
58+
59+
// Encoder impls can encode values. Decoder impls can decode values.
60+
type Encoder interface{ Encode(v any) error }
61+
type Decoder interface{ Decode(v any) error }
62+
63+
// Codec implementations produce encoders/decoders.
64+
type Codec interface {
65+
NewEncoder(io.Writer) Encoder
66+
NewDecoder(io.Reader) Decoder
67+
String() string
68+
}
69+
70+
// CodecGob marshals and unmarshals with https://pkg.go.dev/encoding/gob.
71+
type CodecGob struct{}
72+
73+
func (CodecGob) NewEncoder(w io.Writer) Encoder { return gob.NewEncoder(w) }
74+
func (CodecGob) NewDecoder(r io.Reader) Decoder { return gob.NewDecoder(r) }
75+
func (CodecGob) String() string { return "gob" }
76+
77+
// CodecJSON marshals and unmarshals with https://pkg.go.dev/encoding/json.
78+
type CodecJSON struct{}
79+
80+
func (CodecJSON) NewEncoder(w io.Writer) Encoder { return json.NewEncoder(w) }
81+
func (CodecJSON) NewDecoder(r io.Reader) Decoder { return json.NewDecoder(r) }
82+
func (CodecJSON) String() string { return "json" }
83+
84+
// ParseEncoding converts an encoding to the associated codec.
85+
// An empty string is parsed as "gob".
86+
func ParseEncoding(encoding string) (Codec, error) {
87+
switch encoding {
88+
case "", "gob":
89+
return CodecGob{}, nil
90+
case "json":
91+
return CodecJSON{}, nil
92+
default:
93+
return nil, fmt.Errorf("invalid encoding %q", encoding)
94+
}
95+
}

tracetools/propagate_example_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ func ExampleEncodeTraceContext() {
3535

3636
// Now say we want to launch a child process.
3737
// Prepare it's env vars. This will be the carrier for the tracing data.
38-
if err := EncodeTraceContext(span, childEnv); err != nil {
38+
if err := EncodeTraceContext(span, childEnv, CodecGob{}); err != nil {
3939
fmt.Println("oops an error for parent process trace injection")
4040
}
4141
// Now childEnv will contain the encoded data set with the env var key.
@@ -58,7 +58,7 @@ func ExampleEncodeTraceContext() {
5858
// Make sure tracing is setup the same way (same env var key)
5959
// Normally you'd use os.Environ or similar here (the list of strings is
6060
// supported). We're just reusing childEnv for test simplicity.
61-
sctx, err := DecodeTraceContext(childEnv)
61+
sctx, err := DecodeTraceContext(childEnv, CodecGob{})
6262
if err != nil {
6363
fmt.Println("oops an error for child process trace extraction")
6464
} else {

0 commit comments

Comments
 (0)