Skip to content

Commit

Permalink
Merge branch 'master' into 8913
Browse files Browse the repository at this point in the history
  • Loading branch information
mergify[bot] authored Sep 21, 2020
2 parents c864a93 + 7222b5d commit 6822b46
Show file tree
Hide file tree
Showing 27 changed files with 514 additions and 166 deletions.
4 changes: 3 additions & 1 deletion packages/@aws-cdk/aws-ec2/lib/cfn-init-elements.ts
Original file line number Diff line number Diff line change
Expand Up @@ -489,7 +489,9 @@ export abstract class InitFile extends InitElement {
if (fileOptions.group || fileOptions.owner || fileOptions.mode) {
throw new Error('Owner, group, and mode options not supported for Windows.');
}
return {};
return {
[this.fileName]: { ...contentVars },
};
}

return {
Expand Down
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
16 changes: 16 additions & 0 deletions packages/@aws-cdk/aws-ec2/test/cfn-init-element.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,22 @@ describe('InitFile', () => {
}).toThrow('Owner, group, and mode options not supported for Windows.');
});

test('file renders properly on Windows', () => {
// GIVEN
const file = ec2.InitFile.fromString('/tmp/foo', 'My content');

// WHEN
const rendered = getElementConfig(file, InitPlatform.WINDOWS);

// THEN
expect(rendered).toEqual({
'/tmp/foo': {
content: 'My content',
encoding: 'plain',
},
});
});

test('symlink throws an error if mode is set incorrectly', () => {
expect(() => {
ec2.InitFile.symlink('/tmp/foo', '/tmp/bar', {
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
Original file line number Diff line number Diff line change
Expand Up @@ -50,28 +50,6 @@
"FServiceRole3AC82EE1"
]
},
"FAllowBucketNotificationsFromlambdaeventsources3B101C8CFBA8EBB2AA": {
"Type": "AWS::Lambda::Permission",
"Properties": {
"Action": "lambda:InvokeFunction",
"FunctionName": {
"Fn::GetAtt": [
"FC4345940",
"Arn"
]
},
"Principal": "s3.amazonaws.com",
"SourceAccount": {
"Ref": "AWS::AccountId"
},
"SourceArn": {
"Fn::GetAtt": [
"B08E7C7AF",
"Arn"
]
}
}
},
"B08E7C7AF": {
"Type": "AWS::S3::Bucket",
"UpdateReplacePolicy": "Delete",
Expand Down Expand Up @@ -116,9 +94,31 @@
}
},
"DependsOn": [
"FAllowBucketNotificationsFromlambdaeventsources3B101C8CFBA8EBB2AA"
"BAllowBucketNotificationsTolambdaeventsources3F741608059EF9F709"
]
},
"BAllowBucketNotificationsTolambdaeventsources3F741608059EF9F709": {
"Type": "AWS::Lambda::Permission",
"Properties": {
"Action": "lambda:InvokeFunction",
"FunctionName": {
"Fn::GetAtt": [
"FC4345940",
"Arn"
]
},
"Principal": "s3.amazonaws.com",
"SourceAccount": {
"Ref": "AWS::AccountId"
},
"SourceArn": {
"Fn::GetAtt": [
"B08E7C7AF",
"Arn"
]
}
}
},
"BucketNotificationsHandler050a0587b7544547bf325f094a3db834RoleB6FB88EC": {
"Type": "AWS::IAM::Role",
"Properties": {
Expand Down
15 changes: 12 additions & 3 deletions packages/@aws-cdk/aws-s3-notifications/lib/lambda.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,28 @@ export class LambdaDestination implements s3.IBucketNotificationDestination {
}

public bind(_scope: Construct, bucket: s3.IBucket): s3.BucketNotificationDestinationConfig {
const permissionId = `AllowBucketNotificationsFrom${bucket.node.uniqueId}`;
const permissionId = `AllowBucketNotificationsTo${this.fn.permissionsNode.uniqueId}`;

if (this.fn.permissionsNode.tryFindChild(permissionId) === undefined) {
if (!Construct.isConstruct(bucket)) {
throw new Error(`LambdaDestination for function ${this.fn.permissionsNode.uniqueId} can only be configured on a
bucket construct (Bucket ${bucket.bucketName})`);
}

if (bucket.node.tryFindChild(permissionId) === undefined) {
this.fn.addPermission(permissionId, {
sourceAccount: Stack.of(bucket).account,
principal: new iam.ServicePrincipal('s3.amazonaws.com'),
sourceArn: bucket.bucketArn,
// Placing the permissions node in the same scope as the s3 bucket.
// Otherwise, there is a circular dependency when the s3 bucket
// and lambda functions declared in different stacks.
scope: bucket,
});
}

// if we have a permission resource for this relationship, add it as a dependency
// to the bucket notifications resource, so it will be created first.
const permission = this.fn.permissionsNode.tryFindChild(permissionId) as CfnResource | undefined;
const permission = bucket.node.tryFindChild(permissionId) as CfnResource | undefined;

return {
type: s3.BucketNotificationDestinationType.LAMBDA,
Expand Down
Loading

0 comments on commit 6822b46

Please sign in to comment.