diff --git a/packages/@aws-cdk/aws-ec2/README.md b/packages/@aws-cdk/aws-ec2/README.md index 1a442e104e4b0..3b902e5cb8c75 100644 --- a/packages/@aws-cdk/aws-ec2/README.md +++ b/packages/@aws-cdk/aws-ec2/README.md @@ -538,6 +538,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 1871fc1e75ac5..df4a1eece07e0 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 { ContextProvider, Stack, Token } from '@aws-cdk/core'; +import { ContextProvider, CfnMapping, Aws, Stack, Token } from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; import { UserData } from './user-data'; import { WindowsVersion } from './windows-versions'; @@ -367,24 +367,33 @@ export interface GenericWindowsImageProps { * manually specify an AMI map. */ export class GenericLinuxImage implements IMachineImage { - constructor(private readonly amiMap: {[region: string]: string}, private readonly props: GenericLinuxImageProps = {}) { + constructor(private readonly amiMap: { [region: string]: string }, private readonly props: GenericLinuxImageProps = {}) { } public getImage(scope: Construct): MachineImageConfig { + const userData = this.props.userData ?? UserData.forLinux(); + const osType = OperatingSystemType.LINUX; 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'); + const mapping: { [k1: string]: { [k2: string]: any } } = {}; + for (const [rgn, ami] of Object.entries(this.amiMap)) { + mapping[rgn] = { ami }; + } + const amiMap = new CfnMapping(scope, 'AmiMap', { mapping }); + return { + imageId: amiMap.findInMap(Aws.REGION, 'ami'), + userData, + osType, + }; } - - const ami = region !== 'test-region' ? this.amiMap[region] : 'ami-12345'; - if (!ami) { + const imageId = region !== 'test-region' ? this.amiMap[region] : 'ami-12345'; + if (!imageId) { throw new Error(`Unable to find AMI in AMI map: no AMI specified for region '${region}'`); } - return { - imageId: ami, - userData: this.props.userData ?? UserData.forLinux(), - osType: OperatingSystemType.LINUX, + imageId, + userData, + osType, }; } } @@ -399,20 +408,29 @@ export class GenericWindowsImage implements IMachineImage { } public getImage(scope: Construct): MachineImageConfig { + const userData = this.props.userData ?? UserData.forWindows(); + const osType = OperatingSystemType.WINDOWS; 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'); + const mapping: { [k1: string]: { [k2: string]: any } } = {}; + for (const [rgn, ami] of Object.entries(this.amiMap)) { + mapping[rgn] = { ami }; + } + const amiMap = new CfnMapping(scope, 'AmiMap', { mapping }); + return { + imageId: amiMap.findInMap(Aws.REGION, 'ami'), + userData, + osType, + }; } - - const ami = region !== 'test-region' ? this.amiMap[region] : 'ami-12345'; - if (!ami) { + const imageId = region !== 'test-region' ? this.amiMap[region] : 'ami-12345'; + if (!imageId) { throw new Error(`Unable to find AMI in AMI map: no AMI specified for region '${region}'`); } - return { - imageId: ami, - userData: this.props.userData ?? UserData.forWindows(), - osType: OperatingSystemType.WINDOWS, + imageId, + userData, + osType, }; } } 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..43e6dbdcd88cf 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,43 @@ beforeEach(() => { }); }); +test('can make and use a Linux image', () => { + // WHEN + const image = new ec2.GenericLinuxImage({ + testregion: 'ami-1234', + }); + + // THEN + const details = image.getImage(stack); + expect(details.imageId).toEqual('ami-1234'); + expect(details.osType).toEqual(ec2.OperatingSystemType.LINUX); +}); + +test('can make and use a Linux image in agnostic stack', () => { + // WHEN + app = new App(); + stack = new Stack(app, 'Stack'); + 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({ @@ -23,6 +61,31 @@ test('can make and use a Windows image', () => { expect(details.osType).toEqual(ec2.OperatingSystemType.WINDOWS); }); +test('can make and use a Windows image in agnostic stack', () => { + // WHEN + app = new App(); + stack = new Stack(app, 'Stack'); + const image = new ec2.GenericWindowsImage({ + 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.WINDOWS); +}); + test('can make and use a Generic SSM image', () => { // WHEN const image = new ec2.GenericSSMParameterImage('testParam', ec2.OperatingSystemType.LINUX);