Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
410 lines (377 sloc) 11.9 KB
AWSTemplateFormatVersion: 2010-09-09
Parameters:
GitHubOwner:
Type: String
Description: 'The username of the source GitHub repo.'
GitHubRepo:
Type: String
Description: 'The source GitHub repo name (without the username).'
GitHubBranch:
Type: String
Default: master
Description: 'The source GitHub branch.'
GitHubPersonalAccessToken:
Type: String
NoEcho: true
Description: 'Use a personal access token from https://github.com/settings/tokens with "repo" and "admin:repo_hook" permissions.'
EC2InstanceType:
Type: String
Default: t3.micro
Description: 'The staging host EC2 instance type. Only tested on x86_64.'
EC2AMI:
Type: 'AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>'
Default: '/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2'
Description: 'The EC2 AMI. Only tested on Amazon Linux 2.'
Domain:
Type: String
Default: ''
Description: 'Your root domain name (Example: example.com). HTTPS will only be enabled if a domain is specified. Only provide this if your DNS is already managed by Route 53.'
Certificate:
Type: String
Default: ''
Description: 'An existing ACM certificate ARN for staging.<YOUR DOMAIN>.'
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
- Label:
default: GitHub Configuration
Parameters:
- GitHubOwner
- GitHubRepo
- GitHubBranch
- GitHubPersonalAccessToken
- Label:
default: EC2 Configuration
Parameters:
- EC2InstanceType
- EC2AMI
- Label:
default: HTTPS Configuration (Optional)
Parameters:
- Domain
- Certificate
ParameterLabels:
GitHubOwner:
default: GitHub Username
GitHubRepo:
default: GitHub Repo Name
GitHubBranch:
default: GitHub Branch
GitHubPersonalAccessToken:
default: GitHub Personal Access Token
EC2InstanceType:
default: EC2 Instance Type
EC2AMI:
default: EC2 AMI
Domain:
default: Domain Name
Certificate:
default: ACM Certificate ARN
Outputs:
StagingURL:
Value: !If
- HasDomain
- !Sub https://staging.${Domain}/
- !Sub
- http://${LoadBalancerDNS}/
- { LoadBalancerDNS: !GetAtt StagingLoadBalancer.DNSName }
Conditions:
HasDomain: !Not [!Equals [!Ref Domain, '']]
Resources:
BuildProject:
Type: AWS::CodeBuild::Project
Properties:
Name: !Ref AWS::StackName
ServiceRole: !GetAtt ServiceRole.Arn
Artifacts:
Type: CODEPIPELINE
Environment:
Type: LINUX_CONTAINER
ComputeType: BUILD_GENERAL1_SMALL
Image: aws/codebuild/standard:2.0
Source:
Type: CODEPIPELINE
DeploymentApplication:
Type: AWS::CodeDeploy::Application
Properties:
ApplicationName: !Ref AWS::StackName
ComputePlatform: Server
StagingDeploymentGroup:
Type: AWS::CodeDeploy::DeploymentGroup
DependsOn: StagingInstance
Properties:
DeploymentGroupName: staging
ApplicationName: !Ref DeploymentApplication
DeploymentConfigName: CodeDeployDefault.AllAtOnce
Ec2TagFilters:
- Key: !Ref AWS::StackName
Type: KEY_AND_VALUE
Value: staging
ServiceRoleArn: !GetAtt ServiceRole.Arn
Pipeline:
Type: AWS::CodePipeline::Pipeline
Properties:
Name: !Ref AWS::StackName
ArtifactStore:
Location: !Ref BuildArtifactsBucket
Type: S3
RoleArn: !GetAtt ServiceRole.Arn
Stages:
- Name: Source
Actions:
- Name: Source
ActionTypeId:
Category: Source
Owner: ThirdParty
Version: 1
Provider: GitHub
OutputArtifacts:
- Name: Source
Configuration:
Owner: !Ref GitHubOwner
Repo: !Ref GitHubRepo
Branch: !Ref GitHubBranch
OAuthToken: !Ref GitHubPersonalAccessToken
PollForSourceChanges: false
RunOrder: 1
- Name: Build
Actions:
- Name: Build
ActionTypeId:
Category: Build
Owner: AWS
Version: 1
Provider: CodeBuild
InputArtifacts:
- Name: Source
OutputArtifacts:
- Name: Build
Configuration:
ProjectName: !Ref BuildProject
RunOrder: 1
- Name: Deploy
Actions:
- Name: Staging
InputArtifacts:
- Name: Build
ActionTypeId:
Category: Deploy
Owner: AWS
Version: 1
Provider: CodeDeploy
Configuration:
ApplicationName: !Ref DeploymentApplication
DeploymentGroupName: !Ref StagingDeploymentGroup
RunOrder: 1
BuildArtifactsBucket:
Type: AWS::S3::Bucket
DeletionPolicy: Retain
Properties:
BucketName: !Sub
- 'codepipeline-${id}'
- { id: !Select [2, !Split [/, !Ref 'AWS::StackId']] }
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: AES256
PipelineWebhook:
Type: AWS::CodePipeline::Webhook
Properties:
Authentication: GITHUB_HMAC
AuthenticationConfiguration:
SecretToken: !Ref GitHubPersonalAccessToken
Filters:
- JsonPath: $.ref
MatchEquals: 'refs/heads/{Branch}'
TargetPipeline: !Ref Pipeline
TargetAction: Source
Name: !Sub 'webhook-${AWS::StackName}'
TargetPipelineVersion: !GetAtt Pipeline.Version
RegisterWithThirdParty: true
ServiceRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
Effect: Allow
Principal:
Service:
- codepipeline.amazonaws.com
- codedeploy.amazonaws.com
- codebuild.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/PowerUserAccess
StagingInstance:
Type: AWS::EC2::Instance
CreationPolicy:
ResourceSignal:
Timeout: PT5M
Properties:
SubnetId: !Ref Subnet
IamInstanceProfile: !Ref InstanceProfile
ImageId: !Ref EC2AMI
InstanceType: !Ref EC2InstanceType
SecurityGroupIds:
- !GetAtt SecurityGroup.GroupId
Tags:
- Key: !Ref AWS::StackName
Value: staging
UserData:
Fn::Base64:
!Join
- ''
- - !Sub |
#!/bin/bash
# Get Node.js 12
curl --silent --location https://rpm.nodesource.com/setup_12.x | bash -
# Update all packages
yum -y update
# Install dependencies
yum install -y ruby nodejs
# Install CodeDeploy agent
cd /tmp
curl -O https://aws-codedeploy-${AWS::Region}.s3.amazonaws.com/latest/install
chmod +x ./install
./install auto
- !If
- HasDomain
- !Sub |
# Create a self-signed TLS certificate to communicate with the load balancer
mkdir -p /home/ec2-user/keys
cd /home/ec2-user/keys
openssl req -new -newkey rsa:4096 -days 365 -nodes -x509 -subj "/C=/ST=/L=/O=/CN=localhost" -keyout key.pem -out cert.pem
chown -R ec2-user:ec2-user /home/ec2-user/keys
- ''
- !Sub |
# Signal to CloudFormation that the instance is ready
/opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --region ${AWS::Region} --resource StagingInstance
/opt/aws/bin/cfn-signal -e 0 --stack ${AWS::StackName} --region ${AWS::Region} --resource StagingInstance
# Deploy to outdated instances
aws deploy create-deployment --application-name ${AWS::StackName} --deployment-group staging --update-outdated-instances-only --region ${AWS::Region}
SecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: !Sub 'Security group from ${AWS::StackName} staging host'
VpcId: !Ref VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: !If [HasDomain, 8443, 8080]
ToPort: !If [HasDomain, 8443, 8080]
CidrIp: !GetAtt VPC.CidrBlock
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: 0.0.0.0/0
InstanceProfile:
Type: "AWS::IAM::InstanceProfile"
Properties:
Roles:
- Ref: InstanceRole
InstanceRole:
Type: "AWS::IAM::Role"
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
Effect: Allow
Principal:
Service:
- "ec2.amazonaws.com"
- "codedeploy.amazonaws.com"
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/PowerUserAccess
StagingDNS:
Type: AWS::Route53::RecordSet
Condition: HasDomain
Properties:
HostedZoneName: !Sub '${Domain}.'
Name: !Sub 'staging.${Domain}.'
Type: A
AliasTarget:
HostedZoneId: !GetAtt StagingLoadBalancer.CanonicalHostedZoneID
DNSName: !GetAtt StagingLoadBalancer.DNSName
StagingLoadBalancer:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
Type: network
Subnets:
- !Ref Subnet
StagingLoadBalancerListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
DefaultActions:
- Type: forward
TargetGroupArn: !Ref StagingLoadBalancerTargetGroup
LoadBalancerArn: !Ref StagingLoadBalancer
Certificates:
- CertificateArn: !If [HasDomain, !Ref Certificate, !Ref 'AWS::NoValue']
Port: !If [HasDomain, 443, 80]
Protocol: !If [HasDomain, TLS, TCP]
StagingLoadBalancerTargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
TargetType: ip
Port: !If [HasDomain, 8443, 8080]
Protocol: !If [HasDomain, TLS, TCP]
VpcId: !Ref VPC
HealthCheckEnabled: true
HealthCheckProtocol: !If [HasDomain, HTTPS, HTTP]
Targets:
- Id: !GetAtt StagingInstance.PrivateIp
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/16
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: Name
Value: !Ref AWS::StackName
InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: !Ref AWS::StackName
InternetGatewayAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
InternetGatewayId: !Ref InternetGateway
VpcId: !Ref VPC
RouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Ref AWS::StackName
DefaultPublicRoute:
Type: AWS::EC2::Route
DependsOn: InternetGatewayAttachment
Properties:
RouteTableId: !Ref RouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
SubnetRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref RouteTable
SubnetId: !Ref Subnet
Subnet:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
AvailabilityZone: !Select [ 0, !GetAZs '' ]
CidrBlock: 10.0.1.0/24
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: !Ref AWS::StackName
You can’t perform that action at this time.