From 3559bc9780ee2c8132011f1ec56ea71e0de5e533 Mon Sep 17 00:00:00 2001 From: Gabe Jackson Date: Mon, 22 Feb 2021 13:58:43 -0500 Subject: [PATCH] Enhance AWS client with a resource cache (#420) The AWS client used throughout the provisioner now contains a resource cache to enhance performance and reduce the number of API calls needed for many tasks. Three AWS resource values are now stored: environment name, private hosted zone ID, and public hosted zone ID. These three values are some of the most commonly used by the AWS client so caching them does the following: - Reduces the number of API calls made when creating or updating CNAME records by roughly 80 percent. - Eliminates an all API calls made when sending out installation and cluster installation webhooks. - Greatly reduces the number of API calls made during cluster provisioning. These three values should always remain constant in a given AWS account so they are safe to cache for the lifetime of the client. This also prepares the provisioner to be able to better support managing multiple AWS accounts in a single server. --- cmd/cloud/server.go | 12 +- internal/mocks/aws-tools/client.go | 36 +--- internal/provisioner/fluentbit.go | 34 ++-- internal/provisioner/fluentbit_test.go | 44 +--- .../provisioner/kops_provisioner_cluster.go | 7 +- internal/provisioner/prometheus_operator.go | 11 +- internal/provisioner/teleport.go | 11 +- internal/provisioner/thanos.go | 9 - internal/supervisor/cluster.go | 8 +- internal/supervisor/cluster_installation.go | 8 +- internal/supervisor/installation.go | 46 +---- internal/supervisor/installation_test.go | 13 +- internal/tools/aws/client.go | 100 +++++++-- internal/tools/aws/client_test.go | 12 +- internal/tools/aws/helpers.go | 7 +- internal/tools/aws/route53.go | 152 ++++++-------- internal/tools/aws/route53_test.go | 192 ++---------------- 17 files changed, 232 insertions(+), 470 deletions(-) diff --git a/cmd/cloud/server.go b/cmd/cloud/server.go index 5c8483dc4..e9990869e 100644 --- a/cmd/cloud/server.go +++ b/cmd/cloud/server.go @@ -208,14 +208,12 @@ var serverCmd = &cobra.Command{ // https://github.com/aws/aws-sdk-go/blob/99cd35c8c7d369ba8c32c46ed306f6c88d24cfd7/aws/request/retryer.go#L20 MaxRetries: sdkAWS.Int(toolsAWS.DefaultAWSClientRetries), } - awsClient := toolsAWS.NewAWSClientWithConfig(awsConfig, logger) - - environment, err := awsClient.GetCloudEnvironmentName() + awsClient, err := toolsAWS.NewAWSClientWithConfig(awsConfig, logger) if err != nil { - return errors.Wrap(err, "failed to get the AWS Cloud environment name") + return errors.Wrap(err, "failed to build AWS client") } - err = checkRequirements(awsConfig, logger) + err = checkRequirements(logger) if err != nil { return errors.Wrap(err, "failed health check") } @@ -269,7 +267,7 @@ var serverCmd = &cobra.Command{ Store: sqlStore, Supervisor: supervisor, Provisioner: kopsProvisioner, - Environment: environment, + Environment: awsClient.GetCloudEnvironmentName(), Logger: logger, }) @@ -316,7 +314,7 @@ var serverCmd = &cobra.Command{ }, } -func checkRequirements(awsConfig *sdkAWS.Config, logger logrus.FieldLogger) error { +func checkRequirements(logger logrus.FieldLogger) error { // Check for required tool binaries. silentLogger := logrus.New() silentLogger.Out = ioutil.Discard diff --git a/internal/mocks/aws-tools/client.go b/internal/mocks/aws-tools/client.go index 292564286..ce089476e 100644 --- a/internal/mocks/aws-tools/client.go +++ b/internal/mocks/aws-tools/client.go @@ -9,7 +9,6 @@ package mocks import ( acm "github.com/aws/aws-sdk-go/service/acm" - iam "github.com/aws/aws-sdk-go/service/iam" gomock "github.com/golang/mock/gomock" aws "github.com/mattermost/mattermost-cloud/internal/tools/aws" logrus "github.com/sirupsen/logrus" @@ -55,28 +54,12 @@ func (mr *MockAWSMockRecorder) GetCertificateSummaryByTag(key, value, logger int return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCertificateSummaryByTag", reflect.TypeOf((*MockAWS)(nil).GetCertificateSummaryByTag), key, value, logger) } -// GetAccountAliases mocks base method -func (m *MockAWS) GetAccountAliases() (*iam.ListAccountAliasesOutput, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetAccountAliases") - ret0, _ := ret[0].(*iam.ListAccountAliasesOutput) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetAccountAliases indicates an expected call of GetAccountAliases -func (mr *MockAWSMockRecorder) GetAccountAliases() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAccountAliases", reflect.TypeOf((*MockAWS)(nil).GetAccountAliases)) -} - // GetCloudEnvironmentName mocks base method -func (m *MockAWS) GetCloudEnvironmentName() (string, error) { +func (m *MockAWS) GetCloudEnvironmentName() string { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetCloudEnvironmentName") ret0, _ := ret[0].(string) - ret1, _ := ret[1].(error) - return ret0, ret1 + return ret0 } // GetCloudEnvironmentName indicates an expected call of GetCloudEnvironmentName @@ -172,19 +155,18 @@ func (mr *MockAWSMockRecorder) GetPrivateZoneDomainName(logger interface{}) *gom return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPrivateZoneDomainName", reflect.TypeOf((*MockAWS)(nil).GetPrivateZoneDomainName), logger) } -// GetPrivateZoneIDForDefaultTag mocks base method -func (m *MockAWS) GetPrivateZoneIDForDefaultTag(logger logrus.FieldLogger) (string, error) { +// GetPrivateHostedZoneID mocks base method +func (m *MockAWS) GetPrivateHostedZoneID() string { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetPrivateZoneIDForDefaultTag", logger) + ret := m.ctrl.Call(m, "GetPrivateHostedZoneID") ret0, _ := ret[0].(string) - ret1, _ := ret[1].(error) - return ret0, ret1 + return ret0 } -// GetPrivateZoneIDForDefaultTag indicates an expected call of GetPrivateZoneIDForDefaultTag -func (mr *MockAWSMockRecorder) GetPrivateZoneIDForDefaultTag(logger interface{}) *gomock.Call { +// GetPrivateHostedZoneID indicates an expected call of GetPrivateHostedZoneID +func (mr *MockAWSMockRecorder) GetPrivateHostedZoneID() *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPrivateZoneIDForDefaultTag", reflect.TypeOf((*MockAWS)(nil).GetPrivateZoneIDForDefaultTag), logger) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPrivateHostedZoneID", reflect.TypeOf((*MockAWS)(nil).GetPrivateHostedZoneID)) } // GetTagByKeyAndZoneID mocks base method diff --git a/internal/provisioner/fluentbit.go b/internal/provisioner/fluentbit.go index c6a0896a1..fe163812b 100644 --- a/internal/provisioner/fluentbit.go +++ b/internal/provisioner/fluentbit.go @@ -96,32 +96,28 @@ func (f *fluentbit) NewHelmDeployment(logger log.FieldLogger) *helmDeployment { } var auditLogsConf string - zoneID, err := f.awsClient.GetPrivateZoneIDForDefaultTag(logger) + + tag, err := f.awsClient.GetTagByKeyAndZoneID(aws.DefaultAuditLogsCoreSecurityTagKey, f.awsClient.GetPrivateHostedZoneID(), logger) if err != nil { - logger.WithError(err).Error("unable to get Private Zone ID with the default tag, skipping setup...") - } else { - tag, err := f.awsClient.GetTagByKeyAndZoneID(aws.DefaultAuditLogsCoreSecurityTagKey, zoneID, logger) - if err != nil { - logger.WithError(err).Errorf("unable to find %s", aws.DefaultAuditLogsCoreSecurityTagKey) - } - if tag == nil { - logger.Infof("%s is missing, skipping setup...", aws.DefaultAuditLogsCoreSecurityTagKey) - tag = &aws.Tag{} - } - - hostPort := strings.Split(tag.Value, ":") - if len(hostPort) == 2 { - auditLogsConf = fmt.Sprintf(`[OUTPUT] + logger.WithError(err).Errorf("unable to find %s", aws.DefaultAuditLogsCoreSecurityTagKey) + } + if tag == nil { + logger.Infof("%s is missing, skipping setup...", aws.DefaultAuditLogsCoreSecurityTagKey) + tag = &aws.Tag{} + } + + hostPort := strings.Split(tag.Value, ":") + if len(hostPort) == 2 { + auditLogsConf = fmt.Sprintf(`[OUTPUT] Name forward Match * Host %s Port %s tls On tls.verify Off`, hostPort[0], hostPort[1]) - } else { - logger.Info("AuditLogsCoreSecurity tag is missing from R53 hosted zone, " + - "fluent-bit will be configured without forwarding to audit logs to Security") - } + } else { + logger.Info("AuditLogsCoreSecurity tag is missing from R53 hosted zone, " + + "fluent-bit will be configured without forwarding to audit logs to Security") } elasticSearchDNS := fmt.Sprintf("elasticsearch.%s", privateDomainName) diff --git a/internal/provisioner/fluentbit_test.go b/internal/provisioner/fluentbit_test.go index 7445bef77..968d91fe3 100644 --- a/internal/provisioner/fluentbit_test.go +++ b/internal/provisioner/fluentbit_test.go @@ -31,8 +31,8 @@ func TestNewHelmDeploymentWithAuditLogsConfiguration(t *testing.T) { Return("mockDns", nil). AnyTimes() awsClient.EXPECT(). - GetPrivateZoneIDForDefaultTag(gomock.Eq(logger)). - Return("mockZone", nil). + GetPrivateHostedZoneID(). + Return("mockZone"). AnyTimes() expectedTag := &aws.Tag{Key: "AuditLogsCoreSecurity", Value: "expectedURL:12345"} awsClient.EXPECT(). @@ -62,8 +62,8 @@ func TestNewHelmDeploymentWithDefaultConfiguration(t *testing.T) { Return("mockDns", nil). AnyTimes() awsClient.EXPECT(). - GetPrivateZoneIDForDefaultTag(gomock.Eq(logger)). - Return("mockZone", nil). + GetPrivateHostedZoneID(). + Return("mockZone"). AnyTimes() expectedTag := &aws.Tag{Key: "MattermostCloudDNS", Value: "private"} awsClient.EXPECT(). @@ -81,34 +81,6 @@ func TestNewHelmDeploymentWithDefaultConfiguration(t *testing.T) { assert.Equal(t, "backend.es.host=elasticsearch.mockDns,rawConfig=\n@INCLUDE fluent-bit-service.conf\n@INCLUDE fluent-bit-input.conf\n@INCLUDE fluent-bit-filter.conf\n@INCLUDE fluent-bit-output.conf\n\n", helmDeployment.setArgument) } -func TestNewHelmDeploymentWithZoneIDError(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - provisioner := &KopsProvisioner{} - logger := log.New() - awsClient := mocks.NewMockAWS(ctrl) - - awsClient.EXPECT(). - GetPrivateZoneDomainName(gomock.Eq(logger)). - Return("mockDns", nil). - AnyTimes() - err1 := errors.New("Mock error expected from func GetPrivateZoneIDForDefaultTag") - awsClient.EXPECT(). - GetPrivateZoneIDForDefaultTag(gomock.Eq(logger)). - Return("", err1). - AnyTimes() - - kops := &kops.Cmd{} - fluentbit, err := newFluentbitHandle(&model.HelmUtilityVersion{Chart: "1.2.3"}, provisioner, awsClient, kops, logger) - require.NoError(t, err, "should not error when creating new fluentbit handler") - require.NotNil(t, fluentbit, "fluentbit should not be nil") - - helmDeployment := fluentbit.NewHelmDeployment(logger) - require.NotNil(t, helmDeployment, "helmDeployment should not be nil") - assert.Equal(t, "backend.es.host=elasticsearch.mockDns,rawConfig=\n@INCLUDE fluent-bit-service.conf\n@INCLUDE fluent-bit-input.conf\n@INCLUDE fluent-bit-filter.conf\n@INCLUDE fluent-bit-output.conf\n\n", helmDeployment.setArgument) -} - func TestNewHelmDeploymentWithoutFindingAuditTag(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() @@ -121,8 +93,8 @@ func TestNewHelmDeploymentWithoutFindingAuditTag(t *testing.T) { Return("mockDns", nil). AnyTimes() awsClient.EXPECT(). - GetPrivateZoneIDForDefaultTag(gomock.Eq(logger)). - Return("mockZone", nil). + GetPrivateHostedZoneID(). + Return("mockZone"). AnyTimes() expectedTag := &aws.Tag{} err1 := errors.New("Mock error expected from func GetTagByKeyAndZoneID") @@ -153,8 +125,8 @@ func TestNewHelmDeploymentWithNillTag(t *testing.T) { Return("mockDns", nil). AnyTimes() awsClient.EXPECT(). - GetPrivateZoneIDForDefaultTag(gomock.Eq(logger)). - Return("mockZone", nil). + GetPrivateHostedZoneID(). + Return("mockZone"). AnyTimes() awsClient.EXPECT(). diff --git a/internal/provisioner/kops_provisioner_cluster.go b/internal/provisioner/kops_provisioner_cluster.go index da796ed96..0114932c8 100644 --- a/internal/provisioner/kops_provisioner_cluster.go +++ b/internal/provisioner/kops_provisioner_cluster.go @@ -62,12 +62,7 @@ func (provisioner *KopsProvisioner) CreateCluster(cluster *model.Cluster, awsCli } } - environment, err := awsClient.GetCloudEnvironmentName() - if err != nil { - return errors.Wrap(err, "getting the AWS Cloud environment") - } - - cncVPCName := fmt.Sprintf("mattermost-cloud-%s-command-control", environment) + cncVPCName := fmt.Sprintf("mattermost-cloud-%s-command-control", awsClient.GetCloudEnvironmentName()) cncVPCCIDR, err := awsClient.GetCIDRByVPCTag(cncVPCName, logger) if err != nil { return errors.Wrapf(err, "failed to get the CIDR for the VPC Name %s", cncVPCName) diff --git a/internal/provisioner/prometheus_operator.go b/internal/provisioner/prometheus_operator.go index 68fe81a99..cb77bbb54 100644 --- a/internal/provisioner/prometheus_operator.go +++ b/internal/provisioner/prometheus_operator.go @@ -68,15 +68,6 @@ func newPrometheusOperatorHandle(cluster *model.Cluster, provisioner *KopsProvis func (p *prometheusOperator) CreateOrUpgrade() error { logger := p.logger.WithField("prometheus-action", "create") - environment, err := p.awsClient.GetCloudEnvironmentName() - if err != nil { - return errors.Wrap(err, "failed to get environment name for thanos objstore secret") - } - - if environment == "" { - return errors.New("cannot create a thanos objstore secret if environment is empty") - } - awsRegion := os.Getenv("AWS_REGION") if awsRegion == "" { awsRegion = aws.DefaultAWSRegion @@ -85,7 +76,7 @@ func (p *prometheusOperator) CreateOrUpgrade() error { secretData := map[string]interface{}{ "type": "s3", "config": map[string]string{ - "bucket": fmt.Sprintf("cloud-%s-prometheus-metrics", environment), + "bucket": fmt.Sprintf("cloud-%s-prometheus-metrics", p.awsClient.GetCloudEnvironmentName()), "endpoint": fmt.Sprintf("s3.%s.amazonaws.com", awsRegion), }, } diff --git a/internal/provisioner/teleport.go b/internal/provisioner/teleport.go index db677da90..ca81fe08b 100644 --- a/internal/provisioner/teleport.go +++ b/internal/provisioner/teleport.go @@ -40,18 +40,9 @@ func newTeleportHandle(cluster *model.Cluster, desiredVersion *model.HelmUtility return nil, errors.New("cannot create a connection to Teleport if the Kops command provided is nil") } - environment, err := awsClient.GetCloudEnvironmentName() - if err != nil { - return nil, err - } - - if environment == "" { - return nil, errors.New("cannot create a connection to Teleport if the environment is empty") - } - return &teleport{ awsClient: awsClient, - environment: environment, + environment: awsClient.GetCloudEnvironmentName(), provisioner: provisioner, kops: kops, cluster: cluster, diff --git a/internal/provisioner/thanos.go b/internal/provisioner/thanos.go index c7c27b318..0af7a6837 100644 --- a/internal/provisioner/thanos.go +++ b/internal/provisioner/thanos.go @@ -71,15 +71,6 @@ func (t *thanos) ValuesPath() string { func (t *thanos) CreateOrUpgrade() error { logger := t.logger.WithField("thanos-action", "create") - environment, err := t.awsClient.GetCloudEnvironmentName() - if err != nil { - return errors.Wrap(err, "failed to get environment name for thanos objstore secret") - } - - if environment == "" { - return errors.New("cannot create a thanos objstore secret if environment is empty") - } - awsRegion := os.Getenv("AWS_REGION") if awsRegion == "" { awsRegion = aws.DefaultAWSRegion diff --git a/internal/supervisor/cluster.go b/internal/supervisor/cluster.go index fcb661211..2b8ea96d0 100644 --- a/internal/supervisor/cluster.go +++ b/internal/supervisor/cluster.go @@ -129,19 +129,13 @@ func (s *ClusterSupervisor) Supervise(cluster *model.Cluster) { return } - environment, err := s.aws.GetCloudEnvironmentName() - if err != nil { - logger.WithError(err).Error("getting the AWS Cloud environment") - return - } - webhookPayload := &model.WebhookPayload{ Type: model.TypeCluster, ID: cluster.ID, NewState: newState, OldState: oldState, Timestamp: time.Now().UnixNano(), - ExtraData: map[string]string{"Environment": environment}, + ExtraData: map[string]string{"Environment": s.aws.GetCloudEnvironmentName()}, } err = webhook.SendToAllWebhooks(s.store, webhookPayload, logger.WithField("webhookEvent", webhookPayload.NewState)) if err != nil { diff --git a/internal/supervisor/cluster_installation.go b/internal/supervisor/cluster_installation.go index 968baa93c..5b1d0eec0 100644 --- a/internal/supervisor/cluster_installation.go +++ b/internal/supervisor/cluster_installation.go @@ -129,19 +129,13 @@ func (s *ClusterInstallationSupervisor) Supervise(clusterInstallation *model.Clu return } - environment, err := s.aws.GetCloudEnvironmentName() - if err != nil { - logger.WithError(err).Error("getting the AWS Cloud environment") - return - } - webhookPayload := &model.WebhookPayload{ Type: model.TypeClusterInstallation, ID: clusterInstallation.ID, NewState: newState, OldState: oldState, Timestamp: time.Now().UnixNano(), - ExtraData: map[string]string{"ClusterID": clusterInstallation.ClusterID, "Environment": environment}, + ExtraData: map[string]string{"ClusterID": clusterInstallation.ClusterID, "Environment": s.aws.GetCloudEnvironmentName()}, } err = webhook.SendToAllWebhooks(s.store, webhookPayload, logger.WithField("webhookEvent", webhookPayload.NewState)) if err != nil { diff --git a/internal/supervisor/installation.go b/internal/supervisor/installation.go index 167fab846..9f050e9b5 100644 --- a/internal/supervisor/installation.go +++ b/internal/supervisor/installation.go @@ -211,19 +211,13 @@ func (s *InstallationSupervisor) Supervise(installation *model.Installation) { return } - environment, err := s.aws.GetCloudEnvironmentName() - if err != nil { - logger.WithError(err).Error("getting the AWS Cloud environment") - return - } - webhookPayload := &model.WebhookPayload{ Type: model.TypeInstallation, ID: installation.ID, NewState: installation.State, OldState: oldState, Timestamp: time.Now().UnixNano(), - ExtraData: map[string]string{"DNS": installation.DNS, "Environment": environment}, + ExtraData: map[string]string{"DNS": installation.DNS, "Environment": s.aws.GetCloudEnvironmentName()}, } err = webhook.SendToAllWebhooks(s.store, webhookPayload, logger.WithField("webhookEvent", webhookPayload.NewState)) if err != nil { @@ -482,19 +476,13 @@ func (s *InstallationSupervisor) createClusterInstallation(cluster *model.Cluste return nil } - environment, err := s.aws.GetCloudEnvironmentName() - if err != nil { - logger.WithError(err).Error("getting the AWS Cloud environment") - return nil - } - webhookPayload := &model.WebhookPayload{ Type: model.TypeCluster, ID: cluster.ID, NewState: model.ClusterStateResizeRequested, OldState: model.ClusterStateStable, Timestamp: time.Now().UnixNano(), - ExtraData: map[string]string{"Environment": environment}, + ExtraData: map[string]string{"Environment": s.aws.GetCloudEnvironmentName()}, } err = webhook.SendToAllWebhooks(s.store, webhookPayload, logger.WithField("webhookEvent", webhookPayload.NewState)) @@ -518,19 +506,13 @@ func (s *InstallationSupervisor) createClusterInstallation(cluster *model.Cluste return nil } - environment, err := s.aws.GetCloudEnvironmentName() - if err != nil { - logger.WithError(err).Error("getting the AWS Cloud environment") - return nil - } - webhookPayload := &model.WebhookPayload{ Type: model.TypeClusterInstallation, ID: clusterInstallation.ID, NewState: model.ClusterInstallationStateCreationRequested, OldState: "n/a", Timestamp: time.Now().UnixNano(), - ExtraData: map[string]string{"Environment": environment}, + ExtraData: map[string]string{"Environment": s.aws.GetCloudEnvironmentName()}, } err = webhook.SendToAllWebhooks(s.store, webhookPayload, logger.WithField("webhookEvent", webhookPayload.NewState)) if err != nil { @@ -674,12 +656,6 @@ func (s *InstallationSupervisor) configureInstallationDNS(installation *model.In } func (s *InstallationSupervisor) updateInstallation(installation *model.Installation, instanceID string, logger log.FieldLogger) string { - err := s.aws.UpdatePublicRecordIDForCNAME(installation.DNS, installation.DNS, logger) - if err != nil { - logger.WithError(err).Warn("Failed to update the installation route53 record to the standard ID value") - return installation.State - } - // Before starting, we check the installation and group sequence numbers and // sync them if they are not already. This is used to check if the group // configuration has changed during the upgrade process or not. @@ -688,7 +664,7 @@ func (s *InstallationSupervisor) updateInstallation(installation *model.Installa logger.Debugf("Updating installation to group configuration sequence %d", *installation.GroupSequence) - err = s.store.UpdateInstallationGroupSequence(installation) + err := s.store.UpdateInstallationGroupSequence(installation) if err != nil { logger.WithError(err).Errorf("Failed to set installation sequence to %d", *installation.GroupSequence) return installation.State @@ -759,19 +735,13 @@ func (s *InstallationSupervisor) updateInstallation(installation *model.Installa return installation.State } - environment, err := s.aws.GetCloudEnvironmentName() - if err != nil { - logger.WithError(err).Error("getting the AWS Cloud environment") - return installation.State - } - webhookPayload := &model.WebhookPayload{ Type: model.TypeClusterInstallation, ID: clusterInstallation.ID, NewState: clusterInstallation.State, OldState: oldState, Timestamp: time.Now().UnixNano(), - ExtraData: map[string]string{"Environment": environment}, + ExtraData: map[string]string{"Environment": s.aws.GetCloudEnvironmentName()}, } err = webhook.SendToAllWebhooks(s.store, webhookPayload, logger.WithField("webhookEvent", webhookPayload.NewState)) if err != nil { @@ -802,6 +772,12 @@ func (s *InstallationSupervisor) waitForUpdateStable(installation *model.Install return model.InstallationStateUpdateInProgress } + err = s.aws.UpdatePublicRecordIDForCNAME(installation.DNS, installation.DNS, logger) + if err != nil { + logger.WithError(err).Warn("Failed to update the installation route53 record to the standard ID value") + return installation.State + } + logger.Info("Finished updating installation") return model.InstallationStateStable diff --git a/internal/supervisor/installation_test.go b/internal/supervisor/installation_test.go index 15f081a25..169add9ce 100644 --- a/internal/supervisor/installation_test.go +++ b/internal/supervisor/installation_test.go @@ -11,7 +11,6 @@ import ( "github.com/mattermost/mattermost-cloud/internal/provisioner" "github.com/aws/aws-sdk-go/service/acm" - "github.com/aws/aws-sdk-go/service/iam" "github.com/mattermost/mattermost-cloud/internal/metrics" "github.com/mattermost/mattermost-cloud/internal/store" "github.com/mattermost/mattermost-cloud/internal/supervisor" @@ -234,12 +233,8 @@ func (a *mockAWS) GetCertificateSummaryByTag(key, value string, logger log.Field return nil, nil } -func (a *mockAWS) GetAccountAliases() (*iam.ListAccountAliasesOutput, error) { - return nil, nil -} - -func (a *mockAWS) GetCloudEnvironmentName() (string, error) { - return "test", nil +func (a *mockAWS) GetCloudEnvironmentName() string { + return "test" } func (a *mockAWS) DynamoDBEnsureTableDeleted(tableName string, logger log.FieldLogger) error { @@ -280,8 +275,8 @@ func (a *mockAWS) GetTagByKeyAndZoneID(key string, id string, logger log.FieldLo Value: "examplevalue", }, nil } -func (a *mockAWS) GetPrivateZoneIDForDefaultTag(logger log.FieldLogger) (string, error) { - return "EXAMPLER53ID", nil +func (a *mockAWS) GetPrivateHostedZoneID() string { + return "EXAMPLER53ID" } func (a *mockAWS) CreatePrivateCNAME(dnsName string, dnsEndpoints []string, logger log.FieldLogger) error { diff --git a/internal/tools/aws/client.go b/internal/tools/aws/client.go index ee9961cc6..f8cfa8416 100644 --- a/internal/tools/aws/client.go +++ b/internal/tools/aws/client.go @@ -45,8 +45,7 @@ import ( type AWS interface { GetCertificateSummaryByTag(key, value string, logger log.FieldLogger) (*acm.CertificateSummary, error) - GetAccountAliases() (*iam.ListAccountAliasesOutput, error) - GetCloudEnvironmentName() (string, error) + GetCloudEnvironmentName() string GetAndClaimVpcResources(clusterID, owner string, logger log.FieldLogger) (ClusterResources, error) GetVpcResources(clusterID string, logger log.FieldLogger) (ClusterResources, error) @@ -55,7 +54,7 @@ type AWS interface { DetachPolicyFromRole(roleName, policyName string, logger log.FieldLogger) error GetPrivateZoneDomainName(logger log.FieldLogger) (string, error) - GetPrivateZoneIDForDefaultTag(logger log.FieldLogger) (string, error) + GetPrivateHostedZoneID() string GetTagByKeyAndZoneID(key string, id string, logger log.FieldLogger) (*Tag, error) CreatePrivateCNAME(dnsName string, dnsEndpoints []string, logger log.FieldLogger) error @@ -76,13 +75,39 @@ type AWS interface { GetCIDRByVPCTag(vpcTagName string, logger log.FieldLogger) (string, error) } +// Client is a client for interacting with AWS resources in a single AWS account. +type Client struct { + store model.InstallationDatabaseStoreInterface + logger log.FieldLogger + cache *cache + service *Service + config *aws.Config + mux *sync.Mutex +} + // NewAWSClientWithConfig returns a new instance of Client with a custom configuration. -func NewAWSClientWithConfig(config *aws.Config, logger log.FieldLogger) *Client { - return &Client{ +func NewAWSClientWithConfig(config *aws.Config, logger log.FieldLogger) (*Client, error) { + client := &Client{ logger: logger, config: config, mux: &sync.Mutex{}, + cache: &cache{}, } + err := client.buildCache() + if err != nil { + return nil, errors.Wrap(err, "failed to build AWS client cache") + } + err = client.validateCache() + if err != nil { + return nil, errors.Wrap(err, "invalid client cache") + } + + return client, nil +} + +type cache struct { + environment string + route53 *route53Cache } // Service hold AWS clients for each service. @@ -119,15 +144,6 @@ func NewService(sess *session.Session) *Service { } } -// Client is a client for interacting with AWS resources. -type Client struct { - store model.InstallationDatabaseStoreInterface - logger log.FieldLogger - service *Service - config *aws.Config - mux *sync.Mutex -} - // Service contructs an AWS session if not yet successfully done and returns AWS clients. func (c *Client) Service() *Service { if c.service == nil { @@ -146,6 +162,43 @@ func (c *Client) Service() *Service { return c.service } +func (c *Client) buildCache() error { + err := c.buildCloudEnvironmentNameCache() + if err != nil { + return errors.Wrap(err, "failed to lookup AWS environment value") + } + + err = c.buildRoute53Cache() + if err != nil { + return errors.Wrap(err, "failed to build route53 cache") + } + + c.logger.WithFields(log.Fields{ + "environment": c.cache.environment, + "private-hosted-zone-id": c.cache.route53.privateHostedZoneID, + "public-hosted-zone-id": c.cache.route53.publicHostedZoneID, + }).Info("AWS client cache initialized") + + return nil +} + +func (c *Client) validateCache() error { + if c.cache == nil || c.cache.route53 == nil { + return errors.New("cache has not been properly initialized") + } + if len(c.cache.environment) == 0 { + return errors.New("environment cache value is empty") + } + if len(c.cache.route53.privateHostedZoneID) == 0 { + return errors.New("private hosted zone ID cache value is empty") + } + if len(c.cache.route53.publicHostedZoneID) == 0 { + return errors.New("public hosted zone ID cache value is empty") + } + + return nil +} + // AddSQLStore adds SQLStore functionality to the AWS client. func (c *Client) AddSQLStore(store model.InstallationDatabaseStoreInterface) { if !c.HasSQLStore() { @@ -162,25 +215,32 @@ func (c *Client) HasSQLStore() bool { // GetCloudEnvironmentName looks for a standard cloud account environment name // and returns it. -func (c *Client) GetCloudEnvironmentName() (string, error) { +func (c *Client) GetCloudEnvironmentName() string { + return c.cache.environment +} + +// buildCloudEnvironmentNameCache looks for a standard cloud account environment +// name and chacnes it in the AWS client. +func (c *Client) buildCloudEnvironmentNameCache() error { accountAliases, err := c.GetAccountAliases() if err != nil { - return "", errors.Wrap(err, "failed to get account aliases") + return errors.Wrap(err, "failed to get account aliases") } if len(accountAliases.AccountAliases) < 1 { - return "", errors.New("account alias not defined") + return errors.New("account alias not defined") } for _, alias := range accountAliases.AccountAliases { if strings.HasPrefix(*alias, "mattermost-cloud") && len(strings.Split(*alias, "-")) == 3 { envName := strings.Split(*alias, "-")[2] if len(envName) == 0 { - return "", errors.New("environment name value was empty") + return errors.New("environment name value was empty") } - return envName, nil + c.cache.environment = envName + return nil } } - return "", errors.New("account environment name could not be found from account aliases") + return errors.New("account environment name could not be found from account aliases") } diff --git a/internal/tools/aws/client_test.go b/internal/tools/aws/client_test.go index 9f33fb0c6..27b314591 100644 --- a/internal/tools/aws/client_test.go +++ b/internal/tools/aws/client_test.go @@ -145,7 +145,6 @@ func NewAWSTestSuite(t *testing.T) *AWSTestSuite { SecretStringPassErr: `{"MasterUsername":"mmcloud","password":"oX5rWueZt6ynsijE9PHpUO0VUWSwWSxqXCaZw1dC"}`, RDSEncryptionKeyID: "rds-encryption-key-id-123", ResourceID: "WSxqXCaZw1dC", - HostedZoneID: "ZWI3O6O6N782C", CertifcateARN: "arn:aws:certificate::123456789012", ResourceARN: "arn:aws:kms:us-east-1:526412419611:key/10cbe864-7411-4cda-bd28-3355218d0995", RDSResourceARN: "arn:aws:rds:us-east-1:926412419614:cluster:rds-cluster-multitenant-09d44077df9934f96-97670d43", @@ -181,6 +180,7 @@ func (a *AWSTestSuite) SetupTest() { kms: api.KMS, sts: api.STS, }, + cache: newClientDummyCache(), config: &aws.Config{}, mux: &sync.Mutex{}, }, @@ -196,3 +196,13 @@ func (a *AWSTestSuite) TearDown() { func TestAWSSuite(t *testing.T) { suite.Run(t, NewAWSTestSuite(t)) } + +func newClientDummyCache() *cache { + return &cache{ + environment: "dev", + route53: &route53Cache{ + privateHostedZoneID: "HZONE1", + publicHostedZoneID: "HZONE2", + }, + } +} diff --git a/internal/tools/aws/helpers.go b/internal/tools/aws/helpers.go index aa6dc6169..3f73cba27 100644 --- a/internal/tools/aws/helpers.go +++ b/internal/tools/aws/helpers.go @@ -185,12 +185,7 @@ func getMultitenantBucketNameForCluster(clusterID string, client *Client) (strin } func getMultitenantBucketNameForVPC(vpcID string, client *Client) (string, error) { - envName, err := client.GetCloudEnvironmentName() - if err != nil { - return "", errors.Wrap(err, "failed to get cloud environment name") - } - - bucketName := MattermostMultitenantS3Name(envName, vpcID) + bucketName := MattermostMultitenantS3Name(client.GetCloudEnvironmentName(), vpcID) tags, err := client.Service().s3.GetBucketTagging(&s3.GetBucketTaggingInput{ Bucket: aws.String(bucketName), diff --git a/internal/tools/aws/route53.go b/internal/tools/aws/route53.go index 3cc1587e4..57b64d921 100644 --- a/internal/tools/aws/route53.go +++ b/internal/tools/aws/route53.go @@ -23,52 +23,83 @@ const ( hostedZonePrefix = "/hostedzone/" ) -// CreatePublicCNAME creates a record in Route53 for a public domain name. -func (a *Client) CreatePublicCNAME(dnsName string, dnsEndpoints []string, logger log.FieldLogger) error { - id, err := a.getHostedZoneIDWithTag(Tag{ +type route53Cache struct { + privateHostedZoneID string + publicHostedZoneID string +} + +func (a *Client) buildRoute53Cache() error { + privateID, err := a.getHostedZoneIDWithTag(Tag{ + Key: DefaultCloudDNSTagKey, + Value: DefaultPrivateCloudDNSTagValue, + }, a.logger) + if err != nil { + return errors.Wrap(err, "failed to get private hosted zone ID") + } + + publicID, err := a.getHostedZoneIDWithTag(Tag{ Key: DefaultCloudDNSTagKey, Value: DefaultPublicCloudDNSTagValue, - }, logger) + }, a.logger) if err != nil { - return errors.Wrapf(err, "unable to create a public CNAME: %s", dnsName) + return errors.Wrap(err, "failed to get public hosted zone ID") + } + + a.cache.route53 = &route53Cache{ + privateHostedZoneID: privateID, + publicHostedZoneID: publicID, } - return a.createCNAME(id, dnsName, dnsEndpoints, logger) + return nil +} + +// GetPublicHostedZoneID returns the public R53 hosted zone ID for the AWS +// account. +func (a *Client) GetPublicHostedZoneID() string { + return a.cache.route53.publicHostedZoneID +} + +// CreatePublicCNAME creates a record in Route53 for a public domain name. +func (a *Client) CreatePublicCNAME(dnsName string, dnsEndpoints []string, logger log.FieldLogger) error { + return a.createCNAME(a.GetPublicHostedZoneID(), dnsName, dnsEndpoints, logger) +} + +// UpdatePublicRecordIDForCNAME updates the record ID for the record corresponding +// to a DNS value in the public hosted zone. +func (a *Client) UpdatePublicRecordIDForCNAME(dnsName, newID string, logger log.FieldLogger) error { + return a.updateResourceRecordIDs(a.GetPublicHostedZoneID(), dnsName, newID, logger) +} + +// DeletePublicCNAME deletes a AWS route53 record for a public domain name. +func (a *Client) DeletePublicCNAME(dnsName string, logger log.FieldLogger) error { + return a.deleteCNAME(a.GetPublicHostedZoneID(), dnsName, logger) +} + +// GetPrivateHostedZoneID returns the private R53 hosted zone ID for the AWS +// account. +func (a *Client) GetPrivateHostedZoneID() string { + return a.cache.route53.privateHostedZoneID } // CreatePrivateCNAME creates a record in Route53 for a private domain name. func (a *Client) CreatePrivateCNAME(dnsName string, dnsEndpoints []string, logger log.FieldLogger) error { - id, err := a.getHostedZoneIDWithTag(Tag{ - Key: DefaultCloudDNSTagKey, - Value: DefaultPrivateCloudDNSTagValue, - }, logger) - if err != nil { - return errors.Wrapf(err, "unable to create a private CNAME: %s", dnsName) - } + return a.createCNAME(a.GetPrivateHostedZoneID(), dnsName, dnsEndpoints, logger) +} - return a.createCNAME(id, dnsName, dnsEndpoints, logger) +// IsProvisionedPrivateCNAME returns true if a record has been registered in the +// private hosted zone for the given CNAME (full FQDN required as input) +func (a *Client) IsProvisionedPrivateCNAME(dnsName string, logger log.FieldLogger) bool { + return a.isProvisionedCNAME(a.GetPrivateHostedZoneID(), dnsName, logger) } // GetPrivateZoneDomainName gets the private Route53 domain name. func (a *Client) GetPrivateZoneDomainName(logger log.FieldLogger) (string, error) { - id, err := a.GetPrivateZoneIDForDefaultTag(logger) - if err != nil { - return "", err - } - return a.getZoneDNS(id, logger) + return a.getZoneDNS(a.GetPrivateHostedZoneID(), logger) } -// GetPrivateZoneIDForDefaultTag returns the Private R53 hosted zone ID for the default tag `MattermostCloudDNS` -func (a *Client) GetPrivateZoneIDForDefaultTag(logger log.FieldLogger) (string, error) { - tag := Tag{ - Key: DefaultCloudDNSTagKey, - Value: DefaultPrivateCloudDNSTagValue, - } - id, err := a.getHostedZoneIDWithTag(tag, logger) - if err != nil { - return "", errors.Wrap(err, "unable to get private domain name") - } - return id, nil +// DeletePrivateCNAME deletes an AWS route53 record for a private domain name. +func (a *Client) DeletePrivateCNAME(dnsName string, logger log.FieldLogger) error { + return a.deleteCNAME(a.GetPrivateHostedZoneID(), dnsName, logger) } // GetTagByKeyAndZoneID returns a Tag of a given tag:key and of a given route53 id @@ -169,61 +200,6 @@ func (a *Client) createCNAME(hostedZoneID, dnsName string, dnsEndpoints []string return nil } -// IsProvisionedPrivateCNAME returns true if a record has been -// registered for the given CNAME (full FQDN required as input) -func (a *Client) IsProvisionedPrivateCNAME(dnsName string, logger log.FieldLogger) bool { - id, err := a.getHostedZoneIDWithTag(Tag{ - Key: DefaultCloudDNSTagKey, - Value: DefaultPrivateCloudDNSTagValue, - }, logger) - if err != nil { - logger.WithError(err).Debugf("couldn't look up zone ID for DNS name %s", dnsName) - return false - } - - return a.isProvisionedCNAME(id, dnsName, logger) -} - -// UpdatePublicRecordIDForCNAME updates the record ID for the record corresponding -// to a DNS value in the public hosted zone. -func (a *Client) UpdatePublicRecordIDForCNAME(dnsName, newID string, logger log.FieldLogger) error { - id, err := a.getHostedZoneIDWithTag(Tag{ - Key: DefaultCloudDNSTagKey, - Value: DefaultPublicCloudDNSTagValue, - }, logger) - if err != nil { - return errors.Wrap(err, "failed to find the public hosted zone") - } - - return a.updateResourceRecordIDs(id, dnsName, newID, logger) -} - -// DeletePublicCNAME deletes a AWS route53 record for a public domain name. -func (a *Client) DeletePublicCNAME(dnsName string, logger log.FieldLogger) error { - id, err := a.getHostedZoneIDWithTag(Tag{ - Key: DefaultCloudDNSTagKey, - Value: DefaultPublicCloudDNSTagValue, - }, logger) - if err != nil { - return errors.Wrapf(err, "unable to delete a public CNAME: %s", dnsName) - } - - return a.deleteCNAME(id, dnsName, logger) -} - -// DeletePrivateCNAME deletes a AWS route53 record for a private domain name. -func (a *Client) DeletePrivateCNAME(dnsName string, logger log.FieldLogger) error { - id, err := a.getHostedZoneIDWithTag(Tag{ - Key: DefaultCloudDNSTagKey, - Value: DefaultPrivateCloudDNSTagValue, - }, logger) - if err != nil { - return errors.Wrapf(err, "unable to delete a private CNAME: %s", dnsName) - } - - return a.deleteCNAME(id, dnsName, logger) -} - func (a *Client) isProvisionedCNAME(hostedZoneID, dnsName string, logger log.FieldLogger) bool { recordSets, err := a.getRecordSetsForDNS(hostedZoneID, dnsName, logger) if err != nil { @@ -369,13 +345,13 @@ func (a *Client) getHostedZoneIDWithTag(tag Tag, logger log.FieldLogger) (string for { zoneList, err := a.Service().route53.ListHostedZones(&route53.ListHostedZonesInput{Marker: next}) if err != nil { - return "", errors.Wrapf(err, "listing hosted all zones") + return "", errors.Wrapf(err, "failed to list all hosted zones") } for _, zone := range zoneList.HostedZones { id, err := parseHostedZoneResourceID(zone) if err != nil { - return "", errors.Wrapf(err, "when parsing hosted zone: %s", zone.String()) + return "", errors.Wrapf(err, "failed to parse hosted zone ID: %s", zone.String()) } tagList, err := a.Service().route53.ListTagsForResource(&route53.ListTagsForResourceInput{ @@ -383,7 +359,7 @@ func (a *Client) getHostedZoneIDWithTag(tag Tag, logger log.FieldLogger) (string ResourceType: aws.String(hostedZoneResourceType), }) if err != nil { - return "", err + return "", errors.Wrap(err, "failed to get tag list for hosted zone") } for _, resourceTag := range tagList.ResourceTagSet.Tags { diff --git a/internal/tools/aws/route53_test.go b/internal/tools/aws/route53_test.go index 0b4ac2cbb..305fdfc97 100644 --- a/internal/tools/aws/route53_test.go +++ b/internal/tools/aws/route53_test.go @@ -5,7 +5,6 @@ package aws import ( - "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/route53" "github.com/golang/mock/gomock" testlib "github.com/mattermost/mattermost-cloud/internal/testlib" @@ -15,80 +14,19 @@ import ( func (a *AWSTestSuite) TestRoute53CreatePublicCNAME() { gomock.InOrder( - a.Mocks.API.Route53.EXPECT(). - ListHostedZones(&route53.ListHostedZonesInput{}). - Return(&route53.ListHostedZonesOutput{ - HostedZones: []*route53.HostedZone{ - { - Id: aws.String(a.HostedZoneID), - }, - }, - Marker: aws.String("next"), - IsTruncated: aws.Bool(true), - }, nil). - Times(1), - - a.Mocks.API.Route53.EXPECT(). - ListTagsForResource(gomock.Any()). - Do(func(input *route53.ListTagsForResourceInput) { - a.Assert().Equal(a.HostedZoneID, *input.ResourceId) - a.Assert().Equal("hostedzone", *input.ResourceType) - }). - Return(&route53.ListTagsForResourceOutput{ - ResourceTagSet: &route53.ResourceTagSet{ - Tags: []*route53.Tag{ - { - Key: aws.String("random-key"), - Value: aws.String("random-value"), - }, - }, - }, - }, nil). - Times(1), - - a.Mocks.API.Route53.EXPECT(). - ListHostedZones(gomock.Any()). - Return(&route53.ListHostedZonesOutput{ - HostedZones: []*route53.HostedZone{ - { - Id: aws.String(a.HostedZoneID), - }, - }, - IsTruncated: aws.Bool(false), - }, nil). - Times(1), - - a.Mocks.API.Route53.EXPECT(). - ListTagsForResource(gomock.Any()). - Do(func(input *route53.ListTagsForResourceInput) { - a.Assert().Equal(a.HostedZoneID, *input.ResourceId) - a.Assert().Equal("hostedzone", *input.ResourceType) - }). - Return(&route53.ListTagsForResourceOutput{ - ResourceTagSet: &route53.ResourceTagSet{ - Tags: []*route53.Tag{ - { - Key: aws.String("MattermostCloudDNS"), - Value: aws.String("public"), - }, - }, - }, - }, nil). - Times(1), - a.Mocks.API.Route53.EXPECT(). ChangeResourceRecordSets(gomock.Any()). Do(func(input *route53.ChangeResourceRecordSetsInput) { a.Assert().Equal("mattermost.com", *input.ChangeBatch.Changes[0].ResourceRecordSet.Name) a.Assert().Equal("example.mattermost.com", *input.ChangeBatch.Changes[0].ResourceRecordSet.ResourceRecords[0].Value) - a.Assert().Equal(a.HostedZoneID, *input.HostedZoneId) + a.Assert().Equal(a.Mocks.AWS.GetPublicHostedZoneID(), *input.HostedZoneId) }). Return(&route53.ChangeResourceRecordSetsOutput{}, nil), a.Mocks.Log.Logger.EXPECT().WithFields(log.Fields{ "route53-dns-value": "mattermost.com", "route53-dns-endpoints": []string{"example.mattermost.com"}, - "route53-hosted-zone-id": a.HostedZoneID, + "route53-hosted-zone-id": a.Mocks.AWS.GetPublicHostedZoneID(), }). Return(testlib.NewLoggerEntry()). Times(1), @@ -98,92 +36,38 @@ func (a *AWSTestSuite) TestRoute53CreatePublicCNAME() { a.Assert().NoError(err) } -func (a *AWSTestSuite) TestRoute53CreatePublicCNAMEListZonesError() { - a.Mocks.API.Route53.EXPECT(). - ListHostedZones(&route53.ListHostedZonesInput{}). - Return(nil, errors.New("invalid input")). - Times(1) - - a.Mocks.API.Route53.EXPECT().ListTagsForResource(gomock.Any()).Times(0) - a.Mocks.API.Route53.EXPECT().ChangeResourceRecordSets(gomock.Any()).Times(0) - a.Mocks.Log.Logger.EXPECT().WithFields(gomock.Any()).Times(0) - - err := a.Mocks.AWS.CreatePublicCNAME("mattermost.com", []string{"example.mattermost.com"}, a.Mocks.Log.Logger) - a.Assert().Error(err) - a.Assert().Equal("unable to create a public CNAME: mattermost.com: listing hosted all zones: invalid input", err.Error()) -} - -func (a *AWSTestSuite) TestRoute53CreatePublicCNAMEListTagsError() { +func (a *AWSTestSuite) TestRoute53CreatePrivateCNAME() { gomock.InOrder( a.Mocks.API.Route53.EXPECT(). - ListHostedZones(&route53.ListHostedZonesInput{}). - Return(&route53.ListHostedZonesOutput{ - HostedZones: []*route53.HostedZone{ - { - Id: aws.String(a.HostedZoneID), - }, - }, - Marker: aws.String("next"), - IsTruncated: aws.Bool(true), - }, nil). - Times(1), - - a.Mocks.API.Route53.EXPECT(). - ListTagsForResource(gomock.Any()). - Do(func(input *route53.ListTagsForResourceInput) { - a.Assert().Equal(a.HostedZoneID, *input.ResourceId) - a.Assert().Equal("hostedzone", *input.ResourceType) + ChangeResourceRecordSets(gomock.Any()). + Do(func(input *route53.ChangeResourceRecordSetsInput) { + a.Assert().Equal("mattermost.com", *input.ChangeBatch.Changes[0].ResourceRecordSet.Name) + a.Assert().Equal("example.mattermost.com", *input.ChangeBatch.Changes[0].ResourceRecordSet.ResourceRecords[0].Value) + a.Assert().Equal(a.Mocks.AWS.GetPrivateHostedZoneID(), *input.HostedZoneId) }). - Return(nil, errors.New("region is not set")). + Return(&route53.ChangeResourceRecordSetsOutput{}, nil), + + a.Mocks.Log.Logger.EXPECT().WithFields(log.Fields{ + "route53-dns-value": "mattermost.com", + "route53-dns-endpoints": []string{"example.mattermost.com"}, + "route53-hosted-zone-id": a.Mocks.AWS.GetPrivateHostedZoneID(), + }). + Return(testlib.NewLoggerEntry()). Times(1), ) - a.Mocks.API.Route53.EXPECT().ChangeResourceRecordSets(gomock.Any()).Times(0) - a.Mocks.Log.Logger.EXPECT().WithFields(gomock.Any()).Times(0) - - err := a.Mocks.AWS.CreatePublicCNAME("mattermost.com", []string{"example.mattermost.com"}, a.Mocks.Log.Logger) - a.Assert().Error(err) - a.Assert().Equal("unable to create a public CNAME: mattermost.com: region is not set", err.Error()) + err := a.Mocks.AWS.CreatePrivateCNAME("mattermost.com", []string{"example.mattermost.com"}, a.Mocks.Log.Logger) + a.Assert().NoError(err) } func (a *AWSTestSuite) TestRoute53CreatePublicCNAMEChangeRecordSetsError() { gomock.InOrder( - a.Mocks.API.Route53.EXPECT(). - ListHostedZones(gomock.Any()). - Return(&route53.ListHostedZonesOutput{ - HostedZones: []*route53.HostedZone{ - { - Id: aws.String(a.HostedZoneID), - }, - }, - IsTruncated: aws.Bool(false), - }, nil). - Times(1), - - a.Mocks.API.Route53.EXPECT(). - ListTagsForResource(gomock.Any()). - Do(func(input *route53.ListTagsForResourceInput) { - a.Assert().Equal(a.HostedZoneID, *input.ResourceId) - a.Assert().Equal("hostedzone", *input.ResourceType) - }). - Return(&route53.ListTagsForResourceOutput{ - ResourceTagSet: &route53.ResourceTagSet{ - Tags: []*route53.Tag{ - { - Key: aws.String("MattermostCloudDNS"), - Value: aws.String("public"), - }, - }, - }, - }, nil). - Times(1), - a.Mocks.API.Route53.EXPECT(). ChangeResourceRecordSets(gomock.Any()). Do(func(input *route53.ChangeResourceRecordSetsInput) { a.Assert().Equal("mattermost.com", *input.ChangeBatch.Changes[0].ResourceRecordSet.Name) a.Assert().Equal("example.mattermost.com", *input.ChangeBatch.Changes[0].ResourceRecordSet.ResourceRecords[0].Value) - a.Assert().Equal(a.HostedZoneID, *input.HostedZoneId) + a.Assert().Equal(a.Mocks.AWS.GetPublicHostedZoneID(), *input.HostedZoneId) }). Return(nil, errors.New("unable to change recordsets")). Times(1), @@ -196,44 +80,6 @@ func (a *AWSTestSuite) TestRoute53CreatePublicCNAMEChangeRecordSetsError() { a.Assert().Equal("unable to change recordsets", err.Error()) } -func (a *AWSTestSuite) TestRoute53CreatePublicCNAMENoHostedZone() { - gomock.InOrder( - a.Mocks.API.Route53.EXPECT(). - ListHostedZones(gomock.Any()). - Return(&route53.ListHostedZonesOutput{ - HostedZones: []*route53.HostedZone{ - { - Id: aws.String(a.HostedZoneID), - }, - }, - IsTruncated: aws.Bool(false), - }, nil). - Times(1), - - a.Mocks.API.Route53.EXPECT(). - ListTagsForResource(gomock.Any()). - Do(func(input *route53.ListTagsForResourceInput) { - a.Assert().Equal(a.HostedZoneID, *input.ResourceId) - a.Assert().Equal("hostedzone", *input.ResourceType) - }). - Return(&route53.ListTagsForResourceOutput{ - ResourceTagSet: &route53.ResourceTagSet{ - Tags: []*route53.Tag{ - { - Key: aws.String("random-key"), - Value: aws.String("random-value"), - }, - }, - }, - }, nil). - Times(1), - ) - - err := a.Mocks.AWS.CreatePublicCNAME("mattermost.com", []string{"example.mattermost.com"}, a.Mocks.Log.Logger) - a.Assert().Error(err) - a.Assert().Equal("unable to create a public CNAME: mattermost.com: no hosted zone ID associated with tag: tag:MattermostCloudDNS:public", err.Error()) -} - func (a *AWSTestSuite) TestRoute53DeletePublicCNAME() { a.T().Skip() }