Skip to content

Instantly share code, notes, and snippets.

@nwalke

nwalke/lambda.js Secret

Last active April 19, 2017 16:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nwalke/d8e77630f7a8bdd67c93f927163a7740 to your computer and use it in GitHub Desktop.
Save nwalke/d8e77630f7a8bdd67c93f927163a7740 to your computer and use it in GitHub Desktop.
var AWS = require('aws-sdk');
var autoscaling = new AWS.AutoScaling();
var codedeploy = new AWS.CodeDeploy();
var dynamodb = new AWS.DynamoDB();
var dynamodbTableName = "codedeploy_deploy_tracking";
var processes = [
'AlarmNotification',
'AZRebalance',
'ReplaceUnhealthy',
'ScheduledActions'
];
var counter = 0;
var waiterAttempts = 120;
var waiterInterval = 1000;
var getAutoscalingGroupSizing = function getAutoscalingGroupSizing(asgName) {
var params = {
AutoScalingGroupNames: [asgName]
};
return autoscaling.describeAutoScalingGroups(params).promise().then((response) => {
var asg = response.AutoScalingGroups[0];
return {
Desired: asg.DesiredCapacity,
Min: asg.MinSize,
Max: asg.MaxSize
};
});
};
var getSizingFromDynamo = function getSizingFromDynamo(deploymentId) {
var params = {
Key: {
"deploymentId": {
S: deploymentId
}
},
TableName: dynamodbTableName
};
return dynamodb.getItem(params).promise().then((response) => {
return JSON.parse(response.Item.sizing.S);
});
};
var saveSizingToDynamo = function saveSizingToDynamo(asgName, deploymentId) {
console.log('Detected a user deploy. Saving sizing to Dynamo.');
return getAutoscalingGroupSizing(asgName).then((sizing) => {
var params = {
Item: {
"deploymentId" : {
S: deploymentId
},
"sizing" : {
S: JSON.stringify(sizing)
}
},
TableName: dynamodbTableName
};
return dynamodb.putItem(params).promise();
});
};
var cleanupDynamo = function cleanupDynamo(deploymentId) {
console.log('Cleaning up dynamodb record.');
var params = {
Key: {
"deploymentId" : {
S: deploymentId
}
},
TableName: dynamodbTableName
};
return dynamodb.deleteItem(params).promise();
};
var resetAutoscalingGroupSizing = function resetAutoscalingGroupSizing(asgName, deploymentId) {
console.log('Resetting the ' + asgName + ' ASG sizing back to sizes saved at the start of deploy.');
return getSizingFromDynamo(deploymentId).then((sizing) => {
var params = {
AutoScalingGroupName: asgName,
DesiredCapacity: sizing.Desired,
MaxSize: sizing.Max,
MinSize: sizing.Min,
};
return autoscaling.updateAutoScalingGroup(params).promise();
});
};
var checkUserDeployment = function checkUserDeployment(deploymentId) {
return codedeploy.getDeployment({deploymentId: deploymentId}).promise()
.then(response => response.deploymentInfo.creator === 'user');
};
var pauseAutoscalingGroup = function pauseAutoscalingGroup(asgName) {
console.log('Pausing the ' + asgName + ' ASG.');
var params = {
AutoScalingGroupName: asgName,
ScalingProcesses: processes
};
return autoscaling.suspendProcesses(params).promise();
};
var resumeAutoscalingGroup = function resumeAutoscalingGroup(asgName) {
console.log('Resuming the ' + asgName + ' ASG.');
var params = {
AutoScalingGroupName: asgName,
ScalingProcesses: processes
};
return autoscaling.resumeProcesses(params).promise();
};
var terminateInstance = function terminateInstance(instance) {
var params = {
InstanceId: instance,
ShouldDecrementDesiredCapacity: false
};
return autoscaling.terminateInstanceInAutoScalingGroup(params).promise();
};
var terminateStandbyInstances = function terminateStandbyInstances(asgName) {
console.log('Dealing with any instances left in the standby state.');
var params = {
AutoScalingGroupNames: [asgName]
};
return autoscaling.describeAutoScalingGroups(params).promise()
.then((response) => {
const promises = [];
response.AutoScalingGroups.forEach((autoscalingGroup) => {
autoscalingGroup.Instances.forEach((instance) => {
if (instance.LifecycleState !== 'Standby') {
return;
}
console.log('Terminating instance ' + instance.InstanceId);
promises.push(terminateInstance(instance.InstanceId));
});
});
return Promise.all(promises);
});
};
var waitForAllInstances = function waitForAllInstances(asgName) {
console.log('Waiting for all instances to be InService.');
var params = {
AutoScalingGroupNames: [asgName]
};
return autoscaling.describeAutoScalingGroups(params).promise()
.then((response) => {
if (counter >= waiterAttempts) {
throw new Error('Exceeded waiterAttempts for instances to be InService.');
}
const hasAnyNotInService = response.AutoScalingGroups.some(autoscalingGroup =>
autoscalingGroup.Instances.some(instance => instance.LifecycleState !== 'InService')
);
if (hasAnyNotInService) {
counter++;
return sleep(waiterInterval).then(() => waitForAllInstances(asgName));
}
return 'Success';
});
};
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function handleCreated(message) {
console.log('Detected start of deployment. Checking if this was user initiated or via autoscaling.');
return checkUserDeployment(message.deploymentId).then((isCreatorUser) => {
if (isCreatorUser) {
return waitForAllInstances(message.applicationName)
.then(() => saveSizingToDynamo(message.applicationName, message.deploymentId))
.then(() => pauseAutoscalingGroup(message.applicationName))
.then(() => "Success");
}
else {
return "Not a user deploy";
}
});
}
function handleSucceeded(message) {
console.log('Detected successful deployment. Checking if this deploy was user initiated or via autoscaling.');
return checkUserDeployment(message.deploymentId).then((isCreatorUser) => {
if (isCreatorUser) {
console.log('Detected a user deploy.');
return resumeAutoscalingGroup(message.applicationName)
.then(() => cleanupDynamo(message.deploymentId))
.then(() => "Success");
}
else {
return "Not a user deploy";
}
});
}
function handleFailed(message) {
console.log('Detected deployment failure. Checking if this deploy was user initiated or via autoscaling.');
return checkUserDeployment(message.deploymentId).then((isCreatorUser) => {
if (isCreatorUser) {
console.log('Detected a user deploy.');
return terminateStandbyInstances(message.applicationName)
.then(() => resetAutoscalingGroupSizing(message.applicationName, message.deploymentId))
.then(() => resumeAutoscalingGroup(message.applicationName))
.then(() => cleanupDynamo(message.deploymentId))
.then(() => "Success");
}
else {
return "Not a user deploy";
}
});
}
exports.handler = (event, context, callback) => {
if (!event.hasOwnProperty('Records')) {
console.log(event);
callback('Is this really from SNS?');
throw new Error();
}
var message = event.Records[0].Sns.Message;
var messageObj = JSON.parse(message);
let promise;
console.log('Deployment ID: ' + messageObj.deploymentId);
switch (messageObj.status) {
case 'CREATED':
promise = handleCreated(messageObj);
break;
case 'SUCCEEDED':
promise = handleSucceeded(messageObj);
break;
case 'FAILED':
promise = handleFailed(messageObj);
break;
default:
promise = Promise.reject('Not sure what to do with this SNS message: ' + message);
}
promise.then(result => {
callback(null, result);
}, (err) => {
callback(err);
});
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment