-
Notifications
You must be signed in to change notification settings - Fork 3.9k
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
Hosted Zone Context Provider #425
Changes from all commits
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 |
---|---|---|
|
@@ -3,6 +3,7 @@ import { Construct } from './core/construct'; | |
|
||
const AVAILABILITY_ZONES_PROVIDER = 'availability-zones'; | ||
const SSM_PARAMETER_PROVIDER = 'ssm'; | ||
const HOSTED_ZONE_PROVIDER = 'hosted-zone'; | ||
|
||
/** | ||
* Base class for the model side of context providers | ||
|
@@ -157,13 +158,40 @@ export class SSMParameterProvider { | |
} | ||
} | ||
|
||
export interface HostedZoneFilter { | ||
name: string; | ||
privateZone?: boolean; | ||
vpcId?: string; | ||
} | ||
|
||
/** | ||
* Context provider that will lookup the Hosted Zone ID for the given arguments | ||
*/ | ||
export class HostedZoneProvider { | ||
private provider: ContextProvider; | ||
|
||
constructor(context: Construct) { | ||
this.provider = new ContextProvider(context); | ||
} | ||
|
||
/** | ||
* Return the hosted zone meeting the filter | ||
*/ | ||
public getZone(filter: HostedZoneFilter): string { | ||
const scope = this.provider.accountRegionScope('HostedZoneProvider'); | ||
const args: string[] = [filter.name]; | ||
if (filter.privateZone) { args.push(`${filter.privateZone}`); } | ||
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. Was this intended as a test against |
||
if (filter.vpcId) { args.push(filter.vpcId); } | ||
return this.provider.getStringValue(HOSTED_ZONE_PROVIDER, scope, args, 'dummy'); | ||
} | ||
} | ||
|
||
function formatMissingScopeError(provider: string, args: string[]) { | ||
let s = `Cannot determine scope for context provider ${provider}`; | ||
if (args.length > 0) { | ||
s += JSON.stringify(args); | ||
} | ||
s += '.'; | ||
s += '\n'; | ||
s += '.\n'; | ||
s += 'This usually happens when AWS credentials are not available and the default account/region cannot be determined.'; | ||
return s; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
import { MissingContext } from '@aws-cdk/cx-api'; | ||
import { Mode, SDK } from './api'; | ||
import { Route53 } from 'aws-sdk'; | ||
import { debug } from './logging'; | ||
import { Settings } from './settings'; | ||
|
||
|
@@ -48,12 +49,69 @@ export class SSMContextProviderPlugin implements ContextProviderPlugin { | |
} | ||
} | ||
|
||
/** | ||
* Plugin to read arbitrary SSM parameter names | ||
*/ | ||
export class HostedZoneContextProviderPlugin implements ContextProviderPlugin { | ||
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. An instance of this needs to be added to |
||
constructor(private readonly aws: SDK) { | ||
} | ||
|
||
public async getValue(scope: string[], args: string[]) { | ||
const [account, region] = scope; | ||
const domainName = args[0]; | ||
const privateZone: boolean = args[1] ? args[1] === 'true' : false; | ||
const vpcId = args[2]; | ||
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.
|
||
debug(`Reading hosted zone ${account}:${region}:${domainName}`); | ||
|
||
const r53 = await this.aws.route53(account, region, Mode.ForReading); | ||
const response = await r53.listHostedZonesByName({ DNSName: domainName }).promise(); | ||
if (!response.HostedZones) { | ||
throw new Error(`Hosted Zone not availble in account ${account}, region ${region}: ${domainName}`); | ||
} | ||
const candidateZones = this.filterZones(r53, response.HostedZones, privateZone, vpcId); | ||
if(candidateZones.length > 1) { | ||
const filter = `dns:${domainName}, privateZone:${privateZone}, vpcId:${vpcId}`; | ||
throw new Error(`Found more than one matching HostedZone ${candidateZones} for ${filter}`); | ||
} | ||
return candidateZones[0]; | ||
|
||
} | ||
|
||
private filterZones(r53: Route53, zones: Route53.HostedZone[], privateZone: boolean, vpcId: string | undefined): Route53.HostedZone[]{ | ||
let candidates: Route53.HostedZone[] = []; | ||
if(privateZone) { | ||
candidates = zones.filter(zone => zone.Config && zone.Config.PrivateZone); | ||
} else { | ||
candidates = zones.filter(zone => !zone.Config || !zone.Config.PrivateZone); | ||
} | ||
if(vpcId) { | ||
const vpcZones: Route53.HostedZone[] = []; | ||
for(const zone of candidates) { | ||
r53.getHostedZone({Id: zone.Id}, (err, data) => { | ||
if(err) { | ||
throw new Error(err.message); | ||
} | ||
if(!data.VPCs) { | ||
debug(`Expected VPC for private zone but no VPC found ${zone.Id}`) | ||
return; | ||
} | ||
if(data.VPCs.map(vpc => vpc.VPCId).includes(vpcId)) { | ||
vpcZones.push(zone); | ||
} | ||
}); | ||
} | ||
return vpcZones; | ||
} | ||
return candidates; | ||
} | ||
} | ||
|
||
/** | ||
* Iterate over the list of missing context values and invoke the appropriate providers from the map to retrieve them | ||
*/ | ||
export async function provideContextValues(missingValues: { [key: string]: MissingContext }, | ||
projectConfig: Settings, | ||
availableContextProviders: ProviderMap) { | ||
projectConfig: Settings, | ||
availableContextProviders: ProviderMap) { | ||
for (const key of Object.keys(missingValues)) { | ||
const query = missingValues[key]; | ||
|
||
|
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.
Isn't this intended to be
"hosted-zone"
?