From d60928dedea6d931afd2f2deffc5ff406dfa19c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Gomez?= Date: Fri, 24 Dec 2021 00:31:25 +0100 Subject: [PATCH] Add support for Tempo datasource definition --- datasource/tempo/options.go | 128 +++++++++++++++++++++++++++++++ datasource/tempo/options_test.go | 111 +++++++++++++++++++++++++++ datasource/tempo/tempo.go | 44 +++++++++++ datasource/tempo/tempo_test.go | 22 ++++++ 4 files changed, 305 insertions(+) create mode 100644 datasource/tempo/options.go create mode 100644 datasource/tempo/options_test.go create mode 100644 datasource/tempo/tempo.go create mode 100644 datasource/tempo/tempo_test.go diff --git a/datasource/tempo/options.go b/datasource/tempo/options.go new file mode 100644 index 00000000..f91b654b --- /dev/null +++ b/datasource/tempo/options.go @@ -0,0 +1,128 @@ +package tempo + +import "time" + +// Default configures this datasource to be the default one. +func Default() Option { + return func(datasource *Tempo) { + datasource.builder.IsDefault = true + } +} + +// Timeout sets the timeout for HTTP requests. +func Timeout(timeout time.Duration) Option { + return func(datasource *Tempo) { + datasource.builder.JSONData.(map[string]interface{})["timeout"] = int(timeout.Seconds()) + } +} + +// BasicAuth configures basic authentication for this datasource. +func BasicAuth(username string, password string) Option { + return func(datasource *Tempo) { + yep := true + datasource.builder.BasicAuth = &yep + datasource.builder.BasicAuthUser = &username + datasource.builder.BasicAuthPassword = &password + } +} + +// SkipTLSVerify disables verification of SSL certificates. +func SkipTLSVerify() Option { + return func(datasource *Tempo) { + datasource.builder.JSONData.(map[string]interface{})["tlsSkipVerify"] = true + } +} + +// WithCertificate sets a self-signed certificate that can be verified against. +func WithCertificate(certificate string) Option { + return func(datasource *Tempo) { + datasource.builder.JSONData.(map[string]interface{})["tlsSkipVerify"] = false + datasource.builder.JSONData.(map[string]interface{})["tlsAuthWithCACert"] = true + datasource.builder.SecureJSONData.(map[string]interface{})["tlsCACert"] = certificate + } +} + +// WithCredentials joins credentials such as cookies or auth headers to cross-site requests. +func WithCredentials() Option { + return func(datasource *Tempo) { + datasource.builder.WithCredentials = true + } +} + +// ForwardOauthIdentity forward the user's upstream OAuth identity to the data +// source (Their access token gets passed along). +func ForwardOauthIdentity() Option { + return func(datasource *Tempo) { + datasource.builder.JSONData.(map[string]interface{})["oauthPassThru"] = true + } +} + +// ForwardCookies configures a list of cookies that should be forwarded to the +// datasource. +func ForwardCookies(cookies ...string) Option { + return func(datasource *Tempo) { + datasource.builder.JSONData.(map[string]interface{})["keepCookies"] = cookies + } +} + +// WithNodeGraph enables the Node Graph visualization in the trace viewer. +func WithNodeGraph() Option { + return func(datasource *Tempo) { + datasource.builder.JSONData.(map[string]interface{})["nodeGraph"] = map[string]interface{}{ + "enabled": true, + } + } +} + +// TraceToLogs defines how to navigate from a trace span to the selected datasource logs. +func TraceToLogs(logsDatasourceUID string, options ...TraceToLogsOption) Option { + settings := map[string]interface{}{ + "datasourceUid": logsDatasourceUID, + } + + for _, opt := range options { + opt(settings) + } + + return func(datasource *Tempo) { + datasource.builder.JSONData.(map[string]interface{})["tracesToLogs"] = settings + } +} + +// Tags defines tags that will be used in the Loki query. +// Default tags: 'cluster', 'hostname', 'namespace', 'pod'. +func Tags(tags ...string) TraceToLogsOption { + return func(settings map[string]interface{}) { + settings["tags"] = tags + } +} + +// SpanStartShift shifts the start time of the span. +// Default 0 (Time units can be used here, for example: 5s, 1m, 3h) +func SpanStartShift(shift time.Duration) TraceToLogsOption { + return func(settings map[string]interface{}) { + settings["spanStartTimeShift"] = shift.String() + } +} + +// SpanEndShift shifts the start time of the span. +// Default 0 (Time units can be used here, for example: 5s, 1m, 3h) +func SpanEndShift(shift time.Duration) TraceToLogsOption { + return func(settings map[string]interface{}) { + settings["spanEndTimeShift"] = shift.String() + } +} + +// FilterByTrace filters logs by Trace ID. Appends '|=' to the query. +func FilterByTrace() TraceToLogsOption { + return func(settings map[string]interface{}) { + settings["filterByTraceID"] = true + } +} + +// FilterBySpan filters logs by Trace ID. Appends '|=' to the query. +func FilterBySpan() TraceToLogsOption { + return func(settings map[string]interface{}) { + settings["filterBySpanID"] = true + } +} diff --git a/datasource/tempo/options_test.go b/datasource/tempo/options_test.go new file mode 100644 index 00000000..04d11f3d --- /dev/null +++ b/datasource/tempo/options_test.go @@ -0,0 +1,111 @@ +package tempo + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +func TestDefault(t *testing.T) { + req := require.New(t) + + datasource := New("", "", Default()) + + req.True(datasource.builder.IsDefault) +} + +func TestBasicAuth(t *testing.T) { + req := require.New(t) + + datasource := New("", "", BasicAuth("joe", "lafrite")) + + req.True(*datasource.builder.BasicAuth) + req.Equal("joe", *datasource.builder.BasicAuthUser) + req.Equal("lafrite", *datasource.builder.BasicAuthPassword) +} + +func TestTimeout(t *testing.T) { + req := require.New(t) + + datasource := New("", "", Timeout(30*time.Second)) + + req.Equal(30, datasource.builder.JSONData.(map[string]interface{})["timeout"]) +} + +func TestSkipTlsVerify(t *testing.T) { + req := require.New(t) + + datasource := New("", "", SkipTLSVerify()) + + req.Equal(true, datasource.builder.JSONData.(map[string]interface{})["tlsSkipVerify"]) +} + +func TestWithCertificate(t *testing.T) { + req := require.New(t) + + datasource := New("", "", WithCertificate("certificate-content")) + + req.Equal(false, datasource.builder.JSONData.(map[string]interface{})["tlsSkipVerify"]) + req.Equal(true, datasource.builder.JSONData.(map[string]interface{})["tlsAuthWithCACert"]) + req.Equal("certificate-content", datasource.builder.SecureJSONData.(map[string]interface{})["tlsCACert"]) +} + +func TestWithCredentials(t *testing.T) { + req := require.New(t) + + datasource := New("", "", WithCredentials()) + + req.True(datasource.builder.WithCredentials) +} + +func TestForwardOauthIdentity(t *testing.T) { + req := require.New(t) + + datasource := New("", "", ForwardOauthIdentity()) + + req.Equal(true, datasource.builder.JSONData.(map[string]interface{})["oauthPassThru"]) +} + +func TestForwardCookies(t *testing.T) { + req := require.New(t) + + datasource := New("", "", ForwardCookies("foo", "bar")) + + req.ElementsMatch([]string{"foo", "bar"}, datasource.builder.JSONData.(map[string]interface{})["keepCookies"]) +} + +func TestWithNodeGraph(t *testing.T) { + req := require.New(t) + + datasource := New("", "", WithNodeGraph()) + + jsonData := datasource.builder.JSONData.(map[string]interface{}) + + req.Equal(true, jsonData["nodeGraph"].(map[string]interface{})["enabled"]) +} + +func TestTraceToLogs(t *testing.T) { + req := require.New(t) + + lokiDatasourceUID := "lala" + datasource := New("", "", TraceToLogs( + lokiDatasourceUID, + Tags("pod", "namespace"), + SpanStartShift(2*time.Second), + SpanEndShift(1*time.Second), + FilterByTrace(), + FilterBySpan(), + )) + + jsonData := datasource.builder.JSONData.(map[string]interface{}) + traceToLogsSettings := jsonData["tracesToLogs"].(map[string]interface{}) + + req.NotEmpty(traceToLogsSettings) + req.Equal(lokiDatasourceUID, traceToLogsSettings["datasourceUid"]) + req.ElementsMatch([]string{"pod", "namespace"}, traceToLogsSettings["tags"]) + req.Equal("2s", traceToLogsSettings["spanStartTimeShift"]) + req.Equal("1s", traceToLogsSettings["spanEndTimeShift"]) + req.Equal(true, traceToLogsSettings["filterByTraceID"]) + req.Equal(true, traceToLogsSettings["filterBySpanID"]) +} diff --git a/datasource/tempo/tempo.go b/datasource/tempo/tempo.go new file mode 100644 index 00000000..de62477a --- /dev/null +++ b/datasource/tempo/tempo.go @@ -0,0 +1,44 @@ +package tempo + +import ( + "encoding/json" + + "github.com/K-Phoen/grabana/datasource" + "github.com/K-Phoen/sdk" +) + +var _ datasource.Datasource = Tempo{} + +type Tempo struct { + builder *sdk.Datasource +} + +type Option func(datasource *Tempo) +type TraceToLogsOption func(settings map[string]interface{}) + +func New(name string, url string, options ...Option) Tempo { + jaeger := &Tempo{ + builder: &sdk.Datasource{ + Name: name, + Type: "tempo", + Access: "proxy", + URL: url, + JSONData: map[string]interface{}{}, + SecureJSONData: map[string]interface{}{}, + }, + } + + for _, opt := range options { + opt(jaeger) + } + + return *jaeger +} + +func (datasource Tempo) Name() string { + return datasource.builder.Name +} + +func (datasource Tempo) MarshalJSON() ([]byte, error) { + return json.Marshal(datasource.builder) +} diff --git a/datasource/tempo/tempo_test.go b/datasource/tempo/tempo_test.go new file mode 100644 index 00000000..a2008081 --- /dev/null +++ b/datasource/tempo/tempo_test.go @@ -0,0 +1,22 @@ +package tempo + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestNewTempo(t *testing.T) { + req := require.New(t) + + datasource := New("ds-tempo", "http://localhost:3100") + + req.Equal("ds-tempo", datasource.Name()) + req.Equal("http://localhost:3100", datasource.builder.URL) + req.Equal("tempo", datasource.builder.Type) + req.NotNil(datasource.builder.JSONData) + req.NotNil(datasource.builder.SecureJSONData) + + _, err := datasource.MarshalJSON() + req.NoError(err) +}