From d1b5f7dbe549b9a05525afa03eb750f150627690 Mon Sep 17 00:00:00 2001 From: Adam Ruka Date: Mon, 3 Jun 2019 14:35:25 -0700 Subject: [PATCH] refactor(codebuild): move sources and artifacts into their own separate packages. Fixes #2037 --- .../aws-codebuild-artifacts/.gitignore | 16 + .../aws-codebuild-artifacts/.npmignore | 18 + .../@aws-cdk/aws-codebuild-artifacts/LICENSE | 201 ++++ .../@aws-cdk/aws-codebuild-artifacts/NOTICE | 2 + .../aws-codebuild-artifacts/README.md | 4 + .../aws-codebuild-artifacts/lib/artifacts.ts | 84 ++ .../aws-codebuild-artifacts/lib/index.ts | 1 + .../aws-codebuild-artifacts/package.json | 86 ++ .../test/fake-codepipeline-source.ts | 12 + .../test/test.artifacts.ts | 113 +++ .../@aws-cdk/aws-codebuild-sources/.gitignore | 16 + .../@aws-cdk/aws-codebuild-sources/.npmignore | 18 + .../@aws-cdk/aws-codebuild-sources/LICENSE | 201 ++++ .../@aws-cdk/aws-codebuild-sources/NOTICE | 2 + .../@aws-cdk/aws-codebuild-sources/README.md | 4 + .../aws-codebuild-sources/lib/event-action.ts | 25 + .../lib/filter-group-common.ts | 7 + .../aws-codebuild-sources/lib/filter-group.ts | 245 +++++ .../aws-codebuild-sources/lib/index.ts | 3 + .../aws-codebuild-sources/lib/source.ts | 329 ++++++ .../aws-codebuild-sources/package.json | 89 ++ .../test/integ.github.expected.json | 0 .../test/integ.github.ts | 5 +- .../test/integ.project-bucket.expected.json | 0 .../test/integ.project-bucket.ts | 5 +- ...-secondary-sources-artifacts.expected.json | 0 ...teg.project-secondary-sources-artifacts.ts | 8 +- .../test/test.artifacts.ts | 96 ++ .../test/test.sources.ts | 796 +++++++++++++++ packages/@aws-cdk/aws-codebuild/README.md | 83 +- .../@aws-cdk/aws-codebuild/lib/artifacts.ts | 83 -- .../aws-codebuild/lib/pipeline-project.ts | 16 +- .../@aws-cdk/aws-codebuild/lib/project.ts | 33 +- packages/@aws-cdk/aws-codebuild/lib/source.ts | 631 +----------- packages/@aws-cdk/aws-codebuild/package.json | 6 - .../aws-codebuild/test/fake-source.ts | 22 + .../aws-codebuild/test/test.codebuild.ts | 941 +----------------- .../aws-codebuild/test/test.project.ts | 87 +- .../aws-codepipeline-actions/package.json | 4 +- 39 files changed, 2550 insertions(+), 1742 deletions(-) create mode 100644 packages/@aws-cdk/aws-codebuild-artifacts/.gitignore create mode 100644 packages/@aws-cdk/aws-codebuild-artifacts/.npmignore create mode 100644 packages/@aws-cdk/aws-codebuild-artifacts/LICENSE create mode 100644 packages/@aws-cdk/aws-codebuild-artifacts/NOTICE create mode 100644 packages/@aws-cdk/aws-codebuild-artifacts/README.md create mode 100644 packages/@aws-cdk/aws-codebuild-artifacts/lib/artifacts.ts create mode 100644 packages/@aws-cdk/aws-codebuild-artifacts/lib/index.ts create mode 100644 packages/@aws-cdk/aws-codebuild-artifacts/package.json create mode 100644 packages/@aws-cdk/aws-codebuild-artifacts/test/fake-codepipeline-source.ts create mode 100644 packages/@aws-cdk/aws-codebuild-artifacts/test/test.artifacts.ts create mode 100644 packages/@aws-cdk/aws-codebuild-sources/.gitignore create mode 100644 packages/@aws-cdk/aws-codebuild-sources/.npmignore create mode 100644 packages/@aws-cdk/aws-codebuild-sources/LICENSE create mode 100644 packages/@aws-cdk/aws-codebuild-sources/NOTICE create mode 100644 packages/@aws-cdk/aws-codebuild-sources/README.md create mode 100644 packages/@aws-cdk/aws-codebuild-sources/lib/event-action.ts create mode 100644 packages/@aws-cdk/aws-codebuild-sources/lib/filter-group-common.ts create mode 100644 packages/@aws-cdk/aws-codebuild-sources/lib/filter-group.ts create mode 100644 packages/@aws-cdk/aws-codebuild-sources/lib/index.ts create mode 100644 packages/@aws-cdk/aws-codebuild-sources/lib/source.ts create mode 100644 packages/@aws-cdk/aws-codebuild-sources/package.json rename packages/@aws-cdk/{aws-codebuild => aws-codebuild-sources}/test/integ.github.expected.json (100%) rename packages/@aws-cdk/{aws-codebuild => aws-codebuild-sources}/test/integ.github.ts (73%) rename packages/@aws-cdk/{aws-codebuild => aws-codebuild-sources}/test/integ.project-bucket.expected.json (100%) rename packages/@aws-cdk/{aws-codebuild => aws-codebuild-sources}/test/integ.project-bucket.ts (77%) rename packages/@aws-cdk/{aws-codebuild => aws-codebuild-sources}/test/integ.project-secondary-sources-artifacts.expected.json (100%) rename packages/@aws-cdk/{aws-codebuild => aws-codebuild-sources}/test/integ.project-secondary-sources-artifacts.ts (72%) create mode 100644 packages/@aws-cdk/aws-codebuild-sources/test/test.artifacts.ts create mode 100644 packages/@aws-cdk/aws-codebuild-sources/test/test.sources.ts create mode 100644 packages/@aws-cdk/aws-codebuild/test/fake-source.ts diff --git a/packages/@aws-cdk/aws-codebuild-artifacts/.gitignore b/packages/@aws-cdk/aws-codebuild-artifacts/.gitignore new file mode 100644 index 0000000000000..205e21fe7353b --- /dev/null +++ b/packages/@aws-cdk/aws-codebuild-artifacts/.gitignore @@ -0,0 +1,16 @@ +*.js +tsconfig.json +tslint.json +*.js.map +*.d.ts +*.generated.ts +dist +lib/generated/resources.ts +.jsii + +.LAST_BUILD +.nyc_output +coverage +.nycrc +.LAST_PACKAGE +*.snk \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codebuild-artifacts/.npmignore b/packages/@aws-cdk/aws-codebuild-artifacts/.npmignore new file mode 100644 index 0000000000000..f5a63a96df103 --- /dev/null +++ b/packages/@aws-cdk/aws-codebuild-artifacts/.npmignore @@ -0,0 +1,18 @@ +# Don't include original .ts files when doing `npm pack` +*.ts +!*.d.ts +coverage +.nyc_output +*.tgz + +dist +.LAST_PACKAGE +.LAST_BUILD +!*.js + +# Include .jsii +!.jsii + +*.snk + +*.tsbuildinfo diff --git a/packages/@aws-cdk/aws-codebuild-artifacts/LICENSE b/packages/@aws-cdk/aws-codebuild-artifacts/LICENSE new file mode 100644 index 0000000000000..46c185646b439 --- /dev/null +++ b/packages/@aws-cdk/aws-codebuild-artifacts/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/@aws-cdk/aws-codebuild-artifacts/NOTICE b/packages/@aws-cdk/aws-codebuild-artifacts/NOTICE new file mode 100644 index 0000000000000..8585168af8b7d --- /dev/null +++ b/packages/@aws-cdk/aws-codebuild-artifacts/NOTICE @@ -0,0 +1,2 @@ +AWS Cloud Development Kit (AWS CDK) +Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. diff --git a/packages/@aws-cdk/aws-codebuild-artifacts/README.md b/packages/@aws-cdk/aws-codebuild-artifacts/README.md new file mode 100644 index 0000000000000..648ecdf006ac8 --- /dev/null +++ b/packages/@aws-cdk/aws-codebuild-artifacts/README.md @@ -0,0 +1,4 @@ +## AWS CodeBuild Artifacts + +This package contains classes that can be used as artifacts when creating a CodeBuild Project. +See the documentation of the `@aws-cdk/aws-codebuild` package for instructions on how to use CodeBuild in the CDK. diff --git a/packages/@aws-cdk/aws-codebuild-artifacts/lib/artifacts.ts b/packages/@aws-cdk/aws-codebuild-artifacts/lib/artifacts.ts new file mode 100644 index 0000000000000..7a5260281fa36 --- /dev/null +++ b/packages/@aws-cdk/aws-codebuild-artifacts/lib/artifacts.ts @@ -0,0 +1,84 @@ +import codebuild = require("@aws-cdk/aws-codebuild"); +import s3 = require("@aws-cdk/aws-s3"); + +/** + * CodePipeline Artifact definition for a CodeBuild Project. + * *Note*: this type cannot be used as a secondary artifact, + * and because of that, you're not allowed to specify an identifier for it. + */ +export class CodePipelineBuildArtifacts extends codebuild.BuildArtifacts { + protected readonly type = codebuild.CODEPIPELINE_SOURCE_ARTIFACT_TYPE; + + constructor() { + super({}); + } +} + +/** + * Construction properties for {@link S3BucketBuildArtifacts}. + */ +export interface S3BucketBuildArtifactsProps extends codebuild.BuildArtifactsProps { + /** + * The name of the output bucket. + */ + readonly bucket: s3.IBucket; + + /** + * The path inside of the bucket for the build output .zip file or folder. + * If a value is not specified, then build output will be stored at the root of the + * bucket (or under the directory if `includeBuildId` is set to true). + */ + readonly path?: string; + + /** + * The name of the build output ZIP file or folder inside the bucket. + * + * The full S3 object key will be "//" or + * "/" depending on whether `includeBuildId` is set to true. + */ + readonly name: string; + + /** + * Indicates if the build ID should be included in the path. If this is set to true, + * then the build artifact will be stored in "//". + * + * @default true + */ + readonly includeBuildId?: boolean; + + /** + * If this is true, all build output will be packaged into a single .zip file. + * Otherwise, all files will be uploaded to / + * + * @default true - files will be archived + */ + readonly packageZip?: boolean; +} + +/** + * S3 Artifact definition for a CodeBuild Project. + */ +export class S3BucketBuildArtifacts extends codebuild.BuildArtifacts { + protected readonly type = 'S3'; + + constructor(private readonly props: S3BucketBuildArtifactsProps) { + super(props); + } + + /** + * @internal + */ + public _bind(project: codebuild.Project) { + this.props.bucket.grantReadWrite(project); + } + + protected toArtifactsProperty(): any { + return { + location: this.props.bucket.bucketName, + path: this.props.path, + namespaceType: this.props.includeBuildId === false ? 'NONE' : 'BUILD_ID', + name: this.props.name, + packaging: this.props.packageZip === false ? 'NONE' : 'ZIP', + }; + } +} diff --git a/packages/@aws-cdk/aws-codebuild-artifacts/lib/index.ts b/packages/@aws-cdk/aws-codebuild-artifacts/lib/index.ts new file mode 100644 index 0000000000000..01206c99a99db --- /dev/null +++ b/packages/@aws-cdk/aws-codebuild-artifacts/lib/index.ts @@ -0,0 +1 @@ +export * from './artifacts'; diff --git a/packages/@aws-cdk/aws-codebuild-artifacts/package.json b/packages/@aws-cdk/aws-codebuild-artifacts/package.json new file mode 100644 index 0000000000000..3a4007358f147 --- /dev/null +++ b/packages/@aws-cdk/aws-codebuild-artifacts/package.json @@ -0,0 +1,86 @@ +{ + "name": "@aws-cdk/aws-codebuild-artifacts", + "version": "0.33.0", + "description": "CDK Constructs for AWS CodeBuild Artifacts", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "jsii": { + "outdir": "dist", + "targets": { + "java": { + "package": "software.amazon.awscdk.services.codebuild.artifacts", + "maven": { + "groupId": "software.amazon.awscdk", + "artifactId": "codebuild-artifacts" + } + }, + "dotnet": { + "namespace": "Amazon.CDK.AWS.CodeBuild.Artifacts", + "packageId": "Amazon.CDK.AWS.CodeBuild.Artifacts", + "signAssembly": true, + "assemblyOriginatorKeyFile": "../../key.snk" + }, + "python": { + "distName": "aws-cdk.aws-codebuild-artifacts", + "module": "aws_cdk.aws_codebuild_artifacts" + } + } + }, + "repository": { + "type": "git", + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-codebuild-artifacts" + }, + "scripts": { + "build": "cdk-build", + "watch": "cdk-watch", + "lint": "cdk-lint", + "test": "cdk-test", + "integ": "cdk-integ", + "pkglint": "pkglint -f", + "package": "cdk-package", + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts", + "build+test+package": "npm run build+test && npm run package", + "build+test": "npm run build && npm test" + }, + "nyc": { + "statements": 74, + "lines": 74 + }, + "keywords": [ + "aws", + "cdk", + "constructs", + "codebuild", + "sources" + ], + "author": { + "name": "Amazon Web Services", + "url": "https://aws.amazon.com", + "organization": true + }, + "license": "Apache-2.0", + "devDependencies": { + "@aws-cdk/assert": "^0.33.0", + "cdk-build-tools": "^0.33.0", + "cdk-integ-tools": "^0.33.0", + "pkglint": "^0.33.0" + }, + "dependencies": { + "@aws-cdk/aws-codebuild": "^0.33.0", + "@aws-cdk/aws-iam": "^0.33.0", + "@aws-cdk/aws-s3": "^0.33.0", + "@aws-cdk/cdk": "^0.33.0" + }, + "homepage": "https://github.com/awslabs/aws-cdk", + "peerDependencies": { + "@aws-cdk/aws-codebuild": "^0.33.0", + "@aws-cdk/aws-iam": "^0.33.0", + "@aws-cdk/aws-s3": "^0.33.0", + "@aws-cdk/cdk": "^0.33.0" + }, + "engines": { + "node": ">= 8.10.0" + } +} diff --git a/packages/@aws-cdk/aws-codebuild-artifacts/test/fake-codepipeline-source.ts b/packages/@aws-cdk/aws-codebuild-artifacts/test/fake-codepipeline-source.ts new file mode 100644 index 0000000000000..e0e13ecd6559a --- /dev/null +++ b/packages/@aws-cdk/aws-codebuild-artifacts/test/fake-codepipeline-source.ts @@ -0,0 +1,12 @@ +import codebuild = require('@aws-cdk/aws-codebuild'); + +/** + * Fake CodePipeline source, used only for tests. + */ +export class FakeCodePipelineSource extends codebuild.BuildSource { + public readonly type = codebuild.CODEPIPELINE_SOURCE_ARTIFACT_TYPE; + + constructor() { + super({}); + } +} diff --git a/packages/@aws-cdk/aws-codebuild-artifacts/test/test.artifacts.ts b/packages/@aws-cdk/aws-codebuild-artifacts/test/test.artifacts.ts new file mode 100644 index 0000000000000..63745107cc5db --- /dev/null +++ b/packages/@aws-cdk/aws-codebuild-artifacts/test/test.artifacts.ts @@ -0,0 +1,113 @@ +import { expect, haveResourceLike } from '@aws-cdk/assert'; +import codebuild = require('@aws-cdk/aws-codebuild'); +import s3 = require('@aws-cdk/aws-s3'); +import cdk = require('@aws-cdk/cdk'); +import { Test } from 'nodeunit'; +import cbartifacts = require('../lib'); +import { FakeCodePipelineSource } from './fake-codepipeline-source'; + +// tslint:disable:object-literal-key-quotes + +export = { + 'CodeBuild Artifacts': { + 'S3': { + 'using timeout and path in S3 artifacts sets it correctly'(test: Test) { + const stack = new cdk.Stack(); + const bucket = new s3.Bucket(stack, 'Bucket'); + new codebuild.Project(stack, 'Project', { + buildSpec: { + version: '0.2', + }, + artifacts: new cbartifacts.S3BucketBuildArtifacts({ + path: 'some/path', + name: 'some_name', + bucket, + }), + timeout: 123, + }); + + expect(stack).to(haveResourceLike('AWS::CodeBuild::Project', { + "Artifacts": { + "Path": "some/path", + "Name": "some_name", + "Type": "S3", + }, + "TimeoutInMinutes": 123, + })); + + test.done(); + }, + }, + + 'secondary artifacts': { + 'require providing an identifier when creating a Project'(test: Test) { + const stack = new cdk.Stack(); + + test.throws(() => { + new codebuild.Project(stack, 'MyProject', { + buildSpec: { + version: '0.2', + }, + secondaryArtifacts: [ + new cbartifacts.S3BucketBuildArtifacts({ + bucket: new s3.Bucket(stack, 'MyBucket'), + path: 'some/path', + name: 'name', + }), + ], + }); + }, /identifier/); + + test.done(); + }, + + 'are not allowed for a Project with CodePipeline as Source'(test: Test) { + const stack = new cdk.Stack(); + const project = new codebuild.Project(stack, 'MyProject', { + source: new FakeCodePipelineSource(), + }); + + project.addSecondaryArtifact(new cbartifacts.S3BucketBuildArtifacts({ + bucket: new s3.Bucket(stack, 'MyBucket'), + path: 'some/path', + name: 'name', + identifier: 'id', + })); + + test.throws(() => { + expect(stack); + }, /secondary artifacts/); + + test.done(); + }, + + 'added with an identifier after the Project has been created are rendered in the template'(test: Test) { + const stack = new cdk.Stack(); + const bucket = new s3.Bucket(stack, 'MyBucket'); + const project = new codebuild.Project(stack, 'MyProject', { + buildSpec: { + version: '0.2', + }, + }); + + project.addSecondaryArtifact(new cbartifacts.S3BucketBuildArtifacts({ + bucket, + path: 'another/path', + name: 'name', + identifier: 'artifact1', + })); + + expect(stack).to(haveResourceLike('AWS::CodeBuild::Project', { + "SecondaryArtifacts": [ + { + "ArtifactIdentifier": "artifact1", + "Type": "S3", + }, + ], + })); + + test.done(); + }, + }, + }, +}; diff --git a/packages/@aws-cdk/aws-codebuild-sources/.gitignore b/packages/@aws-cdk/aws-codebuild-sources/.gitignore new file mode 100644 index 0000000000000..205e21fe7353b --- /dev/null +++ b/packages/@aws-cdk/aws-codebuild-sources/.gitignore @@ -0,0 +1,16 @@ +*.js +tsconfig.json +tslint.json +*.js.map +*.d.ts +*.generated.ts +dist +lib/generated/resources.ts +.jsii + +.LAST_BUILD +.nyc_output +coverage +.nycrc +.LAST_PACKAGE +*.snk \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codebuild-sources/.npmignore b/packages/@aws-cdk/aws-codebuild-sources/.npmignore new file mode 100644 index 0000000000000..f5a63a96df103 --- /dev/null +++ b/packages/@aws-cdk/aws-codebuild-sources/.npmignore @@ -0,0 +1,18 @@ +# Don't include original .ts files when doing `npm pack` +*.ts +!*.d.ts +coverage +.nyc_output +*.tgz + +dist +.LAST_PACKAGE +.LAST_BUILD +!*.js + +# Include .jsii +!.jsii + +*.snk + +*.tsbuildinfo diff --git a/packages/@aws-cdk/aws-codebuild-sources/LICENSE b/packages/@aws-cdk/aws-codebuild-sources/LICENSE new file mode 100644 index 0000000000000..46c185646b439 --- /dev/null +++ b/packages/@aws-cdk/aws-codebuild-sources/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/@aws-cdk/aws-codebuild-sources/NOTICE b/packages/@aws-cdk/aws-codebuild-sources/NOTICE new file mode 100644 index 0000000000000..8585168af8b7d --- /dev/null +++ b/packages/@aws-cdk/aws-codebuild-sources/NOTICE @@ -0,0 +1,2 @@ +AWS Cloud Development Kit (AWS CDK) +Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. diff --git a/packages/@aws-cdk/aws-codebuild-sources/README.md b/packages/@aws-cdk/aws-codebuild-sources/README.md new file mode 100644 index 0000000000000..1469a96873a0b --- /dev/null +++ b/packages/@aws-cdk/aws-codebuild-sources/README.md @@ -0,0 +1,4 @@ +## AWS CodeBuild Sources + +This package contains classes that can be used as sources when creating a CodeBuild Project. +See the documentation of the `@aws-cdk/aws-codebuild` package for instructions on how to use CodeBuild in the CDK. diff --git a/packages/@aws-cdk/aws-codebuild-sources/lib/event-action.ts b/packages/@aws-cdk/aws-codebuild-sources/lib/event-action.ts new file mode 100644 index 0000000000000..1d812e04ba555 --- /dev/null +++ b/packages/@aws-cdk/aws-codebuild-sources/lib/event-action.ts @@ -0,0 +1,25 @@ +/** + * The types of webhook event actions. + */ +export enum EventAction { + /** + * A push (of a branch, or a tag) to the repository. + */ + PUSH = 'PUSH', + + /** + * Creating a Pull Request. + */ + PULL_REQUEST_CREATED = 'PULL_REQUEST_CREATED', + + /** + * Updating an Pull Request. + */ + PULL_REQUEST_UPDATED = 'PULL_REQUEST_UPDATED', + + /** + * Re-opening a previously closed Pull Request. + * Note that this event is only supported for GitHub and GitHubEnterprise sources. + */ + PULL_REQUEST_REOPENED = 'PULL_REQUEST_REOPENED', +} diff --git a/packages/@aws-cdk/aws-codebuild-sources/lib/filter-group-common.ts b/packages/@aws-cdk/aws-codebuild-sources/lib/filter-group-common.ts new file mode 100644 index 0000000000000..6703c3950bc26 --- /dev/null +++ b/packages/@aws-cdk/aws-codebuild-sources/lib/filter-group-common.ts @@ -0,0 +1,7 @@ +export const FILE_PATH_WEBHOOK_COND = 'FILE_PATH'; + +export function set2Array(set: Set): T[] { + const ret: T[] = []; + set.forEach(el => ret.push(el)); + return ret; +} diff --git a/packages/@aws-cdk/aws-codebuild-sources/lib/filter-group.ts b/packages/@aws-cdk/aws-codebuild-sources/lib/filter-group.ts new file mode 100644 index 0000000000000..a2a8fceefcb45 --- /dev/null +++ b/packages/@aws-cdk/aws-codebuild-sources/lib/filter-group.ts @@ -0,0 +1,245 @@ +import codebuild = require('@aws-cdk/aws-codebuild'); +import { EventAction } from './event-action'; +import { FILE_PATH_WEBHOOK_COND, set2Array } from './filter-group-common'; + +/** + * An object that represents a group of filter conditions for a webhook. + * Every condition in a given FilterGroup must be true in order for the whole group to be true. + * You construct instances of it by calling the {@link #inEventOf} static factory method, + * and then calling various `andXyz` instance methods to create modified instances of it + * (this class is immutable). + * + * You pass instances of this class to the `webhookFilters` property when constructing a source. + */ +export class FilterGroup { + /** + * Creates a new event FilterGroup that triggers on any of the provided actions. + * + * @param actions the actions to trigger the webhook on + */ + public static inEventOf(...actions: EventAction[]): FilterGroup { + return new FilterGroup(new Set(actions), []); + } + + private readonly actions: Set; + private readonly filters: codebuild.CfnProject.WebhookFilterProperty[]; + + private constructor(actions: Set, filters: codebuild.CfnProject.WebhookFilterProperty[]) { + if (actions.size === 0) { + throw new Error('A filter group must contain at least one event action'); + } + this.actions = actions; + this.filters = filters; + } + + /** + * Create a new FilterGroup with an added condition: + * the event must affect the given branch. + * + * @param branchName the name of the branch (can be a regular expression) + */ + public andBranchIs(branchName: string): FilterGroup { + return this.addHeadBranchFilter(branchName, true); + } + + /** + * Create a new FilterGroup with an added condition: + * the event must not affect the given branch. + * + * @param branchName the name of the branch (can be a regular expression) + */ + public andBranchIsNot(branchName: string): FilterGroup { + return this.addHeadBranchFilter(branchName, false); + } + + /** + * Create a new FilterGroup with an added condition: + * the event must affect the given tag. + * + * @param tagName the name of the tag (can be a regular expression) + */ + public andTagIs(tagName: string): FilterGroup { + return this.addHeadTagFilter(tagName, true); + } + + /** + * Create a new FilterGroup with an added condition: + * the event must not affect the given tag. + * + * @param tagName the name of the tag (can be a regular expression) + */ + public andTagIsNot(tagName: string): FilterGroup { + return this.addHeadTagFilter(tagName, false); + } + + /** + * Create a new FilterGroup with an added condition: + * the event must affect a Git reference (ie., a branch or a tag) + * that matches the given pattern. + * + * @param pattern a regular expression + */ + public andHeadRefIs(pattern: string) { + return this.addHeadRefFilter(pattern, true); + } + + /** + * Create a new FilterGroup with an added condition: + * the event must not affect a Git reference (ie., a branch or a tag) + * that matches the given pattern. + * + * @param pattern a regular expression + */ + public andHeadRefIsNot(pattern: string) { + return this.addHeadRefFilter(pattern, false); + } + + /** + * Create a new FilterGroup with an added condition: + * the account ID of the actor initiating the event must match the given pattern. + * + * @param pattern a regular expression + */ + public andActorAccountIs(pattern: string): FilterGroup { + return this.addActorAccountId(pattern, true); + } + + /** + * Create a new FilterGroup with an added condition: + * the account ID of the actor initiating the event must not match the given pattern. + * + * @param pattern a regular expression + */ + public andActorAccountIsNot(pattern: string): FilterGroup { + return this.addActorAccountId(pattern, false); + } + + /** + * Create a new FilterGroup with an added condition: + * the Pull Request that is the source of the event must target the given base branch. + * Note that you cannot use this method if this Group contains the `PUSH` event action. + * + * @param branchName the name of the branch (can be a regular expression) + */ + public andBaseBranchIs(branchName: string): FilterGroup { + return this.addBaseBranchFilter(branchName, true); + } + + /** + * Create a new FilterGroup with an added condition: + * the Pull Request that is the source of the event must not target the given base branch. + * Note that you cannot use this method if this Group contains the `PUSH` event action. + * + * @param branchName the name of the branch (can be a regular expression) + */ + public andBaseBranchIsNot(branchName: string): FilterGroup { + return this.addBaseBranchFilter(branchName, false); + } + + /** + * Create a new FilterGroup with an added condition: + * the Pull Request that is the source of the event must target the given Git reference. + * Note that you cannot use this method if this Group contains the `PUSH` event action. + * + * @param pattern a regular expression + */ + public andBaseRefIs(pattern: string): FilterGroup { + return this.addBaseRefFilter(pattern, true); + } + + /** + * Create a new FilterGroup with an added condition: + * the Pull Request that is the source of the event must not target the given Git reference. + * Note that you cannot use this method if this Group contains the `PUSH` event action. + * + * @param pattern a regular expression + */ + public andBaseRefIsNot(pattern: string): FilterGroup { + return this.addBaseRefFilter(pattern, false); + } + + /** + * Create a new FilterGroup with an added condition: + * the push that is the source of the event must affect a file that matches the given pattern. + * Note that you can only use this method if this Group contains only the `PUSH` event action, + * and only for GitHub and GitHubEnterprise sources. + * + * @param pattern a regular expression + */ + public andFilePathIs(pattern: string): FilterGroup { + return this.addFilePathFilter(pattern, true); + } + + /** + * Create a new FilterGroup with an added condition: + * the push that is the source of the event must not affect a file that matches the given pattern. + * Note that you can only use this method if this Group contains only the `PUSH` event action, + * and only for GitHub and GitHubEnterprise sources. + * + * @param pattern a regular expression + */ + public andFilePathIsNot(pattern: string): FilterGroup { + return this.addFilePathFilter(pattern, false); + } + + /** @internal */ + public get _actions(): EventAction[] { + return set2Array(this.actions); + } + + /** @internal */ + public get _filters(): codebuild.CfnProject.WebhookFilterProperty[] { + return this.filters.slice(); + } + + /** @internal */ + public _toJson(): codebuild.CfnProject.WebhookFilterProperty[] { + const eventFilter: codebuild.CfnProject.WebhookFilterProperty = { + type: 'EVENT', + pattern: set2Array(this.actions).join(', '), + }; + return [eventFilter].concat(this.filters); + } + + private addHeadBranchFilter(branchName: string, include: boolean): FilterGroup { + return this.addHeadRefFilter(`refs/heads/${branchName}`, include); + } + + private addHeadTagFilter(tagName: string, include: boolean): FilterGroup { + return this.addHeadRefFilter(`refs/tags/${tagName}`, include); + } + + private addHeadRefFilter(refName: string, include: boolean) { + return this.addFilter('HEAD_REF', refName, include); + } + + private addActorAccountId(accountId: string, include: boolean) { + return this.addFilter('ACTOR_ACCOUNT_ID', accountId, include); + } + + private addBaseBranchFilter(branchName: string, include: boolean): FilterGroup { + return this.addBaseRefFilter(`refs/heads/${branchName}`, include); + } + + private addBaseRefFilter(refName: string, include: boolean) { + if (this.actions.has(EventAction.PUSH)) { + throw new Error('A base reference condition cannot be added if a Group contains a PUSH event action'); + } + return this.addFilter('BASE_REF', refName, include); + } + + private addFilePathFilter(pattern: string, include: boolean): FilterGroup { + if (this.actions.size !== 1 || !this.actions.has(EventAction.PUSH)) { + throw new Error('A file path condition cannot be added if a Group contains any event action other than PUSH'); + } + return this.addFilter(FILE_PATH_WEBHOOK_COND, pattern, include); + } + + private addFilter(type: string, pattern: string, include: boolean) { + return new FilterGroup(this.actions, this.filters.concat([{ + type, + pattern, + excludeMatchedPattern: include ? undefined : true, + }])); + } +} diff --git a/packages/@aws-cdk/aws-codebuild-sources/lib/index.ts b/packages/@aws-cdk/aws-codebuild-sources/lib/index.ts new file mode 100644 index 0000000000000..73fd9d4ad446a --- /dev/null +++ b/packages/@aws-cdk/aws-codebuild-sources/lib/index.ts @@ -0,0 +1,3 @@ +export * from './event-action'; +export * from './filter-group'; +export * from './source'; diff --git a/packages/@aws-cdk/aws-codebuild-sources/lib/source.ts b/packages/@aws-cdk/aws-codebuild-sources/lib/source.ts new file mode 100644 index 0000000000000..377922d4301d6 --- /dev/null +++ b/packages/@aws-cdk/aws-codebuild-sources/lib/source.ts @@ -0,0 +1,329 @@ +import codebuild = require('@aws-cdk/aws-codebuild'); +import codecommit = require('@aws-cdk/aws-codecommit'); +import iam = require('@aws-cdk/aws-iam'); +import s3 = require('@aws-cdk/aws-s3'); +import { EventAction } from './event-action'; +import { FilterGroup } from './filter-group'; +import { FILE_PATH_WEBHOOK_COND } from './filter-group-common'; + +/** + * CodePipeline Source definition for a CodeBuild Project. + * *Note*: this type cannot be used as a secondary source, + * and because of that, you're not allowed to specify an identifier for it. + */ +export class CodePipelineSource extends codebuild.BuildSource { + public readonly type = codebuild.CODEPIPELINE_SOURCE_ARTIFACT_TYPE; + + constructor() { + super({}); + } +} + +/** + * Construction properties for {@link S3BucketSource}. + */ +export interface S3BucketSourceProps extends codebuild.BuildSourceProps { + readonly bucket: s3.IBucket; + readonly path: string; +} + +/** + * S3 bucket definition for a CodeBuild project. + */ +export class S3BucketSource extends codebuild.BuildSource { + public readonly type = codebuild.S3_SOURCE_TYPE; + private readonly bucket: s3.IBucket; + private readonly path: string; + + constructor(props: S3BucketSourceProps) { + super(props); + this.bucket = props.bucket; + this.path = props.path; + } + + /** + * @internal + */ + public _bind(project: codebuild.Project) { + this.bucket.grantRead(project); + } + + protected toSourceProperty(): any { + return { + location: `${this.bucket.bucketName}/${this.path}`, + }; + } +} + +/** + * The construction properties common to all build sources that are backed by Git. + */ +interface GitBuildSourceProps extends codebuild.BuildSourceProps { + /** + * The depth of history to download. Minimum value is 0. + * If this value is 0, greater than 25, or not provided, + * then the full history is downloaded with each build of the project. + */ + readonly cloneDepth?: number; +} + +/** + * A common superclass of all build sources that are backed by Git. + */ +abstract class GitBuildSource extends codebuild.BuildSource { + private readonly cloneDepth?: number; + + protected constructor(props: GitBuildSourceProps) { + super(props); + + this.cloneDepth = props.cloneDepth; + } + + public toSourceJSON(): codebuild.CfnProject.SourceProperty { + return { + ...super.toSourceJSON(), + gitCloneDepth: this.cloneDepth + }; + } +} + +/** + * Construction properties for {@link CodeCommitSource}. + */ +export interface CodeCommitSourceProps extends GitBuildSourceProps { + readonly repository: codecommit.IRepository; +} + +/** + * CodeCommit Source definition for a CodeBuild project. + */ +export class CodeCommitSource extends GitBuildSource { + public readonly type = codebuild.CODECOMMIT_SOURCE_TYPE; + private readonly repo: codecommit.IRepository; + + constructor(props: CodeCommitSourceProps) { + super(props); + this.repo = props.repository; + } + + /** + * @internal + */ + public _bind(project: codebuild.Project) { + // https://docs.aws.amazon.com/codebuild/latest/userguide/setting-up.html + project.addToRolePolicy(new iam.PolicyStatement() + .addAction('codecommit:GitPull') + .addResource(this.repo.repositoryArn)); + } + + protected toSourceProperty(): any { + return { + location: this.repo.repositoryCloneUrlHttp + }; + } +} + +/** + * The construction properties common to all third-party build sources that are backed by Git. + */ +interface ThirdPartyGitBuildSourceProps extends GitBuildSourceProps { + /** + * Whether to send notifications on your build's start and end. + * + * @default true + */ + readonly reportBuildStatus?: boolean; + + /** + * Whether to create a webhook that will trigger a build every time an event happens in the repository. + * + * @default true if any `webhookFilters` were provided, false otherwise + */ + readonly webhook?: boolean; + + /** + * A list of webhook filters that can constraint what events in the repository will trigger a build. + * A build is triggered if any of the provided filter groups match. + * Only valid if `webhook` was not provided as false. + * + * @default every push and every Pull Request (create or update) triggers a build + */ + readonly webhookFilters?: FilterGroup[]; +} + +/** + * A common superclass of all third-party build sources that are backed by Git. + */ +abstract class ThirdPartyGitBuildSource extends GitBuildSource { + public readonly badgeSupported: boolean = true; + protected readonly webhookFilters: FilterGroup[]; + private readonly reportBuildStatus: boolean; + private readonly webhook?: boolean; + + protected constructor(props: ThirdPartyGitBuildSourceProps) { + super(props); + + this.webhook = props.webhook; + this.reportBuildStatus = props.reportBuildStatus === undefined ? true : props.reportBuildStatus; + this.webhookFilters = props.webhookFilters || []; + } + + /** @internal */ + public _buildTriggers(): codebuild.CfnProject.ProjectTriggersProperty | undefined { + const anyFilterGroupsProvided = this.webhookFilters.length > 0; + const webhook = this.webhook === undefined ? (anyFilterGroupsProvided ? true : undefined) : this.webhook; + return webhook === undefined ? undefined : { + webhook, + filterGroups: anyFilterGroupsProvided ? this.webhookFilters.map(fg => fg._toJson()) : undefined, + }; + } + + public toSourceJSON(): codebuild.CfnProject.SourceProperty { + return { + ...super.toSourceJSON(), + reportBuildStatus: this.reportBuildStatus, + }; + } +} + +/** + * Construction properties for {@link GitHubSource} and {@link GitHubEnterpriseSource}. + */ +export interface GitHubSourceProps extends ThirdPartyGitBuildSourceProps { + /** + * The GitHub account/user that owns the repo. + * + * @example 'awslabs' + */ + readonly owner: string; + + /** + * The name of the repo (without the username). + * + * @example 'aws-cdk' + */ + readonly repo: string; +} + +/** + * GitHub Source definition for a CodeBuild project. + */ +export class GitHubSource extends ThirdPartyGitBuildSource { + public readonly type = codebuild.GITHUB_SOURCE_TYPE; + private readonly httpsCloneUrl: string; + + constructor(props: GitHubSourceProps) { + super(props); + this.httpsCloneUrl = `https://github.com/${props.owner}/${props.repo}.git`; + } + + protected toSourceProperty(): any { + return { + location: this.httpsCloneUrl, + }; + } +} + +/** + * Construction properties for {@link GitHubEnterpriseSource}. + */ +export interface GitHubEnterpriseSourceProps extends ThirdPartyGitBuildSourceProps { + /** + * The HTTPS URL of the repository in your GitHub Enterprise installation. + */ + readonly httpsCloneUrl: string; + + /** + * Whether to ignore SSL errors when connecting to the repository. + * + * @default false + */ + readonly ignoreSslErrors?: boolean; +} + +/** + * GitHub Enterprise Source definition for a CodeBuild project. + */ +export class GitHubEnterpriseSource extends ThirdPartyGitBuildSource { + public readonly type = codebuild.GITHUB_ENTERPRISE_SOURCE_TYPE; + private readonly httpsCloneUrl: string; + private readonly ignoreSslErrors?: boolean; + + constructor(props: GitHubEnterpriseSourceProps) { + super(props); + this.httpsCloneUrl = props.httpsCloneUrl; + this.ignoreSslErrors = props.ignoreSslErrors; + } + + protected toSourceProperty(): any { + return { + location: this.httpsCloneUrl, + insecureSsl: this.ignoreSslErrors, + }; + } +} + +/** + * Construction properties for {@link BitBucketSource}. + */ +export interface BitBucketSourceProps extends ThirdPartyGitBuildSourceProps { + /** + * The BitBucket account/user that owns the repo. + * + * @example 'awslabs' + */ + readonly owner: string; + + /** + * The name of the repo (without the username). + * + * @example 'aws-cdk' + */ + readonly repo: string; +} + +/** + * BitBucket Source definition for a CodeBuild project. + */ +export class BitBucketSource extends ThirdPartyGitBuildSource { + public readonly type = codebuild.BITBUCKET_SOURCE_TYPE; + private readonly httpsCloneUrl: any; + + constructor(props: BitBucketSourceProps) { + super(props); + this.httpsCloneUrl = `https://bitbucket.org/${props.owner}/${props.repo}.git`; + } + + /** @internal */ + public _buildTriggers(): codebuild.CfnProject.ProjectTriggersProperty | undefined { + // BitBucket sources don't support the PULL_REQUEST_REOPENED event action + if (this.anyWebhookFilterContainsPrReopenedEventAction()) { + throw new Error('BitBucket sources do not support the PULL_REQUEST_REOPENED webhook event action'); + } + + // they also don't support file path conditions + if (this.anyWebhookFilterContainsFilePathConditions()) { + throw new Error('BitBucket sources do not support file path conditions for webhook filters'); + } + + return super._buildTriggers(); + } + + protected toSourceProperty(): any { + return { + location: this.httpsCloneUrl + }; + } + + private anyWebhookFilterContainsPrReopenedEventAction() { + return this.webhookFilters.findIndex(fg => { + return fg._actions.findIndex(a => a === EventAction.PULL_REQUEST_REOPENED) !== -1; + }) !== -1; + } + + private anyWebhookFilterContainsFilePathConditions() { + return this.webhookFilters.findIndex(fg => { + return fg._filters.findIndex(f => f.type === FILE_PATH_WEBHOOK_COND) !== -1; + }) !== -1; + } +} diff --git a/packages/@aws-cdk/aws-codebuild-sources/package.json b/packages/@aws-cdk/aws-codebuild-sources/package.json new file mode 100644 index 0000000000000..0c8618551a696 --- /dev/null +++ b/packages/@aws-cdk/aws-codebuild-sources/package.json @@ -0,0 +1,89 @@ +{ + "name": "@aws-cdk/aws-codebuild-sources", + "version": "0.33.0", + "description": "CDK Constructs for AWS CodeBuild Sources", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "jsii": { + "outdir": "dist", + "targets": { + "java": { + "package": "software.amazon.awscdk.services.codebuild.sources", + "maven": { + "groupId": "software.amazon.awscdk", + "artifactId": "codebuild-sources" + } + }, + "dotnet": { + "namespace": "Amazon.CDK.AWS.CodeBuild.Sources", + "packageId": "Amazon.CDK.AWS.CodeBuild.Sources", + "signAssembly": true, + "assemblyOriginatorKeyFile": "../../key.snk" + }, + "python": { + "distName": "aws-cdk.aws-codebuild-sources", + "module": "aws_cdk.aws_codebuild_sources" + } + } + }, + "repository": { + "type": "git", + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-codebuild-sources" + }, + "scripts": { + "build": "cdk-build", + "watch": "cdk-watch", + "lint": "cdk-lint", + "test": "cdk-test", + "integ": "cdk-integ", + "pkglint": "pkglint -f", + "package": "cdk-package", + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts", + "build+test+package": "npm run build+test && npm run package", + "build+test": "npm run build && npm test" + }, + "nyc": { + "statements": 74, + "lines": 74 + }, + "keywords": [ + "aws", + "cdk", + "constructs", + "codebuild", + "sources" + ], + "author": { + "name": "Amazon Web Services", + "url": "https://aws.amazon.com", + "organization": true + }, + "license": "Apache-2.0", + "devDependencies": { + "@aws-cdk/assert": "^0.33.0", + "@aws-cdk/aws-codebuild-artifacts": "^0.33.0", + "cdk-build-tools": "^0.33.0", + "cdk-integ-tools": "^0.33.0", + "pkglint": "^0.33.0" + }, + "dependencies": { + "@aws-cdk/aws-codebuild": "^0.33.0", + "@aws-cdk/aws-codecommit": "^0.33.0", + "@aws-cdk/aws-iam": "^0.33.0", + "@aws-cdk/aws-s3": "^0.33.0", + "@aws-cdk/cdk": "^0.33.0" + }, + "homepage": "https://github.com/awslabs/aws-cdk", + "peerDependencies": { + "@aws-cdk/aws-codebuild": "^0.33.0", + "@aws-cdk/aws-codecommit": "^0.33.0", + "@aws-cdk/aws-iam": "^0.33.0", + "@aws-cdk/aws-s3": "^0.33.0", + "@aws-cdk/cdk": "^0.33.0" + }, + "engines": { + "node": ">= 8.10.0" + } +} diff --git a/packages/@aws-cdk/aws-codebuild/test/integ.github.expected.json b/packages/@aws-cdk/aws-codebuild-sources/test/integ.github.expected.json similarity index 100% rename from packages/@aws-cdk/aws-codebuild/test/integ.github.expected.json rename to packages/@aws-cdk/aws-codebuild-sources/test/integ.github.expected.json diff --git a/packages/@aws-cdk/aws-codebuild/test/integ.github.ts b/packages/@aws-cdk/aws-codebuild-sources/test/integ.github.ts similarity index 73% rename from packages/@aws-cdk/aws-codebuild/test/integ.github.ts rename to packages/@aws-cdk/aws-codebuild-sources/test/integ.github.ts index e9784a5d2c507..9aed2a8362ff0 100644 --- a/packages/@aws-cdk/aws-codebuild/test/integ.github.ts +++ b/packages/@aws-cdk/aws-codebuild-sources/test/integ.github.ts @@ -1,11 +1,12 @@ +import codebuild = require('@aws-cdk/aws-codebuild'); import cdk = require('@aws-cdk/cdk'); -import codebuild = require('../lib'); +import cbsources = require('../lib'); class TestStack extends cdk.Stack { constructor(scope: cdk.App, id: string) { super(scope, id); - const source = new codebuild.GitHubSource({ + const source = new cbsources.GitHubSource({ owner: 'awslabs', repo: 'aws-cdk', reportBuildStatus: false, diff --git a/packages/@aws-cdk/aws-codebuild/test/integ.project-bucket.expected.json b/packages/@aws-cdk/aws-codebuild-sources/test/integ.project-bucket.expected.json similarity index 100% rename from packages/@aws-cdk/aws-codebuild/test/integ.project-bucket.expected.json rename to packages/@aws-cdk/aws-codebuild-sources/test/integ.project-bucket.expected.json diff --git a/packages/@aws-cdk/aws-codebuild/test/integ.project-bucket.ts b/packages/@aws-cdk/aws-codebuild-sources/test/integ.project-bucket.ts similarity index 77% rename from packages/@aws-cdk/aws-codebuild/test/integ.project-bucket.ts rename to packages/@aws-cdk/aws-codebuild-sources/test/integ.project-bucket.ts index 196c1257bccc2..4399d584a6d60 100644 --- a/packages/@aws-cdk/aws-codebuild/test/integ.project-bucket.ts +++ b/packages/@aws-cdk/aws-codebuild-sources/test/integ.project-bucket.ts @@ -1,7 +1,8 @@ #!/usr/bin/env node +import codebuild = require('@aws-cdk/aws-codebuild'); import s3 = require('@aws-cdk/aws-s3'); import cdk = require('@aws-cdk/cdk'); -import codebuild = require('../lib'); +import cbsources = require('../lib'); const app = new cdk.App(); @@ -12,7 +13,7 @@ const bucket = new s3.Bucket(stack, 'MyBucket', { }); new codebuild.Project(stack, 'MyProject', { - source: new codebuild.S3BucketSource({ + source: new cbsources.S3BucketSource({ bucket, path: 'path/to/my/source.zip', }), diff --git a/packages/@aws-cdk/aws-codebuild/test/integ.project-secondary-sources-artifacts.expected.json b/packages/@aws-cdk/aws-codebuild-sources/test/integ.project-secondary-sources-artifacts.expected.json similarity index 100% rename from packages/@aws-cdk/aws-codebuild/test/integ.project-secondary-sources-artifacts.expected.json rename to packages/@aws-cdk/aws-codebuild-sources/test/integ.project-secondary-sources-artifacts.expected.json diff --git a/packages/@aws-cdk/aws-codebuild/test/integ.project-secondary-sources-artifacts.ts b/packages/@aws-cdk/aws-codebuild-sources/test/integ.project-secondary-sources-artifacts.ts similarity index 72% rename from packages/@aws-cdk/aws-codebuild/test/integ.project-secondary-sources-artifacts.ts rename to packages/@aws-cdk/aws-codebuild-sources/test/integ.project-secondary-sources-artifacts.ts index 6e8767a17021b..9266ef9b0e2c1 100644 --- a/packages/@aws-cdk/aws-codebuild/test/integ.project-secondary-sources-artifacts.ts +++ b/packages/@aws-cdk/aws-codebuild-sources/test/integ.project-secondary-sources-artifacts.ts @@ -1,6 +1,8 @@ +import codebuild = require('@aws-cdk/aws-codebuild'); +import cbartifacts = require('@aws-cdk/aws-codebuild-artifacts'); import s3 = require('@aws-cdk/aws-s3'); import cdk = require('@aws-cdk/cdk'); -import codebuild = require('../lib'); +import cbsources = require('../lib'); const app = new cdk.App(); @@ -15,14 +17,14 @@ new codebuild.Project(stack, 'MyProject', { version: '0.2', }, secondarySources: [ - new codebuild.S3BucketSource({ + new cbsources.S3BucketSource({ bucket, path: 'some/path', identifier: 'AddSource1', }), ], secondaryArtifacts: [ - new codebuild.S3BucketBuildArtifacts({ + new cbartifacts.S3BucketBuildArtifacts({ bucket, path: 'another/path', name: 'name', diff --git a/packages/@aws-cdk/aws-codebuild-sources/test/test.artifacts.ts b/packages/@aws-cdk/aws-codebuild-sources/test/test.artifacts.ts new file mode 100644 index 0000000000000..8c95ddb629a8a --- /dev/null +++ b/packages/@aws-cdk/aws-codebuild-sources/test/test.artifacts.ts @@ -0,0 +1,96 @@ +import { expect, haveResource } from '@aws-cdk/assert'; +import codebuild = require('@aws-cdk/aws-codebuild'); +import cbartifacts = require('@aws-cdk/aws-codebuild-artifacts'); +import codecommit = require('@aws-cdk/aws-codecommit'); +import cdk = require('@aws-cdk/cdk'); +import { Test } from 'nodeunit'; +import cbsources = require('../lib'); + +// tslint:disable:object-literal-key-quotes + +export = { + 'CodeBuild Artifacts': { + 'CodePipeline': { + 'both source and artifacts are set to CodePipeline'(test: Test) { + const stack = new cdk.Stack(); + + new codebuild.Project(stack, 'MyProject', { + source: new cbsources.CodePipelineSource(), + artifacts: new cbartifacts.CodePipelineBuildArtifacts() + }); + + expect(stack).to(haveResource('AWS::CodeBuild::Project', { + "Source": { + "Type": "CODEPIPELINE" + }, + "Artifacts": { + "Type": "CODEPIPELINE" + }, + "ServiceRole": { + "Fn::GetAtt": [ + "MyProjectRole9BBE5233", + "Arn" + ] + }, + "Environment": { + "Type": "LINUX_CONTAINER", + "PrivilegedMode": false, + "Image": "aws/codebuild/standard:1.0", + "ComputeType": "BUILD_GENERAL1_SMALL" + } + })); + + test.done(); + }, + + 'if source is set to CodePipeline, and artifacts are not set, they are defaulted to CodePipeline'(test: Test) { + const stack = new cdk.Stack(); + + new codebuild.Project(stack, 'MyProject', { + source: new cbsources.CodePipelineSource() + }); + + expect(stack).to(haveResource('AWS::CodeBuild::Project', { + "Source": { + "Type": "CODEPIPELINE" + }, + "Artifacts": { + "Type": "CODEPIPELINE" + }, + "ServiceRole": { + "Fn::GetAtt": [ + "MyProjectRole9BBE5233", + "Arn" + ] + }, + "Environment": { + "Type": "LINUX_CONTAINER", + "PrivilegedMode": false, + "Image": "aws/codebuild/standard:1.0", + "ComputeType": "BUILD_GENERAL1_SMALL" + } + })); + + test.done(); + }, + + 'fails if one of source/artifacts is set to CodePipeline and the other isn\'t'(test: Test) { + const stack = new cdk.Stack(); + + test.throws(() => new codebuild.Project(stack, 'MyProject', { + source: new cbsources.CodePipelineSource(), + artifacts: new codebuild.NoBuildArtifacts() + }), /Both source and artifacts must be set to CodePipeline/); + + test.throws(() => new codebuild.Project(stack, 'YourProject', { + source: new cbsources.CodeCommitSource({ + repository: new codecommit.Repository(stack, 'MyRepo', { repositoryName: 'boo' }) + }), + artifacts: new cbartifacts.CodePipelineBuildArtifacts() + }), /Both source and artifacts must be set to CodePipeline/); + + test.done(); + }, + }, + }, +}; diff --git a/packages/@aws-cdk/aws-codebuild-sources/test/test.sources.ts b/packages/@aws-cdk/aws-codebuild-sources/test/test.sources.ts new file mode 100644 index 0000000000000..fc45ba41d83e1 --- /dev/null +++ b/packages/@aws-cdk/aws-codebuild-sources/test/test.sources.ts @@ -0,0 +1,796 @@ +import { expect, haveResource, haveResourceLike } from "@aws-cdk/assert"; +import codebuild = require('@aws-cdk/aws-codebuild'); +import codecommit = require('@aws-cdk/aws-codecommit'); +import s3 = require('@aws-cdk/aws-s3'); +import cdk = require('@aws-cdk/cdk'); +import { Test } from 'nodeunit'; +import cbsources = require('../lib'); + +// tslint:disable:object-literal-key-quotes + +export = { + 'CodeBuild Sources': { + 'CodePipeline'(test: Test) { + const stack = new cdk.Stack(); + + const source = new cbsources.CodePipelineSource(); + new codebuild.Project(stack, 'MyProject', { + source + }); + + expect(stack).toMatch({ + "Resources": { + "MyProjectRole9BBE5233": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { "Fn::Join": ["", ["codebuild.", { Ref: "AWS::URLSuffix" }]] } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "MyProjectRoleDefaultPolicyB19B7C29": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":log-group:/aws/codebuild/", + { + "Ref": "MyProject39F7B0AE" + } + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":log-group:/aws/codebuild/", + { + "Ref": "MyProject39F7B0AE" + }, + ":*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "MyProjectRoleDefaultPolicyB19B7C29", + "Roles": [ + { + "Ref": "MyProjectRole9BBE5233" + } + ] + } + }, + "MyProject39F7B0AE": { + "Type": "AWS::CodeBuild::Project", + "Properties": { + "Source": { + "Type": "CODEPIPELINE" + }, + "Artifacts": { + "Type": "CODEPIPELINE" + }, + "ServiceRole": { + "Fn::GetAtt": [ + "MyProjectRole9BBE5233", + "Arn" + ] + }, + "Environment": { + "Type": "LINUX_CONTAINER", + "PrivilegedMode": false, + "Image": "aws/codebuild/standard:1.0", + "ComputeType": "BUILD_GENERAL1_SMALL" + } + } + } + } + }); + + test.done(); + }, + + 'CodeCommit'(test: Test) { + const stack = new cdk.Stack(); + + const repo = new codecommit.Repository(stack, 'MyRepo', { repositoryName: 'hello-cdk' }); + + const source = new cbsources.CodeCommitSource({ repository: repo, cloneDepth: 2 }); + + new codebuild.Project(stack, 'MyProject', { + source + }); + + expect(stack).toMatch({ + "Resources": { + "MyRepoF4F48043": { + "Type": "AWS::CodeCommit::Repository", + "Properties": { + "RepositoryName": "hello-cdk", + "Triggers": [] + } + }, + "MyProjectRole9BBE5233": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { "Fn::Join": ["", ["codebuild.", { Ref: "AWS::URLSuffix" }]] } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "MyProjectRoleDefaultPolicyB19B7C29": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "codecommit:GitPull", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "MyRepoF4F48043", + "Arn" + ] + } + }, + { + "Action": [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":log-group:/aws/codebuild/", + { + "Ref": "MyProject39F7B0AE" + } + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":log-group:/aws/codebuild/", + { + "Ref": "MyProject39F7B0AE" + }, + ":*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "MyProjectRoleDefaultPolicyB19B7C29", + "Roles": [ + { + "Ref": "MyProjectRole9BBE5233" + } + ] + } + }, + "MyProject39F7B0AE": { + "Type": "AWS::CodeBuild::Project", + "Properties": { + "Artifacts": { + "Type": "NO_ARTIFACTS" + }, + "Environment": { + "ComputeType": "BUILD_GENERAL1_SMALL", + "Image": "aws/codebuild/standard:1.0", + "PrivilegedMode": false, + "Type": "LINUX_CONTAINER" + }, + "ServiceRole": { + "Fn::GetAtt": [ + "MyProjectRole9BBE5233", + "Arn" + ] + }, + "Source": { + "Location": { + "Fn::GetAtt": [ + "MyRepoF4F48043", + "CloneUrlHttp" + ] + }, + "GitCloneDepth": 2, + "Type": "CODECOMMIT" + } + } + } + } + }); + test.done(); + }, + + 'S3'(test: Test) { + const stack = new cdk.Stack(); + const bucket = new s3.Bucket(stack, 'MyBucket'); + + new codebuild.Project(stack, 'MyProject', { + source: new cbsources.S3BucketSource({ + bucket, + path: 'path/to/source.zip', + }), + environment: { + buildImage: codebuild.WindowsBuildImage.WIN_SERVER_CORE_2016_BASE, + }, + }); + + expect(stack).toMatch({ + "Resources": { + "MyBucketF68F3FF0": { + "Type": "AWS::S3::Bucket", + "DeletionPolicy": "Retain" + }, + "MyProjectRole9BBE5233": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { "Fn::Join": ["", ["codebuild.", { Ref: "AWS::URLSuffix" }]] } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "MyProjectRoleDefaultPolicyB19B7C29": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "MyBucketF68F3FF0", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "MyBucketF68F3FF0", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + }, + { + "Action": [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":log-group:/aws/codebuild/", + { + "Ref": "MyProject39F7B0AE" + } + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":log-group:/aws/codebuild/", + { + "Ref": "MyProject39F7B0AE" + }, + ":*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "MyProjectRoleDefaultPolicyB19B7C29", + "Roles": [ + { + "Ref": "MyProjectRole9BBE5233" + } + ] + } + }, + "MyProject39F7B0AE": { + "Type": "AWS::CodeBuild::Project", + "Properties": { + "Artifacts": { + "Type": "NO_ARTIFACTS" + }, + "Environment": { + "ComputeType": "BUILD_GENERAL1_MEDIUM", + "Image": "aws/codebuild/windows-base:1.0", + "PrivilegedMode": false, + "Type": "WINDOWS_CONTAINER" + }, + "ServiceRole": { + "Fn::GetAtt": [ + "MyProjectRole9BBE5233", + "Arn" + ] + }, + "Source": { + "Location": { + "Fn::Join": [ + "", + [ + { + "Ref": "MyBucketF68F3FF0" + }, + "/path/to/source.zip" + ] + ] + }, + "Type": "S3" + } + } + } + } + }); + test.done(); + }, + + 'GitHub': { + 'renders correctly in the template'(test: Test) { + const stack = new cdk.Stack(); + + new codebuild.Project(stack, 'Project', { + source: new cbsources.GitHubSource({ + owner: 'testowner', + repo: 'testrepo', + cloneDepth: 3, + webhook: true, + reportBuildStatus: false, + webhookFilters: [ + cbsources.FilterGroup.inEventOf(cbsources.EventAction.PUSH).andTagIsNot('stable'), + cbsources.FilterGroup.inEventOf(cbsources.EventAction.PULL_REQUEST_REOPENED).andBaseBranchIs('master'), + ], + }) + }); + + expect(stack).to(haveResource('AWS::CodeBuild::Project', { + Source: { + Type: "GITHUB", + Location: 'https://github.com/testowner/testrepo.git', + ReportBuildStatus: false, + GitCloneDepth: 3, + } + })); + + expect(stack).to(haveResourceLike('AWS::CodeBuild::Project', { + Triggers: { + Webhook: true, + FilterGroups: [ + [ + { Type: 'EVENT', Pattern: 'PUSH' }, + { Type: 'HEAD_REF', Pattern: 'refs/tags/stable', ExcludeMatchedPattern: true }, + ], + [ + { Type: 'EVENT', Pattern: 'PULL_REQUEST_REOPENED' }, + { Type: 'BASE_REF', Pattern: 'refs/heads/master' }, + ], + ], + }, + })); + + test.done(); + }, + + 'has reportBuildStatus on by default'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + new codebuild.Project(stack, 'Project', { + source: new cbsources.GitHubSource({ + owner: 'testowner', + repo: 'testrepo', + cloneDepth: 3, + }), + }); + + // THEN + expect(stack).to(haveResource('AWS::CodeBuild::Project', { + Source: { + Type: "GITHUB", + Location: 'https://github.com/testowner/testrepo.git', + ReportBuildStatus: true, + GitCloneDepth: 3, + }, + })); + + test.done(); + }, + + 'can explicitly set reportBuildStatus to false'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + new codebuild.Project(stack, 'Project', { + source: new cbsources.GitHubSource({ + owner: 'testowner', + repo: 'testrepo', + reportBuildStatus: false, + }), + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::CodeBuild::Project', { + Source: { + ReportBuildStatus: false, + }, + })); + + test.done(); + }, + + 'can explicitly set webhook to true'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + new codebuild.Project(stack, 'Project', { + source: new cbsources.GitHubSource({ + owner: 'testowner', + repo: 'testrepo', + webhook: true, + }), + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::CodeBuild::Project', { + Triggers: { + Webhook: true, + }, + })); + + test.done(); + }, + }, + + 'GitHubEnterprise'(test: Test) { + const stack = new cdk.Stack(); + + const pushFilterGroup = cbsources.FilterGroup.inEventOf(cbsources.EventAction.PUSH); + new codebuild.Project(stack, 'MyProject', { + source: new cbsources.GitHubEnterpriseSource({ + httpsCloneUrl: 'https://github.testcompany.com/testowner/testrepo', + ignoreSslErrors: true, + cloneDepth: 4, + webhook: true, + reportBuildStatus: false, + webhookFilters: [ + pushFilterGroup.andBranchIs('master'), + pushFilterGroup.andBranchIs('develop'), + pushFilterGroup.andFilePathIs('ReadMe.md'), + ], + }), + }); + + expect(stack).to(haveResource('AWS::CodeBuild::Project', { + Source: { + Type: "GITHUB_ENTERPRISE", + InsecureSsl: true, + GitCloneDepth: 4, + ReportBuildStatus: false, + Location: 'https://github.testcompany.com/testowner/testrepo', + }, + })); + + expect(stack).to(haveResourceLike('AWS::CodeBuild::Project', { + Triggers: { + Webhook: true, + FilterGroups: [ + [ + { Type: 'EVENT', Pattern: 'PUSH' }, + { Type: 'HEAD_REF', Pattern: 'refs/heads/master' }, + ], + [ + { Type: 'EVENT', Pattern: 'PUSH' }, + { Type: 'HEAD_REF', Pattern: 'refs/heads/develop' }, + ], + [ + { Type: 'EVENT', Pattern: 'PUSH' }, + { Type: 'FILE_PATH', Pattern: 'ReadMe.md' }, + ], + ], + }, + })); + + test.done(); + }, + + 'Bitbucket'(test: Test) { + const stack = new cdk.Stack(); + + new codebuild.Project(stack, 'Project', { + source: new cbsources.BitBucketSource({ + owner: 'testowner', + repo: 'testrepo', + cloneDepth: 5, + reportBuildStatus: false, + webhookFilters: [ + cbsources.FilterGroup.inEventOf( + cbsources.EventAction.PULL_REQUEST_CREATED, + cbsources.EventAction.PULL_REQUEST_UPDATED, + ).andTagIs('v.*'), + // duplicate event actions are fine + cbsources.FilterGroup.inEventOf(cbsources.EventAction.PUSH, cbsources.EventAction.PUSH).andActorAccountIsNot('aws-cdk-dev'), + ], + }) + }); + + expect(stack).to(haveResource('AWS::CodeBuild::Project', { + Source: { + Type: 'BITBUCKET', + Location: 'https://bitbucket.org/testowner/testrepo.git', + GitCloneDepth: 5, + ReportBuildStatus: false, + }, + })); + + expect(stack).to(haveResourceLike('AWS::CodeBuild::Project', { + Triggers: { + Webhook: true, + FilterGroups: [ + [ + { Type: 'EVENT', Pattern: 'PULL_REQUEST_CREATED, PULL_REQUEST_UPDATED' }, + { Type: 'HEAD_REF', Pattern: 'refs/tags/v.*' }, + ], + [ + { Type: 'EVENT', Pattern: 'PUSH' }, + { Type: 'ACTOR_ACCOUNT_ID', Pattern: 'aws-cdk-dev', ExcludeMatchedPattern: true }, + ], + ], + }, + })); + + test.done(); + }, + + 'badge support test'(test: Test) { + const stack = new cdk.Stack(); + + interface BadgeValidationTestCase { + source: codebuild.BuildSource, + shouldPassValidation: boolean + } + + const repo = new codecommit.Repository(stack, 'MyRepo', { repositoryName: 'hello-cdk' }); + const bucket = new s3.Bucket(stack, 'MyBucket'); + + const cases: BadgeValidationTestCase[] = [ + { source: new codebuild.NoSource(), shouldPassValidation: false }, + { source: new cbsources.CodePipelineSource(), shouldPassValidation: false }, + { source: new cbsources.CodeCommitSource({ repository: repo }), shouldPassValidation: false }, + { source: new cbsources.S3BucketSource({ bucket, path: 'path/to/source.zip' }), shouldPassValidation: false }, + { source: new cbsources.GitHubSource({ owner: 'awslabs', repo: 'aws-cdk' }), shouldPassValidation: true }, + { source: new cbsources.GitHubEnterpriseSource({ httpsCloneUrl: 'url' }), shouldPassValidation: true }, + { source: new cbsources.BitBucketSource({ owner: 'awslabs', repo: 'aws-cdk' }), shouldPassValidation: true } + ]; + + cases.forEach(testCase => { + const source = testCase.source; + const validationBlock = () => { new codebuild.Project(stack, `MyProject-${source.type}`, { source, badge: true }); }; + if (testCase.shouldPassValidation) { + test.doesNotThrow(validationBlock, Error, `Badge is not supported for source type ${source.type}`); + } else { + test.throws(validationBlock, Error, `Badge is not supported for source type ${source.type}`); + } + }); + + test.done(); + }, + + 'webhook Filters': { + 'a Group cannot be created with an empty set of event actions'(test: Test) { + test.throws(() => { + cbsources.FilterGroup.inEventOf(); + }, /A filter group must contain at least one event action/); + + test.done(); + }, + + 'cannot have base ref conditions if the Group contains the PUSH action'(test: Test) { + const filterGroup = cbsources.FilterGroup.inEventOf(cbsources.EventAction.PULL_REQUEST_CREATED, + cbsources.EventAction.PUSH); + + test.throws(() => { + filterGroup.andBaseRefIs('.*'); + }, /A base reference condition cannot be added if a Group contains a PUSH event action/); + + test.done(); + }, + + 'cannot have file path conditions if the Group contains any action other than PUSH'(test: Test) { + const filterGroup = cbsources.FilterGroup.inEventOf(cbsources.EventAction.PULL_REQUEST_CREATED, + cbsources.EventAction.PUSH); + + test.throws(() => { + filterGroup.andFilePathIsNot('.*\\.java'); + }, /A file path condition cannot be added if a Group contains any event action other than PUSH/); + + test.done(); + }, + + 'BitBucket sources do not support the PULL_REQUEST_REOPENED event action'(test: Test) { + const stack = new cdk.Stack(); + + test.throws(() => { + new codebuild.Project(stack, 'Project', { + source: new cbsources.BitBucketSource({ + owner: 'owner', + repo: 'repo', + webhookFilters: [ + cbsources.FilterGroup.inEventOf(cbsources.EventAction.PULL_REQUEST_REOPENED), + ], + }), + }); + }, /BitBucket sources do not support the PULL_REQUEST_REOPENED webhook event action/); + + test.done(); + }, + + 'BitBucket sources do not support file path conditions'(test: Test) { + const stack = new cdk.Stack(); + const filterGroup = cbsources.FilterGroup.inEventOf(cbsources.EventAction.PUSH).andFilePathIs('.*'); + + test.throws(() => { + new codebuild.Project(stack, 'Project', { + source: new cbsources.BitBucketSource({ + owner: 'owner', + repo: 'repo', + webhookFilters: [filterGroup], + }), + }); + }, /BitBucket sources do not support file path conditions for webhook filters/); + + test.done(); + }, + }, + }, +}; diff --git a/packages/@aws-cdk/aws-codebuild/README.md b/packages/@aws-cdk/aws-codebuild/README.md index 195c17cae7604..0062b8b9d4d7d 100644 --- a/packages/@aws-cdk/aws-codebuild/README.md +++ b/packages/@aws-cdk/aws-codebuild/README.md @@ -31,7 +31,23 @@ methods and attributes. Build projects are usually associated with a _source_, which is specified via the `source` property which accepts a class that extends the `BuildSource` -abstract base class. The supported sources are: +abstract base class. + +Sources (with the exception of `NoSource` - see below) +live in a separate package, `@aws-cdk/aws-codebuild-sources`. +Make sure to install it: + +```console +$ npm i @aws-cdk/aws-codebuild-sources +``` + +And import it into your code: + +```ts +import codebuild_sources = require('@aws-cdk/aws-codebuild-sources'); +``` + +The supported sources are: ### `NoSource` @@ -51,11 +67,12 @@ Use an AWS CodeCommit repository as the source of this build: ```ts import codebuild = require('@aws-cdk/aws-codebuild'); +import codebuild_sources = require('@aws-cdk/aws-codebuild-sources'); import codecommit = require('@aws-cdk/aws-codecommit'); const repository = new codecommit.Repository(this, 'MyRepo', { repositoryName: 'foo' }); new codebuild.Project(this, 'MyFirstCodeCommitProject', { - source: new codebuild.CodeCommitSource({ repository }), + source: new codebuild_sources.CodeCommitSource({ repository }), }); ``` @@ -65,11 +82,12 @@ Create a CodeBuild project with an S3 bucket as the source: ```ts import codebuild = require('@aws-cdk/aws-codebuild'); +import codebuild_sources = require('@aws-cdk/aws-codebuild-sources'); import s3 = require('@aws-cdk/aws-s3'); const bucket = new s3.Bucket(this, 'MyBucket'); new codebuild.Project(this, 'MyProject', { - source: new codebuild.S3BucketSource({ + source: new codebuild_sources.S3BucketSource({ bucket: bucket, path: 'path/to/file.zip', }), @@ -87,12 +105,12 @@ These source types can be used to build code from a GitHub repository. Example: ```typescript -const gitHubSource = new codebuild.GitHubSource({ +const gitHubSource = new codebuild_sources.GitHubSource({ owner: 'awslabs', repo: 'aws-cdk', webhook: true, // optional, default: true if `webhookFilteres` were provided, false otherwise webhookFilters: [ - codebuild.FilterGroup.inEventOf(codebuild.EventAction.PUSH).andBranchIs('master'), + codebuild_sources.FilterGroup.inEventOf(codebuild_sources.EventAction.PUSH).andBranchIs('master'), ], // optional, by default all pushes and Pull Requests will trigger a build }); ``` @@ -109,6 +127,57 @@ aws codebuild import-source-credentials --server-type GITHUB --auth-type PERSONA This source type can be used to build code from a BitBucket repository. +## Artifacts + +Build projects can also optionally produce _artifacts_, +which are saved outputs that are the result of executing the build. + +Artifacts (with the exception of `NoBuildArtifacts` - see below) +live in a separate package, `@aws-cdk/aws-codebuild-artifacts`. +Make sure to install it: + +```console +$ npm i @aws-cdk/aws-codebuild-artifacts +``` + +And import it into your code: + +```ts +import codebuild_artifacts = require('@aws-cdk/aws-codebuild-artifacts'); +``` + +The supported artifacts are: + +### `NoBuildArtifacts` + +This is the default, and implies that no artifacts are produced as a result of executing the project. + +### `CodePipelineBuildArtifacts` + +Used as a special artifacts type when a CodeBuild project is used in a +CodePipeline action. +The produced outputs will be determined by the action's `artifact` settings, +instead of the project settings. + +### `S3BucketBuildArtifacts` + +Saves its artifacts in S3: + +```ts +import codebuild = require('@aws-cdk/aws-codebuild'); +import codebuild_artifacts = require('@aws-cdk/aws-codebuild-artifacts'); +import s3 = require('@aws-cdk/aws-s3'); + +const bucket = new s3.Bucket(this, 'MyBucket'); +new codebuild.Project(this, 'MyProject', { + artifacts: new codebuild_artifacts.S3BucketBuildArtifacts({ + bucket: bucket, + name: 'output', // this will be the name of the file or directory under which the output will be stored + packageZip: false, // should the output be combined into one ZIP file, defaults to true + }), +}); +``` + ## 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. @@ -213,13 +282,13 @@ multiple outputs. For example: ```ts const project = new codebuild.Project(this, 'MyProject', { secondarySources: [ - new codebuild.CodeCommitSource({ + new codebuild_sources.CodeCommitSource({ identifier: 'source2', repository: repo, }), ], secondaryArtifacts: [ - new codebuild.S3BucketBuildArtifacts({ + new codebuild_artifacts.S3BucketBuildArtifacts({ identifier: 'artifact2', bucket: bucket, path: 'some/path', diff --git a/packages/@aws-cdk/aws-codebuild/lib/artifacts.ts b/packages/@aws-cdk/aws-codebuild/lib/artifacts.ts index 53c9e835a2766..321021935a97f 100644 --- a/packages/@aws-cdk/aws-codebuild/lib/artifacts.ts +++ b/packages/@aws-cdk/aws-codebuild/lib/artifacts.ts @@ -1,4 +1,3 @@ -import s3 = require('@aws-cdk/aws-s3'); import { CfnProject } from './codebuild.generated'; import { Project } from './project'; @@ -61,85 +60,3 @@ export class NoBuildArtifacts extends BuildArtifacts { super({}); } } - -/** - * CodePipeline Artifact definition for a CodeBuild Project. - * *Note*: this type cannot be used as a secondary artifact, - * and because of that, you're not allowed to specify an identifier for it. - */ -export class CodePipelineBuildArtifacts extends BuildArtifacts { - protected readonly type = 'CODEPIPELINE'; - - constructor() { - super({}); - } -} - -/** - * Construction properties for {@link S3BucketBuildArtifacts}. - */ -export interface S3BucketBuildArtifactsProps extends BuildArtifactsProps { - /** - * The name of the output bucket. - */ - readonly bucket: s3.IBucket; - - /** - * The path inside of the bucket for the build output .zip file or folder. - * If a value is not specified, then build output will be stored at the root of the - * bucket (or under the directory if `includeBuildId` is set to true). - */ - readonly path?: string; - - /** - * The name of the build output ZIP file or folder inside the bucket. - * - * The full S3 object key will be "//" or - * "/" depending on whether `includeBuildId` is set to true. - */ - readonly name: string; - - /** - * Indicates if the build ID should be included in the path. If this is set to true, - * then the build artifact will be stored in "//". - * - * @default true - */ - readonly includeBuildId?: boolean; - - /** - * If this is true, all build output will be packaged into a single .zip file. - * Otherwise, all files will be uploaded to / - * - * @default true - files will be archived - */ - readonly packageZip?: boolean; -} - -/** - * S3 Artifact definition for a CodeBuild Project. - */ -export class S3BucketBuildArtifacts extends BuildArtifacts { - protected readonly type = 'S3'; - - constructor(private readonly props: S3BucketBuildArtifactsProps) { - super(props); - } - - /** - * @internal - */ - public _bind(project: Project) { - this.props.bucket.grantReadWrite(project); - } - - protected toArtifactsProperty(): any { - return { - location: this.props.bucket.bucketName, - path: this.props.path, - namespaceType: this.props.includeBuildId === false ? 'NONE' : 'BUILD_ID', - name: this.props.name, - packaging: this.props.packageZip === false ? 'NONE' : 'ZIP', - }; - } -} diff --git a/packages/@aws-cdk/aws-codebuild/lib/pipeline-project.ts b/packages/@aws-cdk/aws-codebuild/lib/pipeline-project.ts index 7d6e4e4193e09..3e509935484ff 100644 --- a/packages/@aws-cdk/aws-codebuild/lib/pipeline-project.ts +++ b/packages/@aws-cdk/aws-codebuild/lib/pipeline-project.ts @@ -1,12 +1,21 @@ import cdk = require('@aws-cdk/cdk'); -import { CodePipelineBuildArtifacts } from './artifacts'; import { CommonProjectProps, Project } from './project'; -import { CodePipelineSource } from './source'; +import { BuildSource, CODEPIPELINE_SOURCE_ARTIFACT_TYPE } from './source'; // tslint:disable-next-line:no-empty-interface export interface PipelineProjectProps extends CommonProjectProps { } +// non-exported CodePipelineSource, which is extremely simple, +// so I can live with the duplication +class CodePipelineSource extends BuildSource { + public readonly type = CODEPIPELINE_SOURCE_ARTIFACT_TYPE; + + constructor() { + super({}); + } +} + /** * A convenience class for CodeBuild Projects that are used in CodePipeline. */ @@ -14,8 +23,7 @@ export class PipelineProject extends Project { constructor(scope: cdk.Construct, id: string, props?: PipelineProjectProps) { super(scope, id, { source: new CodePipelineSource(), - artifacts: new CodePipelineBuildArtifacts(), - ...props + ...props, }); } } diff --git a/packages/@aws-cdk/aws-codebuild/lib/project.ts b/packages/@aws-cdk/aws-codebuild/lib/project.ts index 7355ce328becc..946f3bbefb74c 100644 --- a/packages/@aws-cdk/aws-codebuild/lib/project.ts +++ b/packages/@aws-cdk/aws-codebuild/lib/project.ts @@ -7,12 +7,11 @@ import events = require('@aws-cdk/aws-events'); import iam = require('@aws-cdk/aws-iam'); import kms = require('@aws-cdk/aws-kms'); import { Aws, Construct, IResource, Resource, Token } from '@aws-cdk/cdk'; -import { BuildArtifacts, CodePipelineBuildArtifacts, NoBuildArtifacts } from './artifacts'; +import { BuildArtifacts, NoBuildArtifacts } from './artifacts'; import { Cache } from './cache'; import { CfnProject } from './codebuild.generated'; -import { BuildSource, NoSource, SourceType } from './source'; +import { BuildSource, CODEPIPELINE_SOURCE_ARTIFACT_TYPE, NO_SOURCE_TYPE, NoSource } from './source'; -const CODEPIPELINE_TYPE = 'CODEPIPELINE'; const S3_BUCKET_ENV = 'SCRIPT_S3_BUCKET'; const S3_KEY_ENV = 'SCRIPT_S3_KEY'; @@ -659,7 +658,7 @@ export class Project extends ProjectBase { throw new Error(`Badge is not supported for source type ${this.source.type}`); } - const sourceJson = this.source._toSourceJSON(); + const sourceJson = this.source.toSourceJSON(); if (typeof buildSpec === 'string') { return { ...sourceJson, @@ -672,7 +671,7 @@ export class Project extends ProjectBase { ...sourceJson, buildSpec: JSON.stringify(buildSpec, undefined, 2) }; - } else if (this.source.type === SourceType.None) { + } else if (this.source.type === NO_SOURCE_TYPE) { throw new Error("If the Project's source is NoSource, you need to provide a buildSpec"); } else { return sourceJson; @@ -779,7 +778,7 @@ export class Project extends ProjectBase { */ protected validate(): string[] { const ret = new Array(); - if (this.source.type === SourceType.CodePipeline) { + if (this.source.type === CODEPIPELINE_SOURCE_ARTIFACT_TYPE) { if (this._secondarySources.length > 0) { ret.push('A Project with a CodePipeline Source cannot have secondary sources. ' + "Use the CodeBuild Pipeline Actions' `extraInputs` property instead"); @@ -851,7 +850,7 @@ export class Project extends ProjectBase { private renderSecondarySources(): CfnProject.SourceProperty[] | undefined { return this._secondarySources.length === 0 ? undefined - : this._secondarySources.map((secondarySource) => secondarySource._toSourceJSON()); + : this._secondarySources.map((secondarySource) => secondarySource.toSourceJSON()); } private renderSecondaryArtifacts(): CfnProject.ArtifactsProperty[] | undefined { @@ -914,11 +913,11 @@ export class Project extends ProjectBase { }; } - private parseArtifacts(props: ProjectProps) { + private parseArtifacts(props: ProjectProps): BuildArtifacts { if (props.artifacts) { return props.artifacts; } - if (this.source._toSourceJSON().type === CODEPIPELINE_TYPE) { + if (this.source.toSourceJSON().type === CODEPIPELINE_SOURCE_ARTIFACT_TYPE) { return new CodePipelineBuildArtifacts(); } else { return new NoBuildArtifacts(); @@ -926,10 +925,10 @@ export class Project extends ProjectBase { } private validateCodePipelineSettings(artifacts: BuildArtifacts) { - const sourceType = this.source._toSourceJSON().type; + const sourceType = this.source.toSourceJSON().type; const artifactsType = artifacts.toArtifactsJSON().type; - if ((sourceType === CODEPIPELINE_TYPE || artifactsType === CODEPIPELINE_TYPE) && + if ((sourceType === CODEPIPELINE_SOURCE_ARTIFACT_TYPE || artifactsType === CODEPIPELINE_SOURCE_ARTIFACT_TYPE) && (sourceType !== artifactsType)) { throw new Error('Both source and artifacts must be set to CodePipeline'); } @@ -1260,6 +1259,18 @@ export enum BuildEnvironmentVariableType { ParameterStore = 'PARAMETER_STORE' } +/** + * Private re-implementation of the CodePipeline Artifact, + * used in Project. + */ +class CodePipelineBuildArtifacts extends BuildArtifacts { + protected readonly type = 'CODEPIPELINE'; + + constructor() { + super({}); + } +} + /** * Extend buildSpec phases with the contents of another one */ diff --git a/packages/@aws-cdk/aws-codebuild/lib/source.ts b/packages/@aws-cdk/aws-codebuild/lib/source.ts index 5595bcc58b065..1b6de5015fd50 100644 --- a/packages/@aws-cdk/aws-codebuild/lib/source.ts +++ b/packages/@aws-cdk/aws-codebuild/lib/source.ts @@ -1,9 +1,14 @@ -import codecommit = require('@aws-cdk/aws-codecommit'); -import iam = require('@aws-cdk/aws-iam'); -import s3 = require('@aws-cdk/aws-s3'); import { CfnProject } from './codebuild.generated'; import { Project } from './project'; +export const NO_SOURCE_TYPE = 'NO_SOURCE'; +export const CODEPIPELINE_SOURCE_ARTIFACT_TYPE = 'CODEPIPELINE'; +export const S3_SOURCE_TYPE = 'S3'; +export const CODECOMMIT_SOURCE_TYPE = 'CODECOMMIT'; +export const GITHUB_SOURCE_TYPE = 'GITHUB'; +export const GITHUB_ENTERPRISE_SOURCE_TYPE = 'GITHUB_ENTERPRISE'; +export const BITBUCKET_SOURCE_TYPE = 'BITBUCKET'; + /** * Properties common to all Source classes. */ @@ -20,7 +25,7 @@ export interface BuildSourceProps { */ export abstract class BuildSource { public readonly identifier?: string; - public abstract readonly type: SourceType; + public abstract readonly type: string; public readonly badgeSupported: boolean = false; constructor(props: BuildSourceProps) { @@ -39,8 +44,7 @@ export abstract class BuildSource { return; } - /** @internal */ - public _toSourceJSON(): CfnProject.SourceProperty { + public toSourceJSON(): CfnProject.SourceProperty { const sourceProp = this.toSourceProperty(); return { sourceIdentifier: this.identifier, @@ -68,622 +72,9 @@ export abstract class BuildSource { * and because of that, you're not allowed to specify an identifier for it. */ export class NoSource extends BuildSource { - public readonly type: SourceType = SourceType.None; + public readonly type = NO_SOURCE_TYPE; constructor() { super({}); } } - -/** - * The construction properties common to all build sources that are backed by Git. - */ -export interface GitBuildSourceProps extends BuildSourceProps { - /** - * The depth of history to download. Minimum value is 0. - * If this value is 0, greater than 25, or not provided, - * then the full history is downloaded with each build of the project. - */ - readonly cloneDepth?: number; -} - -/** - * A common superclass of all build sources that are backed by Git. - */ -export abstract class GitBuildSource extends BuildSource { - private readonly cloneDepth?: number; - - protected constructor(props: GitBuildSourceProps) { - super(props); - - this.cloneDepth = props.cloneDepth; - } - - /** @internal */ - public _toSourceJSON(): CfnProject.SourceProperty { - return { - ...super._toSourceJSON(), - gitCloneDepth: this.cloneDepth - }; - } -} - -/** - * The types of webhook event actions. - */ -export enum EventAction { - /** - * A push (of a branch, or a tag) to the repository. - */ - PUSH = 'PUSH', - - /** - * Creating a Pull Request. - */ - PULL_REQUEST_CREATED = 'PULL_REQUEST_CREATED', - - /** - * Updating an Pull Request. - */ - PULL_REQUEST_UPDATED = 'PULL_REQUEST_UPDATED', - - /** - * Re-opening a previously closed Pull Request. - * Note that this event is only supported for GitHub and GitHubEnterprise sources. - */ - PULL_REQUEST_REOPENED = 'PULL_REQUEST_REOPENED', -} - -const FILE_PATH_WEBHOOK_COND = 'FILE_PATH'; - -/** - * An object that represents a group of filter conditions for a webhook. - * Every condition in a given FilterGroup must be true in order for the whole group to be true. - * You construct instances of it by calling the {@link #inEventOf} static factory method, - * and then calling various `andXyz` instance methods to create modified instances of it - * (this class is immutable). - * - * You pass instances of this class to the `webhookFilters` property when constructing a source. - */ -export class FilterGroup { - /** - * Creates a new event FilterGroup that triggers on any of the provided actions. - * - * @param actions the actions to trigger the webhook on - */ - public static inEventOf(...actions: EventAction[]): FilterGroup { - return new FilterGroup(new Set(actions), []); - } - - private readonly actions: Set; - private readonly filters: CfnProject.WebhookFilterProperty[]; - - private constructor(actions: Set, filters: CfnProject.WebhookFilterProperty[]) { - if (actions.size === 0) { - throw new Error('A filter group must contain at least one event action'); - } - this.actions = actions; - this.filters = filters; - } - - /** - * Create a new FilterGroup with an added condition: - * the event must affect the given branch. - * - * @param branchName the name of the branch (can be a regular expression) - */ - public andBranchIs(branchName: string): FilterGroup { - return this.addHeadBranchFilter(branchName, true); - } - - /** - * Create a new FilterGroup with an added condition: - * the event must not affect the given branch. - * - * @param branchName the name of the branch (can be a regular expression) - */ - public andBranchIsNot(branchName: string): FilterGroup { - return this.addHeadBranchFilter(branchName, false); - } - - /** - * Create a new FilterGroup with an added condition: - * the event must affect the given tag. - * - * @param tagName the name of the tag (can be a regular expression) - */ - public andTagIs(tagName: string): FilterGroup { - return this.addHeadTagFilter(tagName, true); - } - - /** - * Create a new FilterGroup with an added condition: - * the event must not affect the given tag. - * - * @param tagName the name of the tag (can be a regular expression) - */ - public andTagIsNot(tagName: string): FilterGroup { - return this.addHeadTagFilter(tagName, false); - } - - /** - * Create a new FilterGroup with an added condition: - * the event must affect a Git reference (ie., a branch or a tag) - * that matches the given pattern. - * - * @param pattern a regular expression - */ - public andHeadRefIs(pattern: string) { - return this.addHeadRefFilter(pattern, true); - } - - /** - * Create a new FilterGroup with an added condition: - * the event must not affect a Git reference (ie., a branch or a tag) - * that matches the given pattern. - * - * @param pattern a regular expression - */ - public andHeadRefIsNot(pattern: string) { - return this.addHeadRefFilter(pattern, false); - } - - /** - * Create a new FilterGroup with an added condition: - * the account ID of the actor initiating the event must match the given pattern. - * - * @param pattern a regular expression - */ - public andActorAccountIs(pattern: string): FilterGroup { - return this.addActorAccountId(pattern, true); - } - - /** - * Create a new FilterGroup with an added condition: - * the account ID of the actor initiating the event must not match the given pattern. - * - * @param pattern a regular expression - */ - public andActorAccountIsNot(pattern: string): FilterGroup { - return this.addActorAccountId(pattern, false); - } - - /** - * Create a new FilterGroup with an added condition: - * the Pull Request that is the source of the event must target the given base branch. - * Note that you cannot use this method if this Group contains the `PUSH` event action. - * - * @param branchName the name of the branch (can be a regular expression) - */ - public andBaseBranchIs(branchName: string): FilterGroup { - return this.addBaseBranchFilter(branchName, true); - } - - /** - * Create a new FilterGroup with an added condition: - * the Pull Request that is the source of the event must not target the given base branch. - * Note that you cannot use this method if this Group contains the `PUSH` event action. - * - * @param branchName the name of the branch (can be a regular expression) - */ - public andBaseBranchIsNot(branchName: string): FilterGroup { - return this.addBaseBranchFilter(branchName, false); - } - - /** - * Create a new FilterGroup with an added condition: - * the Pull Request that is the source of the event must target the given Git reference. - * Note that you cannot use this method if this Group contains the `PUSH` event action. - * - * @param pattern a regular expression - */ - public andBaseRefIs(pattern: string): FilterGroup { - return this.addBaseRefFilter(pattern, true); - } - - /** - * Create a new FilterGroup with an added condition: - * the Pull Request that is the source of the event must not target the given Git reference. - * Note that you cannot use this method if this Group contains the `PUSH` event action. - * - * @param pattern a regular expression - */ - public andBaseRefIsNot(pattern: string): FilterGroup { - return this.addBaseRefFilter(pattern, false); - } - - /** - * Create a new FilterGroup with an added condition: - * the push that is the source of the event must affect a file that matches the given pattern. - * Note that you can only use this method if this Group contains only the `PUSH` event action, - * and only for GitHub and GitHubEnterprise sources. - * - * @param pattern a regular expression - */ - public andFilePathIs(pattern: string): FilterGroup { - return this.addFilePathFilter(pattern, true); - } - - /** - * Create a new FilterGroup with an added condition: - * the push that is the source of the event must not affect a file that matches the given pattern. - * Note that you can only use this method if this Group contains only the `PUSH` event action, - * and only for GitHub and GitHubEnterprise sources. - * - * @param pattern a regular expression - */ - public andFilePathIsNot(pattern: string): FilterGroup { - return this.addFilePathFilter(pattern, false); - } - - /** @internal */ - public get _actions(): EventAction[] { - return set2Array(this.actions); - } - - /** @internal */ - public get _filters(): CfnProject.WebhookFilterProperty[] { - return this.filters.slice(); - } - - /** @internal */ - public _toJson(): CfnProject.WebhookFilterProperty[] { - const eventFilter: CfnProject.WebhookFilterProperty = { - type: 'EVENT', - pattern: set2Array(this.actions).join(', '), - }; - return [eventFilter].concat(this.filters); - } - - private addHeadBranchFilter(branchName: string, include: boolean): FilterGroup { - return this.addHeadRefFilter(`refs/heads/${branchName}`, include); - } - - private addHeadTagFilter(tagName: string, include: boolean): FilterGroup { - return this.addHeadRefFilter(`refs/tags/${tagName}`, include); - } - - private addHeadRefFilter(refName: string, include: boolean) { - return this.addFilter('HEAD_REF', refName, include); - } - - private addActorAccountId(accountId: string, include: boolean) { - return this.addFilter('ACTOR_ACCOUNT_ID', accountId, include); - } - - private addBaseBranchFilter(branchName: string, include: boolean): FilterGroup { - return this.addBaseRefFilter(`refs/heads/${branchName}`, include); - } - - private addBaseRefFilter(refName: string, include: boolean) { - if (this.actions.has(EventAction.PUSH)) { - throw new Error('A base reference condition cannot be added if a Group contains a PUSH event action'); - } - return this.addFilter('BASE_REF', refName, include); - } - - private addFilePathFilter(pattern: string, include: boolean): FilterGroup { - if (this.actions.size !== 1 || !this.actions.has(EventAction.PUSH)) { - throw new Error('A file path condition cannot be added if a Group contains any event action other than PUSH'); - } - return this.addFilter(FILE_PATH_WEBHOOK_COND, pattern, include); - } - - private addFilter(type: string, pattern: string, include: boolean) { - return new FilterGroup(this.actions, this.filters.concat([{ - type, - pattern, - excludeMatchedPattern: include ? undefined : true, - }])); - } -} - -/** - * The construction properties common to all third-party build sources that are backed by Git. - */ -export interface ThirdPartyGitBuildSourceProps extends GitBuildSourceProps { - /** - * Whether to send notifications on your build's start and end. - * - * @default true - */ - readonly reportBuildStatus?: boolean; - - /** - * Whether to create a webhook that will trigger a build every time an event happens in the repository. - * - * @default true if any `webhookFilters` were provided, false otherwise - */ - readonly webhook?: boolean; - - /** - * A list of webhook filters that can constraint what events in the repository will trigger a build. - * A build is triggered if any of the provided filter groups match. - * Only valid if `webhook` was not provided as false. - * - * @default every push and every Pull Request (create or update) triggers a build - */ - readonly webhookFilters?: FilterGroup[]; -} - -/** - * A common superclass of all third-party build sources that are backed by Git. - */ -export abstract class ThirdPartyGitBuildSource extends GitBuildSource { - public readonly badgeSupported: boolean = true; - protected readonly webhookFilters: FilterGroup[]; - private readonly reportBuildStatus: boolean; - private readonly webhook?: boolean; - - protected constructor(props: ThirdPartyGitBuildSourceProps) { - super(props); - - this.webhook = props.webhook; - this.reportBuildStatus = props.reportBuildStatus === undefined ? true : props.reportBuildStatus; - this.webhookFilters = props.webhookFilters || []; - } - - /** @internal */ - public _buildTriggers(): CfnProject.ProjectTriggersProperty | undefined { - const anyFilterGroupsProvided = this.webhookFilters.length > 0; - const webhook = this.webhook === undefined ? (anyFilterGroupsProvided ? true : undefined) : this.webhook; - return webhook === undefined ? undefined : { - webhook, - filterGroups: anyFilterGroupsProvided ? this.webhookFilters.map(fg => fg._toJson()) : undefined, - }; - } - - /** @internal */ - public _toSourceJSON(): CfnProject.SourceProperty { - return { - ...super._toSourceJSON(), - reportBuildStatus: this.reportBuildStatus, - }; - } -} - -/** - * Construction properties for {@link CodeCommitSource}. - */ -export interface CodeCommitSourceProps extends GitBuildSourceProps { - readonly repository: codecommit.IRepository; -} - -/** - * CodeCommit Source definition for a CodeBuild project. - */ -export class CodeCommitSource extends GitBuildSource { - public readonly type: SourceType = SourceType.CodeCommit; - private readonly repo: codecommit.IRepository; - - constructor(props: CodeCommitSourceProps) { - super(props); - this.repo = props.repository; - } - - /** - * @internal - */ - public _bind(project: Project) { - // https://docs.aws.amazon.com/codebuild/latest/userguide/setting-up.html - project.addToRolePolicy(new iam.PolicyStatement() - .addAction('codecommit:GitPull') - .addResource(this.repo.repositoryArn)); - } - - protected toSourceProperty(): any { - return { - location: this.repo.repositoryCloneUrlHttp - }; - } -} - -/** - * Construction properties for {@link S3BucketSource}. - */ -export interface S3BucketSourceProps extends BuildSourceProps { - readonly bucket: s3.IBucket; - readonly path: string; -} - -/** - * S3 bucket definition for a CodeBuild project. - */ -export class S3BucketSource extends BuildSource { - public readonly type: SourceType = SourceType.S3; - private readonly bucket: s3.IBucket; - private readonly path: string; - - constructor(props: S3BucketSourceProps) { - super(props); - this.bucket = props.bucket; - this.path = props.path; - } - - /** - * @internal - */ - public _bind(project: Project) { - this.bucket.grantRead(project); - } - - protected toSourceProperty(): any { - return { - location: `${this.bucket.bucketName}/${this.path}`, - }; - } -} - -/** - * CodePipeline Source definition for a CodeBuild Project. - * *Note*: this type cannot be used as a secondary source, - * and because of that, you're not allowed to specify an identifier for it. - */ -export class CodePipelineSource extends BuildSource { - public readonly type: SourceType = SourceType.CodePipeline; - - constructor() { - super({}); - } -} - -/** - * Construction properties for {@link GitHubSource} and {@link GitHubEnterpriseSource}. - */ -export interface GitHubSourceProps extends ThirdPartyGitBuildSourceProps { - /** - * The GitHub account/user that owns the repo. - * - * @example 'awslabs' - */ - readonly owner: string; - - /** - * The name of the repo (without the username). - * - * @example 'aws-cdk' - */ - readonly repo: string; -} - -/** - * GitHub Source definition for a CodeBuild project. - */ -export class GitHubSource extends ThirdPartyGitBuildSource { - public readonly type: SourceType = SourceType.GitHub; - private readonly httpsCloneUrl: string; - - constructor(props: GitHubSourceProps) { - super(props); - this.httpsCloneUrl = `https://github.com/${props.owner}/${props.repo}.git`; - } - - protected toSourceProperty(): any { - return { - location: this.httpsCloneUrl, - }; - } -} - -/** - * Construction properties for {@link GitHubEnterpriseSource}. - */ -export interface GitHubEnterpriseSourceProps extends ThirdPartyGitBuildSourceProps { - /** - * The HTTPS URL of the repository in your GitHub Enterprise installation. - */ - readonly httpsCloneUrl: string; - - /** - * Whether to ignore SSL errors when connecting to the repository. - * - * @default false - */ - readonly ignoreSslErrors?: boolean; -} - -/** - * GitHub Enterprise Source definition for a CodeBuild project. - */ -export class GitHubEnterpriseSource extends ThirdPartyGitBuildSource { - public readonly type: SourceType = SourceType.GitHubEnterprise; - private readonly httpsCloneUrl: string; - private readonly ignoreSslErrors?: boolean; - - constructor(props: GitHubEnterpriseSourceProps) { - super(props); - this.httpsCloneUrl = props.httpsCloneUrl; - this.ignoreSslErrors = props.ignoreSslErrors; - } - - protected toSourceProperty(): any { - return { - location: this.httpsCloneUrl, - insecureSsl: this.ignoreSslErrors, - }; - } -} - -/** - * Construction properties for {@link BitBucketSource}. - */ -export interface BitBucketSourceProps extends ThirdPartyGitBuildSourceProps { - /** - * The BitBucket account/user that owns the repo. - * - * @example 'awslabs' - */ - readonly owner: string; - - /** - * The name of the repo (without the username). - * - * @example 'aws-cdk' - */ - readonly repo: string; -} - -/** - * BitBucket Source definition for a CodeBuild project. - */ -export class BitBucketSource extends ThirdPartyGitBuildSource { - public readonly type: SourceType = SourceType.BitBucket; - private readonly httpsCloneUrl: any; - - constructor(props: BitBucketSourceProps) { - super(props); - this.httpsCloneUrl = `https://bitbucket.org/${props.owner}/${props.repo}.git`; - } - - /** @internal */ - public _buildTriggers(): CfnProject.ProjectTriggersProperty | undefined { - // BitBucket sources don't support the PULL_REQUEST_REOPENED event action - if (this.anyWebhookFilterContainsPrReopenedEventAction()) { - throw new Error('BitBucket sources do not support the PULL_REQUEST_REOPENED webhook event action'); - } - - // they also don't support file path conditions - if (this.anyWebhookFilterContainsFilePathConditions()) { - throw new Error('BitBucket sources do not support file path conditions for webhook filters'); - } - - return super._buildTriggers(); - } - - protected toSourceProperty(): any { - return { - location: this.httpsCloneUrl - }; - } - - private anyWebhookFilterContainsPrReopenedEventAction() { - return this.webhookFilters.findIndex(fg => { - return fg._actions.findIndex(a => a === EventAction.PULL_REQUEST_REOPENED) !== -1; - }) !== -1; - } - - private anyWebhookFilterContainsFilePathConditions() { - return this.webhookFilters.findIndex(fg => { - return fg._filters.findIndex(f => f.type === FILE_PATH_WEBHOOK_COND) !== -1; - }) !== -1; - } -} - -/** - * Source types for CodeBuild Project - */ -export enum SourceType { - None = 'NO_SOURCE', - CodeCommit = 'CODECOMMIT', - CodePipeline = 'CODEPIPELINE', - GitHub = 'GITHUB', - GitHubEnterprise = 'GITHUB_ENTERPRISE', - BitBucket = 'BITBUCKET', - S3 = 'S3', -} - -function set2Array(set: Set): T[] { - const ret: T[] = []; - set.forEach(el => ret.push(el)); - return ret; -} diff --git a/packages/@aws-cdk/aws-codebuild/package.json b/packages/@aws-cdk/aws-codebuild/package.json index a03fb298f8739..6ec835ad96947 100644 --- a/packages/@aws-cdk/aws-codebuild/package.json +++ b/packages/@aws-cdk/aws-codebuild/package.json @@ -50,10 +50,6 @@ "cdk-build": { "cloudformation": "AWS::CodeBuild" }, - "nyc": { - "statements": 74, - "lines": 74 - }, "keywords": [ "aws", "cdk", @@ -80,7 +76,6 @@ "@aws-cdk/assets": "^0.33.0", "@aws-cdk/assets-docker": "^0.33.0", "@aws-cdk/aws-cloudwatch": "^0.33.0", - "@aws-cdk/aws-codecommit": "^0.33.0", "@aws-cdk/aws-ec2": "^0.33.0", "@aws-cdk/aws-ecr": "^0.33.0", "@aws-cdk/aws-events": "^0.33.0", @@ -94,7 +89,6 @@ "@aws-cdk/assets": "^0.33.0", "@aws-cdk/assets-docker": "^0.33.0", "@aws-cdk/aws-cloudwatch": "^0.33.0", - "@aws-cdk/aws-codecommit": "^0.33.0", "@aws-cdk/aws-ec2": "^0.33.0", "@aws-cdk/aws-ecr": "^0.33.0", "@aws-cdk/aws-events": "^0.33.0", diff --git a/packages/@aws-cdk/aws-codebuild/test/fake-source.ts b/packages/@aws-cdk/aws-codebuild/test/fake-source.ts new file mode 100644 index 0000000000000..4401b31b42485 --- /dev/null +++ b/packages/@aws-cdk/aws-codebuild/test/fake-source.ts @@ -0,0 +1,22 @@ +import { BuildSource, CODEPIPELINE_SOURCE_ARTIFACT_TYPE } from '../lib'; + +export interface FakeSourceProps { + readonly type?: string; + + readonly identifier?: string; +} + +/** + * Fake source, used only for tests. + */ +export class FakeSource extends BuildSource { + public readonly type: string; + + constructor(props: FakeSourceProps = {}) { + super({ + identifier: props.identifier, + }); + + this.type = props.type || CODEPIPELINE_SOURCE_ARTIFACT_TYPE; + } +} diff --git a/packages/@aws-cdk/aws-codebuild/test/test.codebuild.ts b/packages/@aws-cdk/aws-codebuild/test/test.codebuild.ts index 8683696cf804b..644c74fdaff5b 100644 --- a/packages/@aws-cdk/aws-codebuild/test/test.codebuild.ts +++ b/packages/@aws-cdk/aws-codebuild/test/test.codebuild.ts @@ -1,618 +1,15 @@ import { expect, haveResource, haveResourceLike } from '@aws-cdk/assert'; -import codecommit = require('@aws-cdk/aws-codecommit'); import ec2 = require('@aws-cdk/aws-ec2'); import kms = require('@aws-cdk/aws-kms'); -import s3 = require('@aws-cdk/aws-s3'); import cdk = require('@aws-cdk/cdk'); import { Test } from 'nodeunit'; import codebuild = require('../lib'); +import { FakeSource } from "./fake-source"; // tslint:disable:object-literal-key-quotes export = { 'default properties': { - 'with CodePipeline source'(test: Test) { - const stack = new cdk.Stack(); - - const source = new codebuild.CodePipelineSource(); - new codebuild.Project(stack, 'MyProject', { - source - }); - - expect(stack).toMatch({ - "Resources": { - "MyProjectRole9BBE5233": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": { "Fn::Join": ["", ["codebuild.", { Ref: "AWS::URLSuffix" }]] } - } - } - ], - "Version": "2012-10-17" - } - } - }, - "MyProjectRoleDefaultPolicyB19B7C29": { - "Type": "AWS::IAM::Policy", - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "logs:CreateLogGroup", - "logs:CreateLogStream", - "logs:PutLogEvents" - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":logs:", - { - "Ref": "AWS::Region" - }, - ":", - { - "Ref": "AWS::AccountId" - }, - ":log-group:/aws/codebuild/", - { - "Ref": "MyProject39F7B0AE" - } - ] - ] - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":logs:", - { - "Ref": "AWS::Region" - }, - ":", - { - "Ref": "AWS::AccountId" - }, - ":log-group:/aws/codebuild/", - { - "Ref": "MyProject39F7B0AE" - }, - ":*" - ] - ] - } - ] - } - ], - "Version": "2012-10-17" - }, - "PolicyName": "MyProjectRoleDefaultPolicyB19B7C29", - "Roles": [ - { - "Ref": "MyProjectRole9BBE5233" - } - ] - } - }, - "MyProject39F7B0AE": { - "Type": "AWS::CodeBuild::Project", - "Properties": { - "Source": { - "Type": "CODEPIPELINE" - }, - "Artifacts": { - "Type": "CODEPIPELINE" - }, - "ServiceRole": { - "Fn::GetAtt": [ - "MyProjectRole9BBE5233", - "Arn" - ] - }, - "Environment": { - "Type": "LINUX_CONTAINER", - "PrivilegedMode": false, - "Image": "aws/codebuild/standard:1.0", - "ComputeType": "BUILD_GENERAL1_SMALL" - } - } - } - } - }); - - test.done(); - }, - 'with CodeCommit source'(test: Test) { - const stack = new cdk.Stack(); - - const repo = new codecommit.Repository(stack, 'MyRepo', { repositoryName: 'hello-cdk' }); - - const source = new codebuild.CodeCommitSource({ repository: repo, cloneDepth: 2 }); - - new codebuild.Project(stack, 'MyProject', { - source - }); - - expect(stack).toMatch({ - "Resources": { - "MyRepoF4F48043": { - "Type": "AWS::CodeCommit::Repository", - "Properties": { - "RepositoryName": "hello-cdk", - "Triggers": [] - } - }, - "MyProjectRole9BBE5233": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": { "Fn::Join": ["", ["codebuild.", { Ref: "AWS::URLSuffix" }]] } - } - } - ], - "Version": "2012-10-17" - } - } - }, - "MyProjectRoleDefaultPolicyB19B7C29": { - "Type": "AWS::IAM::Policy", - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "codecommit:GitPull", - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "MyRepoF4F48043", - "Arn" - ] - } - }, - { - "Action": [ - "logs:CreateLogGroup", - "logs:CreateLogStream", - "logs:PutLogEvents" - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":logs:", - { - "Ref": "AWS::Region" - }, - ":", - { - "Ref": "AWS::AccountId" - }, - ":log-group:/aws/codebuild/", - { - "Ref": "MyProject39F7B0AE" - } - ] - ] - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":logs:", - { - "Ref": "AWS::Region" - }, - ":", - { - "Ref": "AWS::AccountId" - }, - ":log-group:/aws/codebuild/", - { - "Ref": "MyProject39F7B0AE" - }, - ":*" - ] - ] - } - ] - } - ], - "Version": "2012-10-17" - }, - "PolicyName": "MyProjectRoleDefaultPolicyB19B7C29", - "Roles": [ - { - "Ref": "MyProjectRole9BBE5233" - } - ] - } - }, - "MyProject39F7B0AE": { - "Type": "AWS::CodeBuild::Project", - "Properties": { - "Artifacts": { - "Type": "NO_ARTIFACTS" - }, - "Environment": { - "ComputeType": "BUILD_GENERAL1_SMALL", - "Image": "aws/codebuild/standard:1.0", - "PrivilegedMode": false, - "Type": "LINUX_CONTAINER" - }, - "ServiceRole": { - "Fn::GetAtt": [ - "MyProjectRole9BBE5233", - "Arn" - ] - }, - "Source": { - "Location": { - "Fn::GetAtt": [ - "MyRepoF4F48043", - "CloneUrlHttp" - ] - }, - "GitCloneDepth": 2, - "Type": "CODECOMMIT" - } - } - } - } - }); - test.done(); - }, - 'with S3Bucket source'(test: Test) { - const stack = new cdk.Stack(); - const bucket = new s3.Bucket(stack, 'MyBucket'); - - new codebuild.Project(stack, 'MyProject', { - source: new codebuild.S3BucketSource({ - bucket, - path: 'path/to/source.zip', - }), - environment: { - buildImage: codebuild.WindowsBuildImage.WIN_SERVER_CORE_2016_BASE, - }, - }); - - expect(stack).toMatch({ - "Resources": { - "MyBucketF68F3FF0": { - "Type": "AWS::S3::Bucket", - "DeletionPolicy": "Retain" - }, - "MyProjectRole9BBE5233": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": { "Fn::Join": ["", ["codebuild.", { Ref: "AWS::URLSuffix" }]] } - } - } - ], - "Version": "2012-10-17" - } - } - }, - "MyProjectRoleDefaultPolicyB19B7C29": { - "Type": "AWS::IAM::Policy", - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "s3:GetObject*", - "s3:GetBucket*", - "s3:List*" - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "MyBucketF68F3FF0", - "Arn" - ] - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "MyBucketF68F3FF0", - "Arn" - ] - }, - "/*" - ] - ] - } - ] - }, - { - "Action": [ - "logs:CreateLogGroup", - "logs:CreateLogStream", - "logs:PutLogEvents" - ], - "Effect": "Allow", - "Resource": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":logs:", - { - "Ref": "AWS::Region" - }, - ":", - { - "Ref": "AWS::AccountId" - }, - ":log-group:/aws/codebuild/", - { - "Ref": "MyProject39F7B0AE" - } - ] - ] - }, - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":logs:", - { - "Ref": "AWS::Region" - }, - ":", - { - "Ref": "AWS::AccountId" - }, - ":log-group:/aws/codebuild/", - { - "Ref": "MyProject39F7B0AE" - }, - ":*" - ] - ] - } - ] - } - ], - "Version": "2012-10-17" - }, - "PolicyName": "MyProjectRoleDefaultPolicyB19B7C29", - "Roles": [ - { - "Ref": "MyProjectRole9BBE5233" - } - ] - } - }, - "MyProject39F7B0AE": { - "Type": "AWS::CodeBuild::Project", - "Properties": { - "Artifacts": { - "Type": "NO_ARTIFACTS" - }, - "Environment": { - "ComputeType": "BUILD_GENERAL1_MEDIUM", - "Image": "aws/codebuild/windows-base:1.0", - "PrivilegedMode": false, - "Type": "WINDOWS_CONTAINER" - }, - "ServiceRole": { - "Fn::GetAtt": [ - "MyProjectRole9BBE5233", - "Arn" - ] - }, - "Source": { - "Location": { - "Fn::Join": [ - "", - [ - { - "Ref": "MyBucketF68F3FF0" - }, - "/path/to/source.zip" - ] - ] - }, - "Type": "S3" - } - } - } - } - }); - test.done(); - }, - 'with GitHub source'(test: Test) { - const stack = new cdk.Stack(); - - new codebuild.Project(stack, 'Project', { - source: new codebuild.GitHubSource({ - owner: 'testowner', - repo: 'testrepo', - cloneDepth: 3, - webhook: true, - reportBuildStatus: false, - webhookFilters: [ - codebuild.FilterGroup.inEventOf(codebuild.EventAction.PUSH).andTagIsNot('stable'), - codebuild.FilterGroup.inEventOf(codebuild.EventAction.PULL_REQUEST_REOPENED).andBaseBranchIs('master'), - ], - }) - }); - - expect(stack).to(haveResource('AWS::CodeBuild::Project', { - Source: { - Type: "GITHUB", - Location: 'https://github.com/testowner/testrepo.git', - ReportBuildStatus: false, - GitCloneDepth: 3, - } - })); - - expect(stack).to(haveResourceLike('AWS::CodeBuild::Project', { - Triggers: { - Webhook: true, - FilterGroups: [ - [ - { Type: 'EVENT', Pattern: 'PUSH' }, - { Type: 'HEAD_REF', Pattern: 'refs/tags/stable', ExcludeMatchedPattern: true }, - ], - [ - { Type: 'EVENT', Pattern: 'PULL_REQUEST_REOPENED' }, - { Type: 'BASE_REF', Pattern: 'refs/heads/master' }, - ], - ], - }, - })); - - test.done(); - }, - 'with GitHubEnterprise source'(test: Test) { - const stack = new cdk.Stack(); - - const pushFilterGroup = codebuild.FilterGroup.inEventOf(codebuild.EventAction.PUSH); - new codebuild.Project(stack, 'MyProject', { - source: new codebuild.GitHubEnterpriseSource({ - httpsCloneUrl: 'https://github.testcompany.com/testowner/testrepo', - ignoreSslErrors: true, - cloneDepth: 4, - webhook: true, - reportBuildStatus: false, - webhookFilters: [ - pushFilterGroup.andBranchIs('master'), - pushFilterGroup.andBranchIs('develop'), - pushFilterGroup.andFilePathIs('ReadMe.md'), - ], - }) - }); - - expect(stack).to(haveResource('AWS::CodeBuild::Project', { - Source: { - Type: "GITHUB_ENTERPRISE", - InsecureSsl: true, - GitCloneDepth: 4, - ReportBuildStatus: false, - Location: 'https://github.testcompany.com/testowner/testrepo' - } - })); - - expect(stack).to(haveResourceLike('AWS::CodeBuild::Project', { - Triggers: { - Webhook: true, - FilterGroups: [ - [ - { Type: 'EVENT', Pattern: 'PUSH' }, - { Type: 'HEAD_REF', Pattern: 'refs/heads/master' }, - ], - [ - { Type: 'EVENT', Pattern: 'PUSH' }, - { Type: 'HEAD_REF', Pattern: 'refs/heads/develop' }, - ], - [ - { Type: 'EVENT', Pattern: 'PUSH' }, - { Type: 'FILE_PATH', Pattern: 'ReadMe.md' }, - ], - ], - }, - })); - - test.done(); - }, - 'with Bitbucket source'(test: Test) { - const stack = new cdk.Stack(); - - new codebuild.Project(stack, 'Project', { - source: new codebuild.BitBucketSource({ - owner: 'testowner', - repo: 'testrepo', - cloneDepth: 5, - reportBuildStatus: false, - webhookFilters: [ - codebuild.FilterGroup.inEventOf( - codebuild.EventAction.PULL_REQUEST_CREATED, - codebuild.EventAction.PULL_REQUEST_UPDATED, - ).andTagIs('v.*'), - // duplicate event actions are fine - codebuild.FilterGroup.inEventOf(codebuild.EventAction.PUSH, codebuild.EventAction.PUSH).andActorAccountIsNot('aws-cdk-dev'), - ], - }) - }); - - expect(stack).to(haveResource('AWS::CodeBuild::Project', { - Source: { - Type: 'BITBUCKET', - Location: 'https://bitbucket.org/testowner/testrepo.git', - GitCloneDepth: 5, - ReportBuildStatus: false, - }, - })); - - expect(stack).to(haveResourceLike('AWS::CodeBuild::Project', { - Triggers: { - Webhook: true, - FilterGroups: [ - [ - { Type: 'EVENT', Pattern: 'PULL_REQUEST_CREATED, PULL_REQUEST_UPDATED' }, - { Type: 'HEAD_REF', Pattern: 'refs/tags/v.*' }, - ], - [ - { Type: 'EVENT', Pattern: 'PUSH' }, - { Type: 'ACTOR_ACCOUNT_ID', Pattern: 'aws-cdk-dev', ExcludeMatchedPattern: true }, - ], - ], - }, - })); - - test.done(); - }, 'fail creating a Project when no build spec is given'(test: Test) { const stack = new cdk.Stack(); @@ -623,10 +20,10 @@ export = { test.done(); }, + 'with VPC configuration'(test: Test) { const stack = new cdk.Stack(); - const bucket = new s3.Bucket(stack, 'MyBucket'); const vpc = new ec2.Vpc(stack, 'MyVPC'); const securityGroup = new ec2.SecurityGroup(stack, 'SecurityGroup1', { groupName: 'Bob', @@ -635,10 +32,7 @@ export = { description: 'Example', }); new codebuild.Project(stack, 'MyProject', { - source: new codebuild.S3BucketSource({ - bucket, - path: 'path/to/source.zip', - }), + source: new FakeSource(), vpc, securityGroups: [securityGroup] }); @@ -670,10 +64,10 @@ export = { })); test.done(); }, + 'without VPC configuration but security group identified'(test: Test) { const stack = new cdk.Stack(); - const bucket = new s3.Bucket(stack, 'MyBucket'); const vpc = new ec2.Vpc(stack, 'MyVPC'); const securityGroup = new ec2.SecurityGroup(stack, 'SecurityGroup1', { groupName: 'Bob', @@ -684,18 +78,15 @@ export = { test.throws(() => new codebuild.Project(stack, 'MyProject', { - source: new codebuild.S3BucketSource({ - bucket, - path: 'path/to/source.zip', - }), + source: new FakeSource(), securityGroups: [securityGroup] }) , /Cannot configure 'securityGroup' or 'allowAllOutbound' without configuring a VPC/); test.done(); }, + 'with VPC configuration but allowAllOutbound identified'(test: Test) { const stack = new cdk.Stack(); - const bucket = new s3.Bucket(stack, 'MyBucket'); const vpc = new ec2.Vpc(stack, 'MyVPC'); const securityGroup = new ec2.SecurityGroup(stack, 'SecurityGroup1', { groupName: 'Bob', @@ -705,10 +96,7 @@ export = { }); test.throws(() => new codebuild.Project(stack, 'MyProject', { - source: new codebuild.S3BucketSource({ - bucket, - path: 'path/to/source.zip', - }), + source: new FakeSource(), vpc, allowAllOutbound: true, securityGroups: [securityGroup] @@ -758,33 +146,6 @@ export = { }, }, - 'using timeout and path in S3 artifacts sets it correctly'(test: Test) { - const stack = new cdk.Stack(); - const bucket = new s3.Bucket(stack, 'Bucket'); - new codebuild.Project(stack, 'Project', { - buildSpec: { - version: '0.2', - }, - artifacts: new codebuild.S3BucketBuildArtifacts({ - path: 'some/path', - name: 'some_name', - bucket, - }), - timeout: 123, - }); - - expect(stack).to(haveResourceLike('AWS::CodeBuild::Project', { - "Artifacts": { - "Path": "some/path", - "Name": "some_name", - "Type": "S3", - }, - "TimeoutInMinutes": 123, - })); - - test.done(); - }, - 'secondary sources': { 'require providing an identifier when creating a Project'(test: Test) { const stack = new cdk.Stack(); @@ -795,7 +156,7 @@ export = { version: '0.2', }, secondarySources: [ - new codebuild.CodePipelineSource(), + new FakeSource(), ], }); }, /identifier/); @@ -806,12 +167,11 @@ export = { 'are not allowed for a Project with CodePipeline as Source'(test: Test) { const stack = new cdk.Stack(); const project = new codebuild.Project(stack, 'MyProject', { - source: new codebuild.CodePipelineSource(), + source: new FakeSource({ type: codebuild.CODEPIPELINE_SOURCE_ARTIFACT_TYPE }), }); - project.addSecondarySource(new codebuild.S3BucketSource({ - bucket: new s3.Bucket(stack, 'MyBucket'), - path: 'some/path', + project.addSecondarySource(new FakeSource({ + type: codebuild.S3_SOURCE_TYPE, identifier: 'id', })); @@ -822,20 +182,15 @@ export = { test.done(); }, - 'added with an identifer after the Project has been created are rendered in the template'(test: Test) { + 'added with an identifier after the Project has been created are rendered in the template'(test: Test) { const stack = new cdk.Stack(); - const bucket = new s3.Bucket(stack, 'MyBucket'); const project = new codebuild.Project(stack, 'MyProject', { - source: new codebuild.S3BucketSource({ - bucket, - path: 'some/path', - }), + source: new FakeSource({ type: codebuild.S3_SOURCE_TYPE }), }); - project.addSecondarySource(new codebuild.S3BucketSource({ - bucket, - path: 'another/path', + project.addSecondarySource(new FakeSource({ identifier: 'source1', + type: codebuild.S3_SOURCE_TYPE, })); expect(stack).to(haveResourceLike('AWS::CodeBuild::Project', { @@ -851,167 +206,10 @@ export = { }, }, - 'secondary artifacts': { - 'require providing an identifier when creating a Project'(test: Test) { - const stack = new cdk.Stack(); - - test.throws(() => { - new codebuild.Project(stack, 'MyProject', { - buildSpec: { - version: '0.2', - }, - secondaryArtifacts: [ - new codebuild.S3BucketBuildArtifacts({ - bucket: new s3.Bucket(stack, 'MyBucket'), - path: 'some/path', - name: 'name', - }), - ], - }); - }, /identifier/); - - test.done(); - }, - - 'are not allowed for a Project with CodePipeline as Source'(test: Test) { - const stack = new cdk.Stack(); - const project = new codebuild.Project(stack, 'MyProject', { - source: new codebuild.CodePipelineSource(), - }); - - project.addSecondaryArtifact(new codebuild.S3BucketBuildArtifacts({ - bucket: new s3.Bucket(stack, 'MyBucket'), - path: 'some/path', - name: 'name', - identifier: 'id', - })); - - test.throws(() => { - expect(stack); - }, /secondary artifacts/); - - test.done(); - }, - - 'added with an identifier after the Project has been created are rendered in the template'(test: Test) { - const stack = new cdk.Stack(); - const bucket = new s3.Bucket(stack, 'MyBucket'); - const project = new codebuild.Project(stack, 'MyProject', { - source: new codebuild.S3BucketSource({ - bucket, - path: 'some/path', - }), - }); - - project.addSecondaryArtifact(new codebuild.S3BucketBuildArtifacts({ - bucket, - path: 'another/path', - name: 'name', - identifier: 'artifact1', - })); - - expect(stack).to(haveResourceLike('AWS::CodeBuild::Project', { - "SecondaryArtifacts": [ - { - "ArtifactIdentifier": "artifact1", - "Type": "S3", - }, - ], - })); - - test.done(); - }, - }, - - 'artifacts': { - 'CodePipeline': { - 'both source and artifacs are set to CodePipeline'(test: Test) { - const stack = new cdk.Stack(); - - new codebuild.Project(stack, 'MyProject', { - source: new codebuild.CodePipelineSource(), - artifacts: new codebuild.CodePipelineBuildArtifacts() - }); - - expect(stack).to(haveResource('AWS::CodeBuild::Project', { - "Source": { - "Type": "CODEPIPELINE" - }, - "Artifacts": { - "Type": "CODEPIPELINE" - }, - "ServiceRole": { - "Fn::GetAtt": [ - "MyProjectRole9BBE5233", - "Arn" - ] - }, - "Environment": { - "Type": "LINUX_CONTAINER", - "PrivilegedMode": false, - "Image": "aws/codebuild/standard:1.0", - "ComputeType": "BUILD_GENERAL1_SMALL" - } - })); - - test.done(); - }, - - 'if source is set to CodePipeline, and artifacts are not set, they are defaulted to CodePipeline'(test: Test) { - const stack = new cdk.Stack(); - - new codebuild.Project(stack, 'MyProject', { - source: new codebuild.CodePipelineSource() - }); - - expect(stack).to(haveResource('AWS::CodeBuild::Project', { - "Source": { - "Type": "CODEPIPELINE" - }, - "Artifacts": { - "Type": "CODEPIPELINE" - }, - "ServiceRole": { - "Fn::GetAtt": [ - "MyProjectRole9BBE5233", - "Arn" - ] - }, - "Environment": { - "Type": "LINUX_CONTAINER", - "PrivilegedMode": false, - "Image": "aws/codebuild/standard:1.0", - "ComputeType": "BUILD_GENERAL1_SMALL" - } - })); - - test.done(); - }, - - 'fails if one of source/artifacts is set to CodePipeline and the other isn\'t'(test: Test) { - const stack = new cdk.Stack(); - - test.throws(() => new codebuild.Project(stack, 'MyProject', { - source: new codebuild.CodePipelineSource(), - artifacts: new codebuild.NoBuildArtifacts() - }), /Both source and artifacts must be set to CodePipeline/); - - test.throws(() => new codebuild.Project(stack, 'YourProject', { - source: new codebuild.CodeCommitSource({ - repository: new codecommit.Repository(stack, 'MyRepo', { repositoryName: 'boo' }) - }), - artifacts: new codebuild.CodePipelineBuildArtifacts() - }), /Both source and artifacts must be set to CodePipeline/); - - test.done(); - } - } - }, - 'events'(test: Test) { const stack = new cdk.Stack(); const project = new codebuild.Project(stack, 'MyProject', { - source: new codebuild.CodePipelineSource() + source: new FakeSource() }); project.onBuildFailed('OnBuildFailed', { target: { bind: () => ({ arn: 'ARN', id: 'ID' }) }}); @@ -1131,7 +329,7 @@ export = { const stack = new cdk.Stack(); new codebuild.Project(stack, 'Project', { - source: new codebuild.CodePipelineSource(), + source: new FakeSource(), environment: { environmentVariables: { FOO: { value: '1234' }, @@ -1196,7 +394,7 @@ export = { '.metricXxx() methods can be used to obtain Metrics for CodeBuild projects'(test: Test) { const stack = new cdk.Stack(); - const project = new codebuild.Project(stack, 'MyBuildProject', { source: new codebuild.CodePipelineSource() }); + const project = new codebuild.Project(stack, 'MyBuildProject', { source: new FakeSource() }); const metricBuilds = project.metricBuilds(); test.same(metricBuilds.dimensions!.ProjectName, project.projectName); @@ -1224,112 +422,11 @@ export = { test.throws(() => { new codebuild.Project(stack, 'MyProject', { - source: new codebuild.CodePipelineSource(), + source: new FakeSource(), environment: invalidEnvironment, }); }, /Windows images do not support the Small ComputeType/); test.done(); }, - - 'badge support test'(test: Test) { - const stack = new cdk.Stack(); - - interface BadgeValidationTestCase { - source: codebuild.BuildSource, - shouldPassValidation: boolean - } - - const repo = new codecommit.Repository(stack, 'MyRepo', { repositoryName: 'hello-cdk' }); - const bucket = new s3.Bucket(stack, 'MyBucket'); - - const cases: BadgeValidationTestCase[] = [ - { source: new codebuild.NoSource(), shouldPassValidation: false }, - { source: new codebuild.CodePipelineSource(), shouldPassValidation: false }, - { source: new codebuild.CodeCommitSource({ repository: repo }), shouldPassValidation: false }, - { source: new codebuild.S3BucketSource({ bucket, path: 'path/to/source.zip' }), shouldPassValidation: false }, - { source: new codebuild.GitHubSource({ owner: 'awslabs', repo: 'aws-cdk' }), shouldPassValidation: true }, - { source: new codebuild.GitHubEnterpriseSource({ httpsCloneUrl: 'url' }), shouldPassValidation: true }, - { source: new codebuild.BitBucketSource({ owner: 'awslabs', repo: 'aws-cdk' }), shouldPassValidation: true } - ]; - - cases.forEach(testCase => { - const source = testCase.source; - const validationBlock = () => { new codebuild.Project(stack, `MyProject-${source.type}`, { source, badge: true }); }; - if (testCase.shouldPassValidation) { - test.doesNotThrow(validationBlock, Error, `Badge is not supported for source type ${source.type}`); - } else { - test.throws(validationBlock, Error, `Badge is not supported for source type ${source.type}`); - } - }); - - test.done(); - }, - - 'webhook Filters': { - 'a Group cannot be created with an empty set of event actions'(test: Test) { - test.throws(() => { - codebuild.FilterGroup.inEventOf(); - }, /A filter group must contain at least one event action/); - - test.done(); - }, - - 'cannot have base ref conditions if the Group contains the PUSH action'(test: Test) { - const filterGroup = codebuild.FilterGroup.inEventOf(codebuild.EventAction.PULL_REQUEST_CREATED, - codebuild.EventAction.PUSH); - - test.throws(() => { - filterGroup.andBaseRefIs('.*'); - }, /A base reference condition cannot be added if a Group contains a PUSH event action/); - - test.done(); - }, - - 'cannot have file path conditions if the Group contains any action other than PUSH'(test: Test) { - const filterGroup = codebuild.FilterGroup.inEventOf(codebuild.EventAction.PULL_REQUEST_CREATED, - codebuild.EventAction.PUSH); - - test.throws(() => { - filterGroup.andFilePathIsNot('.*\\.java'); - }, /A file path condition cannot be added if a Group contains any event action other than PUSH/); - - test.done(); - }, - - 'BitBucket sources do not support the PULL_REQUEST_REOPENED event action'(test: Test) { - const stack = new cdk.Stack(); - - test.throws(() => { - new codebuild.Project(stack, 'Project', { - source: new codebuild.BitBucketSource({ - owner: 'owner', - repo: 'repo', - webhookFilters: [ - codebuild.FilterGroup.inEventOf(codebuild.EventAction.PULL_REQUEST_REOPENED), - ], - }), - }); - }, /BitBucket sources do not support the PULL_REQUEST_REOPENED webhook event action/); - - test.done(); - }, - - 'BitBucket sources do not support file path conditions'(test: Test) { - const stack = new cdk.Stack(); - const filterGroup = codebuild.FilterGroup.inEventOf(codebuild.EventAction.PUSH).andFilePathIs('.*'); - - test.throws(() => { - new codebuild.Project(stack, 'Project', { - source: new codebuild.BitBucketSource({ - owner: 'owner', - repo: 'repo', - webhookFilters: [filterGroup], - }), - }); - }, /BitBucket sources do not support file path conditions for webhook filters/); - - test.done(); - }, - }, }; diff --git a/packages/@aws-cdk/aws-codebuild/test/test.project.ts b/packages/@aws-cdk/aws-codebuild/test/test.project.ts index 174ce65be4eae..fb3502daa17e2 100644 --- a/packages/@aws-cdk/aws-codebuild/test/test.project.ts +++ b/packages/@aws-cdk/aws-codebuild/test/test.project.ts @@ -1,4 +1,4 @@ -import { expect, haveResource, haveResourceLike, not } from '@aws-cdk/assert'; +import { expect, 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'); @@ -6,6 +6,7 @@ import { Test } from 'nodeunit'; import path = require('path'); import codebuild = require('../lib'); import { Cache, LocalCacheMode } from '../lib/cache'; +import { FakeSource } from './fake-source'; // tslint:disable:object-literal-key-quotes @@ -16,7 +17,7 @@ export = { // WHEN new codebuild.Project(stack, 'Project', { - source: new codebuild.CodePipelineSource(), + source: new FakeSource(), buildSpec: 'hello.yml', }); @@ -36,7 +37,7 @@ export = { // WHEN new codebuild.Project(stack, 'Project', { - source: new codebuild.CodePipelineSource(), + source: new FakeSource(), buildSpec: { phases: ['say hi'] } }); @@ -50,80 +51,6 @@ export = { test.done(); }, - 'GitHub source': { - 'has reportBuildStatus on by default'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - - // WHEN - new codebuild.Project(stack, 'Project', { - source: new codebuild.GitHubSource({ - owner: 'testowner', - repo: 'testrepo', - cloneDepth: 3, - }) - }); - - // THEN - expect(stack).to(haveResource('AWS::CodeBuild::Project', { - Source: { - Type: "GITHUB", - Location: 'https://github.com/testowner/testrepo.git', - ReportBuildStatus: true, - GitCloneDepth: 3, - } - })); - - test.done(); - }, - - 'can explicitly set reportBuildStatus to false'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - - // WHEN - new codebuild.Project(stack, 'Project', { - source: new codebuild.GitHubSource({ - owner: 'testowner', - repo: 'testrepo', - reportBuildStatus: false, - }) - }); - - // THEN - expect(stack).to(haveResourceLike('AWS::CodeBuild::Project', { - Source: { - ReportBuildStatus: false, - }, - })); - - test.done(); - }, - - 'can explicitly set webhook to true'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - - // WHEN - new codebuild.Project(stack, 'Project', { - source: new codebuild.GitHubSource({ - owner: 'testowner', - repo: 'testrepo', - webhook: true, - }) - }); - - // THEN - expect(stack).to(haveResourceLike('AWS::CodeBuild::Project', { - Triggers: { - Webhook: true, - }, - })); - - test.done(); - }, - }, - 'construct from asset'(test: Test) { // GIVEN const stack = new cdk.Stack(); @@ -171,7 +98,7 @@ export = { // WHEN new codebuild.Project(stack, 'Project', { - source: new codebuild.CodePipelineSource(), + source: new FakeSource(), cache: Cache.bucket(new Bucket(stack, 'Bucket'), { prefix: "cache-prefix" }) @@ -204,7 +131,7 @@ export = { // WHEN new codebuild.Project(stack, 'Project', { - source: new codebuild.CodePipelineSource(), + source: new FakeSource(), cache: Cache.local(LocalCacheMode.Custom, LocalCacheMode.DockerLayer, LocalCacheMode.Source) }); @@ -229,7 +156,7 @@ export = { // WHEN new codebuild.Project(stack, 'Project', { - source: new codebuild.CodePipelineSource() + source: new FakeSource() }); // THEN diff --git a/packages/@aws-cdk/aws-codepipeline-actions/package.json b/packages/@aws-cdk/aws-codepipeline-actions/package.json index bc337b321719f..675a8d6093aca 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/package.json +++ b/packages/@aws-cdk/aws-codepipeline-actions/package.json @@ -15,8 +15,8 @@ } }, "dotnet": { - "namespace": "Amazon.CDK.AWS.Codepipeline.Actions", - "packageId": "Amazon.CDK.AWS.Codepipeline.Actions", + "namespace": "Amazon.CDK.AWS.CodePipeline.Actions", + "packageId": "Amazon.CDK.AWS.CodePipeline.Actions", "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" },