From 257686cbd8970d740f221a376848b1d57eae9fcc Mon Sep 17 00:00:00 2001 From: IevaVasiljeva Date: Tue, 28 Mar 2023 11:21:15 +0100 Subject: [PATCH] use temp Grafana service accounts instead of API keys when managing Grafana Stack --- client.go | 4 +- cloud_grafana_api_key.go | 57 ----------------------- cloud_grafana_service_account.go | 80 ++++++++++++++++++++++++++++++++ 3 files changed, 82 insertions(+), 59 deletions(-) delete mode 100644 cloud_grafana_api_key.go create mode 100644 cloud_grafana_service_account.go diff --git a/client.go b/client.go index 16679fde..2f43100f 100644 --- a/client.go +++ b/client.go @@ -26,7 +26,7 @@ type Client struct { // Config contains client configuration. type Config struct { - // APIKey is an optional API key. + // APIKey is an optional API key or service account token. APIKey string // BasicAuth is optional basic auth credentials. BasicAuth *url.Userinfo @@ -36,7 +36,7 @@ type Config struct { Client *http.Client // OrgID provides an optional organization ID // with BasicAuth, it defaults to last used org - // with APIKey, it is disallowed because API keys are scoped to a single org + // with APIKey, it is disallowed because service account tokens are scoped to a single org OrgID int64 // NumRetries contains the number of attempted retries NumRetries int diff --git a/cloud_grafana_api_key.go b/cloud_grafana_api_key.go deleted file mode 100644 index 11d788c5..00000000 --- a/cloud_grafana_api_key.go +++ /dev/null @@ -1,57 +0,0 @@ -package gapi - -import ( - "bytes" - "encoding/json" - "fmt" - "time" -) - -// This function creates a API key inside the Grafana instance running in stack `stack`. It's used in order -// to provision API keys inside Grafana while just having access to a Grafana Cloud API key. -// -// See https://grafana.com/docs/grafana-cloud/api/#create-grafana-api-keys for more information. -func (c *Client) CreateGrafanaAPIKeyFromCloud(stack string, input *CreateAPIKeyRequest) (*CreateAPIKeyResponse, error) { - data, err := json.Marshal(input) - if err != nil { - return nil, err - } - - resp := &CreateAPIKeyResponse{} - err = c.request("POST", fmt.Sprintf("/api/instances/%s/api/auth/keys", stack), nil, bytes.NewBuffer(data), resp) - return resp, err -} - -// The Grafana Cloud API is disconnected from the Grafana API on the stacks unfortunately. That's why we can't use -// the Grafana Cloud API key to fully manage API keys on the Grafana API. The only thing we can do is to create -// a temporary Admin key, and create a Grafana API client with that. -func (c *Client) CreateTemporaryStackGrafanaClient(stackSlug, tempKeyPrefix string, tempKeyDuration time.Duration) (tempClient *Client, cleanup func() error, err error) { - stack, err := c.StackBySlug(stackSlug) - if err != nil { - return nil, nil, err - } - - name := fmt.Sprintf("%s-%d", tempKeyPrefix, time.Now().UnixNano()) - req := &CreateAPIKeyRequest{ - Name: name, - Role: "Admin", - SecondsToLive: int64(tempKeyDuration.Seconds()), - } - - apiKey, err := c.CreateGrafanaAPIKeyFromCloud(stackSlug, req) - if err != nil { - return nil, nil, err - } - - client, err := New(stack.URL, Config{APIKey: apiKey.Key}) - if err != nil { - return nil, nil, err - } - - cleanup = func() error { - _, err = client.DeleteAPIKey(apiKey.ID) - return err - } - - return client, cleanup, nil -} diff --git a/cloud_grafana_service_account.go b/cloud_grafana_service_account.go new file mode 100644 index 00000000..3fbb2c72 --- /dev/null +++ b/cloud_grafana_service_account.go @@ -0,0 +1,80 @@ +package gapi + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "time" +) + +// This function creates a service account inside the Grafana instance running in stack `stack`. It's used in order +// to provision service accounts inside Grafana while just having access to a Grafana Cloud API key. +func (c *Client) CreateGrafanaServiceAccountFromCloud(stack string, input *CreateServiceAccountRequest) (*ServiceAccountDTO, error) { + data, err := json.Marshal(input) + if err != nil { + return nil, err + } + + resp := &ServiceAccountDTO{} + err = c.request(http.MethodPost, fmt.Sprintf("/api/instances/%s/api/serviceaccounts", stack), nil, bytes.NewBuffer(data), resp) + return resp, err +} + +// This function creates a service account token inside the Grafana instance running in stack `stack`. It's used in order +// to provision service accounts inside Grafana while just having access to a Grafana Cloud API key. +func (c *Client) CreateGrafanaServiceAccountTokenFromCloud(stack string, input *CreateServiceAccountTokenRequest) (*CreateServiceAccountTokenResponse, error) { + data, err := json.Marshal(input) + if err != nil { + return nil, err + } + + resp := &CreateServiceAccountTokenResponse{} + err = c.request(http.MethodPost, fmt.Sprintf("/api/instances/%s/api/serviceaccounts/%d/tokens", stack, input.ServiceAccountID), nil, bytes.NewBuffer(data), resp) + return resp, err +} + +// The Grafana Cloud API is disconnected from the Grafana API on the stacks unfortunately. That's why we can't use +// the Grafana Cloud API key to fully manage service accounts on the Grafana API. The only thing we can do is to create +// a temporary Admin service account, and create a Grafana API client with that. +func (c *Client) CreateTemporaryStackGrafanaClient(stackSlug, tempSaPrefix string, tempKeyDuration time.Duration) (tempClient *Client, cleanup func() error, err error) { + stack, err := c.StackBySlug(stackSlug) + if err != nil { + return nil, nil, err + } + + name := fmt.Sprintf("%s%d", tempSaPrefix, time.Now().UnixNano()) + + req := &CreateServiceAccountRequest{ + Name: name, + Role: "Admin", + } + + sa, err := c.CreateGrafanaServiceAccountFromCloud(stackSlug, req) + if err != nil { + return nil, nil, err + } + + tokenRequest := &CreateServiceAccountTokenRequest{ + Name: name, + ServiceAccountID: sa.ID, + SecondsToLive: int64(tempKeyDuration.Seconds()), + } + + token, err := c.CreateGrafanaServiceAccountTokenFromCloud(stackSlug, tokenRequest) + if err != nil { + return nil, nil, err + } + + client, err := New(stack.URL, Config{APIKey: token.Key}) + if err != nil { + return nil, nil, err + } + + cleanup = func() error { + _, err = client.DeleteServiceAccount(sa.ID) + return err + } + + return client, cleanup, nil +}