├── README.md
├── app
└── index.html
├── appspec.yml
├── deploy
├── application_start.sh
├── before_install.sh
├── stack.yml
└── validate_service.sh
├── lambda
└── index.js
└── setup.sh
/README.md:
--------------------------------------------------------------------------------
1 | # Example of a Continuous Deliver pipeline with CodePipeline and CodeDeploy
2 |
3 | Creating a simple Continuous Delivery pipeline allowing you to push changes from your GitHub repository to your EC2 Instances.
4 |
5 | ## Services
6 |
7 | The following AWS services are used to create a Continuous Delivery pipeline.
8 |
9 | * CodePipeline
10 | * CodeDeploy
11 | * CloudFormation
12 | * EC2
13 | * S3
14 | * IAM
15 |
16 | ## Setup
17 |
18 | Fork this repository.
19 |
20 | Use the `./setup.sh` script to create a Continuous Delivery pipeline.
21 |
22 | Note: The script will create a CloudFormation stack which launches an EC2 instance into the default VPC of your default region.
23 |
24 | The script will ask you for:
25 | * GitHub repository
26 | * GitHub owner (username of individual or organisation)
27 | * GitHub [OAuth Token with access to Repo](https://github.com/settings/tokens).
28 |
29 | It might take a few minutes until the CodePipeline job has finished and the EC2 Instance is able to answer your HTTP request.
--------------------------------------------------------------------------------
/app/index.html:
--------------------------------------------------------------------------------
1 |
Automation for the people!
--------------------------------------------------------------------------------
/appspec.yml:
--------------------------------------------------------------------------------
1 | version: 0.0
2 | os: linux
3 | files:
4 | - source: app/index.html
5 | destination: /var/www/html
6 | hooks:
7 | BeforeInstall:
8 | - location: deploy/before_install.sh
9 | timeout: 180
10 | ApplicationStart:
11 | - location: deploy/application_start.sh
12 | timeout: 180
13 | ValidateService:
14 | - location: deploy/validate_service.sh
15 | timeout: 180
--------------------------------------------------------------------------------
/deploy/application_start.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash -e
2 | service httpd start
--------------------------------------------------------------------------------
/deploy/before_install.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash -e
2 | yum install -y httpd
--------------------------------------------------------------------------------
/deploy/stack.yml:
--------------------------------------------------------------------------------
1 | ---
2 | AWSTemplateFormatVersion: '2010-09-09'
3 | Description: 'Continuous Delivery pipeline for static website'
4 | Parameters:
5 | GitHubOwner:
6 | Description: 'The owner of the GitHub repository.'
7 | Type: String
8 | GitHubOAuthToken:
9 | Description: 'The OAuthToken of the GitHub user.'
10 | Type: String
11 | GitHubRepo:
12 | Description: 'The GitHub repository.'
13 | Type: String
14 | VPC:
15 | Description: 'The VPC Id to launch the EC2 instance in.'
16 | Type: AWS::EC2::VPC::Id
17 | Subnet:
18 | Description: 'The Subnet Id to launch the EC2 instance in.'
19 | Type: AWS::EC2::Subnet::Id
20 | Mappings:
21 | RegionMap:
22 | 'ap-south-1':
23 | AMI: 'ami-cacbbea5'
24 | 'eu-west-1':
25 | AMI: 'ami-d41d58a7'
26 | 'ap-northeast-2':
27 | AMI: 'ami-a04297ce'
28 | 'ap-northeast-1':
29 | AMI: 'ami-1a15c77b'
30 | 'sa-east-1':
31 | AMI: 'ami-b777e4db'
32 | 'ap-southeast-1':
33 | AMI: 'ami-7243e611'
34 | 'ap-southeast-2':
35 | AMI: 'ami-55d4e436'
36 | 'eu-central-1':
37 | AMI: 'ami-0044b96f'
38 | 'us-east-1':
39 | AMI: 'ami-c481fad3'
40 | 'us-east-2':
41 | AMI: 'ami-71ca9114'
42 | 'us-west-1':
43 | AMI: 'ami-de347abe'
44 | 'us-west-2':
45 | AMI: 'ami-b04e92d0'
46 | Resources:
47 | ArtifactStore:
48 | Type: "AWS::S3::Bucket"
49 | Properties:
50 | VersioningConfiguration:
51 | Status: Enabled
52 | CodePipelineIAMRole:
53 | Type: 'AWS::IAM::Role'
54 | Properties:
55 | AssumeRolePolicyDocument:
56 | Version: '2012-10-17'
57 | Statement:
58 | - Effect: Allow
59 | Principal:
60 | Service:
61 | - 'codepipeline.amazonaws.com'
62 | Action:
63 | - 'sts:AssumeRole'
64 | Path: '/'
65 | Policies:
66 | - PolicyName: logs
67 | PolicyDocument:
68 | Version: '2012-10-17'
69 | Statement:
70 | - Action:
71 | - s3:GetObject
72 | - s3:GetObjectVersion
73 | - s3:GetBucketVersioning
74 | Resource: "*"
75 | Effect: Allow
76 | - Action:
77 | - s3:PutObject
78 | Resource:
79 | - arn:aws:s3:::codepipeline*
80 | - arn:aws:s3:::elasticbeanstalk*
81 | Effect: Allow
82 | - Action:
83 | - codecommit:CancelUploadArchive
84 | - codecommit:GetBranch
85 | - codecommit:GetCommit
86 | - codecommit:GetUploadArchiveStatus
87 | - codecommit:UploadArchive
88 | Resource: "*"
89 | Effect: Allow
90 | - Action:
91 | - codedeploy:CreateDeployment
92 | - codedeploy:GetApplicationRevision
93 | - codedeploy:GetDeployment
94 | - codedeploy:GetDeploymentConfig
95 | - codedeploy:RegisterApplicationRevision
96 | Resource: "*"
97 | Effect: Allow
98 | - Action:
99 | - elasticbeanstalk:*
100 | - ec2:*
101 | - elasticloadbalancing:*
102 | - autoscaling:*
103 | - cloudwatch:*
104 | - s3:*
105 | - sns:*
106 | - cloudformation:*
107 | - rds:*
108 | - sqs:*
109 | - ecs:*
110 | - iam:PassRole
111 | Resource: "*"
112 | Effect: Allow
113 | - Action:
114 | - lambda:InvokeFunction
115 | - lambda:ListFunctions
116 | Resource: "*"
117 | Effect: Allow
118 | - Action:
119 | - opsworks:CreateDeployment
120 | - opsworks:DescribeApps
121 | - opsworks:DescribeCommands
122 | - opsworks:DescribeDeployments
123 | - opsworks:DescribeInstances
124 | - opsworks:DescribeStacks
125 | - opsworks:UpdateApp
126 | - opsworks:UpdateStack
127 | Resource: "*"
128 | Effect: Allow
129 | CodePipeline:
130 | DependsOn:
131 | - EC2Instance
132 | Type: "AWS::CodePipeline::Pipeline"
133 | Properties:
134 | ArtifactStore:
135 | Location: !Ref ArtifactStore
136 | Type: S3
137 | RoleArn: !Sub '${CodePipelineIAMRole.Arn}'
138 | Stages:
139 | - Name: Source
140 | Actions:
141 | - Name: Source
142 | ActionTypeId:
143 | Category: Source
144 | Owner: ThirdParty
145 | Version: 1
146 | Provider: GitHub
147 | OutputArtifacts:
148 | - Name: staticwebsite
149 | Configuration:
150 | Owner: !Ref GitHubOwner
151 | Repo: !Ref GitHubRepo
152 | Branch: master
153 | OAuthToken: !Ref GitHubOAuthToken
154 | - Name: Deploy
155 | Actions:
156 | - Name: Deploy
157 | ActionTypeId:
158 | Category: Deploy
159 | Owner: AWS
160 | Version: 1
161 | Provider: CodeDeploy
162 | InputArtifacts:
163 | - Name: staticwebsite
164 | Configuration:
165 | ApplicationName: !Ref Application
166 | DeploymentGroupName: !Ref DeploymentGroup
167 | - Name: Test
168 | Actions:
169 | - Name: Test
170 | ActionTypeId:
171 | Category: Invoke
172 | Owner: AWS
173 | Version: 1
174 | Provider: Lambda
175 | Configuration:
176 | FunctionName: !Ref TestLambda
177 | UserParameters: !Sub 'http://${EC2Instance.PublicDnsName}'
178 | CodeDeployIAMRole:
179 | Type: 'AWS::IAM::Role'
180 | Properties:
181 | AssumeRolePolicyDocument:
182 | Version: '2012-10-17'
183 | Statement:
184 | - Effect: Allow
185 | Principal:
186 | Service:
187 | - 'codedeploy.amazonaws.com'
188 | Action:
189 | - 'sts:AssumeRole'
190 | Path: '/'
191 | ManagedPolicyArns:
192 | - 'arn:aws:iam::aws:policy/service-role/AWSCodeDeployRole'
193 | Application:
194 | Type: "AWS::CodeDeploy::Application"
195 | DeploymentGroup:
196 | Type: "AWS::CodeDeploy::DeploymentGroup"
197 | Properties:
198 | ApplicationName: !Ref Application
199 | Ec2TagFilters:
200 | - Key: DeploymentGroup
201 | Type: KEY_AND_VALUE
202 | Value: !Ref AWS::StackName
203 | ServiceRoleArn: !Sub '${CodeDeployIAMRole.Arn}'
204 | SecurityGroup:
205 | Type: 'AWS::EC2::SecurityGroup'
206 | Properties:
207 | GroupDescription: 'static website'
208 | VpcId: !Ref VPC
209 | SecurityGroupIngress:
210 | - FromPort: 80
211 | ToPort: 80
212 | IpProtocol: tcp
213 | CidrIp: '0.0.0.0/0'
214 | EC2InstanceProfile:
215 | Type: 'AWS::IAM::InstanceProfile'
216 | Properties:
217 | Path: '/'
218 | Roles:
219 | - !Ref EC2InstanceIAMRole
220 | EC2InstanceIAMRole:
221 | Type: 'AWS::IAM::Role'
222 | Properties:
223 | AssumeRolePolicyDocument:
224 | Version: '2012-10-17'
225 | Statement:
226 | - Effect: Allow
227 | Principal:
228 | Service:
229 | - 'ec2.amazonaws.com'
230 | Action:
231 | - 'sts:AssumeRole'
232 | Path: '/'
233 | ManagedPolicyArns:
234 | - 'arn:aws:iam::aws:policy/service-role/AmazonEC2RoleforAWSCodeDeploy'
235 | EC2Instance:
236 | Type: 'AWS::EC2::Instance'
237 | Properties:
238 | IamInstanceProfile: !Ref EC2InstanceProfile
239 | ImageId: !FindInMap [RegionMap, !Ref 'AWS::Region', AMI]
240 | InstanceType: 't2.micro'
241 | SecurityGroupIds:
242 | - !Ref SecurityGroup
243 | UserData:
244 | 'Fn::Base64': !Sub |
245 | #!/bin/bash -x
246 | yum update -y && yum install -y ruby wget && wget https://aws-codedeploy-us-east-1.s3.amazonaws.com/latest/install && chmod +x ./install && ./install auto && service codedeploy-agent start
247 | /opt/aws/bin/cfn-signal -e $? --region ${AWS::Region} --stack ${AWS::StackName} --resource EC2Instance
248 | SubnetId: !Ref Subnet
249 | Tags:
250 | - Key: DeploymentGroup
251 | Value: !Ref AWS::StackName
252 | - Key: Name
253 | Value: !Ref AWS::StackName
254 | CreationPolicy:
255 | ResourceSignal:
256 | Count: 1
257 | Timeout: PT10M
258 | LambdaIAMRole:
259 | Type: 'AWS::IAM::Role'
260 | Properties:
261 | AssumeRolePolicyDocument:
262 | Version: '2012-10-17'
263 | Statement:
264 | - Effect: Allow
265 | Principal:
266 | Service:
267 | - 'lambda.amazonaws.com'
268 | Action:
269 | - 'sts:AssumeRole'
270 | Path: '/'
271 | ManagedPolicyArns:
272 | - 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'
273 | Policies:
274 | - PolicyName: codepipeline
275 | PolicyDocument:
276 | Version: '2012-10-17'
277 | Statement:
278 | - Action:
279 | - codepipeline:PutJobFailureResult
280 | - codepipeline:PutJobSuccessResult
281 | Resource: "*"
282 | Effect: Allow
283 | TestLambda:
284 | Type: "AWS::Lambda::Function"
285 | Properties:
286 | Code:
287 | S3Bucket: 'codepipeline-codedeploy-example-lambda'
288 | S3Key: 'v2.zip'
289 | FunctionName: 'codepipeline_http_test'
290 | Handler: 'index.handler'
291 | MemorySize: 128
292 | Role: !Sub '${LambdaIAMRole.Arn}'
293 | Runtime: 'nodejs4.3'
294 | Timeout: 30
295 | Outputs:
296 | URL:
297 | Description: 'The URL pointing to the static website.'
298 | Value: !Sub 'http://${EC2Instance.PublicDnsName}'
--------------------------------------------------------------------------------
/deploy/validate_service.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash -e
2 |
3 | curl -m 5 http://localhost
--------------------------------------------------------------------------------
/lambda/index.js:
--------------------------------------------------------------------------------
1 | var AWS = require('aws-sdk');
2 | var http = require('http');
3 |
4 | exports.handler = function(event, context) {
5 |
6 | var codepipeline = new AWS.CodePipeline();
7 | var job = event["CodePipeline.job"]
8 |
9 | var jobId = job.id;
10 | var url = job.data.actionConfiguration.configuration.UserParameters;
11 |
12 | function success(message) {
13 | codepipeline.putJobSuccessResult({
14 | jobId: jobId
15 | }, function(err, data) {
16 | if(err) {
17 | context.fail(err);
18 | } else {
19 | context.succeed(message);
20 | }
21 | });
22 | }
23 |
24 | function failure(message) {
25 | codepipeline.putJobFailureResult({
26 | jobId: jobId,
27 | failureDetails: {
28 | message: JSON.stringify(message),
29 | type: 'JobFailed',
30 | externalExecutionId: context.invokeid
31 | }
32 | }, function(err, data) {
33 | if(err) {
34 | context.fail(err);
35 | } else {
36 | context.succeed(message);
37 | }
38 | });
39 | }
40 |
41 | http.get(url, function(response) {
42 | var body = '';
43 | response.on('data', function (chunk) {
44 | body += chunk;
45 | });
46 | response.on('end', function () {
47 | if (response.statusCode === 200 && body.includes("Automation for the people
")) {
48 | success("URL check successful.")
49 | } else {
50 | console.log(response.statusCode);
51 | console.log8(body);
52 | failure("Invalid status code or content.");
53 | }
54 | });
55 | }).on('error', function(error) {
56 | failure(error);
57 | });
58 |
59 | };
--------------------------------------------------------------------------------
/setup.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash -e
2 |
3 | DEFAULT_VPC=$(aws ec2 describe-vpcs --filter Name=isDefault,Values=true --query Vpcs[0].VpcId --output text)
4 | SUBNET=$(aws ec2 describe-subnets --filter Name=vpc-id,Values=$DEFAULT_VPC --query Subnets[0].SubnetId --output text)
5 |
6 | echo "Enter the Stack name:"
7 | read STACK_NAME
8 |
9 | echo "Enter the GitHub repository:"
10 | read GITHUB_REPO
11 |
12 | echo "Enter the GitHub owner:"
13 | read GITHUB_OWNER
14 |
15 | echo "Enter the GitHub OAuth Token:"
16 | read GITHUB_OAUTH_TOKEN
17 |
18 | aws cloudformation create-stack --stack-name $STACK_NAME --template-body file://deploy/stack.yml --parameters ParameterKey=GitHubOwner,ParameterValue=$GITHUB_OWNER ParameterKey=GitHubOAuthToken,ParameterValue=$GITHUB_OAUTH_TOKEN ParameterKey=GitHubRepo,ParameterValue=$GITHUB_REPO ParameterKey=VPC,ParameterValue=$DEFAULT_VPC ParameterKey=Subnet,ParameterValue=$SUBNET --capabilities CAPABILITY_IAM
19 |
20 | echo "Creating the CloudFormation stack, this will take a few minutes ..."
21 |
22 | aws cloudformation wait stack-create-complete --stack-name $STACK_NAME
23 |
24 | echo "Done! Send an HTTP request to the following URL, please."
25 | aws cloudformation describe-stacks --stack-name $STACK_NAME --query 'Stacks[0].Outputs[?OutputKey==`URL`].OutputValue' --output text
--------------------------------------------------------------------------------