From a076d24f74d0abc4400691a1bfd2af750cc27c60 Mon Sep 17 00:00:00 2001 From: maafk Date: Fri, 3 Sep 2021 12:27:48 -0400 Subject: [PATCH] feat(aws-ec2): Allow ApplyCloudformationInitOptions to set additional params (#16121) Resolves #16004 When using CloudformationInit, can opt to include the `--role` and `--url` argument to `cfn-init` and `cfn-signal` ([ref](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-init.html#cfn-init-Syntax)). User can do this by including [InitOptions](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-ec2.ApplyCloudFormationInitOptions.html) that include `includeUrl` and `includeRole` ```typescript ec2.Instance(this, 'MyInstance', { init: // init config, initOptions: { includeUrl: true, includeRole: true, } } ``` It's possible to _always_ include these added arguments, but not sure whether that would cause regressions. Open to any suggestions on a better way to implement ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../aws-autoscaling/lib/auto-scaling-group.ts | 21 ++++++++++++++ packages/@aws-cdk/aws-ec2/README.md | 6 ++++ packages/@aws-cdk/aws-ec2/lib/cfn-init.ts | 29 ++++++++++++++++++- packages/@aws-cdk/aws-ec2/lib/instance.ts | 21 ++++++++++++++ .../@aws-cdk/aws-ec2/test/cfn-init.test.ts | 28 ++++++++++++++++++ 5 files changed, 104 insertions(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts b/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts index a57f226208c9c..027034249c4dd 100644 --- a/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts +++ b/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts @@ -1145,6 +1145,8 @@ export class AutoScalingGroup extends AutoScalingGroupBase implements embedFingerprint: options.embedFingerprint, printLog: options.printLog, ignoreFailures: options.ignoreFailures, + includeRole: options.includeRole, + includeUrl: options.includeUrl, }); } @@ -1780,4 +1782,23 @@ export interface ApplyCloudFormationInitOptions { * @default false */ readonly ignoreFailures?: boolean; + + /** + * Include --url argument when running cfn-init and cfn-signal commands + * + * This will be the cloudformation endpoint in the deployed region + * e.g. https://cloudformation.us-east-1.amazonaws.com + * + * @default false + */ + readonly includeUrl?: boolean; + + /** + * Include --role argument when running cfn-init and cfn-signal commands + * + * This will be the IAM instance profile attached to the EC2 instance + * + * @default false + */ + readonly includeRole?: boolean; } diff --git a/packages/@aws-cdk/aws-ec2/README.md b/packages/@aws-cdk/aws-ec2/README.md index 0ed5fc82d0252..802b5fe9b3e0c 100644 --- a/packages/@aws-cdk/aws-ec2/README.md +++ b/packages/@aws-cdk/aws-ec2/README.md @@ -824,6 +824,12 @@ new ec2.Instance(this, 'Instance', { // Optional, how long the installation is expected to take (5 minutes by default) timeout: Duration.minutes(30), + + // Optional, whether to include the --url argument when running cfn-init and cfn-signal commands (false by default) + includeUrl: true + + // Optional, whether to include the --role argument when running cfn-init and cfn-signal commands (false by default) + includeRole: true }, }); ``` diff --git a/packages/@aws-cdk/aws-ec2/lib/cfn-init.ts b/packages/@aws-cdk/aws-ec2/lib/cfn-init.ts index 910e7ab85cf7f..78b3345743347 100644 --- a/packages/@aws-cdk/aws-ec2/lib/cfn-init.ts +++ b/packages/@aws-cdk/aws-ec2/lib/cfn-init.ts @@ -124,7 +124,15 @@ export class CloudFormationInit { // To identify the resources that have the metadata and where the signal // needs to be sent, we need { region, stackName, logicalId } - const resourceLocator = `--region ${Aws.REGION} --stack ${Aws.STACK_NAME} --resource ${attachedResource.logicalId}`; + let resourceLocator = `--region ${Aws.REGION} --stack ${Aws.STACK_NAME} --resource ${attachedResource.logicalId}`; + + // If specified in attachOptions, include arguments in cfn-init/cfn-signal commands + if (attachOptions.includeUrl) { + resourceLocator = `${resourceLocator} --url https://cloudformation.${Aws.REGION}.${Aws.URL_SUFFIX}`; + } + if (attachOptions.includeRole) { + resourceLocator = `${resourceLocator} --role ${attachOptions.instanceRole}`; + } const configSets = (attachOptions.configSets ?? ['default']).join(','); const printLog = attachOptions.printLog ?? true; @@ -346,6 +354,25 @@ export interface AttachInitOptions { */ readonly instanceRole: iam.IRole; + /** + * Include --url argument when running cfn-init and cfn-signal commands + * + * This will be the cloudformation endpoint in the deployed region + * e.g. https://cloudformation.us-east-1.amazonaws.com + * + * @default false + */ + readonly includeUrl?: boolean; + + /** + * Include --role argument when running cfn-init and cfn-signal commands + * + * This will be the IAM instance profile attached to the EC2 instance + * + * @default false + */ + readonly includeRole?: boolean; + /** * OS Platform the init config will be used for */ diff --git a/packages/@aws-cdk/aws-ec2/lib/instance.ts b/packages/@aws-cdk/aws-ec2/lib/instance.ts index aad8cda0f5deb..813d4d5f43880 100644 --- a/packages/@aws-cdk/aws-ec2/lib/instance.ts +++ b/packages/@aws-cdk/aws-ec2/lib/instance.ts @@ -452,6 +452,8 @@ export class Instance extends Resource implements IInstance { embedFingerprint: options.embedFingerprint, printLog: options.printLog, ignoreFailures: options.ignoreFailures, + includeRole: options.includeRole, + includeUrl: options.includeUrl, }); this.waitForResourceSignal(options.timeout ?? Duration.minutes(5)); } @@ -557,4 +559,23 @@ export interface ApplyCloudFormationInitOptions { * @default false */ readonly ignoreFailures?: boolean; + + /** + * Include --url argument when running cfn-init and cfn-signal commands + * + * This will be the cloudformation endpoint in the deployed region + * e.g. https://cloudformation.us-east-1.amazonaws.com + * + * @default false + */ + readonly includeUrl?: boolean; + + /** + * Include --role argument when running cfn-init and cfn-signal commands + * + * This will be the IAM instance profile attached to the EC2 instance + * + * @default false + */ + readonly includeRole?: boolean; } diff --git a/packages/@aws-cdk/aws-ec2/test/cfn-init.test.ts b/packages/@aws-cdk/aws-ec2/test/cfn-init.test.ts index 61ed0e53fa887..e2539b71ac223 100644 --- a/packages/@aws-cdk/aws-ec2/test/cfn-init.test.ts +++ b/packages/@aws-cdk/aws-ec2/test/cfn-init.test.ts @@ -153,6 +153,34 @@ describe('userdata', () => { expectLine(lines, /fingerprint/); }); + test('linux userdata contains right commands when url and role included', () => { + // WHEN + simpleInit.attach(resource, { + platform: ec2.OperatingSystemType.LINUX, + instanceRole, + includeUrl: true, + includeRole: true, + userData: linuxUserData, + }); + + // THEN + const lines = linuxUserData.render().split('\n'); + expectLine(lines, cmdArg('cfn-init', `--region ${Aws.REGION}`)); + expectLine(lines, cmdArg('cfn-init', `--stack ${Aws.STACK_NAME}`)); + expectLine(lines, cmdArg('cfn-init', `--resource ${resource.logicalId}`)); + expectLine(lines, cmdArg('cfn-init', `--role ${instanceRole}`)); + expectLine(lines, cmdArg('cfn-init', `--url https://cloudformation.${Aws.REGION}.${Aws.URL_SUFFIX}`)); + expectLine(lines, cmdArg('cfn-init', '-c default')); + expectLine(lines, cmdArg('cfn-signal', `--region ${Aws.REGION}`)); + expectLine(lines, cmdArg('cfn-signal', `--stack ${Aws.STACK_NAME}`)); + expectLine(lines, cmdArg('cfn-signal', `--resource ${resource.logicalId}`)); + expectLine(lines, cmdArg('cfn-init', `--role ${instanceRole}`)); + expectLine(lines, cmdArg('cfn-init', `--url https://cloudformation.${Aws.REGION}.${Aws.URL_SUFFIX}`)); + expectLine(lines, cmdArg('cfn-signal', '-e $?')); + expectLine(lines, cmdArg('cat', 'cfn-init.log')); + expectLine(lines, /fingerprint/); + }); + test('Windows userdata contains right commands', () => { // WHEN const windowsUserData = ec2.UserData.forWindows();