From 417fa6dc3c5def6780ca899b769713d7c9f42006 Mon Sep 17 00:00:00 2001 From: Sander Date: Sun, 12 May 2019 16:14:48 +0200 Subject: [PATCH 1/7] feat(codebuild): add support for local cache modes --- .../@aws-cdk/aws-codebuild/lib/project.ts | 23 ++++++- .../aws-codebuild/test/test.project.ts | 69 +++++++++++++++++++ 2 files changed, 89 insertions(+), 3 deletions(-) diff --git a/packages/@aws-cdk/aws-codebuild/lib/project.ts b/packages/@aws-cdk/aws-codebuild/lib/project.ts index 1707887f6b995..5658c59f80f19 100644 --- a/packages/@aws-cdk/aws-codebuild/lib/project.ts +++ b/packages/@aws-cdk/aws-codebuild/lib/project.ts @@ -392,17 +392,23 @@ export interface CommonProjectProps { readonly role?: iam.IRole; /** - * Encryption key to use to read and write artifacts + * Encryption key to use to read and write artifacts. * If not specified, a role will be created. */ readonly encryptionKey?: kms.IEncryptionKey; /** - * Bucket to store cached source artifacts - * If not specified, source artifacts will not be cached. + * Bucket to store cached source artifacts. + * If cacheBucket is specified, cacheModes must not be specified */ readonly cacheBucket?: s3.IBucket; + /** + * Local cache(s) to use. + * If cacheModes is specified, cacheBucket must not be specified + */ + readonly cacheModes?: ProjectCacheModes[]; + /** * Subdirectory to store cached artifacts */ @@ -613,6 +619,10 @@ export class Project extends ProjectBase { throw new Error('To use buildScriptAssetEntrypoint, supply buildScriptAsset as well.'); } + if (props.cacheBucket && props.cacheModes) { + throw new Error('At most one of props.cacheBucket or props.cacheMode is allowed.'); + } + this.role = props.role || new iam.Role(this, 'Role', { assumedBy: new iam.ServicePrincipal('codebuild.amazonaws.com') }); @@ -629,6 +639,13 @@ export class Project extends ProjectBase { props.cacheBucket.grantReadWrite(this.role); } + if (props.cacheModes) { + cache = { + type: 'LOCAL', + modes: props.cacheModes, + }; + } + this.buildImage = (props.environment && props.environment.buildImage) || LinuxBuildImage.STANDARD_1_0; // let source "bind" to the project. this usually involves granting permissions diff --git a/packages/@aws-cdk/aws-codebuild/test/test.project.ts b/packages/@aws-cdk/aws-codebuild/test/test.project.ts index 520d6aaf0a192..ee2b62d660130 100644 --- a/packages/@aws-cdk/aws-codebuild/test/test.project.ts +++ b/packages/@aws-cdk/aws-codebuild/test/test.project.ts @@ -1,8 +1,10 @@ import { expect, haveResource, haveResourceLike } from '@aws-cdk/assert'; import assets = require('@aws-cdk/assets'); +import { Bucket } from '@aws-cdk/aws-s3'; import cdk = require('@aws-cdk/cdk'); import { Test } from 'nodeunit'; import codebuild = require('../lib'); +import { ProjectCacheModes } from '../lib'; // tslint:disable:object-literal-key-quotes @@ -161,4 +163,71 @@ export = { test.done(); }, + + 'project with s3 cache bucket'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + new codebuild.Project(stack, 'Project', { + source: new codebuild.CodePipelineSource(), + cacheBucket: new Bucket(stack, 'Bucket') + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::CodeBuild::Project', { + Cache: { + Type: "S3" + } + })); + + test.done(); + }, + + 'project with local cache modes'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + new codebuild.Project(stack, 'Project', { + source: new codebuild.CodePipelineSource(), + cacheModes: [ + ProjectCacheModes.LocalCustomCache, + ProjectCacheModes.LocalDockerLayerCache, + ProjectCacheModes.LocalSourceCache, + ] + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::CodeBuild::Project', { + Cache: { + Modes: [ + "LOCAL_CUSTOM_CACHE", + "LOCAL_DOCKER_LAYER_CACHE", + "LOCAL_SOURCE_CACHE" + ] + }, + })); + + test.done(); + }, + + 'project with both s3 cache mode and local cache mode is not allowed'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN / THEN + test.throws(() => { + new codebuild.Project(stack, 'Project', { + source: new codebuild.CodePipelineSource(), + cacheBucket: new Bucket(stack, 'Bucket'), + cacheModes: [ + ProjectCacheModes.LocalCustomCache, + ] + }); + + }, /At most one of props.cacheBucket or props.cacheMode is allowed\./); + + test.done(); + }, }; From 056e24a34ebc0188b3d8c2a13c7fb1a096212213 Mon Sep 17 00:00:00 2001 From: Sander Date: Mon, 13 May 2019 13:46:50 +0200 Subject: [PATCH 2/7] Implement requested changes --- packages/@aws-cdk/aws-codebuild/README.md | 32 +++++++++++++++++++ .../aws-codebuild/test/test.project.ts | 8 ++--- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/packages/@aws-cdk/aws-codebuild/README.md b/packages/@aws-cdk/aws-codebuild/README.md index 5e3602d611718..6f1abd651e91f 100644 --- a/packages/@aws-cdk/aws-codebuild/README.md +++ b/packages/@aws-cdk/aws-codebuild/README.md @@ -109,6 +109,38 @@ aws codebuild import-source-credentials --server-type GITHUB --auth-type PERSONA This source type can be used to build code from a BitBucket repository. +## Caching + +You can save time when your project builds by using a cache. A cache can store reusable pieces of your build environment and use them across multiple builds. Your build project can use one of two types of caching: Amazon S3 or local. In general, S3 caching is a good option for small and intermediate build artifacts that are more expensive to build than to download. Local caching is a good option for large intermediate build artifacts because the cache is immediately available on the build host. + +### S3 Caching + +With S3 caching, the cache is stored in an S3 bucket which is available from multiple hosts. + +```typescript +new codebuild.Project(stack, 'Project', { + source: new codebuild.CodePipelineSource(), + cacheBucket: new Bucket(stack, 'Bucket') +}); +``` + +### Local Caching + +With local caching, the cache is stored on the codebuild instance itself. Three different cache modes are supported: + +* `ProjectCacheModes.SourceCache` caches Git metadata for primary and secondary sources. +* `ProjectCacheModes.DockerLayerCache` caches existing Docker layers. +* `ProjectCacheModes.CustomCache` caches directories you specify in the buildspec file. + +```typescript +new codebuild.Project(stack, 'Project', { + source: new codebuild.CodePipelineSource(), + cacheModes: [ + ProjectCacheModes.DockerLayerCache, + ] +}); +``` + ## Environment By default, projects use a small instance with an Ubuntu 18.04 image. You diff --git a/packages/@aws-cdk/aws-codebuild/test/test.project.ts b/packages/@aws-cdk/aws-codebuild/test/test.project.ts index ee2b62d660130..27eeca2709104 100644 --- a/packages/@aws-cdk/aws-codebuild/test/test.project.ts +++ b/packages/@aws-cdk/aws-codebuild/test/test.project.ts @@ -192,9 +192,9 @@ export = { new codebuild.Project(stack, 'Project', { source: new codebuild.CodePipelineSource(), cacheModes: [ - ProjectCacheModes.LocalCustomCache, - ProjectCacheModes.LocalDockerLayerCache, - ProjectCacheModes.LocalSourceCache, + ProjectCacheModes.CustomCache, + ProjectCacheModes.DockerLayerCache, + ProjectCacheModes.SourceCache, ] }); @@ -222,7 +222,7 @@ export = { source: new codebuild.CodePipelineSource(), cacheBucket: new Bucket(stack, 'Bucket'), cacheModes: [ - ProjectCacheModes.LocalCustomCache, + ProjectCacheModes.CustomCache, ] }); From a75f0e8c9284aef920480c1ab53bf3b5a2ce1e0b Mon Sep 17 00:00:00 2001 From: Sander Date: Tue, 14 May 2019 11:51:34 +0200 Subject: [PATCH 3/7] add additional documentation regarding local caching --- packages/@aws-cdk/aws-codebuild/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-codebuild/README.md b/packages/@aws-cdk/aws-codebuild/README.md index 6f1abd651e91f..4246a71ce9505 100644 --- a/packages/@aws-cdk/aws-codebuild/README.md +++ b/packages/@aws-cdk/aws-codebuild/README.md @@ -126,7 +126,7 @@ new codebuild.Project(stack, 'Project', { ### Local Caching -With local caching, the cache is stored on the codebuild instance itself. Three different cache modes are supported: +With local caching, the cache is stored on the codebuild instance itself. CodeBuild cannot guarantee a reuse of instance. For example, when a build starts and caches files locally, if two subsequent builds start at the same time afterwards only one of those builds would get the cache. Three different cache modes are supported: * `ProjectCacheModes.SourceCache` caches Git metadata for primary and secondary sources. * `ProjectCacheModes.DockerLayerCache` caches existing Docker layers. From 5c65d48fc63c415ade9f6d8510b44b6108b949bc Mon Sep 17 00:00:00 2001 From: Sander Date: Tue, 14 May 2019 14:04:06 +0200 Subject: [PATCH 4/7] refactor codebuild caching strategy to use DI --- packages/@aws-cdk/aws-codebuild/README.md | 12 ++- packages/@aws-cdk/aws-codebuild/lib/cache.ts | 73 +++++++++++++++++++ .../@aws-cdk/aws-codebuild/lib/project.ts | 47 +++--------- .../aws-codebuild/test/test.project.ts | 28 +------ 4 files changed, 91 insertions(+), 69 deletions(-) create mode 100644 packages/@aws-cdk/aws-codebuild/lib/cache.ts diff --git a/packages/@aws-cdk/aws-codebuild/README.md b/packages/@aws-cdk/aws-codebuild/README.md index 4246a71ce9505..144cdbb9c4f99 100644 --- a/packages/@aws-cdk/aws-codebuild/README.md +++ b/packages/@aws-cdk/aws-codebuild/README.md @@ -120,7 +120,7 @@ With S3 caching, the cache is stored in an S3 bucket which is available from mul ```typescript new codebuild.Project(stack, 'Project', { source: new codebuild.CodePipelineSource(), - cacheBucket: new Bucket(stack, 'Bucket') + cache: Cache.bucket(new Bucket(stack, 'Bucket')) }); ``` @@ -128,16 +128,14 @@ new codebuild.Project(stack, 'Project', { With local caching, the cache is stored on the codebuild instance itself. CodeBuild cannot guarantee a reuse of instance. For example, when a build starts and caches files locally, if two subsequent builds start at the same time afterwards only one of those builds would get the cache. Three different cache modes are supported: -* `ProjectCacheModes.SourceCache` caches Git metadata for primary and secondary sources. -* `ProjectCacheModes.DockerLayerCache` caches existing Docker layers. -* `ProjectCacheModes.CustomCache` caches directories you specify in the buildspec file. +* `LocalCacheMode.SourceCache` caches Git metadata for primary and secondary sources. +* `LocalCacheMode.DockerLayerCache` caches existing Docker layers. +* `LocalCacheMode.CustomCache` caches directories you specify in the buildspec file. ```typescript new codebuild.Project(stack, 'Project', { source: new codebuild.CodePipelineSource(), - cacheModes: [ - ProjectCacheModes.DockerLayerCache, - ] + cache: Cache.local(LocalCacheMode.DockerLayerCache) }); ``` diff --git a/packages/@aws-cdk/aws-codebuild/lib/cache.ts b/packages/@aws-cdk/aws-codebuild/lib/cache.ts new file mode 100644 index 0000000000000..e58528fdaa934 --- /dev/null +++ b/packages/@aws-cdk/aws-codebuild/lib/cache.ts @@ -0,0 +1,73 @@ +import { IBucket } from "@aws-cdk/aws-s3"; +import { Aws, Fn } from "@aws-cdk/cdk"; +import { CfnProject } from "./codebuild.generated"; +import { IProject } from "./project"; + +/** + * Local cache modes to enable for the CodeBuild Project + */ +export enum LocalCacheMode { + SourceCache = 'LOCAL_SOURCE_CACHE', + DockerLayerCache = 'LOCAL_DOCKER_LAYER_CACHE', + CustomCache = 'LOCAL_CUSTOM_CACHE', +} + +/** + * If and what strategy to use for build caching + */ +export enum CacheType { + None = 'NO_CACHE', + S3 = 'S3', + Local = 'LOCAL', +} + +/** + * Cache options for CodeBuild Project + * @see https://docs.aws.amazon.com/codebuild/latest/userguide/build-caching.html + */ +export abstract class Cache { + public static none(): Cache { + return { _toCloudFormation: () => undefined, _bind: () => { return; } }; + } + + /** + * Create a local caching strategy + * @param modes the mode(s) to enable for local caching + */ + public static local(...modes: LocalCacheMode[]): Cache { + return { + _toCloudFormation: () => ({ + type: CacheType.Local, + modes + }), + _bind: () => { + return; + } + }; + } + + /** + * Create an S3 caching strategy + * @param bucket the S3 bucket to use for caching + * @param prefix the optional prefix to use to store the cache in the bucket + */ + public static bucket(bucket: IBucket, prefix?: string): Cache { + return { + _toCloudFormation: () => ({ + type: CacheType.S3, + location: Fn.join('/', [bucket.bucketName, prefix || Aws.noValue]) + }), + _bind: (project) => { + if (project.role) { + bucket.grantReadWrite(project.role); + } + } + }; + } + + /** @internal */ + public abstract _toCloudFormation(): CfnProject.ProjectCacheProperty | undefined; + + /** @internal */ + public abstract _bind(project: IProject): void; +} diff --git a/packages/@aws-cdk/aws-codebuild/lib/project.ts b/packages/@aws-cdk/aws-codebuild/lib/project.ts index 5658c59f80f19..bf32f269a48dc 100644 --- a/packages/@aws-cdk/aws-codebuild/lib/project.ts +++ b/packages/@aws-cdk/aws-codebuild/lib/project.ts @@ -11,6 +11,7 @@ import { Aws, CfnOutput, Construct, Fn, IResource, Resource, Token } from '@aws- import { BuildArtifacts, CodePipelineBuildArtifacts, NoBuildArtifacts } from './artifacts'; import { CfnProject } from './codebuild.generated'; import { BuildSource, NoSource, SourceType } from './source'; +import { Cache } from './cache'; const CODEPIPELINE_TYPE = 'CODEPIPELINE'; const S3_BUCKET_ENV = 'SCRIPT_S3_BUCKET'; @@ -398,21 +399,10 @@ export interface CommonProjectProps { readonly encryptionKey?: kms.IEncryptionKey; /** - * Bucket to store cached source artifacts. - * If cacheBucket is specified, cacheModes must not be specified + * Caching strategy to use. + * @default Cache.none */ - readonly cacheBucket?: s3.IBucket; - - /** - * Local cache(s) to use. - * If cacheModes is specified, cacheBucket must not be specified - */ - readonly cacheModes?: ProjectCacheModes[]; - - /** - * Subdirectory to store cached artifacts - */ - readonly cacheDir?: string; + readonly cache?: Cache; /** * Build environment to use for the build. @@ -619,33 +609,11 @@ export class Project extends ProjectBase { throw new Error('To use buildScriptAssetEntrypoint, supply buildScriptAsset as well.'); } - if (props.cacheBucket && props.cacheModes) { - throw new Error('At most one of props.cacheBucket or props.cacheMode is allowed.'); - } - this.role = props.role || new iam.Role(this, 'Role', { assumedBy: new iam.ServicePrincipal('codebuild.amazonaws.com') }); this.grantPrincipal = this.role; - let cache: CfnProject.ProjectCacheProperty | undefined; - if (props.cacheBucket) { - const cacheDir = props.cacheDir != null ? props.cacheDir : Aws.noValue; - cache = { - type: 'S3', - location: Fn.join('/', [props.cacheBucket.bucketName, cacheDir]), - }; - - props.cacheBucket.grantReadWrite(this.role); - } - - if (props.cacheModes) { - cache = { - type: 'LOCAL', - modes: props.cacheModes, - }; - } - this.buildImage = (props.environment && props.environment.buildImage) || LinuxBuildImage.STANDARD_1_0; // let source "bind" to the project. this usually involves granting permissions @@ -656,6 +624,11 @@ export class Project extends ProjectBase { const artifacts = this.parseArtifacts(props); artifacts._bind(this); + // give the caching strategy the option to grant permissions to any required resources + if (props.cache) { + props.cache._bind(this); + } + // Inject download commands for asset if requested const environmentVariables = props.environmentVariables || {}; const buildSpec = props.buildSpec || {}; @@ -713,7 +686,7 @@ export class Project extends ProjectBase { environment: this.renderEnvironment(props.environment, environmentVariables), encryptionKey: props.encryptionKey && props.encryptionKey.keyArn, badgeEnabled: props.badge, - cache, + cache: props.cache && props.cache._toCloudFormation() || Cache.none()._toCloudFormation(), name: props.projectName, timeoutInMinutes: props.timeout, secondarySources: new Token(() => this.renderSecondarySources()), diff --git a/packages/@aws-cdk/aws-codebuild/test/test.project.ts b/packages/@aws-cdk/aws-codebuild/test/test.project.ts index 27eeca2709104..d39583a5e7ac8 100644 --- a/packages/@aws-cdk/aws-codebuild/test/test.project.ts +++ b/packages/@aws-cdk/aws-codebuild/test/test.project.ts @@ -5,6 +5,7 @@ import cdk = require('@aws-cdk/cdk'); import { Test } from 'nodeunit'; import codebuild = require('../lib'); import { ProjectCacheModes } from '../lib'; +import { Cache, LocalCacheMode } from '../lib/cache'; // tslint:disable:object-literal-key-quotes @@ -171,7 +172,7 @@ export = { // WHEN new codebuild.Project(stack, 'Project', { source: new codebuild.CodePipelineSource(), - cacheBucket: new Bucket(stack, 'Bucket') + cache: Cache.bucket(new Bucket(stack, 'Bucket')) }); // THEN @@ -191,11 +192,7 @@ export = { // WHEN new codebuild.Project(stack, 'Project', { source: new codebuild.CodePipelineSource(), - cacheModes: [ - ProjectCacheModes.CustomCache, - ProjectCacheModes.DockerLayerCache, - ProjectCacheModes.SourceCache, - ] + cache: Cache.local(LocalCacheMode.CustomCache, LocalCacheMode.DockerLayerCache, LocalCacheMode.SourceCache) }); // THEN @@ -211,23 +208,4 @@ export = { test.done(); }, - - 'project with both s3 cache mode and local cache mode is not allowed'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - - // WHEN / THEN - test.throws(() => { - new codebuild.Project(stack, 'Project', { - source: new codebuild.CodePipelineSource(), - cacheBucket: new Bucket(stack, 'Bucket'), - cacheModes: [ - ProjectCacheModes.CustomCache, - ] - }); - - }, /At most one of props.cacheBucket or props.cacheMode is allowed\./); - - test.done(); - }, }; From f2fc1be0a933fa5842ce66bbd80433c4e798312f Mon Sep 17 00:00:00 2001 From: Sander Date: Tue, 14 May 2019 17:48:12 +0200 Subject: [PATCH 5/7] implement requested changes --- packages/@aws-cdk/aws-codebuild/README.md | 14 +-- packages/@aws-cdk/aws-codebuild/lib/cache.ts | 114 ++++++++++-------- packages/@aws-cdk/aws-codebuild/lib/index.ts | 1 + .../@aws-cdk/aws-codebuild/lib/project.ts | 9 +- .../aws-codebuild/test/integ.caching.ts | 3 +- .../aws-codebuild/test/test.project.ts | 42 ++++++- 6 files changed, 113 insertions(+), 70 deletions(-) diff --git a/packages/@aws-cdk/aws-codebuild/README.md b/packages/@aws-cdk/aws-codebuild/README.md index 144cdbb9c4f99..c0601ce61df59 100644 --- a/packages/@aws-cdk/aws-codebuild/README.md +++ b/packages/@aws-cdk/aws-codebuild/README.md @@ -118,9 +118,9 @@ You can save time when your project builds by using a cache. A cache can store r With S3 caching, the cache is stored in an S3 bucket which is available from multiple hosts. ```typescript -new codebuild.Project(stack, 'Project', { +new codebuild.Project(this, 'Project', { source: new codebuild.CodePipelineSource(), - cache: Cache.bucket(new Bucket(stack, 'Bucket')) + cache: Cache.bucket(new Bucket(this, 'Bucket')) }); ``` @@ -128,14 +128,14 @@ new codebuild.Project(stack, 'Project', { With local caching, the cache is stored on the codebuild instance itself. CodeBuild cannot guarantee a reuse of instance. For example, when a build starts and caches files locally, if two subsequent builds start at the same time afterwards only one of those builds would get the cache. Three different cache modes are supported: -* `LocalCacheMode.SourceCache` caches Git metadata for primary and secondary sources. -* `LocalCacheMode.DockerLayerCache` caches existing Docker layers. -* `LocalCacheMode.CustomCache` caches directories you specify in the buildspec file. +* `LocalCacheMode.Source` caches Git metadata for primary and secondary sources. +* `LocalCacheMode.DockerLayer` caches existing Docker layers. +* `LocalCacheMode.Custom` caches directories you specify in the buildspec file. ```typescript -new codebuild.Project(stack, 'Project', { +new codebuild.Project(this, 'Project', { source: new codebuild.CodePipelineSource(), - cache: Cache.local(LocalCacheMode.DockerLayerCache) + cache: Cache.local(LocalCacheMode.DockerLayer) }); ``` diff --git a/packages/@aws-cdk/aws-codebuild/lib/cache.ts b/packages/@aws-cdk/aws-codebuild/lib/cache.ts index e58528fdaa934..aac93ec29ebe9 100644 --- a/packages/@aws-cdk/aws-codebuild/lib/cache.ts +++ b/packages/@aws-cdk/aws-codebuild/lib/cache.ts @@ -3,71 +3,81 @@ import { Aws, Fn } from "@aws-cdk/cdk"; import { CfnProject } from "./codebuild.generated"; import { IProject } from "./project"; +export interface BucketCacheOptions { + /** + * The prefix to use to store the cache in the bucket + */ + readonly prefix?: string; +} + /** * Local cache modes to enable for the CodeBuild Project */ export enum LocalCacheMode { - SourceCache = 'LOCAL_SOURCE_CACHE', - DockerLayerCache = 'LOCAL_DOCKER_LAYER_CACHE', - CustomCache = 'LOCAL_CUSTOM_CACHE', -} + /** + * Caches Git metadata for primary and secondary sources + */ + Source = 'LOCAL_SOURCE_CACHE', -/** - * If and what strategy to use for build caching - */ -export enum CacheType { - None = 'NO_CACHE', - S3 = 'S3', - Local = 'LOCAL', + /** + * Caches existing Docker layers + */ + DockerLayer = 'LOCAL_DOCKER_LAYER_CACHE', + + /** + * Caches directories you specify in the buildspec file + */ + Custom = 'LOCAL_CUSTOM_CACHE', } /** - * Cache options for CodeBuild Project + * Cache options for CodeBuild Project. + * A cache can store reusable pieces of your build environment and use them across multiple builds. * @see https://docs.aws.amazon.com/codebuild/latest/userguide/build-caching.html */ export abstract class Cache { - public static none(): Cache { - return { _toCloudFormation: () => undefined, _bind: () => { return; } }; - } + public static none(): Cache { + return { _toCloudFormation: () => undefined, _bind: () => { return; } }; + } - /** - * Create a local caching strategy - * @param modes the mode(s) to enable for local caching - */ - public static local(...modes: LocalCacheMode[]): Cache { - return { - _toCloudFormation: () => ({ - type: CacheType.Local, - modes - }), - _bind: () => { - return; - } - }; - } + /** + * Create a local caching strategy. + * @param modes the mode(s) to enable for local caching + */ + public static local(...modes: LocalCacheMode[]): Cache { + return { + _toCloudFormation: () => ({ + type: 'LOCAL', + modes + }), + _bind: () => { return; } + }; + } - /** - * Create an S3 caching strategy - * @param bucket the S3 bucket to use for caching - * @param prefix the optional prefix to use to store the cache in the bucket - */ - public static bucket(bucket: IBucket, prefix?: string): Cache { - return { - _toCloudFormation: () => ({ - type: CacheType.S3, - location: Fn.join('/', [bucket.bucketName, prefix || Aws.noValue]) - }), - _bind: (project) => { - if (project.role) { - bucket.grantReadWrite(project.role); - } - } - }; - } + /** + * Create an S3 caching strategy. + * @param bucket the S3 bucket to use for caching + * @param options additional options to pass to the S3 caching + */ + public static bucket(bucket: IBucket, options?: BucketCacheOptions): Cache { + return { + _toCloudFormation: () => ({ + type: 'S3', + location: Fn.join('/', [bucket.bucketName, options && options.prefix || Aws.noValue]) + }), + _bind: (project) => { + bucket.grantReadWrite(project); + } + }; + } - /** @internal */ - public abstract _toCloudFormation(): CfnProject.ProjectCacheProperty | undefined; + /** + * @internal + */ + public abstract _toCloudFormation(): CfnProject.ProjectCacheProperty | undefined; - /** @internal */ - public abstract _bind(project: IProject): void; + /** + * @internal + */ + public abstract _bind(project: IProject): void; } diff --git a/packages/@aws-cdk/aws-codebuild/lib/index.ts b/packages/@aws-cdk/aws-codebuild/lib/index.ts index 7fbe814d26429..2cb640b7cc9a0 100644 --- a/packages/@aws-cdk/aws-codebuild/lib/index.ts +++ b/packages/@aws-cdk/aws-codebuild/lib/index.ts @@ -2,6 +2,7 @@ export * from './pipeline-project'; export * from './project'; export * from './source'; export * from './artifacts'; +export * from './cache'; // AWS::CodeBuild CloudFormation Resources: export * from './codebuild.generated'; diff --git a/packages/@aws-cdk/aws-codebuild/lib/project.ts b/packages/@aws-cdk/aws-codebuild/lib/project.ts index bf32f269a48dc..fdbcc690c1138 100644 --- a/packages/@aws-cdk/aws-codebuild/lib/project.ts +++ b/packages/@aws-cdk/aws-codebuild/lib/project.ts @@ -6,12 +6,11 @@ import ecr = require('@aws-cdk/aws-ecr'); import events = require('@aws-cdk/aws-events'); import iam = require('@aws-cdk/aws-iam'); import kms = require('@aws-cdk/aws-kms'); -import s3 = require('@aws-cdk/aws-s3'); -import { Aws, CfnOutput, Construct, Fn, IResource, Resource, Token } from '@aws-cdk/cdk'; +import { Aws, CfnOutput, Construct, IResource, Resource, Token } from '@aws-cdk/cdk'; import { BuildArtifacts, CodePipelineBuildArtifacts, NoBuildArtifacts } from './artifacts'; +import { Cache } from './cache'; import { CfnProject } from './codebuild.generated'; import { BuildSource, NoSource, SourceType } from './source'; -import { Cache } from './cache'; const CODEPIPELINE_TYPE = 'CODEPIPELINE'; const S3_BUCKET_ENV = 'SCRIPT_S3_BUCKET'; @@ -629,6 +628,8 @@ export class Project extends ProjectBase { props.cache._bind(this); } + const cache = props.cache || Cache.none(); + // Inject download commands for asset if requested const environmentVariables = props.environmentVariables || {}; const buildSpec = props.buildSpec || {}; @@ -686,7 +687,7 @@ export class Project extends ProjectBase { environment: this.renderEnvironment(props.environment, environmentVariables), encryptionKey: props.encryptionKey && props.encryptionKey.keyArn, badgeEnabled: props.badge, - cache: props.cache && props.cache._toCloudFormation() || Cache.none()._toCloudFormation(), + cache: cache._toCloudFormation(), name: props.projectName, timeoutInMinutes: props.timeout, secondarySources: new Token(() => this.renderSecondarySources()), diff --git a/packages/@aws-cdk/aws-codebuild/test/integ.caching.ts b/packages/@aws-cdk/aws-codebuild/test/integ.caching.ts index fc1a34c0b1a8d..a381a88f36eb8 100644 --- a/packages/@aws-cdk/aws-codebuild/test/integ.caching.ts +++ b/packages/@aws-cdk/aws-codebuild/test/integ.caching.ts @@ -2,6 +2,7 @@ import s3 = require('@aws-cdk/aws-s3'); import cdk = require('@aws-cdk/cdk'); import codebuild = require('../lib'); +import { Cache } from '../lib/cache'; const app = new cdk.App(); @@ -12,7 +13,7 @@ const bucket = new s3.Bucket(stack, 'CacheBucket', { }); new codebuild.Project(stack, 'MyProject', { - cacheBucket: bucket, + cache: Cache.bucket(bucket), buildSpec: { build: { commands: ['echo Hello'] diff --git a/packages/@aws-cdk/aws-codebuild/test/test.project.ts b/packages/@aws-cdk/aws-codebuild/test/test.project.ts index d39583a5e7ac8..27715110cce8f 100644 --- a/packages/@aws-cdk/aws-codebuild/test/test.project.ts +++ b/packages/@aws-cdk/aws-codebuild/test/test.project.ts @@ -1,10 +1,9 @@ -import { expect, haveResource, haveResourceLike } from '@aws-cdk/assert'; +import { expect, haveResource, haveResourceLike, not } from '@aws-cdk/assert'; import assets = require('@aws-cdk/assets'); import { Bucket } from '@aws-cdk/aws-s3'; import cdk = require('@aws-cdk/cdk'); import { Test } from 'nodeunit'; import codebuild = require('../lib'); -import { ProjectCacheModes } from '../lib'; import { Cache, LocalCacheMode } from '../lib/cache'; // tslint:disable:object-literal-key-quotes @@ -172,14 +171,27 @@ export = { // WHEN new codebuild.Project(stack, 'Project', { source: new codebuild.CodePipelineSource(), - cache: Cache.bucket(new Bucket(stack, 'Bucket')) + cache: Cache.bucket(new Bucket(stack, 'Bucket'), { + prefix: "cache-prefix" + }) }); // THEN expect(stack).to(haveResourceLike('AWS::CodeBuild::Project', { Cache: { - Type: "S3" - } + Type: "S3", + Location: { + "Fn::Join": [ + "/", + [ + { + "Ref": "Bucket83908E77" + }, + "cache-prefix" + ] + ] + } + }, })); test.done(); @@ -192,12 +204,13 @@ export = { // WHEN new codebuild.Project(stack, 'Project', { source: new codebuild.CodePipelineSource(), - cache: Cache.local(LocalCacheMode.CustomCache, LocalCacheMode.DockerLayerCache, LocalCacheMode.SourceCache) + cache: Cache.local(LocalCacheMode.Custom, LocalCacheMode.DockerLayer, LocalCacheMode.Source) }); // THEN expect(stack).to(haveResourceLike('AWS::CodeBuild::Project', { Cache: { + Type: "LOCAL", Modes: [ "LOCAL_CUSTOM_CACHE", "LOCAL_DOCKER_LAYER_CACHE", @@ -208,4 +221,21 @@ export = { test.done(); }, + + 'project by default has no cache modes'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + new codebuild.Project(stack, 'Project', { + source: new codebuild.CodePipelineSource() + }); + + // THEN + expect(stack).to(not(haveResourceLike('AWS::CodeBuild::Project', { + Cache: {} + }))); + + test.done(); + }, }; From c92a86c082321204562646588202f18586172580 Mon Sep 17 00:00:00 2001 From: Sander Date: Wed, 15 May 2019 09:30:23 +0200 Subject: [PATCH 6/7] small refactor --- packages/@aws-cdk/aws-codebuild/lib/project.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/@aws-cdk/aws-codebuild/lib/project.ts b/packages/@aws-cdk/aws-codebuild/lib/project.ts index fdbcc690c1138..54e967296d2dd 100644 --- a/packages/@aws-cdk/aws-codebuild/lib/project.ts +++ b/packages/@aws-cdk/aws-codebuild/lib/project.ts @@ -623,13 +623,11 @@ export class Project extends ProjectBase { const artifacts = this.parseArtifacts(props); artifacts._bind(this); - // give the caching strategy the option to grant permissions to any required resources - if (props.cache) { - props.cache._bind(this); - } - const cache = props.cache || Cache.none(); + // give the caching strategy the option to grant permissions to any required resources + cache._bind(this); + // Inject download commands for asset if requested const environmentVariables = props.environmentVariables || {}; const buildSpec = props.buildSpec || {}; From 1923a7f038510dca89a1777c8bd830897350f63f Mon Sep 17 00:00:00 2001 From: Sander Date: Wed, 15 May 2019 09:37:49 +0200 Subject: [PATCH 7/7] update documentation --- packages/@aws-cdk/aws-codebuild/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/aws-codebuild/README.md b/packages/@aws-cdk/aws-codebuild/README.md index c0601ce61df59..3f1a07a2c3830 100644 --- a/packages/@aws-cdk/aws-codebuild/README.md +++ b/packages/@aws-cdk/aws-codebuild/README.md @@ -120,7 +120,7 @@ With S3 caching, the cache is stored in an S3 bucket which is available from mul ```typescript new codebuild.Project(this, 'Project', { source: new codebuild.CodePipelineSource(), - cache: Cache.bucket(new Bucket(this, 'Bucket')) + cache: codebuild.Cache.bucket(new Bucket(this, 'Bucket')) }); ``` @@ -135,7 +135,7 @@ With local caching, the cache is stored on the codebuild instance itself. CodeBu ```typescript new codebuild.Project(this, 'Project', { source: new codebuild.CodePipelineSource(), - cache: Cache.local(LocalCacheMode.DockerLayer) + cache: codebuild.Cache.local(LocalCacheMode.DockerLayer, LocalCacheMode.Custom) }); ```