From f6c1ec2ffd29fec8909b90cb7daaf311013ab610 Mon Sep 17 00:00:00 2001 From: Mike Cowgill Date: Fri, 5 Oct 2018 06:12:34 -0700 Subject: [PATCH 01/12] 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", }; /** From 2e77cacebf91d7dedd54d457dd0634fab447bca0 Mon Sep 17 00:00:00 2001 From: Mike Cowgill Date: Tue, 9 Oct 2018 23:33:27 -0700 Subject: [PATCH 02/12] feat(context-providers) Refactor to enable context providers properties * Add Hosted Zone Provider * Add ability to pass dictionary to MissingContext --- .../@aws-cdk/cdk/lib/cloudformation/stack.ts | 4 +- packages/@aws-cdk/cdk/lib/context.ts | 81 +++++++++---------- packages/@aws-cdk/cdk/test/test.app.ts | 44 +++++----- packages/@aws-cdk/cdk/test/test.context.ts | 4 +- packages/@aws-cdk/cx-api/lib/cxapi.ts | 12 +-- packages/aws-cdk/lib/contextplugins.ts | 56 ++++++------- tools/cdk-integ-tools/lib/integ-helpers.ts | 4 +- 7 files changed, 102 insertions(+), 103 deletions(-) diff --git a/packages/@aws-cdk/cdk/lib/cloudformation/stack.ts b/packages/@aws-cdk/cdk/lib/cloudformation/stack.ts index bc2769f66e258..82cce8b4f1538 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.ContextProviderProps } = { }; + public readonly missingContext: { [key: string]: cxapi.MissingContext } = { }; /** * 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.ContextProviderProps) { + public reportMissingContext(key: string, details: cxapi.MissingContext) { this.missingContext[key] = details; } diff --git a/packages/@aws-cdk/cdk/lib/context.ts b/packages/@aws-cdk/cdk/lib/context.ts index 9c3d206a5c2bc..cd250127400f1 100644 --- a/packages/@aws-cdk/cdk/lib/context.ts +++ b/packages/@aws-cdk/cdk/lib/context.ts @@ -18,25 +18,20 @@ const HOSTED_ZONE_PROVIDER = 'hosted-zone'; export class ContextProvider { private readonly stack: Stack; - private readonly provider: string; - private readonly props: {[key: string]: any}; - constructor(private context: Construct, provider: string, props: {[key: string]: any} = {}) { + constructor( + private readonly context: Construct, + private readonly provider: string, + private readonly 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; + const propStrings: string[] = propsToArray({ + ...this.props, + ...{account: this.account, region: this.region}, + }); + return `${this.provider}:${propStrings.join(':')}`; } /** * Read a provider value, verifying it's a string @@ -64,9 +59,7 @@ export class ContextProvider { this.stack.reportMissingContext(this.key, { provider: this.provider, - account: this.account, - region: this.region, - props: this.props, + props: { ...this.props, ...{region: this.region, account: this.account} }, }); return defaultValue; } @@ -99,39 +92,12 @@ export class ContextProvider { this.stack.reportMissingContext(this.key, { provider: this.provider, - account: this.account, - region: this.region, - props: this.props, + props: { ...this.props, ...{region: this.region, account: this.account} }, }); return defaultValue; } - 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; - } - private get account(): string | undefined { return this.stack.env.account; } @@ -215,3 +181,28 @@ function formatMissingScopeError(provider: string, props: {[key: string]: string s += 'This usually happens when AWS credentials are not available and the default account/region cannot be determined.'; return s; } + +function propsToArray(props: {[key: string]: any}): string[] { + const propArray: string[] = []; + const keys = Object.keys(props); + keys.sort(); + for (const key of keys) { + switch (typeof props[key]) { + case 'object': { + const childObjStrs = propsToArray(props[key]); + const qualifiedChildStr = childObjStrs.map( child => (`${key}.${child}`)).join(':'); + propArray.push(qualifiedChildStr); + break; + } + case 'string': { + propArray.push(`${key}=${colonQuote(props[key])}`); + break; + } + default: { + propArray.push(`${key}=${JSON.stringify(props[key])}`); + break; + } + } + } + return propArray; +} diff --git a/packages/@aws-cdk/cdk/test/test.app.ts b/packages/@aws-cdk/cdk/test/test.app.ts index f9a2fa49bd6d4..859d69b55e0a7 100644 --- a/packages/@aws-cdk/cdk/test/test.app.ts +++ b/packages/@aws-cdk/cdk/test/test.app.ts @@ -254,19 +254,22 @@ export = { super(parent, name, props); this.reportMissingContext('missing-context-key', { - provider: 'fake', + provider: 'fake', + props: { account: '12345689012', region: 'ab-north-1', - props: {}, }, + }, ); this.reportMissingContext('missing-context-key-2', { - provider: 'fake2', + provider: 'fake2', + props: { + foo: 'bar', account: '12345689012', region: 'ab-south-1', - props: {foo: 'bar'}, }, + }, ); } } @@ -279,16 +282,19 @@ export = { test.deepEqual(response.stacks[0].missing, { "missing-context-key": { - provider: 'fake', - account: '12345689012', - region: 'ab-north-1', - props: {}, + provider: 'fake', + props: { + account: '12345689012', + region: 'ab-north-1', + }, }, "missing-context-key-2": { - provider: 'fake2', - account: '12345689012', - region: 'ab-south-1', - props: {foo: 'bar'}, + provider: 'fake2', + props: { + account: '12345689012', + region: 'ab-south-1', + foo: 'bar', + }, }, }); @@ -303,15 +309,15 @@ export = { test.deepEqual(resp, { stacks: [ { - name: "stack1", - environment: { - name: "12345/us-east-1", - account: "12345", - region: "us-east-1" - } + name: "stack1", + environment: { + name: "12345/us-east-1", + account: "12345", + region: "us-east-1" + } }, { - name: "stack2" + name: "stack2" } ] }); diff --git a/packages/@aws-cdk/cdk/test/test.context.ts b/packages/@aws-cdk/cdk/test/test.context.ts index 5a272f558ea97..da984a242ee0a 100644 --- a/packages/@aws-cdk/cdk/test/test.context.ts +++ b/packages/@aws-cdk/cdk/test/test.context.ts @@ -48,7 +48,7 @@ export = { anyStringParam: 'bar', }); const key = provider.key; - test.deepEqual(key, 'ssm:12345:us-east-1:anyStringParam=bar:parameterName=foo'); + test.deepEqual(key, 'ssm:account=12345:anyStringParam=bar:parameterName=foo:region=us-east-1'); const complex = new ContextProvider(stack, 'vpc', { cidrBlock: '192.168.0.16', tags: { Name: 'MyVPC', Env: 'Preprod' }, @@ -56,7 +56,7 @@ export = { }); const complexKey = complex.key; test.deepEqual(complexKey, - 'vpc:12345:us-east-1:cidrBlock=192.168.0.16:igw=false:tagsEnv=Preprod:tagsName=MyVPC'); + 'vpc:account=12345:cidrBlock=192.168.0.16:igw=false:region=us-east-1:tags.Env=Preprod:tags.Name=MyVPC'); test.done(); }, 'SSM parameter provider will return context values if available'(test: Test) { diff --git a/packages/@aws-cdk/cx-api/lib/cxapi.ts b/packages/@aws-cdk/cx-api/lib/cxapi.ts index 1350efaeba309..a2187a1842560 100644 --- a/packages/@aws-cdk/cx-api/lib/cxapi.ts +++ b/packages/@aws-cdk/cx-api/lib/cxapi.ts @@ -24,11 +24,13 @@ 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 ContextProviderProps { +export interface MissingContext { provider: string; - account: string; - region: string; - props: {[key: string]: any}; + props: { + account: string; + region: string; + [key: string]: any; + }; } export interface HostedZoneProviderProps { @@ -64,7 +66,7 @@ export interface StackInfo extends StackId { * A complete synthesized stack */ export interface SynthesizedStack extends StackInfo { - missing?: { [key: string]: ContextProviderProps }; + missing?: { [key: string]: MissingContext }; metadata: StackMetadata; template: any; } diff --git a/packages/aws-cdk/lib/contextplugins.ts b/packages/aws-cdk/lib/contextplugins.ts index abe63af0df282..4325e311a8c22 100644 --- a/packages/aws-cdk/lib/contextplugins.ts +++ b/packages/aws-cdk/lib/contextplugins.ts @@ -4,7 +4,7 @@ import { debug } from './logging'; import { Settings } from './settings'; export interface ContextProviderPlugin { - getValue(filter: cxapi.ContextProviderProps): Promise; + getValue(args: {[key: string]: any}): Promise; } export type ProviderMap = {[name: string]: ContextProviderPlugin}; @@ -16,9 +16,9 @@ export class AZContextProviderPlugin implements ContextProviderPlugin { constructor(private readonly aws: SDK) { } - public async getValue(props: cxapi.ContextProviderProps) { - const region = props.region; - const account = props.account; + public async getValue(args: {[key: string]: any}) { + const region = args.region; + const account = args.account; debug(`Reading AZs for ${account}:${region}`); const ec2 = await this.aws.ec2(account, region, Mode.ForReading); const response = await ec2.describeAvailabilityZones().promise(); @@ -35,13 +35,13 @@ export class SSMContextProviderPlugin implements ContextProviderPlugin { constructor(private readonly aws: SDK) { } - public async getValue(filter: cxapi.ContextProviderProps) { - const region = filter.region; - const account = filter.account; - if (!('parameterName' in filter.props)) { + public async getValue(args: {[key: string]: any}) { + const region = args.region; + const account = args.account; + if (!('parameterName' in args.props)) { throw new Error('parameterName must be provided in props for SSMContextProviderPlugin'); } - const parameterName = filter.props.parameterName; + const parameterName = args.props.parameterName; debug(`Reading SSM parameter ${account}:${region}:${parameterName}`); const ssm = await this.aws.ssm(account, region, Mode.ForReading); @@ -58,32 +58,32 @@ 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}`); + public async getValue(args: {[key: string]: any}) { + const account = args.account; + const region = args.region; + if (!this.isHostedZoneProps(args)) { + throw new Error(`HostedZoneProvider requires domainName property to be set in ${args.props}`); } - const props: cxapi.HostedZoneProviderProps = filter.props; - const domainName = props.domainName; - const privateZone: boolean = !!props.privateZone; - const vpcId = + const domainName = args.domainName; 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); + const candidateZones = await this.filterZones(r53, response.HostedZones, + !!args.privateZone, args.vpcId); if (candidateZones.length > 1) { - const filteProps = `dns:${domainName}, privateZone:${privateZone}, vpcId:${vpcId}`; + const filteProps = `dns:${domainName}, privateZone:${args.privateZone}, vpcId:${args.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[] { + private async filterZones( + r53: AWS.Route53, zones: AWS.Route53.HostedZone[], + privateZone: boolean, + vpcId: string | undefined): Promise { let candidates: AWS.Route53.HostedZone[] = []; if (privateZone) { @@ -94,7 +94,7 @@ export class HostedZoneContextProviderPlugin implements ContextProviderPlugin { if (vpcId) { const vpcZones: AWS.Route53.HostedZone[] = []; for (const zone of candidates) { - r53.getHostedZone({Id: zone.Id}, (err, data) => { + await r53.getHostedZone({Id: zone.Id}, (err, data) => { if (err) { throw new Error(err.message); } @@ -120,18 +120,18 @@ export class HostedZoneContextProviderPlugin implements ContextProviderPlugin { * 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]: cxapi.ContextProviderProps }, + missingValues: { [key: string]: cxapi.MissingContext }, projectConfig: Settings, availableContextProviders: ProviderMap) { for (const key of Object.keys(missingValues)) { - const props = missingValues[key]; + const missingContext = missingValues[key]; - const provider = availableContextProviders[props.provider]; + const provider = availableContextProviders[missingContext.provider]; if (!provider) { - throw new Error(`Unrecognized context provider name: ${props.provider}`); + throw new Error(`Unrecognized context provider name: ${missingContext.provider}`); } - const value = await provider.getValue(props); + const value = await provider.getValue(missingContext.props); projectConfig.set(['context', key], value); } } diff --git a/tools/cdk-integ-tools/lib/integ-helpers.ts b/tools/cdk-integ-tools/lib/integ-helpers.ts index 700d966e5ea56..9a48d4d856e6a 100644 --- a/tools/cdk-integ-tools/lib/integ-helpers.ts +++ b/tools/cdk-integ-tools/lib/integ-helpers.ts @@ -88,8 +88,8 @@ export class IntegrationTest { 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:parameterName=/aws/service/ami-amazon-linux-latest/amzn-ami-hvm-x86_64-gp2": "ami-1234", + "availability-zones:account=12345678:region=test-region": [ "test-region-1a", "test-region-1b", "test-region-1c" ], + "ssm:account=12345678:parameterName=/aws/service/ami-amazon-linux-latest/amzn-ami-hvm-x86_64-gp2:region=test-region": "ami-1234", }; /** From b3b72262deff0a81421f81dad5bb47ebb10aeb21 Mon Sep 17 00:00:00 2001 From: Mike Cowgill Date: Sun, 14 Oct 2018 20:53:13 -0700 Subject: [PATCH 03/12] hosted zone refactor --- packages/@aws-cdk/cdk/lib/context.ts | 39 +++++++++++++++----------- packages/@aws-cdk/cx-api/lib/cxapi.ts | 10 ++----- packages/aws-cdk/lib/contextplugins.ts | 30 +++++++++++--------- 3 files changed, 41 insertions(+), 38 deletions(-) diff --git a/packages/@aws-cdk/cdk/lib/context.ts b/packages/@aws-cdk/cdk/lib/context.ts index cd250127400f1..221eace0950ff 100644 --- a/packages/@aws-cdk/cdk/lib/context.ts +++ b/packages/@aws-cdk/cdk/lib/context.ts @@ -1,4 +1,3 @@ -import cxapi = require('@aws-cdk/cx-api'); import { Stack } from './cloudformation/stack'; import { Construct } from './core/construct'; @@ -6,6 +5,11 @@ const AVAILABILITY_ZONES_PROVIDER = 'availability-zones'; const SSM_PARAMETER_PROVIDER = 'ssm'; const HOSTED_ZONE_PROVIDER = 'hosted-zone'; +export interface ContextProviderProps { + account?: string; + region?: string; + [key: string]: any; +} /** * Base class for the model side of context providers * @@ -18,18 +22,23 @@ const HOSTED_ZONE_PROVIDER = 'hosted-zone'; export class ContextProvider { private readonly stack: Stack; + private readonly props: ContextProviderProps; constructor( private readonly context: Construct, private readonly provider: string, - private readonly props: {[key: string]: any} = {}) { + props: {[key: string]: any} = {}) { this.stack = Stack.find(context); + this.props = { + account: this.stack.env.account, + region: this.stack.env.region, + ...props, + }; } public get key(): string { const propStrings: string[] = propsToArray({ ...this.props, - ...{account: this.account, region: this.region}, }); return `${this.provider}:${propStrings.join(':')}`; } @@ -43,7 +52,7 @@ export class ContextProvider { public getStringValue( defaultValue: string): string { // if scope is undefined, this is probably a test mode, so we just // return the default value - if (!this.account || !this.region) { + if (!this.props.account || !this.props.region) { this.context.addError(formatMissingScopeError(this.provider, this.props)); return defaultValue; } @@ -59,7 +68,7 @@ export class ContextProvider { this.stack.reportMissingContext(this.key, { provider: this.provider, - props: { ...this.props, ...{region: this.region, account: this.account} }, + props: this.props, }); return defaultValue; } @@ -76,7 +85,7 @@ export class ContextProvider { // 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) { + if (!this.props.account || !this.props.region) { this.context.addError(formatMissingScopeError(this.provider, this.props)); return defaultValue; } @@ -92,19 +101,11 @@ export class ContextProvider { this.stack.reportMissingContext(this.key, { provider: this.provider, - props: { ...this.props, ...{region: this.region, account: this.account} }, + props: this.props, }); return defaultValue; } - - private get account(): string | undefined { - return this.stack.env.account; - } - - private get region(): string | undefined { - return this.stack.env.region; - } } /** @@ -157,12 +158,18 @@ export class SSMParameterProvider { } } +export interface HostedZoneProviderProps { + domainName: 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, props: cxapi.HostedZoneProviderProps) { + constructor(context: Construct, props: HostedZoneProviderProps) { this.provider = new ContextProvider(context, HOSTED_ZONE_PROVIDER, props); } /** diff --git a/packages/@aws-cdk/cx-api/lib/cxapi.ts b/packages/@aws-cdk/cx-api/lib/cxapi.ts index a2187a1842560..9b3970625102f 100644 --- a/packages/@aws-cdk/cx-api/lib/cxapi.ts +++ b/packages/@aws-cdk/cx-api/lib/cxapi.ts @@ -27,18 +27,12 @@ export type CXRequest = ListStacksRequest | SynthesizeRequest; export interface MissingContext { provider: string; props: { - account: string; - region: string; + account?: string; + region?: string; [key: string]: any; }; } -export interface HostedZoneProviderProps { - domainName: string; - privateZone?: boolean; - vpcId?: string; -} - export interface ListStacksResponse { stacks: StackInfo[] } diff --git a/packages/aws-cdk/lib/contextplugins.ts b/packages/aws-cdk/lib/contextplugins.ts index 4325e311a8c22..da826d0489aa6 100644 --- a/packages/aws-cdk/lib/contextplugins.ts +++ b/packages/aws-cdk/lib/contextplugins.ts @@ -53,6 +53,12 @@ export class SSMContextProviderPlugin implements ContextProviderPlugin { } } +export interface HostedZoneProviderProps { + domainName: string; + privateZone?: boolean; + vpcId?: string; +} + export class HostedZoneContextProviderPlugin implements ContextProviderPlugin { constructor(private readonly aws: SDK) { @@ -94,26 +100,22 @@ export class HostedZoneContextProviderPlugin implements ContextProviderPlugin { if (vpcId) { const vpcZones: AWS.Route53.HostedZone[] = []; for (const zone of candidates) { - await 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); - } - }); + const data = await r53.getHostedZone({ Id: zone. Id }).promise(); + if (!data.VPCs) { + debug(`Expected VPC for private zone but no VPC found ${zone.Id}`); + continue; + } + 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; + private isHostedZoneProps(props: HostedZoneProviderProps | any): props is HostedZoneProviderProps { + return (props as HostedZoneProviderProps).domainName !== undefined; } } /** From ca25785b65055c9cc5ca6e661cbb24f58ed151c3 Mon Sep 17 00:00:00 2001 From: Mike Cowgill Date: Sun, 14 Oct 2018 23:01:37 -0700 Subject: [PATCH 04/12] fixing commit merge --- packages/@aws-cdk/cdk/lib/context.ts | 6 ++--- packages/@aws-cdk/cdk/package-lock.json | 2 +- packages/@aws-cdk/cdk/test/test.app.ts | 29 ------------------------- 3 files changed, 3 insertions(+), 34 deletions(-) diff --git a/packages/@aws-cdk/cdk/lib/context.ts b/packages/@aws-cdk/cdk/lib/context.ts index 9c8e88f178560..135025dedaaa7 100644 --- a/packages/@aws-cdk/cdk/lib/context.ts +++ b/packages/@aws-cdk/cdk/lib/context.ts @@ -153,12 +153,10 @@ export class SSMParameterProvider { /** * Return the SSM parameter string with the indicated key */ - public getString(parameterName: string, defaultValue: string = "dummy"): any { - const scope = this.provider.accountRegionScope('SSMParameterProvider'); - return this.provider.getStringValue(SSM_PARAMETER_PROVIDER, scope, [parameterName], defaultValue); + public parameterValue(): any { + return this.provider.getStringValue('dummy'); } } - export interface HostedZoneProviderProps { domainName: string; privateZone?: boolean; diff --git a/packages/@aws-cdk/cdk/package-lock.json b/packages/@aws-cdk/cdk/package-lock.json index 74d0009ee822e..143432e7f8fa8 100644 --- a/packages/@aws-cdk/cdk/package-lock.json +++ b/packages/@aws-cdk/cdk/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/cdk", - "version": "0.11.0", + "version": "0.12.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/@aws-cdk/cdk/test/test.app.ts b/packages/@aws-cdk/cdk/test/test.app.ts index 9c8d627025980..9b8d7325e1203 100644 --- a/packages/@aws-cdk/cdk/test/test.app.ts +++ b/packages/@aws-cdk/cdk/test/test.app.ts @@ -264,35 +264,6 @@ export = { test.done(); }, - - 'requests can also be base64 encoded'(test: Test) { - const req = { - type: 'list' - }; - const resp = main('base64:' + new Buffer(JSON.stringify(req)).toString('base64')); - test.deepEqual(resp, { - stacks: [ - { - name: "stack1", - environment: { - name: "12345/us-east-1", - account: "12345", - region: "us-east-1" - } - }, - { - name: "stack2" - } - ] - }); - - test.done(); - }, - - 'fails when base64 cannot be encoded'(test: Test) { - test.throws(() => main('base64:'), /Unexpected end of JSON input/); - test.done(); - } }; class MyConstruct extends Construct { From 0e13281bfe7530a7195c08f5c7ca6c5553197ca1 Mon Sep 17 00:00:00 2001 From: Mike Cowgill Date: Mon, 15 Oct 2018 12:34:23 -0700 Subject: [PATCH 05/12] initial working filters and errors --- .../aws-route53/lib/hosted-zone-provider.ts | 57 ++++++++++++++ packages/@aws-cdk/aws-route53/lib/index.ts | 1 + packages/@aws-cdk/cdk/lib/context.ts | 77 ++++++++++++------- packages/@aws-cdk/cdk/test/test.context.ts | 24 +++--- packages/aws-cdk/lib/api/util/sdk.ts | 1 - packages/aws-cdk/lib/contextplugins.ts | 24 +++--- 6 files changed, 131 insertions(+), 53 deletions(-) create mode 100644 packages/@aws-cdk/aws-route53/lib/hosted-zone-provider.ts diff --git a/packages/@aws-cdk/aws-route53/lib/hosted-zone-provider.ts b/packages/@aws-cdk/aws-route53/lib/hosted-zone-provider.ts new file mode 100644 index 0000000000000..74289885ab470 --- /dev/null +++ b/packages/@aws-cdk/aws-route53/lib/hosted-zone-provider.ts @@ -0,0 +1,57 @@ +import cdk = require('@aws-cdk/cdk'); +import { HostedZoneRefProps } from './hosted-zone-ref'; + +export interface HostedZoneProviderProps { + domainName: string; + privateZone?: boolean; + vpcId?: string; +} + +const HOSTED_ZONE_PROVIDER = 'hosted-zone'; + +const DEFAULT_HOSTED_ZONE: HostedZoneRefProps = { + hostedZoneId: '/hostedzone/DUMMY', + zoneName: 'example.com', +}; + +interface AwsHostedZone { + Id: string; + Name: string; +} + +/** + * Context provider that will lookup the Hosted Zone ID for the given arguments + */ +export class HostedZoneProvider { + private provider: cdk.ContextProvider; + constructor(context: cdk.Construct, props: HostedZoneProviderProps) { + this.provider = new cdk.ContextProvider(context, HOSTED_ZONE_PROVIDER, props); + } + /** + * Return the hosted zone meeting the filter + */ + public findHostedZone(): HostedZoneRefProps { + const zone = this.provider.getValue(DEFAULT_HOSTED_ZONE); + if (zone === DEFAULT_HOSTED_ZONE) { + return zone; + } + if (!this.isAwsHostedZone(zone)) { + throw new Error(`Expected an AWS Hosted Zone received ${JSON.stringify(zone)}`); + } else { + const actualZone = zone as AwsHostedZone; + // CDK handles the '.' at the end, so remove it here + if (actualZone.Name.endsWith('.')) { + actualZone.Name = actualZone.Name.substring(0, actualZone.Name.length - 1); + } + return { + hostedZoneId: actualZone.Id, + zoneName: actualZone.Name, + }; + } + } + + private isAwsHostedZone(zone: AwsHostedZone | any): zone is AwsHostedZone { + const candidateZone = zone as AwsHostedZone; + return candidateZone.Name !== undefined && candidateZone.Id !== undefined; + } +} diff --git a/packages/@aws-cdk/aws-route53/lib/index.ts b/packages/@aws-cdk/aws-route53/lib/index.ts index 734879c561c12..d333674618ecf 100644 --- a/packages/@aws-cdk/aws-route53/lib/index.ts +++ b/packages/@aws-cdk/aws-route53/lib/index.ts @@ -1,4 +1,5 @@ export * from './hosted-zone'; +export * from './hosted-zone-provider'; export * from './hosted-zone-ref'; export * from './records'; diff --git a/packages/@aws-cdk/cdk/lib/context.ts b/packages/@aws-cdk/cdk/lib/context.ts index 135025dedaaa7..29fbd02b1b609 100644 --- a/packages/@aws-cdk/cdk/lib/context.ts +++ b/packages/@aws-cdk/cdk/lib/context.ts @@ -3,7 +3,7 @@ import { Construct } from './core/construct'; const AVAILABILITY_ZONES_PROVIDER = 'availability-zones'; const SSM_PARAMETER_PROVIDER = 'ssm'; -const HOSTED_ZONE_PROVIDER = 'hosted-zone'; +// const HOSTED_ZONE_PROVIDER = 'hosted-zone'; export interface ContextProviderProps { account?: string; @@ -42,11 +42,32 @@ export class ContextProvider { }); return `${this.provider}:${propStrings.join(':')}`; } + + /** + * Read a provider value and verify it is not `null` + */ + public getValue(defaultValue: any): any { + // if account or region is not defined this is probably a test mode, so we just + // return the default value + if (!this.props.account || !this.props.region) { + this.context.addError(formatMissingScopeError(this.provider, this.props)); + return defaultValue; + } + + const value = this.context.getContext(this.key); + + if (value != null) { + return value; + } + + this.stack.reportMissingContext(this.key, { + provider: this.provider, + props: this.props, + }); + return defaultValue; + } /** * Read a provider value, verifying it's a string - * @param provider The name of the context provider - * @param scope The scope (e.g. account/region) for the value - * @param args Any arguments * @param defaultValue The value to return if there is no value defined for this context key */ public getStringValue( defaultValue: string): string { @@ -61,7 +82,7 @@ export class ContextProvider { if (value != null) { if (typeof value !== 'string') { - throw new TypeError(`Expected context parameter '${this.key}' to be a string, but got '${value}'`); + throw new TypeError(`Expected context parameter '${this.key}' to be a string, but got '${JSON.stringify(value)}'`); } return value; } @@ -75,9 +96,6 @@ export class ContextProvider { /** * Read a provider value, verifying it's a list - * @param provider The name of the context provider - * @param scope The scope (e.g. account/region) for the value - * @param args Any arguments * @param defaultValue The value to return if there is no value defined for this context key */ public getStringListValue( @@ -94,7 +112,7 @@ export class ContextProvider { if (value != null) { if (!value.map) { - throw new Error(`Context value '${this.key}' is supposed to be a list, got '${value}'`); + throw new Error(`Context value '${this.key}' is supposed to be a list, got '${JSON.stringify(value)}'`); } return value; } @@ -157,27 +175,28 @@ export class SSMParameterProvider { return this.provider.getStringValue('dummy'); } } -export interface HostedZoneProviderProps { - domainName: 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, props: 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'); - } -} +// export interface HostedZoneProviderProps { +// domainName: 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, props: 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'); +// } +// } function formatMissingScopeError(provider: string, props: {[key: string]: string}) { let s = `Cannot determine scope for context provider ${provider}`; diff --git a/packages/@aws-cdk/cdk/test/test.context.ts b/packages/@aws-cdk/cdk/test/test.context.ts index 979025a2251e5..1f34de787e8b1 100644 --- a/packages/@aws-cdk/cdk/test/test.context.ts +++ b/packages/@aws-cdk/cdk/test/test.context.ts @@ -1,7 +1,7 @@ import cxapi = require('@aws-cdk/cx-api'); import { Test } from 'nodeunit'; import { App, AvailabilityZoneProvider, Construct, ContextProvider, - HostedZoneProvider, MetadataEntry, resolve, SSMParameterProvider, Stack } from '../lib'; + MetadataEntry, resolve, SSMParameterProvider, Stack } from '../lib'; export = { 'AvailabilityZoneProvider returns a list with dummy values if the context is not available'(test: Test) { @@ -72,17 +72,17 @@ export = { 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(); - }, + // '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(); diff --git a/packages/aws-cdk/lib/api/util/sdk.ts b/packages/aws-cdk/lib/api/util/sdk.ts index d4c392701c9ec..ee91083528a01 100644 --- a/packages/aws-cdk/lib/api/util/sdk.ts +++ b/packages/aws-cdk/lib/api/util/sdk.ts @@ -107,7 +107,6 @@ export class SDK { return new AWS.Route53({ region, credentials: await this.credentialsCache.get(awsAccountId, mode), - ...this.defaultClientArgs }); } public async defaultRegion(): Promise { diff --git a/packages/aws-cdk/lib/contextplugins.ts b/packages/aws-cdk/lib/contextplugins.ts index 2feaf9f9662bc..6cd1c33bd2a39 100644 --- a/packages/aws-cdk/lib/contextplugins.ts +++ b/packages/aws-cdk/lib/contextplugins.ts @@ -77,27 +77,29 @@ export class HostedZoneContextProviderPlugin implements ContextProviderPlugin { if (!response.HostedZones) { throw new Error(`Hosted Zone not found in account ${account}, region ${region}: ${domainName}`); } - const candidateZones = await this.filterZones(r53, response.HostedZones, - !!args.privateZone, args.vpcId); - if (candidateZones.length > 1) { + const candidateZones = await this.filterZones(r53, response.HostedZones, args); + if (candidateZones.length !== 1) { const filteProps = `dns:${domainName}, privateZone:${args.privateZone}, vpcId:${args.vpcId}`; - throw new Error(`Found more than one matching HostedZone ${candidateZones} for ${filteProps}`); + throw new Error(`Found zones: ${JSON.stringify(candidateZones)} for ${filteProps}, but wanted exactly 1 zone`); } return candidateZones[0]; } private async filterZones( r53: AWS.Route53, zones: AWS.Route53.HostedZone[], - privateZone: boolean, - vpcId: string | undefined): Promise { + props: HostedZoneProviderProps): Promise { let candidates: AWS.Route53.HostedZone[] = []; - if (privateZone) { - candidates = zones.filter(zone => zone.Config && zone.Config.PrivateZone); + const domainName = props.domainName.endsWith('.') ? props.domainName : `${props.domainName}.`; + debug(`Found the following zones ${JSON.stringify(zones)}`); + candidates = zones.filter( zone => zone.Name === domainName); + debug(`Found the following matched name zones ${JSON.stringify(candidates)}`); + if (props.privateZone) { + candidates = candidates.filter(zone => zone.Config && zone.Config.PrivateZone); } else { - candidates = zones.filter(zone => !zone.Config || !zone.Config.PrivateZone); + candidates = candidates.filter(zone => !zone.Config || !zone.Config.PrivateZone); } - if (vpcId) { + if (props.vpcId) { const vpcZones: AWS.Route53.HostedZone[] = []; for (const zone of candidates) { const data = await r53.getHostedZone({ Id: zone. Id }).promise(); @@ -105,7 +107,7 @@ export class HostedZoneContextProviderPlugin implements ContextProviderPlugin { debug(`Expected VPC for private zone but no VPC found ${zone.Id}`); continue; } - if (data.VPCs.map(vpc => vpc.VPCId).includes(vpcId)) { + if (data.VPCs.map(vpc => vpc.VPCId).includes(props.vpcId)) { vpcZones.push(zone); } } From 318afccc0bdeb5a3bfd8465b87217f7acb692c81 Mon Sep 17 00:00:00 2001 From: Mike Cowgill Date: Mon, 15 Oct 2018 14:23:09 -0700 Subject: [PATCH 06/12] moving test into route53 module --- .../test/test.hosted-zone-provider.ts | 36 +++++++++++++++++++ packages/@aws-cdk/cdk/test/test.context.ts | 11 ------ 2 files changed, 36 insertions(+), 11 deletions(-) create mode 100644 packages/@aws-cdk/aws-route53/test/test.hosted-zone-provider.ts diff --git a/packages/@aws-cdk/aws-route53/test/test.hosted-zone-provider.ts b/packages/@aws-cdk/aws-route53/test/test.hosted-zone-provider.ts new file mode 100644 index 0000000000000..4e04b219f1083 --- /dev/null +++ b/packages/@aws-cdk/aws-route53/test/test.hosted-zone-provider.ts @@ -0,0 +1,36 @@ +import cdk = require('@aws-cdk/cdk'); +import { Test } from 'nodeunit'; +import { HostedZoneProvider } from '../lib'; + +export = { + 'Hosted Zone Provider': { + 'HostedZoneProvider will return context values if availble'(test: Test) { + const stack = new cdk.Stack(undefined, 'TestStack', { env: { account: '12345', region: 'us-east-1' } }); + const filter = {domainName: 'test.com'}; + new HostedZoneProvider(stack, filter).findHostedZone(); + const key = Object.keys(stack.missingContext)[0]; + + const fakeZone = { + Id: "/hostedzone/11111111111111", + Name: "example.com.", + CallerReference: "TestLates-PublicZo-OESZPDFV7G6A", + Config: { + Comment: "CDK created", + PrivateZone: false + }, + ResourceRecordSetCount: 3 + }; + + stack.setContext(key, fakeZone); + + const cdkZone = { + hostedZoneId: fakeZone.Id, + zoneName: 'example.com', + }; + + const zone = cdk.resolve(new HostedZoneProvider(stack, filter).findHostedZone()); + test.deepEqual(zone, cdkZone); + test.done(); + }, + } +}; diff --git a/packages/@aws-cdk/cdk/test/test.context.ts b/packages/@aws-cdk/cdk/test/test.context.ts index 1f34de787e8b1..4a9b67b559abb 100644 --- a/packages/@aws-cdk/cdk/test/test.context.ts +++ b/packages/@aws-cdk/cdk/test/test.context.ts @@ -72,17 +72,6 @@ export = { 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(); From ffa83c3c017c943c21808d2dcf5d3629998ff69b Mon Sep 17 00:00:00 2001 From: Mike Cowgill Date: Mon, 15 Oct 2018 16:39:09 -0700 Subject: [PATCH 07/12] removing commented code --- packages/@aws-cdk/cdk/lib/context.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/@aws-cdk/cdk/lib/context.ts b/packages/@aws-cdk/cdk/lib/context.ts index 29fbd02b1b609..ccb2d8549b48a 100644 --- a/packages/@aws-cdk/cdk/lib/context.ts +++ b/packages/@aws-cdk/cdk/lib/context.ts @@ -3,7 +3,6 @@ import { Construct } from './core/construct'; const AVAILABILITY_ZONES_PROVIDER = 'availability-zones'; const SSM_PARAMETER_PROVIDER = 'ssm'; -// const HOSTED_ZONE_PROVIDER = 'hosted-zone'; export interface ContextProviderProps { account?: string; From c0d863dc695dd5f6b16c7f858985c6dd01514005 Mon Sep 17 00:00:00 2001 From: Mike Cowgill Date: Wed, 17 Oct 2018 11:43:40 -0700 Subject: [PATCH 08/12] adding findAndImport to HostedZoneProvider --- .../aws-route53/lib/hosted-zone-provider.ts | 9 +++++++- .../test/test.hosted-zone-provider.ts | 21 +++++++++++++++---- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/packages/@aws-cdk/aws-route53/lib/hosted-zone-provider.ts b/packages/@aws-cdk/aws-route53/lib/hosted-zone-provider.ts index 74289885ab470..1a54cf668fee1 100644 --- a/packages/@aws-cdk/aws-route53/lib/hosted-zone-provider.ts +++ b/packages/@aws-cdk/aws-route53/lib/hosted-zone-provider.ts @@ -1,5 +1,5 @@ import cdk = require('@aws-cdk/cdk'); -import { HostedZoneRefProps } from './hosted-zone-ref'; +import { HostedZoneRef, HostedZoneRefProps } from './hosted-zone-ref'; export interface HostedZoneProviderProps { domainName: string; @@ -27,6 +27,13 @@ export class HostedZoneProvider { constructor(context: cdk.Construct, props: HostedZoneProviderProps) { this.provider = new cdk.ContextProvider(context, HOSTED_ZONE_PROVIDER, props); } + + /** + * This method calls `findHostedZone` and returns the imported `HostedZoneRef` + */ + public findAndImport(parent: cdk.Construct, id: string): HostedZoneRef { + return HostedZoneRef.import(parent, id, this.findHostedZone()); + } /** * Return the hosted zone meeting the filter */ diff --git a/packages/@aws-cdk/aws-route53/test/test.hosted-zone-provider.ts b/packages/@aws-cdk/aws-route53/test/test.hosted-zone-provider.ts index 4e04b219f1083..c923b71488b56 100644 --- a/packages/@aws-cdk/aws-route53/test/test.hosted-zone-provider.ts +++ b/packages/@aws-cdk/aws-route53/test/test.hosted-zone-provider.ts @@ -1,10 +1,11 @@ import cdk = require('@aws-cdk/cdk'); import { Test } from 'nodeunit'; -import { HostedZoneProvider } from '../lib'; +import { HostedZoneProvider, HostedZoneRef, HostedZoneRefProps } from '../lib'; export = { 'Hosted Zone Provider': { 'HostedZoneProvider will return context values if availble'(test: Test) { + // GIVEN const stack = new cdk.Stack(undefined, 'TestStack', { env: { account: '12345', region: 'us-east-1' } }); const filter = {domainName: 'test.com'}; new HostedZoneProvider(stack, filter).findHostedZone(); @@ -23,13 +24,25 @@ export = { stack.setContext(key, fakeZone); - const cdkZone = { + const cdkZoneProps: HostedZoneRefProps = { hostedZoneId: fakeZone.Id, zoneName: 'example.com', }; - const zone = cdk.resolve(new HostedZoneProvider(stack, filter).findHostedZone()); - test.deepEqual(zone, cdkZone); + const cdkZone = HostedZoneRef.import(stack, 'MyZone', cdkZoneProps); + + // WHEN + const provider = new HostedZoneProvider(stack, filter); + const zoneProps = cdk.resolve(provider.findHostedZone()); + const zoneRef = provider.findAndImport(stack, 'MyZoneProvider'); + + // THEN + test.deepEqual(zoneProps, cdkZoneProps); + test.deepEqual(zoneRef.hostedZoneId, cdkZone.hostedZoneId); + test.done(); + }, + 'findAndImport will return a HostedZoneRef'(test: Test) { + test.done(); }, } From 42758db8ebfc2353c9798f244ed47c7f957e7b3c Mon Sep 17 00:00:00 2001 From: Mike Cowgill Date: Wed, 17 Oct 2018 12:04:45 -0700 Subject: [PATCH 09/12] refactor context provider props from interface to type --- .../aws-route53/lib/hosted-zone-provider.ts | 14 ++++++++++++++ packages/@aws-cdk/cdk/lib/context.ts | 8 ++------ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/packages/@aws-cdk/aws-route53/lib/hosted-zone-provider.ts b/packages/@aws-cdk/aws-route53/lib/hosted-zone-provider.ts index 1a54cf668fee1..f1bf7205a9d9b 100644 --- a/packages/@aws-cdk/aws-route53/lib/hosted-zone-provider.ts +++ b/packages/@aws-cdk/aws-route53/lib/hosted-zone-provider.ts @@ -1,9 +1,23 @@ import cdk = require('@aws-cdk/cdk'); import { HostedZoneRef, HostedZoneRefProps } from './hosted-zone-ref'; +/** + * Zone properties for looking up the Hosted Zone + */ export interface HostedZoneProviderProps { + /** + * The zone domain e.g. example.com + */ domainName: string; + + /** + * Is this a private zone + */ privateZone?: boolean; + + /** + * If this is a private zone which VPC is assocaitated + */ vpcId?: string; } diff --git a/packages/@aws-cdk/cdk/lib/context.ts b/packages/@aws-cdk/cdk/lib/context.ts index ccb2d8549b48a..b778d485fb4af 100644 --- a/packages/@aws-cdk/cdk/lib/context.ts +++ b/packages/@aws-cdk/cdk/lib/context.ts @@ -4,11 +4,7 @@ import { Construct } from './core/construct'; const AVAILABILITY_ZONES_PROVIDER = 'availability-zones'; const SSM_PARAMETER_PROVIDER = 'ssm'; -export interface ContextProviderProps { - account?: string; - region?: string; - [key: string]: any; -} +type ContextProviderProps = {[key: string]: any}; /** * Base class for the model side of context providers * @@ -26,7 +22,7 @@ export class ContextProvider { constructor( private readonly context: Construct, private readonly provider: string, - props: {[key: string]: any} = {}) { + props: ContextProviderProps = {}) { this.stack = Stack.find(context); this.props = { account: this.stack.env.account, From 47816e0260380ba1c2ee45adc55c3b0fd285993c Mon Sep 17 00:00:00 2001 From: Mike Cowgill Date: Wed, 17 Oct 2018 12:11:32 -0700 Subject: [PATCH 10/12] resolving conflict --- packages/@aws-cdk/cdk/package-lock.json | 46 +++++++++++++++++++++---- 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/packages/@aws-cdk/cdk/package-lock.json b/packages/@aws-cdk/cdk/package-lock.json index 143432e7f8fa8..9026349c1c045 100644 --- a/packages/@aws-cdk/cdk/package-lock.json +++ b/packages/@aws-cdk/cdk/package-lock.json @@ -1,14 +1,16 @@ { - "name": "@aws-cdk/cdk", - "version": "0.12.0", - "lockfileVersion": 1, "requires": true, + "lockfileVersion": 1, "dependencies": { "@types/js-base64": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/@types/js-base64/-/js-base64-2.3.1.tgz", - "integrity": "sha512-4RKbhIDGC87s4EBy2Cp2/5S2O6kmCRcZnD5KRCq1q9z2GhBte1+BdsfVKCpG8yKpDGNyEE2G6IqFIh6W2YwWPA==", - "dev": true + "integrity": "sha512-4RKbhIDGC87s4EBy2Cp2/5S2O6kmCRcZnD5KRCq1q9z2GhBte1+BdsfVKCpG8yKpDGNyEE2G6IqFIh6W2YwWPA==" + }, + "@types/lodash": { + "version": "4.14.117", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.117.tgz", + "integrity": "sha512-xyf2m6tRbz8qQKcxYZa7PA4SllYcay+eh25DN3jmNYY6gSTL7Htc/bttVdkqj2wfJGbeWlQiX8pIyJpKU+tubw==" }, "cli-color": { "version": "0.1.7", @@ -39,6 +41,15 @@ "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.8.2.tgz", "integrity": "sha1-q6jZ4ZQ6iVrJaDemKjmz9V7NlKs=" }, + "fast-check": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-1.6.2.tgz", + "integrity": "sha512-RmCPZYkDfRGxPg/CEjuCA3bcb53aVOz495Cw+1IIuOr5S3sznLRTGIQF2U/AaanMbNg7UacZIGCWwiqp54ifAw==", + "requires": { + "lorem-ipsum": "~1.0.6", + "pure-rand": "^1.4.2" + } + }, "heap": { "version": "0.2.6", "resolved": "https://registry.npmjs.org/heap/-/heap-0.2.6.tgz", @@ -51,7 +62,7 @@ }, "json-diff": { "version": "0.3.1", - "resolved": "https://registry.npmjs.org/json-diff/-/json-diff-0.3.1.tgz", + "resolved": "http://registry.npmjs.org/json-diff/-/json-diff-0.3.1.tgz", "integrity": "sha1-bbw64tJeB1p/1xvNmHRFhmb7aBs=", "requires": { "cli-color": "~0.1.6", @@ -59,6 +70,29 @@ "dreamopt": "~0.6.0" } }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" + }, + "lorem-ipsum": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/lorem-ipsum/-/lorem-ipsum-1.0.6.tgz", + "integrity": "sha512-Rx4XH8X4KSDCKAVvWGYlhAfNqdUP5ZdT4rRyf0jjrvWgtViZimDIlopWNfn/y3lGM5K4uuiAoY28TaD+7YKFrQ==", + "requires": { + "minimist": "~1.2.0" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + }, + "pure-rand": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-1.4.2.tgz", + "integrity": "sha512-5WrOH3ZPZgwW5CRyeNxmZ8BcQnL6s0YWGOZL6SROLfhIw9Uc1SseEyeNw9q5tc3Y5E783yzvNlsE9KJY8IuxcA==" + }, "wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", From 0cde788fb798b28da8ac5c56d27bfa66000ab8ff Mon Sep 17 00:00:00 2001 From: Mike Cowgill Date: Thu, 18 Oct 2018 11:14:37 -0700 Subject: [PATCH 11/12] resolving conflicts in package-lock --- packages/@aws-cdk/cdk/package-lock.json | 46 ++++--------------------- 1 file changed, 6 insertions(+), 40 deletions(-) diff --git a/packages/@aws-cdk/cdk/package-lock.json b/packages/@aws-cdk/cdk/package-lock.json index 9026349c1c045..143432e7f8fa8 100644 --- a/packages/@aws-cdk/cdk/package-lock.json +++ b/packages/@aws-cdk/cdk/package-lock.json @@ -1,16 +1,14 @@ { - "requires": true, + "name": "@aws-cdk/cdk", + "version": "0.12.0", "lockfileVersion": 1, + "requires": true, "dependencies": { "@types/js-base64": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/@types/js-base64/-/js-base64-2.3.1.tgz", - "integrity": "sha512-4RKbhIDGC87s4EBy2Cp2/5S2O6kmCRcZnD5KRCq1q9z2GhBte1+BdsfVKCpG8yKpDGNyEE2G6IqFIh6W2YwWPA==" - }, - "@types/lodash": { - "version": "4.14.117", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.117.tgz", - "integrity": "sha512-xyf2m6tRbz8qQKcxYZa7PA4SllYcay+eh25DN3jmNYY6gSTL7Htc/bttVdkqj2wfJGbeWlQiX8pIyJpKU+tubw==" + "integrity": "sha512-4RKbhIDGC87s4EBy2Cp2/5S2O6kmCRcZnD5KRCq1q9z2GhBte1+BdsfVKCpG8yKpDGNyEE2G6IqFIh6W2YwWPA==", + "dev": true }, "cli-color": { "version": "0.1.7", @@ -41,15 +39,6 @@ "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.8.2.tgz", "integrity": "sha1-q6jZ4ZQ6iVrJaDemKjmz9V7NlKs=" }, - "fast-check": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-1.6.2.tgz", - "integrity": "sha512-RmCPZYkDfRGxPg/CEjuCA3bcb53aVOz495Cw+1IIuOr5S3sznLRTGIQF2U/AaanMbNg7UacZIGCWwiqp54ifAw==", - "requires": { - "lorem-ipsum": "~1.0.6", - "pure-rand": "^1.4.2" - } - }, "heap": { "version": "0.2.6", "resolved": "https://registry.npmjs.org/heap/-/heap-0.2.6.tgz", @@ -62,7 +51,7 @@ }, "json-diff": { "version": "0.3.1", - "resolved": "http://registry.npmjs.org/json-diff/-/json-diff-0.3.1.tgz", + "resolved": "https://registry.npmjs.org/json-diff/-/json-diff-0.3.1.tgz", "integrity": "sha1-bbw64tJeB1p/1xvNmHRFhmb7aBs=", "requires": { "cli-color": "~0.1.6", @@ -70,29 +59,6 @@ "dreamopt": "~0.6.0" } }, - "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" - }, - "lorem-ipsum": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/lorem-ipsum/-/lorem-ipsum-1.0.6.tgz", - "integrity": "sha512-Rx4XH8X4KSDCKAVvWGYlhAfNqdUP5ZdT4rRyf0jjrvWgtViZimDIlopWNfn/y3lGM5K4uuiAoY28TaD+7YKFrQ==", - "requires": { - "minimist": "~1.2.0" - } - }, - "minimist": { - "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" - }, - "pure-rand": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-1.4.2.tgz", - "integrity": "sha512-5WrOH3ZPZgwW5CRyeNxmZ8BcQnL6s0YWGOZL6SROLfhIw9Uc1SseEyeNw9q5tc3Y5E783yzvNlsE9KJY8IuxcA==" - }, "wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", From 9dd737a3bd59c4e719fd76f38dbcfe4ef340f6ea Mon Sep 17 00:00:00 2001 From: Mike Cowgill Date: Fri, 19 Oct 2018 08:47:32 -0700 Subject: [PATCH 12/12] cleaning up code --- .../test/test.hosted-zone-provider.ts | 4 --- packages/@aws-cdk/cdk/lib/context.ts | 29 +++---------------- packages/aws-cdk/lib/contextplugins.ts | 14 +++++++++ 3 files changed, 18 insertions(+), 29 deletions(-) diff --git a/packages/@aws-cdk/aws-route53/test/test.hosted-zone-provider.ts b/packages/@aws-cdk/aws-route53/test/test.hosted-zone-provider.ts index c923b71488b56..a26f57fe36449 100644 --- a/packages/@aws-cdk/aws-route53/test/test.hosted-zone-provider.ts +++ b/packages/@aws-cdk/aws-route53/test/test.hosted-zone-provider.ts @@ -41,9 +41,5 @@ export = { test.deepEqual(zoneRef.hostedZoneId, cdkZone.hostedZoneId); test.done(); }, - 'findAndImport will return a HostedZoneRef'(test: Test) { - - test.done(); - }, } }; diff --git a/packages/@aws-cdk/cdk/lib/context.ts b/packages/@aws-cdk/cdk/lib/context.ts index b778d485fb4af..1492113bc1180 100644 --- a/packages/@aws-cdk/cdk/lib/context.ts +++ b/packages/@aws-cdk/cdk/lib/context.ts @@ -32,9 +32,7 @@ export class ContextProvider { } public get key(): string { - const propStrings: string[] = propsToArray({ - ...this.props, - }); + const propStrings: string[] = propsToArray(this.props); return `${this.provider}:${propStrings.join(':')}`; } @@ -151,6 +149,9 @@ export class AvailabilityZoneProvider { } export interface SSMParameterProviderProps { + /** + * The name of the parameter to lookup + */ parameterName: string; } /** @@ -171,28 +172,6 @@ export class SSMParameterProvider { } } -// export interface HostedZoneProviderProps { -// domainName: 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, props: 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'); -// } -// } - 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]}`)); diff --git a/packages/aws-cdk/lib/contextplugins.ts b/packages/aws-cdk/lib/contextplugins.ts index 6cd1c33bd2a39..6817b30ff1e9b 100644 --- a/packages/aws-cdk/lib/contextplugins.ts +++ b/packages/aws-cdk/lib/contextplugins.ts @@ -54,8 +54,22 @@ export class SSMContextProviderPlugin implements ContextProviderPlugin { } export interface HostedZoneProviderProps { + /** + * The domain name e.g. example.com to lookup + */ domainName: string; + + /** + * True if the zone you want to find is a private hosted zone + */ privateZone?: boolean; + + /** + * The VPC ID to that the private zone must be associated with + * + * If you provide VPC ID and privateZone is false, this will return no results + * and raise an error. + */ vpcId?: string; }