Skip to content
This repository was archived by the owner on Jan 15, 2024. It is now read-only.

Commit 5c6d8c2

Browse files
Merge pull request #140 from grafana/use-sas-through-cloud-provider
Service accounts: use temp Grafana service account instead of API key
2 parents 618bf97 + b33991a commit 5c6d8c2

3 files changed

+82
-37
lines changed

client.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ type Client struct {
2626

2727
// Config contains client configuration.
2828
type Config struct {
29-
// APIKey is an optional API key.
29+
// APIKey is an optional API key or service account token.
3030
APIKey string
3131
// BasicAuth is optional basic auth credentials.
3232
BasicAuth *url.Userinfo
@@ -36,7 +36,7 @@ type Config struct {
3636
Client *http.Client
3737
// OrgID provides an optional organization ID
3838
// with BasicAuth, it defaults to last used org
39-
// with APIKey, it is disallowed because API keys are scoped to a single org
39+
// with APIKey, it is disallowed because service account tokens are scoped to a single org
4040
OrgID int64
4141
// NumRetries contains the number of attempted retries
4242
NumRetries int

cloud_grafana_api_key.go

-35
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"bytes"
55
"encoding/json"
66
"fmt"
7-
"time"
87
)
98

109
// This function creates a API key inside the Grafana instance running in stack `stack`. It's used in order
@@ -21,37 +20,3 @@ func (c *Client) CreateGrafanaAPIKeyFromCloud(stack string, input *CreateAPIKeyR
2120
err = c.request("POST", fmt.Sprintf("/api/instances/%s/api/auth/keys", stack), nil, bytes.NewBuffer(data), resp)
2221
return resp, err
2322
}
24-
25-
// The Grafana Cloud API is disconnected from the Grafana API on the stacks unfortunately. That's why we can't use
26-
// the Grafana Cloud API key to fully manage API keys on the Grafana API. The only thing we can do is to create
27-
// a temporary Admin key, and create a Grafana API client with that.
28-
func (c *Client) CreateTemporaryStackGrafanaClient(stackSlug, tempKeyPrefix string, tempKeyDuration time.Duration) (tempClient *Client, cleanup func() error, err error) {
29-
stack, err := c.StackBySlug(stackSlug)
30-
if err != nil {
31-
return nil, nil, err
32-
}
33-
34-
name := fmt.Sprintf("%s-%d", tempKeyPrefix, time.Now().UnixNano())
35-
req := &CreateAPIKeyRequest{
36-
Name: name,
37-
Role: "Admin",
38-
SecondsToLive: int64(tempKeyDuration.Seconds()),
39-
}
40-
41-
apiKey, err := c.CreateGrafanaAPIKeyFromCloud(stackSlug, req)
42-
if err != nil {
43-
return nil, nil, err
44-
}
45-
46-
client, err := New(stack.URL, Config{APIKey: apiKey.Key})
47-
if err != nil {
48-
return nil, nil, err
49-
}
50-
51-
cleanup = func() error {
52-
_, err = client.DeleteAPIKey(apiKey.ID)
53-
return err
54-
}
55-
56-
return client, cleanup, nil
57-
}

cloud_grafana_service_account.go

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package gapi
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"fmt"
7+
"net/http"
8+
"time"
9+
)
10+
11+
// This function creates a service account inside the Grafana instance running in stack `stack`. It's used in order
12+
// to provision service accounts inside Grafana while just having access to a Grafana Cloud API key.
13+
func (c *Client) CreateGrafanaServiceAccountFromCloud(stack string, input *CreateServiceAccountRequest) (*ServiceAccountDTO, error) {
14+
data, err := json.Marshal(input)
15+
if err != nil {
16+
return nil, err
17+
}
18+
19+
resp := &ServiceAccountDTO{}
20+
err = c.request(http.MethodPost, fmt.Sprintf("/api/instances/%s/api/serviceaccounts", stack), nil, bytes.NewBuffer(data), resp)
21+
return resp, err
22+
}
23+
24+
// This function creates a service account token inside the Grafana instance running in stack `stack`. It's used in order
25+
// to provision service accounts inside Grafana while just having access to a Grafana Cloud API key.
26+
func (c *Client) CreateGrafanaServiceAccountTokenFromCloud(stack string, input *CreateServiceAccountTokenRequest) (*CreateServiceAccountTokenResponse, error) {
27+
data, err := json.Marshal(input)
28+
if err != nil {
29+
return nil, err
30+
}
31+
32+
resp := &CreateServiceAccountTokenResponse{}
33+
err = c.request(http.MethodPost, fmt.Sprintf("/api/instances/%s/api/serviceaccounts/%d/tokens", stack, input.ServiceAccountID), nil, bytes.NewBuffer(data), resp)
34+
return resp, err
35+
}
36+
37+
// The Grafana Cloud API is disconnected from the Grafana API on the stacks unfortunately. That's why we can't use
38+
// the Grafana Cloud API key to fully manage service accounts on the Grafana API. The only thing we can do is to create
39+
// a temporary Admin service account, and create a Grafana API client with that.
40+
func (c *Client) CreateTemporaryStackGrafanaClient(stackSlug, tempSaPrefix string, tempKeyDuration time.Duration) (tempClient *Client, cleanup func() error, err error) {
41+
stack, err := c.StackBySlug(stackSlug)
42+
if err != nil {
43+
return nil, nil, err
44+
}
45+
46+
name := fmt.Sprintf("%s%d", tempSaPrefix, time.Now().UnixNano())
47+
48+
req := &CreateServiceAccountRequest{
49+
Name: name,
50+
Role: "Admin",
51+
}
52+
53+
sa, err := c.CreateGrafanaServiceAccountFromCloud(stackSlug, req)
54+
if err != nil {
55+
return nil, nil, err
56+
}
57+
58+
tokenRequest := &CreateServiceAccountTokenRequest{
59+
Name: name,
60+
ServiceAccountID: sa.ID,
61+
SecondsToLive: int64(tempKeyDuration.Seconds()),
62+
}
63+
64+
token, err := c.CreateGrafanaServiceAccountTokenFromCloud(stackSlug, tokenRequest)
65+
if err != nil {
66+
return nil, nil, err
67+
}
68+
69+
client, err := New(stack.URL, Config{APIKey: token.Key})
70+
if err != nil {
71+
return nil, nil, err
72+
}
73+
74+
cleanup = func() error {
75+
_, err = client.DeleteServiceAccount(sa.ID)
76+
return err
77+
}
78+
79+
return client, cleanup, nil
80+
}

0 commit comments

Comments
 (0)