Skip to content

Commit

Permalink
feat: support deploy-time container image build (#15)
Browse files Browse the repository at this point in the history
* feat: support deploy-time container image build

* update doc
  • Loading branch information
tmokmss authored Apr 26, 2024
1 parent 12b5c98 commit 74e3e37
Show file tree
Hide file tree
Showing 28 changed files with 4,015 additions and 149 deletions.
411 changes: 411 additions & 0 deletions API.md

Large diffs are not rendered by default.

30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
AWS CDK L3 construct that allows you to run a build job for specific purposes. Currently this library supports the following use cases:

* Build web frontend static files
* Build a container image
* Build Seekable OCI (SOCI) indices for container images

## Usage
Expand Down Expand Up @@ -118,6 +119,35 @@ To mitigate this issue, you can separate the stack for frontend construct from o
],
```

### Build a container image
You can build a container image at deploy time by the following code:

```ts
import { ContainerImageBuild } from 'deploy-time-build;

const image = new ContainerImageBuild(this, 'Build', {
directory: 'example-image',
buildArgs: { DUMMY_FILE_SIZE_MB: '15' },
tag: 'my-image-tag',
});
new DockerImageFunction(this, 'Function', {
code: image.toLambdaDockerImageCode(),
});
const armImage = new ContainerImageBuild(this, 'BuildArm', {
directory: 'example-image',
platform: Platform.LINUX_ARM64,
repository: image.repository,
zstdCompression: true,
});
new FargateTaskDefinition(this, 'TaskDefinition', {
runtimePlatform: { cpuArchitecture: CpuArchitecture.ARM64 }
}).addContainer('main', {
image: armImage.toEcsDockerImageCode(),
});
```

The third argument (props) are the super set of DockerImageAsset's properties. You can additionally set `tag` and `repository`.

### Build SOCI index for a container image
[Seekable OCI (SOCI)](https://aws.amazon.com/about-aws/whats-new/2022/09/introducing-seekable-oci-lazy-loading-container-images/) is a way to help start tasks faster for Amazon ECS tasks on Fargate 1.4.0. You can build and push a SOCI index using the `SociIndexBuild` construct.

Expand Down
2 changes: 1 addition & 1 deletion example/example-image/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM nginx:latest
FROM public.ecr.aws/nginx/nginx:1.26
# create dummy file to change the size of a image
ARG DUMMY_FILE_SIZE_MB="10"
# RUN fallocate -l ${DUMMY_FILE_SIZE_MB} dummy.img
Expand Down
41 changes: 39 additions & 2 deletions example/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { Stack, StackProps, App, RemovalPolicy, CfnOutput } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { MockIntegration, RestApi } from 'aws-cdk-lib/aws-apigateway';
import { NodejsBuild, SociIndexBuild } from '../src/';
import { ContainerImageBuild, NodejsBuild, SociIndexBuild } from '../src/';
import { BlockPublicAccess, Bucket, BucketEncryption } from 'aws-cdk-lib/aws-s3';
import { OriginAccessIdentity, CloudFrontWebDistribution } from 'aws-cdk-lib/aws-cloudfront';
import { DockerImageAsset } from 'aws-cdk-lib/aws-ecr-assets';
import { DockerImageAsset, Platform } from 'aws-cdk-lib/aws-ecr-assets';
import { DockerImageFunction } from 'aws-cdk-lib/aws-lambda';
import { AwsLogDriver, Cluster, CpuArchitecture, FargateTaskDefinition } from 'aws-cdk-lib/aws-ecs';
import { SubnetType, Vpc } from 'aws-cdk-lib/aws-ec2';

class NodejsTestStack extends Stack {
constructor(scope: Construct, id: string, props: StackProps = {}) {
Expand Down Expand Up @@ -85,12 +88,46 @@ class SociIndexTestStack extends Stack {
}
}

class ContainerImageTestStack extends Stack {
constructor(scope: Construct, id: string, props: StackProps = {}) {
super(scope, id, props);

const image = new ContainerImageBuild(this, 'Build', { directory: 'example-image', buildArgs: { DUMMY_FILE_SIZE_MB: '15' } });
const armImage = new ContainerImageBuild(this, 'BuildArm', {
directory: 'example-image',
platform: Platform.LINUX_ARM64,
repository: image.repository,
zstdCompression: true,
});
new DockerImageFunction(this, 'Function', {
code: image.toLambdaDockerImageCode(),
});
new Cluster(this, 'Cluster', {
vpc: new Vpc(this, 'Vpc', {
maxAzs: 1,
subnetConfiguration: [
{
cidrMask: 24,
name: 'public',
subnetType: SubnetType.PUBLIC,
},
],
}),
});
new FargateTaskDefinition(this, 'TaskDefinition', { runtimePlatform: { cpuArchitecture: CpuArchitecture.ARM64 } }).addContainer('main', {
image: armImage.toEcsDockerImageCode(),
logging: new AwsLogDriver({ streamPrefix: 'main' }),
});
}
}

class TestApp extends App {
constructor() {
super();

new NodejsTestStack(this, 'NodejsTestStack');
new SociIndexTestStack(this, 'SociIndexTestStack');
new ContainerImageTestStack(this, 'ContainerImageTestStack');
}
}

Expand Down
69 changes: 45 additions & 24 deletions lambda/trigger-codebuild/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,32 @@ export const handler = async (event: Event, context: any) => {
try {
if (event.RequestType == 'Create' || event.RequestType == 'Update') {
const props = event.ResourceProperties;
const commonEnvironments = [
{
name: 'responseURL',
value: event.ResponseURL,
},
{
name: 'stackId',
value: event.StackId,
},
{
name: 'requestId',
value: event.RequestId,
},
{
name: 'logicalResourceId',
value: event.LogicalResourceId,
},
];

let command: StartBuildCommand;
switch (props.type) {
case 'NodejsBuild':
command = new StartBuildCommand({
projectName: props.codeBuildProjectName,
environmentVariablesOverride: [
...commonEnvironments,
{
name: 'input',
value: JSON.stringify(
Expand Down Expand Up @@ -62,22 +81,6 @@ export const handler = async (event: Event, context: any) => {
name: 'projectName',
value: props.codeBuildProjectName,
},
{
name: 'responseURL',
value: event.ResponseURL,
},
{
name: 'stackId',
value: event.StackId,
},
{
name: 'requestId',
value: event.RequestId,
},
{
name: 'logicalResourceId',
value: event.LogicalResourceId,
},
...Object.entries(props.environment ?? {}).map(([name, value]) => ({
name,
value,
Expand All @@ -89,6 +92,7 @@ export const handler = async (event: Event, context: any) => {
command = new StartBuildCommand({
projectName: props.codeBuildProjectName,
environmentVariablesOverride: [
...commonEnvironments,
{
name: 'repositoryName',
value: props.repositoryName,
Expand All @@ -101,25 +105,42 @@ export const handler = async (event: Event, context: any) => {
name: 'projectName',
value: props.codeBuildProjectName,
},
],
});
break;
case 'ContainerImageBuild': {
command = new StartBuildCommand({
projectName: props.codeBuildProjectName,
environmentVariablesOverride: [
...commonEnvironments,
{
name: 'repositoryUri',
value: props.repositoryUri,
},
{
name: 'responseURL',
value: event.ResponseURL,
name: 'repositoryAuthUri',
value: props.repositoryUri.split('/')[0],
},
{
name: 'stackId',
value: event.StackId,
name: 'buildCommand',
value: props.buildCommand,
},
{
name: 'requestId',
value: event.RequestId,
name: 'imageTag',
value: props.imageTag,
},
{
name: 'projectName',
value: props.codeBuildProjectName,
},
{
name: 'logicalResourceId',
value: event.LogicalResourceId,
name: 'sourceS3Url',
value: props.sourceS3Url,
},
],
});
break;
}
default:
throw new Error(`invalid event type ${props}}`);
}
Expand Down
Loading

0 comments on commit 74e3e37

Please sign in to comment.