Skip to content

Commit afdc550

Browse files
authored
fix(ec2): UserData.addSignalOnExitCommand does not work in combination with userDataCausesReplacement (#18726)
If both `addSignalOnExitCommand` _and_ `userDataCausesReplacement` are used it results in an invalid logicalId being used in the `cfn-signal` call. This is due to `addSignalOnExitCommand` getting the logicalID from `Stack.getLogicalId` which does not take into consideration logicalId overrides which `userDataCausesReplacement` uses. This updates `addSignalOnExitCommand` to use the `logicalId` of the resource which is evaluated lazily and happens after all overrides. fixes #12749 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 13e1c7f commit afdc550

File tree

2 files changed

+86
-6
lines changed

2 files changed

+86
-6
lines changed

Diff for: packages/@aws-cdk/aws-ec2/lib/user-data.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { IBucket } from '@aws-cdk/aws-s3';
2-
import { CfnElement, Fn, Resource, Stack } from '@aws-cdk/core';
2+
import { Fn, Resource, Stack, CfnResource } from '@aws-cdk/core';
33
import { OperatingSystemType } from './machine-image';
44

55
/**
@@ -178,7 +178,7 @@ class LinuxUserData extends UserData {
178178

179179
public addSignalOnExitCommand( resource: Resource ): void {
180180
const stack = Stack.of(resource);
181-
const resourceID = stack.getLogicalId(resource.node.defaultChild as CfnElement);
181+
const resourceID = (resource.node.defaultChild as CfnResource).logicalId;
182182
this.addOnExitCommands(`/opt/aws/bin/cfn-signal --stack ${stack.stackName} --resource ${resourceID} --region ${stack.region} -e $exitCode || echo 'Failed to send Cloudformation Signal'`);
183183
}
184184

@@ -235,7 +235,7 @@ class WindowsUserData extends UserData {
235235

236236
public addSignalOnExitCommand( resource: Resource ): void {
237237
const stack = Stack.of(resource);
238-
const resourceID = stack.getLogicalId(resource.node.defaultChild as CfnElement);
238+
const resourceID = (resource.node.defaultChild as CfnResource).logicalId;
239239

240240
this.addOnExitCommands(`cfn-signal --stack ${stack.stackName} --resource ${resourceID} --region ${stack.region} --success ($success.ToString().ToLower())`);
241241
}

Diff for: packages/@aws-cdk/aws-ec2/test/userdata.test.ts

+83-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Bucket } from '@aws-cdk/aws-s3';
2-
import { Aws, Stack } from '@aws-cdk/core';
2+
import { Template, Match } from '@aws-cdk/assertions';
3+
import { Aws, Stack, CfnResource } from '@aws-cdk/core';
34
import * as ec2 from '../lib';
45

56
describe('user data', () => {
@@ -41,6 +42,7 @@ describe('user data', () => {
4142
const stack = new Stack();
4243
const resource = new ec2.Vpc(stack, 'RESOURCE');
4344
const userData = ec2.UserData.forWindows();
45+
const logicalId = (resource.node.defaultChild as CfnResource).logicalId;
4446

4547
// WHEN
4648
userData.addSignalOnExitCommand( resource );
@@ -49,16 +51,55 @@ describe('user data', () => {
4951
// THEN
5052
const rendered = userData.render();
5153

54+
expect(stack.resolve(logicalId)).toEqual('RESOURCE1989552F');
5255
expect(rendered).toEqual('<powershell>trap {\n' +
5356
'$success=($PSItem.Exception.Message -eq "Success")\n' +
54-
`cfn-signal --stack Default --resource RESOURCE1989552F --region ${Aws.REGION} --success ($success.ToString().ToLower())\n` +
57+
`cfn-signal --stack Default --resource ${logicalId} --region ${Aws.REGION} --success ($success.ToString().ToLower())\n` +
5558
'break\n' +
5659
'}\n' +
5760
'command1\n' +
5861
'throw "Success"</powershell>',
5962
);
6063

6164
});
65+
test('can create Windows with Signal Command and userDataCausesReplacement', () => {
66+
// GIVEN
67+
const stack = new Stack();
68+
const vpc = new ec2.Vpc(stack, 'Vpc');
69+
const userData = ec2.UserData.forWindows();
70+
const resource = new ec2.Instance(stack, 'RESOURCE', {
71+
vpc,
72+
instanceType: ec2.InstanceType.of(ec2.InstanceClass.T2, ec2.InstanceSize.LARGE),
73+
machineImage: ec2.MachineImage.genericWindows({ ['us-east-1']: 'ami-12345678' }),
74+
userDataCausesReplacement: true,
75+
userData,
76+
});
77+
78+
const logicalId = (resource.node.defaultChild as CfnResource).logicalId;
79+
80+
// WHEN
81+
userData.addSignalOnExitCommand( resource );
82+
userData.addCommands('command1');
83+
84+
// THEN
85+
Template.fromStack(stack).templateMatches({
86+
Resources: Match.objectLike({
87+
RESOURCE1989552Fdfd505305f427919: {
88+
Type: 'AWS::EC2::Instance',
89+
},
90+
}),
91+
});
92+
expect(stack.resolve(logicalId)).toEqual('RESOURCE1989552Fdfd505305f427919');
93+
const rendered = userData.render();
94+
expect(rendered).toEqual('<powershell>trap {\n' +
95+
'$success=($PSItem.Exception.Message -eq "Success")\n' +
96+
`cfn-signal --stack Default --resource ${logicalId} --region ${Aws.REGION} --success ($success.ToString().ToLower())\n` +
97+
'break\n' +
98+
'}\n' +
99+
'command1\n' +
100+
'throw "Success"</powershell>',
101+
);
102+
});
62103
test('can windows userdata download S3 files', () => {
63104
// GIVEN
64105
const stack = new Stack();
@@ -174,6 +215,7 @@ describe('user data', () => {
174215
// GIVEN
175216
const stack = new Stack();
176217
const resource = new ec2.Vpc(stack, 'RESOURCE');
218+
const logicalId = (resource.node.defaultChild as CfnResource).logicalId;
177219

178220
// WHEN
179221
const userData = ec2.UserData.forLinux();
@@ -182,15 +224,53 @@ describe('user data', () => {
182224

183225
// THEN
184226
const rendered = userData.render();
227+
expect(stack.resolve(logicalId)).toEqual('RESOURCE1989552F');
185228
expect(rendered).toEqual('#!/bin/bash\n' +
186229
'function exitTrap(){\n' +
187230
'exitCode=$?\n' +
188-
`/opt/aws/bin/cfn-signal --stack Default --resource RESOURCE1989552F --region ${Aws.REGION} -e $exitCode || echo \'Failed to send Cloudformation Signal\'\n` +
231+
`/opt/aws/bin/cfn-signal --stack Default --resource ${logicalId} --region ${Aws.REGION} -e $exitCode || echo \'Failed to send Cloudformation Signal\'\n` +
189232
'}\n' +
190233
'trap exitTrap EXIT\n' +
191234
'command1');
192235

193236
});
237+
test('can create Linux with Signal Command and userDataCausesReplacement', () => {
238+
// GIVEN
239+
const stack = new Stack();
240+
const vpc = new ec2.Vpc(stack, 'Vpc');
241+
const userData = ec2.UserData.forLinux();
242+
const resource = new ec2.Instance(stack, 'RESOURCE', {
243+
vpc,
244+
instanceType: ec2.InstanceType.of(ec2.InstanceClass.T2, ec2.InstanceSize.LARGE),
245+
machineImage: ec2.MachineImage.genericLinux({ ['us-east-1']: 'ami-12345678' }),
246+
userDataCausesReplacement: true,
247+
userData,
248+
});
249+
250+
const logicalId = (resource.node.defaultChild as CfnResource).logicalId;
251+
252+
// WHEN
253+
userData.addSignalOnExitCommand( resource );
254+
userData.addCommands('command1');
255+
256+
// THEN
257+
Template.fromStack(stack).templateMatches({
258+
Resources: Match.objectLike({
259+
RESOURCE1989552F74a24ef4fbc89422: {
260+
Type: 'AWS::EC2::Instance',
261+
},
262+
}),
263+
});
264+
expect(stack.resolve(logicalId)).toEqual('RESOURCE1989552F74a24ef4fbc89422');
265+
const rendered = userData.render();
266+
expect(rendered).toEqual('#!/bin/bash\n' +
267+
'function exitTrap(){\n' +
268+
'exitCode=$?\n' +
269+
`/opt/aws/bin/cfn-signal --stack Default --resource ${logicalId} --region ${Aws.REGION} -e $exitCode || echo \'Failed to send Cloudformation Signal\'\n` +
270+
'}\n' +
271+
'trap exitTrap EXIT\n' +
272+
'command1');
273+
});
194274
test('can linux userdata download S3 files', () => {
195275
// GIVEN
196276
const stack = new Stack();

0 commit comments

Comments
 (0)