From 9dd1d5f55362fe98340322db45778037a3911a0e Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Fri, 12 Mar 2021 05:30:21 -0800 Subject: [PATCH] Cloudwatch: use shared library for aws auth (#29550) * use sdk for handling auth * fix broken test * lint fixes Co-authored-by: Erik Sundell --- go.mod | 2 +- go.sum | 4 +- package.json | 2 +- pkg/plugins/backendplugin/manager/manager.go | 16 ++ .../backendplugin/manager/manager_test.go | 13 +- pkg/setting/setting.go | 18 +- pkg/tsdb/cloudwatch/cloudwatch.go | 201 ++---------------- pkg/tsdb/cloudwatch/log_actions_test.go | 14 +- pkg/tsdb/cloudwatch/metric_find_query.go | 6 +- pkg/tsdb/cloudwatch/metric_find_query_test.go | 16 +- pkg/tsdb/cloudwatch/query_transformer_test.go | 2 +- pkg/tsdb/cloudwatch/session.go | 44 ---- pkg/tsdb/cloudwatch/session_test.go | 188 ---------------- pkg/tsdb/cloudwatch/test_utils.go | 11 + pkg/tsdb/cloudwatch/time_series_query_test.go | 2 +- yarn.lock | 101 ++++----- 16 files changed, 151 insertions(+), 489 deletions(-) delete mode 100644 pkg/tsdb/cloudwatch/session.go delete mode 100644 pkg/tsdb/cloudwatch/session_test.go diff --git a/go.mod b/go.mod index a75001deaf3..8fd22e1cc47 100644 --- a/go.mod +++ b/go.mod @@ -40,7 +40,7 @@ require ( github.com/google/uuid v1.2.0 github.com/gosimple/slug v1.9.0 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-sdk-go v0.88.0 github.com/grafana/loki v1.6.2-0.20201026154740-6978ee5d7387 diff --git a/go.sum b/go.sum index 402c8dd9e9d..57b80410311 100644 --- a/go.sum +++ b/go.sum @@ -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/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/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/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= 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/go.mod h1:5IppnPguSHcCbVLGCVzVjBvuQZNbYgVJ4KyXXjhCyWY= 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.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/go.mod h1:nc0XxBzjeGcrMltCDw269LoWF9S8ibhgxolCdA1R8To= github.com/grafana/grafana-plugin-sdk-go v0.79.0/go.mod h1:NvxLzGkVhnoBKwzkst6CFfpMFKwAdIUZ1q8ssuLeF60= diff --git a/package.json b/package.json index 84c8ad43b59..096e934ccc9 100644 --- a/package.json +++ b/package.json @@ -202,7 +202,7 @@ }, "dependencies": { "@emotion/core": "10.0.27", - "@grafana/aws-sdk": "0.0.2", + "@grafana/aws-sdk": "0.0.22", "@grafana/slate-react": "0.22.9-grafana", "@popperjs/core": "2.5.4", "@reduxjs/toolkit": "1.5.0", diff --git a/pkg/plugins/backendplugin/manager/manager.go b/pkg/plugins/backendplugin/manager/manager.go index 50c2851985c..5c87e408324 100644 --- a/pkg/plugins/backendplugin/manager/manager.go +++ b/pkg/plugins/backendplugin/manager/manager.go @@ -9,9 +9,11 @@ import ( "io/ioutil" "net/http" "net/url" + "strings" "sync" "time" + "github.com/grafana/grafana-aws-sdk/pkg/awsds" "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana/pkg/infra/log" "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) pluginLogger := m.logger.New("pluginId", pluginID) @@ -99,6 +103,18 @@ func (m *manager) Register(pluginID string, factory backendplugin.PluginFactoryF 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{} { plugin := m.plugins[pluginID] if plugin == nil || !plugin.CanHandleDataQueries() { diff --git a/pkg/plugins/backendplugin/manager/manager_test.go b/pkg/plugins/backendplugin/manager/manager_test.go index 0095a6d7e1f..443b61bd721 100644 --- a/pkg/plugins/backendplugin/manager/manager_test.go +++ b/pkg/plugins/backendplugin/manager/manager_test.go @@ -3,12 +3,14 @@ package manager import ( "bytes" "context" + "fmt" "net/http" "net/http/httptest" "sync" "testing" "time" + "github.com/grafana/grafana-aws-sdk/pkg/awsds" "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana/pkg/infra/log" "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) { - require.Len(t, ctx.env, 2) - require.EqualValues(t, []string{"GF_VERSION=7.0.0", "GF_EDITION=Open Source"}, ctx.env) + require.Len(t, ctx.env, 4) + 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) { @@ -261,8 +263,8 @@ func TestManager(t *testing.T) { require.NoError(t, err) t.Run("Should provide expected host environment variables", func(t *testing.T) { - require.Len(t, ctx.env, 4) - 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.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", 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)) { t.Helper() cfg := setting.NewCfg() + cfg.AWSAllowedAuthProviders = []string{"keys", "credentials"} + cfg.AWSAssumeRoleEnabled = true + license := &testLicensingService{} validator := &testPluginRequestValidator{} ctx := &managerScenarioCtx{ diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index b97ae595486..e4dc3c26a99 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -12,12 +12,14 @@ import ( "os" "path" "path/filepath" + "strconv" "strings" "time" "github.com/prometheus/common/model" 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/infra/log" "github.com/grafana/grafana/pkg/util" @@ -876,7 +878,7 @@ func (cfg *Cfg) Load(args *CommandLineArgs) error { } cfg.readLDAPConfig() - cfg.readAWSConfig() + cfg.handleAWSConfig() cfg.readSessionConfig() cfg.readSmtpSettings() cfg.readQuotaSettings() @@ -940,10 +942,10 @@ func (cfg *Cfg) readLDAPConfig() { cfg.LDAPAllowSignup = LDAPAllowSignup } -func (cfg *Cfg) readAWSConfig() { +func (cfg *Cfg) handleAWSConfig() { awsPluginSec := cfg.Raw.Section("aws") 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, ",") { authProvider = strings.TrimSpace(authProvider) if authProvider != "" { @@ -951,6 +953,16 @@ func (cfg *Cfg) readAWSConfig() { } } 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() { diff --git a/pkg/tsdb/cloudwatch/cloudwatch.go b/pkg/tsdb/cloudwatch/cloudwatch.go index 877a54fe74a..b8b6c6144c2 100644 --- a/pkg/tsdb/cloudwatch/cloudwatch.go +++ b/pkg/tsdb/cloudwatch/cloudwatch.go @@ -4,15 +4,12 @@ import ( "context" "fmt" "regexp" - "strings" "time" + "github.com/grafana/grafana-aws-sdk/pkg/awsds" "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/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/session" "github.com/aws/aws-sdk-go/service/cloudwatch" @@ -31,19 +28,6 @@ import ( "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 defaultRegion = "default" @@ -65,20 +49,27 @@ func init() { type CloudWatchService struct { LogsService *LogsService `inject:""` Cfg *setting.Cfg `inject:""` + sessions SessionCache } func (s *CloudWatchService) Init() error { + s.sessions = awsds.NewSessionCache() return nil } 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{ - logsService: logsService, cfg: cfg, + logsService: logsService, + sessions: sessions, } } @@ -91,135 +82,13 @@ type cloudWatchExecutor struct { logsService *LogsService cfg *setting.Cfg + sessions SessionCache } func (e *cloudWatchExecutor) newSession(region string) (*session.Session, error) { - dsInfo := e.getDSInfo(region) + awsDatasourceSettings := e.getAWSDatasourceSettings(region) - authTypeAllowed := false - 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 + return e.sessions.GetSession(region, *awsDatasourceSettings) } func (e *cloudWatchExecutor) getCWClient(region string) (cloudwatchiface.CloudWatchAPI, error) { @@ -415,31 +284,7 @@ func (e *cloudWatchExecutor) executeLogAlertQuery(ctx context.Context, queryCont return response, nil } -type authType int - -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 { +func (e *cloudWatchExecutor) getAWSDatasourceSettings(region string) *awsds.AWSDatasourceSettings { if region == defaultRegion { region = e.DataSource.JsonData.Get("defaultRegion").MustString() } @@ -452,19 +297,19 @@ func (e *cloudWatchExecutor) getDSInfo(region string) *datasourceInfo { accessKey := decrypted["accessKey"] secretKey := decrypted["secretKey"] - at := authTypeDefault + at := awsds.AuthTypeDefault switch atStr { case "credentials": - at = authTypeSharedCreds + at = awsds.AuthTypeSharedCreds case "keys": - at = authTypeKeys + at = awsds.AuthTypeKeys case "default": - at = authTypeDefault - case "ec2_iam_role": - at = authTypeEC2IAMRole + at = awsds.AuthTypeDefault case "arn": - at = authTypeDefault + at = awsds.AuthTypeDefault plog.Warn("Authentication type \"arn\" is deprecated, falling back to default") + case "ec2_iam_role": + at = awsds.AuthTypeEC2IAMRole default: 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 } - return &datasourceInfo{ + return &awsds.AWSDatasourceSettings{ Region: region, Profile: profile, AuthType: at, diff --git a/pkg/tsdb/cloudwatch/log_actions_test.go b/pkg/tsdb/cloudwatch/log_actions_test.go index f8546fc1a7a..66a4987814a 100644 --- a/pkg/tsdb/cloudwatch/log_actions_test.go +++ b/pkg/tsdb/cloudwatch/log_actions_test.go @@ -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{ 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{ Queries: []plugins.DataSubQuery{ { @@ -170,7 +170,7 @@ func TestQuery_GetLogGroupFields(t *testing.T) { const refID = "A" - executor := newExecutor(nil, newTestConfig()) + executor := newExecutor(nil, newTestConfig(), fakeSessionCache{}) resp, err := executor.DataQuery(context.Background(), fakeDataSource(), plugins.DataQuery{ Queries: []plugins.DataSubQuery{ { @@ -249,7 +249,7 @@ func TestQuery_StartQuery(t *testing.T) { To: "1584700643000", } - executor := newExecutor(nil, newTestConfig()) + executor := newExecutor(nil, newTestConfig(), fakeSessionCache{}) _, err := executor.DataQuery(context.Background(), fakeDataSource(), plugins.DataQuery{ TimeRange: &timeRange, Queries: []plugins.DataSubQuery{ @@ -295,7 +295,7 @@ func TestQuery_StartQuery(t *testing.T) { To: "1584873443000", } - executor := newExecutor(nil, newTestConfig()) + executor := newExecutor(nil, newTestConfig(), fakeSessionCache{}) resp, err := executor.DataQuery(context.Background(), fakeDataSource(), plugins.DataQuery{ TimeRange: &timeRange, Queries: []plugins.DataSubQuery{ @@ -371,7 +371,7 @@ func TestQuery_StopQuery(t *testing.T) { To: "1584700643000", } - executor := newExecutor(nil, newTestConfig()) + executor := newExecutor(nil, newTestConfig(), fakeSessionCache{}) resp, err := executor.DataQuery(context.Background(), fakeDataSource(), plugins.DataQuery{ TimeRange: &timeRange, 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{ Queries: []plugins.DataSubQuery{ { diff --git a/pkg/tsdb/cloudwatch/metric_find_query.go b/pkg/tsdb/cloudwatch/metric_find_query.go index 5026e629129..e50bda89966 100644 --- a/pkg/tsdb/cloudwatch/metric_find_query.go +++ b/pkg/tsdb/cloudwatch/metric_find_query.go @@ -323,7 +323,7 @@ func parseMultiSelectValue(input string) []string { // Please update the region list in public/app/plugins/datasource/cloudwatch/partials/config.html func (e *cloudWatchExecutor) handleGetRegions(ctx context.Context, parameters *simplejson.Json, queryContext plugins.DataQuery) ([]suggestData, error) { - dsInfo := e.getDSInfo(defaultRegion) + dsInfo := e.getAWSDatasourceSettings(defaultRegion) profile := dsInfo.Profile if cache, ok := regionCache.Load(profile); ok { if cache2, ok2 := cache.([]suggestData); ok2 { @@ -716,7 +716,7 @@ func (e *cloudWatchExecutor) getMetricsForCustomMetrics(region, namespace string metricsCacheLock.Lock() defer metricsCacheLock.Unlock() - dsInfo := e.getDSInfo(region) + dsInfo := e.getAWSDatasourceSettings(region) if _, ok := customMetricsMetricsMap[dsInfo.Profile]; !ok { customMetricsMetricsMap[dsInfo.Profile] = make(map[string]map[string]*customMetricsCache) @@ -760,7 +760,7 @@ func (e *cloudWatchExecutor) getDimensionsForCustomMetrics(region, namespace str dimensionsCacheLock.Lock() defer dimensionsCacheLock.Unlock() - dsInfo := e.getDSInfo(region) + dsInfo := e.getAWSDatasourceSettings(region) if _, ok := customMetricsDimensionsMap[dsInfo.Profile]; !ok { customMetricsDimensionsMap[dsInfo.Profile] = make(map[string]map[string]*customMetricsCache) diff --git a/pkg/tsdb/cloudwatch/metric_find_query_test.go b/pkg/tsdb/cloudwatch/metric_find_query_test.go index e9918504c30..1688daf7e7e 100644 --- a/pkg/tsdb/cloudwatch/metric_find_query_test.go +++ b/pkg/tsdb/cloudwatch/metric_find_query_test.go @@ -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{ 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{ Queries: []plugins.DataSubQuery{ { @@ -164,7 +164,7 @@ func TestQuery_Regions(t *testing.T) { cli = fakeEC2Client{ regions: []string{regionName}, } - executor := newExecutor(nil, newTestConfig()) + executor := newExecutor(nil, newTestConfig(), fakeSessionCache{}) resp, err := executor.DataQuery(context.Background(), fakeDataSource(), plugins.DataQuery{ 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{ 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{ 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{ 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) { 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() response, err := executor.listMetrics("default", &cloudwatch.ListMetricsInput{}) 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) { 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() response, err := executor.listMetrics("default", &cloudwatch.ListMetricsInput{}) require.NoError(t, err) diff --git a/pkg/tsdb/cloudwatch/query_transformer_test.go b/pkg/tsdb/cloudwatch/query_transformer_test.go index 451ef9ec364..3b96c11bd9d 100644 --- a/pkg/tsdb/cloudwatch/query_transformer_test.go +++ b/pkg/tsdb/cloudwatch/query_transformer_test.go @@ -12,7 +12,7 @@ import ( ) 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) { requestQueries := []*requestQuery{ { diff --git a/pkg/tsdb/cloudwatch/session.go b/pkg/tsdb/cloudwatch/session.go deleted file mode 100644 index 0cc3b01c9f1..00000000000 --- a/pkg/tsdb/cloudwatch/session.go +++ /dev/null @@ -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}) -} diff --git a/pkg/tsdb/cloudwatch/session_test.go b/pkg/tsdb/cloudwatch/session_test.go deleted file mode 100644 index b240c1df3d8..00000000000 --- a/pkg/tsdb/cloudwatch/session_test.go +++ /dev/null @@ -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) - }) -} diff --git a/pkg/tsdb/cloudwatch/test_utils.go b/pkg/tsdb/cloudwatch/test_utils.go index 7ceb36f8448..9d28a99f70c 100644 --- a/pkg/tsdb/cloudwatch/test_utils.go +++ b/pkg/tsdb/cloudwatch/test_utils.go @@ -5,6 +5,7 @@ import ( "github.com/aws/aws-sdk-go/aws" "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/cloudwatchiface" "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/resourcegroupstaggingapi" "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/simplejson" "github.com/grafana/grafana/pkg/models" @@ -179,3 +181,12 @@ func chunkSlice(slice []*cloudwatch.Metric, chunkSize int) [][]*cloudwatch.Metri func newTestConfig() *setting.Cfg { 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 +} diff --git a/pkg/tsdb/cloudwatch/time_series_query_test.go b/pkg/tsdb/cloudwatch/time_series_query_test.go index 2910c314d0b..d44b9bfe24a 100644 --- a/pkg/tsdb/cloudwatch/time_series_query_test.go +++ b/pkg/tsdb/cloudwatch/time_series_query_test.go @@ -9,7 +9,7 @@ import ( ) 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) { timeRange := plugins.NewDataTimeRange("now-1h", "now-2h") diff --git a/yarn.lock b/yarn.lock index b1c03237dc0..cb1b631aedf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3136,6 +3136,18 @@ "@emotion/sheet" "0.9.4" "@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": version "10.0.21" resolved "https://registry.yarnpkg.com/@emotion/core/-/core-10.0.21.tgz#2e8398d2b92fd90d4ed6ac4d0b66214971de3458" @@ -3148,18 +3160,6 @@ "@emotion/sheet" "0.9.3" "@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": version "10.0.14" resolved "https://registry.yarnpkg.com/@emotion/css/-/css-10.0.14.tgz#95dacabdd0e22845d1a1b0b5968d9afa34011139" @@ -3430,33 +3430,33 @@ source-map "~0.6.1" typescript "~3.9.7" -"@grafana/aws-sdk@0.0.2": - version "0.0.2" - resolved "https://registry.yarnpkg.com/@grafana/aws-sdk/-/aws-sdk-0.0.2.tgz#dcaa8672158ce12d5e83b10c6d801b718e03e66a" - integrity sha512-nKkFoNJ76NgGolJArdro39Ho7YA769BO3nq8/NeUHai9MeEVmPviuqC5pnswb08Q4QTEKESi0Zo3x8GvfF6cZg== +"@grafana/aws-sdk@0.0.22": + version "0.0.22" + resolved "https://registry.yarnpkg.com/@grafana/aws-sdk/-/aws-sdk-0.0.22.tgz#58389616beda01ef137804ab6d9ef07d4a4a35b0" + integrity sha512-gTyHmD5VeXjjnnq3zVyI2x3ktJB7dBp92GoJW+lH2t9bWNkcnXyzHSaXZZdrnNpjctaqpv85uxcocK4U7BGCvw== dependencies: - "@grafana/data" "7.5.0-beta.1" - "@grafana/runtime" "7.5.0-beta.1" - "@grafana/ui" "7.5.0-beta.1" + "@grafana/data" "7.4.0" + "@grafana/runtime" "7.4.0" + "@grafana/ui" "7.4.0" -"@grafana/data@7.5.0-beta.1": - version "7.5.0-beta.1" - resolved "https://registry.yarnpkg.com/@grafana/data/-/data-7.5.0-beta.1.tgz#4c6dcd07daee713a06e46d230b9c09877fd5f732" - integrity sha512-hYU3XWVp2rsLfSZVwYFM9wL0e+i1ktlNPzELGL66EcSV+ak41orlcjpwEpke4O7Vtht6t5qSoTycYlmj6+aZEw== +"@grafana/data@7.4.0": + version "7.4.0" + resolved "https://registry.yarnpkg.com/@grafana/data/-/data-7.4.0.tgz#7e7ae998cc4d916ba7d004e27b38eab4b1199ee8" + integrity sha512-YlsA9uu3dO7hnbIUagjhAMXUU7puSSXxc42aOyuj/K9daPVMhOunI7lSM8PSm1uhKVifvd5DJOwlA0UQQRJqSw== dependencies: "@braintree/sanitize-url" "4.0.0" "@types/d3-interpolate" "^1.3.1" apache-arrow "0.16.0" eventemitter3 "4.0.7" - lodash "4.17.21" - marked "2.0.1" + lodash "4.17.20" + marked "1.2.2" rxjs "6.6.3" xss "1.0.6" -"@grafana/e2e-selectors@7.5.0-beta.1": - version "7.5.0-beta.1" - resolved "https://registry.yarnpkg.com/@grafana/e2e-selectors/-/e2e-selectors-7.5.0-beta.1.tgz#063734af7a112033d2e78219969619194fca3526" - integrity sha512-zn23xTrkNtCRSI4wg23CatAq2t4R2JWtqwEKvRfP3qWuJTwql2vu1pLJufJUcTkJtn3ImmwSMGvxZ/wI9SPbqQ== +"@grafana/e2e-selectors@7.4.0": + version "7.4.0" + resolved "https://registry.yarnpkg.com/@grafana/e2e-selectors/-/e2e-selectors-7.4.0.tgz#f286dab307b26e6b0628a1d5e9907e3943c38ed3" + integrity sha512-RCAXLOTUlO8nWALo1Wnl0pTkZYzUGnBV6RuetwEDaRkfZEEKXn3F1OerzF5iJWFsdFJvVehCZ7sFYOPmnEl/gg== dependencies: "@grafana/tsconfig" "^1.0.0-rc1" commander "5.0.0" @@ -3480,13 +3480,13 @@ prettier "2.2.1" typescript "4.1.3" -"@grafana/runtime@7.5.0-beta.1": - version "7.5.0-beta.1" - resolved "https://registry.yarnpkg.com/@grafana/runtime/-/runtime-7.5.0-beta.1.tgz#f0e29a2493d440b7dd6d09def9727c2ce41cb743" - integrity sha512-Cn62/9N1g3TX9vaoGgpD0/bWG2U1nsAoHboLbQnKkztH8tmLoChHqUaZ/5Lsu3s9K5mxI3YA+4PrAxr4NlUPjw== +"@grafana/runtime@7.4.0": + version "7.4.0" + resolved "https://registry.yarnpkg.com/@grafana/runtime/-/runtime-7.4.0.tgz#c097adcdb151efd4211a4fc2bd91598c9cd7e627" + integrity sha512-ug2FPtzlEL4CykmrVp1Cnp6066ndzlMP8x/+rHNK7RR3HbS0ju2c+t6iq0vwU6605dwL71uOC+NOUuNIyZtYyg== dependencies: - "@grafana/data" "7.5.0-beta.1" - "@grafana/ui" "7.5.0-beta.1" + "@grafana/data" "7.4.0" + "@grafana/ui" "7.4.0" systemjs "0.20.19" systemjs-plugin-css "0.1.37" @@ -3517,14 +3517,14 @@ resolved "https://registry.yarnpkg.com/@grafana/tsconfig/-/tsconfig-1.0.0-rc1.tgz#d07ea16755a50cae21000113f30546b61647a200" integrity sha512-nucKPGyzlSKYSiJk5RA8GzMdVWhdYNdF+Hh65AXxjD9PlY69JKr5wANj8bVdQboag6dgg0BFKqgKPyY+YtV4Iw== -"@grafana/ui@7.5.0-beta.1": - version "7.5.0-beta.1" - resolved "https://registry.yarnpkg.com/@grafana/ui/-/ui-7.5.0-beta.1.tgz#0de30ebe9e51df956fa20f1d0a396d286b3f8c5f" - integrity sha512-/+VeGVRjRtd9bOwhzdwWqk8IVrENlGoMWCuRaNQU0+q2tdPQurXYbE3rmxKVtKysPTYNzbhS5Ox5QzYkPkoriA== +"@grafana/ui@7.4.0": + version "7.4.0" + resolved "https://registry.yarnpkg.com/@grafana/ui/-/ui-7.4.0.tgz#4c5eea4c5a00a342fb87bd9a462398e32a72bb84" + integrity sha512-jbpqWGqzdDmU5+lCOHY/DLTlpLD0rNgXJfQLWwPq2ilOdCq0ItIaehxM+e5XqzTy3J+q5nyNQZFH2wM+wqzZRw== dependencies: - "@emotion/core" "10.0.27" - "@grafana/data" "7.5.0-beta.1" - "@grafana/e2e-selectors" "7.5.0-beta.1" + "@emotion/core" "^10.0.27" + "@grafana/data" "7.4.0" + "@grafana/e2e-selectors" "7.4.0" "@grafana/slate-react" "0.22.9-grafana" "@grafana/tsconfig" "^1.0.0-rc1" "@iconscout/react-unicons" "1.1.4" @@ -3539,18 +3539,13 @@ "@types/react-table" "7.0.12" "@types/slate" "0.47.1" "@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" d3 "5.15.0" emotion "10.0.27" hoist-non-react-statics "3.3.2" immutable "3.8.2" jquery "3.5.1" - lodash "4.17.21" + lodash "4.17.20" moment "2.24.0" monaco-editor "0.20.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" 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: version "4.17.21" 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" 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: version "2.0.1" resolved "https://registry.yarnpkg.com/marked/-/marked-2.0.1.tgz#5e7ed7009bfa5c95182e4eb696f85e948cefcee3"