Skip to content

Commit

Permalink
feat: allow inclusion of image copy to pipeline
Browse files Browse the repository at this point in the history
Support both codepipeline and pipelines
  • Loading branch information
Hi-Fi committed Sep 26, 2022
1 parent fbeeee1 commit 1eebc0c
Show file tree
Hide file tree
Showing 7 changed files with 2,649 additions and 248 deletions.
68 changes: 4 additions & 64 deletions API.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@

Name|Description
----|-----------
[DockerImageName](#cdk-ecr-deployment-dockerimagename)|*No description*
[ECRDeployment](#cdk-ecr-deployment-ecrdeployment)|*No description*
[S3ArchiveName](#cdk-ecr-deployment-s3archivename)|*No description*


**Structs**
Expand All @@ -27,36 +25,6 @@ Name|Description



## class DockerImageName <a id="cdk-ecr-deployment-dockerimagename"></a>



__Implements__: [IImageName](#cdk-ecr-deployment-iimagename)

### Initializer




```ts
new DockerImageName(name: string, creds?: ICredentials)
```

* **name** (<code>string</code>) *No description*
* **creds** (<code>[ICredentials](#cdk-ecr-deployment-icredentials)</code>) *No description*



### Properties


Name | Type | Description
-----|------|-------------
**uri** | <code>string</code> | The uri of the docker image.
**creds**? | <code>[ICredentials](#cdk-ecr-deployment-icredentials)</code> | __*Optional*__



## class ECRDeployment <a id="cdk-ecr-deployment-ecrdeployment"></a>


Expand All @@ -82,8 +50,10 @@ new ECRDeployment(scope: Construct, id: string, props: ECRDeploymentProps)
* **environment** (<code>Map<string, string></code>) The environment variable to set. __*Optional*__
* **memoryLimit** (<code>number</code>) The amount of memory (in MiB) to allocate to the AWS Lambda function which replicates the files from the CDK bucket to the destination bucket. __*Default*__: 512
* **role** (<code>[aws_iam.IRole](#aws-cdk-lib-aws-iam-irole)</code>) Execution role associated with this function. __*Default*__: A role is automatically created
* **stage** (<code>[aws_codepipeline.IStage](#aws-cdk-lib-aws-codepipeline-istage)</code>) CodePipeline Stage to include lambda to. __*Optional*__
* **vpc** (<code>[aws_ec2.IVpc](#aws-cdk-lib-aws-ec2-ivpc)</code>) The VPC network to place the deployment lambda handler in. __*Default*__: None
* **vpcSubnets** (<code>[aws_ec2.SubnetSelection](#aws-cdk-lib-aws-ec2-subnetselection)</code>) Where in the VPC to place the deployment lambda handler. __*Default*__: the Vpc default strategy if not specified
* **wave** (<code>[pipelines.Wave](#aws-cdk-lib-pipelines-wave)</code>) Pipelines Wave to include lambda to. __*Optional*__


### Methods
Expand All @@ -104,37 +74,6 @@ __Returns__:



## class S3ArchiveName <a id="cdk-ecr-deployment-s3archivename"></a>



__Implements__: [IImageName](#cdk-ecr-deployment-iimagename)

### Initializer




```ts
new S3ArchiveName(p: string, ref?: string, creds?: ICredentials)
```

* **p** (<code>string</code>) *No description*
* **ref** (<code>string</code>) *No description*
* **creds** (<code>[ICredentials](#cdk-ecr-deployment-icredentials)</code>) *No description*



### Properties


Name | Type | Description
-----|------|-------------
**uri** | <code>string</code> | The uri of the docker image.
**creds**? | <code>[ICredentials](#cdk-ecr-deployment-icredentials)</code> | __*Optional*__



## struct ECRDeploymentProps <a id="cdk-ecr-deployment-ecrdeploymentprops"></a>


Expand All @@ -150,8 +89,10 @@ Name | Type | Description
**environment**? | <code>Map<string, string></code> | The environment variable to set.<br/>__*Optional*__
**memoryLimit**? | <code>number</code> | The amount of memory (in MiB) to allocate to the AWS Lambda function which replicates the files from the CDK bucket to the destination bucket.<br/>__*Default*__: 512
**role**? | <code>[aws_iam.IRole](#aws-cdk-lib-aws-iam-irole)</code> | Execution role associated with this function.<br/>__*Default*__: A role is automatically created
**stage**? | <code>[aws_codepipeline.IStage](#aws-cdk-lib-aws-codepipeline-istage)</code> | CodePipeline Stage to include lambda to.<br/>__*Optional*__
**vpc**? | <code>[aws_ec2.IVpc](#aws-cdk-lib-aws-ec2-ivpc)</code> | The VPC network to place the deployment lambda handler in.<br/>__*Default*__: None
**vpcSubnets**? | <code>[aws_ec2.SubnetSelection](#aws-cdk-lib-aws-ec2-subnetselection)</code> | Where in the VPC to place the deployment lambda handler.<br/>__*Default*__: the Vpc default strategy if not specified
**wave**? | <code>[pipelines.Wave](#aws-cdk-lib-pipelines-wave)</code> | Pipelines Wave to include lambda to.<br/>__*Optional*__



Expand All @@ -172,7 +113,6 @@ Name | Type | Description

## interface IImageName <a id="cdk-ecr-deployment-iimagename"></a>

__Implemented by__: [DockerImageName](#cdk-ecr-deployment-dockerimagename), [S3ArchiveName](#cdk-ecr-deployment-s3archivename)



Expand Down
209 changes: 30 additions & 179 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,156 +5,18 @@
import * as child_process from 'child_process';
import * as path from 'path';
import {
aws_ec2 as ec2,
aws_iam as iam,
aws_lambda as lambda,
aws_secretsmanager as sm,
Duration,
CustomResource,
Token,
DockerImage,
} from 'aws-cdk-lib';
import { PolicyStatement, AddToPrincipalPolicyResult } from 'aws-cdk-lib/aws-iam';
import { Construct } from 'constructs';
import { shouldUsePrebuiltLambda } from './config';

export interface ECRDeploymentProps {

/**
* Image to use to build Golang lambda for custom resource, if download fails or is not wanted.
*
* Might be needed for local build if all images need to come from own registry.
*
* Note that image should use yum as a package manager and have golang available.
*
* @default public.ecr.aws/sam/build-go1.x:latest
*/
readonly buildImage?: string;
/**
* The source of the docker image.
*/
readonly src: IImageName;

/**
* The destination of the docker image.
*/
readonly dest: IImageName;

/**
* The amount of memory (in MiB) to allocate to the AWS Lambda function which
* replicates the files from the CDK bucket to the destination bucket.
*
* If you are deploying large files, you will need to increase this number
* accordingly.
*
* @default 512
*/
readonly memoryLimit?: number;

/**
* Execution role associated with this function
*
* @default - A role is automatically created
*/
readonly role?: iam.IRole;

/**
* The VPC network to place the deployment lambda handler in.
*
* @default None
*/
readonly vpc?: ec2.IVpc;

/**
* Where in the VPC to place the deployment lambda handler.
* Only used if 'vpc' is supplied.
*
* @default - the Vpc default strategy if not specified
*/
readonly vpcSubnets?: ec2.SubnetSelection;

/**
* The environment variable to set
*/
readonly environment?: { [key: string]: string };
}

export interface IImageName {
/**
* The uri of the docker image.
*
* The uri spec follows https://github.com/containers/skopeo
*/
readonly uri: string;

/**
* The credentials of the docker image. Format `user:password` or `AWS Secrets Manager secret arn` or `AWS Secrets Manager secret name`
*/
creds?: ICredentials;
}

/**
* Credentials to autenticate to used container registry
*/
export interface ICredentials {
/**
* Plain text authentication
*
* Not recommended, as credentials are left in code, stack template and lambda logs.
*/
plainText?: IPlainText;

/**
* Secrets Manager stored authentication
*/
secretManager?: ISecret;
}

/**
* Secrets Manager provided credentials
*/
export interface ISecret {
/**
* Reference to secret where credentials are stored
*
* By default handled to include only authentication token.
*
* If using key-value secret, please define also `usernameKey` and `passwordKey`.
*/
secret: sm.ISecret;
/**
* Key containing username
*/
usenameKey?: string;
/**
* Key containing password
*/
passwordKey?: string;
}

/**
* Plain text credentials
*/
export interface IPlainText {
/** Username to registry */
userName: string;
/** Password to registry */
password: string;
};

/**
* Simplified credentials delivery to Lambda
*/
interface LambdaCredentials {
/** Plain text credentials in form of username: password */
plainText?: string;
/** ARN of secret containing credentials. If only this is provided, secret's whole content is used */
secretArn?: string;
/** Key containing username */
usernameKey?: string;
/** Key containing password */
passwordKey?: string;
}
import { LambdaInvokeStep } from './lambdaInvokeStep';
import { ECRDeploymentProps } from './types';
import { formatCredentials } from './utils';

function getPrebuiltLambda(outputDir: string): boolean {
try {
Expand All @@ -169,38 +31,16 @@ function getPrebuiltLambda(outputDir: string): boolean {
}
}

export class DockerImageName implements IImageName {
public constructor(private name: string, public creds?: ICredentials) { }
public get uri(): string { return `docker://${this.name}`; }
}

export class S3ArchiveName implements IImageName {
private name: string;
public constructor(p: string, ref?: string, public creds?: ICredentials) {
this.name = p;
if (ref) {
this.name += ':' + ref;
}
}
public get uri(): string { return `s3://${this.name}`; }
}

/** Format credentials for Lambda call */
const formatCredentials = (creds?: ICredentials): LambdaCredentials => ({
plainText: creds?.plainText ? `${creds?.plainText?.userName}:${creds?.plainText?.password}` : undefined,
secretArn: creds?.secretManager?.secret.secretArn,
usernameKey: creds?.secretManager?.usenameKey,
passwordKey: creds?.secretManager?.passwordKey,
});


export class ECRDeployment extends Construct {
private handler: lambda.SingletonFunction;

constructor(scope: Construct, id: string, props: ECRDeploymentProps) {
super(scope, id);
const partOfPipeline = props.stage || props.wave;
const memoryLimit = props.memoryLimit ?? 512;
this.handler = new lambda.SingletonFunction(this, 'CustomResourceHandler', {
const lambdaId = partOfPipeline ? 'ImageCopyHandler' : 'CustomResourceHandler';
const lambdaPurpose = partOfPipeline ? 'ImageCopy' : 'Custom::CDKECRDeployment';
this.handler = new lambda.SingletonFunction(this, lambdaId, {
uuid: this.renderSingletonUuid(memoryLimit),
code: lambda.Code.fromAsset(path.join(__dirname, '../lambda'), {
bundling: {
Expand Down Expand Up @@ -254,7 +94,7 @@ export class ECRDeployment extends Construct {
runtime: lambda.Runtime.GO_1_X,
handler: 'main',
environment: props.environment,
lambdaPurpose: 'Custom::CDKECRDeployment',
lambdaPurpose,
timeout: Duration.minutes(15),
role: props.role,
memorySize: memoryLimit,
Expand Down Expand Up @@ -298,19 +138,26 @@ export class ECRDeployment extends Construct {
props.src.creds?.secretManager?.secret.grantRead(handlerRole);
props.dest.creds?.secretManager?.secret.grantRead(handlerRole);

new CustomResource(this, 'CustomResource', {
serviceToken: this.handler.functionArn,
resourceType: 'Custom::CDKBucketDeployment',
properties: {
SrcImage: props.src.uri,
SrcCreds: formatCredentials(props.src.creds),
DestImage: props.dest.uri,
DestCreds: formatCredentials(props.dest.creds),
},
});
if (partOfPipeline) {
const factory = new LambdaInvokeStep(this.handler, props, lambdaPurpose);

props.stage?.addAction(factory.getAction());
props.wave?.addPost(factory);
} else {
new CustomResource(this, 'CustomResource', {
serviceToken: this.handler.functionArn,
resourceType: lambdaPurpose,
properties: {
SrcImage: props.src.uri,
SrcCreds: formatCredentials(props.src.creds),
DestImage: props.dest.uri,
DestCreds: formatCredentials(props.dest.creds),
},
});
}
}

public addToPrincipalPolicy(statement: PolicyStatement): AddToPrincipalPolicyResult {
public addToPrincipalPolicy(statement: iam.PolicyStatement): iam.AddToPrincipalPolicyResult {
const handlerRole = this.handler.role;
if (!handlerRole) { throw new Error('lambda.SingletonFunction should have created a Role'); }

Expand All @@ -334,3 +181,7 @@ export class ECRDeployment extends Construct {
return uuid;
}
}

// Exports for JSII
// see https://github.com/aws/jsii/issues/1818
export { ECRDeploymentProps, IImageName, ICredentials, IPlainText, ISecret } from './types';
Loading

0 comments on commit 1eebc0c

Please sign in to comment.