From dd903ea4b2b64d8401be5e4969aaf434b5e1ed93 Mon Sep 17 00:00:00 2001 From: Kaixiang Zhao Date: Wed, 17 Apr 2019 21:57:25 -0700 Subject: [PATCH] feat(codebuild): add filter groups feature for GitHub, GitHubEnterprise and BitBucket Fixes #1842 --- packages/@aws-cdk/aws-codebuild/lib/source.ts | 137 +++++++++++++----- .../aws-codebuild/test/test.codebuild.ts | 101 +++++++++++++ 2 files changed, 203 insertions(+), 35 deletions(-) diff --git a/packages/@aws-cdk/aws-codebuild/lib/source.ts b/packages/@aws-cdk/aws-codebuild/lib/source.ts index 75c03a90aba82..51c8efb8c33d9 100644 --- a/packages/@aws-cdk/aws-codebuild/lib/source.ts +++ b/packages/@aws-cdk/aws-codebuild/lib/source.ts @@ -89,7 +89,6 @@ export interface GitBuildSourceProps extends BuildSourceProps { * A common superclass of all build sources that are backed by Git. */ export abstract class GitBuildSource extends BuildSource { - public readonly badgeSupported: boolean = true; private readonly cloneDepth?: number; protected constructor(props: GitBuildSourceProps) { @@ -106,6 +105,67 @@ export abstract class GitBuildSource extends BuildSource { } } +/** + * The construction properties common to all external build sources that are backed by Git. + */ +export interface ExternalGitBuildSourceProps extends GitBuildSourceProps { + /** + * Whether to create a webhook that will trigger a build every time a commit is pushed to the repository. + * + * @default false + */ + readonly webhook?: boolean; + + /** + * Whether to send notifications on your build's start and end. + * + * @default true + */ + readonly reportBuildStatus?: boolean; + + /** + * A list of lists of WebhookFilter objects used to determine which webhook events are triggered. + */ + readonly filterGroups?: WebhookFilter[][]; +} + +/** + * A common superclass of all external build sources that are backed by Git. + */ +export abstract class ExternalGitBuildSource extends GitBuildSource { + public readonly badgeSupported: boolean = true; + private readonly reportBuildStatus: boolean; + private readonly webhook?: boolean; + private readonly filterGroups?: WebhookFilter[][]; + + protected constructor(props: ExternalGitBuildSourceProps) { + super(props); + + this.webhook = props.webhook; + this.reportBuildStatus = props.reportBuildStatus === undefined ? true : props.reportBuildStatus; + this.filterGroups = props.filterGroups; + } + + public buildTriggers(): CfnProject.ProjectTriggersProperty | undefined { + if (!this.webhook && this.filterGroups) { + throw new Error("filterGroups property could only be set when webhook property is true"); + } + return this.webhook === undefined + ? undefined + : { + webhook: this.webhook, + filterGroups: this.filterGroups, + }; + } + + public toSourceJSON(): CfnProject.SourceProperty { + return { + ...super.toSourceJSON(), + reportBuildStatus: this.reportBuildStatus, + }; + } +} + /** * Construction properties for {@link CodeCommitSource}. */ @@ -118,7 +178,6 @@ export interface CodeCommitSourceProps extends GitBuildSourceProps { */ export class CodeCommitSource extends GitBuildSource { public readonly type: SourceType = SourceType.CodeCommit; - public readonly badgeSupported: boolean = false; private readonly repo: codecommit.IRepository; constructor(props: CodeCommitSourceProps) { @@ -195,7 +254,7 @@ export class CodePipelineSource extends BuildSource { /** * Construction properties for {@link GitHubSource} and {@link GitHubEnterpriseSource}. */ -export interface GitHubSourceProps extends GitBuildSourceProps { +export interface GitHubSourceProps extends ExternalGitBuildSourceProps { /** * The GitHub account/user that owns the repo. * @@ -209,50 +268,23 @@ export interface GitHubSourceProps extends GitBuildSourceProps { * @example 'aws-cdk' */ readonly repo: string; - - /** - * Whether to create a webhook that will trigger a build every time a commit is pushed to the GitHub repository. - * - * @default false - */ - readonly webhook?: boolean; - - /** - * Whether to send GitHub notifications on your build's start and end. - * - * @default true - */ - readonly reportBuildStatus?: boolean; } /** * GitHub Source definition for a CodeBuild project. */ -export class GitHubSource extends GitBuildSource { +export class GitHubSource extends ExternalGitBuildSource { public readonly type: SourceType = SourceType.GitHub; private readonly httpsCloneUrl: string; - private readonly reportBuildStatus: boolean; - private readonly webhook?: boolean; constructor(props: GitHubSourceProps) { super(props); this.httpsCloneUrl = `https://github.com/${props.owner}/${props.repo}.git`; - this.webhook = props.webhook; - this.reportBuildStatus = props.reportBuildStatus === undefined ? true : props.reportBuildStatus; - } - - public buildTriggers(): CfnProject.ProjectTriggersProperty | undefined { - return this.webhook === undefined - ? undefined - : { - webhook: this.webhook, - }; } protected toSourceProperty(): any { return { location: this.httpsCloneUrl, - reportBuildStatus: this.reportBuildStatus, }; } } @@ -260,7 +292,7 @@ export class GitHubSource extends GitBuildSource { /** * Construction properties for {@link GitHubEnterpriseSource}. */ -export interface GitHubEnterpriseSourceProps extends GitBuildSourceProps { +export interface GitHubEnterpriseSourceProps extends ExternalGitBuildSourceProps { /** * The HTTPS URL of the repository in your GitHub Enterprise installation. */ @@ -277,7 +309,7 @@ export interface GitHubEnterpriseSourceProps extends GitBuildSourceProps { /** * GitHub Enterprise Source definition for a CodeBuild project. */ -export class GitHubEnterpriseSource extends GitBuildSource { +export class GitHubEnterpriseSource extends ExternalGitBuildSource { public readonly type: SourceType = SourceType.GitHubEnterprise; private readonly httpsCloneUrl: string; private readonly ignoreSslErrors?: boolean; @@ -299,7 +331,7 @@ export class GitHubEnterpriseSource extends GitBuildSource { /** * Construction properties for {@link BitBucketSource}. */ -export interface BitBucketSourceProps extends GitBuildSourceProps { +export interface BitBucketSourceProps extends ExternalGitBuildSourceProps { /** * The BitBucket account/user that owns the repo. * @@ -318,7 +350,7 @@ export interface BitBucketSourceProps extends GitBuildSourceProps { /** * BitBucket Source definition for a CodeBuild project. */ -export class BitBucketSource extends GitBuildSource { +export class BitBucketSource extends ExternalGitBuildSource { public readonly type: SourceType = SourceType.BitBucket; private readonly httpsCloneUrl: any; @@ -334,6 +366,41 @@ export class BitBucketSource extends GitBuildSource { } } +/** + * Filter used to determine which webhooks trigger a build + */ +export interface WebhookFilter { + /** + * The type of webhook filter. + */ + readonly type: WebhookFilterType, + + /** + * A regular expression pattern. + */ + readonly pattern: string, + + /** + * Used to indicate that the pattern determines which webhook events do not trigger a build. + * If true, then a webhook event that does not match the pattern triggers a build. + * If false, then a webhook event that matches the pattern triggers a build. + * + * @default false + */ + readonly excludeMatchedPattern?: boolean, +} + +/** + * Filter types for webhook filters + */ +export enum WebhookFilterType { + Event = 'EVENT', + ActorAccountId = 'ACTOR_ACCOUNT_ID', + HeadRef = 'HEAD_REF', + BaseRef = 'BASE_REF', + FilePath = 'FILE_PATH', +} + /** * Source types for CodeBuild Project */ diff --git a/packages/@aws-cdk/aws-codebuild/test/test.codebuild.ts b/packages/@aws-cdk/aws-codebuild/test/test.codebuild.ts index 210d13b43b5dd..b41d94bbcf3fd 100644 --- a/packages/@aws-cdk/aws-codebuild/test/test.codebuild.ts +++ b/packages/@aws-cdk/aws-codebuild/test/test.codebuild.ts @@ -5,6 +5,7 @@ import s3 = require('@aws-cdk/aws-s3'); import cdk = require('@aws-cdk/cdk'); import { Test } from 'nodeunit'; import codebuild = require('../lib'); +import { WebhookFilterType } from '../lib'; // tslint:disable:object-literal-key-quotes @@ -480,6 +481,16 @@ export = { cloneDepth: 3, webhook: true, reportBuildStatus: false, + filterGroups: [ + [ + { type: WebhookFilterType.Event, pattern: 'PUSH' }, + { type: WebhookFilterType.HeadRef, pattern: 'master', excludeMatchedPattern: false }, + ], + [ + { type: WebhookFilterType.Event, pattern: 'PULL_REQUEST_OPEN' }, + { type: WebhookFilterType.BaseRef, pattern: 'master' }, + ], + ], }) }); @@ -495,6 +506,16 @@ export = { expect(stack).to(haveResourceLike('AWS::CodeBuild::Project', { Triggers: { Webhook: true, + FilterGroups: [ + [ + { Type: 'EVENT', Pattern: 'PUSH' }, + { Type: 'HEAD_REF', Pattern: 'master', ExcludeMatchedPattern: false }, + ], + [ + { Type: 'EVENT', Pattern: 'PULL_REQUEST_OPEN' }, + { Type: 'BASE_REF', Pattern: 'master' }, + ], + ], }, })); @@ -508,6 +529,18 @@ export = { httpsCloneUrl: 'https://github.testcompany.com/testowner/testrepo', ignoreSslErrors: true, cloneDepth: 4, + webhook: true, + reportBuildStatus: false, + filterGroups: [ + [ + { type: WebhookFilterType.Event, pattern: 'PUSH' }, + { type: WebhookFilterType.HeadRef, pattern: 'master', excludeMatchedPattern: false }, + ], + [ + { type: WebhookFilterType.Event, pattern: 'PULL_REQUEST_OPEN' }, + { type: WebhookFilterType.BaseRef, pattern: 'master' }, + ], + ], }) }); @@ -516,10 +549,27 @@ export = { 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: 'master', ExcludeMatchedPattern: false }, + ], + [ + { Type: 'EVENT', Pattern: 'PULL_REQUEST_OPEN' }, + { Type: 'BASE_REF', Pattern: 'master' }, + ], + ], + }, + })); + test.done(); }, 'with Bitbucket source'(test: Test) { @@ -530,6 +580,18 @@ export = { owner: 'testowner', repo: 'testrepo', cloneDepth: 5, + webhook: true, + reportBuildStatus: false, + filterGroups: [ + [ + { type: WebhookFilterType.Event, pattern: 'PUSH' }, + { type: WebhookFilterType.HeadRef, pattern: 'master', excludeMatchedPattern: false }, + ], + [ + { type: WebhookFilterType.Event, pattern: 'PULL_REQUEST_OPEN' }, + { type: WebhookFilterType.BaseRef, pattern: 'master' }, + ], + ], }) }); @@ -538,6 +600,23 @@ export = { 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: 'PUSH' }, + { Type: 'HEAD_REF', Pattern: 'master', ExcludeMatchedPattern: false }, + ], + [ + { Type: 'EVENT', Pattern: 'PULL_REQUEST_OPEN' }, + { Type: 'BASE_REF', Pattern: 'master' }, + ], + ], }, })); @@ -1153,6 +1232,28 @@ export = { } }); + test.done(); + }, + + 'filter groups validation'(test: Test) { + const stack = new cdk.Stack(); + + // should throw exception when webhook is not specified but filterGroups is + test.throws(() => { + new codebuild.Project(stack, 'Project', { + source: new codebuild.GitHubSource({ + owner: 'testowner', + repo: 'testrepo', + cloneDepth: 3, + filterGroups: [ + [ + { type: WebhookFilterType.Event, pattern: 'PUSH' } + ] + ], + }) + }); + }, Error, "filterGroups property could only be set when webhook property is true"); + test.done(); } };