From 8a7600e81db313b22d9605f68530616d9e2c37cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Gomez?= Date: Mon, 10 Apr 2023 17:20:14 +0200 Subject: [PATCH] Support defining CloudWatch datasources --- datasource/cloudwatch/cloudwatch.go | 48 ++++++++++++++ datasource/cloudwatch/cloudwatch_test.go | 22 +++++++ datasource/cloudwatch/options.go | 81 ++++++++++++++++++++++++ datasource/cloudwatch/options_test.go | 81 ++++++++++++++++++++++++ 4 files changed, 232 insertions(+) create mode 100644 datasource/cloudwatch/cloudwatch.go create mode 100644 datasource/cloudwatch/cloudwatch_test.go create mode 100644 datasource/cloudwatch/options.go create mode 100644 datasource/cloudwatch/options_test.go diff --git a/datasource/cloudwatch/cloudwatch.go b/datasource/cloudwatch/cloudwatch.go new file mode 100644 index 00000000..5edb136e --- /dev/null +++ b/datasource/cloudwatch/cloudwatch.go @@ -0,0 +1,48 @@ +package cloudwatch + +import ( + "encoding/json" + + "github.com/K-Phoen/grabana/datasource" + "github.com/K-Phoen/sdk" +) + +var _ datasource.Datasource = CloudWatch{} + +type CloudWatch struct { + builder *sdk.Datasource +} + +type Option func(datasource *CloudWatch) error + +func New(name string, options ...Option) (CloudWatch, error) { + cloudwatch := &CloudWatch{ + builder: &sdk.Datasource{ + Name: name, + Type: "cloudwatch", + Access: "proxy", + JSONData: map[string]interface{}{}, + SecureJSONData: map[string]interface{}{}, + }, + } + + defaults := []Option{ + DefaultAuth(), + } + + for _, opt := range append(defaults, options...) { + if err := opt(cloudwatch); err != nil { + return *cloudwatch, err + } + } + + return *cloudwatch, nil +} + +func (datasource CloudWatch) Name() string { + return datasource.builder.Name +} + +func (datasource CloudWatch) MarshalJSON() ([]byte, error) { + return json.Marshal(datasource.builder) +} diff --git a/datasource/cloudwatch/cloudwatch_test.go b/datasource/cloudwatch/cloudwatch_test.go new file mode 100644 index 00000000..fdbab859 --- /dev/null +++ b/datasource/cloudwatch/cloudwatch_test.go @@ -0,0 +1,22 @@ +package cloudwatch + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestNewCloudWatch(t *testing.T) { + req := require.New(t) + + datasource, err := New("ds-name") + + req.NoError(err) + req.Equal("ds-name", datasource.Name()) + req.Equal("cloudwatch", datasource.builder.Type) + req.NotNil(datasource.builder.JSONData) + req.NotNil(datasource.builder.SecureJSONData) + + _, err = datasource.MarshalJSON() + req.NoError(err) +} diff --git a/datasource/cloudwatch/options.go b/datasource/cloudwatch/options.go new file mode 100644 index 00000000..718a241e --- /dev/null +++ b/datasource/cloudwatch/options.go @@ -0,0 +1,81 @@ +package cloudwatch + +import ( + "strings" +) + +// DefaultAuth relies on the AWS SDK default authentication to authenticate to the CloudWatch service. +func DefaultAuth() Option { + return func(datasource *CloudWatch) error { + datasource.builder.JSONData.(map[string]interface{})["authType"] = "default" + + return nil + } +} + +// AccessSecretAuth relies on an access and secret key to authenticate to the CloudWatch service. +func AccessSecretAuth(accessKey string, secretKey string) Option { + return func(datasource *CloudWatch) error { + datasource.builder.JSONData.(map[string]interface{})["authType"] = "keys" + datasource.builder.SecureJSONData.(map[string]interface{})["accessKey"] = accessKey + datasource.builder.SecureJSONData.(map[string]interface{})["secretKey"] = secretKey + + return nil + } +} + +// Default configures this datasource to be the default one. +func Default() Option { + return func(datasource *CloudWatch) error { + datasource.builder.IsDefault = true + + return nil + } +} + +// DefaultRegion sets the default region to use. +// Example: eu-north-1. +func DefaultRegion(region string) Option { + return func(datasource *CloudWatch) error { + datasource.builder.JSONData.(map[string]interface{})["defaultRegion"] = region + + return nil + } +} + +// AssumeRoleARN specifies the ARN of a role to assume. +// Format: arn:aws:iam:* +func AssumeRoleARN(roleARN string) Option { + return func(datasource *CloudWatch) error { + datasource.builder.JSONData.(map[string]interface{})["assumeRoleArn"] = roleARN + + return nil + } +} + +// ExternalID specifies the external identifier of a role to assume in another account. +func ExternalID(externalID string) Option { + return func(datasource *CloudWatch) error { + datasource.builder.JSONData.(map[string]interface{})["externalId"] = externalID + + return nil + } +} + +// Endpoint specifies a custom endpoint for the CloudWatch service. +func Endpoint(endpoint string) Option { + return func(datasource *CloudWatch) error { + datasource.builder.JSONData.(map[string]interface{})["endpoint"] = endpoint + + return nil + } +} + +// CustomMetricsNamespaces specifies a list of namespaces for custom metrics. +func CustomMetricsNamespaces(namespaces ...string) Option { + return func(datasource *CloudWatch) error { + datasource.builder.JSONData.(map[string]interface{})["customMetricsNamespaces"] = strings.Join(namespaces, ",") + + return nil + } +} diff --git a/datasource/cloudwatch/options_test.go b/datasource/cloudwatch/options_test.go new file mode 100644 index 00000000..94f5f944 --- /dev/null +++ b/datasource/cloudwatch/options_test.go @@ -0,0 +1,81 @@ +package cloudwatch + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestDefault(t *testing.T) { + req := require.New(t) + + datasource, err := New("", Default()) + + req.NoError(err) + req.True(datasource.builder.IsDefault) +} + +func TestDefaultAuth(t *testing.T) { + req := require.New(t) + + datasource, err := New("", DefaultAuth()) + + req.NoError(err) + req.Equal("default", datasource.builder.JSONData.(map[string]interface{})["authType"]) +} + +func TestAccessSecretAuth(t *testing.T) { + req := require.New(t) + + datasource, err := New("", AccessSecretAuth("access", "secret")) + + req.NoError(err) + req.Equal("keys", datasource.builder.JSONData.(map[string]interface{})["authType"]) + req.Equal("access", datasource.builder.SecureJSONData.(map[string]interface{})["accessKey"]) + req.Equal("secret", datasource.builder.SecureJSONData.(map[string]interface{})["secretKey"]) +} + +func TestDefaultRegion(t *testing.T) { + req := require.New(t) + + datasource, err := New("", DefaultRegion("eu-north-1")) + + req.NoError(err) + req.Equal("eu-north-1", datasource.builder.JSONData.(map[string]interface{})["defaultRegion"]) +} + +func TestAssumeRoleARN(t *testing.T) { + req := require.New(t) + + datasource, err := New("", AssumeRoleARN("arn:aws:iam:role")) + + req.NoError(err) + req.Equal("arn:aws:iam:role", datasource.builder.JSONData.(map[string]interface{})["assumeRoleArn"]) +} + +func TestExternalID(t *testing.T) { + req := require.New(t) + + datasource, err := New("", ExternalID("external-id")) + + req.NoError(err) + req.Equal("external-id", datasource.builder.JSONData.(map[string]interface{})["externalId"]) +} + +func TestEndpoint(t *testing.T) { + req := require.New(t) + + datasource, err := New("", Endpoint("endpoint")) + + req.NoError(err) + req.Equal("endpoint", datasource.builder.JSONData.(map[string]interface{})["endpoint"]) +} + +func TestCustomMetricsNamespaces(t *testing.T) { + req := require.New(t) + + datasource, err := New("", CustomMetricsNamespaces("ns1", "ns2")) + + req.NoError(err) + req.Equal("ns1,ns2", datasource.builder.JSONData.(map[string]interface{})["customMetricsNamespaces"]) +}