Skip to content

Commit

Permalink
feat(codedeploy): CodeDeploy deployment config constructs for Lambda …
Browse files Browse the repository at this point in the history
…and ECS (#22159)

CloudFormation now supports Lambda and ECS in the `AWS::CodeDeploy::DeploymentConfig` resource type.  This PR adds L2 constructs specific to ECS and Lambda for that resource type.

----

### All Submissions:

* [x] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md)

### New Features

* [x] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/main/INTEGRATION_TESTS.md)?
	* [x] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)?

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
clareliguori authored Sep 29, 2022
1 parent 798f9e8 commit 6840d8e
Show file tree
Hide file tree
Showing 34 changed files with 1,753 additions and 216 deletions.
163 changes: 126 additions & 37 deletions packages/@aws-cdk/aws-codedeploy/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ AWS CodeDeploy is a deployment service that automates application deployments to
Amazon EC2 instances, on-premises instances, serverless Lambda functions, or
Amazon ECS services.

The CDK currently supports Amazon EC2, on-premise and AWS Lambda applications.
The CDK currently supports Amazon EC2, on-premise, AWS Lambda, and Amazon ECS applications.

## EC2/on-premise Applications

Expand Down Expand Up @@ -143,7 +143,7 @@ const deploymentGroup = new codedeploy.ServerDeploymentGroup(this, 'DeploymentGr
});
```

## Deployment Configurations
## EC2/on-premise Deployment Configurations

You can also pass a Deployment Configuration when creating the Deployment Group:

Expand Down Expand Up @@ -226,41 +226,6 @@ In order to deploy a new version of this function:
2. Re-deploy the stack (this will trigger a deployment).
3. Monitor the CodeDeploy deployment as traffic shifts between the versions.


### Create a custom Deployment Config

CodeDeploy for Lambda comes with built-in configurations for traffic shifting.
If you want to specify your own strategy,
you can do so with the CustomLambdaDeploymentConfig construct,
letting you specify precisely how fast a new function version is deployed.

```ts
const config = new codedeploy.CustomLambdaDeploymentConfig(this, 'CustomConfig', {
type: codedeploy.CustomLambdaDeploymentConfigType.CANARY,
interval: Duration.minutes(1),
percentage: 5,
});

declare const application: codedeploy.LambdaApplication;
declare const alias: lambda.Alias;
const deploymentGroup = new codedeploy.LambdaDeploymentGroup(this, 'BlueGreenDeployment', {
application,
alias,
deploymentConfig: config,
});
```

You can specify a custom name for your deployment config, but if you do you will not be able to update the interval/percentage through CDK.

```ts
const config = new codedeploy.CustomLambdaDeploymentConfig(this, 'CustomConfig', {
type: codedeploy.CustomLambdaDeploymentConfigType.CANARY,
interval: Duration.minutes(1),
percentage: 5,
deploymentConfigName: 'MyDeploymentConfig',
});
```

### Rollbacks and Alarms

CodeDeploy will roll back if the deployment fails. You can optionally trigger a rollback when one or more alarms are in a failed state:
Expand Down Expand Up @@ -327,3 +292,127 @@ const deploymentGroup = codedeploy.LambdaDeploymentGroup.fromLambdaDeploymentGro
deploymentGroupName: 'MyExistingDeploymentGroup',
});
```

## Lambda Deployment Configurations

CodeDeploy for Lambda comes with predefined configurations for traffic shifting.
The predefined configurations are available as LambdaDeploymentConfig constants.

```ts
const config = codedeploy.LambdaDeploymentConfig.CANARY_10PERCENT_30MINUTES;

declare const application: codedeploy.LambdaApplication;
declare const alias: lambda.Alias;
const deploymentGroup = new codedeploy.LambdaDeploymentGroup(this, 'BlueGreenDeployment', {
application,
alias,
deploymentConfig: config,
});
```

If you want to specify your own strategy,
you can do so with the LambdaDeploymentConfig construct,
letting you specify precisely how fast a new function version is deployed.

```ts
const config = new codedeploy.LambdaDeploymentConfig(this, 'CustomConfig', {
trafficRoutingConfig: new codedeploy.TimeBasedCanaryTrafficRoutingConfig({
interval: cdk.Duration.minutes(15),
percentage: 5,
}),
});

declare const application: codedeploy.LambdaApplication;
declare const alias: lambda.Alias;
const deploymentGroup = new codedeploy.LambdaDeploymentGroup(this, 'BlueGreenDeployment', {
application,
alias,
deploymentConfig: config,
});
```

You can specify a custom name for your deployment config, but if you do you will not be able to update the interval/percentage through CDK.

```ts
const config = new codedeploy.LambdaDeploymentConfig(this, 'CustomConfig', {
trafficRoutingConfig: new codedeploy.TimeBasedCanaryTrafficRoutingConfig({
interval: cdk.Duration.minutes(15),
percentage: 5,
}),
deploymentConfigName: 'MyDeploymentConfig',
});
```

To import an already existing Deployment Config:

```ts
const deploymentConfig = codedeploy.LambdaDeploymentConfig.fromLambdaDeploymentConfigName(
this,
'ExistingDeploymentConfiguration',
'MyExistingDeploymentConfiguration',
);
```

## ECS Applications

To create a new CodeDeploy Application that deploys an ECS service:

```ts
const application = new codedeploy.EcsApplication(this, 'CodeDeployApplication', {
applicationName: 'MyApplication', // optional property
});
```

To import an already existing Application:

```ts
const application = codedeploy.EcsApplication.fromEcsApplicationName(
this,
'ExistingCodeDeployApplication',
'MyExistingApplication',
);
```

## ECS Deployment Configurations

CodeDeploy for ECS comes with predefined configurations for traffic shifting.
The predefined configurations are available as LambdaDeploymentConfig constants.

```ts
const config = codedeploy.EcsDeploymentConfig.CANARY_10PERCENT_5MINUTES;
```

If you want to specify your own strategy,
you can do so with the EcsDeploymentConfig construct,
letting you specify precisely how fast an ECS service is deployed.

```ts
new codedeploy.EcsDeploymentConfig(this, 'CustomConfig', {
trafficRoutingConfig: new codedeploy.TimeBasedCanaryTrafficRoutingConfig({
interval: cdk.Duration.minutes(15),
percentage: 5,
}),
});
```

You can specify a custom name for your deployment config, but if you do you will not be able to update the interval/percentage through CDK.

```ts
const config = new codedeploy.EcsDeploymentConfig(this, 'CustomConfig', {
trafficRoutingConfig: new codedeploy.TimeBasedCanaryTrafficRoutingConfig({
interval: cdk.Duration.minutes(15),
percentage: 5,
}),
deploymentConfigName: 'MyDeploymentConfig',
});
```

Or import an existing one:

```ts
const deploymentConfig = codedeploy.EcsDeploymentConfig.fromEcsDeploymentConfigName(
this,
'ExistingDeploymentConfiguration',
'MyExistingDeploymentConfiguration',
);
```
171 changes: 171 additions & 0 deletions packages/@aws-cdk/aws-codedeploy/lib/base-deployment-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
import { ArnFormat, Resource, Stack } from '@aws-cdk/core';
import { Construct } from 'constructs';
import { CfnDeploymentConfig } from './codedeploy.generated';
import { MinimumHealthyHosts } from './host-health-config';
import { TrafficRouting } from './traffic-routing-config';
import { arnForDeploymentConfig, validateName } from './utils';

/**
* The base class for ServerDeploymentConfig, EcsDeploymentConfig,
* and LambdaDeploymentConfig deployment configurations.
*/
export interface IBaseDeploymentConfig {
/**
* The physical, human-readable name of the Deployment Configuration.
* @attribute
*/
readonly deploymentConfigName: string;

/**
* The ARN of the Deployment Configuration.
* @attribute
*/
readonly deploymentConfigArn: string;
}

/**
* Construction properties of {@link BaseDeploymentConfig}.
*/
export interface BaseDeploymentConfigOptions {
/**
* The physical, human-readable name of the Deployment Configuration.
* @default - automatically generated name
*/
readonly deploymentConfigName?: string;
}

/**
* The compute platform of a deployment configuration
*/
export enum ComputePlatform {
/**
* The deployment will target EC2 instances or on-premise servers
*/
SERVER = 'Server',

/**
* The deployment will target a Lambda function
*/
LAMBDA = 'Lambda',

/**
* The deployment will target an ECS server
*/
ECS = 'ECS'
}

/**
* Complete base deployment config properties that are required to be supplied by the implementation
* of the BaseDeploymentConfig class.
*/
export interface BaseDeploymentConfigProps extends BaseDeploymentConfigOptions {
/**
* The destination compute platform for the deployment.
*
* @default ComputePlatform.Server
*/
readonly computePlatform?: ComputePlatform;

/**
* The configuration that specifies how traffic is shifted during a deployment.
* Only applicable to ECS and Lambda deployments, and must not be specified for Server deployments.
* @default None
*/
readonly trafficRouting?: TrafficRouting;

/**
* Minimum number of healthy hosts.
* @default None
*/
readonly minimumHealthyHosts?: MinimumHealthyHosts;
}

/**
* The base class for ServerDeploymentConfig, EcsDeploymentConfig,
* and LambdaDeploymentConfig deployment configurations.
*
* @resource AWS::CodeDeploy::DeploymentConfig
*/
export abstract class BaseDeploymentConfig extends Resource implements IBaseDeploymentConfig {
/**
* Import a custom Deployment Configuration for a Deployment Group defined outside the CDK.
*
* @param scope the parent Construct for this new Construct
* @param id the logical ID of this new Construct
* @param deploymentConfigName the name of the referenced custom Deployment Configuration
* @returns a Construct representing a reference to an existing custom Deployment Configuration
*/
protected static fromDeploymentConfigName(scope: Construct, id: string, deploymentConfigName: string): IBaseDeploymentConfig {
ignore(id);
const arn = Stack.of(scope).formatArn({
service: 'codedeploy',
resource: 'deploymentconfig',
resourceName: deploymentConfigName,
arnFormat: ArnFormat.COLON_RESOURCE_NAME,
});
return {
deploymentConfigName: deploymentConfigName,
deploymentConfigArn: arn,
};
}

/**
* This method should be used only for static references to predefined deployment configurations,
* like EcsDeploymentConfig.ALL_AT_ONCE
* @param name the name of the referenced custom Deployment Configuration
* @returns a reference to an existing custom Deployment Configuration
*/
protected static deploymentConfig(name: string): IBaseDeploymentConfig {
return {
deploymentConfigName: name,
deploymentConfigArn: arnForDeploymentConfig(name),
};
}

/**
* The name of the deployment config
* @attribute
*/
public readonly deploymentConfigName: string;

/**
* The arn of the deployment config
* @attribute
*/
public readonly deploymentConfigArn: string;

public constructor(scope: Construct, id: string, props?: BaseDeploymentConfigProps) {
super(scope, id, {
physicalName: props?.deploymentConfigName,
});

// Traffic routing is not applicable to Server-based deployment configs
if (props?.trafficRouting && (props?.computePlatform === undefined || props?.computePlatform === ComputePlatform.SERVER)) {
throw new Error('Traffic routing config must not be specified for a Server-base deployment configuration');
}

// Minimum healthy hosts is only applicable to Server-based deployment configs
if (props?.minimumHealthyHosts && props?.computePlatform && props?.computePlatform !== ComputePlatform.SERVER) {
throw new Error('Minimum healthy hosts config must only be specified for a Server-base deployment configuration');
}

const resource = new CfnDeploymentConfig(this, 'Resource', {
deploymentConfigName: this.physicalName,
computePlatform: props?.computePlatform,
trafficRoutingConfig: props?.trafficRouting?.bind(this),
minimumHealthyHosts: props?.minimumHealthyHosts?._json,
});

this.deploymentConfigName = this.getResourceNameAttribute(resource.ref);
this.deploymentConfigArn = this.getResourceArnAttribute(arnForDeploymentConfig(resource.ref), {
service: 'codedeploy',
resource: 'deploymentconfig',
resourceName: this.physicalName,
arnFormat: ArnFormat.COLON_RESOURCE_NAME,
});

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

function ignore(_x: any) { return; }
Loading

0 comments on commit 6840d8e

Please sign in to comment.