diff --git a/packages/@aws-cdk/aws-ec2/README.md b/packages/@aws-cdk/aws-ec2/README.md index b8a583d1a2ea5..d5a2d175a38a7 100644 --- a/packages/@aws-cdk/aws-ec2/README.md +++ b/packages/@aws-cdk/aws-ec2/README.md @@ -503,6 +503,9 @@ examples of things you might want to use: > `cdk.context.json`, or use the `cdk context` command. For more information, see > [Runtime Context](https://docs.aws.amazon.com/cdk/latest/guide/context.html) in the CDK > developer guide. +> +> `MachineImage.genericLinux()`, `MachineImage.genericWindows()` will use `CfnMapping` in +> an agnostic stack. ## Special VPC configurations diff --git a/packages/@aws-cdk/aws-ec2/lib/machine-image.ts b/packages/@aws-cdk/aws-ec2/lib/machine-image.ts index 34405181774a1..e01869f7866c1 100644 --- a/packages/@aws-cdk/aws-ec2/lib/machine-image.ts +++ b/packages/@aws-cdk/aws-ec2/lib/machine-image.ts @@ -1,6 +1,6 @@ import * as ssm from '@aws-cdk/aws-ssm'; import * as cxschema from '@aws-cdk/cloud-assembly-schema'; -import { Construct, ContextProvider, Stack, Token } from '@aws-cdk/core'; +import { Construct, ContextProvider, CfnMapping, Aws } from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; import { UserData } from './user-data'; import { WindowsVersion } from './windows-versions'; @@ -363,22 +363,25 @@ export interface GenericWindowsImageProps { * manually specify an AMI map. */ export class GenericLinuxImage implements IMachineImage { - constructor(private readonly amiMap: {[region: string]: string}, private readonly props: GenericLinuxImageProps = {}) { - } - - public getImage(scope: Construct): MachineImageConfig { - const region = Stack.of(scope).region; - if (Token.isUnresolved(region)) { - throw new Error('Unable to determine AMI from AMI map since stack is region-agnostic'); - } + private mapping: { [k1: string]: { [k2: string]: any } } = {}; - const ami = region !== 'test-region' ? this.amiMap[region] : 'ami-12345'; - if (!ami) { - throw new Error(`Unable to find AMI in AMI map: no AMI specified for region '${region}'`); + /** + * A Linux image where you specify the AMI ID for every region + * + * @param amiMap For every region where you are deploying the stack, + * specify the AMI ID for that region. + * @param props Customize the image by supplying additional props + */ + constructor(readonly amiMap: { [region: string]: string }, private readonly props: GenericLinuxImageProps = {}) { + for (const [region, ami] of Object.entries(amiMap)) { + this.mapping[region] = { ami }; } + } + public getImage(scope: Construct): MachineImageConfig { + const amiMap = new CfnMapping(scope, 'AmiMap', { mapping: this.mapping }); return { - imageId: ami, + imageId: amiMap.findInMap(Aws.REGION, 'ami'), userData: this.props.userData ?? UserData.forLinux(), osType: OperatingSystemType.LINUX, }; @@ -391,22 +394,25 @@ export class GenericLinuxImage implements IMachineImage { * Allows you to create a generic Windows EC2 , manually specify an AMI map. */ export class GenericWindowsImage implements IMachineImage { - constructor(private readonly amiMap: {[region: string]: string}, private readonly props: GenericWindowsImageProps = {}) { - } - - public getImage(scope: Construct): MachineImageConfig { - const region = Stack.of(scope).region; - if (Token.isUnresolved(region)) { - throw new Error('Unable to determine AMI from AMI map since stack is region-agnostic'); - } + private mapping: { [k1: string]: { [k2: string]: any } } = {}; - const ami = region !== 'test-region' ? this.amiMap[region] : 'ami-12345'; - if (!ami) { - throw new Error(`Unable to find AMI in AMI map: no AMI specified for region '${region}'`); + /** + * A Windows image where you specify the AMI ID for every region + * + * @param amiMap For every region where you are deploying the stack, + * specify the AMI ID for that region. + * @param props Customize the image by supplying additional props + */ + constructor(readonly amiMap: {[region: string]: string}, private readonly props: GenericWindowsImageProps = {}) { + for (const [region, ami] of Object.entries(amiMap)) { + this.mapping[region] = { ami }; } + } + public getImage(scope: Construct): MachineImageConfig { + const amiMap = new CfnMapping(scope, 'AmiMap', { mapping: this.mapping }); return { - imageId: ami, + imageId: amiMap.findInMap(Aws.REGION, 'ami'), userData: this.props.userData ?? UserData.forWindows(), osType: OperatingSystemType.WINDOWS, }; diff --git a/packages/@aws-cdk/aws-ec2/test/machine-image.test.ts b/packages/@aws-cdk/aws-ec2/test/machine-image.test.ts index 2fcf7e2980be9..fd5dd6bd78b48 100644 --- a/packages/@aws-cdk/aws-ec2/test/machine-image.test.ts +++ b/packages/@aws-cdk/aws-ec2/test/machine-image.test.ts @@ -1,3 +1,4 @@ +import { expect as cdkExpect, matchTemplate, MatchStyle } from '@aws-cdk/assert'; import { App, Stack } from '@aws-cdk/core'; import * as ec2 from '../lib'; @@ -11,6 +12,29 @@ beforeEach(() => { }); }); +test('can make and use a Linux image', () => { + // WHEN + const image = new ec2.GenericLinuxImage({ + testregion: 'ami-1234', + }); + + // THEN + const details = image.getImage(stack); + const expected = { + Mappings: { + AmiMap: { + testregion: { + ami: 'ami-1234', + }, + }, + }, + }; + + cdkExpect(stack).to(matchTemplate(expected, MatchStyle.EXACT)); + expect(stack.resolve(details.imageId)).toEqual({ 'Fn::FindInMap': ['AmiMap', { Ref: 'AWS::Region' }, 'ami'] }); + expect(details.osType).toEqual(ec2.OperatingSystemType.LINUX); +}); + test('can make and use a Windows image', () => { // WHEN const image = new ec2.GenericWindowsImage({ @@ -19,7 +43,18 @@ test('can make and use a Windows image', () => { // THEN const details = image.getImage(stack); - expect(details.imageId).toEqual('ami-1234'); + const expected = { + Mappings: { + AmiMap: { + testregion: { + ami: 'ami-1234', + }, + }, + }, + }; + + cdkExpect(stack).to(matchTemplate(expected, MatchStyle.EXACT)); + expect(stack.resolve(details.imageId)).toEqual({ 'Fn::FindInMap': ['AmiMap', { Ref: 'AWS::Region' }, 'ami'] }); expect(details.osType).toEqual(ec2.OperatingSystemType.WINDOWS); }); diff --git a/packages/@aws-cdk/aws-ec2/test/vpc.test.ts b/packages/@aws-cdk/aws-ec2/test/vpc.test.ts index 0b9401f289119..8f1120196d42b 100644 --- a/packages/@aws-cdk/aws-ec2/test/vpc.test.ts +++ b/packages/@aws-cdk/aws-ec2/test/vpc.test.ts @@ -895,7 +895,7 @@ nodeunitShim({ // THEN expect(stack).to(countResources('AWS::EC2::Instance', 3)); expect(stack).to(haveResource('AWS::EC2::Instance', { - ImageId: 'ami-1', + ImageId: { 'Fn::FindInMap': ['TheVPCPublicSubnet1NatInstanceAmiMap7BC63154', { Ref: 'AWS::Region' }, 'ami'] }, InstanceType: 'q86.mega', SourceDestCheck: false, }));