Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion .projen/deps.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .projenrc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { awscdk } from 'projen';
const project = new awscdk.AwsCdkConstructLibrary({
author: 'Parker Scanlon',
authorAddress: 'https://aws.amazon.com/',
cdkVersion: '2.24.0', // needed for node16
cdkVersion: '2.187.0', // needed for node16
defaultReleaseBranch: 'main',
name: 'cdk-docker-image-deployment',
projenrcTs: true,
Expand Down
56 changes: 56 additions & 0 deletions API.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 30 additions & 0 deletions src/docker-image-deployment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import * as codebuild from 'aws-cdk-lib/aws-codebuild';
import * as iam from 'aws-cdk-lib/aws-iam';
import { Runtime } from 'aws-cdk-lib/aws-lambda';
import * as lambda from 'aws-cdk-lib/aws-lambda-nodejs';
import * as logs from 'aws-cdk-lib/aws-logs';
import { LogOptions } from 'aws-cdk-lib/aws-stepfunctions';
import * as cr from 'aws-cdk-lib/custom-resources';
import { Construct } from 'constructs';
import { Destination } from './destination';
Expand All @@ -19,6 +21,30 @@ export interface DockerImageDeploymentProps {
* Destination repository to deploy the image to.
*/
readonly destination: Destination;
/**
* The Log Group used for logging of events emitted by the custom resource's lambda function.
*
* @default - a default log group created by AWS Lambda
*/
readonly crProviderLogGroup?: logs.ILogGroup;
/**
* The Log Group used for logging of events emitted by the custom resource's onEventHandler lambda function.
*
* @default - a default log group created by AWS Lambda
*/
readonly onEventHandlerLogGroup?: logs.ILogGroup;
/**
* The Log Group used for logging of events emitted by the custom resource's isCompleteHandler lambda function.
*
* @default - a default log group created by AWS Lambda
*/
readonly isCompleteHandlerLogGroup?: logs.ILogGroup;
/**
* The Log Option used for logging of events emitted by the custom resource's waiter state machine.
*
* @default - A default log group will be created if logging for the waiter state machine is enabled.
*/
readonly crWaiterStateMachineLogOptions?: LogOptions;
}

/**
Expand Down Expand Up @@ -78,12 +104,14 @@ export class DockerImageDeployment extends Construct {
entry: path.join(__dirname, 'codebuild-handler/index.js'),
handler: 'onEventhandler',
runtime: Runtime.NODEJS_16_X,
logGroup: props.onEventHandlerLogGroup,
});

const isCompleteHandler = new lambda.NodejsFunction(this, 'isCompleteHandler', {
entry: path.join(__dirname, 'codebuild-handler/index.js'),
handler: 'isCompleteHandler',
runtime: Runtime.NODEJS_16_X,
logGroup: props.isCompleteHandlerLogGroup,
});

// https://github.com/aws/aws-cdk/issues/21721 issue to add grant methods to codebuild
Expand All @@ -109,6 +137,8 @@ export class DockerImageDeployment extends Construct {
isCompleteHandler: isCompleteHandler,
queryInterval: Duration.seconds(30),
totalTimeout: Duration.minutes(30),
logGroup: props.crProviderLogGroup,
waiterStateMachineLogOptions: props.crWaiterStateMachineLogOptions,
});

const customResource = new CustomResource(this, `CustomResource${Date.now().toString()}`, {
Expand Down
168 changes: 111 additions & 57 deletions test/docker-image-deploy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
import * as path from 'path';
import { Match, Template } from 'aws-cdk-lib/assertions';
import * as ecr from 'aws-cdk-lib/aws-ecr';
import * as logs from 'aws-cdk-lib/aws-logs';
import * as cdk from 'aws-cdk-lib/core';
import * as imagedeploy from '../lib/index';

describe('DockerImageDeploy', () => {
// GIVEN
const stack = new cdk.Stack();
describe('Source: directory', () => {
// GIVEN
const stack = new cdk.Stack();
const testSource = imagedeploy.Source.directory(path.join(__dirname, 'assets/test1'));

describe('Destination: ecr', () => {
Expand All @@ -35,6 +35,15 @@ describe('DockerImageDeploy', () => {
destination: testDesinationNoOptions,
});

new imagedeploy.DockerImageDeployment(stack, 'TestDeploymentWithLogGroup', {
source: testSource,
destination: testDesination,
crProviderLogGroup: new logs.LogGroup(stack, 'CrProviderLogGroup', { retention: logs.RetentionDays.ONE_DAY }),
onEventHandlerLogGroup: new logs.LogGroup(stack, 'OnEventLogGroup', { retention: logs.RetentionDays.FIVE_DAYS }),
isCompleteHandlerLogGroup: new logs.LogGroup(stack, 'IsCompleteLogGroup', { retention: logs.RetentionDays.ONE_WEEK }),
crWaiterStateMachineLogOptions: { destination: new logs.LogGroup(stack, 'WaiterSfnLogGroup', { retention: logs.RetentionDays.ONE_MONTH }) },
} );

test('iam policy is granted correct permissions', () => {
Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', {
PolicyDocument: {
Expand Down Expand Up @@ -80,21 +89,10 @@ describe('DockerImageDeploy', () => {
'ecr:BatchCheckLayerAvailability',
'ecr:GetDownloadUrlForLayer',
'ecr:BatchGetImage',
],
Effect: 'Allow',
Resource: {
'Fn::GetAtt': [
'TestRepositoryC0DA8195',
'Arn',
],
},
},
{
Action: [
'ecr:PutImage',
'ecr:InitiateLayerUpload',
'ecr:UploadLayerPart',
'ecr:CompleteLayerUpload',
'ecr:UploadLayerPart',
'ecr:InitiateLayerUpload',
'ecr:PutImage',
],
Effect: 'Allow',
Resource: {
Expand Down Expand Up @@ -335,10 +333,107 @@ describe('DockerImageDeploy', () => {
},
});
});

describe('Custom Resrouces', () => {
test('onEventHandler has correct permissions', () => {
Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', {
PolicyDocument: {
Statement: Match.arrayWith([
{
Action: 'codebuild:StartBuild',
Effect: 'Allow',
Resource: {
'Fn::GetAtt': [
'TestDeploymentDockerImageDeployProject0884B3B5',
'Arn',
],
},
},
]),
},
});
});

test('isCompleteHandler has correct permissions', () => {
Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', {
PolicyDocument: {
Statement: Match.arrayWith([
{
Action: [
'codebuild:ListBuildsForProject',
'codebuild:BatchGetBuilds',
],
Effect: 'Allow',
Resource: {
'Fn::GetAtt': [
'TestDeploymentDockerImageDeployProject0884B3B5',
'Arn',
],
},
},
]),
},
});
});

test('deploy with onEventhandler log group', () => {
Template.fromStack(stack).hasResourceProperties('AWS::Lambda::Function', {
Handler: 'index.onEventhandler',
LoggingConfig: {
LogGroup: {
Ref: 'OnEventLogGroup5AB79325',
},
},
});
});

test('deploy with onEventhandler log group', () => {
Template.fromStack(stack).hasResourceProperties('AWS::Lambda::Function', {
Handler: 'index.isCompleteHandler',
LoggingConfig: {
LogGroup: {
Ref: 'IsCompleteLogGroupE01E0185',
},
},
});
});

test('deploy with CRProvider log group', () => {
Template.fromStack(stack).hasResourceProperties('AWS::Lambda::Function', {
LoggingConfig: {
LogGroup: {
Ref: 'CrProviderLogGroup33080E63',
},
},
});
});

test('deploy with WaiterStateMachine log options', () => {
Template.fromStack(stack).hasResourceProperties('AWS::StepFunctions::StateMachine', {
LoggingConfiguration: {
Destinations: [
{
CloudWatchLogsLogGroup: {
LogGroupArn: {
'Fn::GetAtt': [
'TestDeploymentCRProviderwaiterstatemachineLogGroupC06C22F6',
'Arn',
],
},
},
},
],
IncludeExecutionData: false,
Level: 'ERROR',
},
});
});
});
});

describe('Tag validation', () => {
// GIVEN
const stack = new cdk.Stack();
const repo = new ecr.Repository(stack, 'TestTagsRepository');
const testSource = imagedeploy.Source.directory(path.join(__dirname, 'assets/test1'));

Expand Down Expand Up @@ -409,46 +504,5 @@ describe('DockerImageDeploy', () => {
});
});

describe('Custom Resrouces', () => {
test('onEventHandler has correct permissions', () => {
Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', {
PolicyDocument: {
Statement: Match.arrayWith([
{
Action: 'codebuild:StartBuild',
Effect: 'Allow',
Resource: {
'Fn::GetAtt': [
'TestDeploymentDockerImageDeployProject0884B3B5',
'Arn',
],
},
},
]),
},
});
});

test('isCompleteHandler has correct permissions', () => {
Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', {
PolicyDocument: {
Statement: Match.arrayWith([
{
Action: [
'codebuild:ListBuildsForProject',
'codebuild:BatchGetBuilds',
],
Effect: 'Allow',
Resource: {
'Fn::GetAtt': [
'TestDeploymentDockerImageDeployProject0884B3B5',
'Arn',
],
},
},
]),
},
});
});
});
});
Loading