Skip to content

Commit a7f291a

Browse files
authored
Add support for Managed Grafana v10 (#28)
* Introduce support for service account tokens * Update README * Update tests * Bump binary version * Fix existing tests * Update SDK for v10 support * Go mod tidy
1 parent aa24334 commit a7f291a

File tree

11 files changed

+315
-94
lines changed

11 files changed

+315
-94
lines changed

README.md

+48-9
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,25 @@
33
[![Build](https://github.com/aws-observability/amazon-managed-grafana-migrator/actions/workflows/go.yml/badge.svg)](https://github.com/aws-observability/amazon-managed-grafana-migrator/actions/workflows/go.yml)
44
[![Go Report Card](https://goreportcard.com/badge/github.com/aws-observability/amazon-managed-grafana-migrator)](https://goreportcard.com/report/github.com/aws-observability/amazon-managed-grafana-migrator)
55

6-
🚨 Jul-25: Alerts migration are currently disabled with v0.1.9. See [#19](https://github.com/aws-observability/amazon-managed-grafana-migrator/issues/19)
6+
🎉 May-15-24: v0.2.0 supports Grafana Service Accounts for v9 and v10 workspaces.
7+
See [Amazon Managed Grafana announces support for Grafana version 10.4]()
78

8-
🎉 Jul-19: [Amazon Grafana supports now in-place update from v8.4 to v9.4](https://aws.amazon.com/about-aws/whats-new/2023/07/amazon-managed-grafana-in-place-update-version-9-4/)
9+
🚨 Jul-25-23: Alerts migration are currently disabled with v0.1.9. See [#19](https://github.com/aws-observability/amazon-managed-grafana-migrator/issues/19)
10+
11+
🎉 Jul-19-23: [Amazon Grafana supports now in-place update from v8.4 to v9.4](https://aws.amazon.com/about-aws/whats-new/2023/07/amazon-managed-grafana-in-place-update-version-9-4/)
912

1013
Amazon Managed Grafana Migrator is a CLI migration utility to migrate Grafana
1114
content (data sources, dashboards, folders and alert rules) to Amazon Managed
1215
Grafana. It supports the following migration scenarios:
1316

14-
- Migrating from and to Amazon Managed Grafana Workspace (eg. Moving to v9.4), although consider using the native functionality in the AWS Console
17+
- Migrating from and to Amazon Managed Grafana Workspace (eg. Moving to v10.4), although consider using the native functionality in the AWS Console, after testing
1518
- Migrating from a Grafana server to an Amazon Managed Grafana Workspace
1619

1720
<img src="https://user-images.githubusercontent.com/10175027/235176809-9b71af1a-79a9-416a-b26e-ccdf725779d7.gif" width="80%" height="80%"/>
1821

19-
20-
:warning: Alerting rules migration are only supported in a migration to Amazon
21-
Managed Grafana v9.4. (migrating alerts to v8.x is not supported)
22+
Amazon Managed Grafana v10.4 workspaces will require to
23+
provide an `ADMIN` level [Grafana Service Account]() with the
24+
`--src-service-account-id` or `--src-service-account-id` flags.
2225

2326
## Installation
2427

@@ -52,6 +55,30 @@ amazon-managed-grafana-migrator -v
5255
amazon-managed-grafana-migrator discover --region eu-west-1
5356
```
5457

58+
### Migrating to Amazon Managed Grafana v10
59+
60+
v9 and v10 introduced Grafana Service Accounts which will be required by the
61+
migrator, especially for v10. Note that Service Accounts are billed as active
62+
users
63+
64+
1. Creating a Service Account
65+
66+
```console
67+
aws grafana create-workspace-service-account --workspace-id g-abcdef5678 \
68+
--grafana-role ADMIN \
69+
--name <SA_NAME_HERE>
70+
```
71+
72+
2. Running the migration
73+
74+
```console
75+
amazon-managed-grafana-migrator migrate \
76+
--src-url https://grafana.example.com/
77+
--src-api-key API_KEY_HERE
78+
--dst g-abcdef5678.grafana-workspace.us-west-2.amazonaws.com
79+
--dst-service-account-id SERVICE_ACCOUNT_ID_HERE
80+
```
81+
5582
### Migrating between Workspaces
5683

5784
```console
@@ -60,7 +87,17 @@ amazon-managed-grafana-migrator migrate \
6087
--dst g-abcdef5678.grafana-workspace.us-west-2.amazonaws.com
6188
```
6289

63-
### Migrating to Amazon Managed Grafana
90+
Or for v9+ workspaces:
91+
92+
```console
93+
amazon-managed-grafana-migrator migrate \
94+
--src g-abcdef1234.grafana-workspace.eu-central-1.amazonaws.com \
95+
--src-service-account-id SERVICE_ACCOUNT_ID_HERE
96+
--dst g-abcdef5678.grafana-workspace.us-west-2.amazonaws.com
97+
--dst-service-account-id SERVICE_ACCOUNT_ID_HERE
98+
```
99+
100+
### Migrating to Amazon Managed Grafana v8/v9
64101

65102
```console
66103
amazon-managed-grafana-migrator migrate \
@@ -91,9 +128,11 @@ start. Below are the minimum permissions required by the tool:
91128
{
92129
"Effect": "Allow",
93130
"Action": [
94-
"grafana:DeleteWorkspaceApiKey",
95131
"grafana:DescribeWorkspace",
96-
"grafana:CreateWorkspaceApiKey"
132+
"grafana:CreateWorkspaceApiKey",
133+
"grafana:DeleteWorkspaceApiKey",
134+
"grafana:CreateWorkspaceServiceAccountToken",
135+
"grafana:DeleteWorkspaceServiceAccountToken"
97136
],
98137
"Resource": "arn:aws:grafana:*:<ACCOUNT_ID>:/workspaces/<WORKSPACE_ID>"
99138
},

go.mod

+3-3
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@ require (
1515
github.com/mattn/go-colorable v0.1.13 // indirect
1616
github.com/mattn/go-isatty v0.0.20 // indirect
1717
github.com/pmezard/go-difflib v1.0.0 // indirect
18-
golang.org/x/sys v0.19.0 // indirect
18+
golang.org/x/sys v0.20.0 // indirect
1919
gopkg.in/yaml.v3 v3.0.1 // indirect
2020
)
2121

2222
require (
23-
github.com/aws/aws-sdk-go v1.51.30
24-
github.com/fatih/color v1.16.0
23+
github.com/aws/aws-sdk-go v1.53.3
24+
github.com/fatih/color v1.17.0
2525
github.com/golang/mock v1.6.0
2626
github.com/grafana/grafana-api-golang-client v0.27.0
2727
github.com/inconshreveable/mousetrap v1.1.0 // indirect

go.sum

+6-6
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
github.com/aws/aws-sdk-go v1.51.30 h1:RVFkjn9P0JMwnuZCVH0TlV5k9zepHzlbc4943eZMhGw=
2-
github.com/aws/aws-sdk-go v1.51.30/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk=
1+
github.com/aws/aws-sdk-go v1.53.3 h1:xv0iGCCLdf6ZtlLPMCBjm+tU9UBLP5hXnSqnbKFYmto=
2+
github.com/aws/aws-sdk-go v1.53.3/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk=
33
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
44
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
55
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
66
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
7-
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
8-
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
7+
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
8+
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
99
github.com/gobs/pretty v0.0.0-20180724170744-09732c25a95b h1:/vQ+oYKu+JoyaMPDsv5FzwuL2wwWBgBbtj/YLCi4LuA=
1010
github.com/gobs/pretty v0.0.0-20180724170744-09732c25a95b/go.mod h1:Xo4aNUOrJnVruqWQJBtW6+bTBDTniY8yZum5rF3b5jw=
1111
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
@@ -51,8 +51,8 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w
5151
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
5252
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
5353
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
54-
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
55-
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
54+
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
55+
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
5656
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
5757
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
5858
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=

internal/pkg/app/app.go

+1-8
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ type App struct {
3131
// const minAlertingMigrationVersion = 9.4
3232

3333
// Run orchestrates the migration of grafana contents
34-
func (a *App) Run(srcCustomGrafanaClient CustomGrafanaClient) error {
34+
func (a *App) Run() error {
3535
log.Info()
3636
migratedDs, err := a.migrateDataSources()
3737
if err != nil {
@@ -54,12 +54,5 @@ func (a *App) Run(srcCustomGrafanaClient CustomGrafanaClient) error {
5454
log.Info()
5555

5656
log.Info("Skipping alert rules migration")
57-
/*
58-
alertsMigrated, err := a.migrateAlertRules(fx, srcCustomGrafanaClient)
59-
if err != nil {
60-
return err
61-
}
62-
log.Success("Migrated ", alertsMigrated, " alerts")
63-
*/
6457
return nil
6558
}

internal/pkg/app/app_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ func TestApp_Run(t *testing.T) {
176176
Dst: mockDst,
177177
}
178178

179-
err := app.Run(CustomGrafanaClient{Client: mockcustomAPI})
179+
err := app.Run()
180180
if tc.expectedError != nil {
181181
require.EqualError(t, err, tc.expectedError.Error())
182182
} else {

internal/pkg/app/input.go

+53-28
Original file line numberDiff line numberDiff line change
@@ -11,37 +11,45 @@ import (
1111
gapi "github.com/grafana/grafana-api-golang-client"
1212
)
1313

14+
const (
15+
AMG_V10 = "10.4"
16+
AMG_V9 = "9.4"
17+
AMG_V8 = "8.4"
18+
)
19+
1420
// GrafanaInput holds the infos about the grafana server from the CLI
1521
type GrafanaInput struct {
16-
URL string
17-
WorkspaceID string
18-
APIKey string
19-
Region string
20-
IsAMG bool
21-
IsGamma bool
22+
URL string
23+
WorkspaceID string
24+
APIKey string
25+
Region string
26+
ServiceAccountID string
27+
WorkspaceVersion string
28+
IsAMG bool
29+
IsGamma bool
2230
}
2331

2432
// GrafanaHTTPClient contains the grafana client and AWS API key
2533
type GrafanaHTTPClient struct {
2634
Client *gapi.Client
27-
Key aws.AMGApiKey
35+
Auth aws.GrafanaAuth
2836
Input *GrafanaInput
2937
}
3038

3139
// NewGrafanaInput validate input from command line to return a GrafanaInput object
32-
func NewGrafanaInput(wkspEndpoint, url, apiKey string) (GrafanaInput, error) {
33-
40+
func NewGrafanaInput(wkspEndpoint, url, serviceAccountID, apiKey string) (GrafanaInput, error) {
3441
if wkspEndpoint != "" {
3542
sx := strings.Split(wkspEndpoint, ".")
3643
if len(sx) != 5 {
3744
return GrafanaInput{}, fmt.Errorf("invalid input: workspace should be its DNS endpoint")
3845
}
3946
return GrafanaInput{
40-
WorkspaceID: sx[0],
41-
Region: sx[2],
42-
URL: wkspEndpoint,
43-
IsAMG: true,
44-
IsGamma: strings.Contains(sx[1], "gamma"),
47+
WorkspaceID: sx[0],
48+
Region: sx[2],
49+
URL: wkspEndpoint,
50+
ServiceAccountID: serviceAccountID,
51+
IsAMG: true,
52+
IsGamma: strings.Contains(sx[1], "gamma"),
4553
}, nil
4654
} else if url != "" && apiKey != "" {
4755
return GrafanaInput{
@@ -54,8 +62,9 @@ func NewGrafanaInput(wkspEndpoint, url, apiKey string) (GrafanaInput, error) {
5462
return GrafanaInput{}, errors.New("invalid input")
5563
}
5664

57-
// getAPIKey create api keys only when provided with a managed grafana ID
58-
func (input *GrafanaInput) getAPIKey(awsgrafanacli *aws.AMG) (aws.AMGApiKey, error) {
65+
// getGrafanaAuthToken create Grafana api keys only when provided with a managed grafana ID
66+
// if service account is provided, it will create a service account token
67+
func (input *GrafanaInput) getGrafanaAuthToken(awsgrafanacli *aws.AMG) (aws.GrafanaAuth, error) {
5968

6069
if !input.IsAMG {
6170
log.InfoLight("Skipping API key creation for ", input.URL)
@@ -64,45 +73,61 @@ func (input *GrafanaInput) getAPIKey(awsgrafanacli *aws.AMG) (aws.AMGApiKey, err
6473
}, nil
6574
}
6675

67-
key, err := awsgrafanacli.CreateWorkspaceApiKey(input.WorkspaceID)
68-
if err != nil {
69-
return key, err
76+
wksp, err := awsgrafanacli.DescribeWorkspace(input.WorkspaceID)
77+
if err == nil {
78+
input.WorkspaceVersion = wksp.Version
79+
}
80+
81+
// forcing V10 to use service account token
82+
if input.WorkspaceVersion == AMG_V10 && input.ServiceAccountID == "" {
83+
return nil, errors.New("input error: service account ID is required for AMG v10, run migrate -h for help")
7084
}
71-
return key, nil
85+
86+
// creating service account token if service account is provided
87+
if input.ServiceAccountID != "" {
88+
return awsgrafanacli.CreateServiceAccountToken(input.WorkspaceID, input.ServiceAccountID)
89+
}
90+
91+
// creating temporary API key if no service account is provided
92+
return awsgrafanacli.CreateWorkspaceApiKey(input.WorkspaceID)
7293
}
7394

7495
// CreateGrafanaAPIClient create a grafana HTTP API client from the input
7596
func (input *GrafanaInput) CreateGrafanaAPIClient(awsgrafanacli *aws.AMG) (*GrafanaHTTPClient, error) {
7697
var url string
7798

7899
if input.IsAMG {
79-
// could be replaced by describe workspace
80100
url = fmt.Sprintf("https://%s", input.URL)
81101
} else {
82102
url = input.URL
83103
}
84104

85-
// get API keys
86-
apiKey, err := input.getAPIKey(awsgrafanacli)
105+
// get final auth key or token
106+
apiKey, err := input.getGrafanaAuthToken(awsgrafanacli)
87107
if err != nil {
88108
return nil, err
89109
}
90110

91-
client, err := gapi.New(url, gapi.Config{APIKey: apiKey.APIKey})
111+
client, err := gapi.New(url, gapi.Config{APIKey: apiKey.GetAuth()})
92112
if err != nil {
93113
return nil, err
94114
}
95115
return &GrafanaHTTPClient{
96116
Client: client,
97-
Key: apiKey,
117+
Auth: apiKey,
98118
Input: input,
99119
}, nil
100120
}
101121

102-
// DeleteAPIKeys delete the temporary API key from the AWS grafana workspace
103-
func (input *GrafanaInput) DeleteAPIKeys(awsgrafanacli *aws.AMG, apiKey aws.AMGApiKey) error {
122+
// DeleteGrafanaAuth delete the temporary API key from the AWS grafana workspace
123+
func (input *GrafanaInput) DeleteGrafanaAuth(awsgrafanacli *aws.AMG, auth aws.GrafanaAuth) error {
104124
if !input.IsAMG {
105125
return nil
106126
}
107-
return awsgrafanacli.DeleteWorkspaceApiKey(apiKey)
127+
128+
if input.ServiceAccountID != "" {
129+
return awsgrafanacli.DeleteServiceAccountToken(auth.(aws.AMGServiceAccountToken))
130+
}
131+
132+
return awsgrafanacli.DeleteWorkspaceApiKey(auth.(aws.AMGApiKey))
108133
}

0 commit comments

Comments
 (0)