Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(codedeploy): CodeDeploy deployment config constructs for Lambda and ECS #22159

Merged
merged 14 commits into from
Sep 29, 2022
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
clareliguori marked this conversation as resolved.
Show resolved Hide resolved

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) {
clareliguori marked this conversation as resolved.
Show resolved Hide resolved
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