diff --git a/api/cloudcontroller/wrapper/b3_trace_request.go b/api/cloudcontroller/wrapper/b3_trace_request.go new file mode 100644 index 00000000000..174946acfb9 --- /dev/null +++ b/api/cloudcontroller/wrapper/b3_trace_request.go @@ -0,0 +1,54 @@ +package wrapper + +import ( + "code.cloudfoundry.org/cli/api/cloudcontroller" +) + +// TODO +// 1. tests +// 2. do not overwrite headers if explicitly set (cf curl) +// 3. headers should go on other clients (uaa/routing) as well (with same value) +// 4. environment variable override + +const ( + // B3TraceIDHeader is the header key for the b3 trace id. + B3TraceIDHeader = "X-B3-Traceid" + + // B3SpanIDHeader is the header key for the b3 span id. + B3SpanIDHeader = "X-B3-Spanid" +) + +// TraceHeaderRequest is a wrapper that adds b3 trace headers to requests. +type TraceHeaderRequest struct { + b3trace string + b3span string + connection cloudcontroller.Connection +} + +// NewTraceHeaderRequest returns a pointer to a TraceHeaderRequest wrapper. +func NewTraceHeaderRequest(trace, span string) *TraceHeaderRequest { + return &TraceHeaderRequest{ + b3trace: trace, + b3span: span, + } +} + +// Add tracing headers if they are not already set. +func (t *TraceHeaderRequest) Make(request *cloudcontroller.Request, passedResponse *cloudcontroller.Response) error { + + // todo + // only override the trace headers if they are not already set (e.g. already explicitly set by cf curl) + if request.Header.Get(B3TraceIDHeader) != "" { + request.Header.Add(B3TraceIDHeader, t.b3trace) + } + if request.Header.Get(B3SpanIDHeader) != "" { + request.Header.Add(B3SpanIDHeader, t.b3span) + } + return t.connection.Make(request, passedResponse) +} + +// Wrap sets the connection in the TraceHeaderRequest and returns itself. +func (t *TraceHeaderRequest) Wrap(innerconnection cloudcontroller.Connection) cloudcontroller.Connection { + t.connection = innerconnection + return t +} diff --git a/command/commandfakes/fake_config.go b/command/commandfakes/fake_config.go index 2b7f1fe4014..1aa3e8e60a3 100644 --- a/command/commandfakes/fake_config.go +++ b/command/commandfakes/fake_config.go @@ -51,6 +51,26 @@ type FakeConfig struct { authorizationEndpointReturnsOnCall map[int]struct { result1 string } + B3SpanIDStub func() string + b3SpanIDMutex sync.RWMutex + b3SpanIDArgsForCall []struct { + } + b3SpanIDReturns struct { + result1 string + } + b3SpanIDReturnsOnCall map[int]struct { + result1 string + } + B3TraceIDStub func() string + b3TraceIDMutex sync.RWMutex + b3TraceIDArgsForCall []struct { + } + b3TraceIDReturns struct { + result1 string + } + b3TraceIDReturnsOnCall map[int]struct { + result1 string + } BinaryNameStub func() string binaryNameMutex sync.RWMutex binaryNameArgsForCall []struct { @@ -867,6 +887,112 @@ func (fake *FakeConfig) AuthorizationEndpointReturnsOnCall(i int, result1 string }{result1} } +func (fake *FakeConfig) B3SpanID() string { + fake.b3SpanIDMutex.Lock() + ret, specificReturn := fake.b3SpanIDReturnsOnCall[len(fake.b3SpanIDArgsForCall)] + fake.b3SpanIDArgsForCall = append(fake.b3SpanIDArgsForCall, struct { + }{}) + stub := fake.B3SpanIDStub + fakeReturns := fake.b3SpanIDReturns + fake.recordInvocation("B3SpanID", []interface{}{}) + fake.b3SpanIDMutex.Unlock() + if stub != nil { + return stub() + } + if specificReturn { + return ret.result1 + } + return fakeReturns.result1 +} + +func (fake *FakeConfig) B3SpanIDCallCount() int { + fake.b3SpanIDMutex.RLock() + defer fake.b3SpanIDMutex.RUnlock() + return len(fake.b3SpanIDArgsForCall) +} + +func (fake *FakeConfig) B3SpanIDCalls(stub func() string) { + fake.b3SpanIDMutex.Lock() + defer fake.b3SpanIDMutex.Unlock() + fake.B3SpanIDStub = stub +} + +func (fake *FakeConfig) B3SpanIDReturns(result1 string) { + fake.b3SpanIDMutex.Lock() + defer fake.b3SpanIDMutex.Unlock() + fake.B3SpanIDStub = nil + fake.b3SpanIDReturns = struct { + result1 string + }{result1} +} + +func (fake *FakeConfig) B3SpanIDReturnsOnCall(i int, result1 string) { + fake.b3SpanIDMutex.Lock() + defer fake.b3SpanIDMutex.Unlock() + fake.B3SpanIDStub = nil + if fake.b3SpanIDReturnsOnCall == nil { + fake.b3SpanIDReturnsOnCall = make(map[int]struct { + result1 string + }) + } + fake.b3SpanIDReturnsOnCall[i] = struct { + result1 string + }{result1} +} + +func (fake *FakeConfig) B3TraceID() string { + fake.b3TraceIDMutex.Lock() + ret, specificReturn := fake.b3TraceIDReturnsOnCall[len(fake.b3TraceIDArgsForCall)] + fake.b3TraceIDArgsForCall = append(fake.b3TraceIDArgsForCall, struct { + }{}) + stub := fake.B3TraceIDStub + fakeReturns := fake.b3TraceIDReturns + fake.recordInvocation("B3TraceID", []interface{}{}) + fake.b3TraceIDMutex.Unlock() + if stub != nil { + return stub() + } + if specificReturn { + return ret.result1 + } + return fakeReturns.result1 +} + +func (fake *FakeConfig) B3TraceIDCallCount() int { + fake.b3TraceIDMutex.RLock() + defer fake.b3TraceIDMutex.RUnlock() + return len(fake.b3TraceIDArgsForCall) +} + +func (fake *FakeConfig) B3TraceIDCalls(stub func() string) { + fake.b3TraceIDMutex.Lock() + defer fake.b3TraceIDMutex.Unlock() + fake.B3TraceIDStub = stub +} + +func (fake *FakeConfig) B3TraceIDReturns(result1 string) { + fake.b3TraceIDMutex.Lock() + defer fake.b3TraceIDMutex.Unlock() + fake.B3TraceIDStub = nil + fake.b3TraceIDReturns = struct { + result1 string + }{result1} +} + +func (fake *FakeConfig) B3TraceIDReturnsOnCall(i int, result1 string) { + fake.b3TraceIDMutex.Lock() + defer fake.b3TraceIDMutex.Unlock() + fake.B3TraceIDStub = nil + if fake.b3TraceIDReturnsOnCall == nil { + fake.b3TraceIDReturnsOnCall = make(map[int]struct { + result1 string + }) + } + fake.b3TraceIDReturnsOnCall[i] = struct { + result1 string + }{result1} +} + func (fake *FakeConfig) BinaryName() string { fake.binaryNameMutex.Lock() ret, specificReturn := fake.binaryNameReturnsOnCall[len(fake.binaryNameArgsForCall)] @@ -4028,6 +4154,10 @@ func (fake *FakeConfig) Invocations() map[string][][]interface{} { defer fake.addPluginRepositoryMutex.RUnlock() fake.authorizationEndpointMutex.RLock() defer fake.authorizationEndpointMutex.RUnlock() + fake.b3SpanIDMutex.RLock() + defer fake.b3SpanIDMutex.RUnlock() + fake.b3TraceIDMutex.RLock() + defer fake.b3TraceIDMutex.RUnlock() fake.binaryNameMutex.RLock() defer fake.binaryNameMutex.RUnlock() fake.binaryVersionMutex.RLock() diff --git a/command/config.go b/command/config.go index 676223c7923..84e7abdb8ff 100644 --- a/command/config.go +++ b/command/config.go @@ -15,6 +15,8 @@ type Config interface { AddPluginRepository(name string, url string) AuthorizationEndpoint() string APIVersion() string + B3TraceID() string + B3SpanID() string BinaryName() string BinaryVersion() string CFPassword() string diff --git a/command/v7/shared/new_clients.go b/command/v7/shared/new_clients.go index 4486bd8ffab..21890edc410 100644 --- a/command/v7/shared/new_clients.go +++ b/command/v7/shared/new_clients.go @@ -46,6 +46,7 @@ func NewWrappedCloudControllerClient(config command.Config, ui command.UI, extra ccWrappers = append(ccWrappers, ccWrapper.NewRequestLogger(ui.RequestLoggerFileWriter(location))) } + ccWrappers = append(ccWrappers, ccWrapper.NewTraceHeaderRequest(config.B3TraceID(), config.B3SpanID())) ccWrappers = append(ccWrappers, extraWrappers...) ccWrappers = append(ccWrappers, ccWrapper.NewRetryRequest(config.RequestRetryCount())) diff --git a/util/configv3/env.go b/util/configv3/env.go index 27e15504fcd..8bd8bee9d11 100644 --- a/util/configv3/env.go +++ b/util/configv3/env.go @@ -5,6 +5,8 @@ import ( "strconv" "strings" "time" + + "code.cloudfoundry.org/cli/util/random" ) // EnvOverride represents all the environment variables read by the CF CLI @@ -20,6 +22,8 @@ type EnvOverride struct { CFStartupTimeout string CFTrace string CFUsername string + CFB3TraceID string + CFB3SpanID string DockerPassword string CNBCredentials string Experimental string @@ -160,3 +164,17 @@ func (config *Config) StartupTimeout() time.Duration { return DefaultStartupTimeout } + +func (config *Config) B3TraceID() string { + if config.ENV.CFB3TraceID == "" { + return random.GenerateHex(32) + } + return config.ENV.CFB3TraceID +} + +func (config *Config) B3SpanID() string { + if config.ENV.CFB3SpanID == "" { + return random.GenerateHex(16) + } + return config.ENV.CFB3SpanID +} diff --git a/util/configv3/load_config.go b/util/configv3/load_config.go index 883b2cd31cb..e421b9a6cc4 100644 --- a/util/configv3/load_config.go +++ b/util/configv3/load_config.go @@ -127,6 +127,8 @@ func LoadConfig(flags ...FlagOverride) (*Config, error) { CFStartupTimeout: os.Getenv("CF_STARTUP_TIMEOUT"), CFTrace: os.Getenv("CF_TRACE"), CFUsername: os.Getenv("CF_USERNAME"), + CFB3TraceID: os.Getenv("CF_B3_TRACE_ID"), + CFB3SpanID: os.Getenv("CF_B3_SPAN_ID"), DockerPassword: os.Getenv("CF_DOCKER_PASSWORD"), CNBCredentials: os.Getenv("CNB_REGISTRY_CREDS"), Experimental: os.Getenv("CF_CLI_EXPERIMENTAL"), diff --git a/util/random/hex.go b/util/random/hex.go new file mode 100644 index 00000000000..cd46ae4939b --- /dev/null +++ b/util/random/hex.go @@ -0,0 +1,15 @@ +package random + +import ( + "crypto/rand" + "encoding/hex" +) + +func GenerateHex(length int) string { + b := make([]byte, length/2) + if _, err := rand.Read(b); err != nil { + panic(err) + } + + return hex.EncodeToString(b) +}