Skip to content

Commit

Permalink
fix(ecr-assets): docker build targets (#4185)
Browse files Browse the repository at this point in the history
* feat(aws-ecr-assets): add build target for docker image asset,
feat(aws-ecs): add build target for docker image asset, feat(aws-ecs):
add build target for docker image asset, feat(cx-api): add build
target for docker image asset, feat(aws-cdk): build docker images with
target when provided

fixes #4184

* fix(aws-cdk): use push instead of concat
  • Loading branch information
themizzi authored and mergify[bot] committed Sep 22, 2019
1 parent 8477d60 commit 91cda9d
Show file tree
Hide file tree
Showing 7 changed files with 104 additions and 2 deletions.
9 changes: 9 additions & 0 deletions packages/@aws-cdk/aws-ecr-assets/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,15 @@ const asset = new DockerImageAsset(this, 'MyBuildImage', {
});
```

You can optionally pass a target to the `docker build` command by specifying
the `target` property:

```typescript
const asset = new DockerImageAsset(this, 'MyBuildImage', {
directory: path.join(__dirname, 'my-image'),
target: 'a-target'
})
```
### Pull Permissions

Depending on the consumer of your image asset, you will need to make sure
Expand Down
12 changes: 10 additions & 2 deletions packages/@aws-cdk/aws-ecr-assets/lib/image-asset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ export interface DockerImageAssetProps extends assets.CopyOptions {
* @default - no build args are passed
*/
readonly buildArgs?: { [key: string]: string };

/**
* Docker target to build to
*
* @default - no target
*/
readonly target?: string;
}

/**
Expand Down Expand Up @@ -95,7 +102,8 @@ export class DockerImageAsset extends cdk.Construct implements assets.IAsset {
sourceHash: this.sourceHash,
imageNameParameter: imageNameParameter.logicalId,
repositoryName: props.repositoryName,
buildArgs: props.buildArgs
buildArgs: props.buildArgs,
target: props.target
};

this.node.addMetadata(cxapi.ASSET_METADATA, asset);
Expand Down Expand Up @@ -123,4 +131,4 @@ function validateBuildArgs(buildArgs?: { [key: string]: string }) {
throw new Error(`Cannot use tokens in keys or values of "buildArgs" since they are needed before deployment`);
}
}
}
}
19 changes: 19 additions & 0 deletions packages/@aws-cdk/aws-ecr-assets/test/test.image-asset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,25 @@ export = {
test.done();
},

'with target'(test: Test) {
// GIVEN
const stack = new Stack();

// WHEN
const asset = new DockerImageAsset(stack, 'Image', {
directory: path.join(__dirname, 'demo-image'),
buildArgs: {
a: 'b'
},
target: 'a-target'
});

// THEN
const assetMetadata = asset.node.metadata.find(({ type }) => type === 'aws:cdk:asset');
test.deepEqual(assetMetadata && assetMetadata.data.target, 'a-target');
test.done();
},

'asset.repository.grantPull can be used to grant a principal permissions to use the image'(test: Test) {
// GIVEN
const stack = new Stack();
Expand Down
8 changes: 8 additions & 0 deletions packages/@aws-cdk/aws-ecs/lib/images/asset-image.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ export interface AssetImageProps {
* @default none
*/
readonly buildArgs?: { [key: string]: string };

/**
* Docker target to build to
*
* @default none
*/
readonly target?: string;
}

/**
Expand All @@ -32,6 +39,7 @@ export class AssetImage extends ContainerImage {
const asset = new DockerImageAsset(scope, 'AssetImage', {
directory: this.directory,
buildArgs: this.props.buildArgs,
target: this.props.target,
});
asset.repository.grantPull(containerDefinition.taskDefinition.obtainExecutionRole());

Expand Down
7 changes: 7 additions & 0 deletions packages/@aws-cdk/cx-api/lib/assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,13 @@ export interface ContainerImageAssetMetadataEntry extends BaseAssetMetadataEntry
* @default no build args are passed
*/
readonly buildArgs?: { [key: string]: string };

/**
* Docker target to build to
*
* @default no build target
*/
readonly target?: string;
}

export type AssetMetadataEntry = FileAssetMetadataEntry | ContainerImageAssetMetadataEntry;
5 changes: 5 additions & 0 deletions packages/aws-cdk/lib/docker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,18 @@ export async function prepareContainerAsset(assemblyDir: string,
}

const buildArgs = ([] as string[]).concat(...Object.entries(asset.buildArgs || {}).map(([k, v]) => ['--build-arg', `${k}=${v}`]));

const baseCommand = [
'docker', 'build',
...buildArgs,
'--tag', latest,
contextPath
];

if (asset.target) {
baseCommand.push('--target', asset.target);
}

const command = ci
? [...baseCommand, '--cache-from', latest] // This does not fail if latest is not available
: baseCommand;
Expand Down
46 changes: 46 additions & 0 deletions packages/aws-cdk/test/test.docker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,52 @@ export = {
test.done();
},

async 'passes the correct target 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',
sourceHash: '1234567890abcdef',
repositoryName: 'some-name',
buildArgs: {
a: 'b',
c: 'd'
},
target: 'a-target',
};

try {
await prepareContainerAsset('.', asset, toolkit, false, false);
} catch (e) {
if (!/STOPTEST/.test(e.toString())) { throw e; }
}

// THEN
const command = ['docker', 'build', '--build-arg', 'a=b', '--build-arg', 'c=d', '--tag', `uri:latest`, '/foo', '--target', 'a-target'];
test.ok(shellStub.calledWith(command));

prepareEcrRepositoryStub.restore();
shellStub.restore();
test.done();
},

async 'passes the correct args to docker build'(test: Test) {
// GIVEN
const toolkit = new ToolkitInfo({
Expand Down

0 comments on commit 91cda9d

Please sign in to comment.