Skip to content

Commit 9ee25c7

Browse files
committed
initial import
0 parents  commit 9ee25c7

16 files changed

+886
-0
lines changed

.editorconfig

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
root = true
2+
3+
[*]
4+
charset = utf-8
5+
indent_style = space
6+
indent_size = 2
7+
end_of_line = lf
8+
insert_final_newline = true
9+
trim_trailing_whitespace = true
10+
11+
[*.md]
12+
insert_final_newline = false
13+
trim_trailing_whitespace = false

.envrc.example

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#!/usr/bin/env bash
2+
3+
export AWS_ACCESS_KEY_ID=
4+
export AWS_SECRET_ACCESS_KEY=
5+
export AWS_DEFAULT_REGION=
6+
export AWS_REGION=
7+
8+
export PROD_ROLE_ARN=
9+
10+
export SLACK_WEBHOOK_URL=
11+
12+
unset AWS_SESSION_TOKEN

.gitignore

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.idea
2+
.serverless
3+
node_modules
4+
.envrc
5+
*.iml

.npmignore

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# package directories
2+
node_modules
3+
jspm_packages
4+
5+
# Serverless directories
6+
.serverless

.travis.yml

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
---
2+
sudo: required
3+
dist: trusty
4+
language: node_js
5+
cache:
6+
directories:
7+
- $HOME/.yarn-cache
8+
- $HOME/.cache/pip
9+
node_js:
10+
- '4.3.2'
11+
12+
before_install:
13+
- npm install -g yarn serverless
14+
install:
15+
- yarn install
16+
17+
script:
18+
# TODO
19+
- echo "do some tests..."
20+
21+
before_deploy:
22+
# Parse branch name and determine an environment to deploy
23+
- export ENV=$(echo "${TRAVIS_BRANCH}" | perl -ne "print $& if /(?<=deploy\/).*/")
24+
# install aws cli
25+
- sudo apt-get -y install python-pip
26+
- sudo pip install awscli
27+
- aws --version
28+
# account number
29+
- account_number=$(aws sts get-caller-identity --output text --query 'Account')
30+
deploy:
31+
- provider: script
32+
script: ./scripts/deploy.sh | sed -e "s/${account_number}/SECRET/g"
33+
skip_cleanup: true
34+
on:
35+
branch: deploy/*

functions/notify_to_slack.js

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
'use strict';
2+
3+
const notifyToSlack = require('./notify_to_slack/index');
4+
5+
module.exports.handle = (event, context, callback) => {
6+
7+
notifyToSlack(event);
8+
callback(null, {});
9+
10+
};
11+

functions/notify_to_slack/index.js

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
'use strict';
2+
3+
require('dotenv').config();
4+
const IncomingWebhooks = require('@slack/client').IncomingWebhook;
5+
6+
module.exports = function (obj) {
7+
const wh = new IncomingWebhooks(process.env.SLACK_WEBHOOK_URL);
8+
9+
wh.send(JSON.stringify(obj, null, 2));
10+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
'use strict';
2+
3+
const removeUnusedAndRotatedImages = require('./remove_unused_and_rotated_images/index');
4+
5+
module.exports.handle = (event, context, callback) => {
6+
7+
removeUnusedAndRotatedImages().then(() => {
8+
callback(null, {});
9+
}).catch(e => {
10+
console.error(e);
11+
});
12+
13+
};
14+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
'use strict';
2+
3+
const AWS = require('aws-sdk');
4+
const _ = require('lodash');
5+
6+
const EC2 = new AWS.EC2();
7+
const AutoScaling = new AWS.AutoScaling();
8+
9+
module.exports = function () {
10+
return Promise.all([rotatedImages(), usedImageIds()])
11+
.then(_.spread((rotatedImages, usedImageIds) => {
12+
return _.reject(rotatedImages, image => {
13+
return _.includes(usedImageIds, image.ImageId);
14+
});
15+
}))
16+
.then(images => {
17+
const promises = images.map(image => {
18+
return deregisterImage(image)
19+
.then(deleteSnapShot)
20+
});
21+
return Promise.all(promises);
22+
});
23+
};
24+
25+
function rotatedImages() {
26+
return EC2.describeImages({
27+
Filters: [
28+
{
29+
Name: 'tag:Rotated',
30+
Values: ['true'],
31+
},
32+
],
33+
}).promise()
34+
.then(data => data.Images);
35+
}
36+
37+
function usedImageIds() {
38+
return AutoScaling.describeLaunchConfigurations({}).promise()
39+
.then(data => {
40+
return data.LaunchConfigurations.map(lc => lc.ImageId);
41+
});
42+
}
43+
44+
function deregisterImage(image) {
45+
return EC2.deregisterImage({
46+
ImageId: image.ImageId,
47+
}).promise()
48+
.then(() => {
49+
console.log(`deregistered ${image.ImageId}`);
50+
return image;
51+
})
52+
// ignore error and continue
53+
.catch(() => image)
54+
}
55+
56+
function deleteSnapShot(image) {
57+
const snapShotId = _.chain(image.BlockDeviceMappings)
58+
.flatten()
59+
.map(m => m.Ebs)
60+
.compact()
61+
.map(m => m.SnapshotId)
62+
.first()
63+
.value();
64+
65+
return EC2.deleteSnapshot({
66+
SnapshotId: snapShotId,
67+
}).promise()
68+
.then(() => {
69+
console.log(`deleted ${snapShotId}`);
70+
return image;
71+
})
72+
.catch(() => image)
73+
}
74+

functions/replace_web_instances.js

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
'use strict';
2+
3+
const replaceAutoScalingInstances = require('./replace_web_instances/index');
4+
5+
module.exports.handle = (event, context, callback) => {
6+
7+
replaceAutoScalingInstances().then(() => {
8+
callback(null, {});
9+
}).catch(e => {
10+
console.error(e);
11+
});
12+
13+
};
+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
const AWS = require('aws-sdk');
2+
3+
// AWS.config.region = process.env.AWS_DEFAULT_REGION;
4+
const AutoScaling = new AWS.AutoScaling();
5+
6+
module.exports = function () {
7+
return AutoScaling.describeAutoScalingGroups({
8+
AutoScalingGroupNames: ['web'],
9+
}).promise()
10+
.then(data => data.AutoScalingGroups[0])
11+
.then(validateAsgCapacity)
12+
.then(multipleDesiredCapacity);
13+
};
14+
15+
function validateAsgCapacity(asg) {
16+
if (asg.DesiredCapacity * 2 > asg.MaxSize) {
17+
throw 'Max capacity is too small so that it can not replace instances.';
18+
}
19+
console.log(`Current desired capacity is ${asg.DesiredCapacity}`);
20+
21+
return asg;
22+
}
23+
24+
function multipleDesiredCapacity(asg) {
25+
console.log(`Scale out to ${asg.DesiredCapacity * 2}.`);
26+
27+
return AutoScaling.setDesiredCapacity({
28+
AutoScalingGroupName: asg.AutoScalingGroupName,
29+
DesiredCapacity: asg.DesiredCapacity * 2,
30+
HonorCooldown: false,
31+
}).promise();
32+
}
33+

package.json

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"dependencies": {
3+
"@slack/client": "^3.6.0",
4+
"dotenv": "^2.0.0",
5+
"lodash": "^4.16.4"
6+
},
7+
"devDependencies": {
8+
"aws-sdk": "^2.6.12",
9+
"serverless-plugin-write-env-vars": "^1.0.1"
10+
}
11+
}

scripts/deploy.sh

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#!/usr/bin/env bash
2+
3+
set -u
4+
5+
ENV=$1
6+
7+
if [ "${ENV}" = "prod" ]; then
8+
# reset current role if exists
9+
test ! -v AWS_SESSION_TOKEN && direnv reload
10+
# switch to production role
11+
source scripts/switch-production-role.sh
12+
fi
13+
14+
sls deploy -v --stage ${ENV}
15+
16+
if [ "${ENV}" = "prod" ]; then
17+
# reset current role if exists
18+
test ! -v AWS_SESSION_TOKEN && direnv reload
19+
fi

scripts/switch-production-role.sh

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#!/usr/bin/env bash
2+
3+
credentials=$(aws sts assume-role --role-arn ${PROD_ROLE_ARN} --role-session-name travisci)
4+
5+
export AWS_ACCESS_KEY_ID=$(echo ${credentials} | jq --raw-output .Credentials.AccessKeyId)
6+
export AWS_SECRET_ACCESS_KEY=$(echo ${credentials} | jq --raw-output .Credentials.SecretAccessKey)
7+
export AWS_SESSION_TOKEN=$(echo ${credentials} | jq --raw-output .Credentials.SessionToken)
8+
9+
unset credentials
10+
11+
echo "Switched to production role."

serverless.yml

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
service: micropost
2+
3+
provider:
4+
name: aws
5+
runtime: nodejs4.3
6+
region: ap-northeast-1
7+
stage: ${opt:stage, self:custom.defaultStage}
8+
iamRoleStatements:
9+
- Effect: "Allow"
10+
Action:
11+
- "ec2:DescribeImages"
12+
- "ec2:DeregisterImage"
13+
- "ec2:DeleteSnapshot"
14+
- "autoscaling:DescribeAutoScalingGroups"
15+
- "autoscaling:SetDesiredCapacity"
16+
- "autoscaling:DescribeLaunchConfigurations"
17+
Resource: "*"
18+
19+
functions:
20+
notifyToSlack:
21+
handler: functions/notify_to_slack.handle
22+
events:
23+
- sns: frontend_deployed
24+
- sns: web_autoscaled
25+
removeUnusedAndRotatedImages:
26+
handler: functions/remove_unused_and_rotated_images.handle
27+
events:
28+
- sns: web_image_updated
29+
replaceWebInstances:
30+
handler: functions/replace_web_instances.handle
31+
events:
32+
- sns: backend_app_updated
33+
- sns: web_image_updated
34+
35+
custom:
36+
defaultStage: stg
37+
writeEnvVars:
38+
SLACK_WEBHOOK_URL: ${env:SLACK_WEBHOOK_URL}
39+
40+
plugins:
41+
- serverless-plugin-write-env-vars

0 commit comments

Comments
 (0)