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) lambda application and deployment groups #1628

Merged
merged 18 commits into from
Feb 4, 2019
Merged
Show file tree
Hide file tree
Changes from 9 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
105 changes: 86 additions & 19 deletions packages/@aws-cdk/aws-codedeploy/README.md
Original file line number Diff line number Diff line change
@@ -1,26 +1,10 @@
## The CDK Construct Library for AWS CodeDeploy

### Applications
AWS CodeDeploy is a deployment service that automates application deployments to Amazon EC2 instances, on-premises instances, serverless Lambda functions, or Amazon ECS services.

To create a new CodeDeploy Application that deploys to EC2/on-premise instances:
The CDK currently supports EC2/on-premise and Lambda applications.
sam-goodwin marked this conversation as resolved.
Show resolved Hide resolved

```ts
import codedeploy = require('@aws-cdk/aws-codedeploy');

sam-goodwin marked this conversation as resolved.
Show resolved Hide resolved
const application = new codedeploy.ServerApplication(this, 'CodeDeployApplication', {
applicationName: 'MyApplication', // optional property
});
```

To import an already existing Application:

```ts
const application = codedeploy.ServerApplication.import(this, 'ExistingCodeDeployApplication', {
applicationName: 'MyExistingApplication',
});
```

### Deployment Groups
### EC2/on-premise Deployment Groups
sam-goodwin marked this conversation as resolved.
Show resolved Hide resolved

To create a new CodeDeploy Deployment Group that deploys to EC2/on-premise instances:

Expand Down Expand Up @@ -184,3 +168,86 @@ You can also add the Deployment Group to the Pipeline directly:
// equivalent to the code above:
deploymentGroup.addToPipeline(deployStage, 'CodeDeploy');
```

### Lambda Deployment Groups

To enable traffic shifting deployments for Lambda functions, CodeDeploy uses Lambda Aliases, which can balance incoming traffic between two different versions of your function. Before deployment, the alias sends 100% of invokes to the version used in production. When you publish a new version of the function to your stack, CodeDeploy will send a small percentage of traffic to the new version, monitor, and validate before shifting 100% of traffic to the new version.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you break these lines on full sentence boundaries? Makes the file read better in text mode, and is later easier to edit (the diffs are cleaner when you break on sentence boundaries).


To create a new CodeDeploy Deployment Group that deploys to a Lambda function:

```ts
import codedeploy = require('@aws-cdk/aws-codedeploy');
import lambda = require('@aws-cdk/aws-lambda');

const func = new lambda.Function(..);
const version = func.addVersion('1');
const alias = new lambda.Alias({
aliasName: 'prod',
version
});

const deploymentGroup = new codedeploy.LambdaDeploymentGroup(stack, 'BlueGreenDeployment', {
alias: alias,
sam-goodwin marked this conversation as resolved.
Show resolved Hide resolved
deploymentConfig: codedeploy.LambdaDeploymentConfig.Linear10PercentEvery1Minute,
});
```

Creating the deployment group will modify the `lambda.Alias`'s UpdatePolicy to trigger a deployment on update. Incrementing the version, e.g. `const version = func.addVersion('2')`, and re-deploying the stack will have CodeDeploy safely shift traffic between the versions.
sam-goodwin marked this conversation as resolved.
Show resolved Hide resolved

#### 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:

```ts
const deploymentGroup = new codedeploy.LambdaDeploymentGroup(stack, 'BlueGreenDeployment', {
alias: alias,
sam-goodwin marked this conversation as resolved.
Show resolved Hide resolved
deploymentConfig: codedeploy.LambdaDeploymentConfig.Linear10PercentEvery1Minute,
alarms: [
// pass some alarms when constructing the deployment group
new cloudwatch.Alarm(stack, 'Errors', {
comparisonOperator: cloudwatch.ComparisonOperator.GreaterThanThreshold,
threshold: 1,
evaluationPeriods: 1,
metric: alias.metricErrors()
})
]
});

// or add alarms to an existing group
deploymentGroup.addAlarm(new cloudwatch.Alarm(stack, 'BlueGreenErrors', {
comparisonOperator: cloudwatch.ComparisonOperator.GreaterThanThreshold,
threshold: 1,
evaluationPeriods: 1,
metric: blueGreenAlias.metricErrors()
}));
```

#### Pre and Post Hooks

CodeDeploy allows you to run an arbitrary Lambda function before traffic shifting actually starts (PreTraffic Hook) and after it completes (PostTraffic Hook). With either hook, you have the opportunity to run logic that determines whether the deployment must succeed or fail. For example, with PreTraffic hook you could run integration tests against the newly created Lambda version (but not serving traffic). With PostTraffic hook, you could run end-to-end validation checks.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same note here (break lines on sentence boundaries).


```ts
const preHook = new lambda.Function(..);
sam-goodwin marked this conversation as resolved.
Show resolved Hide resolved
const postHook = new lambda.Function(..);

// pass a hook whe creating the deployment group
const deploymentGroup = new codedeploy.LambdaDeploymentGroup(stack, 'BlueGreenDeployment', {
alias: alias,
sam-goodwin marked this conversation as resolved.
Show resolved Hide resolved
deploymentConfig: codedeploy.LambdaDeploymentConfig.Linear10PercentEvery1Minute,
preHook,
});

// or configure one on an existing deployment group
deploymentGroup.onPostHook(postHook);
```

#### Import an existing Deployment Group

To import an already existing Deployment Group:

```ts
const deploymentGroup = codedeploy.LambdaDeploymentGroup.import(this, 'ExistingCodeDeployDeploymentGroup', {
application,
deploymentGroupName: 'MyExistingDeploymentGroup',
});
```
42 changes: 42 additions & 0 deletions packages/@aws-cdk/aws-codedeploy/lib/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
export enum DeploymentType {
sam-goodwin marked this conversation as resolved.
Show resolved Hide resolved
InPlace = 'IN_PLACE',
BlueGreen = 'BLUE_GREEN'
}

export enum DeploymentOption {
WithTrafficControl = 'WITH_TRAFFIC_CONTROL',
WithoutTrafficControl = 'WITHOUT_TRAFFIC_CONTROL',
}

export enum AutoRollbackEvent {
DeploymentFailure = 'DEPLOYMENT_FAILURE',
DeploymentStopOnAlarm = 'DEPLOYMENT_STOP_ON_ALARM',
DeploymentStopOnRequest = 'DEPLOYMENT_STOP_ON_REQUEST'
}

/**
* The configuration for automatically rolling back deployments in a given Deployment Group.
*/
export interface AutoRollbackConfig {
/**
* Whether to automatically roll back a deployment that fails.
*
* @default true
*/
failedDeployment?: boolean;

/**
* Whether to automatically roll back a deployment that was manually stopped.
*
* @default false
*/
stoppedDeployment?: boolean;

/**
* Whether to automatically roll back a deployment during which one of the configured
* CloudWatch alarms for this Deployment Group went off.
*
* @default true if you've provided any Alarms with the `alarms` property, false otherwise
*/
deploymentInAlarm?: boolean;
}
6 changes: 3 additions & 3 deletions packages/@aws-cdk/aws-codedeploy/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export * from './application';
export * from './deployment-config';
export * from './deployment-group';
export * from './config';
export * from './lambda';
export * from './pipeline-action';
export * from './server';

// AWS::CodeDeploy CloudFormation Resources:
export * from './codedeploy.generated';
97 changes: 97 additions & 0 deletions packages/@aws-cdk/aws-codedeploy/lib/lambda/application.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import cdk = require("@aws-cdk/cdk");
import { CfnApplication } from "../codedeploy.generated";
import { applicationNameToArn } from "../utils";

/**
* Represents a reference to a CodeDeploy Application deploying to AWS Lambda.
*
* If you're managing the Application alongside the rest of your CDK resources,
* use the {@link LambdaApplication} class.
*
* If you want to reference an already existing Application,
* or one defined in a different CDK Stack,
* use the {@link #import} method.
sam-goodwin marked this conversation as resolved.
Show resolved Hide resolved
*/
export interface ILambdaApplication extends cdk.IConstruct {
readonly applicationArn: string;
readonly applicationName: string;

export(): LambdaApplicationImportProps;
}

/**
* Construction properties for {@link LambdaApplication}.
*/
export interface LambdaApplicationProps {
/**
* The physical, human-readable name of the CodeDeploy Application.
*
* @default an auto-generated name will be used
*/
applicationName?: string;
}

/**
* A CodeDeploy Application that deploys to an AWS Lambda function.
*/
export class LambdaApplication extends cdk.Construct implements ILambdaApplication {
/**
* Import an Application defined either outside the CDK,
* or in a different CDK Stack and exported using the {@link #export} method.
sam-goodwin marked this conversation as resolved.
Show resolved Hide resolved
*
* @param parent the parent Construct for this new Construct
* @param id the logical ID of this new Construct
* @param props the properties of the referenced Application
* @returns a Construct representing a reference to an existing Application
*/
public static import(scope: cdk.Construct, id: string, props: LambdaApplicationImportProps): ILambdaApplication {
return new ImportedLambdaApplication(scope, id, props);
}
sam-goodwin marked this conversation as resolved.
Show resolved Hide resolved

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

constructor(scope: cdk.Construct, id: string, props: LambdaApplicationProps = {}) {
super(scope, id);
sam-goodwin marked this conversation as resolved.
Show resolved Hide resolved
const resource = new CfnApplication(this, 'Resource', {
applicationName: props.applicationName,
computePlatform: 'Lambda'
});

this.applicationName = resource.ref;
this.applicationArn = applicationNameToArn(this.applicationName, this);
}

public export(): LambdaApplicationImportProps {
return {
applicationName: new cdk.Output(this, 'ApplicationName', { value: this.applicationName }).makeImportValue().toString()
};
}
}

/**
* Properties of a reference to a CodeDeploy Application.
*
* @see LambdaApplication#import
* @see ILambdaApplication#export
*/
export interface LambdaApplicationImportProps {
/**
* The physical, human-readable name of the Lambda Application we're referencing.
* The Application must be in the same account and region as the root Stack.
*/
applicationName: string;
}

class ImportedLambdaApplication extends cdk.Construct implements ILambdaApplication {
public readonly applicationArn: string;
public readonly applicationName: string;
constructor(scope: cdk.Construct, id: string, private readonly props: LambdaApplicationImportProps) {
sam-goodwin marked this conversation as resolved.
Show resolved Hide resolved
sam-goodwin marked this conversation as resolved.
Show resolved Hide resolved
super(scope, id);
this.applicationName = props.applicationName;
this.applicationArn = applicationNameToArn(props.applicationName, this);
}
public export(): LambdaApplicationImportProps {
sam-goodwin marked this conversation as resolved.
Show resolved Hide resolved
return this.props;
}
}
102 changes: 102 additions & 0 deletions packages/@aws-cdk/aws-codedeploy/lib/lambda/deployment-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import cdk = require('@aws-cdk/cdk');
import { arnForDeploymentConfigName } from '../utils';

/**
* The Deployment Configuration of a Lambda Deployment Group.
* The default, pre-defined Configurations are available as constants on the {@link LambdaDeploymentConfig} class
* (`LambdaDeploymentConfig.AllAtOnce`, `LambdaDeploymentConfig.Canary10Percent30Minutes`, etc.).
*
* Note: CloudFormation does not currently support creating custom lambda configs outside
* of using a custom resource. You can import custom deployment config created outside the
* CDK or via a custom resource with {@link LambdaDeploymentConfig#import}.
*/
export interface ILambdaDeploymentConfig {
readonly deploymentConfigName: string;
deploymentConfigArn(scope: cdk.IConstruct): string;
}

class DefaultLambdaDeploymentConfig implements ILambdaDeploymentConfig {
public readonly deploymentConfigName: string;

constructor(deploymentConfigName: string) {
this.deploymentConfigName = `CodeDeployDefault.Lambda${deploymentConfigName}`;
}

public deploymentConfigArn(scope: cdk.IConstruct): string {
return arnForDeploymentConfigName(this.deploymentConfigName, scope);
}
}

/**
* Properties of a reference to a CodeDeploy Lambda Deployment Configuration.
*
* @see LambdaDeploymentConfig#import
* @see LambdaDeploymentConfig#export
*/
export interface LambdaDeploymentConfigImportProps {
/**
* The physical, human-readable name of the custom CodeDeploy Lambda Deployment Configuration
* that we are referencing.
*/
deploymentConfigName: string;
}

class ImportedLambdaDeploymentConfig extends cdk.Construct implements ILambdaDeploymentConfig {
public readonly deploymentConfigName: string;

constructor(scope: cdk.Construct, id: string, private readonly props: LambdaDeploymentConfigImportProps) {
super(scope, id);

this.deploymentConfigName = props.deploymentConfigName;
}

public deploymentConfigArn(scope: cdk.IConstruct): string {
return arnForDeploymentConfigName(this.deploymentConfigName, scope);
}

public export() {
return this.props;
}
}

export interface LambdaDeploymentConfigProps {
sam-goodwin marked this conversation as resolved.
Show resolved Hide resolved
/**
* The physical, human-readable name of the Deployment Configuration.
*
* @default a name will be auto-generated
*/
deploymentConfigName?: string;
}

/**
* A custom Deployment Configuration for a Lambda Deployment Group.
*
* Note: This class currently stands as namespaced container of the default configurations
* until CloudFormation supports custom Lambda Deployment Configs. Until then it is closed
* (private constructor) and does not extend {@link cdk.Construct}
*/
export class LambdaDeploymentConfig {
public static readonly AllAtOnce: ILambdaDeploymentConfig = new DefaultLambdaDeploymentConfig('AllAtOnce');
public static readonly Canary10Percent30Minutes: ILambdaDeploymentConfig = new DefaultLambdaDeploymentConfig('Canary10Percent30Minutes');
public static readonly Canary10Percent5Minutes: ILambdaDeploymentConfig = new DefaultLambdaDeploymentConfig('Canary10Percent5Minutes');
public static readonly Canary10Percent10Minutes: ILambdaDeploymentConfig = new DefaultLambdaDeploymentConfig('Canary10Percent10Minutes');
public static readonly Canary10Percent15Minutes: ILambdaDeploymentConfig = new DefaultLambdaDeploymentConfig('Canary10Percent15Minutes');
public static readonly Linear10PercentEvery10Minutes: ILambdaDeploymentConfig = new DefaultLambdaDeploymentConfig('Linear10PercentEvery10Minutes');
public static readonly Linear10PercentEvery1Minute: ILambdaDeploymentConfig = new DefaultLambdaDeploymentConfig('Linear10PercentEvery1Minute');
public static readonly Linear10PercentEvery2Minutes: ILambdaDeploymentConfig = new DefaultLambdaDeploymentConfig('Linear10PercentEvery2Minutes');
public static readonly Linear10PercentEvery3Minutes: ILambdaDeploymentConfig = new DefaultLambdaDeploymentConfig('Linear10PercentEvery3Minutes');

/**
* Import a custom Deployment Configuration for a Lambda Deployment Group defined either outside the CDK.
sam-goodwin marked this conversation as resolved.
Show resolved Hide resolved
*
* @param parent the parent Construct for this new Construct
* @param id the logical ID of this new Construct
* @param props the properties of the referenced custom Deployment Configuration
* @returns a Construct representing a reference to an existing custom Deployment Configuration
*/
public static import(scope: cdk.Construct, id: string, props: LambdaDeploymentConfigImportProps): ILambdaDeploymentConfig {
return new ImportedLambdaDeploymentConfig(scope, id, props);
}

private constructor() {}
sam-goodwin marked this conversation as resolved.
Show resolved Hide resolved
}
Loading