Cloudwatch: use shared library for aws auth (#29550)

* use sdk for handling auth

* fix broken test

* lint fixes

Co-authored-by: Erik Sundell <erik.sundell87@gmail.com>
This commit is contained in:
Ryan McKinley
2021-03-12 05:30:21 -08:00
committed by GitHub
parent 8404d54277
commit 9dd1d5f553
16 changed files with 151 additions and 489 deletions

2
go.mod
View File

@ -40,7 +40,7 @@ require (
github.com/google/uuid v1.2.0 github.com/google/uuid v1.2.0
github.com/gosimple/slug v1.9.0 github.com/gosimple/slug v1.9.0
github.com/grafana/alerting-api v0.0.0-20210311171115-b0eb4577f38c github.com/grafana/alerting-api v0.0.0-20210311171115-b0eb4577f38c
github.com/grafana/grafana-aws-sdk v0.1.0 github.com/grafana/grafana-aws-sdk v0.2.0
github.com/grafana/grafana-plugin-model v0.0.0-20190930120109-1fc953a61fb4 github.com/grafana/grafana-plugin-model v0.0.0-20190930120109-1fc953a61fb4
github.com/grafana/grafana-plugin-sdk-go v0.88.0 github.com/grafana/grafana-plugin-sdk-go v0.88.0
github.com/grafana/loki v1.6.2-0.20201026154740-6978ee5d7387 github.com/grafana/loki v1.6.2-0.20201026154740-6978ee5d7387

4
go.sum
View File

@ -229,7 +229,6 @@ github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b h1:L/QXpzIa3pO
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA= github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
github.com/bsm/sarama-cluster v2.1.13+incompatible/go.mod h1:r7ao+4tTNXvWm+VRpRJchr2kQhqxgmAp2iEX5W96gMM= github.com/bsm/sarama-cluster v2.1.13+incompatible/go.mod h1:r7ao+4tTNXvWm+VRpRJchr2kQhqxgmAp2iEX5W96gMM=
github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34=
github.com/c2h5oh/datasize v0.0.0-20200112174442-28bbd4740fee h1:BnPxIde0gjtTnc9Er7cxvBk8DHLWhEux0SxayC8dP6I=
github.com/c2h5oh/datasize v0.0.0-20200112174442-28bbd4740fee/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M= github.com/c2h5oh/datasize v0.0.0-20200112174442-28bbd4740fee/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M=
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
github.com/cenkalti/backoff v0.0.0-20181003080854-62661b46c409/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff v0.0.0-20181003080854-62661b46c409/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
@ -795,8 +794,9 @@ github.com/grafana/alerting-api v0.0.0-20210311143043-45ae733ad75e/go.mod h1:5Ip
github.com/grafana/alerting-api v0.0.0-20210311171115-b0eb4577f38c h1:xmmEjOGr87S1ZMinUTCA+ikMSLvJRrCYADZ9/ewMtWM= github.com/grafana/alerting-api v0.0.0-20210311171115-b0eb4577f38c h1:xmmEjOGr87S1ZMinUTCA+ikMSLvJRrCYADZ9/ewMtWM=
github.com/grafana/alerting-api v0.0.0-20210311171115-b0eb4577f38c/go.mod h1:5IppnPguSHcCbVLGCVzVjBvuQZNbYgVJ4KyXXjhCyWY= github.com/grafana/alerting-api v0.0.0-20210311171115-b0eb4577f38c/go.mod h1:5IppnPguSHcCbVLGCVzVjBvuQZNbYgVJ4KyXXjhCyWY=
github.com/grafana/grafana v1.9.2-0.20210308201921-4ce0a49eac03/go.mod h1:AHRRvd4utJGY25J5nW8aL7wZzn/LcJ0z2za9oOp14j4= github.com/grafana/grafana v1.9.2-0.20210308201921-4ce0a49eac03/go.mod h1:AHRRvd4utJGY25J5nW8aL7wZzn/LcJ0z2za9oOp14j4=
github.com/grafana/grafana-aws-sdk v0.1.0 h1:25TSkS57lvVen3nTCNMu5SdI8ju5Z0f7cvCsukhlqJc=
github.com/grafana/grafana-aws-sdk v0.1.0/go.mod h1:+pPo5U+pX0zWimR7YBc7ASeSQfbRkcTyQYqMiAj7G5U= github.com/grafana/grafana-aws-sdk v0.1.0/go.mod h1:+pPo5U+pX0zWimR7YBc7ASeSQfbRkcTyQYqMiAj7G5U=
github.com/grafana/grafana-aws-sdk v0.2.0 h1:UTBBYwye+ad5YUIlwN7TGxLdz1wXN3Ezhl0pseDGRVA=
github.com/grafana/grafana-aws-sdk v0.2.0/go.mod h1:+pPo5U+pX0zWimR7YBc7ASeSQfbRkcTyQYqMiAj7G5U=
github.com/grafana/grafana-plugin-model v0.0.0-20190930120109-1fc953a61fb4 h1:SPdxCL9BChFTlyi0Khv64vdCW4TMna8+sxL7+Chx+Ag= github.com/grafana/grafana-plugin-model v0.0.0-20190930120109-1fc953a61fb4 h1:SPdxCL9BChFTlyi0Khv64vdCW4TMna8+sxL7+Chx+Ag=
github.com/grafana/grafana-plugin-model v0.0.0-20190930120109-1fc953a61fb4/go.mod h1:nc0XxBzjeGcrMltCDw269LoWF9S8ibhgxolCdA1R8To= github.com/grafana/grafana-plugin-model v0.0.0-20190930120109-1fc953a61fb4/go.mod h1:nc0XxBzjeGcrMltCDw269LoWF9S8ibhgxolCdA1R8To=
github.com/grafana/grafana-plugin-sdk-go v0.79.0/go.mod h1:NvxLzGkVhnoBKwzkst6CFfpMFKwAdIUZ1q8ssuLeF60= github.com/grafana/grafana-plugin-sdk-go v0.79.0/go.mod h1:NvxLzGkVhnoBKwzkst6CFfpMFKwAdIUZ1q8ssuLeF60=

View File

@ -202,7 +202,7 @@
}, },
"dependencies": { "dependencies": {
"@emotion/core": "10.0.27", "@emotion/core": "10.0.27",
"@grafana/aws-sdk": "0.0.2", "@grafana/aws-sdk": "0.0.22",
"@grafana/slate-react": "0.22.9-grafana", "@grafana/slate-react": "0.22.9-grafana",
"@popperjs/core": "2.5.4", "@popperjs/core": "2.5.4",
"@reduxjs/toolkit": "1.5.0", "@reduxjs/toolkit": "1.5.0",

View File

@ -9,9 +9,11 @@ import (
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"net/url" "net/url"
"strings"
"sync" "sync"
"time" "time"
"github.com/grafana/grafana-aws-sdk/pkg/awsds"
"github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
@ -86,6 +88,8 @@ func (m *manager) Register(pluginID string, factory backendplugin.PluginFactoryF
} }
} }
hostEnv = append(hostEnv, m.getAWSEnvironmentVariables()...)
env := pluginSettings.ToEnv("GF_PLUGIN", hostEnv) env := pluginSettings.ToEnv("GF_PLUGIN", hostEnv)
pluginLogger := m.logger.New("pluginId", pluginID) pluginLogger := m.logger.New("pluginId", pluginID)
@ -99,6 +103,18 @@ func (m *manager) Register(pluginID string, factory backendplugin.PluginFactoryF
return nil return nil
} }
func (m *manager) getAWSEnvironmentVariables() []string {
variables := []string{}
if m.Cfg.AWSAssumeRoleEnabled {
variables = append(variables, awsds.AssumeRoleEnabledEnvVarKeyName+"=true")
}
if len(m.Cfg.AWSAllowedAuthProviders) > 0 {
variables = append(variables, awsds.AllowedAuthProvidersEnvVarKeyName+"="+strings.Join(m.Cfg.AWSAllowedAuthProviders, ","))
}
return variables
}
func (m *manager) GetDataPlugin(pluginID string) interface{} { func (m *manager) GetDataPlugin(pluginID string) interface{} {
plugin := m.plugins[pluginID] plugin := m.plugins[pluginID]
if plugin == nil || !plugin.CanHandleDataQueries() { if plugin == nil || !plugin.CanHandleDataQueries() {

View File

@ -3,12 +3,14 @@ package manager
import ( import (
"bytes" "bytes"
"context" "context"
"fmt"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"sync" "sync"
"testing" "testing"
"time" "time"
"github.com/grafana/grafana-aws-sdk/pkg/awsds"
"github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
@ -58,8 +60,8 @@ func TestManager(t *testing.T) {
}) })
t.Run("Should provide expected host environment variables", func(t *testing.T) { t.Run("Should provide expected host environment variables", func(t *testing.T) {
require.Len(t, ctx.env, 2) require.Len(t, ctx.env, 4)
require.EqualValues(t, []string{"GF_VERSION=7.0.0", "GF_EDITION=Open Source"}, ctx.env) require.EqualValues(t, []string{"GF_VERSION=7.0.0", "GF_EDITION=Open Source", fmt.Sprintf("%s=true", awsds.AssumeRoleEnabledEnvVarKeyName), fmt.Sprintf("%s=keys,credentials", awsds.AllowedAuthProvidersEnvVarKeyName)}, ctx.env)
}) })
t.Run("When manager runs should start and stop plugin", func(t *testing.T) { t.Run("When manager runs should start and stop plugin", func(t *testing.T) {
@ -261,8 +263,8 @@ func TestManager(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
t.Run("Should provide expected host environment variables", func(t *testing.T) { t.Run("Should provide expected host environment variables", func(t *testing.T) {
require.Len(t, ctx.env, 4) require.Len(t, ctx.env, 6)
require.EqualValues(t, []string{"GF_VERSION=7.0.0", "GF_EDITION=Enterprise", "GF_ENTERPRISE_LICENSE_PATH=/license.txt", "GF_ENTERPRISE_LICENSE_TEXT=testtoken"}, ctx.env) require.EqualValues(t, []string{"GF_VERSION=7.0.0", "GF_EDITION=Enterprise", "GF_ENTERPRISE_LICENSE_PATH=/license.txt", "GF_ENTERPRISE_LICENSE_TEXT=testtoken", fmt.Sprintf("%s=true", awsds.AssumeRoleEnabledEnvVarKeyName), fmt.Sprintf("%s=keys,credentials", awsds.AllowedAuthProvidersEnvVarKeyName)}, ctx.env)
}) })
}) })
}) })
@ -280,6 +282,9 @@ type managerScenarioCtx struct {
func newManagerScenario(t *testing.T, managed bool, fn func(t *testing.T, ctx *managerScenarioCtx)) { func newManagerScenario(t *testing.T, managed bool, fn func(t *testing.T, ctx *managerScenarioCtx)) {
t.Helper() t.Helper()
cfg := setting.NewCfg() cfg := setting.NewCfg()
cfg.AWSAllowedAuthProviders = []string{"keys", "credentials"}
cfg.AWSAssumeRoleEnabled = true
license := &testLicensingService{} license := &testLicensingService{}
validator := &testPluginRequestValidator{} validator := &testPluginRequestValidator{}
ctx := &managerScenarioCtx{ ctx := &managerScenarioCtx{

View File

@ -12,12 +12,14 @@ import (
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
"strconv"
"strings" "strings"
"time" "time"
"github.com/prometheus/common/model" "github.com/prometheus/common/model"
ini "gopkg.in/ini.v1" ini "gopkg.in/ini.v1"
"github.com/grafana/grafana-aws-sdk/pkg/awsds"
"github.com/grafana/grafana/pkg/components/gtime" "github.com/grafana/grafana/pkg/components/gtime"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util"
@ -876,7 +878,7 @@ func (cfg *Cfg) Load(args *CommandLineArgs) error {
} }
cfg.readLDAPConfig() cfg.readLDAPConfig()
cfg.readAWSConfig() cfg.handleAWSConfig()
cfg.readSessionConfig() cfg.readSessionConfig()
cfg.readSmtpSettings() cfg.readSmtpSettings()
cfg.readQuotaSettings() cfg.readQuotaSettings()
@ -940,10 +942,10 @@ func (cfg *Cfg) readLDAPConfig() {
cfg.LDAPAllowSignup = LDAPAllowSignup cfg.LDAPAllowSignup = LDAPAllowSignup
} }
func (cfg *Cfg) readAWSConfig() { func (cfg *Cfg) handleAWSConfig() {
awsPluginSec := cfg.Raw.Section("aws") awsPluginSec := cfg.Raw.Section("aws")
cfg.AWSAssumeRoleEnabled = awsPluginSec.Key("assume_role_enabled").MustBool(true) cfg.AWSAssumeRoleEnabled = awsPluginSec.Key("assume_role_enabled").MustBool(true)
allowedAuthProviders := awsPluginSec.Key("allowed_auth_providers").String() allowedAuthProviders := awsPluginSec.Key("allowed_auth_providers").MustString("default,keys,credentials")
for _, authProvider := range strings.Split(allowedAuthProviders, ",") { for _, authProvider := range strings.Split(allowedAuthProviders, ",") {
authProvider = strings.TrimSpace(authProvider) authProvider = strings.TrimSpace(authProvider)
if authProvider != "" { if authProvider != "" {
@ -951,6 +953,16 @@ func (cfg *Cfg) readAWSConfig() {
} }
} }
cfg.AWSListMetricsPageLimit = awsPluginSec.Key("list_metrics_page_limit").MustInt(500) cfg.AWSListMetricsPageLimit = awsPluginSec.Key("list_metrics_page_limit").MustInt(500)
// Also set environment variables that can be used by core plugins
err := os.Setenv(awsds.AssumeRoleEnabledEnvVarKeyName, strconv.FormatBool(cfg.AWSAssumeRoleEnabled))
if err != nil {
cfg.Logger.Error(fmt.Sprintf("could not set environment variable '%s'", awsds.AssumeRoleEnabledEnvVarKeyName), err)
}
err = os.Setenv(awsds.AllowedAuthProvidersEnvVarKeyName, allowedAuthProviders)
if err != nil {
cfg.Logger.Error(fmt.Sprintf("could not set environment variable '%s'", awsds.AllowedAuthProvidersEnvVarKeyName), err)
}
} }
func (cfg *Cfg) readSessionConfig() { func (cfg *Cfg) readSessionConfig() {

View File

@ -4,15 +4,12 @@ import (
"context" "context"
"fmt" "fmt"
"regexp" "regexp"
"strings"
"time" "time"
"github.com/grafana/grafana-aws-sdk/pkg/awsds"
"github.com/grafana/grafana-plugin-sdk-go/data" "github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/client" "github.com/aws/aws-sdk-go/aws/client"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/credentials/stscreds"
"github.com/aws/aws-sdk-go/aws/request" "github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/cloudwatch" "github.com/aws/aws-sdk-go/service/cloudwatch"
@ -31,19 +28,6 @@ import (
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
) )
type datasourceInfo struct {
Profile string
Region string
AuthType authType
AssumeRoleARN string
ExternalID string
Namespace string
Endpoint string
AccessKey string
SecretKey string
}
const cloudWatchTSFormat = "2006-01-02 15:04:05.000" const cloudWatchTSFormat = "2006-01-02 15:04:05.000"
const defaultRegion = "default" const defaultRegion = "default"
@ -65,20 +49,27 @@ func init() {
type CloudWatchService struct { type CloudWatchService struct {
LogsService *LogsService `inject:""` LogsService *LogsService `inject:""`
Cfg *setting.Cfg `inject:""` Cfg *setting.Cfg `inject:""`
sessions SessionCache
} }
func (s *CloudWatchService) Init() error { func (s *CloudWatchService) Init() error {
s.sessions = awsds.NewSessionCache()
return nil return nil
} }
func (s *CloudWatchService) NewExecutor(*models.DataSource) (plugins.DataPlugin, error) { func (s *CloudWatchService) NewExecutor(*models.DataSource) (plugins.DataPlugin, error) {
return newExecutor(s.LogsService, s.Cfg), nil return newExecutor(s.LogsService, s.Cfg, s.sessions), nil
} }
func newExecutor(logsService *LogsService, cfg *setting.Cfg) *cloudWatchExecutor { type SessionCache interface {
GetSession(region string, s awsds.AWSDatasourceSettings) (*session.Session, error)
}
func newExecutor(logsService *LogsService, cfg *setting.Cfg, sessions SessionCache) *cloudWatchExecutor {
return &cloudWatchExecutor{ return &cloudWatchExecutor{
logsService: logsService,
cfg: cfg, cfg: cfg,
logsService: logsService,
sessions: sessions,
} }
} }
@ -91,135 +82,13 @@ type cloudWatchExecutor struct {
logsService *LogsService logsService *LogsService
cfg *setting.Cfg cfg *setting.Cfg
sessions SessionCache
} }
func (e *cloudWatchExecutor) newSession(region string) (*session.Session, error) { func (e *cloudWatchExecutor) newSession(region string) (*session.Session, error) {
dsInfo := e.getDSInfo(region) awsDatasourceSettings := e.getAWSDatasourceSettings(region)
authTypeAllowed := false return e.sessions.GetSession(region, *awsDatasourceSettings)
for _, provider := range e.cfg.AWSAllowedAuthProviders {
if provider == dsInfo.AuthType.String() {
authTypeAllowed = true
break
}
}
if !authTypeAllowed {
return nil, fmt.Errorf("attempting to use an auth type that is not allowed: %q", dsInfo.AuthType.String())
}
if dsInfo.AssumeRoleARN != "" && !e.cfg.AWSAssumeRoleEnabled {
return nil, fmt.Errorf("attempting to use assume role (ARN) which is disabled in grafana.ini")
}
bldr := strings.Builder{}
for i, s := range []string{
dsInfo.AuthType.String(), dsInfo.AccessKey, dsInfo.Profile, dsInfo.AssumeRoleARN, region, dsInfo.Endpoint,
} {
if i != 0 {
bldr.WriteString(":")
}
bldr.WriteString(strings.ReplaceAll(s, ":", `\:`))
}
cacheKey := bldr.String()
sessCacheLock.RLock()
if env, ok := sessCache[cacheKey]; ok {
if env.expiration.After(time.Now().UTC()) {
sessCacheLock.RUnlock()
return env.session, nil
}
}
sessCacheLock.RUnlock()
cfgs := []*aws.Config{
{
CredentialsChainVerboseErrors: aws.Bool(true),
},
}
var regionCfg *aws.Config
if dsInfo.Region == defaultRegion {
plog.Warn("Region is set to \"default\", which is unsupported")
dsInfo.Region = ""
}
if dsInfo.Region != "" {
regionCfg = &aws.Config{Region: aws.String(dsInfo.Region)}
cfgs = append(cfgs, regionCfg)
}
if dsInfo.Endpoint != "" {
cfgs = append(cfgs, &aws.Config{Endpoint: aws.String(dsInfo.Endpoint)})
}
switch dsInfo.AuthType {
case authTypeSharedCreds:
plog.Debug("Authenticating towards AWS with shared credentials", "profile", dsInfo.Profile,
"region", dsInfo.Region)
cfgs = append(cfgs, &aws.Config{
Credentials: credentials.NewSharedCredentials("", dsInfo.Profile),
})
case authTypeKeys:
plog.Debug("Authenticating towards AWS with an access key pair", "region", dsInfo.Region)
cfgs = append(cfgs, &aws.Config{
Credentials: credentials.NewStaticCredentials(dsInfo.AccessKey, dsInfo.SecretKey, ""),
})
case authTypeDefault:
plog.Debug("Authenticating towards AWS with default SDK method", "region", dsInfo.Region)
case authTypeEC2IAMRole:
plog.Debug("Authenticating towards AWS with IAM Role", "region", dsInfo.Region)
sess, err := newSession(cfgs...)
if err != nil {
return nil, err
}
cfgs = append(cfgs, &aws.Config{Credentials: newEC2RoleCredentials(sess)})
default:
panic(fmt.Sprintf("Unrecognized authType: %d", dsInfo.AuthType))
}
sess, err := newSession(cfgs...)
if err != nil {
return nil, err
}
duration := stscreds.DefaultDuration
expiration := time.Now().UTC().Add(duration)
if dsInfo.AssumeRoleARN != "" && e.cfg.AWSAssumeRoleEnabled {
// We should assume a role in AWS
plog.Debug("Trying to assume role in AWS", "arn", dsInfo.AssumeRoleARN)
cfgs := []*aws.Config{
{
CredentialsChainVerboseErrors: aws.Bool(true),
},
{
Credentials: newSTSCredentials(sess, dsInfo.AssumeRoleARN, func(p *stscreds.AssumeRoleProvider) {
// Not sure if this is necessary, overlaps with p.Duration and is undocumented
p.Expiry.SetExpiration(expiration, 0)
p.Duration = duration
if dsInfo.ExternalID != "" {
p.ExternalID = aws.String(dsInfo.ExternalID)
}
}),
},
}
if regionCfg != nil {
cfgs = append(cfgs, regionCfg)
}
sess, err = newSession(cfgs...)
if err != nil {
return nil, err
}
}
plog.Debug("Successfully created AWS session")
sessCacheLock.Lock()
sessCache[cacheKey] = envelope{
session: sess,
expiration: expiration,
}
sessCacheLock.Unlock()
return sess, nil
} }
func (e *cloudWatchExecutor) getCWClient(region string) (cloudwatchiface.CloudWatchAPI, error) { func (e *cloudWatchExecutor) getCWClient(region string) (cloudwatchiface.CloudWatchAPI, error) {
@ -415,31 +284,7 @@ func (e *cloudWatchExecutor) executeLogAlertQuery(ctx context.Context, queryCont
return response, nil return response, nil
} }
type authType int func (e *cloudWatchExecutor) getAWSDatasourceSettings(region string) *awsds.AWSDatasourceSettings {
const (
authTypeDefault authType = iota
authTypeSharedCreds
authTypeKeys
authTypeEC2IAMRole
)
func (at authType) String() string {
switch at {
case authTypeDefault:
return "default"
case authTypeSharedCreds:
return "credentials"
case authTypeKeys:
return "keys"
case authTypeEC2IAMRole:
return "ec2_iam_role"
default:
panic(fmt.Sprintf("Unrecognized auth type %d", at))
}
}
func (e *cloudWatchExecutor) getDSInfo(region string) *datasourceInfo {
if region == defaultRegion { if region == defaultRegion {
region = e.DataSource.JsonData.Get("defaultRegion").MustString() region = e.DataSource.JsonData.Get("defaultRegion").MustString()
} }
@ -452,19 +297,19 @@ func (e *cloudWatchExecutor) getDSInfo(region string) *datasourceInfo {
accessKey := decrypted["accessKey"] accessKey := decrypted["accessKey"]
secretKey := decrypted["secretKey"] secretKey := decrypted["secretKey"]
at := authTypeDefault at := awsds.AuthTypeDefault
switch atStr { switch atStr {
case "credentials": case "credentials":
at = authTypeSharedCreds at = awsds.AuthTypeSharedCreds
case "keys": case "keys":
at = authTypeKeys at = awsds.AuthTypeKeys
case "default": case "default":
at = authTypeDefault at = awsds.AuthTypeDefault
case "ec2_iam_role":
at = authTypeEC2IAMRole
case "arn": case "arn":
at = authTypeDefault at = awsds.AuthTypeDefault
plog.Warn("Authentication type \"arn\" is deprecated, falling back to default") plog.Warn("Authentication type \"arn\" is deprecated, falling back to default")
case "ec2_iam_role":
at = awsds.AuthTypeEC2IAMRole
default: default:
plog.Warn("Unrecognized AWS authentication type", "type", atStr) plog.Warn("Unrecognized AWS authentication type", "type", atStr)
} }
@ -474,7 +319,7 @@ func (e *cloudWatchExecutor) getDSInfo(region string) *datasourceInfo {
profile = e.DataSource.Database // legacy support profile = e.DataSource.Database // legacy support
} }
return &datasourceInfo{ return &awsds.AWSDatasourceSettings{
Region: region, Region: region,
Profile: profile, Profile: profile,
AuthType: at, AuthType: at,

View File

@ -47,7 +47,7 @@ func TestQuery_DescribeLogGroups(t *testing.T) {
}, },
} }
executor := newExecutor(nil, newTestConfig()) executor := newExecutor(nil, newTestConfig(), fakeSessionCache{})
resp, err := executor.DataQuery(context.Background(), fakeDataSource(), plugins.DataQuery{ resp, err := executor.DataQuery(context.Background(), fakeDataSource(), plugins.DataQuery{
Queries: []plugins.DataSubQuery{ Queries: []plugins.DataSubQuery{
{ {
@ -100,7 +100,7 @@ func TestQuery_DescribeLogGroups(t *testing.T) {
}, },
} }
executor := newExecutor(nil, newTestConfig()) executor := newExecutor(nil, newTestConfig(), fakeSessionCache{})
resp, err := executor.DataQuery(context.Background(), fakeDataSource(), plugins.DataQuery{ resp, err := executor.DataQuery(context.Background(), fakeDataSource(), plugins.DataQuery{
Queries: []plugins.DataSubQuery{ Queries: []plugins.DataSubQuery{
{ {
@ -170,7 +170,7 @@ func TestQuery_GetLogGroupFields(t *testing.T) {
const refID = "A" const refID = "A"
executor := newExecutor(nil, newTestConfig()) executor := newExecutor(nil, newTestConfig(), fakeSessionCache{})
resp, err := executor.DataQuery(context.Background(), fakeDataSource(), plugins.DataQuery{ resp, err := executor.DataQuery(context.Background(), fakeDataSource(), plugins.DataQuery{
Queries: []plugins.DataSubQuery{ Queries: []plugins.DataSubQuery{
{ {
@ -249,7 +249,7 @@ func TestQuery_StartQuery(t *testing.T) {
To: "1584700643000", To: "1584700643000",
} }
executor := newExecutor(nil, newTestConfig()) executor := newExecutor(nil, newTestConfig(), fakeSessionCache{})
_, err := executor.DataQuery(context.Background(), fakeDataSource(), plugins.DataQuery{ _, err := executor.DataQuery(context.Background(), fakeDataSource(), plugins.DataQuery{
TimeRange: &timeRange, TimeRange: &timeRange,
Queries: []plugins.DataSubQuery{ Queries: []plugins.DataSubQuery{
@ -295,7 +295,7 @@ func TestQuery_StartQuery(t *testing.T) {
To: "1584873443000", To: "1584873443000",
} }
executor := newExecutor(nil, newTestConfig()) executor := newExecutor(nil, newTestConfig(), fakeSessionCache{})
resp, err := executor.DataQuery(context.Background(), fakeDataSource(), plugins.DataQuery{ resp, err := executor.DataQuery(context.Background(), fakeDataSource(), plugins.DataQuery{
TimeRange: &timeRange, TimeRange: &timeRange,
Queries: []plugins.DataSubQuery{ Queries: []plugins.DataSubQuery{
@ -371,7 +371,7 @@ func TestQuery_StopQuery(t *testing.T) {
To: "1584700643000", To: "1584700643000",
} }
executor := newExecutor(nil, newTestConfig()) executor := newExecutor(nil, newTestConfig(), fakeSessionCache{})
resp, err := executor.DataQuery(context.Background(), fakeDataSource(), plugins.DataQuery{ resp, err := executor.DataQuery(context.Background(), fakeDataSource(), plugins.DataQuery{
TimeRange: &timeRange, TimeRange: &timeRange,
Queries: []plugins.DataSubQuery{ Queries: []plugins.DataSubQuery{
@ -458,7 +458,7 @@ func TestQuery_GetQueryResults(t *testing.T) {
}, },
} }
executor := newExecutor(nil, newTestConfig()) executor := newExecutor(nil, newTestConfig(), fakeSessionCache{})
resp, err := executor.DataQuery(context.Background(), fakeDataSource(), plugins.DataQuery{ resp, err := executor.DataQuery(context.Background(), fakeDataSource(), plugins.DataQuery{
Queries: []plugins.DataSubQuery{ Queries: []plugins.DataSubQuery{
{ {

View File

@ -323,7 +323,7 @@ func parseMultiSelectValue(input string) []string {
// Please update the region list in public/app/plugins/datasource/cloudwatch/partials/config.html // Please update the region list in public/app/plugins/datasource/cloudwatch/partials/config.html
func (e *cloudWatchExecutor) handleGetRegions(ctx context.Context, parameters *simplejson.Json, func (e *cloudWatchExecutor) handleGetRegions(ctx context.Context, parameters *simplejson.Json,
queryContext plugins.DataQuery) ([]suggestData, error) { queryContext plugins.DataQuery) ([]suggestData, error) {
dsInfo := e.getDSInfo(defaultRegion) dsInfo := e.getAWSDatasourceSettings(defaultRegion)
profile := dsInfo.Profile profile := dsInfo.Profile
if cache, ok := regionCache.Load(profile); ok { if cache, ok := regionCache.Load(profile); ok {
if cache2, ok2 := cache.([]suggestData); ok2 { if cache2, ok2 := cache.([]suggestData); ok2 {
@ -716,7 +716,7 @@ func (e *cloudWatchExecutor) getMetricsForCustomMetrics(region, namespace string
metricsCacheLock.Lock() metricsCacheLock.Lock()
defer metricsCacheLock.Unlock() defer metricsCacheLock.Unlock()
dsInfo := e.getDSInfo(region) dsInfo := e.getAWSDatasourceSettings(region)
if _, ok := customMetricsMetricsMap[dsInfo.Profile]; !ok { if _, ok := customMetricsMetricsMap[dsInfo.Profile]; !ok {
customMetricsMetricsMap[dsInfo.Profile] = make(map[string]map[string]*customMetricsCache) customMetricsMetricsMap[dsInfo.Profile] = make(map[string]map[string]*customMetricsCache)
@ -760,7 +760,7 @@ func (e *cloudWatchExecutor) getDimensionsForCustomMetrics(region, namespace str
dimensionsCacheLock.Lock() dimensionsCacheLock.Lock()
defer dimensionsCacheLock.Unlock() defer dimensionsCacheLock.Unlock()
dsInfo := e.getDSInfo(region) dsInfo := e.getAWSDatasourceSettings(region)
if _, ok := customMetricsDimensionsMap[dsInfo.Profile]; !ok { if _, ok := customMetricsDimensionsMap[dsInfo.Profile]; !ok {
customMetricsDimensionsMap[dsInfo.Profile] = make(map[string]map[string]*customMetricsCache) customMetricsDimensionsMap[dsInfo.Profile] = make(map[string]map[string]*customMetricsCache)

View File

@ -45,7 +45,7 @@ func TestQuery_Metrics(t *testing.T) {
}, },
}, },
} }
executor := newExecutor(nil, newTestConfig()) executor := newExecutor(nil, newTestConfig(), fakeSessionCache{})
resp, err := executor.DataQuery(context.Background(), fakeDataSource(), plugins.DataQuery{ resp, err := executor.DataQuery(context.Background(), fakeDataSource(), plugins.DataQuery{
Queries: []plugins.DataSubQuery{ Queries: []plugins.DataSubQuery{
{ {
@ -102,7 +102,7 @@ func TestQuery_Metrics(t *testing.T) {
}, },
}, },
} }
executor := newExecutor(nil, newTestConfig()) executor := newExecutor(nil, newTestConfig(), fakeSessionCache{})
resp, err := executor.DataQuery(context.Background(), fakeDataSource(), plugins.DataQuery{ resp, err := executor.DataQuery(context.Background(), fakeDataSource(), plugins.DataQuery{
Queries: []plugins.DataSubQuery{ Queries: []plugins.DataSubQuery{
{ {
@ -164,7 +164,7 @@ func TestQuery_Regions(t *testing.T) {
cli = fakeEC2Client{ cli = fakeEC2Client{
regions: []string{regionName}, regions: []string{regionName},
} }
executor := newExecutor(nil, newTestConfig()) executor := newExecutor(nil, newTestConfig(), fakeSessionCache{})
resp, err := executor.DataQuery(context.Background(), fakeDataSource(), plugins.DataQuery{ resp, err := executor.DataQuery(context.Background(), fakeDataSource(), plugins.DataQuery{
Queries: []plugins.DataSubQuery{ Queries: []plugins.DataSubQuery{
{ {
@ -246,7 +246,7 @@ func TestQuery_InstanceAttributes(t *testing.T) {
}, },
}, },
} }
executor := newExecutor(nil, newTestConfig()) executor := newExecutor(nil, newTestConfig(), fakeSessionCache{})
resp, err := executor.DataQuery(context.Background(), fakeDataSource(), plugins.DataQuery{ resp, err := executor.DataQuery(context.Background(), fakeDataSource(), plugins.DataQuery{
Queries: []plugins.DataSubQuery{ Queries: []plugins.DataSubQuery{
{ {
@ -349,7 +349,7 @@ func TestQuery_EBSVolumeIDs(t *testing.T) {
}, },
}, },
} }
executor := newExecutor(nil, newTestConfig()) executor := newExecutor(nil, newTestConfig(), fakeSessionCache{})
resp, err := executor.DataQuery(context.Background(), fakeDataSource(), plugins.DataQuery{ resp, err := executor.DataQuery(context.Background(), fakeDataSource(), plugins.DataQuery{
Queries: []plugins.DataSubQuery{ Queries: []plugins.DataSubQuery{
{ {
@ -449,7 +449,7 @@ func TestQuery_ResourceARNs(t *testing.T) {
}, },
}, },
} }
executor := newExecutor(nil, newTestConfig()) executor := newExecutor(nil, newTestConfig(), fakeSessionCache{})
resp, err := executor.DataQuery(context.Background(), fakeDataSource(), plugins.DataQuery{ resp, err := executor.DataQuery(context.Background(), fakeDataSource(), plugins.DataQuery{
Queries: []plugins.DataSubQuery{ Queries: []plugins.DataSubQuery{
{ {
@ -528,7 +528,7 @@ func TestQuery_ListMetricsPagination(t *testing.T) {
t.Run("List Metrics and page limit is reached", func(t *testing.T) { t.Run("List Metrics and page limit is reached", func(t *testing.T) {
client = FakeCWClient{Metrics: metrics, MetricsPerPage: 2} client = FakeCWClient{Metrics: metrics, MetricsPerPage: 2}
executor := newExecutor(nil, &setting.Cfg{AWSListMetricsPageLimit: 3, AWSAllowedAuthProviders: []string{"default"}, AWSAssumeRoleEnabled: true}) executor := newExecutor(nil, &setting.Cfg{AWSListMetricsPageLimit: 3, AWSAllowedAuthProviders: []string{"default"}, AWSAssumeRoleEnabled: true}, fakeSessionCache{})
executor.DataSource = fakeDataSource() executor.DataSource = fakeDataSource()
response, err := executor.listMetrics("default", &cloudwatch.ListMetricsInput{}) response, err := executor.listMetrics("default", &cloudwatch.ListMetricsInput{})
require.NoError(t, err) require.NoError(t, err)
@ -539,7 +539,7 @@ func TestQuery_ListMetricsPagination(t *testing.T) {
t.Run("List Metrics and page limit is not reached", func(t *testing.T) { t.Run("List Metrics and page limit is not reached", func(t *testing.T) {
client = FakeCWClient{Metrics: metrics, MetricsPerPage: 2} client = FakeCWClient{Metrics: metrics, MetricsPerPage: 2}
executor := newExecutor(nil, &setting.Cfg{AWSListMetricsPageLimit: 1000, AWSAllowedAuthProviders: []string{"default"}, AWSAssumeRoleEnabled: true}) executor := newExecutor(nil, &setting.Cfg{AWSListMetricsPageLimit: 1000, AWSAllowedAuthProviders: []string{"default"}, AWSAssumeRoleEnabled: true}, fakeSessionCache{})
executor.DataSource = fakeDataSource() executor.DataSource = fakeDataSource()
response, err := executor.listMetrics("default", &cloudwatch.ListMetricsInput{}) response, err := executor.listMetrics("default", &cloudwatch.ListMetricsInput{})
require.NoError(t, err) require.NoError(t, err)

View File

@ -12,7 +12,7 @@ import (
) )
func TestQueryTransformer(t *testing.T) { func TestQueryTransformer(t *testing.T) {
executor := newExecutor(nil, &setting.Cfg{}) executor := newExecutor(nil, &setting.Cfg{}, fakeSessionCache{})
t.Run("One cloudwatchQuery is generated when its request query has one stat", func(t *testing.T) { t.Run("One cloudwatchQuery is generated when its request query has one stat", func(t *testing.T) {
requestQueries := []*requestQuery{ requestQueries := []*requestQuery{
{ {

View File

@ -1,44 +0,0 @@
package cloudwatch
import (
"sync"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds"
"github.com/aws/aws-sdk-go/aws/credentials/stscreds"
"github.com/aws/aws-sdk-go/aws/ec2metadata"
"github.com/aws/aws-sdk-go/aws/session"
)
type envelope struct {
session *session.Session
expiration time.Time
}
var sessCache = map[string]envelope{}
var sessCacheLock sync.RWMutex
// Session factory.
// Stubbable by tests.
//nolint:gocritic
var newSession = func(cfgs ...*aws.Config) (*session.Session, error) {
return session.NewSession(cfgs...)
}
// STS credentials factory.
// Stubbable by tests.
//nolint:gocritic
var newSTSCredentials = stscreds.NewCredentials
// EC2Metadata service factory.
// Stubbable by tests.
//nolint:gocritic
var newEC2Metadata = ec2metadata.New
// EC2 role credentials factory.
// Stubbable by tests.
var newEC2RoleCredentials = func(sess *session.Session) *credentials.Credentials {
return credentials.NewCredentials(&ec2rolecreds.EC2RoleProvider{Client: ec2metadata.New(sess), ExpiryWindow: stscreds.DefaultDuration})
}

View File

@ -1,188 +0,0 @@
package cloudwatch
import (
"reflect"
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/client"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds"
"github.com/aws/aws-sdk-go/aws/credentials/stscreds"
"github.com/aws/aws-sdk-go/aws/ec2metadata"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/grafana/grafana/pkg/setting"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// Test cloudWatchExecutor.newSession with assumption of IAM role.
func TestNewSession_AssumeRole(t *testing.T) {
origNewSession := newSession
origNewSTSCredentials := newSTSCredentials
origNewEC2Metadata := newEC2Metadata
t.Cleanup(func() {
newSession = origNewSession
newSTSCredentials = origNewSTSCredentials
newEC2Metadata = origNewEC2Metadata
})
newSession = func(cfgs ...*aws.Config) (*session.Session, error) {
cfg := aws.Config{}
cfg.MergeIn(cfgs...)
return &session.Session{
Config: &cfg,
}, nil
}
newSTSCredentials = func(c client.ConfigProvider, roleARN string,
options ...func(*stscreds.AssumeRoleProvider)) *credentials.Credentials {
p := &stscreds.AssumeRoleProvider{
RoleARN: roleARN,
}
for _, o := range options {
o(p)
}
return credentials.NewCredentials(p)
}
newEC2Metadata = func(p client.ConfigProvider, cfgs ...*aws.Config) *ec2metadata.EC2Metadata {
return nil
}
duration := stscreds.DefaultDuration
t.Run("Without external ID", func(t *testing.T) {
t.Cleanup(func() {
sessCache = map[string]envelope{}
})
const roleARN = "test"
e := newExecutor(nil, newTestConfig())
e.DataSource = fakeDataSource(fakeDataSourceCfg{
assumeRoleARN: roleARN,
})
sess, err := e.newSession(defaultRegion)
require.NoError(t, err)
require.NotNil(t, sess)
expCreds := credentials.NewCredentials(&stscreds.AssumeRoleProvider{
RoleARN: roleARN,
Duration: duration,
})
diff := cmp.Diff(expCreds, sess.Config.Credentials, cmp.Exporter(func(_ reflect.Type) bool {
return true
}), cmpopts.IgnoreFields(stscreds.AssumeRoleProvider{}, "Expiry"))
assert.Empty(t, diff)
})
t.Run("With external ID", func(t *testing.T) {
t.Cleanup(func() {
sessCache = map[string]envelope{}
})
const roleARN = "test"
const externalID = "external"
e := newExecutor(nil, newTestConfig())
e.DataSource = fakeDataSource(fakeDataSourceCfg{
assumeRoleARN: roleARN,
externalID: externalID,
})
sess, err := e.newSession(defaultRegion)
require.NoError(t, err)
require.NotNil(t, sess)
expCreds := credentials.NewCredentials(&stscreds.AssumeRoleProvider{
RoleARN: roleARN,
ExternalID: aws.String(externalID),
Duration: duration,
})
diff := cmp.Diff(expCreds, sess.Config.Credentials, cmp.Exporter(func(_ reflect.Type) bool {
return true
}), cmpopts.IgnoreFields(stscreds.AssumeRoleProvider{}, "Expiry"))
assert.Empty(t, diff)
})
t.Run("Assume role not enabled", func(t *testing.T) {
t.Cleanup(func() {
sessCache = map[string]envelope{}
})
const roleARN = "test"
e := newExecutor(nil, &setting.Cfg{AWSAllowedAuthProviders: []string{"default"}, AWSAssumeRoleEnabled: false})
e.DataSource = fakeDataSource(fakeDataSourceCfg{
assumeRoleARN: roleARN,
})
sess, err := e.newSession(defaultRegion)
require.Error(t, err)
require.Nil(t, sess)
expectedError := "attempting to use assume role (ARN) which is disabled in grafana.ini"
assert.Equal(t, expectedError, err.Error())
})
}
func TestNewSession_AllowedAuthProviders(t *testing.T) {
t.Run("Not allowed auth type is used", func(t *testing.T) {
e := newExecutor(nil, &setting.Cfg{AWSAllowedAuthProviders: []string{"keys"}})
e.DataSource = fakeDataSource()
e.DataSource.JsonData.Set("authType", "default")
sess, err := e.newSession(defaultRegion)
require.Error(t, err)
require.Nil(t, sess)
assert.Equal(t, `attempting to use an auth type that is not allowed: "default"`, err.Error())
})
t.Run("Allowed auth type is used", func(t *testing.T) {
e := newExecutor(nil, &setting.Cfg{AWSAllowedAuthProviders: []string{"keys"}})
e.DataSource = fakeDataSource()
e.DataSource.JsonData.Set("authType", "keys")
sess, err := e.newSession(defaultRegion)
require.NoError(t, err)
require.NotNil(t, sess)
})
}
func TestNewSession_EC2IAMRole(t *testing.T) {
newSession = func(cfgs ...*aws.Config) (*session.Session, error) {
cfg := aws.Config{}
cfg.MergeIn(cfgs...)
return &session.Session{
Config: &cfg,
}, nil
}
newEC2Metadata = func(p client.ConfigProvider, cfgs ...*aws.Config) *ec2metadata.EC2Metadata {
return nil
}
newEC2RoleCredentials = func(sess *session.Session) *credentials.Credentials {
return credentials.NewCredentials(&ec2rolecreds.EC2RoleProvider{Client: newEC2Metadata(nil), ExpiryWindow: stscreds.DefaultDuration})
}
t.Run("Credentials are created", func(t *testing.T) {
e := newExecutor(nil, &setting.Cfg{AWSAllowedAuthProviders: []string{"ec2_iam_role"}, AWSAssumeRoleEnabled: true})
e.DataSource = fakeDataSource()
e.DataSource.JsonData.Set("authType", "ec2_iam_role")
sess, err := e.newSession(defaultRegion)
require.NoError(t, err)
require.NotNil(t, sess)
expCreds := credentials.NewCredentials(&ec2rolecreds.EC2RoleProvider{
Client: newEC2Metadata(nil), ExpiryWindow: stscreds.DefaultDuration,
})
diff := cmp.Diff(expCreds, sess.Config.Credentials, cmp.Exporter(func(_ reflect.Type) bool {
return true
}), cmpopts.IgnoreFields(stscreds.AssumeRoleProvider{}, "Expiry"))
assert.Empty(t, diff)
})
}

View File

@ -5,6 +5,7 @@ import (
"github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/request" "github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/cloudwatch" "github.com/aws/aws-sdk-go/service/cloudwatch"
"github.com/aws/aws-sdk-go/service/cloudwatch/cloudwatchiface" "github.com/aws/aws-sdk-go/service/cloudwatch/cloudwatchiface"
"github.com/aws/aws-sdk-go/service/cloudwatchlogs" "github.com/aws/aws-sdk-go/service/cloudwatchlogs"
@ -13,6 +14,7 @@ import (
"github.com/aws/aws-sdk-go/service/ec2/ec2iface" "github.com/aws/aws-sdk-go/service/ec2/ec2iface"
"github.com/aws/aws-sdk-go/service/resourcegroupstaggingapi" "github.com/aws/aws-sdk-go/service/resourcegroupstaggingapi"
"github.com/aws/aws-sdk-go/service/resourcegroupstaggingapi/resourcegroupstaggingapiiface" "github.com/aws/aws-sdk-go/service/resourcegroupstaggingapi/resourcegroupstaggingapiiface"
"github.com/grafana/grafana-aws-sdk/pkg/awsds"
"github.com/grafana/grafana/pkg/components/securejsondata" "github.com/grafana/grafana/pkg/components/securejsondata"
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
@ -179,3 +181,12 @@ func chunkSlice(slice []*cloudwatch.Metric, chunkSize int) [][]*cloudwatch.Metri
func newTestConfig() *setting.Cfg { func newTestConfig() *setting.Cfg {
return &setting.Cfg{AWSAllowedAuthProviders: []string{"default"}, AWSAssumeRoleEnabled: true, AWSListMetricsPageLimit: 1000} return &setting.Cfg{AWSAllowedAuthProviders: []string{"default"}, AWSAssumeRoleEnabled: true, AWSListMetricsPageLimit: 1000}
} }
type fakeSessionCache struct {
}
func (s fakeSessionCache) GetSession(region string, settings awsds.AWSDatasourceSettings) (*session.Session, error) {
return &session.Session{
Config: &aws.Config{},
}, nil
}

View File

@ -9,7 +9,7 @@ import (
) )
func TestTimeSeriesQuery(t *testing.T) { func TestTimeSeriesQuery(t *testing.T) {
executor := newExecutor(nil, newTestConfig()) executor := newExecutor(nil, newTestConfig(), fakeSessionCache{})
t.Run("End time before start time should result in error", func(t *testing.T) { t.Run("End time before start time should result in error", func(t *testing.T) {
timeRange := plugins.NewDataTimeRange("now-1h", "now-2h") timeRange := plugins.NewDataTimeRange("now-1h", "now-2h")

101
yarn.lock
View File

@ -3136,6 +3136,18 @@
"@emotion/sheet" "0.9.4" "@emotion/sheet" "0.9.4"
"@emotion/utils" "0.11.3" "@emotion/utils" "0.11.3"
"@emotion/core@^10.0.27", "@emotion/core@^10.1.1":
version "10.1.1"
resolved "https://registry.yarnpkg.com/@emotion/core/-/core-10.1.1.tgz#c956c1365f2f2481960064bcb8c4732e5fb612c3"
integrity sha512-ZMLG6qpXR8x031NXD8HJqugy/AZSkAuMxxqB46pmAR7ze47MhNJ56cdoX243QPZdGctrdfo+s08yZTiwaUcRKA==
dependencies:
"@babel/runtime" "^7.5.5"
"@emotion/cache" "^10.0.27"
"@emotion/css" "^10.0.27"
"@emotion/serialize" "^0.11.15"
"@emotion/sheet" "0.9.4"
"@emotion/utils" "0.11.3"
"@emotion/core@^10.0.9": "@emotion/core@^10.0.9":
version "10.0.21" version "10.0.21"
resolved "https://registry.yarnpkg.com/@emotion/core/-/core-10.0.21.tgz#2e8398d2b92fd90d4ed6ac4d0b66214971de3458" resolved "https://registry.yarnpkg.com/@emotion/core/-/core-10.0.21.tgz#2e8398d2b92fd90d4ed6ac4d0b66214971de3458"
@ -3148,18 +3160,6 @@
"@emotion/sheet" "0.9.3" "@emotion/sheet" "0.9.3"
"@emotion/utils" "0.11.2" "@emotion/utils" "0.11.2"
"@emotion/core@^10.1.1":
version "10.1.1"
resolved "https://registry.yarnpkg.com/@emotion/core/-/core-10.1.1.tgz#c956c1365f2f2481960064bcb8c4732e5fb612c3"
integrity sha512-ZMLG6qpXR8x031NXD8HJqugy/AZSkAuMxxqB46pmAR7ze47MhNJ56cdoX243QPZdGctrdfo+s08yZTiwaUcRKA==
dependencies:
"@babel/runtime" "^7.5.5"
"@emotion/cache" "^10.0.27"
"@emotion/css" "^10.0.27"
"@emotion/serialize" "^0.11.15"
"@emotion/sheet" "0.9.4"
"@emotion/utils" "0.11.3"
"@emotion/css@^10.0.14", "@emotion/css@^10.0.9": "@emotion/css@^10.0.14", "@emotion/css@^10.0.9":
version "10.0.14" version "10.0.14"
resolved "https://registry.yarnpkg.com/@emotion/css/-/css-10.0.14.tgz#95dacabdd0e22845d1a1b0b5968d9afa34011139" resolved "https://registry.yarnpkg.com/@emotion/css/-/css-10.0.14.tgz#95dacabdd0e22845d1a1b0b5968d9afa34011139"
@ -3430,33 +3430,33 @@
source-map "~0.6.1" source-map "~0.6.1"
typescript "~3.9.7" typescript "~3.9.7"
"@grafana/aws-sdk@0.0.2": "@grafana/aws-sdk@0.0.22":
version "0.0.2" version "0.0.22"
resolved "https://registry.yarnpkg.com/@grafana/aws-sdk/-/aws-sdk-0.0.2.tgz#dcaa8672158ce12d5e83b10c6d801b718e03e66a" resolved "https://registry.yarnpkg.com/@grafana/aws-sdk/-/aws-sdk-0.0.22.tgz#58389616beda01ef137804ab6d9ef07d4a4a35b0"
integrity sha512-nKkFoNJ76NgGolJArdro39Ho7YA769BO3nq8/NeUHai9MeEVmPviuqC5pnswb08Q4QTEKESi0Zo3x8GvfF6cZg== integrity sha512-gTyHmD5VeXjjnnq3zVyI2x3ktJB7dBp92GoJW+lH2t9bWNkcnXyzHSaXZZdrnNpjctaqpv85uxcocK4U7BGCvw==
dependencies: dependencies:
"@grafana/data" "7.5.0-beta.1" "@grafana/data" "7.4.0"
"@grafana/runtime" "7.5.0-beta.1" "@grafana/runtime" "7.4.0"
"@grafana/ui" "7.5.0-beta.1" "@grafana/ui" "7.4.0"
"@grafana/data@7.5.0-beta.1": "@grafana/data@7.4.0":
version "7.5.0-beta.1" version "7.4.0"
resolved "https://registry.yarnpkg.com/@grafana/data/-/data-7.5.0-beta.1.tgz#4c6dcd07daee713a06e46d230b9c09877fd5f732" resolved "https://registry.yarnpkg.com/@grafana/data/-/data-7.4.0.tgz#7e7ae998cc4d916ba7d004e27b38eab4b1199ee8"
integrity sha512-hYU3XWVp2rsLfSZVwYFM9wL0e+i1ktlNPzELGL66EcSV+ak41orlcjpwEpke4O7Vtht6t5qSoTycYlmj6+aZEw== integrity sha512-YlsA9uu3dO7hnbIUagjhAMXUU7puSSXxc42aOyuj/K9daPVMhOunI7lSM8PSm1uhKVifvd5DJOwlA0UQQRJqSw==
dependencies: dependencies:
"@braintree/sanitize-url" "4.0.0" "@braintree/sanitize-url" "4.0.0"
"@types/d3-interpolate" "^1.3.1" "@types/d3-interpolate" "^1.3.1"
apache-arrow "0.16.0" apache-arrow "0.16.0"
eventemitter3 "4.0.7" eventemitter3 "4.0.7"
lodash "4.17.21" lodash "4.17.20"
marked "2.0.1" marked "1.2.2"
rxjs "6.6.3" rxjs "6.6.3"
xss "1.0.6" xss "1.0.6"
"@grafana/e2e-selectors@7.5.0-beta.1": "@grafana/e2e-selectors@7.4.0":
version "7.5.0-beta.1" version "7.4.0"
resolved "https://registry.yarnpkg.com/@grafana/e2e-selectors/-/e2e-selectors-7.5.0-beta.1.tgz#063734af7a112033d2e78219969619194fca3526" resolved "https://registry.yarnpkg.com/@grafana/e2e-selectors/-/e2e-selectors-7.4.0.tgz#f286dab307b26e6b0628a1d5e9907e3943c38ed3"
integrity sha512-zn23xTrkNtCRSI4wg23CatAq2t4R2JWtqwEKvRfP3qWuJTwql2vu1pLJufJUcTkJtn3ImmwSMGvxZ/wI9SPbqQ== integrity sha512-RCAXLOTUlO8nWALo1Wnl0pTkZYzUGnBV6RuetwEDaRkfZEEKXn3F1OerzF5iJWFsdFJvVehCZ7sFYOPmnEl/gg==
dependencies: dependencies:
"@grafana/tsconfig" "^1.0.0-rc1" "@grafana/tsconfig" "^1.0.0-rc1"
commander "5.0.0" commander "5.0.0"
@ -3480,13 +3480,13 @@
prettier "2.2.1" prettier "2.2.1"
typescript "4.1.3" typescript "4.1.3"
"@grafana/runtime@7.5.0-beta.1": "@grafana/runtime@7.4.0":
version "7.5.0-beta.1" version "7.4.0"
resolved "https://registry.yarnpkg.com/@grafana/runtime/-/runtime-7.5.0-beta.1.tgz#f0e29a2493d440b7dd6d09def9727c2ce41cb743" resolved "https://registry.yarnpkg.com/@grafana/runtime/-/runtime-7.4.0.tgz#c097adcdb151efd4211a4fc2bd91598c9cd7e627"
integrity sha512-Cn62/9N1g3TX9vaoGgpD0/bWG2U1nsAoHboLbQnKkztH8tmLoChHqUaZ/5Lsu3s9K5mxI3YA+4PrAxr4NlUPjw== integrity sha512-ug2FPtzlEL4CykmrVp1Cnp6066ndzlMP8x/+rHNK7RR3HbS0ju2c+t6iq0vwU6605dwL71uOC+NOUuNIyZtYyg==
dependencies: dependencies:
"@grafana/data" "7.5.0-beta.1" "@grafana/data" "7.4.0"
"@grafana/ui" "7.5.0-beta.1" "@grafana/ui" "7.4.0"
systemjs "0.20.19" systemjs "0.20.19"
systemjs-plugin-css "0.1.37" systemjs-plugin-css "0.1.37"
@ -3517,14 +3517,14 @@
resolved "https://registry.yarnpkg.com/@grafana/tsconfig/-/tsconfig-1.0.0-rc1.tgz#d07ea16755a50cae21000113f30546b61647a200" resolved "https://registry.yarnpkg.com/@grafana/tsconfig/-/tsconfig-1.0.0-rc1.tgz#d07ea16755a50cae21000113f30546b61647a200"
integrity sha512-nucKPGyzlSKYSiJk5RA8GzMdVWhdYNdF+Hh65AXxjD9PlY69JKr5wANj8bVdQboag6dgg0BFKqgKPyY+YtV4Iw== integrity sha512-nucKPGyzlSKYSiJk5RA8GzMdVWhdYNdF+Hh65AXxjD9PlY69JKr5wANj8bVdQboag6dgg0BFKqgKPyY+YtV4Iw==
"@grafana/ui@7.5.0-beta.1": "@grafana/ui@7.4.0":
version "7.5.0-beta.1" version "7.4.0"
resolved "https://registry.yarnpkg.com/@grafana/ui/-/ui-7.5.0-beta.1.tgz#0de30ebe9e51df956fa20f1d0a396d286b3f8c5f" resolved "https://registry.yarnpkg.com/@grafana/ui/-/ui-7.4.0.tgz#4c5eea4c5a00a342fb87bd9a462398e32a72bb84"
integrity sha512-/+VeGVRjRtd9bOwhzdwWqk8IVrENlGoMWCuRaNQU0+q2tdPQurXYbE3rmxKVtKysPTYNzbhS5Ox5QzYkPkoriA== integrity sha512-jbpqWGqzdDmU5+lCOHY/DLTlpLD0rNgXJfQLWwPq2ilOdCq0ItIaehxM+e5XqzTy3J+q5nyNQZFH2wM+wqzZRw==
dependencies: dependencies:
"@emotion/core" "10.0.27" "@emotion/core" "^10.0.27"
"@grafana/data" "7.5.0-beta.1" "@grafana/data" "7.4.0"
"@grafana/e2e-selectors" "7.5.0-beta.1" "@grafana/e2e-selectors" "7.4.0"
"@grafana/slate-react" "0.22.9-grafana" "@grafana/slate-react" "0.22.9-grafana"
"@grafana/tsconfig" "^1.0.0-rc1" "@grafana/tsconfig" "^1.0.0-rc1"
"@iconscout/react-unicons" "1.1.4" "@iconscout/react-unicons" "1.1.4"
@ -3539,18 +3539,13 @@
"@types/react-table" "7.0.12" "@types/react-table" "7.0.12"
"@types/slate" "0.47.1" "@types/slate" "0.47.1"
"@types/slate-react" "0.22.5" "@types/slate-react" "0.22.5"
"@visx/event" "1.3.0"
"@visx/gradient" "1.0.0"
"@visx/scale" "1.4.0"
"@visx/shape" "1.4.0"
"@visx/tooltip" "1.3.0"
classnames "2.2.6" classnames "2.2.6"
d3 "5.15.0" d3 "5.15.0"
emotion "10.0.27" emotion "10.0.27"
hoist-non-react-statics "3.3.2" hoist-non-react-statics "3.3.2"
immutable "3.8.2" immutable "3.8.2"
jquery "3.5.1" jquery "3.5.1"
lodash "4.17.21" lodash "4.17.20"
moment "2.24.0" moment "2.24.0"
monaco-editor "0.20.0" monaco-editor "0.20.0"
papaparse "5.3.0" papaparse "5.3.0"
@ -17724,6 +17719,11 @@ lodash.uniq@4.5.0, lodash.uniq@^4.5.0:
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=
lodash@4.17.20:
version "4.17.20"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52"
integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==
lodash@4.17.21, lodash@^4, lodash@^4.0.0, lodash@^4.0.1, lodash@^4.1.1, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.1, lodash@^4.3.0, lodash@~4.17.10, lodash@~4.17.15: lodash@4.17.21, lodash@^4, lodash@^4.0.0, lodash@^4.0.1, lodash@^4.1.1, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.1, lodash@^4.3.0, lodash@~4.17.10, lodash@~4.17.15:
version "4.17.21" version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
@ -17999,6 +17999,11 @@ markdown-to-jsx@^6.11.4:
prop-types "^15.6.2" prop-types "^15.6.2"
unquote "^1.1.0" unquote "^1.1.0"
marked@1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/marked/-/marked-1.2.2.tgz#5d77ffb789c4cb0ae828bfe76250f7140b123f70"
integrity sha512-5jjKHVl/FPo0Z6ocP3zYhKiJLzkwJAw4CZoLjv57FkvbUuwOX4LIBBGGcXjAY6ATcd1q9B8UTj5T9Umauj0QYQ==
marked@2.0.1: marked@2.0.1:
version "2.0.1" version "2.0.1"
resolved "https://registry.yarnpkg.com/marked/-/marked-2.0.1.tgz#5e7ed7009bfa5c95182e4eb696f85e948cefcee3" resolved "https://registry.yarnpkg.com/marked/-/marked-2.0.1.tgz#5e7ed7009bfa5c95182e4eb696f85e948cefcee3"