Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ec2): generic ssm backed machine image #10369

Merged
merged 4 commits into from
Sep 21, 2020
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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