Skip to content

Commit 96242d7

Browse files
authored
fix(codedeploy): referenced Applications are not environment-aware (#23405)
Applications (and associated DeploymentGroups) that were referenced using `XxxApplication.fromXxxApplicationName` always assumed they lived in the "current" stack. Add `XxxApplication.fromXxxApplicationArn` for all types, which will take account and region from the ARN, allowing cross-environment references. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 4673c8b commit 96242d7

16 files changed

+356
-128
lines changed

packages/@aws-cdk/aws-codedeploy/lib/base-deployment-config.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import { ArnFormat, Resource, Stack } from '@aws-cdk/core';
22
import { Construct } from 'constructs';
33
import { CfnDeploymentConfig } from './codedeploy.generated';
44
import { MinimumHealthyHosts } from './host-health-config';
5+
import { arnForDeploymentConfig, validateName } from './private/utils';
56
import { TrafficRouting } from './traffic-routing-config';
6-
import { arnForDeploymentConfig, validateName } from './utils';
77

88
/**
99
* The base class for ServerDeploymentConfig, EcsDeploymentConfig,

packages/@aws-cdk/aws-codedeploy/lib/ecs/application.ts

+22-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { ArnFormat, IResource, Resource } from '@aws-cdk/core';
1+
import { ArnFormat, IResource, Resource, Stack, Arn } from '@aws-cdk/core';
22
import { Construct } from 'constructs';
33
import { CfnApplication } from '../codedeploy.generated';
4-
import { arnForApplication, validateName } from '../utils';
4+
import { arnForApplication, validateName } from '../private/utils';
55

66
/**
77
* Represents a reference to a CodeDeploy Application deploying to Amazon ECS.
@@ -42,20 +42,38 @@ export class EcsApplication extends Resource implements IEcsApplication {
4242
/**
4343
* Import an Application defined either outside the CDK, or in a different CDK Stack.
4444
*
45+
* The Application's account and region are assumed to be the same as the stack it is being imported
46+
* into. If not, use `fromEcsApplicationArn`.
47+
*
4548
* @param scope the parent Construct for this new Construct
4649
* @param id the logical ID of this new Construct
4750
* @param ecsApplicationName the name of the application to import
4851
* @returns a Construct representing a reference to an existing Application
4952
*/
5053
public static fromEcsApplicationName(scope: Construct, id: string, ecsApplicationName: string): IEcsApplication {
5154
class Import extends Resource implements IEcsApplication {
52-
public applicationArn = arnForApplication(ecsApplicationName);
55+
public applicationArn = arnForApplication(Stack.of(scope), ecsApplicationName);
5356
public applicationName = ecsApplicationName;
5457
}
5558

5659
return new Import(scope, id);
5760
}
5861

62+
/**
63+
* Import an Application defined either outside the CDK, or in a different CDK Stack, by ARN.
64+
*
65+
* @param scope the parent Construct for this new Construct
66+
* @param id the logical ID of this new Construct
67+
* @param ecsApplicationArn the ARN of the application to import
68+
* @returns a Construct representing a reference to an existing Application
69+
*/
70+
public static fromEcsApplicationArn(scope: Construct, id: string, ecsApplicationArn: string): IEcsApplication {
71+
return new class extends Resource implements IEcsApplication {
72+
public applicationArn = ecsApplicationArn;
73+
public applicationName = Arn.split(ecsApplicationArn, ArnFormat.COLON_RESOURCE_NAME).resourceName ?? '<invalid arn>';
74+
} (scope, id, { environmentFromArn: ecsApplicationArn });
75+
}
76+
5977
public readonly applicationArn: string;
6078
public readonly applicationName: string;
6179

@@ -70,7 +88,7 @@ export class EcsApplication extends Resource implements IEcsApplication {
7088
});
7189

7290
this.applicationName = this.getResourceNameAttribute(resource.ref);
73-
this.applicationArn = this.getResourceArnAttribute(arnForApplication(resource.ref), {
91+
this.applicationArn = this.getResourceArnAttribute(arnForApplication(Stack.of(scope), resource.ref), {
7492
service: 'codedeploy',
7593
resource: 'application',
7694
resourceName: this.physicalName,

packages/@aws-cdk/aws-codedeploy/lib/ecs/deployment-config.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Construct } from 'constructs';
22
import { BaseDeploymentConfig, BaseDeploymentConfigOptions, ComputePlatform, IBaseDeploymentConfig } from '../base-deployment-config';
3+
import { deploymentConfig } from '../private/utils';
34
import { TrafficRouting } from '../traffic-routing-config';
4-
import { deploymentConfig } from '../utils';
55

66
/**
77
* The Deployment Configuration of an ECS Deployment Group.

packages/@aws-cdk/aws-codedeploy/lib/ecs/deployment-group.ts

+18-26
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ import * as iam from '@aws-cdk/aws-iam';
55
import * as cdk from '@aws-cdk/core';
66
import { Construct } from 'constructs';
77
import { CfnDeploymentGroup } from '../codedeploy.generated';
8+
import { ImportedDeploymentGroupBase, DeploymentGroupBase } from '../private/base-deployment-group';
9+
import { renderAlarmConfiguration, renderAutoRollbackConfiguration } from '../private/utils';
810
import { AutoRollbackConfig } from '../rollback-config';
9-
import { arnForDeploymentGroup, renderAlarmConfiguration, renderAutoRollbackConfiguration, validateName } from '../utils';
1011
import { IEcsApplication, EcsApplication } from './application';
1112
import { EcsDeploymentConfig, IEcsDeploymentConfig } from './deployment-config';
1213

@@ -182,9 +183,11 @@ export interface EcsDeploymentGroupProps {
182183
* A CodeDeploy deployment group that orchestrates ECS blue-green deployments.
183184
* @resource AWS::CodeDeploy::DeploymentGroup
184185
*/
185-
export class EcsDeploymentGroup extends cdk.Resource implements IEcsDeploymentGroup {
186+
export class EcsDeploymentGroup extends DeploymentGroupBase implements IEcsDeploymentGroup {
186187
/**
187-
* Import an ECS Deployment Group defined outside the CDK app.
188+
* Reference an ECS Deployment Group defined outside the CDK app.
189+
*
190+
* Account and region for the DeploymentGroup are taken from the application.
188191
*
189192
* @param scope the parent Construct for this new Construct
190193
* @param id the logical ID of this new Construct
@@ -199,8 +202,6 @@ export class EcsDeploymentGroup extends cdk.Resource implements IEcsDeploymentGr
199202
}
200203

201204
public readonly application: IEcsApplication;
202-
public readonly deploymentGroupName: string;
203-
public readonly deploymentGroupArn: string;
204205
public readonly deploymentConfig: IEcsDeploymentConfig;
205206
/**
206207
* The service Role of this Deployment Group.
@@ -211,16 +212,15 @@ export class EcsDeploymentGroup extends cdk.Resource implements IEcsDeploymentGr
211212

212213
constructor(scope: Construct, id: string, props: EcsDeploymentGroupProps) {
213214
super(scope, id, {
214-
physicalName: props.deploymentGroupName,
215+
deploymentGroupName: props.deploymentGroupName,
216+
role: props.role,
217+
roleConstructId: 'ServiceRole',
215218
});
219+
this.role = this._role;
216220

217221
this.application = props.application || new EcsApplication(this, 'Application');
218222
this.alarms = props.alarms || [];
219223

220-
this.role = props.role || new iam.Role(this, 'ServiceRole', {
221-
assumedBy: new iam.ServicePrincipal('codedeploy.amazonaws.com'),
222-
});
223-
224224
this.role.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('AWSCodeDeployRoleForECS'));
225225
this.deploymentConfig = props.deploymentConfig || EcsDeploymentConfig.ALL_AT_ONCE;
226226

@@ -261,21 +261,13 @@ export class EcsDeploymentGroup extends cdk.Resource implements IEcsDeploymentGr
261261
autoRollbackConfiguration: cdk.Lazy.any({ produce: () => renderAutoRollbackConfiguration(this.alarms, props.autoRollback) }),
262262
});
263263

264-
this.deploymentGroupName = this.getResourceNameAttribute(resource.ref);
265-
this.deploymentGroupArn = this.getResourceArnAttribute(arnForDeploymentGroup(this.application.applicationName, resource.ref), {
266-
service: 'codedeploy',
267-
resource: 'deploymentgroup',
268-
resourceName: `${this.application.applicationName}/${this.physicalName}`,
269-
arnFormat: cdk.ArnFormat.COLON_RESOURCE_NAME,
270-
});
264+
this._setNameAndArn(resource, this.application);
271265

272266
// If the deployment config is a construct, add a dependency to ensure the deployment config
273267
// is created before the deployment group is.
274268
if (Construct.isConstruct(this.deploymentConfig)) {
275269
this.node.addDependency(this.deploymentConfig);
276270
}
277-
278-
this.node.addValidation({ validate: () => validateName('Deployment group', this.physicalName) });
279271
}
280272

281273
/**
@@ -355,17 +347,17 @@ export interface EcsDeploymentGroupAttributes {
355347
readonly deploymentConfig?: IEcsDeploymentConfig;
356348
}
357349

358-
class ImportedEcsDeploymentGroup extends cdk.Resource implements IEcsDeploymentGroup {
350+
class ImportedEcsDeploymentGroup extends ImportedDeploymentGroupBase implements IEcsDeploymentGroup {
359351
public readonly application: IEcsApplication;
360-
public readonly deploymentGroupName: string;
361-
public readonly deploymentGroupArn: string;
362352
public readonly deploymentConfig: IEcsDeploymentConfig;
363353

364-
constructor(scope:Construct, id: string, props: EcsDeploymentGroupAttributes) {
365-
super(scope, id);
354+
constructor(scope: Construct, id: string, props: EcsDeploymentGroupAttributes) {
355+
super(scope, id, {
356+
application: props.application,
357+
deploymentGroupName: props.deploymentGroupName,
358+
});
359+
366360
this.application = props.application;
367-
this.deploymentGroupName = props.deploymentGroupName;
368-
this.deploymentGroupArn = arnForDeploymentGroup(props.application.applicationName, props.deploymentGroupName);
369361
this.deploymentConfig = props.deploymentConfig || EcsDeploymentConfig.ALL_AT_ONCE;
370362
}
371363
}

packages/@aws-cdk/aws-codedeploy/lib/lambda/application.ts

+22-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { ArnFormat, IResource, Resource } from '@aws-cdk/core';
1+
import { ArnFormat, IResource, Resource, Stack, Arn } from '@aws-cdk/core';
22
import { Construct } from 'constructs';
33
import { CfnApplication } from '../codedeploy.generated';
4-
import { arnForApplication, validateName } from '../utils';
4+
import { arnForApplication, validateName } from '../private/utils';
55

66
/**
77
* Represents a reference to a CodeDeploy Application deploying to AWS Lambda.
@@ -42,20 +42,38 @@ export class LambdaApplication extends Resource implements ILambdaApplication {
4242
/**
4343
* Import an Application defined either outside the CDK, or in a different CDK Stack.
4444
*
45+
* The Application's account and region are assumed to be the same as the stack it is being imported
46+
* into. If not, use `fromLambdaApplicationArn`.
47+
*
4548
* @param scope the parent Construct for this new Construct
4649
* @param id the logical ID of this new Construct
4750
* @param lambdaApplicationName the name of the application to import
4851
* @returns a Construct representing a reference to an existing Application
4952
*/
5053
public static fromLambdaApplicationName(scope: Construct, id: string, lambdaApplicationName: string): ILambdaApplication {
5154
class Import extends Resource implements ILambdaApplication {
52-
public applicationArn = arnForApplication(lambdaApplicationName);
55+
public applicationArn = arnForApplication(Stack.of(scope), lambdaApplicationName);
5356
public applicationName = lambdaApplicationName;
5457
}
5558

5659
return new Import(scope, id);
5760
}
5861

62+
/**
63+
* Import an Application defined either outside the CDK, or in a different CDK Stack, by ARN.
64+
*
65+
* @param scope the parent Construct for this new Construct
66+
* @param id the logical ID of this new Construct
67+
* @param lambdaApplicationArn the ARN of the application to import
68+
* @returns a Construct representing a reference to an existing Application
69+
*/
70+
public static fromLambdaApplicationArn(scope: Construct, id: string, lambdaApplicationArn: string): ILambdaApplication {
71+
return new class extends Resource implements ILambdaApplication {
72+
public applicationArn = lambdaApplicationArn;
73+
public applicationName = Arn.split(lambdaApplicationArn, ArnFormat.COLON_RESOURCE_NAME).resourceName ?? '<invalid arn>';
74+
}(scope, id, { environmentFromArn: lambdaApplicationArn });
75+
}
76+
5977
public readonly applicationArn: string;
6078
public readonly applicationName: string;
6179

@@ -70,7 +88,7 @@ export class LambdaApplication extends Resource implements ILambdaApplication {
7088
});
7189

7290
this.applicationName = this.getResourceNameAttribute(resource.ref);
73-
this.applicationArn = this.getResourceArnAttribute(arnForApplication(resource.ref), {
91+
this.applicationArn = this.getResourceArnAttribute(arnForApplication(Stack.of(this), resource.ref), {
7492
service: 'codedeploy',
7593
resource: 'application',
7694
resourceName: this.physicalName,

packages/@aws-cdk/aws-codedeploy/lib/lambda/custom-deployment-config.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Duration, Names, Resource } from '@aws-cdk/core';
22
import { AwsCustomResource, AwsCustomResourcePolicy, PhysicalResourceId } from '@aws-cdk/custom-resources';
33
import { Construct } from 'constructs';
4-
import { arnForDeploymentConfig, validateName } from '../utils';
4+
import { arnForDeploymentConfig, validateName } from '../private/utils';
55
import { ILambdaDeploymentConfig } from './deployment-config';
66

77
/**

packages/@aws-cdk/aws-codedeploy/lib/lambda/deployment-config.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Construct } from 'constructs';
22
import { BaseDeploymentConfig, BaseDeploymentConfigOptions, ComputePlatform, IBaseDeploymentConfig } from '../base-deployment-config';
3+
import { deploymentConfig } from '../private/utils';
34
import { TrafficRouting } from '../traffic-routing-config';
4-
import { deploymentConfig } from '../utils';
55

66
/**
77
* The Deployment Configuration of a Lambda Deployment Group.

packages/@aws-cdk/aws-codedeploy/lib/lambda/deployment-group.ts

+20-25
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ import * as lambda from '@aws-cdk/aws-lambda';
44
import * as cdk from '@aws-cdk/core';
55
import { Construct } from 'constructs';
66
import { CfnDeploymentGroup } from '../codedeploy.generated';
7+
import { ImportedDeploymentGroupBase, DeploymentGroupBase } from '../private/base-deployment-group';
8+
import { renderAlarmConfiguration, renderAutoRollbackConfiguration } from '../private/utils';
79
import { AutoRollbackConfig } from '../rollback-config';
8-
import { arnForDeploymentGroup, renderAlarmConfiguration, renderAutoRollbackConfiguration, validateName } from '../utils';
910
import { ILambdaApplication, LambdaApplication } from './application';
1011
import { ILambdaDeploymentConfig, LambdaDeploymentConfig } from './deployment-config';
1112

@@ -120,10 +121,12 @@ export interface LambdaDeploymentGroupProps {
120121
/**
121122
* @resource AWS::CodeDeploy::DeploymentGroup
122123
*/
123-
export class LambdaDeploymentGroup extends cdk.Resource implements ILambdaDeploymentGroup {
124+
export class LambdaDeploymentGroup extends DeploymentGroupBase implements ILambdaDeploymentGroup {
124125
/**
125126
* Import an Lambda Deployment Group defined either outside the CDK app, or in a different AWS region.
126127
*
128+
* Account and region for the DeploymentGroup are taken from the application.
129+
*
127130
* @param scope the parent Construct for this new Construct
128131
* @param id the logical ID of this new Construct
129132
* @param attrs the properties of the referenced Deployment Group
@@ -137,9 +140,10 @@ export class LambdaDeploymentGroup extends cdk.Resource implements ILambdaDeploy
137140
}
138141

139142
public readonly application: ILambdaApplication;
140-
public readonly deploymentGroupName: string;
141-
public readonly deploymentGroupArn: string;
142143
public readonly deploymentConfig: ILambdaDeploymentConfig;
144+
/**
145+
* The service Role of this Deployment Group.
146+
*/
143147
public readonly role: iam.IRole;
144148

145149
private readonly alarms: cloudwatch.IAlarm[];
@@ -148,16 +152,15 @@ export class LambdaDeploymentGroup extends cdk.Resource implements ILambdaDeploy
148152

149153
constructor(scope: Construct, id: string, props: LambdaDeploymentGroupProps) {
150154
super(scope, id, {
151-
physicalName: props.deploymentGroupName,
155+
deploymentGroupName: props.deploymentGroupName,
156+
role: props.role,
157+
roleConstructId: 'ServiceRole',
152158
});
159+
this.role = this._role;
153160

154161
this.application = props.application || new LambdaApplication(this, 'Application');
155162
this.alarms = props.alarms || [];
156163

157-
this.role = props.role || new iam.Role(this, 'ServiceRole', {
158-
assumedBy: new iam.ServicePrincipal('codedeploy.amazonaws.com'),
159-
});
160-
161164
this.role.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSCodeDeployRoleForLambdaLimited'));
162165
this.deploymentConfig = props.deploymentConfig || LambdaDeploymentConfig.CANARY_10PERCENT_5MINUTES;
163166

@@ -174,13 +177,7 @@ export class LambdaDeploymentGroup extends cdk.Resource implements ILambdaDeploy
174177
autoRollbackConfiguration: cdk.Lazy.any({ produce: () => renderAutoRollbackConfiguration(this.alarms, props.autoRollback) }),
175178
});
176179

177-
this.deploymentGroupName = this.getResourceNameAttribute(resource.ref);
178-
this.deploymentGroupArn = this.getResourceArnAttribute(arnForDeploymentGroup(this.application.applicationName, resource.ref), {
179-
service: 'codedeploy',
180-
resource: 'deploymentgroup',
181-
resourceName: `${this.application.applicationName}/${this.physicalName}`,
182-
arnFormat: cdk.ArnFormat.COLON_RESOURCE_NAME,
183-
});
180+
this._setNameAndArn(resource, this.application);
184181

185182
if (props.preHook) {
186183
this.addPreHook(props.preHook);
@@ -203,8 +200,6 @@ export class LambdaDeploymentGroup extends cdk.Resource implements ILambdaDeploy
203200
if (this.deploymentConfig instanceof Construct) {
204201
this.node.addDependency(this.deploymentConfig);
205202
}
206-
207-
this.node.addValidation({ validate: () => validateName('Deployment group', this.physicalName) });
208203
}
209204

210205
/**
@@ -284,17 +279,17 @@ export interface LambdaDeploymentGroupAttributes {
284279
readonly deploymentConfig?: ILambdaDeploymentConfig;
285280
}
286281

287-
class ImportedLambdaDeploymentGroup extends cdk.Resource implements ILambdaDeploymentGroup {
282+
class ImportedLambdaDeploymentGroup extends ImportedDeploymentGroupBase implements ILambdaDeploymentGroup {
288283
public readonly application: ILambdaApplication;
289-
public readonly deploymentGroupName: string;
290-
public readonly deploymentGroupArn: string;
291284
public readonly deploymentConfig: ILambdaDeploymentConfig;
292285

293-
constructor(scope:Construct, id: string, props: LambdaDeploymentGroupAttributes) {
294-
super(scope, id);
286+
constructor(scope: Construct, id: string, props: LambdaDeploymentGroupAttributes) {
287+
super(scope, id, {
288+
application: props.application,
289+
deploymentGroupName: props.deploymentGroupName,
290+
});
291+
295292
this.application = props.application;
296-
this.deploymentGroupName = props.deploymentGroupName;
297-
this.deploymentGroupArn = arnForDeploymentGroup(props.application.applicationName, props.deploymentGroupName);
298293
this.deploymentConfig = props.deploymentConfig || LambdaDeploymentConfig.CANARY_10PERCENT_5MINUTES;
299294
}
300295
}

0 commit comments

Comments
 (0)