Skip to content
Merged
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
13 changes: 13 additions & 0 deletions .drone/drone.jsonnet
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,19 @@ local pipeline(name, steps, services=[]) = {
]
),

pipeline(
'unit tests',
steps=[
{
name: 'tests',
image: images.go,
commands: [
'go test ./...',
],
},
]
),

pipeline(
'cloud tests',
steps=[
Expand Down
23 changes: 22 additions & 1 deletion .drone/drone.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,27 @@ workspace:
path: /drone/terraform-provider-grafana
---
kind: pipeline
name: unit tests
platform:
arch: amd64
os: linux
services: []
steps:
- commands:
- go test ./...
image: golang:1.16
name: tests
trigger:
branch:
- master
event:
- pull_request
- push
type: docker
workspace:
path: /drone/terraform-provider-grafana
---
kind: pipeline
name: cloud tests
platform:
arch: amd64
Expand Down Expand Up @@ -252,6 +273,6 @@ workspace:
path: /drone/terraform-provider-grafana
---
kind: signature
hmac: 21722dfbb237f702dd2e062347c7adfeb8a15a7cf103670928142a975d743d33
hmac: da6ac4bfbcd28b5ba91a57b10128a0bae335bf46b1bc0ca08ea59326eae3ed16

...
53 changes: 23 additions & 30 deletions grafana/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ func Provider(version string) func() *schema.Provider {
Type: schema.TypeMap,
Optional: true,
Sensitive: true,
DefaultFunc: JSONEnvDefaultFunc("GRAFANA_HTTP_HEADERS", nil),
Elem: &schema.Schema{Type: schema.TypeString},
Description: "Optional. HTTP headers mapping keys to values used for accessing the Grafana API. May alternatively be set via the `GRAFANA_HTTP_HEADERS` environment variable in JSON format.",
},
"retries": {
Expand Down Expand Up @@ -195,33 +195,33 @@ func Provider(version string) func() *schema.Provider {
}

type client struct {
gapiURL string
gapi *gapi.Client
gcloudapi *gapi.Client
smapi *smapi.Client
mlapi *mlapi.Client
gapiURL string
gapi *gapi.Client
gapiConfig *gapi.Config
gcloudapi *gapi.Client
smapi *smapi.Client
mlapi *mlapi.Client
}

func configure(version string, p *schema.Provider) func(context.Context, *schema.ResourceData) (interface{}, diag.Diagnostics) {
return func(ctx context.Context, d *schema.ResourceData) (interface{}, diag.Diagnostics) {
var (
cfg *gapi.Config
diags diag.Diagnostics
err error
)
p.UserAgent("terraform-provider-grafana", version)

c := &client{}

c.gapiURL, cfg, c.gapi, err = createGrafanaClient(d)
c.gapiURL, c.gapiConfig, c.gapi, err = createGrafanaClient(d)
if err != nil {
return nil, diag.FromErr(err)
}
c.gcloudapi, err = createCloudClient(d)
if err != nil {
return nil, diag.FromErr(err)
}
c.mlapi, err = createMLClient(c.gapiURL, cfg)
c.mlapi, err = createMLClient(c.gapiURL, c.gapiConfig)
if err != nil {
return nil, diag.FromErr(err)
}
Expand Down Expand Up @@ -279,17 +279,14 @@ func createGrafanaClient(d *schema.ResourceData) (string, *gapi.Config, *gapi.Cl

headersMap := d.Get("http_headers").(map[string]interface{})
if headersMap != nil && len(headersMap) == 0 {
// Workaround for a bug when DefaultFunc returns a schema.TypeMap
headersMapAbs, err := JSONEnvDefaultFunc("GRAFANA_HTTP_HEADERS", nil)()
// We cannot use a DefaultFunc because they do not work on maps
var err error
headersMap, err = getJSONMap("GRAFANA_HTTP_HEADERS")
if err != nil {
return "", nil, nil, err
}
if headersMapAbs != nil {
headersMap = headersMapAbs.(map[string]interface{})
return "", nil, nil, fmt.Errorf("invalid http_headers config: %w", err)
}
}
if headersMap != nil {
// Convert headers from map[string]interface{} to map[string]string
if len(headersMap) > 0 {
headers := make(map[string]string)
for k, v := range headersMap {
if v, ok := v.(string); ok {
Expand Down Expand Up @@ -338,19 +335,15 @@ func createSMClient(d *schema.ResourceData) *smapi.Client {
return smapi.NewClient(smURL, smToken, nil)
}

// JSONEnvDefaultFunc is a helper function that parses the given environment
// variable as a JSON object, or returns the default value otherwise.
func JSONEnvDefaultFunc(k string, dv interface{}) schema.SchemaDefaultFunc {
return func() (interface{}, error) {
if valStr := os.Getenv(k); valStr != "" {
var valObj map[string]interface{}
err := json.Unmarshal([]byte(valStr), &valObj)
if err != nil {
return nil, err
}
return valObj, nil
// getJSONMap is a helper function that parses the given environment variable as a JSON object
func getJSONMap(k string) (map[string]interface{}, error) {
if valStr := os.Getenv(k); valStr != "" {
var valObj map[string]interface{}
err := json.Unmarshal([]byte(valStr), &valObj)
if err != nil {
return nil, err
}

return dv, nil
return valObj, nil
}
return nil, nil
}
142 changes: 142 additions & 0 deletions grafana/provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ import (
"context"
"io/ioutil"
"os"
"regexp"
"strconv"
"strings"
"sync"
"testing"

"github.com/Masterminds/semver/v3"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
)
Expand Down Expand Up @@ -46,11 +49,142 @@ func init() {
}

func TestProvider(t *testing.T) {
IsUnitTest(t)

if err := Provider("dev")().InternalValidate(); err != nil {
t.Fatalf("err: %s", err)
}
}

func TestProviderConfigure(t *testing.T) {
IsUnitTest(t)

// Helper for header tests
checkHeaders := func(t *testing.T, provider *schema.Provider) {
gotHeaders := provider.Meta().(*client).gapiConfig.HTTPHeaders
if len(gotHeaders) != 2 {
t.Errorf("expected 2 HTTP header, got %d", len(gotHeaders))
}
if gotHeaders["Authorization"] != "Bearer test" {
t.Errorf("expected HTTP header Authorization to be \"Bearer test\", got %q", gotHeaders["Authorization"])
}
if gotHeaders["X-Custom-Header"] != "custom-value" {
t.Errorf("expected HTTP header X-Custom-Header to be \"custom-value\", got %q", gotHeaders["X-Custom-Header"])
}
}

envBackup := os.Environ()
defer func() {
os.Clearenv()
for _, v := range envBackup {
kv := strings.SplitN(v, "=", 2)
os.Setenv(kv[0], kv[1])
}
}()

cases := []struct {
name string
config map[string]interface{}
env map[string]string
expectedErr string
check func(t *testing.T, provider *schema.Provider)
}{
{
name: "no config",
env: map[string]string{},
expectedErr: "\"auth\": one of `auth,cloud_api_key,sm_access_token` must be specified",
},
{
name: "grafana config from env",
env: map[string]string{
"GRAFANA_AUTH": "admin:admin",
"GRAFANA_URL": "https://test.com",
},
},
{
name: "header config",
env: map[string]string{
"GRAFANA_AUTH": "admin:admin",
"GRAFANA_URL": "https://test.com",
},
config: map[string]interface{}{
"http_headers": map[string]interface{}{
"Authorization": "Bearer test",
"X-Custom-Header": "custom-value",
},
},
check: checkHeaders,
},
{
name: "header config from env",
env: map[string]string{
"GRAFANA_AUTH": "admin:admin",
"GRAFANA_URL": "https://test.com",
"GRAFANA_HTTP_HEADERS": `{"X-Custom-Header": "custom-value", "Authorization": "Bearer test"}`,
},
check: checkHeaders,
},
{
name: "invalid header",
env: map[string]string{
"GRAFANA_AUTH": "admin:admin",
"GRAFANA_URL": "https://test.com",
"GRAFANA_HTTP_HEADERS": `blabla`,
},
expectedErr: "invalid http_headers config: invalid character 'b' looking for beginning of value",
},
{
name: "grafana cloud config from env",
env: map[string]string{
"GRAFANA_CLOUD_API_KEY": "testtest",
},
},
{
name: "grafana sm config from env",
env: map[string]string{
"GRAFANA_SM_ACCESS_TOKEN": "testtest",
},
},
}

for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
os.Clearenv()
for k, v := range tc.env {
os.Setenv(k, v)
}

test := resource.TestStep{
// Resource is irrelevant, it's just there to test the provider being configured
// Terraform will "validate" the provider, but not actually use it when planning
PlanOnly: true,
ExpectNonEmptyPlan: true,
Config: `resource "grafana_folder" "test" {
title = "test"
}`,
}

if tc.expectedErr != "" {
test.ExpectError = regexp.MustCompile(tc.expectedErr)
}

// Configure the provider and check it
provider := Provider("dev")()
provider.Configure(context.Background(), terraform.NewResourceConfigRaw(tc.config))
if tc.check != nil {
tc.check(t, provider)
}
// Run the plan to check for validation errors
resource.UnitTest(t, resource.TestCase{
Providers: map[string]*schema.Provider{
"grafana": provider,
},
Steps: []resource.TestStep{test},
})
})
}
}

// testAccPreCheckEnv contains all environment variables that must be present
// for acceptance tests to run. These are checked in testAccPreCheck.
var testAccPreCheckEnv = []string{
Expand Down Expand Up @@ -114,6 +248,14 @@ func accTestsEnabled(t *testing.T, envVarName string) bool {
return enabled
}

func IsUnitTest(t *testing.T) {
t.Helper()

if accTestsEnabled(t, "TF_ACC") {
t.Skip("Skipping acceptance tests")
}
}

func CheckOSSTestsEnabled(t *testing.T) {
t.Helper()
if !accTestsEnabled(t, "TF_ACC_OSS") {
Expand Down
2 changes: 2 additions & 0 deletions grafana/resource_dashboard_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,8 @@ func testAccDashboardFolderCheckDestroy(dashboard *gapi.Dashboard, folder *gapi.
}

func Test_normalizeDashboardConfigJSON(t *testing.T) {
IsUnitTest(t)

type args struct {
config interface{}
}
Expand Down
2 changes: 2 additions & 0 deletions grafana/resource_data_source_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -568,6 +568,8 @@ func TestAccDataSource_basic(t *testing.T) {
}

func TestDatasourceMigrationV0(t *testing.T) {
IsUnitTest(t)

cases := []struct {
name string
state map[string]interface{}
Expand Down