Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion internal/pkg/addon/addon_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func TestAddons(t *testing.T) {
outFileName string
}{
"aurora": {
addonMarshaler: addon.NewRDSTemplate(addon.RDSProps{
addonMarshaler: addon.NewServerlessV2Template(addon.RDSProps{
ClusterName: "aurora",
Engine: "MySQL",
InitialDBName: "main",
Expand Down
52 changes: 38 additions & 14 deletions internal/pkg/addon/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,19 @@ import (
)

const (
dynamoDbTemplatePath = "addons/ddb/cf.yml"
s3TemplatePath = "addons/s3/cf.yml"
rdsTemplatePath = "addons/aurora/cf.yml"
rdsRDWSTemplatePath = "addons/aurora/rdws/cf.yml"
rdsRDWSParamsPath = "addons/aurora/rdws/addons.parameters.yml"
dynamoDbTemplatePath = "addons/ddb/cf.yml"
s3TemplatePath = "addons/s3/cf.yml"
rdsTemplatePath = "addons/aurora/cf.yml"
rdsV2TemplatePath = "addons/aurora/serverlessv2.yml"
rdsRDWSTemplatePath = "addons/aurora/rdws/cf.yml"
rdsRDWSV2TemplatePath = "addons/aurora/rdws/serverlessv2.yml"
rdsRDWSParamsPath = "addons/aurora/rdws/addons.parameters.yml"
)

const (
// Aurora Serverless versions.
auroraServerlessVersionV1 = "v1"
auroraServerlessVersionV2 = "v2"
)

const (
Expand Down Expand Up @@ -82,8 +90,12 @@ type RDSTemplate struct {
// MarshalBinary serializes the content of the template into binary.
func (r *RDSTemplate) MarshalBinary() ([]byte, error) {
path := rdsTemplatePath
if r.WorkloadType == manifest.RequestDrivenWebServiceType {
if r.WorkloadType != manifest.RequestDrivenWebServiceType && r.auroraServerlessVersion == auroraServerlessVersionV2 {
path = rdsV2TemplatePath
} else if r.WorkloadType == manifest.RequestDrivenWebServiceType && r.auroraServerlessVersion == auroraServerlessVersionV1 {
path = rdsRDWSTemplatePath
} else if r.WorkloadType == manifest.RequestDrivenWebServiceType && r.auroraServerlessVersion == auroraServerlessVersionV2 {
path = rdsRDWSV2TemplatePath
}
content, err := r.parser.Parse(path, *r, template.WithFuncs(storageTemplateFunctions))
if err != nil {
Expand Down Expand Up @@ -147,16 +159,28 @@ func NewDDBTemplate(input *DynamoDBProps) *DynamoDBTemplate {

// RDSProps holds RDS-specific properties for addon.NewRDSTemplate().
type RDSProps struct {
WorkloadType string // The type of the workload associated with the RDS addon.
ClusterName string // The name of the cluster.
Engine string // The engine type of the RDS Aurora Serverless cluster.
InitialDBName string // The name of the initial database created inside the cluster.
ParameterGroup string // The parameter group to use for the cluster.
Envs []string // The copilot environments found inside the current app.
WorkloadType string // The type of the workload associated with the RDS addon.
ClusterName string // The name of the cluster.
auroraServerlessVersion string // The version of Aurora Serverless.
Engine string // The engine type of the RDS Aurora Serverless cluster.
InitialDBName string // The name of the initial database created inside the cluster.
ParameterGroup string // The parameter group to use for the cluster.
Envs []string // The copilot environments found inside the current app.
}

// NewServerlessV1Template creates a new RDS marshaler which can be used to write an Aurora Serverless v1 CloudFormation template.
func NewServerlessV1Template(input RDSProps) *RDSTemplate {
input.auroraServerlessVersion = auroraServerlessVersionV1
return &RDSTemplate{
RDSProps: input,

parser: template.New(),
}
}

// NewRDSTemplate creates a new RDS marshaler which can be used to write a RDS CloudFormation template.
func NewRDSTemplate(input RDSProps) *RDSTemplate {
// NewServerlessV2Template creates a new RDS marshaler which can be used to write an Aurora Serverless v2 CloudFormation template.
func NewServerlessV2Template(input RDSProps) *RDSTemplate {
input.auroraServerlessVersion = auroraServerlessVersionV2
return &RDSTemplate{
RDSProps: input,

Expand Down
58 changes: 50 additions & 8 deletions internal/pkg/addon/storage_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,23 +111,26 @@ func TestS3Template_MarshalBinary(t *testing.T) {
func TestRDSTemplate_MarshalBinary(t *testing.T) {
testCases := map[string]struct {
workloadType string
version string
engine string
mockDependencies func(ctrl *gomock.Controller, r *RDSTemplate)

wantedBinary []byte
wantedError error
}{
"error parsing template": {
engine: RDSEngineTypePostgreSQL,
version: auroraServerlessVersionV1,
engine: RDSEngineTypePostgreSQL,
mockDependencies: func(ctrl *gomock.Controller, r *RDSTemplate) {
m := mocks.NewMockParser(ctrl)
r.parser = m
m.EXPECT().Parse(gomock.Any(), *r, gomock.Any()).Return(nil, errors.New("some error"))
},
wantedError: errors.New("some error"),
},
"renders postgresql content": {
engine: RDSEngineTypePostgreSQL,
"renders postgresql v1 content": {
version: auroraServerlessVersionV1,
engine: RDSEngineTypePostgreSQL,
mockDependencies: func(ctrl *gomock.Controller, r *RDSTemplate) {
m := mocks.NewMockParser(ctrl)
r.parser = m
Expand All @@ -137,8 +140,9 @@ func TestRDSTemplate_MarshalBinary(t *testing.T) {
},
wantedBinary: []byte("psql"),
},
"renders mysql content": {
engine: RDSEngineTypeMySQL,
"renders mysql v1 content": {
version: auroraServerlessVersionV1,
engine: RDSEngineTypeMySQL,
mockDependencies: func(ctrl *gomock.Controller, r *RDSTemplate) {
m := mocks.NewMockParser(ctrl)
r.parser = m
Expand All @@ -148,8 +152,9 @@ func TestRDSTemplate_MarshalBinary(t *testing.T) {
},
wantedBinary: []byte("mysql"),
},
"renders rdws rds template": {
"renders rdws rds v1 template": {
workloadType: "Request-Driven Web Service",
version: auroraServerlessVersionV1,
engine: RDSEngineTypeMySQL,
mockDependencies: func(ctrl *gomock.Controller, r *RDSTemplate) {
m := mocks.NewMockParser(ctrl)
Expand All @@ -159,6 +164,42 @@ func TestRDSTemplate_MarshalBinary(t *testing.T) {
},
wantedBinary: []byte("mysql"),
},
"renders postgresql v2 content": {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yayy thank you for all the unit tests !

version: auroraServerlessVersionV2,
engine: RDSEngineTypePostgreSQL,
mockDependencies: func(ctrl *gomock.Controller, r *RDSTemplate) {
m := mocks.NewMockParser(ctrl)
r.parser = m
m.EXPECT().Parse(gomock.Eq(rdsV2TemplatePath), *r, gomock.Any()).
Return(&template.Content{Buffer: bytes.NewBufferString("psql")}, nil)

},
wantedBinary: []byte("psql"),
},
"renders mysql v2 content": {
version: auroraServerlessVersionV2,
engine: RDSEngineTypeMySQL,
mockDependencies: func(ctrl *gomock.Controller, r *RDSTemplate) {
m := mocks.NewMockParser(ctrl)
r.parser = m
m.EXPECT().Parse(gomock.Eq(rdsV2TemplatePath), *r, gomock.Any()).
Return(&template.Content{Buffer: bytes.NewBufferString("mysql")}, nil)

},
wantedBinary: []byte("mysql"),
},
"renders rdws rds v2 template": {
workloadType: "Request-Driven Web Service",
version: auroraServerlessVersionV2,
engine: RDSEngineTypeMySQL,
mockDependencies: func(ctrl *gomock.Controller, r *RDSTemplate) {
m := mocks.NewMockParser(ctrl)
r.parser = m
m.EXPECT().Parse(gomock.Eq(rdsRDWSV2TemplatePath), *r, gomock.Any()).
Return(&template.Content{Buffer: bytes.NewBufferString("mysql")}, nil)
},
wantedBinary: []byte("mysql"),
},
}

for name, tc := range testCases {
Expand All @@ -168,8 +209,9 @@ func TestRDSTemplate_MarshalBinary(t *testing.T) {
defer ctrl.Finish()
addon := &RDSTemplate{
RDSProps: RDSProps{
WorkloadType: tc.workloadType,
Engine: tc.engine,
WorkloadType: tc.workloadType,
auroraServerlessVersion: tc.version,
Engine: tc.engine,
},
}
tc.mockDependencies(ctrl, addon)
Expand Down
109 changes: 66 additions & 43 deletions internal/pkg/addon/testdata/storage/aurora.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,59 +11,55 @@ Parameters:
# Customize your Aurora Serverless cluster by setting the default value of the following parameters.
auroraDBName:
Type: String
Description: The name of the initial database to be created in the DB cluster.
Description: The name of the initial database to be created in the Aurora Serverless v2 cluster.
Default: main
# Cannot have special characters
# Naming constraints: https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/CHAP_Limits.html#RDS_Limits.Constraints
auroraDBAutoPauseSeconds:
Type: Number
Description: The duration in seconds before the cluster pauses.
Default: 1000
Mappings:
auroraEnvScalingConfigurationMap:
auroraEnvScalingConfigurationMap:
test:
"DBMinCapacity": 1 # AllowedValues: [1, 2, 4, 8, 16, 32, 64, 128, 256]
"DBMaxCapacity": 8 # AllowedValues: [1, 2, 4, 8, 16, 32, 64, 128, 256]
"DBMinCapacity": 0.5 # AllowedValues: from 0.5 through 128
"DBMaxCapacity": 8 # AllowedValues: from 0.5 through 128

All:
"DBMinCapacity": 1 # AllowedValues: [1, 2, 4, 8, 16, 32, 64, 128, 256]
"DBMaxCapacity": 8 # AllowedValues: [1, 2, 4, 8, 16, 32, 64, 128, 256]
"DBMinCapacity": 0.5 # AllowedValues: from 0.5 through 128
"DBMaxCapacity": 8 # AllowedValues: from 0.5 through 128

Resources:
auroraDBSubnetGroup:
Type: 'AWS::RDS::DBSubnetGroup'
Type: "AWS::RDS::DBSubnetGroup"
Properties:
DBSubnetGroupDescription: Group of Copilot private subnets for Aurora cluster.
DBSubnetGroupDescription: Group of Copilot private subnets for Aurora Serverless v2 cluster.
SubnetIds:
!Split [',', { 'Fn::ImportValue': !Sub '${App}-${Env}-PrivateSubnets' }]
!Split [",", { "Fn::ImportValue": !Sub "${App}-${Env}-PrivateSubnets" }]
auroraSecurityGroup:
Metadata:
'aws:copilot:description': 'A security group for your workload to access the DB cluster aurora'
Type: 'AWS::EC2::SecurityGroup'
"aws:copilot:description": "A security group for your workload to access the Aurora Serverless v2 cluster aurora"
Type: "AWS::EC2::SecurityGroup"
Properties:
GroupDescription: !Sub 'The Security Group for ${Name} to access DB cluster aurora.'
GroupDescription: !Sub "The Security Group for ${Name} to access Aurora Serverless v2 cluster aurora."
VpcId:
Fn::ImportValue:
!Sub '${App}-${Env}-VpcId'
Fn::ImportValue: !Sub "${App}-${Env}-VpcId"
Tags:
- Key: Name
Value: !Sub 'copilot-${App}-${Env}-${Name}-Aurora'
Value: !Sub "copilot-${App}-${Env}-${Name}-Aurora"
auroraDBClusterSecurityGroup:
Metadata:
"aws:copilot:description": A security group for your DB cluster aurora
"aws:copilot:description": "A security group for your Aurora Serverless v2 cluster aurora"
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: The Security Group for the database cluster.
GroupDescription: The Security Group for the Aurora Serverless v2 cluster.
SecurityGroupIngress:
- ToPort: 3306
FromPort: 3306
IpProtocol: tcp
Description: !Sub 'From the Aurora Security Group of the workload ${Name}.'
Description: !Sub "From the Aurora Security Group of the workload ${Name}."
SourceSecurityGroupId: !Ref auroraSecurityGroup
VpcId:
Fn::ImportValue:
!Sub '${App}-${Env}-VpcId'
Fn::ImportValue: !Sub "${App}-${Env}-VpcId"
auroraAuroraSecret:
Metadata:
'aws:copilot:description': A Secrets Manager secret to store your DB credentials
"aws:copilot:description": "A Secrets Manager secret to store your DB credentials"
Type: AWS::SecretsManager::Secret
Properties:
Description: !Sub Aurora main user secret for ${AWS::StackName}
Expand All @@ -75,35 +71,63 @@ Resources:
PasswordLength: 16
auroraDBClusterParameterGroup:
Metadata:
'aws:copilot:description': A DB parameter group for engine configuration values
Type: 'AWS::RDS::DBClusterParameterGroup'
"aws:copilot:description": "A DB parameter group for engine configuration values"
Type: "AWS::RDS::DBClusterParameterGroup"
Properties:
Description: !Ref 'AWS::StackName'
Family: 'aurora-mysql5.7'
Description: !Ref "AWS::StackName"
Family: "aurora-mysql8.0"
Parameters:
character_set_client: 'utf8'
character_set_client: "utf8"
auroraDBCluster:
Metadata:
'aws:copilot:description': The aurora Aurora Serverless database cluster
Type: 'AWS::RDS::DBCluster'
"aws:copilot:description": "The aurora Aurora Serverless v2 database cluster"
Type: "AWS::RDS::DBCluster"
Properties:
MasterUsername:
!Join [ "", [ '{{resolve:secretsmanager:', !Ref auroraAuroraSecret, ":SecretString:username}}" ]]
!Join [
"",
[
"{{resolve:secretsmanager:",
!Ref auroraAuroraSecret,
":SecretString:username}}",
],
]
MasterUserPassword:
!Join [ "", [ '{{resolve:secretsmanager:', !Ref auroraAuroraSecret, ":SecretString:password}}" ]]
!Join [
"",
[
"{{resolve:secretsmanager:",
!Ref auroraAuroraSecret,
":SecretString:password}}",
],
]
DatabaseName: !Ref auroraDBName
Engine: 'aurora-mysql'
EngineVersion: '5.7.mysql_aurora.2.07.1'
EngineMode: serverless
Engine: "aurora-mysql"
EngineVersion: "8.0.mysql_aurora.3.02.0"
DBClusterParameterGroupName: !Ref auroraDBClusterParameterGroup
DBSubnetGroupName: !Ref auroraDBSubnetGroup
VpcSecurityGroupIds:
- !Ref auroraDBClusterSecurityGroup
ScalingConfiguration:
AutoPause: true
MinCapacity: !FindInMap [auroraEnvScalingConfigurationMap, All, DBMinCapacity]
MaxCapacity: !FindInMap [auroraEnvScalingConfigurationMap, All, DBMaxCapacity]
SecondsUntilAutoPause: !Ref auroraDBAutoPauseSeconds
ServerlessV2ScalingConfiguration:
# Replace "All" below with "!Ref Env" to set different autoscaling limits per environment.
MinCapacity:
!FindInMap [auroraEnvScalingConfigurationMap, All, DBMinCapacity]
MaxCapacity:
!FindInMap [auroraEnvScalingConfigurationMap, All, DBMaxCapacity]
auroraDBWriterInstance:
Metadata:
"aws:copilot:description": "The aurora Aurora Serverless v2 writer instance"
Type: "AWS::RDS::DBInstance"
Properties:
DBClusterIdentifier: !Ref auroraDBCluster
DBInstanceClass: db.serverless
Engine: "aurora-mysql"
PromotionTier: 1
AvailabilityZone: !Select
- 0
- !GetAZs
Ref: AWS::Region

auroraSecretAuroraClusterAttachment:
Type: AWS::SecretsManager::SecretTargetAttachment
Properties:
Expand All @@ -117,4 +141,3 @@ Outputs:
auroraSecurityGroup:
Description: "The security group to attach to the workload."
Value: !Ref auroraSecurityGroup

22 changes: 12 additions & 10 deletions internal/pkg/cli/flag.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,16 @@ const (
noSubscriptionFlag = "no-subscribe"
subscribeTopicsFlag = "subscribe-topics"

storageTypeFlag = "storage-type"
storagePartitionKeyFlag = "partition-key"
storageSortKeyFlag = "sort-key"
storageNoSortFlag = "no-sort"
storageLSIConfigFlag = "lsi"
storageNoLSIFlag = "no-lsi"
storageRDSEngineFlag = "engine"
storageRDSInitialDBFlag = "initial-db"
storageRDSParameterGroupFlag = "parameter-group"
storageTypeFlag = "storage-type"
storagePartitionKeyFlag = "partition-key"
storageSortKeyFlag = "sort-key"
storageNoSortFlag = "no-sort"
storageLSIConfigFlag = "lsi"
storageNoLSIFlag = "no-lsi"
storageAuroraServerlessVersionFlag = "serverless-version"
storageRDSEngineFlag = "engine"
storageRDSInitialDBFlag = "initial-db"
storageRDSParameterGroupFlag = "parameter-group"

taskGroupNameFlag = "task-group-name"
countFlag = "count"
Expand Down Expand Up @@ -278,7 +279,8 @@ Must be of the format '<keyName>:<dataType>'.`
storageNoLSIFlagDescription = `Optional. Don't ask about configuring alternate sort keys.`
storageLSIConfigFlagDescription = `Optional. Attribute to use as an alternate sort key. May be specified up to 5 times.
Must be of the format '<keyName>:<dataType>'.`
storageRDSEngineFlagDescription = `The database engine used in the cluster.
storageAuroraServerlessVersionFlagDescription = `Optional. Aurora Serverless version. Must be either "v1" or "v2".`
storageRDSEngineFlagDescription = `The database engine used in the cluster.
Must be either "MySQL" or "PostgreSQL".`
storageRDSInitialDBFlagDescription = "The initial database to create in the cluster."
storageRDSParameterGroupFlagDescription = "Optional. The name of the parameter group to associate with the cluster."
Expand Down
Loading