Skip to content

Commit

Permalink
fix(codedeploy): referenced Applications are not environment-aware (#…
Browse files Browse the repository at this point in the history
…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*
  • Loading branch information
rix0rrr authored Dec 27, 2022
1 parent 4673c8b commit 96242d7
Show file tree
Hide file tree
Showing 16 changed files with 356 additions and 128 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { ArnFormat, Resource, Stack } from '@aws-cdk/core';
import { Construct } from 'constructs';
import { CfnDeploymentConfig } from './codedeploy.generated';
import { MinimumHealthyHosts } from './host-health-config';
import { arnForDeploymentConfig, validateName } from './private/utils';
import { TrafficRouting } from './traffic-routing-config';
import { arnForDeploymentConfig, validateName } from './utils';

/**
* The base class for ServerDeploymentConfig, EcsDeploymentConfig,
Expand Down
26 changes: 22 additions & 4 deletions packages/@aws-cdk/aws-codedeploy/lib/ecs/application.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ArnFormat, IResource, Resource } from '@aws-cdk/core';
import { ArnFormat, IResource, Resource, Stack, Arn } from '@aws-cdk/core';
import { Construct } from 'constructs';
import { CfnApplication } from '../codedeploy.generated';
import { arnForApplication, validateName } from '../utils';
import { arnForApplication, validateName } from '../private/utils';

/**
* Represents a reference to a CodeDeploy Application deploying to Amazon ECS.
Expand Down Expand Up @@ -42,20 +42,38 @@ export class EcsApplication extends Resource implements IEcsApplication {
/**
* Import an Application defined either outside the CDK, or in a different CDK Stack.
*
* The Application's account and region are assumed to be the same as the stack it is being imported
* into. If not, use `fromEcsApplicationArn`.
*
* @param scope the parent Construct for this new Construct
* @param id the logical ID of this new Construct
* @param ecsApplicationName the name of the application to import
* @returns a Construct representing a reference to an existing Application
*/
public static fromEcsApplicationName(scope: Construct, id: string, ecsApplicationName: string): IEcsApplication {
class Import extends Resource implements IEcsApplication {
public applicationArn = arnForApplication(ecsApplicationName);
public applicationArn = arnForApplication(Stack.of(scope), ecsApplicationName);
public applicationName = ecsApplicationName;
}

return new Import(scope, id);
}

/**
* Import an Application defined either outside the CDK, or in a different CDK Stack, by ARN.
*
* @param scope the parent Construct for this new Construct
* @param id the logical ID of this new Construct
* @param ecsApplicationArn the ARN of the application to import
* @returns a Construct representing a reference to an existing Application
*/
public static fromEcsApplicationArn(scope: Construct, id: string, ecsApplicationArn: string): IEcsApplication {
return new class extends Resource implements IEcsApplication {
public applicationArn = ecsApplicationArn;
public applicationName = Arn.split(ecsApplicationArn, ArnFormat.COLON_RESOURCE_NAME).resourceName ?? '<invalid arn>';
} (scope, id, { environmentFromArn: ecsApplicationArn });
}

public readonly applicationArn: string;
public readonly applicationName: string;

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

this.applicationName = this.getResourceNameAttribute(resource.ref);
this.applicationArn = this.getResourceArnAttribute(arnForApplication(resource.ref), {
this.applicationArn = this.getResourceArnAttribute(arnForApplication(Stack.of(scope), resource.ref), {
service: 'codedeploy',
resource: 'application',
resourceName: this.physicalName,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Construct } from 'constructs';
import { BaseDeploymentConfig, BaseDeploymentConfigOptions, ComputePlatform, IBaseDeploymentConfig } from '../base-deployment-config';
import { deploymentConfig } from '../private/utils';
import { TrafficRouting } from '../traffic-routing-config';
import { deploymentConfig } from '../utils';

/**
* The Deployment Configuration of an ECS Deployment Group.
Expand Down
44 changes: 18 additions & 26 deletions packages/@aws-cdk/aws-codedeploy/lib/ecs/deployment-group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import * as iam from '@aws-cdk/aws-iam';
import * as cdk from '@aws-cdk/core';
import { Construct } from 'constructs';
import { CfnDeploymentGroup } from '../codedeploy.generated';
import { ImportedDeploymentGroupBase, DeploymentGroupBase } from '../private/base-deployment-group';
import { renderAlarmConfiguration, renderAutoRollbackConfiguration } from '../private/utils';
import { AutoRollbackConfig } from '../rollback-config';
import { arnForDeploymentGroup, renderAlarmConfiguration, renderAutoRollbackConfiguration, validateName } from '../utils';
import { IEcsApplication, EcsApplication } from './application';
import { EcsDeploymentConfig, IEcsDeploymentConfig } from './deployment-config';

Expand Down Expand Up @@ -182,9 +183,11 @@ export interface EcsDeploymentGroupProps {
* A CodeDeploy deployment group that orchestrates ECS blue-green deployments.
* @resource AWS::CodeDeploy::DeploymentGroup
*/
export class EcsDeploymentGroup extends cdk.Resource implements IEcsDeploymentGroup {
export class EcsDeploymentGroup extends DeploymentGroupBase implements IEcsDeploymentGroup {
/**
* Import an ECS Deployment Group defined outside the CDK app.
* Reference an ECS Deployment Group defined outside the CDK app.
*
* Account and region for the DeploymentGroup are taken from the application.
*
* @param scope the parent Construct for this new Construct
* @param id the logical ID of this new Construct
Expand All @@ -199,8 +202,6 @@ export class EcsDeploymentGroup extends cdk.Resource implements IEcsDeploymentGr
}

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

constructor(scope: Construct, id: string, props: EcsDeploymentGroupProps) {
super(scope, id, {
physicalName: props.deploymentGroupName,
deploymentGroupName: props.deploymentGroupName,
role: props.role,
roleConstructId: 'ServiceRole',
});
this.role = this._role;

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

this.role = props.role || new iam.Role(this, 'ServiceRole', {
assumedBy: new iam.ServicePrincipal('codedeploy.amazonaws.com'),
});

this.role.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('AWSCodeDeployRoleForECS'));
this.deploymentConfig = props.deploymentConfig || EcsDeploymentConfig.ALL_AT_ONCE;

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

this.deploymentGroupName = this.getResourceNameAttribute(resource.ref);
this.deploymentGroupArn = this.getResourceArnAttribute(arnForDeploymentGroup(this.application.applicationName, resource.ref), {
service: 'codedeploy',
resource: 'deploymentgroup',
resourceName: `${this.application.applicationName}/${this.physicalName}`,
arnFormat: cdk.ArnFormat.COLON_RESOURCE_NAME,
});
this._setNameAndArn(resource, this.application);

// If the deployment config is a construct, add a dependency to ensure the deployment config
// is created before the deployment group is.
if (Construct.isConstruct(this.deploymentConfig)) {
this.node.addDependency(this.deploymentConfig);
}

this.node.addValidation({ validate: () => validateName('Deployment group', this.physicalName) });
}

/**
Expand Down Expand Up @@ -355,17 +347,17 @@ export interface EcsDeploymentGroupAttributes {
readonly deploymentConfig?: IEcsDeploymentConfig;
}

class ImportedEcsDeploymentGroup extends cdk.Resource implements IEcsDeploymentGroup {
class ImportedEcsDeploymentGroup extends ImportedDeploymentGroupBase implements IEcsDeploymentGroup {
public readonly application: IEcsApplication;
public readonly deploymentGroupName: string;
public readonly deploymentGroupArn: string;
public readonly deploymentConfig: IEcsDeploymentConfig;

constructor(scope:Construct, id: string, props: EcsDeploymentGroupAttributes) {
super(scope, id);
constructor(scope: Construct, id: string, props: EcsDeploymentGroupAttributes) {
super(scope, id, {
application: props.application,
deploymentGroupName: props.deploymentGroupName,
});

this.application = props.application;
this.deploymentGroupName = props.deploymentGroupName;
this.deploymentGroupArn = arnForDeploymentGroup(props.application.applicationName, props.deploymentGroupName);
this.deploymentConfig = props.deploymentConfig || EcsDeploymentConfig.ALL_AT_ONCE;
}
}
26 changes: 22 additions & 4 deletions packages/@aws-cdk/aws-codedeploy/lib/lambda/application.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ArnFormat, IResource, Resource } from '@aws-cdk/core';
import { ArnFormat, IResource, Resource, Stack, Arn } from '@aws-cdk/core';
import { Construct } from 'constructs';
import { CfnApplication } from '../codedeploy.generated';
import { arnForApplication, validateName } from '../utils';
import { arnForApplication, validateName } from '../private/utils';

/**
* Represents a reference to a CodeDeploy Application deploying to AWS Lambda.
Expand Down Expand Up @@ -42,20 +42,38 @@ export class LambdaApplication extends Resource implements ILambdaApplication {
/**
* Import an Application defined either outside the CDK, or in a different CDK Stack.
*
* The Application's account and region are assumed to be the same as the stack it is being imported
* into. If not, use `fromLambdaApplicationArn`.
*
* @param scope the parent Construct for this new Construct
* @param id the logical ID of this new Construct
* @param lambdaApplicationName the name of the application to import
* @returns a Construct representing a reference to an existing Application
*/
public static fromLambdaApplicationName(scope: Construct, id: string, lambdaApplicationName: string): ILambdaApplication {
class Import extends Resource implements ILambdaApplication {
public applicationArn = arnForApplication(lambdaApplicationName);
public applicationArn = arnForApplication(Stack.of(scope), lambdaApplicationName);
public applicationName = lambdaApplicationName;
}

return new Import(scope, id);
}

/**
* Import an Application defined either outside the CDK, or in a different CDK Stack, by ARN.
*
* @param scope the parent Construct for this new Construct
* @param id the logical ID of this new Construct
* @param lambdaApplicationArn the ARN of the application to import
* @returns a Construct representing a reference to an existing Application
*/
public static fromLambdaApplicationArn(scope: Construct, id: string, lambdaApplicationArn: string): ILambdaApplication {
return new class extends Resource implements ILambdaApplication {
public applicationArn = lambdaApplicationArn;
public applicationName = Arn.split(lambdaApplicationArn, ArnFormat.COLON_RESOURCE_NAME).resourceName ?? '<invalid arn>';
}(scope, id, { environmentFromArn: lambdaApplicationArn });
}

public readonly applicationArn: string;
public readonly applicationName: string;

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

this.applicationName = this.getResourceNameAttribute(resource.ref);
this.applicationArn = this.getResourceArnAttribute(arnForApplication(resource.ref), {
this.applicationArn = this.getResourceArnAttribute(arnForApplication(Stack.of(this), resource.ref), {
service: 'codedeploy',
resource: 'application',
resourceName: this.physicalName,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Duration, Names, Resource } from '@aws-cdk/core';
import { AwsCustomResource, AwsCustomResourcePolicy, PhysicalResourceId } from '@aws-cdk/custom-resources';
import { Construct } from 'constructs';
import { arnForDeploymentConfig, validateName } from '../utils';
import { arnForDeploymentConfig, validateName } from '../private/utils';
import { ILambdaDeploymentConfig } from './deployment-config';

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Construct } from 'constructs';
import { BaseDeploymentConfig, BaseDeploymentConfigOptions, ComputePlatform, IBaseDeploymentConfig } from '../base-deployment-config';
import { deploymentConfig } from '../private/utils';
import { TrafficRouting } from '../traffic-routing-config';
import { deploymentConfig } from '../utils';

/**
* The Deployment Configuration of a Lambda Deployment Group.
Expand Down
45 changes: 20 additions & 25 deletions packages/@aws-cdk/aws-codedeploy/lib/lambda/deployment-group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import * as lambda from '@aws-cdk/aws-lambda';
import * as cdk from '@aws-cdk/core';
import { Construct } from 'constructs';
import { CfnDeploymentGroup } from '../codedeploy.generated';
import { ImportedDeploymentGroupBase, DeploymentGroupBase } from '../private/base-deployment-group';
import { renderAlarmConfiguration, renderAutoRollbackConfiguration } from '../private/utils';
import { AutoRollbackConfig } from '../rollback-config';
import { arnForDeploymentGroup, renderAlarmConfiguration, renderAutoRollbackConfiguration, validateName } from '../utils';
import { ILambdaApplication, LambdaApplication } from './application';
import { ILambdaDeploymentConfig, LambdaDeploymentConfig } from './deployment-config';

Expand Down Expand Up @@ -120,10 +121,12 @@ export interface LambdaDeploymentGroupProps {
/**
* @resource AWS::CodeDeploy::DeploymentGroup
*/
export class LambdaDeploymentGroup extends cdk.Resource implements ILambdaDeploymentGroup {
export class LambdaDeploymentGroup extends DeploymentGroupBase implements ILambdaDeploymentGroup {
/**
* Import an Lambda Deployment Group defined either outside the CDK app, or in a different AWS region.
*
* Account and region for the DeploymentGroup are taken from the application.
*
* @param scope the parent Construct for this new Construct
* @param id the logical ID of this new Construct
* @param attrs the properties of the referenced Deployment Group
Expand All @@ -137,9 +140,10 @@ export class LambdaDeploymentGroup extends cdk.Resource implements ILambdaDeploy
}

public readonly application: ILambdaApplication;
public readonly deploymentGroupName: string;
public readonly deploymentGroupArn: string;
public readonly deploymentConfig: ILambdaDeploymentConfig;
/**
* The service Role of this Deployment Group.
*/
public readonly role: iam.IRole;

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

constructor(scope: Construct, id: string, props: LambdaDeploymentGroupProps) {
super(scope, id, {
physicalName: props.deploymentGroupName,
deploymentGroupName: props.deploymentGroupName,
role: props.role,
roleConstructId: 'ServiceRole',
});
this.role = this._role;

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

this.role = props.role || new iam.Role(this, 'ServiceRole', {
assumedBy: new iam.ServicePrincipal('codedeploy.amazonaws.com'),
});

this.role.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSCodeDeployRoleForLambdaLimited'));
this.deploymentConfig = props.deploymentConfig || LambdaDeploymentConfig.CANARY_10PERCENT_5MINUTES;

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

this.deploymentGroupName = this.getResourceNameAttribute(resource.ref);
this.deploymentGroupArn = this.getResourceArnAttribute(arnForDeploymentGroup(this.application.applicationName, resource.ref), {
service: 'codedeploy',
resource: 'deploymentgroup',
resourceName: `${this.application.applicationName}/${this.physicalName}`,
arnFormat: cdk.ArnFormat.COLON_RESOURCE_NAME,
});
this._setNameAndArn(resource, this.application);

if (props.preHook) {
this.addPreHook(props.preHook);
Expand All @@ -203,8 +200,6 @@ export class LambdaDeploymentGroup extends cdk.Resource implements ILambdaDeploy
if (this.deploymentConfig instanceof Construct) {
this.node.addDependency(this.deploymentConfig);
}

this.node.addValidation({ validate: () => validateName('Deployment group', this.physicalName) });
}

/**
Expand Down Expand Up @@ -284,17 +279,17 @@ export interface LambdaDeploymentGroupAttributes {
readonly deploymentConfig?: ILambdaDeploymentConfig;
}

class ImportedLambdaDeploymentGroup extends cdk.Resource implements ILambdaDeploymentGroup {
class ImportedLambdaDeploymentGroup extends ImportedDeploymentGroupBase implements ILambdaDeploymentGroup {
public readonly application: ILambdaApplication;
public readonly deploymentGroupName: string;
public readonly deploymentGroupArn: string;
public readonly deploymentConfig: ILambdaDeploymentConfig;

constructor(scope:Construct, id: string, props: LambdaDeploymentGroupAttributes) {
super(scope, id);
constructor(scope: Construct, id: string, props: LambdaDeploymentGroupAttributes) {
super(scope, id, {
application: props.application,
deploymentGroupName: props.deploymentGroupName,
});

this.application = props.application;
this.deploymentGroupName = props.deploymentGroupName;
this.deploymentGroupArn = arnForDeploymentGroup(props.application.applicationName, props.deploymentGroupName);
this.deploymentConfig = props.deploymentConfig || LambdaDeploymentConfig.CANARY_10PERCENT_5MINUTES;
}
}
Loading

0 comments on commit 96242d7

Please sign in to comment.