Skip to content

Commit

Permalink
Add CodeBuild RDS Flyway example (#11)
Browse files Browse the repository at this point in the history
Added a sample in rds-flyway-migrations that includes a Cloudformation stack that creates a VPC, RDS Serverless Aurora V2 instance, and CodeBuild script to run Flyway migrations.
  • Loading branch information
krimple authored Mar 13, 2023
1 parent 1b3c3d7 commit 79d711d
Show file tree
Hide file tree
Showing 7 changed files with 415 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.idea
21 changes: 21 additions & 0 deletions rds-flyway-migrations/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# RDS Flyway Migrations Sample

This sample project creates an RDS instance within the private subnet of a VPC, and installs a CodeBuild project to run Flyway database migrations to provision the database.

Setup steps:

1. Install AWS CLI
2. Access an AWS account with rights to create an RDS instance, VPC, and create and run CodeBuilds.
3. Run the deploy.sh script - two positional parameters are
- the name of the stack to create
- the region to create your stack within

- Example:
```bash
./deploy.sh rds-flyway-migrations us-east-2
```
4. Run the project created within CodeBuild to perform the migrations
5. Observe the logs to see that the environment variables for the username, database name and password are not exposed to the logs
6. You may also use the RDS control panel to perform queries against the database


362 changes: 362 additions & 0 deletions rds-flyway-migrations/cloudformation.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,362 @@
##
## Sample RDS configuration with Flyway migrations
##

AWSTemplateFormatVersion: "2010-09-09"
Description: "VPC, CodeBuild and RDS for Flyway Demo"

Parameters:
BaseCIDR:
Description: "The first two bytes of a /16 CIDR that will be used for the VPC"
Type: "String"
Default: "10.10"

GitHubRepoUrl:
Description: "The URL of the project to clone"
Type: "String"
Default: "https://github.com/chariotsolutions/aws-examples.git"

Resources:
VPC:
Type: "AWS::EC2::VPC"
Properties:
CidrBlock: !Sub "${BaseCIDR}.0.0/16"
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: "Name"
Value: "rds-flyway-migrations-sample"

PublicSubnet01:
Type: "AWS::EC2::Subnet"
Properties:
VpcId: !Ref VPC
CidrBlock: !Sub "${BaseCIDR}.0.0/20"
AvailabilityZone: !Sub "${AWS::Region}a"
MapPublicIpOnLaunch: true

PublicSubnet02:
Type: "AWS::EC2::Subnet"
Properties:
VpcId: !Ref VPC
CidrBlock: !Sub "${BaseCIDR}.16.0/20"
AvailabilityZone: !Sub "${AWS::Region}b"
MapPublicIpOnLaunch: true

InternetGateway:
Type: "AWS::EC2::InternetGateway"

InternetGatewayAttachment:
Type: "AWS::EC2::VPCGatewayAttachment"
Properties:
VpcId: !Ref VPC
InternetGatewayId: !Ref InternetGateway

PublicSubnet01Routes:
Type: "AWS::EC2::SubnetRouteTableAssociation"
Properties:
SubnetId: !Ref PublicSubnet01
RouteTableId: !Ref PublicRouteTable

PublicSubnet02Routes:
Type: "AWS::EC2::SubnetRouteTableAssociation"
Properties:
SubnetId: !Ref PublicSubnet02
RouteTableId: !Ref PublicRouteTable

PublicRouteTableIGW:
Type: "AWS::EC2::Route"
Properties:
RouteTableId: !Ref PublicRouteTable
DestinationCidrBlock: "0.0.0.0/0"
GatewayId: !Ref InternetGateway

PublicRouteTable:
Type: "AWS::EC2::RouteTable"
Properties:
VpcId: !Ref VPC

##
## Private Subnets - two are configured to support RDS (which requires two subnets for a serverless implementation)
##

PrivateSubnet01:
Type: "AWS::EC2::Subnet"
Properties:
VpcId: !Ref VPC
CidrBlock: !Sub "${BaseCIDR}.128.0/20"
AvailabilityZone: !Sub "${AWS::Region}a"
MapPublicIpOnLaunch: false

PrivateSubnet02:
Type: "AWS::EC2::Subnet"
Properties:
VpcId: !Ref VPC
CidrBlock: !Sub "${BaseCIDR}.144.0/20"
AvailabilityZone: !Sub "${AWS::Region}b"
MapPublicIpOnLaunch: false

PrivateRouteTable:
Type: "AWS::EC2::RouteTable"
Properties:
VpcId: !Ref VPC

PrivateSubnet01Routes:
Type: "AWS::EC2::SubnetRouteTableAssociation"
Properties:
SubnetId: !Ref PrivateSubnet01
RouteTableId: !Ref PrivateRouteTable

PrivateSubnet02Routes:
Type: "AWS::EC2::SubnetRouteTableAssociation"
Properties:
SubnetId: !Ref PrivateSubnet02
RouteTableId: !Ref PrivateRouteTable

##
## NAT configuration - this is a demonstration sample, so we are only configuring a single NAT.
## For production, each AZ would have its own NAT.
##

ElasticIP01:
Type: "AWS::EC2::EIP"
Properties:
Domain: "vpc"

NAT:
Type: "AWS::EC2::NatGateway"
Properties:
SubnetId: !Ref PublicSubnet01
AllocationId: !GetAtt ElasticIP01.AllocationId

PrivateRouteNAT:
Type: "AWS::EC2::Route"
Properties:
RouteTableId: !Ref PrivateRouteTable
DestinationCidrBlock: "0.0.0.0/0"
NatGatewayId: !Ref NAT

##
## Configure a SecretsManager Secret to hold the database name, user name and password
## in an encrypted store
##

DBSecrets:
Type: AWS::SecretsManager::Secret
Properties:
Description: "Secrets for the demo application"
GenerateSecretString:
SecretStringTemplate: |
{
"dbDatabaseName": "demodb",
"dbUserName": "demouser"
}
GenerateStringKey: "dbPassword"
ExcludePunctuation: true # easier to select for those times that you need to copy/paste
PasswordLength: 20

##
## Define network visibility rules via a security group and ingress rule
##
DatabaseSecurityGroup:
Type: "AWS::EC2::SecurityGroup"
Properties:
GroupName: !Sub "${AWS::StackName}-dbSG"
GroupDescription: "Allows access to the database (nominally from application)"
VpcId: !Ref VPC

DatabaseSecurityGroupCBtoDB:
Type: "AWS::EC2::SecurityGroupIngress"
Properties:
GroupId: !Ref DatabaseSecurityGroup
Description: "Access from codebuild container"
IpProtocol: "tcp"
FromPort: 5432
ToPort: 5432
SourceSecurityGroupId: !GetAtt CodeBuildSecurityGroup.GroupId

##
## Database - Aurora Serverless V2
##

RDSParameterGroup:
Type: "AWS::RDS::DBClusterParameterGroup"
Properties:
Description: !Sub "Managed by CloudFormation: ${AWS::StackName}"
Family: "aurora-postgresql14"
Parameters:
client_encoding: "UTF8"

RDSSubnetGroup:
Type: "AWS::RDS::DBSubnetGroup"
Properties:
DBSubnetGroupName: !Sub "${AWS::StackName}-rds-subnet-group"
DBSubnetGroupDescription: !Sub "Managed by CloudFormation: ${AWS::StackName}"
SubnetIds: [ !Ref PrivateSubnet01, !Ref PrivateSubnet02 ]

DatabaseCluster:
Type: "AWS::RDS::DBCluster"
Properties:
Engine: "aurora-postgresql"
EngineVersion: "14.6"
ServerlessV2ScalingConfiguration:
MinCapacity: 0.5
MaxCapacity: 2
# enable logs?
# EnableCloudwatchLogsExports:
# - postgresql
DBClusterParameterGroupName: !Ref RDSParameterGroup
MasterUsername: !Sub "{{resolve:secretsmanager:${DBSecrets}:SecretString:dbUserName}}"
MasterUserPassword: !Sub "{{resolve:secretsmanager:${DBSecrets}:SecretString:dbPassword}}"
DatabaseName: !Sub "{{resolve:secretsmanager:${DBSecrets}:SecretString:dbDatabaseName}}"
Port: 5432 # note, default is 3306, even for Postgres
DBSubnetGroupName: !Ref RDSSubnetGroup
VpcSecurityGroupIds:
- !Ref DatabaseSecurityGroup
# can define when backups and maintenance run
# PreferredMaintenanceWindow: "Sun:06:00-Sun:06:59"
# PreferredBackupWindow: "05:00-05:30"
BackupRetentionPeriod: 7
StorageEncrypted: true

ServerlessInstance:
Type: "AWS::RDS::DBInstance"
Properties:
DBClusterIdentifier: !Ref DatabaseCluster
DBInstanceClass: db.serverless
Engine: aurora-postgresql


##
## CodeBuild - set up security group, execution role and CodeBuild project
##

CodeBuildSecurityGroup:
Type: "AWS::EC2::SecurityGroup"
Properties:
GroupName: !Sub "${AWS::StackName}-CodeBuildSG"
GroupDescription: "Security Group for CodeBuild"
VpcId: !Ref VPC


CodeBuildExecutionRole:
Type: "AWS::IAM::Role"
Properties:
RoleName: !Sub "${AWS::StackName}-cbExecRole"
Path: "/builder/"
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Principal:
Service:
- "codebuild.amazonaws.com"
Action:
- "sts:AssumeRole"
ManagedPolicyArns:
- "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryPowerUser"
- "arn:aws:iam::aws:policy/AWSCloudFormationReadOnlyAccess"
Policies:
- PolicyName: CodeBuildLoggingPolicy
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Action:
- "logs:CreateLogGroup"
- "logs:CreateLogStream"
- "logs:PutLogEvents"
Resource: "*"
- PolicyName: CodeBuildECSTaskExecution
PolicyDocument:
Version: "2012-10-17"
Statement:
- # Secrets manager
Effect: "Allow"
Action:
- "secretsmanager:Describe*"
- "secretsmanager:Get*"
- "secretsmanager:List*"
Resource: !Ref DBSecrets
- PolicyName: CodeBuildEC2NetworkingAccess
PolicyDocument:
Version: "2012-10-17"
Statement:
- # Access to networking resources
Effect: Allow
Action:
- "ec2:CreateNetworkInterface"
- "ec2:DescribeDhcpOptions"
- "ec2:DescribeNetworkInterfaces"
- "ec2:DeleteNetworkInterface"
- "ec2:DescribeSubnets"
- "ec2:DescribeSecurityGroups"
- "ec2:DescribeVpcs"
- "ec2:CreateNetworkInterfacePermission"
Resource: "*"

MigrationsProject:
Type: AWS::CodeBuild::Project
Properties:
Name: !Sub "${AWS::StackName}-cbMigrations"
Description: Run DB Migrations for the platform
ServiceRole: !GetAtt CodeBuildExecutionRole.Arn
Artifacts:
Type: NO_ARTIFACTS
VpcConfig:
VpcId: !Ref VPC
SecurityGroupIds: [ !Ref CodeBuildSecurityGroup ]
Subnets: [ !Ref PrivateSubnet01, !Ref PrivateSubnet02 ]
LogsConfig:
CloudWatchLogs:
GroupName: "/codebuild/migration"
Status: ENABLED
StreamName: "codebuild-log"
Environment:
Type: LINUX_CONTAINER
ComputeType: BUILD_GENERAL1_SMALL
Image: aws/codebuild/amazonlinux2-x86_64-standard:4.0
PrivilegedMode: true
EnvironmentVariables:
- Name: "DB_HOSTNAME"
Value: !GetAtt DatabaseCluster.Endpoint.Address

- Name: "DB_PORT"
Value: !GetAtt DatabaseCluster.Endpoint.Port

- Name: "AWS_REGION"
Value: !Sub "${AWS::Region}"

- Name: "DB_SECRETS_ARN"
Value: !Ref DBSecrets

Source:
Auth:
Type: OAUTH
Location: !Ref GitHubRepoUrl
Type: GITHUB
GitCloneDepth: 1
BuildSpec: |
version: 0.2
env:
secrets-manager:
DB_DATABASE: ${DB_SECRETS_ARN}:dbDatabaseName
DB_USERNAME: ${DB_SECRETS_ARN}:dbUserName
DB_PASSWORD: ${DB_SECRETS_ARN}:dbPassword
phases:
install:
runtime-versions:
java: corretto17
build:
commands:
- wget -qO- https://repo1.maven.org/maven2/org/flywaydb/flyway-commandline/9.11.0/flyway-commandline-9.11.0-linux-x64.tar.gz | tar xvz && ln -s `pwd`/flyway-9.11.0/flyway /usr/local/bin
- cd rds-flyway-migrations/flyway
- flyway migrate -locations=filesystem:migrations/**/*.sql -password=$DB_PASSWORD -user=$DB_USERNAME -url=jdbc:postgresql://$DB_HOSTNAME:$DB_PORT/$DB_DATABASE -connectRetries=300 -X
TimeoutInMinutes: 10

Outputs:
DatabaseEndpointAddress:
Value: !GetAtt DatabaseCluster.Endpoint.Address

DatabaseEndpointPort:
Value: !GetAtt DatabaseCluster.Endpoint.Port
2 changes: 2 additions & 0 deletions rds-flyway-migrations/deploy.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Use with: ./deploy.sh stackname region
aws cloudformation deploy --stack-name $1 --region $2 --capabilities CAPABILITY_NAMED_IAM --template-file cloudformation.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CREATE EXTENSION IF NOT EXISTS pgcrypto;
Loading

0 comments on commit 79d711d

Please sign in to comment.