Skip to content

Commit 2828480

Browse files
Save dashboard sha256sum instead of full config json in tfstate (#222)
* Save dashboard sha256sum instead of full config json in tfstate * go lint * go generate * set store_dashboard_sha256 on provider config * update doc grafana_dashboard * go generate * set global var instead of env var * add acceptance test * add acc test for sha256 dashboard * Reuse the basic test to check that sha256 works * Lint * fix: apply inkel requested changes * fix: really apply inkel requested changes Co-authored-by: Julien Duchesne <[email protected]>
1 parent c206d24 commit 2828480

File tree

4 files changed

+96
-59
lines changed

4 files changed

+96
-59
lines changed

docs/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ provider "grafana" {
3333
- **retries** (Number) The amount of retries to use for Grafana API calls. May alternatively be set via the `GRAFANA_RETRIES` environment variable.
3434
- **sm_access_token** (String, Sensitive) A Synthetic Monitoring access token. May alternatively be set via the `GRAFANA_SM_ACCESS_TOKEN` environment variable.
3535
- **sm_url** (String) Synthetic monitoring backend address. May alternatively be set via the `GRAFANA_SM_URL` environment variable.
36+
- **store_dashboard_sha256** (Boolean) Set to true if you want to save only the sha256sum instead of complete dashboard model JSON in the tfstate.
3637
- **tls_cert** (String) Client TLS certificate file to use to authenticate to the Grafana server. May alternatively be set via the `GRAFANA_TLS_CERT` environment variable.
3738
- **tls_key** (String) Client TLS key file to use to authenticate to the Grafana server. May alternatively be set via the `GRAFANA_TLS_KEY` environment variable.
3839
- **url** (String) The root URL of a Grafana server. May alternatively be set via the `GRAFANA_URL` environment variable.

grafana/provider.go

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,11 @@ import (
2222
)
2323

2424
var (
25-
idRegexp = regexp.MustCompile(`^\d+$`)
26-
uidRegexp = regexp.MustCompile(`^[a-zA-Z0-9-_]+$`)
27-
emailRegexp = regexp.MustCompile(`.+\@.+\..+`)
25+
idRegexp = regexp.MustCompile(`^\d+$`)
26+
uidRegexp = regexp.MustCompile(`^[a-zA-Z0-9-_]+$`)
27+
emailRegexp = regexp.MustCompile(`.+\@.+\..+`)
28+
sha256Regexp = regexp.MustCompile(`^[A-Fa-f0-9]{64}$`)
29+
storeDashboardSHA256 bool
2830
)
2931

3032
func init() {
@@ -124,6 +126,12 @@ func Provider(version string) func() *schema.Provider {
124126
Description: "Synthetic monitoring backend address. May alternatively be set via the `GRAFANA_SM_URL` environment variable.",
125127
ValidateFunc: validation.IsURLWithHTTPorHTTPS,
126128
},
129+
"store_dashboard_sha256": {
130+
Type: schema.TypeBool,
131+
Optional: true,
132+
DefaultFunc: schema.EnvDefaultFunc("GRAFANA_STORE_DASHBOARD_SHA256", false),
133+
Description: "Set to true if you want to save only the sha256sum instead of complete dashboard model JSON in the tfstate.",
134+
},
127135
},
128136

129137
ResourcesMap: map[string]*schema.Resource{
@@ -210,6 +218,8 @@ func configure(version string, p *schema.Provider) func(context.Context, *schema
210218
}
211219
c.smapi = createSMClient(d)
212220

221+
storeDashboardSHA256 = d.Get("store_dashboard_sha256").(bool)
222+
213223
return c, diags
214224
}
215225
}

grafana/resource_dashboard.go

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package grafana
22

33
import (
44
"context"
5+
"crypto/sha256"
56
"encoding/json"
67
"fmt"
78
"net/url"
@@ -211,12 +212,15 @@ func ReadDashboard(ctx context.Context, d *schema.ResourceData, meta interface{}
211212
return diag.FromErr(err)
212213
}
213214

215+
configJSON := d.Get("config_json").(string)
216+
217+
// Skip if configJSON string is a sha256 hash
214218
// If `uid` is not set in configuration, we need to delete it from the
215219
// dashboard JSON we just read from the Grafana API. This is so it does not
216220
// create a diff. We can assume the uid was randomly generated by Grafana or
217221
// it was removed after dashboard creation. In any case, the user doesn't
218222
// care to manage it.
219-
if configJSON := d.Get("config_json").(string); configJSON != "" {
223+
if configJSON != "" && !sha256Regexp.MatchString(configJSON) {
220224
configuredDashJSON, err := unmarshalDashboardConfigJSON(configJSON)
221225
if err != nil {
222226
return diag.FromErr(err)
@@ -225,8 +229,7 @@ func ReadDashboard(ctx context.Context, d *schema.ResourceData, meta interface{}
225229
delete(remoteDashJSON, "uid")
226230
}
227231
}
228-
229-
configJSON := normalizeDashboardConfigJSON(remoteDashJSON)
232+
configJSON = normalizeDashboardConfigJSON(remoteDashJSON)
230233
d.Set("config_json", configJSON)
231234

232235
return diags
@@ -340,5 +343,11 @@ func normalizeDashboardConfigJSON(config interface{}) string {
340343
}
341344

342345
j, _ := json.Marshal(dashboardJSON)
343-
return string(j)
346+
347+
if storeDashboardSHA256 {
348+
configHash := sha256.Sum256(j)
349+
return fmt.Sprintf("%x", configHash[:])
350+
} else {
351+
return string(j)
352+
}
344353
}

grafana/resource_dashboard_test.go

Lines changed: 69 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package grafana
22

33
import (
44
"fmt"
5+
"os"
56
"testing"
67

78
gapi "github.com/grafana/grafana-api-golang-client"
@@ -15,58 +16,74 @@ func TestAccDashboard_basic(t *testing.T) {
1516

1617
var dashboard gapi.Dashboard
1718

18-
resource.Test(t, resource.TestCase{
19-
PreCheck: func() { testAccPreCheck(t) },
20-
ProviderFactories: testAccProviderFactories,
21-
CheckDestroy: testAccDashboardCheckDestroy(&dashboard),
22-
Steps: []resource.TestStep{
23-
{
24-
// Test resource creation.
25-
Config: testAccExample(t, "resources/grafana_dashboard/_acc_basic.tf"),
26-
Check: resource.ComposeTestCheckFunc(
27-
testAccDashboardCheckExists("grafana_dashboard.test", &dashboard),
28-
resource.TestCheckResourceAttr("grafana_dashboard.test", "id", "basic"),
29-
resource.TestCheckResourceAttr("grafana_dashboard.test", "uid", "basic"),
30-
resource.TestCheckResourceAttr(
31-
"grafana_dashboard.test", "config_json", `{"title":"Terraform Acceptance Test","uid":"basic"}`,
32-
),
33-
),
34-
},
35-
{
36-
// Updates title.
37-
Config: testAccExample(t, "resources/grafana_dashboard/_acc_basic_update.tf"),
38-
Check: resource.ComposeTestCheckFunc(
39-
testAccDashboardCheckExists("grafana_dashboard.test", &dashboard),
40-
resource.TestCheckResourceAttr("grafana_dashboard.test", "id", "basic"),
41-
resource.TestCheckResourceAttr("grafana_dashboard.test", "uid", "basic"),
42-
resource.TestCheckResourceAttr(
43-
"grafana_dashboard.test", "config_json", `{"title":"Updated Title","uid":"basic"}`,
44-
),
45-
),
46-
},
47-
{
48-
// Updates uid.
49-
// uid is removed from `config_json` before writing it to state so it's
50-
// important to ensure changing it triggers an update of `config_json`.
51-
Config: testAccExample(t, "resources/grafana_dashboard/_acc_basic_update_uid.tf"),
52-
Check: resource.ComposeTestCheckFunc(
53-
testAccDashboardCheckExists("grafana_dashboard.test", &dashboard),
54-
resource.TestCheckResourceAttr("grafana_dashboard.test", "id", "basic-update"),
55-
resource.TestCheckResourceAttr("grafana_dashboard.test", "uid", "basic-update"),
56-
resource.TestCheckResourceAttr(
57-
"grafana_dashboard.test", "config_json", `{"title":"Updated Title","uid":"basic-update"}`,
58-
),
59-
),
60-
},
61-
{
62-
// Importing matches the state of the previous step.
63-
ResourceName: "grafana_dashboard.test",
64-
ImportState: true,
65-
ImportStateVerify: true,
66-
ImportStateVerifyIgnore: []string{"message"},
67-
},
68-
},
69-
})
19+
for _, useSHA256 := range []bool{false, true} {
20+
t.Run(fmt.Sprintf("useSHA256=%t", useSHA256), func(t *testing.T) {
21+
os.Setenv("GRAFANA_STORE_DASHBOARD_SHA256", fmt.Sprintf("%t", useSHA256))
22+
defer os.Unsetenv("GRAFANA_STORE_DASHBOARD_SHA256")
23+
24+
expectedInitialConfig := `{"title":"Terraform Acceptance Test","uid":"basic"}`
25+
expectedUpdatedTitleConfig := `{"title":"Updated Title","uid":"basic"}`
26+
expectedUpdatedUIDConfig := `{"title":"Updated Title","uid":"basic-update"}`
27+
if useSHA256 {
28+
expectedInitialConfig = "fadbc115a19bfd7962d8f8d749d22c20d0a44043d390048bf94b698776d9f7f1"
29+
expectedUpdatedTitleConfig = "4669abda43a4a6d6ae9ecaa19f8508faf4095682b679da0b5ce4176aa9171ab2"
30+
expectedUpdatedUIDConfig = "2934e80938a672bd09d8e56385159a1bf8176e2a2ef549437f200d82ff398bfb"
31+
}
32+
33+
resource.Test(t, resource.TestCase{
34+
PreCheck: func() { testAccPreCheck(t) },
35+
ProviderFactories: testAccProviderFactories,
36+
CheckDestroy: testAccDashboardCheckDestroy(&dashboard),
37+
Steps: []resource.TestStep{
38+
{
39+
// Test resource creation.
40+
Config: testAccExample(t, "resources/grafana_dashboard/_acc_basic.tf"),
41+
Check: resource.ComposeTestCheckFunc(
42+
testAccDashboardCheckExists("grafana_dashboard.test", &dashboard),
43+
resource.TestCheckResourceAttr("grafana_dashboard.test", "id", "basic"),
44+
resource.TestCheckResourceAttr("grafana_dashboard.test", "uid", "basic"),
45+
resource.TestCheckResourceAttr(
46+
"grafana_dashboard.test", "config_json", expectedInitialConfig,
47+
),
48+
),
49+
},
50+
{
51+
// Updates title.
52+
Config: testAccExample(t, "resources/grafana_dashboard/_acc_basic_update.tf"),
53+
Check: resource.ComposeTestCheckFunc(
54+
testAccDashboardCheckExists("grafana_dashboard.test", &dashboard),
55+
resource.TestCheckResourceAttr("grafana_dashboard.test", "id", "basic"),
56+
resource.TestCheckResourceAttr("grafana_dashboard.test", "uid", "basic"),
57+
resource.TestCheckResourceAttr(
58+
"grafana_dashboard.test", "config_json", expectedUpdatedTitleConfig,
59+
),
60+
),
61+
},
62+
{
63+
// Updates uid.
64+
// uid is removed from `config_json` before writing it to state so it's
65+
// important to ensure changing it triggers an update of `config_json`.
66+
Config: testAccExample(t, "resources/grafana_dashboard/_acc_basic_update_uid.tf"),
67+
Check: resource.ComposeTestCheckFunc(
68+
testAccDashboardCheckExists("grafana_dashboard.test", &dashboard),
69+
resource.TestCheckResourceAttr("grafana_dashboard.test", "id", "basic-update"),
70+
resource.TestCheckResourceAttr("grafana_dashboard.test", "uid", "basic-update"),
71+
resource.TestCheckResourceAttr(
72+
"grafana_dashboard.test", "config_json", expectedUpdatedUIDConfig,
73+
),
74+
),
75+
},
76+
{
77+
// Importing matches the state of the previous step.
78+
ResourceName: "grafana_dashboard.test",
79+
ImportState: true,
80+
ImportStateVerify: true,
81+
ImportStateVerifyIgnore: []string{"message"},
82+
},
83+
},
84+
})
85+
})
86+
}
7087
}
7188

7289
func TestAccDashboard_uid_unset(t *testing.T) {

0 commit comments

Comments
 (0)