-
Notifications
You must be signed in to change notification settings - Fork 413
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
chore(custom-resource): extend alias to app-level and domain-level for NLB #3070
Changes from 3 commits
9352ca5
e472b05
be562ab
57e01c9
389898b
31ea498
1ae696f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -9,7 +9,8 @@ const DELAY_RECORD_SETS_CHANGE_IN_S = 30; | |||||
const ATTEMPTS_CERTIFICATE_VALIDATED = 19; | ||||||
const DELAY_CERTIFICATE_VALIDATED_IN_S = 30; | ||||||
|
||||||
let acm, envRoute53, envHostedZoneID, appName, envName, serviceName, certificateDomain; | ||||||
let acm, appRoute53, envRoute53, rootHostedZoneID, appHostedZoneID, envHostedZoneID; | ||||||
let appName, envName, serviceName, certificateDomain, domainTypes, rootDNSRole, domainName; | ||||||
let defaultSleep = function (ms) { | ||||||
return new Promise((resolve) => setTimeout(resolve, ms)); | ||||||
}; | ||||||
|
@@ -78,22 +79,25 @@ function report ( | |||||
} | ||||||
|
||||||
exports.handler = async function (event, context) { | ||||||
// Destruct resource properties into local variables. | ||||||
const props = event.ResourceProperties; | ||||||
|
||||||
let {LoadBalancerDNS: loadBalancerDNS, | ||||||
LoadBalancerHostedZoneID: loadBalancerHostedZoneID, | ||||||
DomainName: domainName, | ||||||
} = props; | ||||||
const aliases = new Set(props.Aliases); | ||||||
|
||||||
acm = new AWS.ACM(); | ||||||
envRoute53 = new AWS.Route53(); | ||||||
// Initialize global variables. | ||||||
envHostedZoneID = props.EnvHostedZoneId; | ||||||
envName = props.EnvName; | ||||||
appName = props.AppName; | ||||||
serviceName = props.ServiceName; | ||||||
domainName = props.DomainName; | ||||||
rootDNSRole = props.RootDNSRole; | ||||||
certificateDomain = `${serviceName}-nlb.${envName}.${appName}.${domainName}`; | ||||||
|
||||||
// Load resources that are needed by default. | ||||||
loadResources(); | ||||||
|
||||||
// NOTE: If the aliases have changed, then we need to replace the certificate being used, as well as deleting/adding | ||||||
// validation records and A records. In general, any change in aliases indicate a "replacement" of the resources | ||||||
// managed by the custom resource lambda; on the contrary, the same set of aliases indicate that there is no need to | ||||||
|
@@ -141,8 +145,9 @@ async function validateAliases(aliases, loadBalancerDNS) { | |||||
let promises = []; | ||||||
|
||||||
for (let alias of aliases) { | ||||||
const promise = envRoute53.listResourceRecordSets({ | ||||||
HostedZoneId: envHostedZoneID, | ||||||
let r = await domainResources(alias); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
const promise = r.route53Client.listResourceRecordSets({ | ||||||
HostedZoneId: r.hostedZoneID, | ||||||
MaxItems: "1", | ||||||
StartRecordName: alias, | ||||||
}).promise().then((data) => { | ||||||
|
@@ -243,7 +248,6 @@ async function activate(validationOptions, certificateARN, loadBalancerDNS, load | |||||
promises.push(activateOption(option, loadBalancerDNS, loadBalancerHostedZone)); | ||||||
} | ||||||
await Promise.all(promises); | ||||||
|
||||||
await acm.waitFor("certificateValidated", { | ||||||
// Wait up to 9 minutes and 30 seconds | ||||||
$waiter: { | ||||||
|
@@ -291,15 +295,16 @@ async function activateOption(option, loadBalancerDNS, loadBalancerHostedZone) { | |||||
}); | ||||||
} | ||||||
|
||||||
let { ChangeInfo } = await envRoute53.changeResourceRecordSets({ | ||||||
let r = await domainResources(option.DomainName); | ||||||
let { ChangeInfo } = await r.route53Client.changeResourceRecordSets({ | ||||||
ChangeBatch: { | ||||||
Comment: "Validate the certificate and create A record for the alias", | ||||||
Changes: changes, | ||||||
}, | ||||||
HostedZoneId: envHostedZoneID, | ||||||
HostedZoneId: r.hostedZoneID, | ||||||
}).promise(); | ||||||
|
||||||
await envRoute53.waitFor('resourceRecordSetsChanged', { | ||||||
await r.route53Client.waitFor('resourceRecordSetsChanged', { | ||||||
// Wait up to 5 minutes | ||||||
$waiter: { | ||||||
delay: DELAY_RECORD_SETS_CHANGE_IN_S, | ||||||
|
@@ -319,6 +324,107 @@ exports.deadlineExpired = function () { | |||||
}); | ||||||
}; | ||||||
|
||||||
/** | ||||||
* Load clients and variables that can be reused between calls. | ||||||
*/ | ||||||
function loadResources() { | ||||||
if (!acm) { | ||||||
acm = new AWS.ACM(); | ||||||
} | ||||||
|
||||||
if (!envRoute53) { | ||||||
envRoute53 = new AWS.Route53(); | ||||||
} | ||||||
|
||||||
domainTypes = { | ||||||
EnvDomainZone: { | ||||||
regex: new RegExp(`^([^\.]+\.)?${envName}.${appName}.${domainName}`), | ||||||
domain: `${envName}.${appName}.${domainName}`, | ||||||
}, | ||||||
AppDomainZone: { | ||||||
regex: new RegExp(`^([^\.]+\.)?${appName}.${domainName}`), | ||||||
domain: `${appName}.${domainName}`, | ||||||
}, | ||||||
RootDomainZone: { | ||||||
regex: new RegExp(`^([^\.]+\.)?${domainName}`), | ||||||
domain: `${domainName}`, | ||||||
}, | ||||||
OtherDomainZone: {}, | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm just curious about this domainType. How is it used? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch! This is not used. I should just delete it! |
||||||
}; | ||||||
|
||||||
} | ||||||
|
||||||
/** | ||||||
* Lazy load application-level clients and variables that can be reused between calls. | ||||||
*/ | ||||||
async function lazyLoadAppResources() { | ||||||
lazyLoadAppRoute53Client(); | ||||||
if (!appHostedZoneID) { | ||||||
appHostedZoneID = await hostedZoneID(`${appName}.${domainName}`); | ||||||
} | ||||||
} | ||||||
|
||||||
/** | ||||||
* Lazy load application-level clients and variables that can be reused between calls. | ||||||
*/ | ||||||
async function lazyLoadRootResources() { | ||||||
lazyLoadAppRoute53Client(); | ||||||
if (!rootHostedZoneID) { | ||||||
rootHostedZoneID = await hostedZoneID(domainName); | ||||||
} | ||||||
} | ||||||
|
||||||
function lazyLoadAppRoute53Client() { | ||||||
if (appRoute53) { | ||||||
return; | ||||||
} | ||||||
appRoute53 = new AWS.Route53({ | ||||||
credentials: new AWS.ChainableTemporaryCredentials({ | ||||||
params: { RoleArn: rootDNSRole, }, | ||||||
masterCredentials: new AWS.EnvironmentCredentials("AWS"), | ||||||
}), | ||||||
}); | ||||||
} | ||||||
|
||||||
async function hostedZoneID(domain) { | ||||||
const { HostedZones } = await appRoute53 | ||||||
.listHostedZonesByName({ | ||||||
DNSName: domain, | ||||||
MaxItems: "1", | ||||||
}).promise(); | ||||||
if (!HostedZones || HostedZones.length === 0) { | ||||||
throw new Error( `Couldn't find any Hosted Zone with DNS name ${domainName}.`); | ||||||
} | ||||||
return HostedZones[0].Id.split("/").pop(); | ||||||
} | ||||||
|
||||||
async function domainResources (alias) { | ||||||
if (domainTypes.EnvDomainZone.regex.test(alias)) { | ||||||
return { | ||||||
domain: domainTypes.EnvDomainZone.domain, | ||||||
route53Client: envRoute53, | ||||||
hostedZoneID: envHostedZoneID, | ||||||
}; | ||||||
} | ||||||
if (domainTypes.AppDomainZone.regex.test(alias)) { | ||||||
await lazyLoadAppResources(); | ||||||
return { | ||||||
domain: domainTypes.AppDomainZone.domain, | ||||||
route53Client: appRoute53, | ||||||
hostedZoneID: appHostedZoneID, | ||||||
}; | ||||||
} | ||||||
if (domainTypes.RootDomainZone.regex.test(alias)) { | ||||||
await lazyLoadRootResources(); | ||||||
return { | ||||||
domain: domainTypes.RootDomainZone.domain, | ||||||
route53Client: appRoute53, | ||||||
hostedZoneID: rootHostedZoneID, | ||||||
}; | ||||||
} | ||||||
throw new Error(`unrecognized domain type for ${alias}`); | ||||||
} | ||||||
|
||||||
exports.withSleep = function (s) { | ||||||
sleep = s; | ||||||
}; | ||||||
|
@@ -328,4 +434,4 @@ exports.reset = function () { | |||||
exports.withDeadlineExpired = function (d) { | ||||||
exports.deadlineExpired = d; | ||||||
}; | ||||||
exports.attemptsValidationOptionsReady = ATTEMPTS_VALIDATION_OPTIONS_READY; | ||||||
exports.attemptsValidationOptionsReady = ATTEMPTS_VALIDATION_OPTIONS_READY; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what do you think of something like this? Does it make testing difficult?
This way throughout the code we can do
client.app.route53()
orclient.acm()
everything is guaranteed to be a singleton and it removes all these global clientsThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea - updated!