Skip to content

Commit

Permalink
feat(ec2): generic ssm backed machine image (#10369)
Browse files Browse the repository at this point in the history
This PR adds a machine image that is backed by a custom SSM parameter.

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
hoegertn authored Sep 21, 2020
1 parent 9f430fc commit 1dbad6e
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 53 deletions.
114 changes: 61 additions & 53 deletions packages/@aws-cdk/aws-ec2/lib/machine-image.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,22 @@ export abstract class MachineImage {
return new GenericWindowsImage(amiMap, props);
}

/**
* An image specified in SSM parameter store that is automatically kept up-to-date
*
* This Machine Image automatically updates to the latest version on every
* deployment. Be aware this will cause your instances to be replaced when a
* new version of the image becomes available. Do not store stateful information
* on the instance if you are using this image.
*
* @param parameterName The name of SSM parameter containing the AMi id
* @param os The operating system type of the AMI
* @param userData optional user data for the given image
*/
public static fromSSMParameter(parameterName: string, os: OperatingSystemType, userData?: UserData): IMachineImage {
return new GenericSSMParameterImage(parameterName, os, userData);
}

/**
* Look up a shared Machine Image using DescribeImages
*
Expand Down Expand Up @@ -102,6 +118,34 @@ export interface MachineImageConfig {
readonly userData: UserData;
}

/**
* Select the image based on a given SSM parameter
*
* This Machine Image automatically updates to the latest version on every
* deployment. Be aware this will cause your instances to be replaced when a
* new version of the image becomes available. Do not store stateful information
* on the instance if you are using this image.
*
* The AMI ID is selected using the values published to the SSM parameter store.
*/
export class GenericSSMParameterImage implements IMachineImage {

constructor(private readonly parameterName: string, private readonly os: OperatingSystemType, private readonly userData?: UserData) {
}

/**
* Return the image to use in the given context
*/
public getImage(scope: Construct): MachineImageConfig {
const ami = ssm.StringParameter.valueForTypedStringParameter(scope, this.parameterName, ssm.ParameterType.AWS_EC2_IMAGE_ID);
return {
imageId: ami,
osType: this.os,
userData: this.userData ?? (this.os === OperatingSystemType.WINDOWS ? UserData.forWindows() : UserData.forLinux()),
};
}
}

/**
* Configuration options for WindowsImage
*/
Expand All @@ -126,28 +170,9 @@ export interface WindowsImageProps {
*
* https://aws.amazon.com/blogs/mt/query-for-the-latest-windows-ami-using-systems-manager-parameter-store/
*/
export class WindowsImage implements IMachineImage {
constructor(private readonly version: WindowsVersion, private readonly props: WindowsImageProps = {}) {
}

/**
* Return the image to use in the given context
*/
public getImage(scope: Construct): MachineImageConfig {
const parameterName = this.imageParameterName();
const ami = ssm.StringParameter.valueForTypedStringParameter(scope, parameterName, ssm.ParameterType.AWS_EC2_IMAGE_ID);
return {
imageId: ami,
userData: this.props.userData ?? UserData.forWindows(),
osType: OperatingSystemType.WINDOWS,
};
}

/**
* Construct the SSM parameter name for the given Windows image
*/
private imageParameterName(): string {
return '/aws/service/ami-windows-latest/' + this.version;
export class WindowsImage extends GenericSSMParameterImage {
constructor(version: WindowsVersion, props: WindowsImageProps = {}) {
super('/aws/service/ami-windows-latest/' + version, OperatingSystemType.WINDOWS, props.userData);
}
}

Expand Down Expand Up @@ -223,42 +248,25 @@ export interface AmazonLinuxImageProps {
*
* The AMI ID is selected using the values published to the SSM parameter store.
*/
export class AmazonLinuxImage implements IMachineImage {
private readonly generation: AmazonLinuxGeneration;
private readonly edition: AmazonLinuxEdition;
private readonly virtualization: AmazonLinuxVirt;
private readonly storage: AmazonLinuxStorage;
private readonly cpu: AmazonLinuxCpuType;

constructor(private readonly props: AmazonLinuxImageProps = {}) {
this.generation = (props && props.generation) || AmazonLinuxGeneration.AMAZON_LINUX;
this.edition = (props && props.edition) || AmazonLinuxEdition.STANDARD;
this.virtualization = (props && props.virtualization) || AmazonLinuxVirt.HVM;
this.storage = (props && props.storage) || AmazonLinuxStorage.GENERAL_PURPOSE;
this.cpu = (props && props.cpuType) || AmazonLinuxCpuType.X86_64;
}

/**
* Return the image to use in the given context
*/
public getImage(scope: Construct): MachineImageConfig {
export class AmazonLinuxImage extends GenericSSMParameterImage {

constructor(props: AmazonLinuxImageProps = {}) {
const generation = (props && props.generation) || AmazonLinuxGeneration.AMAZON_LINUX;
const edition = (props && props.edition) || AmazonLinuxEdition.STANDARD;
const virtualization = (props && props.virtualization) || AmazonLinuxVirt.HVM;
const storage = (props && props.storage) || AmazonLinuxStorage.GENERAL_PURPOSE;
const cpu = (props && props.cpuType) || AmazonLinuxCpuType.X86_64;
const parts: Array<string|undefined> = [
this.generation,
generation,
'ami',
this.edition !== AmazonLinuxEdition.STANDARD ? this.edition : undefined,
this.virtualization,
this.cpu,
this.storage,
edition !== AmazonLinuxEdition.STANDARD ? edition : undefined,
virtualization,
cpu,
storage,
].filter(x => x !== undefined); // Get rid of undefineds

const parameterName = '/aws/service/ami-amazon-linux-latest/' + parts.join('-');
const ami = ssm.StringParameter.valueForTypedStringParameter(scope, parameterName, ssm.ParameterType.AWS_EC2_IMAGE_ID);

return {
imageId: ami,
userData: this.props.userData ?? UserData.forLinux(),
osType: OperatingSystemType.LINUX,
};
super(parameterName, OperatingSystemType.LINUX, props.userData);
}
}

Expand Down
4 changes: 4 additions & 0 deletions packages/@aws-cdk/aws-ec2/test/example.images.lit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ const amznLinux = ec2.MachineImage.latestAmazonLinux({
// Pick a Windows edition to use
const windows = ec2.MachineImage.latestWindows(ec2.WindowsVersion.WINDOWS_SERVER_2019_ENGLISH_FULL_BASE);

// Read AMI id from SSM parameter store
const ssm = ec2.MachineImage.fromSSMParameter('/my/ami', ec2.OperatingSystemType.LINUX);

// Look up the most recent image matching a set of AMI filters.
// In this case, look up the NAT instance AMI, by using a wildcard
// in the 'name' field:
Expand Down Expand Up @@ -42,5 +45,6 @@ const genericWindows = ec2.MachineImage.genericWindows({
Array.isArray(windows);
Array.isArray(amznLinux);
Array.isArray(linux);
Array.isArray(ssm);
Array.isArray(genericWindows);
Array.isArray(natAmi);
10 changes: 10 additions & 0 deletions packages/@aws-cdk/aws-ec2/test/machine-image.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,16 @@ test('can make and use a Windows image', () => {
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);

// THEN
const details = image.getImage(stack);
expect(details.imageId).toContain('TOKEN');
expect(details.osType).toEqual(ec2.OperatingSystemType.LINUX);
});

test('WindowsImage retains userdata if given', () => {
// WHEN
const ud = ec2.UserData.forWindows();
Expand Down

0 comments on commit 1dbad6e

Please sign in to comment.