From 02b63c213bb368d646b3efeb09f77638a4eae7ac Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Tue, 21 May 2019 17:09:56 +0200 Subject: [PATCH] feat(toolkit): allow to pass build args to docker build --- packages/@aws-cdk/assets-docker/README.md | 16 ++++++- .../@aws-cdk/assets-docker/lib/image-asset.ts | 8 ++++ .../assets-docker/test/test.image-asset.ts | 18 +++++++ .../aws-ecs/lib/images/asset-image.ts | 12 ++++- .../@aws-cdk/cx-api/lib/metadata/assets.ts | 9 +++- packages/aws-cdk/lib/docker.ts | 2 + packages/aws-cdk/test/test.docker.ts | 48 ++++++++++++++++++- 7 files changed, 108 insertions(+), 5 deletions(-) diff --git a/packages/@aws-cdk/assets-docker/README.md b/packages/@aws-cdk/assets-docker/README.md index 1ecd3a655fa63..f54221ff7111c 100644 --- a/packages/@aws-cdk/assets-docker/README.md +++ b/packages/@aws-cdk/assets-docker/README.md @@ -20,8 +20,20 @@ This will instruct the toolkit to build a Docker image from `my-image`, push it to an AWS ECR repository and wire the name of the repository as CloudFormation parameters to your stack. -Use `asset.imageUri` can be used to reference the image (it includes both the -ECR image URL and tag. +Use `asset.imageUri` to reference the image (it includes both the ECR image URL +and tag. + +You can optionally pass build args to the `docker build` command by specifying +the `buildArgs` property: + +```typescript +const asset = new DockerImageAsset(this, 'MyBuildImage', { + directory: path.join(__dirname, 'my-image'), + buildArgs: { + HTTP_PROXY: 'http://10.20.30.2:1234' + } +}); +``` ### Pull Permissions diff --git a/packages/@aws-cdk/assets-docker/lib/image-asset.ts b/packages/@aws-cdk/assets-docker/lib/image-asset.ts index 7d939dfca904d..0644af975dfde 100644 --- a/packages/@aws-cdk/assets-docker/lib/image-asset.ts +++ b/packages/@aws-cdk/assets-docker/lib/image-asset.ts @@ -22,6 +22,13 @@ export interface DockerImageAssetProps { * @default automatically derived from the asset's ID. */ readonly repositoryName?: string; + + /** + * Build args to pass to the `docker build` command + * + * @default no build args are passed + */ + readonly buildArgs?: { [key: string]: string }; } /** @@ -75,6 +82,7 @@ export class DockerImageAsset extends cdk.Construct { id: this.node.uniqueId, imageNameParameter: imageNameParameter.logicalId, repositoryName: props.repositoryName, + buildArgs: props.buildArgs }; this.node.addMetadata(cxapi.ASSET_METADATA, asset); diff --git a/packages/@aws-cdk/assets-docker/test/test.image-asset.ts b/packages/@aws-cdk/assets-docker/test/test.image-asset.ts index 43cc5c5705989..6374e0381ee15 100644 --- a/packages/@aws-cdk/assets-docker/test/test.image-asset.ts +++ b/packages/@aws-cdk/assets-docker/test/test.image-asset.ts @@ -31,6 +31,24 @@ export = { test.done(); }, + 'with build args'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const asset = new DockerImageAsset(stack, 'Image', { + directory: path.join(__dirname, 'demo-image'), + buildArgs: { + a: 'b' + } + }); + + // THEN + const assetMetadata = asset.node.metadata.find(({ type }) => type === 'aws:cdk:asset'); + test.deepEqual(assetMetadata && assetMetadata.data.buildArgs, { a: 'b' }); + test.done(); + }, + 'asset.repository.grantPull can be used to grant a principal permissions to use the image'(test: Test) { // GIVEN const stack = new cdk.Stack(); diff --git a/packages/@aws-cdk/aws-ecs/lib/images/asset-image.ts b/packages/@aws-cdk/aws-ecs/lib/images/asset-image.ts index ccaaaa2a4f18d..a85dac7eeb7b9 100644 --- a/packages/@aws-cdk/aws-ecs/lib/images/asset-image.ts +++ b/packages/@aws-cdk/aws-ecs/lib/images/asset-image.ts @@ -9,6 +9,13 @@ export interface AssetImageProps { * The directory where the Dockerfile is stored */ readonly directory: string; + + /** + * Build args to pass to the `docker build` command + * + * @default no build args are passed + */ + readonly buildArgs?: { [key: string]: string }; } /** @@ -19,7 +26,10 @@ export class AssetImage extends ContainerImage { constructor(scope: cdk.Construct, id: string, props: AssetImageProps) { super(); - this.asset = new DockerImageAsset(scope, id, { directory: props.directory }); + this.asset = new DockerImageAsset(scope, id, { + directory: props.directory, + buildArgs: props.buildArgs, + }); } public bind(containerDefinition: ContainerDefinition): void { diff --git a/packages/@aws-cdk/cx-api/lib/metadata/assets.ts b/packages/@aws-cdk/cx-api/lib/metadata/assets.ts index ef446fc583cac..98086a44616b1 100644 --- a/packages/@aws-cdk/cx-api/lib/metadata/assets.ts +++ b/packages/@aws-cdk/cx-api/lib/metadata/assets.ts @@ -81,9 +81,16 @@ export interface ContainerImageAssetMetadataEntry { * Note, this is only the repository name, without the registry and * the tag parts. * - * * @default automatically derived from the asset's ID. + * @default automatically derived from the asset's ID. */ readonly repositoryName?: string; + + /** + * Build args to pass to the `docker build` command + * + * @default no build args are passed + */ + readonly buildArgs?: { [key: string]: string }; } export type AssetMetadataEntry = FileAssetMetadataEntry | ContainerImageAssetMetadataEntry; diff --git a/packages/aws-cdk/lib/docker.ts b/packages/aws-cdk/lib/docker.ts index 524eee7df69d3..f7593ac21828d 100644 --- a/packages/aws-cdk/lib/docker.ts +++ b/packages/aws-cdk/lib/docker.ts @@ -64,8 +64,10 @@ export async function prepareContainerAsset(asset: ContainerImageAssetMetadataEn const baseCommand = ['docker', 'build', + ...Object.entries(asset.buildArgs || {}).map(([k, v]) => `--build-arg ${k}=${v}`), // Pass build args if any '--quiet', asset.path]; + const command = ci ? [...baseCommand, '--cache-from', latest] // This does not fail if latest is not available : baseCommand; diff --git a/packages/aws-cdk/test/test.docker.ts b/packages/aws-cdk/test/test.docker.ts index efda5f52d728e..b46d81b8042b8 100644 --- a/packages/aws-cdk/test/test.docker.ts +++ b/packages/aws-cdk/test/test.docker.ts @@ -1,7 +1,9 @@ import cxapi = require('@aws-cdk/cx-api'); import { Test } from 'nodeunit'; +import sinon = require('sinon'); import { ToolkitInfo } from '../lib'; import { prepareContainerAsset } from '../lib/docker'; +import os = require('../lib/os'); import { MockSDK } from './util/mock-sdk'; export = { @@ -51,4 +53,48 @@ export = { test.done(); }, -}; \ No newline at end of file + + async 'passes the correct args to docker build'(test: Test) { + // GIVEN + const toolkit = new ToolkitInfo({ + sdk: new MockSDK(), + bucketName: 'BUCKET_NAME', + bucketEndpoint: 'BUCKET_ENDPOINT', + environment: { name: 'env', account: '1234', region: 'abc' } + }); + + const prepareEcrRepositoryStub = sinon.stub(toolkit, 'prepareEcrRepository').resolves({ + repositoryUri: 'uri', + repositoryName: 'name' + }); + + const shellStub = sinon.stub(os, 'shell').rejects('STOPTEST'); + + // WHEN + const asset: cxapi.ContainerImageAssetMetadataEntry = { + id: 'assetId', + imageNameParameter: 'MyParameter', + packaging: 'container-image', + path: '/foo', + repositoryName: 'some-name', + buildArgs: { + a: 'b', + c: 'd' + } + }; + + try { + await prepareContainerAsset(asset, toolkit, false); + } catch (e) { + if (!/STOPTEST/.test(e.toString())) { throw e; } + } + + // THEN + const command = ['docker', 'build', '--build-arg a=b', '--build-arg c=d', '--quiet', '/foo']; + test.ok(shellStub.calledWith(command)); + + prepareEcrRepositoryStub.restore(); + shellStub.restore(); + test.done(); + } +};