From fd74d0704d5b4657862cb27c958a99c3cc88b00c Mon Sep 17 00:00:00 2001 From: Kaixiang-AWS Date: Thu, 2 May 2019 13:13:57 -0700 Subject: [PATCH] feat(codebuild): add webhook Filter Groups. (#2319) Fixes #1842 --- packages/@aws-cdk/aws-codebuild/README.md | 7 +- .../@aws-cdk/aws-codebuild/lib/project.ts | 10 +- packages/@aws-cdk/aws-codebuild/lib/source.ts | 419 ++++++++++++++++-- .../aws-codebuild/test/test.codebuild.ts | 138 +++++- 4 files changed, 527 insertions(+), 47 deletions(-) diff --git a/packages/@aws-cdk/aws-codebuild/README.md b/packages/@aws-cdk/aws-codebuild/README.md index 80d86ecae79f9..5e3602d611718 100644 --- a/packages/@aws-cdk/aws-codebuild/README.md +++ b/packages/@aws-cdk/aws-codebuild/README.md @@ -90,7 +90,10 @@ Example: const gitHubSource = new codebuild.GitHubSource({ owner: 'awslabs', repo: 'aws-cdk', - webhook: true, // optional, default: false + webhook: true, // optional, default: true if `webhookFilteres` were provided, false otherwise + webhookFilters: [ + codebuild.FilterGroup.inEventOf(codebuild.EventAction.PUSH).andBranchIs('master'), + ], // optional, by default all pushes and Pull Requests will trigger a build }); ``` @@ -283,4 +286,4 @@ new Project(stack, 'MyProject', { securityGroups: [securityGroup], vpc: vpc }); -``` \ No newline at end of file +``` diff --git a/packages/@aws-cdk/aws-codebuild/lib/project.ts b/packages/@aws-cdk/aws-codebuild/lib/project.ts index 816826f5310a2..2f6c7f016531e 100644 --- a/packages/@aws-cdk/aws-codebuild/lib/project.ts +++ b/packages/@aws-cdk/aws-codebuild/lib/project.ts @@ -625,7 +625,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, @@ -670,7 +670,7 @@ export class Project extends ProjectBase { timeoutInMinutes: props.timeout, secondarySources: new Token(() => this.renderSecondarySources()), secondaryArtifacts: new Token(() => this.renderSecondaryArtifacts()), - triggers: this.source.buildTriggers(), + triggers: this.source._buildTriggers(), vpcConfig: this.configureVpc(props), }); @@ -822,7 +822,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 { @@ -889,7 +889,7 @@ export class Project extends ProjectBase { if (props.artifacts) { return props.artifacts; } - if (this.source.toSourceJSON().type === CODEPIPELINE_TYPE) { + if (this.source._toSourceJSON().type === CODEPIPELINE_TYPE) { return new CodePipelineBuildArtifacts(); } else { return new NoBuildArtifacts(); @@ -897,7 +897,7 @@ 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) && diff --git a/packages/@aws-cdk/aws-codebuild/lib/source.ts b/packages/@aws-cdk/aws-codebuild/lib/source.ts index 75c03a90aba82..5595bcc58b065 100644 --- a/packages/@aws-cdk/aws-codebuild/lib/source.ts +++ b/packages/@aws-cdk/aws-codebuild/lib/source.ts @@ -39,7 +39,8 @@ export abstract class BuildSource { return; } - public toSourceJSON(): CfnProject.SourceProperty { + /** @internal */ + public _toSourceJSON(): CfnProject.SourceProperty { const sourceProp = this.toSourceProperty(); return { sourceIdentifier: this.identifier, @@ -48,7 +49,8 @@ export abstract class BuildSource { }; } - public buildTriggers(): CfnProject.ProjectTriggersProperty | undefined { + /** @internal */ + public _buildTriggers(): CfnProject.ProjectTriggersProperty | undefined { return undefined; } @@ -89,7 +91,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) { @@ -98,14 +99,349 @@ export abstract class GitBuildSource extends BuildSource { this.cloneDepth = props.cloneDepth; } - public toSourceJSON(): CfnProject.SourceProperty { + /** @internal */ + public _toSourceJSON(): CfnProject.SourceProperty { return { - ...super.toSourceJSON(), + ...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}. */ @@ -118,7 +454,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 +530,7 @@ export class CodePipelineSource extends BuildSource { /** * Construction properties for {@link GitHubSource} and {@link GitHubEnterpriseSource}. */ -export interface GitHubSourceProps extends GitBuildSourceProps { +export interface GitHubSourceProps extends ThirdPartyGitBuildSourceProps { /** * The GitHub account/user that owns the repo. * @@ -209,50 +544,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 ThirdPartyGitBuildSource { 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 +568,7 @@ export class GitHubSource extends GitBuildSource { /** * Construction properties for {@link GitHubEnterpriseSource}. */ -export interface GitHubEnterpriseSourceProps extends GitBuildSourceProps { +export interface GitHubEnterpriseSourceProps extends ThirdPartyGitBuildSourceProps { /** * The HTTPS URL of the repository in your GitHub Enterprise installation. */ @@ -277,7 +585,7 @@ export interface GitHubEnterpriseSourceProps extends GitBuildSourceProps { /** * GitHub Enterprise Source definition for a CodeBuild project. */ -export class GitHubEnterpriseSource extends GitBuildSource { +export class GitHubEnterpriseSource extends ThirdPartyGitBuildSource { public readonly type: SourceType = SourceType.GitHubEnterprise; private readonly httpsCloneUrl: string; private readonly ignoreSslErrors?: boolean; @@ -299,7 +607,7 @@ export class GitHubEnterpriseSource extends GitBuildSource { /** * Construction properties for {@link BitBucketSource}. */ -export interface BitBucketSourceProps extends GitBuildSourceProps { +export interface BitBucketSourceProps extends ThirdPartyGitBuildSourceProps { /** * The BitBucket account/user that owns the repo. * @@ -318,7 +626,7 @@ export interface BitBucketSourceProps extends GitBuildSourceProps { /** * BitBucket Source definition for a CodeBuild project. */ -export class BitBucketSource extends GitBuildSource { +export class BitBucketSource extends ThirdPartyGitBuildSource { public readonly type: SourceType = SourceType.BitBucket; private readonly httpsCloneUrl: any; @@ -327,11 +635,38 @@ export class BitBucketSource extends GitBuildSource { 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; + } } /** @@ -346,3 +681,9 @@ export enum SourceType { 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/test/test.codebuild.ts b/packages/@aws-cdk/aws-codebuild/test/test.codebuild.ts index 210d13b43b5dd..030bd009fbc1b 100644 --- a/packages/@aws-cdk/aws-codebuild/test/test.codebuild.ts +++ b/packages/@aws-cdk/aws-codebuild/test/test.codebuild.ts @@ -480,6 +480,10 @@ export = { 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'), + ], }) }); @@ -495,6 +499,16 @@ export = { 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' }, + ], + ], }, })); @@ -503,11 +517,19 @@ export = { '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'), + ], }) }); @@ -516,10 +538,31 @@ 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: '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) { @@ -530,6 +573,15 @@ export = { 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'), + ], }) }); @@ -538,6 +590,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: '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 }, + ], + ], }, })); @@ -1154,5 +1223,72 @@ export = { }); 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(); + }, + }, };