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: support deploy-time container image build #15

Merged
merged 2 commits into from
Apr 26, 2024
Merged
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
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
Loading