Last active
March 31, 2019 00:25
-
-
Save duttonw/798df82271907b45b593a36bb1b2044a to your computer and use it in GitHub Desktop.
AutoScaling Group to update Route53 hostedZoneID by tag, asgUpdateRoute53.js is the un-minified code in the tamplte
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
--- | |
AWSTemplateFormatVersion: '2010-09-09' | |
Transform: 'AWS::Serverless-2016-10-31' | |
Description: AutoScaling Group to Route 53 record update | |
#ensure you have the Tag DomainMeta set which a value of <HostedZoneId>:<Domain> on the ASG i.e. DomainMeta: Z10MWC8V7JDDU1:www.mydomain.com | |
Parameters: | |
Service: | |
Type: String | |
Default: 'asgToRoute53' | |
Description: Service name for this product | |
HostedZone: | |
Type: AWS::Route53::HostedZone::Id | |
Description: This is the hosted zone which this lambda function can edit, unless allHostedZone is true i.e. arn:aws:route53:::hostedzone/${HostedZone} | |
AllowAllHostedZoneControl: | |
Type: String | |
Default: "false" | |
AllowedValues : | |
- "true" | |
- "false" | |
Description: If true, then HostedZone is not used and wild card will be set for iam policy | |
Conditions: | |
hasRestrictedRoute53Control: | |
!Equals [ !Ref AllowAllHostedZoneControl, 'false' ] | |
hasWildCardRoute53Control: | |
!Equals [ !Ref AllowAllHostedZoneControl, 'true' ] | |
Resources: | |
ASGtoRoute53UpdateSNSTopic: | |
Type: AWS::SNS::Topic | |
Properties: | |
DisplayName: ASGtoRoute53Update | |
LambdaASGtoRoute53Update: | |
Type: 'AWS::Serverless::Function' | |
DependsOn: | |
- LambdaASGtoRoute53UpdateRole | |
Properties: | |
#FunctionName: LambdaASGtoRoute53Update | |
InlineCode: | | |
var AWS=require("aws-sdk"),nextTick=function(e){"function"==typeof setImmediate?setImmediate(e):"undefined"!=typeof process&&process.nextTick?process.nextTick(e):setTimeout(e,0)},makeIterator=function(e){var n=function(o){var t=function(){return e.length&&e[o].apply(null,arguments),t.next()};return t.next=function(){return o<e.length-1?n(o+1):null},t};return n(0)},_isArray=Array.isArray||function(e){return"[object Array]"===Object.prototype.toString.call(e)},waterfall=function(e,n){if(n=n||function(){},!_isArray(e)){var o=new Error("First argument to waterfall must be an array of functions");return n(o)}if(!e.length)return n();var t=function(e){return function(o){if(o)n.apply(null,arguments),n=function(){};else{var r=Array.prototype.slice.call(arguments,1),s=e.next();s?r.push(t(s)):r.push(n),nextTick(function(){e.apply(null,r)})}}};t(makeIterator(e))()}; | |
exports.handler=function(e,n){console.log(e);var o=JSON.parse(e.Records[0].Sns.Message),t=o.AutoScalingGroupName,r=(o.EC2InstanceId,o.Event);if(console.log(r),"autoscaling:EC2_INSTANCE_LAUNCH"===r||"autoscaling:EC2_INSTANCE_TERMINATE"===r){console.log("Handling Launch/Terminate Event for "+t);var s=process.env.AWS_DEFAULT_REGION;console.log(s);var a=new AWS.AutoScaling({region:s}),c=new AWS.EC2({region:s}),l=new AWS.Route53; | |
waterfall([function(e){console.log("Describing ASG Tags"),a.describeTags({Filters:[{Name:"auto-scaling-group",Values:[t]},{Name:"key",Values:["DomainMeta"]}],MaxRecords:1},e)}, | |
function(e,n){console.log("Processing ASG Tags"),console.log(e.Tags),0==e.Tags.length&&n("ASG: "+t+" does not define Route53 DomainMeta tag.");var o=e.Tags[0].Value.split(":"),r={HostedZoneId:o[0],RecordName:o[1]};console.log(r),n(null,r)}, | |
function(e,n){console.log("Retrieving Instances in ASG"),a.describeAutoScalingGroups({AutoScalingGroupNames:[t],MaxRecords:1},function(o,t){n(o,e,t)})}, | |
function(e,n,o){console.log(n.AutoScalingGroups[0]);var t=n.AutoScalingGroups[0].Instances.map(function(e){return e.InstanceId});c.describeInstances({DryRun:!1,InstanceIds:t}, | |
function(n,t){o(n,e,t)})},function(e,n,o){console.log(n.Reservations);var t=n.Reservations.map(function(e){return{Value:e.Instances[0].NetworkInterfaces[0].Association.PublicIp}});console.log(t),l.changeResourceRecordSets({ChangeBatch:{Changes:[{Action:"UPSERT",ResourceRecordSet:{Name:e.RecordName,Type:"A",TTL:10,ResourceRecords:t}}]},HostedZoneId:e.HostedZoneId},o)}], | |
function(e){e?console.error("Failed to process DNS updates for ASG event: ",e):console.log("Successfully processed DNS updates for ASG event."),n.done(e)})}else console.log("Unsupported ASG event: "+t,r),n.done("Unsupported ASG event: "+t,r)}; | |
Description: !Sub 'Asg to Route53 record domain update' | |
Handler: index.handler | |
MemorySize: 128 | |
Role: !GetAtt LambdaASGtoRoute53UpdateRole.Arn | |
Runtime: nodejs8.10 | |
Timeout: 20 | |
Events: | |
SNS1: | |
Type: SNS | |
Properties: | |
Topic: | |
Ref: ASGtoRoute53UpdateSNSTopic | |
# Has either AsgToRoute53EditPolicy attached for route53 edit rights | |
LambdaASGtoRoute53UpdateRole: | |
Type: AWS::IAM::Role | |
Properties: | |
AssumeRolePolicyDocument: | |
Version: 2012-10-17 | |
Statement: | |
- Effect: Allow | |
Principal: | |
Service: [lambda.amazonaws.com] | |
Action: | |
- sts:AssumeRole | |
ManagedPolicyArns: | |
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole | |
- arn:aws:iam::aws:policy/AmazonEC2ReadOnlyAccess | |
#is run when restricted access is set | |
AsgToRoute53EditPolicy: | |
Condition: hasRestrictedRoute53Control | |
DependsOn: LambdaASGtoRoute53UpdateRole | |
Type: AWS::IAM::Policy | |
Properties: | |
PolicyName: "AsgToRoute53EditPolicy" | |
Roles: | |
- | |
!Ref LambdaASGtoRoute53UpdateRole | |
PolicyDocument: | |
Version: "2012-10-17" | |
Statement: | |
- Effect: Allow | |
Action: | |
- route53:ListHostedZones | |
- route53:GetChange | |
Resource: | |
- "*" | |
- Effect: Allow | |
Action: | |
- route53:ChangeResourceRecordSets | |
Resource: | |
- !Sub "arn:aws:route53:::hostedzone/${HostedZone}" | |
#is run when allowed to change all route53 in this account | |
AsgToRoute53EditPolicy: | |
Condition: hasWildCardRoute53Control | |
DependsOn: LambdaASGtoRoute53UpdateRole | |
Type: AWS::IAM::Policy | |
Properties: | |
PolicyName: "AsgToRoute53EditPolicy" | |
Roles: | |
- | |
!Ref LambdaASGtoRoute53UpdateRole | |
PolicyDocument: | |
Version: "2012-10-17" | |
Statement: | |
- Effect: Allow | |
Action: | |
- route53:ListHostedZones | |
- route53:GetChange | |
Resource: | |
- "*" | |
- Effect: Allow | |
Action: | |
- route53:ChangeResourceRecordSets | |
Resource: | |
- !Sub "arn:aws:route53:::hostedzone/*" | |
Outputs: | |
ASGtoRoute53UpdateSNSTopic: | |
Description: ARN of newly created SNS Topic | |
Value: | |
Ref: ASGtoRoute53UpdateSNSTopic | |
Export: | |
Name: !Sub "${Service}-sns" | |
QueueName: | |
Description: Name of newly created SNS Topic | |
Value: | |
Fn::GetAtt: | |
- ASGtoRoute53UpdateSNSTopic | |
- TopicName |
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
//# ================================================================================================== | |
//# Function: Asg to Route53 record domain update | |
//# Purpose: Update domain with new list of public ip addresses of the asg | |
//# ================================================================================================== | |
var AWS=require("aws-sdk"); | |
// MIT license (by Elan Shanker). | |
//https://objectpartners.com/2015/07/07/aws-tricks-updating-route53-dns-for-autoscalinggroup-using-lambda/ | |
// with https://github.com/es128/async-waterfall/blob/master/index.js | |
'use strict'; | |
var nextTick = function (fn) { | |
if (typeof setImmediate === 'function') { | |
setImmediate(fn); | |
} else if (typeof process !== 'undefined' && process.nextTick) { | |
process.nextTick(fn); | |
} else { | |
setTimeout(fn, 0); | |
} | |
}; | |
var makeIterator = function (tasks) { | |
var makeCallback = function (index) { | |
var fn = function () { | |
if (tasks.length) { | |
tasks[index].apply(null, arguments); | |
} | |
return fn.next(); | |
}; | |
fn.next = function () { | |
return (index < tasks.length - 1) ? makeCallback(index + 1): null; | |
}; | |
return fn; | |
}; | |
return makeCallback(0); | |
}; | |
var _isArray = Array.isArray || function(maybeArray){ | |
return Object.prototype.toString.call(maybeArray) === '[object Array]'; | |
}; | |
var waterfall = function (tasks, callback) { | |
callback = callback || function () {}; | |
if (!_isArray(tasks)) { | |
var err = new Error('First argument to waterfall must be an array of functions'); | |
return callback(err); | |
} | |
if (!tasks.length) { | |
return callback(); | |
} | |
var wrapIterator = function (iterator) { | |
return function (err) { | |
if (err) { | |
callback.apply(null, arguments); | |
callback = function () {}; | |
} else { | |
var args = Array.prototype.slice.call(arguments, 1); | |
var next = iterator.next(); | |
if (next) { | |
args.push(wrapIterator(next)); | |
} else { | |
args.push(callback); | |
} | |
nextTick(function () { | |
iterator.apply(null, args); | |
}); | |
} | |
}; | |
}; | |
wrapIterator(makeIterator(tasks))(); | |
}; | |
exports.handler = function (event, context) { | |
console.log(event); | |
var asg_msg = JSON.parse(event.Records[0].Sns.Message); | |
var asg_name = asg_msg.AutoScalingGroupName; | |
var instance_id = asg_msg.EC2InstanceId; | |
var asg_event = asg_msg.Event; | |
console.log(asg_event); | |
if (asg_event === "autoscaling:EC2_INSTANCE_LAUNCH" || asg_event === "autoscaling:EC2_INSTANCE_TERMINATE") { | |
console.log("Handling Launch/Terminate Event for " + asg_name); | |
var region = process.env.AWS_DEFAULT_REGION | |
console.log(region) | |
var autoscaling = new AWS.AutoScaling({region: region}); // ${AWS::Region} | |
var ec2 = new AWS.EC2({region: region}); // ${AWS::Region} | |
var route53 = new AWS.Route53(); | |
waterfall([ | |
function describeTags(next) { | |
console.log("Describing ASG Tags"); | |
autoscaling.describeTags({ | |
Filters: [ | |
{ | |
Name: "auto-scaling-group", | |
Values: [ | |
asg_name | |
] | |
}, | |
{ | |
Name: "key", | |
Values: ['DomainMeta'] | |
} | |
], | |
MaxRecords: 1 | |
}, next); | |
}, | |
function processTags(response, next) { | |
console.log("Processing ASG Tags"); | |
console.log(response.Tags); | |
if (response.Tags.length == 0) { | |
next("ASG: " + asg_name + " does not define Route53 DomainMeta tag."); | |
} | |
var tokens = response.Tags[0].Value.split(':'); | |
var route53Tags = { | |
HostedZoneId: tokens[0], | |
RecordName: tokens[1] | |
}; | |
console.log(route53Tags); | |
next(null, route53Tags); | |
}, | |
function retrieveASGInstances(route53Tags, next) { | |
console.log("Retrieving Instances in ASG"); | |
autoscaling.describeAutoScalingGroups({ | |
AutoScalingGroupNames: [asg_name], | |
MaxRecords: 1 | |
}, function(err, data) { | |
next(err, route53Tags, data); | |
}); | |
}, | |
function retrieveInstanceIds(route53Tags, asgResponse, next) { | |
console.log(asgResponse.AutoScalingGroups[0]); | |
var instance_ids = asgResponse.AutoScalingGroups[0].Instances.map(function(instance) { | |
return instance.InstanceId | |
}); | |
ec2.describeInstances({ | |
DryRun: false, | |
InstanceIds: instance_ids | |
}, function(err, data) { | |
next(err, route53Tags, data); | |
}); | |
}, | |
function updateDNS(route53Tags, ec2Response, next) { | |
console.log(ec2Response.Reservations); | |
var resource_records = ec2Response.Reservations.map(function(reservation) { | |
return { | |
Value: reservation.Instances[0].NetworkInterfaces[0].Association.PublicIp | |
}; | |
}); | |
console.log(resource_records); | |
route53.changeResourceRecordSets({ | |
ChangeBatch: { | |
Changes: [ | |
{ | |
Action: 'UPSERT', | |
ResourceRecordSet: { | |
Name: route53Tags.RecordName, | |
Type: 'A', | |
TTL: 10, | |
ResourceRecords: resource_records | |
} | |
} | |
] | |
}, | |
HostedZoneId: route53Tags.HostedZoneId | |
}, next); | |
} | |
], function (err) { | |
if (err) { | |
console.error('Failed to process DNS updates for ASG event: ', err); | |
} else { | |
console.log("Successfully processed DNS updates for ASG event."); | |
} | |
context.done(err); | |
}) | |
} else { | |
console.log("Unsupported ASG event: " + asg_name, asg_event); | |
context.done("Unsupported ASG event: " + asg_name, asg_event); | |
} | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment