package tracing import ( "net/http" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/trace" "k8s.io/client-go/transport" ) // RestConfigWrapper returns a rest.Config.Wrap-compatible wrapper that adds // a client span to every Kubernetes API request already running inside a // trace. Requests with no parent span pass through untouched: informer // list/watch long-polls, leader-election renewals, and metrics authn would // otherwise each become meaningless (and in the watch case, minutes-long) // root spans. client-go applies Wrap innermost, so the span sees the final // authenticated request. func RestConfigWrapper(opts ...Option) transport.WrapperFunc { o := newOptions(opts) return func(rt http.RoundTripper) http.RoundTripper { otelOpts := []otelhttp.Option{ // Explicit propagators: deterministic regardless of global // state. The apiserver ignores incoming traceparent (by // design), so injection is harmless there. otelhttp.WithPropagators(propagation.NewCompositeTextMapPropagator( propagation.TraceContext{}, propagation.Baggage{})), } if o.tp != nil { otelOpts = append(otelOpts, otelhttp.WithTracerProvider(o.tp)) } return &parentGatedTransport{ traced: otelhttp.NewTransport(rt, otelOpts...), plain: rt, } } } // parentGatedTransport enforces the parent-span requirement itself rather // than relying on otelhttp filter semantics for transports. type parentGatedTransport struct { traced http.RoundTripper plain http.RoundTripper } func (t *parentGatedTransport) RoundTrip(r *http.Request) (*http.Response, error) { if trace.SpanContextFromContext(r.Context()).IsValid() { return t.traced.RoundTrip(r) } return t.plain.RoundTrip(r) }