-
-
Save nwalke/d8e77630f7a8bdd67c93f927163a7740 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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