From f6c1ec2ffd29fec8909b90cb7daaf311013ab610 Mon Sep 17 00:00:00 2001 From: Mike Cowgill Date: Fri, 5 Oct 2018 06:12:34 -0700 Subject: [PATCH] feat(context-providers) Refactor to enable context providers properties * Add Hosted Zone Provider * Enabling complex filter types for context provider keys --- .../@aws-cdk/aws-ec2/lib/machine-image.ts | 13 +- .../@aws-cdk/cdk/lib/cloudformation/stack.ts | 4 +- packages/@aws-cdk/cdk/lib/context.ts | 174 +++++++++++------- packages/@aws-cdk/cdk/test/test.app.ts | 48 +++-- packages/@aws-cdk/cdk/test/test.context.ts | 42 ++++- packages/@aws-cdk/cx-api/lib/cxapi.ts | 15 +- packages/aws-cdk/bin/cdk.ts | 1 + packages/aws-cdk/lib/api/util/sdk.ts | 16 +- packages/aws-cdk/lib/contextplugins.ts | 97 ++++++++-- packages/aws-cdk/package.json | 1 + tools/cdk-integ-tools/lib/integ-helpers.ts | 2 +- 11 files changed, 286 insertions(+), 127 deletions(-) diff --git a/packages/@aws-cdk/aws-ec2/lib/machine-image.ts b/packages/@aws-cdk/aws-ec2/lib/machine-image.ts index e908919259956..1dc5a6d4bcf2c 100644 --- a/packages/@aws-cdk/aws-ec2/lib/machine-image.ts +++ b/packages/@aws-cdk/aws-ec2/lib/machine-image.ts @@ -25,10 +25,11 @@ export class WindowsImage implements IMachineImageSource { * Return the image to use in the given context */ public getImage(parent: Construct): MachineImage { - const ssmProvider = new SSMParameterProvider(parent); + const ssmProvider = new SSMParameterProvider(parent, { + parameterName: this.imageParameterName(this.version), + }); - const parameterName = this.imageParameterName(this.version); - const ami = ssmProvider.getString(parameterName); + const ami = ssmProvider.parameterValue(); return new MachineImage(ami, new WindowsOS()); } @@ -98,8 +99,10 @@ export class AmazonLinuxImage implements IMachineImageSource { const parameterName = '/aws/service/ami-amazon-linux-latest/' + parts.join('-'); - const ssmProvider = new SSMParameterProvider(parent); - const ami = ssmProvider.getString(parameterName); + const ssmProvider = new SSMParameterProvider(parent, { + parameterName, + }); + const ami = ssmProvider.parameterValue(); return new MachineImage(ami, new LinuxOS()); } } diff --git a/packages/@aws-cdk/cdk/lib/cloudformation/stack.ts b/packages/@aws-cdk/cdk/lib/cloudformation/stack.ts index 82cce8b4f1538..bc2769f66e258 100644 --- a/packages/@aws-cdk/cdk/lib/cloudformation/stack.ts +++ b/packages/@aws-cdk/cdk/lib/cloudformation/stack.ts @@ -65,7 +65,7 @@ export class Stack extends Construct { * This is returned when the stack is synthesized under the 'missing' attribute * and allows tooling to obtain the context and re-synthesize. */ - public readonly missingContext: { [key: string]: cxapi.MissingContext } = { }; + public readonly missingContext: { [key: string]: cxapi.ContextProviderProps } = { }; /** * The environment in which this stack is deployed. @@ -181,7 +181,7 @@ export class Stack extends Construct { * @param key Key that uniquely identifies this missing context. * @param details The set of parameters needed to obtain the context (specific to context provider). */ - public reportMissingContext(key: string, details: cxapi.MissingContext) { + public reportMissingContext(key: string, details: cxapi.ContextProviderProps) { this.missingContext[key] = details; } diff --git a/packages/@aws-cdk/cdk/lib/context.ts b/packages/@aws-cdk/cdk/lib/context.ts index 3c17890b6e129..9c3d206a5c2bc 100644 --- a/packages/@aws-cdk/cdk/lib/context.ts +++ b/packages/@aws-cdk/cdk/lib/context.ts @@ -1,8 +1,10 @@ +import cxapi = require('@aws-cdk/cx-api'); import { Stack } from './cloudformation/stack'; 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 @@ -16,11 +18,26 @@ const SSM_PARAMETER_PROVIDER = 'ssm'; export class ContextProvider { private readonly stack: Stack; + private readonly provider: string; + private readonly props: {[key: string]: any}; - constructor(private context: Construct) { + constructor(private context: Construct, provider: string, props: {[key: string]: any} = {}) { this.stack = Stack.find(context); + this.provider = provider; + this.props = props; } + public get key(): string { + const account = this.account; + const region = this.region; + let keyStr = `${this.provider}:${account}:${region}`; + const propStrings: string[] = this.objectToString(this.props); + if (propStrings.length > 0) { + keyStr += ':'; + keyStr += propStrings.join(':'); + } + return keyStr; + } /** * Read a provider value, verifying it's a string * @param provider The name of the context provider @@ -28,27 +45,29 @@ export class ContextProvider { * @param args Any arguments * @param defaultValue The value to return if there is no value defined for this context key */ - public getStringValue( - provider: string, - scope: undefined | string[], - args: string[], - defaultValue: string): string { + public getStringValue( defaultValue: string): string { // if scope is undefined, this is probably a test mode, so we just // return the default value - if (!scope) { - this.context.addError(formatMissingScopeError(provider, args)); + if (!this.account || !this.region) { + this.context.addError(formatMissingScopeError(this.provider, this.props)); return defaultValue; } - const key = colonQuote([provider].concat(scope).concat(args)).join(':'); - const value = this.context.getContext(key); + + const value = this.context.getContext(this.key); + if (value != null) { if (typeof value !== 'string') { - throw new TypeError(`Expected context parameter '${key}' to be a string, but got '${value}'`); + throw new TypeError(`Expected context parameter '${this.key}' to be a string, but got '${value}'`); } return value; } - this.stack.reportMissingContext(key, { provider, scope, args }); + this.stack.reportMissingContext(this.key, { + provider: this.provider, + account: this.account, + region: this.region, + props: this.props, + }); return defaultValue; } @@ -60,50 +79,65 @@ export class ContextProvider { * @param defaultValue The value to return if there is no value defined for this context key */ public getStringListValue( - provider: string, - scope: undefined | string[], - args: string[], defaultValue: string[]): string[] { - // if scope is undefined, this is probably a test mode, so we just - // return the default value and report an error so this in not accidentally used - // in the toolkit - if (!scope) { - // tslint:disable-next-line:max-line-length - this.context.addError(formatMissingScopeError(provider, args)); - return defaultValue; - } + // if scope is undefined, this is probably a test mode, so we just + // return the default value and report an error so this in not accidentally used + // in the toolkit + if (!this.account || !this.region) { + this.context.addError(formatMissingScopeError(this.provider, this.props)); + return defaultValue; + } - const key = colonQuote([provider].concat(scope).concat(args)).join(':'); - const value = this.context.getContext(key); + const value = this.context.getContext(this.key); - if (value != null) { - if (!value.map) { - throw new Error(`Context value '${key}' is supposed to be a list, got '${value}'`); + if (value != null) { + if (!value.map) { + throw new Error(`Context value '${this.key}' is supposed to be a list, got '${value}'`); + } + return value; } - return value; - } - this.stack.reportMissingContext(key, { provider, scope, args }); - return defaultValue; - } + this.stack.reportMissingContext(this.key, { + provider: this.provider, + account: this.account, + region: this.region, + props: this.props, + }); - /** - * Helper function to wrap up account and region into a scope tuple - */ - public accountRegionScope(providerDescription: string): undefined | string[] { - const stack = Stack.find(this.context); - if (!stack) { - throw new Error(`${providerDescription}: construct must be in a stack`); + return defaultValue; } - const account = stack.env.account; - const region = stack.env.region; - - if (account == null || region == null) { - return undefined; + private objectToString(obj: any): string[] { + const objStr: string[] = []; + const keys = Object.keys(obj); + keys.sort(); + for (const key of keys) { + switch (typeof obj[key]) { + case 'object': { + const childObjStrs = this.objectToString(obj[key]); + const qualifiedChildStr = childObjStrs.map( child => (`${key}${child}`)).join(':'); + objStr.push(qualifiedChildStr); + break; + } + case 'string': { + objStr.push(`${key}=${colonQuote(obj[key])}`); + break; + } + default: { + objStr.push(`${key}=${JSON.stringify(obj[key])}`); + break; + } + } } + return objStr; + } - return [account, region]; + private get account(): string | undefined { + return this.stack.env.account; + } + + private get region(): string | undefined { + return this.stack.env.region; } } @@ -113,8 +147,8 @@ export class ContextProvider { * We'll use $ as a quoting character, for no particularly good reason other * than that \ is going to lead to quoting hell when the keys are stored in JSON. */ -function colonQuote(xs: string[]): string[] { - return xs.map(x => x.replace('$', '$$').replace(':', '$:')); +function colonQuote(xs: string): string { + return xs.replace('$', '$$').replace(':', '$:'); } /** @@ -124,45 +158,59 @@ export class AvailabilityZoneProvider { private provider: ContextProvider; constructor(context: Construct) { - this.provider = new ContextProvider(context); + this.provider = new ContextProvider(context, AVAILABILITY_ZONES_PROVIDER); } /** * Return the list of AZs for the current account and region */ public get availabilityZones(): string[] { - return this.provider.getStringListValue(AVAILABILITY_ZONES_PROVIDER, - this.provider.accountRegionScope('AvailabilityZoneProvider'), - [], - ['dummy1a', 'dummy1b', 'dummy1c']); + + return this.provider.getStringListValue(['dummy1a', 'dummy1b', 'dummy1c']); } } +export interface SSMParameterProviderProps { + parameterName: string; +} /** * Context provider that will read values from the SSM parameter store in the indicated account and region */ export class SSMParameterProvider { private provider: ContextProvider; - constructor(context: Construct) { - this.provider = new ContextProvider(context); + constructor(context: Construct, props: SSMParameterProviderProps) { + this.provider = new ContextProvider(context, SSM_PARAMETER_PROVIDER, props); } /** * Return the SSM parameter string with the indicated key */ - public getString(parameterName: string): any { - const scope = this.provider.accountRegionScope('SSMParameterProvider'); - return this.provider.getStringValue(SSM_PARAMETER_PROVIDER, scope, [parameterName], 'dummy'); + public parameterValue(): any { + return this.provider.getStringValue('dummy'); } } -function formatMissingScopeError(provider: string, args: string[]) { - let s = `Cannot determine scope for context provider ${provider}`; - if (args.length > 0) { - s += JSON.stringify(args); +/** + * Context provider that will lookup the Hosted Zone ID for the given arguments + */ +export class HostedZoneProvider { + private provider: ContextProvider; + constructor(context: Construct, props: cxapi.HostedZoneProviderProps) { + this.provider = new ContextProvider(context, HOSTED_ZONE_PROVIDER, props); + } + /** + * Return the hosted zone meeting the filter + */ + public zoneId(): string { + return this.provider.getStringValue('dummy-zone'); } - s += '.'; +} + +function formatMissingScopeError(provider: string, props: {[key: string]: string}) { + let s = `Cannot determine scope for context provider ${provider}`; + const propsString = Object.keys(props).map( key => (`${key}=${props[key]}`)); + s += ` with props: ${propsString}.`; s += '\n'; s += 'This usually happens when AWS credentials are not available and the default account/region cannot be determined.'; return s; diff --git a/packages/@aws-cdk/cdk/test/test.app.ts b/packages/@aws-cdk/cdk/test/test.app.ts index 42dfda20a6725..f9a2fa49bd6d4 100644 --- a/packages/@aws-cdk/cdk/test/test.app.ts +++ b/packages/@aws-cdk/cdk/test/test.app.ts @@ -254,16 +254,20 @@ export = { super(parent, name, props); this.reportMissingContext('missing-context-key', { - provider: 'ctx-provider', - args: [ 'arg1', 'arg2' ], - scope: [ 'scope1', 'scope2' ] - }); + provider: 'fake', + account: '12345689012', + region: 'ab-north-1', + props: {}, + }, + ); this.reportMissingContext('missing-context-key-2', { - provider: 'ctx-provider', - args: [ 'arg1', 'arg2' ], - scope: [ 'scope1', 'scope2' ] - }); + provider: 'fake2', + account: '12345689012', + region: 'ab-south-1', + props: {foo: 'bar'}, + }, + ); } } @@ -275,27 +279,17 @@ export = { test.deepEqual(response.stacks[0].missing, { "missing-context-key": { - provider: "ctx-provider", - args: [ - "arg1", - "arg2" - ], - scope: [ - "scope1", - "scope2" - ] + provider: 'fake', + account: '12345689012', + region: 'ab-north-1', + props: {}, }, "missing-context-key-2": { - provider: "ctx-provider", - args: [ - "arg1", - "arg2" - ], - scope: [ - "scope1", - "scope2" - ] - } + provider: 'fake2', + account: '12345689012', + region: 'ab-south-1', + props: {foo: 'bar'}, + }, }); test.done(); diff --git a/packages/@aws-cdk/cdk/test/test.context.ts b/packages/@aws-cdk/cdk/test/test.context.ts index a3c6ab46d148c..5a272f558ea97 100644 --- a/packages/@aws-cdk/cdk/test/test.context.ts +++ b/packages/@aws-cdk/cdk/test/test.context.ts @@ -1,6 +1,7 @@ import cxapi = require('@aws-cdk/cx-api'); import { Test } from 'nodeunit'; -import { App, AvailabilityZoneProvider, Construct, MetadataEntry, resolve, SSMParameterProvider, Stack } from '../lib'; +import { App, AvailabilityZoneProvider, Construct, ContextProvider, + HostedZoneProvider, MetadataEntry, resolve, SSMParameterProvider, Stack } from '../lib'; export = { 'AvailabilityZoneProvider returns a list with dummy values if the context is not available'(test: Test) { @@ -40,19 +41,48 @@ export = { test.done(); }, + 'ContextProvider consistently generates a key'(test: Test) { + const stack = new Stack(undefined, 'TestStack', { env: { account: '12345', region: 'us-east-1' } }); + const provider = new ContextProvider(stack, 'ssm', { + parameterName: 'foo', + anyStringParam: 'bar', + }); + const key = provider.key; + test.deepEqual(key, 'ssm:12345:us-east-1:anyStringParam=bar:parameterName=foo'); + const complex = new ContextProvider(stack, 'vpc', { + cidrBlock: '192.168.0.16', + tags: { Name: 'MyVPC', Env: 'Preprod' }, + igw: false, + }); + const complexKey = complex.key; + test.deepEqual(complexKey, + 'vpc:12345:us-east-1:cidrBlock=192.168.0.16:igw=false:tagsEnv=Preprod:tagsName=MyVPC'); + test.done(); + }, 'SSM parameter provider will return context values if available'(test: Test) { const stack = new Stack(undefined, 'TestStack', { env: { account: '12345', region: 'us-east-1' } }); - new SSMParameterProvider(stack).getString('test'); + new SSMParameterProvider(stack, {parameterName: 'test'}).parameterValue(); const key = expectedContextKey(stack); stack.setContext(key, 'abc'); - const azs = resolve(new SSMParameterProvider(stack).getString('test')); + const ssmp = new SSMParameterProvider(stack, {parameterName: 'test'}); + const azs = resolve(ssmp.parameterValue()); test.deepEqual(azs, 'abc'); test.done(); }, + 'HostedZoneProvider will return context values if availble'(test: Test) { + const stack = new Stack(undefined, 'TestStack', { env: { account: '12345', region: 'us-east-1' } }); + const filter = {domainName: 'test.com'}; + new HostedZoneProvider(stack, filter).zoneId(); + const key = expectedContextKey(stack); + stack.setContext(key, 'HOSTEDZONEID'); + const zone = resolve(new HostedZoneProvider(stack, filter).zoneId()); + test.deepEqual(zone, 'HOSTEDZONEID'); + test.done(); + }, 'Return default values if "env" is undefined to facilitate unit tests, but also expect metadata to include "error" messages'(test: Test) { const app = new App(); const stack = new Stack(app, 'test-stack'); @@ -60,15 +90,15 @@ export = { const child = new Construct(stack, 'ChildConstruct'); test.deepEqual(new AvailabilityZoneProvider(stack).availabilityZones, [ 'dummy1a', 'dummy1b', 'dummy1c' ]); - test.deepEqual(new SSMParameterProvider(child).getString('foo'), 'dummy'); + test.deepEqual(new SSMParameterProvider(child, {parameterName: 'foo'}).parameterValue(), 'dummy'); const output = app.synthesizeStack(stack.id); const azError: MetadataEntry | undefined = output.metadata['/test-stack'].find(x => x.type === cxapi.ERROR_METADATA_KEY); const ssmError: MetadataEntry | undefined = output.metadata['/test-stack/ChildConstruct'].find(x => x.type === cxapi.ERROR_METADATA_KEY); - test.ok(azError && (azError.data as string).includes('Cannot determine scope for context provider availability-zones.')); - test.ok(ssmError && (ssmError.data as string).includes('Cannot determine scope for context provider ssm["foo"].')); + test.ok(azError && (azError.data as string).includes('Cannot determine scope for context provider availability-zones')); + test.ok(ssmError && (ssmError.data as string).includes('Cannot determine scope for context provider ssm')); test.done(); }, diff --git a/packages/@aws-cdk/cx-api/lib/cxapi.ts b/packages/@aws-cdk/cx-api/lib/cxapi.ts index 118fa25c9e033..1350efaeba309 100644 --- a/packages/@aws-cdk/cx-api/lib/cxapi.ts +++ b/packages/@aws-cdk/cx-api/lib/cxapi.ts @@ -24,10 +24,17 @@ export type CXRequest = ListStacksRequest | SynthesizeRequest; * Represents a missing piece of context. * (should have been an interface, but jsii still doesn't have support for structs). */ -export interface MissingContext { +export interface ContextProviderProps { provider: string; - scope: string[]; - args: string[]; + account: string; + region: string; + props: {[key: string]: any}; +} + +export interface HostedZoneProviderProps { + domainName: string; + privateZone?: boolean; + vpcId?: string; } export interface ListStacksResponse { @@ -57,7 +64,7 @@ export interface StackInfo extends StackId { * A complete synthesized stack */ export interface SynthesizedStack extends StackInfo { - missing?: { [key: string]: MissingContext }; + missing?: { [key: string]: ContextProviderProps }; metadata: StackMetadata; template: any; } diff --git a/packages/aws-cdk/bin/cdk.ts b/packages/aws-cdk/bin/cdk.ts index 82371c8734334..a9e850f1b382e 100644 --- a/packages/aws-cdk/bin/cdk.ts +++ b/packages/aws-cdk/bin/cdk.ts @@ -113,6 +113,7 @@ async function initCommandLine() { const availableContextProviders: contextplugins.ProviderMap = { 'availability-zones': new contextplugins.AZContextProviderPlugin(aws), 'ssm': new contextplugins.SSMContextProviderPlugin(aws), + 'hosted-zone': new contextplugins.HostedZoneContextProviderPlugin(aws), }; const defaultConfig = new Settings({ versionReporting: true }); diff --git a/packages/aws-cdk/lib/api/util/sdk.ts b/packages/aws-cdk/lib/api/util/sdk.ts index 03b6f448551a6..1b6c8e4307c40 100644 --- a/packages/aws-cdk/lib/api/util/sdk.ts +++ b/packages/aws-cdk/lib/api/util/sdk.ts @@ -106,7 +106,13 @@ export class SDK { ...this.defaultClientArgs }); } - + public async route53(awsAccountId: string | undefined, region: string | undefined, mode: Mode): Promise { + return new AWS.Route53({ + region, + credentials: await this.credentialsCache.get(awsAccountId, mode), + ...this.defaultClientArgs + }); + } public async defaultRegion(): Promise { return await getCLICompatibleDefaultRegion(this.profile); } @@ -132,8 +138,8 @@ class CredentialsCache { private readonly cache: {[key: string]: AWS.Credentials} = {}; public constructor( - private readonly defaultAwsAccount: DefaultAWSAccount, - private readonly defaultCredentialProvider: Promise) { + private readonly defaultAwsAccount: DefaultAWSAccount, + private readonly defaultCredentialProvider: Promise) { } public async get(awsAccountId: string | undefined, mode: Mode): Promise { @@ -306,10 +312,10 @@ async function getCLICompatibleDefaultRegion(profile: string | undefined): Promi const toCheck = [ {filename: process.env.AWS_SHARED_CREDENTIALS_FILE }, {isConfig: true, filename: process.env.AWS_CONFIG_FILE}, - ]; + ]; let region = process.env.AWS_REGION || process.env.AMAZON_REGION || - process.env.AWS_DEFAULT_REGION || process.env.AMAZON_DEFAULT_REGION; + process.env.AWS_DEFAULT_REGION || process.env.AMAZON_DEFAULT_REGION; while (!region && toCheck.length > 0) { const configFile = new SharedIniFile(toCheck.shift()); diff --git a/packages/aws-cdk/lib/contextplugins.ts b/packages/aws-cdk/lib/contextplugins.ts index f8064433e2cb2..abe63af0df282 100644 --- a/packages/aws-cdk/lib/contextplugins.ts +++ b/packages/aws-cdk/lib/contextplugins.ts @@ -1,10 +1,10 @@ -import { MissingContext } from '@aws-cdk/cx-api'; +import cxapi = require('@aws-cdk/cx-api'); import { Mode, SDK } from './api'; import { debug } from './logging'; import { Settings } from './settings'; export interface ContextProviderPlugin { - getValue(scope: string[], args: string[]): Promise; + getValue(filter: cxapi.ContextProviderProps): Promise; } export type ProviderMap = {[name: string]: ContextProviderPlugin}; @@ -16,8 +16,9 @@ export class AZContextProviderPlugin implements ContextProviderPlugin { constructor(private readonly aws: SDK) { } - public async getValue(scope: string[], _args: string[]) { - const [account, region] = scope; + public async getValue(props: cxapi.ContextProviderProps) { + const region = props.region; + const account = props.account; debug(`Reading AZs for ${account}:${region}`); const ec2 = await this.aws.ec2(account, region, Mode.ForReading); const response = await ec2.describeAvailabilityZones().promise(); @@ -34,9 +35,13 @@ export class SSMContextProviderPlugin implements ContextProviderPlugin { constructor(private readonly aws: SDK) { } - public async getValue(scope: string[], args: string[]) { - const [account, region] = scope; - const parameterName = args[0]; + public async getValue(filter: cxapi.ContextProviderProps) { + const region = filter.region; + const account = filter.account; + if (!('parameterName' in filter.props)) { + throw new Error('parameterName must be provided in props for SSMContextProviderPlugin'); + } + const parameterName = filter.props.parameterName; debug(`Reading SSM parameter ${account}:${region}:${parameterName}`); const ssm = await this.aws.ssm(account, region, Mode.ForReading); @@ -48,21 +53,85 @@ export class SSMContextProviderPlugin implements ContextProviderPlugin { } } +export class HostedZoneContextProviderPlugin implements ContextProviderPlugin { + + constructor(private readonly aws: SDK) { + } + + public async getValue(filter: cxapi.ContextProviderProps) { + const account = filter.account; + const region = filter.region; + if (!this.isHostedZoneProps(filter.props)) { + throw new Error(`HostedZoneProvider requires domainName property to be set in ${filter.props}`); + } + const props: cxapi.HostedZoneProviderProps = filter.props; + const domainName = props.domainName; + const privateZone: boolean = !!props.privateZone; + const vpcId = + 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 found in account ${account}, region ${region}: ${domainName}`); + } + const candidateZones = this.filterZones(r53, response.HostedZones, + !!props.privateZone, props.vpcId); + if (candidateZones.length > 1) { + const filteProps = `dns:${domainName}, privateZone:${privateZone}, vpcId:${vpcId}`; + throw new Error(`Found more than one matching HostedZone ${candidateZones} for ${filteProps}`); + } + return candidateZones[0]; + } + + private filterZones(r53: AWS.Route53, zones: AWS.Route53.HostedZone[], privateZone: boolean, vpcId: string | undefined): AWS.Route53.HostedZone[] { + + let candidates: AWS.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: AWS.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; + } + + private isHostedZoneProps(props: cxapi.HostedZoneProviderProps | any): props is cxapi.HostedZoneProviderProps { + return (props as cxapi.HostedZoneProviderProps).domainName !== undefined; + } +} /** * 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) { +export async function provideContextValues( + missingValues: { [key: string]: cxapi.ContextProviderProps }, + projectConfig: Settings, + availableContextProviders: ProviderMap) { for (const key of Object.keys(missingValues)) { - const query = missingValues[key]; + const props = missingValues[key]; - const provider = availableContextProviders[query.provider]; + const provider = availableContextProviders[props.provider]; if (!provider) { - throw new Error(`Unrecognized context provider name: ${query.provider}`); + throw new Error(`Unrecognized context provider name: ${props.provider}`); } - const value = await provider.getValue(query.scope, query.args); + const value = await provider.getValue(props); projectConfig.set(['context', key], value); } } diff --git a/packages/aws-cdk/package.json b/packages/aws-cdk/package.json index 0dd1a244e783d..216eb8ce2af34 100644 --- a/packages/aws-cdk/package.json +++ b/packages/aws-cdk/package.json @@ -46,6 +46,7 @@ "dependencies": { "@aws-cdk/cloudformation-diff": "^0.10.0", "@aws-cdk/cx-api": "^0.10.0", + "@aws-cdk/cdk": "^0.10.0", "archiver": "^2.1.1", "aws-sdk": "^2.259.1", "camelcase": "^5.0.0", diff --git a/tools/cdk-integ-tools/lib/integ-helpers.ts b/tools/cdk-integ-tools/lib/integ-helpers.ts index 5e1e9960cb620..700d966e5ea56 100644 --- a/tools/cdk-integ-tools/lib/integ-helpers.ts +++ b/tools/cdk-integ-tools/lib/integ-helpers.ts @@ -89,7 +89,7 @@ export const STATIC_TEST_CONTEXT = { [DEFAULT_ACCOUNT_CONTEXT_KEY]: "12345678", [DEFAULT_REGION_CONTEXT_KEY]: "test-region", "availability-zones:12345678:test-region": [ "test-region-1a", "test-region-1b", "test-region-1c" ], - "ssm:12345678:test-region:/aws/service/ami-amazon-linux-latest/amzn-ami-hvm-x86_64-gp2": "ami-1234", + "ssm:12345678:test-region:parameterName=/aws/service/ami-amazon-linux-latest/amzn-ami-hvm-x86_64-gp2": "ami-1234", }; /**