Skip to content

Commit

Permalink
Merge branch 'main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
mergify[bot] committed Mar 8, 2023
2 parents b95eaed + 7c7ad6d commit 607f222
Show file tree
Hide file tree
Showing 34 changed files with 3,227 additions and 72 deletions.
12 changes: 12 additions & 0 deletions packages/@aws-cdk/aws-ecr-assets/README.md
Expand Up @@ -121,6 +121,18 @@ const asset = new DockerImageAsset(this, 'MyBuildImage', {
})
```

You can optionally pass cache from and cache to options to cache images:

```ts
import { DockerImageAsset, Platform } from '@aws-cdk/aws-ecr-assets';

const asset = new DockerImageAsset(this, 'MyBuildImage', {
directory: path.join(__dirname, 'my-image'),
cacheFrom: [{ type: 'registry', params: { ref: 'ghcr.io/myorg/myimage:cache' }}],
cacheTo: { type: 'registry', params: { ref: 'ghcr.io/myorg/myimage:cache', mode: 'max', compression: 'zstd' }}
})
```

## Images from Tarball

Images are loaded from a local tarball, uploaded to ECR by the CDK toolkit and/or your app's CI-CD pipeline, and can be
Expand Down
54 changes: 54 additions & 0 deletions packages/@aws-cdk/aws-ecr-assets/lib/image-asset.ts
Expand Up @@ -148,6 +148,28 @@ export interface DockerImageAssetInvalidationOptions {
readonly outputs?: boolean;
}

/**
* Options for configuring the Docker cache backend
*/
export interface DockerCacheOption {
/**
* The type of cache to use.
* Refer to https://docs.docker.com/build/cache/backends/ for full list of backends.
* @default - unspecified
*
* @example 'registry'
*/
readonly type: string;
/**
* Any parameters to pass into the docker cache backend configuration.
* Refer to https://docs.docker.com/build/cache/backends/ for cache backend configuration.
* @default {} No options provided
*
* @example { ref: `12345678.dkr.ecr.us-west-2.amazonaws.com/cache:${branch}`, mode: "max" }
*/
readonly params?: { [key: string]: string };
}

/**
* Options for DockerImageAsset
*/
Expand Down Expand Up @@ -236,6 +258,22 @@ export interface DockerImageAssetOptions extends FingerprintOptions, FileFingerp
* @see https://docs.docker.com/engine/reference/commandline/build/#custom-build-outputs
*/
readonly outputs?: string[];

/**
* Cache from options to pass to the `docker build` command.
*
* @default - no cache from options are passed to the build command
* @see https://docs.docker.com/build/cache/backends/
*/
readonly cacheFrom?: DockerCacheOption[];

/**
* Cache to options to pass to the `docker build` command.
*
* @default - no cache to options are passed to the build command
* @see https://docs.docker.com/build/cache/backends/
*/
readonly cacheTo?: DockerCacheOption;
}

/**
Expand Down Expand Up @@ -316,6 +354,16 @@ export class DockerImageAsset extends Construct implements IAsset {
*/
private readonly dockerOutputs?: string[];

/**
* Cache from options to pass to the `docker build` command.
*/
private readonly dockerCacheFrom?: DockerCacheOption[];

/**
* Cache to options to pass to the `docker build` command.
*/
private readonly dockerCacheTo?: DockerCacheOption;

/**
* Docker target to build to
*/
Expand Down Expand Up @@ -407,6 +455,8 @@ export class DockerImageAsset extends Construct implements IAsset {
this.dockerBuildSecrets = props.buildSecrets;
this.dockerBuildTarget = props.target;
this.dockerOutputs = props.outputs;
this.dockerCacheFrom = props.cacheFrom;
this.dockerCacheTo = props.cacheTo;

const location = stack.synthesizer.addDockerImageAsset({
directoryName: this.assetPath,
Expand All @@ -418,6 +468,8 @@ export class DockerImageAsset extends Construct implements IAsset {
networkMode: props.networkMode?.mode,
platform: props.platform?.platform,
dockerOutputs: this.dockerOutputs,
dockerCacheFrom: this.dockerCacheFrom,
dockerCacheTo: this.dockerCacheTo,
});

this.repository = ecr.Repository.fromRepositoryName(this, 'Repository', location.repositoryName);
Expand Down Expand Up @@ -456,6 +508,8 @@ export class DockerImageAsset extends Construct implements IAsset {
resource.cfnOptions.metadata[cxapi.ASSET_RESOURCE_METADATA_DOCKER_BUILD_TARGET_KEY] = this.dockerBuildTarget;
resource.cfnOptions.metadata[cxapi.ASSET_RESOURCE_METADATA_PROPERTY_KEY] = resourceProperty;
resource.cfnOptions.metadata[cxapi.ASSET_RESOURCE_METADATA_DOCKER_OUTPUTS_KEY] = this.dockerOutputs;
resource.cfnOptions.metadata[cxapi.ASSET_RESOURCE_METADATA_DOCKER_CACHE_FROM_KEY] = this.dockerCacheFrom;
resource.cfnOptions.metadata[cxapi.ASSET_RESOURCE_METADATA_DOCKER_CACHE_TO_KEY] = this.dockerCacheTo;
}

}
Expand Down
88 changes: 88 additions & 0 deletions packages/@aws-cdk/aws-ecr-assets/test/build-image-cache.test.ts
@@ -0,0 +1,88 @@
import * as fs from 'fs';
import * as path from 'path';
import { AssetManifest } from '@aws-cdk/cloud-assembly-schema';
import { App, Stack } from '@aws-cdk/core';
import { AssetManifestArtifact, CloudArtifact, CloudAssembly } from '@aws-cdk/cx-api';
import { DockerImageAsset } from '../lib';

describe('build cache', () => {
test('manifest contains cache from options ', () => {
// GIVEN
const app = new App();
const stack = new Stack(app);
const asset = new DockerImageAsset(stack, 'DockerImage6', {
directory: path.join(__dirname, 'demo-image'),
cacheFrom: [{ type: 'registry', params: { image: 'foo' } }],
});

// WHEN
const asm = app.synth();

// THEN
const manifestArtifact = getAssetManifest(asm);
const manifest = readAssetManifest(manifestArtifact);

expect(Object.keys(manifest.dockerImages ?? {}).length).toBe(1);
expect(manifest.dockerImages?.[asset.assetHash]?.source.cacheFrom?.length).toBe(1);
expect(manifest.dockerImages?.[asset.assetHash]?.source.cacheFrom?.[0]).toStrictEqual({
type: 'registry',
params: { image: 'foo' },
});
});
test('manifest contains cache to options ', () => {
// GIVEN
const app = new App();
const stack = new Stack(app);
const asset = new DockerImageAsset(stack, 'DockerImage6', {
directory: path.join(__dirname, 'demo-image'),
cacheTo: { type: 'inline' },
});

// WHEN
const asm = app.synth();

// THEN
const manifestArtifact = getAssetManifest(asm);
const manifest = readAssetManifest(manifestArtifact);

expect(Object.keys(manifest.dockerImages ?? {}).length).toBe(1);
expect(manifest.dockerImages?.[asset.assetHash]?.source.cacheTo).toStrictEqual({
type: 'inline',
});
});

test('manifest does not contain options when not specified', () => {
// GIVEN
const app = new App();
const stack = new Stack(app);
const asset = new DockerImageAsset(stack, 'DockerImage6', {
directory: path.join(__dirname, 'demo-image'),
});

// WHEN
const asm = app.synth();

// THEN
const manifestArtifact = getAssetManifest(asm);
const manifest = readAssetManifest(manifestArtifact);
expect(Object.keys(manifest.dockerImages ?? {}).length).toBe(1);
expect(manifest.dockerImages?.[asset.assetHash]?.source.cacheFrom).toBeUndefined();
expect(manifest.dockerImages?.[asset.assetHash]?.source.cacheTo).toBeUndefined();
});
});

function isAssetManifest(x: CloudArtifact): x is AssetManifestArtifact {
return x instanceof AssetManifestArtifact;
}

function getAssetManifest(asm: CloudAssembly): AssetManifestArtifact {
const manifestArtifact = asm.artifacts.filter(isAssetManifest)[0];
if (!manifestArtifact) {
throw new Error('no asset manifest in assembly');
}
return manifestArtifact;
}

function readAssetManifest(manifestArtifact: AssetManifestArtifact): AssetManifest {
return JSON.parse(fs.readFileSync(manifestArtifact.file, { encoding: 'utf-8' }));
}
Expand Up @@ -81,6 +81,11 @@
"Value": {
"Fn::Sub": "${AWS::AccountId}.dkr.ecr.${AWS::Region}.${AWS::URLSuffix}/cdk-hnb659fds-container-assets-${AWS::AccountId}-${AWS::Region}:60dea2e16e94d1977b92fe03fa7085fea446233f1fe499702b69593438baa59f"
}
},
"ImageUri6": {
"Value": {
"Fn::Sub": "${AWS::AccountId}.dkr.ecr.${AWS::Region}.${AWS::URLSuffix}/cdk-hnb659fds-container-assets-${AWS::AccountId}-${AWS::Region}:0a3355be12051c9984bf2b0b2bba4e6ea535968e5b6e7396449701732fe5ed14"
}
}
},
"Parameters": {
Expand Down
Expand Up @@ -262,8 +262,8 @@
"version": "0.0.0"
}
},
"ImageUri5": {
"id": "ImageUri5",
"ImageUri4": {
"id": "ImageUri4",
"path": "integ-assets-docker/ImageUri5",
"constructInfo": {
"fqn": "@aws-cdk/core.CfnOutput",
Expand Down
7 changes: 7 additions & 0 deletions packages/@aws-cdk/aws-ecr-assets/test/integ.assets-docker.ts
Expand Up @@ -31,17 +31,24 @@ const asset5 = new assets.DockerImageAsset(stack, 'DockerImage5', {
},
});

const asset6 = new assets.DockerImageAsset(stack, 'DockerImage6', {
directory: path.join(__dirname, 'demo-image'),
cacheTo: { type: 'inline' },
});

const user = new iam.User(stack, 'MyUser');
asset.repository.grantPull(user);
asset2.repository.grantPull(user);
asset3.repository.grantPull(user);
asset4.repository.grantPull(user);
asset5.repository.grantPull(user);
asset6.repository.grantPull(user);

new cdk.CfnOutput(stack, 'ImageUri', { value: asset.imageUri });
new cdk.CfnOutput(stack, 'ImageUri2', { value: asset2.imageUri });
new cdk.CfnOutput(stack, 'ImageUri3', { value: asset3.imageUri });
new cdk.CfnOutput(stack, 'ImageUri4', { value: asset4.imageUri });
new cdk.CfnOutput(stack, 'ImageUri5', { value: asset5.imageUri });
new cdk.CfnOutput(stack, 'ImageUri6', { value: asset6.imageUri });

app.synth();
16 changes: 14 additions & 2 deletions packages/@aws-cdk/aws-kinesisanalytics-flink/README.md
Expand Up @@ -46,7 +46,7 @@ const flinkApp = new flink.Application(this, 'Application', {
},
},
// ...
runtime: flink.Runtime.FLINK_1_13,
runtime: flink.Runtime.FLINK_1_15,
code: flink.ApplicationCode.fromBucket(bucket, 'my-app.jar'),
});
```
Expand All @@ -59,7 +59,7 @@ snapshotting, monitoring, and parallelism.
declare const bucket: s3.Bucket;
const flinkApp = new flink.Application(this, 'Application', {
code: flink.ApplicationCode.fromBucket(bucket, 'my-app.jar'),
runtime: flink.Runtime.FLINK_1_13,
runtime: flink.Runtime.FLINK_1_15,
checkpointingEnabled: true, // default is true
checkpointInterval: Duration.seconds(30), // default is 1 minute
minPauseBetweenCheckpoints: Duration.seconds(10), // default is 5 seconds
Expand All @@ -72,3 +72,15 @@ const flinkApp = new flink.Application(this, 'Application', {
logGroup: new logs.LogGroup(this, 'LogGroup'), // by default, a new LogGroup will be created
});
```

Flink applications can optionally be deployed in a VPC:

```ts
declare const bucket: s3.Bucket;
declare const vpc: ec2.Vpc;
const flinkApp = new flink.Application(this, 'Application', {
code: flink.ApplicationCode.fromBucket(bucket, 'my-app.jar'),
runtime: flink.Runtime.FLINK_1_15,
vpc,
});
```

0 comments on commit 607f222

Please sign in to comment.