Skip to content

Commit

Permalink
feat(ecr-assets): Support cache-from and cache-to flags (aws#24024)
Browse files Browse the repository at this point in the history
This adds the `--cache-from` and `--cache-to` flag options.

---

### All Submissions:
* [x]  Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md?rgh-link-date=2022-12-09T23%3A48%3A14Z)

### Adding new Construct Runtime Dependencies:
* [ ]  This PR adds new construct runtime dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md/?rgh-link-date=2022-12-09T23%3A48%3A14Z#adding-construct-runtime-dependencies)

### New Features
* [x]  Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/main/INTEGRATION_TESTS.md?rgh-link-date=2022-12-09T23%3A48%3A14Z)?

  * [x]  Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)?

_By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license_
  • Loading branch information
RichiCoder1 authored and homakk committed Mar 28, 2023
1 parent 5035698 commit bc86384
Show file tree
Hide file tree
Showing 17 changed files with 366 additions and 5 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();
Expand Up @@ -97,6 +97,22 @@ export interface DockerImageSource {
* @see https://docs.docker.com/engine/reference/commandline/build/#custom-build-outputs
*/
readonly dockerOutputs?: 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 All @@ -113,3 +129,25 @@ export interface DockerImageDestination extends AwsDestination {
*/
readonly imageTag: string;
}

/**
* 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 };
}
Expand Up @@ -71,6 +71,28 @@ export interface Tag {
readonly value: string
}

/**
* Options for configuring the Docker cache backend
*/
export interface ContainerImageAssetCacheOption {
/**
* 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 };
}

/**
* Metadata Entry spec for container images.
*/
Expand Down Expand Up @@ -160,6 +182,22 @@ export interface ContainerImageAssetMetadataEntry extends BaseAssetMetadataEntry
* @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?: ContainerImageAssetCacheOption[];

/**
* 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?: ContainerImageAssetCacheOption;
}

/**
Expand Down
31 changes: 31 additions & 0 deletions packages/@aws-cdk/cloud-assembly-schema/schema/assets.schema.json
Expand Up @@ -176,9 +176,40 @@
"items": {
"type": "string"
}
},
"cacheFrom": {
"description": "Cache from options to pass to the `docker build` command. (Default - no cache from options are passed to the build command)",
"type": "array",
"items": {
"$ref": "#/definitions/DockerCacheOption"
}
},
"cacheTo": {
"description": "Cache to options to pass to the `docker build` command. (Default - no cache to options are passed to the build command)",
"$ref": "#/definitions/DockerCacheOption"
}
}
},
"DockerCacheOption": {
"description": "Options for configuring the Docker cache backend",
"type": "object",
"properties": {
"type": {
"description": "The type of cache to use.\nRefer to https://docs.docker.com/build/cache/backends/ for full list of backends. (Default - unspecified)",
"type": "string"
},
"params": {
"description": "Any parameters to pass into the docker cache backend configuration.\nRefer to https://docs.docker.com/build/cache/backends/ for cache backend configuration. (Default {} No options provided)",
"type": "object",
"additionalProperties": {
"type": "string"
}
}
},
"required": [
"type"
]
},
"DockerImageDestination": {
"description": "Where to publish docker images",
"type": "object",
Expand Down

0 comments on commit bc86384

Please sign in to comment.