Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,26 @@ To use debug mode with the Claude Desktop configuration, update your config as f

> Note: As with the standard configuration, the `-t stdio` argument is required to override the default SSE mode in the Docker image.

### AAD Configuration
If you wish to use AAD auth to authenticate to a grafana instance.

**If using the binary:**

```json
{
"mcpServers": {
"grafana": {
"command": "mcp-grafana",
"args": [],
"env": {
"GRAFANA_URL": "https://<your-base-url>.grafana.azure.com/",
"USE_AAD_AUTH": "true",
}
}
}
}
```

### TLS Configuration

If your Grafana instance is behind mTLS or requires custom TLS certificates, you can configure the MCP server to use custom certificates. The server supports the following TLS configuration options:
Expand Down
8 changes: 8 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ toolchain go1.24.2

require (
connectrpc.com/connect v1.18.1
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1
github.com/go-openapi/runtime v0.28.0
github.com/go-openapi/strfmt v0.23.0
github.com/google/uuid v1.6.0
Expand All @@ -23,6 +24,9 @@ require (
)

require (
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 // indirect
github.com/BurntSushi/toml v1.4.0 // indirect
github.com/apache/arrow-go/v18 v18.2.0 // indirect
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
Expand Down Expand Up @@ -50,6 +54,7 @@ require (
github.com/go-openapi/validate v0.24.0 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/flatbuffers v25.2.10+incompatible // indirect
github.com/google/go-cmp v0.7.0 // indirect
Expand All @@ -73,6 +78,7 @@ require (
github.com/jszwedko/go-datemath v0.1.1-0.20230526204004-640a500621d6 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/magefile/mage v1.15.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattetti/filebuffer v1.0.1 // indirect
Expand All @@ -93,6 +99,7 @@ require (
github.com/opentracing/opentracing-go v1.2.0 // indirect
github.com/perimeterx/marshmallow v1.1.5 // indirect
github.com/pierrec/lz4/v4 v4.1.22 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
Expand Down Expand Up @@ -122,6 +129,7 @@ require (
go.opentelemetry.io/otel/sdk v1.35.0 // indirect
go.opentelemetry.io/otel/trace v1.35.0 // indirect
go.opentelemetry.io/proto/otlp v1.5.0 // indirect
golang.org/x/crypto v0.38.0 // indirect
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect
golang.org/x/mod v0.24.0 // indirect
golang.org/x/net v0.40.0 // indirect
Expand Down
25 changes: 25 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
connectrpc.com/connect v1.18.1 h1:PAg7CjSAGvscaf6YZKUefjoih5Z/qYkyaTrBW8xvYPw=
connectrpc.com/connect v1.18.1/go.mod h1:0292hj1rnx8oFrStN7cB4jjVBeqs+Yx5yDIC2prWDO8=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 h1:Gt0j3wceWMwPmiazCa8MzMA0MfhmPIz0Qp0FJ6qcM0U=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 h1:B+blDbyVIG3WaikNxPnhPiJ1MThR03b3vKGtER95TP4=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1/go.mod h1:JdM5psgjfBf5fo2uWOZhflPWyDBZ/O/CNAH9CtsuZE4=
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY=
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 h1:FPKJS1T+clwv+OLGt13a8UjqeRuh0O4SJ3lUriThc+4=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1/go.mod h1:j2chePtV91HrC22tGoRX3sGY42uF13WzmmV80/OdVAA=
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM=
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE=
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs=
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
Expand Down Expand Up @@ -34,6 +46,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
Expand Down Expand Up @@ -81,6 +95,8 @@ github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
Expand Down Expand Up @@ -150,6 +166,8 @@ github.com/jszwedko/go-datemath v0.1.1-0.20230526204004-640a500621d6/go.mod h1:W
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU=
github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/asmfmt v1.3.2 h1:4Ri7ox3EwapiOjCki+hw14RyKk201CN4rzyCJRFLpK4=
Expand Down Expand Up @@ -221,6 +239,8 @@ github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX
github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=
github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
Expand All @@ -236,6 +256,8 @@ github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0leargg
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/prometheus/prometheus v0.304.1 h1:e4kpJMb2Vh/PcR6LInake+ofcvFYHT+bCfmBvOkaZbY=
github.com/prometheus/prometheus v0.304.1/go.mod h1:ioGx2SGKTY+fLnJSQCdTHqARVldGNS8OlIe3kvp98so=
github.com/redis/go-redis/v9 v9.8.0 h1:q3nRvjrlge/6UD7eTu/DSg2uYiU2mCL0G/uzBWqhicI=
github.com/redis/go-redis/v9 v9.8.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
Expand Down Expand Up @@ -326,6 +348,8 @@ go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA=
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
Expand Down Expand Up @@ -356,6 +380,7 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
Expand Down
104 changes: 93 additions & 11 deletions mcpgrafana.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ import (
"net/url"
"os"
"strings"
"sync"

"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
"github.com/go-openapi/strfmt"
"github.com/grafana/grafana-openapi-client-go/client"
"github.com/grafana/incident-go"
Expand All @@ -23,9 +26,13 @@ const (

grafanaURLEnvVar = "GRAFANA_URL"
grafanaAPIEnvVar = "GRAFANA_API_KEY"
useAADEnvVar = "USE_AAD_AUTH"

grafanaURLHeader = "X-Grafana-URL"
grafanaAPIKeyHeader = "X-Grafana-API-Key"

// Default AAD Resources
defaultGrafanaAADResource = "ce34e7e5-485f-4d76-964f-b3d2b16d1e4f"
)

func urlAndAPIKeyFromEnv() (string, string) {
Expand All @@ -40,6 +47,28 @@ func urlAndAPIKeyFromHeaders(req *http.Request) (string, string) {
return u, apiKey
}

// isAADEnabled checks if AAD authentication is enabled via environment variable
func isAADEnabled() bool {
val := os.Getenv(useAADEnvVar)
return strings.ToLower(val) == "true" || val == "1"
}

// createAADConfigFromEnv creates a DefaultAzureCredential instance
// if AAD authentication is enabled via environment variable.
func createAADConfigFromEnv() *azidentity.DefaultAzureCredential {
if !isAADEnabled() {
return nil
}
// Create a new DefaultAzureCredential instance
// This will use the environment variables set by Azure CLI or Managed Identity
// if available, otherwise it will fall back to other authentication methods.
cred, err := azidentity.NewDefaultAzureCredential(nil)
if err != nil {
panic(fmt.Errorf("failed to create Azure AD credential: %w", err))
}
return cred
}

// grafanaConfigKey is the context key for Grafana configuration.
type grafanaConfigKey struct{}

Expand Down Expand Up @@ -72,6 +101,8 @@ type GrafanaConfig struct {

// TLSConfig holds TLS configuration for all Grafana clients.
TLSConfig *TLSConfig

AADCredential *azidentity.DefaultAzureCredential // credential to use Azure AD authentication for Grafana API calls.
}

// WithGrafanaConfig adds Grafana configuration to the context.
Expand Down Expand Up @@ -156,6 +187,8 @@ var ExtractGrafanaInfoFromEnv server.StdioContextFunc = func(ctx context.Context
config := GrafanaConfigFromContext(ctx)
config.URL = u
config.APIKey = apiKey
config.AADCredential = createAADConfigFromEnv()

return WithGrafanaConfig(ctx, config)
}

Expand Down Expand Up @@ -184,6 +217,7 @@ var ExtractGrafanaInfoFromHeaders httpContextFunc = func(ctx context.Context, re
config := GrafanaConfigFromContext(ctx)
config.URL = u
config.APIKey = apiKey
config.AADCredential = createAADConfigFromEnv()
return WithGrafanaConfig(ctx, config)
}

Expand All @@ -209,7 +243,7 @@ func MustWithOnBehalfOfAuth(ctx context.Context, accessToken, userToken string)
return ctx
}

type grafanaClientKey struct{}
type grafanaClientFunctorKey struct{}

func makeBasePath(path string) string {
return strings.Join([]string{strings.TrimRight(path, "/"), "api"}, "/")
Expand Down Expand Up @@ -274,8 +308,12 @@ var ExtractGrafanaClientFromEnv server.StdioContextFunc = func(ctx context.Conte
}
apiKey := os.Getenv(grafanaAPIEnvVar)

grafanaClient := NewGrafanaClient(ctx, grafanaURL, apiKey)
return context.WithValue(ctx, grafanaClientKey{}, grafanaClient)
if isAADEnabled() {
return WithAADGrafanaClientFunc(ctx)
} else {
grafanaClient := NewGrafanaClient(ctx, grafanaURL, apiKey)
return WithGrafanaClient(ctx, grafanaClient)
}
}

// ExtractGrafanaClientFromHeaders is a HTTPContextFunc that extracts Grafana configuration
Expand All @@ -294,24 +332,68 @@ var ExtractGrafanaClientFromHeaders httpContextFunc = func(ctx context.Context,
apiKey = apiKeyEnv
}

grafanaClient := NewGrafanaClient(ctx, u, apiKey)
return WithGrafanaClient(ctx, grafanaClient)
if isAADEnabled() {
return WithAADGrafanaClientFunc(ctx)
} else {
grafanaClient := NewGrafanaClient(ctx, u, apiKey)
return WithGrafanaClient(ctx, grafanaClient)
}
}

// WithGrafanaClient sets the Grafana client in the context.
//
// It can be retrieved using GrafanaClientFromContext.
func WithGrafanaClient(ctx context.Context, client *client.GrafanaHTTPAPI) context.Context {
return context.WithValue(ctx, grafanaClientKey{}, client)
func WithGrafanaClient(ctx context.Context, clientParam *client.GrafanaHTTPAPI) context.Context {
return context.WithValue(ctx, grafanaClientFunctorKey{}, func() (*client.GrafanaHTTPAPI, error) { return clientParam, nil })
}

func WithAADGrafanaClientFunc(ctx context.Context) context.Context {
var mutex sync.Mutex // protects the cached token and cached client
config := GrafanaConfigFromContext(ctx)
cred := config.AADCredential
cachedToken, err := cred.GetToken(ctx, policy.TokenRequestOptions{
Scopes: []string{defaultGrafanaAADResource},
})
if err != nil {
panic(fmt.Errorf("failed to get AAD token for Grafana: %w", err))
}
cachedGrafanaClient := NewGrafanaClient(ctx, config.URL, cachedToken.Token)

slog.Debug("Constructed cached Grafana client with AAD authentication")
var functor func() (*client.GrafanaHTTPAPI, error) = func() (*client.GrafanaHTTPAPI, error) {

funcCred, funcErr := cred.GetToken(ctx, policy.TokenRequestOptions{
Scopes: []string{defaultGrafanaAADResource},
})
// TODO: do error handling here, and update functor to return errors
if funcErr != nil {
return nil, fmt.Errorf("failed to get AAD token for Grafana: %w", funcErr)
}
// Use the cached client if the token is still valid, otherwise create a new one
mutex.Lock()
defer mutex.Unlock()
if cachedToken == funcCred {
// If the cached token is still valid, return the cached client
return cachedGrafanaClient, nil
}

slog.Debug("Cached client didn't match need to refresh it.")

cachedToken = funcCred
cachedGrafanaClient = NewGrafanaClient(ctx, config.URL, cachedToken.Token)
return cachedGrafanaClient, nil
}

return context.WithValue(ctx, grafanaClientFunctorKey{}, functor)
}

// GrafanaClientFromContext retrieves the Grafana client from the context.
func GrafanaClientFromContext(ctx context.Context) *client.GrafanaHTTPAPI {
c, ok := ctx.Value(grafanaClientKey{}).(*client.GrafanaHTTPAPI)
func GrafanaClientFromContext(ctx context.Context) (*client.GrafanaHTTPAPI, error) {
c, ok := ctx.Value(grafanaClientFunctorKey{}).(func() (*client.GrafanaHTTPAPI, error))
if !ok {
return nil
return nil, fmt.Errorf("grafana client not found in context")
}
return c
return c()
}

type incidentClientKey struct{}
Expand Down
Loading