@@ -33,33 +33,46 @@ func urlAndAPIKeyFromEnv() (string, string) {
33
33
}
34
34
35
35
func urlAndAPIKeyFromHeaders (req * http.Request ) (string , string ) {
36
- u := req .Header .Get (grafanaURLHeader )
36
+ u := strings . TrimRight ( req .Header .Get (grafanaURLHeader ), "/" )
37
37
apiKey := req .Header .Get (grafanaAPIKeyHeader )
38
38
return u , apiKey
39
39
}
40
40
41
- type grafanaURLKey struct {}
42
- type grafanaAPIKeyKey struct {}
43
- type grafanaAccessTokenKey struct {}
41
+ // grafanaConfigKey is the context key for Grafana configuration.
42
+ type grafanaConfigKey struct {}
44
43
45
- // grafanaDebugKey is the context key for the Grafana transport's debug flag.
46
- type grafanaDebugKey struct {}
44
+ // GrafanaConfig represents the full configuration for Grafana clients.
45
+ type GrafanaConfig struct {
46
+ // Debug enables debug mode for the Grafana client.
47
+ Debug bool
47
48
48
- // WithGrafanaDebug adds the Grafana debug flag to the context.
49
- func WithGrafanaDebug (ctx context.Context , debug bool ) context.Context {
50
- if debug {
51
- slog .Info ("Grafana transport debug mode enabled" )
52
- }
53
- return context .WithValue (ctx , grafanaDebugKey {}, debug )
49
+ // URL is the URL of the Grafana instance.
50
+ URL string
51
+
52
+ // APIKey is the API key or service account token for the Grafana instance.
53
+ // It may be empty if we are using on-behalf-of auth.
54
+ APIKey string
55
+
56
+ // AccessToken is the Grafana Cloud access policy token used for on-behalf-of auth in Grafana Cloud.
57
+ AccessToken string
58
+ // IDToken is an ID token identifying the user for the current request.
59
+ // It comes from the `X-Grafana-Id` header sent from Grafana to plugin backends.
60
+ // It is used for on-behalf-of auth in Grafana Cloud.
61
+ IDToken string
62
+ }
63
+
64
+ // WithGrafanaConfig adds Grafana configuration to the context.
65
+ func WithGrafanaConfig (ctx context.Context , config GrafanaConfig ) context.Context {
66
+ return context .WithValue (ctx , grafanaConfigKey {}, config )
54
67
}
55
68
56
- // GrafanaDebugFromContext extracts the Grafana debug flag from the context.
57
- // If the flag is not set, it returns false .
58
- func GrafanaDebugFromContext (ctx context.Context ) bool {
59
- if debug , ok := ctx .Value (grafanaDebugKey {}).(bool ); ok {
60
- return debug
69
+ // GrafanaConfigFromContext extracts Grafana configuration from the context.
70
+ // If no config is found, returns a zero-value GrafanaConfig .
71
+ func GrafanaConfigFromContext (ctx context.Context ) GrafanaConfig {
72
+ if config , ok := ctx .Value (grafanaConfigKey {}).(GrafanaConfig ); ok {
73
+ return config
61
74
}
62
- return false
75
+ return GrafanaConfig {}
63
76
}
64
77
65
78
// ExtractGrafanaInfoFromEnv is a StdioContextFunc that extracts Grafana configuration
@@ -74,7 +87,13 @@ var ExtractGrafanaInfoFromEnv server.StdioContextFunc = func(ctx context.Context
74
87
panic (fmt .Errorf ("invalid Grafana URL %s: %w" , u , err ))
75
88
}
76
89
slog .Info ("Using Grafana configuration" , "url" , parsedURL .Redacted (), "api_key_set" , apiKey != "" )
77
- return WithGrafanaURL (WithGrafanaAPIKey (ctx , apiKey ), u )
90
+
91
+ // Get existing config or create a new one.
92
+ // This will respect the existing debug flag, if set.
93
+ config := GrafanaConfigFromContext (ctx )
94
+ config .URL = u
95
+ config .APIKey = apiKey
96
+ return WithGrafanaConfig (ctx , config )
78
97
}
79
98
80
99
// httpContextFunc is a function that can be used as a `server.HTTPContextFunc` or a
@@ -96,30 +115,29 @@ var ExtractGrafanaInfoFromHeaders httpContextFunc = func(ctx context.Context, re
96
115
if apiKey == "" {
97
116
apiKey = apiKeyEnv
98
117
}
99
- return WithGrafanaURL (WithGrafanaAPIKey (ctx , apiKey ), u )
100
- }
101
118
102
- // WithGrafanaURL adds the Grafana URL to the context.
103
- func WithGrafanaURL (ctx context.Context , url string ) context.Context {
104
- return context .WithValue (ctx , grafanaURLKey {}, url )
105
- }
106
-
107
- // WithGrafanaAPIKey adds the Grafana API key to the context.
108
- func WithGrafanaAPIKey (ctx context.Context , apiKey string ) context.Context {
109
- return context .WithValue (ctx , grafanaAPIKeyKey {}, apiKey )
119
+ // Get existing config or create a new one.
120
+ // This will respect the existing debug flag, if set.
121
+ config := GrafanaConfigFromContext (ctx )
122
+ config .URL = u
123
+ config .APIKey = apiKey
124
+ return WithGrafanaConfig (ctx , config )
110
125
}
111
126
112
127
// WithOnBehalfOfAuth adds the Grafana access token and user token to the
113
- // context . These tokens are used for on-behalf-of auth in Grafana Cloud.
128
+ // Grafana config . These tokens are used for on-behalf-of auth in Grafana Cloud.
114
129
func WithOnBehalfOfAuth (ctx context.Context , accessToken , userToken string ) (context.Context , error ) {
115
130
if accessToken == "" || userToken == "" {
116
131
return nil , fmt .Errorf ("neither accessToken nor userToken can be empty" )
117
132
}
118
- return context .WithValue (ctx , grafanaAccessTokenKey {}, []string {accessToken , userToken }), nil
133
+ cfg := GrafanaConfigFromContext (ctx )
134
+ cfg .AccessToken = accessToken
135
+ cfg .IDToken = userToken
136
+ return WithGrafanaConfig (ctx , cfg ), nil
119
137
}
120
138
121
139
// MustWithOnBehalfOfAuth adds the access and user tokens to the context,
122
- // panicing if either are empty.
140
+ // panicking if either are empty.
123
141
func MustWithOnBehalfOfAuth (ctx context.Context , accessToken , userToken string ) context.Context {
124
142
ctx , err := WithOnBehalfOfAuth (ctx , accessToken , userToken )
125
143
if err != nil {
@@ -128,31 +146,6 @@ func MustWithOnBehalfOfAuth(ctx context.Context, accessToken, userToken string)
128
146
return ctx
129
147
}
130
148
131
- // GrafanaURLFromContext extracts the Grafana URL from the context.
132
- func GrafanaURLFromContext (ctx context.Context ) string {
133
- if u , ok := ctx .Value (grafanaURLKey {}).(string ); ok {
134
- return u
135
- }
136
- return defaultGrafanaURL
137
- }
138
-
139
- // GrafanaAPIKeyFromContext extracts the Grafana API key from the context.
140
- func GrafanaAPIKeyFromContext (ctx context.Context ) string {
141
- if k , ok := ctx .Value (grafanaAPIKeyKey {}).(string ); ok {
142
- return k
143
- }
144
- return ""
145
- }
146
-
147
- // OnBehalfOfAuthFromContext extracts the Grafana access and user tokens from
148
- // the context. These tokens are used for on-behalf-of auth in Grafana Cloud.
149
- func OnBehalfOfAuthFromContext (ctx context.Context ) (string , string ) {
150
- if k , ok := ctx .Value (grafanaAccessTokenKey {}).([]string ); ok {
151
- return k [0 ], k [1 ]
152
- }
153
- return "" , ""
154
- }
155
-
156
149
type grafanaClientKey struct {}
157
150
158
151
func makeBasePath (path string ) string {
@@ -188,7 +181,8 @@ func NewGrafanaClient(ctx context.Context, grafanaURL, apiKey string) *client.Gr
188
181
cfg .APIKey = apiKey
189
182
}
190
183
191
- cfg .Debug = GrafanaDebugFromContext (ctx )
184
+ config := GrafanaConfigFromContext (ctx )
185
+ cfg .Debug = config .Debug
192
186
193
187
slog .Debug ("Creating Grafana client" , "url" , parsedURL .Redacted (), "api_key_set" , apiKey != "" )
194
188
return client .NewHTTPClientWithConfig (strfmt .Default , cfg )
@@ -258,6 +252,7 @@ var ExtractIncidentClientFromEnv server.StdioContextFunc = func(ctx context.Cont
258
252
}
259
253
slog .Debug ("Creating Incident client" , "url" , parsedURL .Redacted (), "api_key_set" , apiKey != "" )
260
254
client := incident .NewClient (incidentURL , apiKey )
255
+
261
256
return context .WithValue (ctx , incidentClientKey {}, client )
262
257
}
263
258
@@ -275,6 +270,7 @@ var ExtractIncidentClientFromHeaders httpContextFunc = func(ctx context.Context,
275
270
}
276
271
incidentURL := fmt .Sprintf ("%s/api/plugins/grafana-irm-app/resources/api/v1/" , grafanaURL )
277
272
client := incident .NewClient (incidentURL , apiKey )
273
+
278
274
return context .WithValue (ctx , incidentClientKey {}, client )
279
275
}
280
276
@@ -322,10 +318,10 @@ func ComposeHTTPContextFuncs(funcs ...httpContextFunc) server.HTTPContextFunc {
322
318
323
319
// ComposedStdioContextFunc returns a StdioContextFunc that comprises all predefined StdioContextFuncs,
324
320
// as well as the Grafana debug flag.
325
- func ComposedStdioContextFunc (debug bool ) server.StdioContextFunc {
321
+ func ComposedStdioContextFunc (config GrafanaConfig ) server.StdioContextFunc {
326
322
return ComposeStdioContextFuncs (
327
323
func (ctx context.Context ) context.Context {
328
- return WithGrafanaDebug (ctx , debug )
324
+ return WithGrafanaConfig (ctx , config )
329
325
},
330
326
ExtractGrafanaInfoFromEnv ,
331
327
ExtractGrafanaClientFromEnv ,
@@ -334,10 +330,10 @@ func ComposedStdioContextFunc(debug bool) server.StdioContextFunc {
334
330
}
335
331
336
332
// ComposedSSEContextFunc is a SSEContextFunc that comprises all predefined SSEContextFuncs.
337
- func ComposedSSEContextFunc (debug bool ) server.SSEContextFunc {
333
+ func ComposedSSEContextFunc (config GrafanaConfig ) server.SSEContextFunc {
338
334
return ComposeSSEContextFuncs (
339
335
func (ctx context.Context , req * http.Request ) context.Context {
340
- return WithGrafanaDebug (ctx , debug )
336
+ return WithGrafanaConfig (ctx , config )
341
337
},
342
338
ExtractGrafanaInfoFromHeaders ,
343
339
ExtractGrafanaClientFromHeaders ,
@@ -346,10 +342,10 @@ func ComposedSSEContextFunc(debug bool) server.SSEContextFunc {
346
342
}
347
343
348
344
// ComposedHTTPContextFunc is a HTTPContextFunc that comprises all predefined HTTPContextFuncs.
349
- func ComposedHTTPContextFunc (debug bool ) server.HTTPContextFunc {
345
+ func ComposedHTTPContextFunc (config GrafanaConfig ) server.HTTPContextFunc {
350
346
return ComposeHTTPContextFuncs (
351
347
func (ctx context.Context , req * http.Request ) context.Context {
352
- return WithGrafanaDebug (ctx , debug )
348
+ return WithGrafanaConfig (ctx , config )
353
349
},
354
350
ExtractGrafanaInfoFromHeaders ,
355
351
ExtractGrafanaClientFromHeaders ,
0 commit comments