diff --git a/API.md b/API.md
index 7fb8d3e6..691f0dd2 100644
--- a/API.md
+++ b/API.md
@@ -9,6 +9,7 @@ Name|Description
[DockerCredential](#cdk-pipelines-github-dockercredential)|Represents a credential used to authenticate to a docker registry.
[GitHubActionRole](#cdk-pipelines-github-githubactionrole)|Creates or references a GitHub OIDC provider and accompanying role that trusts the provider.
[GitHubActionStep](#cdk-pipelines-github-githubactionstep)|Specifies a GitHub Action as a step in the pipeline.
+[GitHubWave](#cdk-pipelines-github-githubwave)|Multiple stages that are deployed in parallel.
[GitHubWorkflow](#cdk-pipelines-github-githubworkflow)|CDK Pipelines for GitHub workflows.
[JsonPatch](#cdk-pipelines-github-jsonpatch)|Utility for applying RFC-6902 JSON-Patch to a document.
[Runner](#cdk-pipelines-github-runner)|The type of runner to run the job on.
@@ -367,6 +368,7 @@ new GitHubActionStep(id: string, props: GitHubActionStepProps)
* **props** ([GitHubActionStepProps](#cdk-pipelines-github-githubactionstepprops)
) *No description*
* **jobSteps** (Array<[JobStep](#cdk-pipelines-github-jobstep)>
) The Job steps.
* **env** (Map
) Environment variables to set. __*Optional*__
+ * **if** (string
) Add an addition `if` clause on the `job.*` step for this `GitHubActionStep`. __*Optional*__
@@ -377,6 +379,88 @@ Name | Type | Description
-----|------|-------------
**env** | Map
|
**jobSteps** | Array<[JobStep](#cdk-pipelines-github-jobstep)>
|
+**if**? | string
| __*Optional*__
+
+
+
+## class GitHubWave
+
+Multiple stages that are deployed in parallel.
+
+A `Wave`, but with addition GitHub options - created by `GitHubWorkflow.addWave()` or
+`GitHubWorkflow.addGitHubWave()` - DO NOT CREATE DIRECTLY
+
+__Extends__: [pipelines.Wave](#aws-cdk-lib-pipelines-wave)
+
+### Initializer
+
+
+
+
+```ts
+new GitHubWave(id: string, pipeline: GitHubWorkflow, props?: WaveProps)
+```
+
+* **id** (string
) Identifier for this Wave.
+* **pipeline** ([GitHubWorkflow](#cdk-pipelines-github-githubworkflow)
) GitHubWorkflow that this wave is part of.
+* **props** ([pipelines.WaveProps](#aws-cdk-lib-pipelines-waveprops)
) *No description*
+ * **post** (Array<[pipelines.Step](#aws-cdk-lib-pipelines-step)>
) Additional steps to run after all of the stages in the wave. __*Default*__: No additional steps
+ * **pre** (Array<[pipelines.Step](#aws-cdk-lib-pipelines-step)>
) Additional steps to run before any of the stages in the wave. __*Default*__: No additional steps
+
+
+
+### Properties
+
+
+Name | Type | Description
+-----|------|-------------
+**id** | string
| Identifier for this Wave.
+
+### Methods
+
+
+#### addStage(stage, options?)
+
+Add a Stage to this wave.
+
+It will be deployed in parallel with all other stages in this
+wave.
+
+```ts
+addStage(stage: Stage, options?: AddStageOpts): StageDeployment
+```
+
+* **stage** ([Stage](#aws-cdk-lib-stage)
) *No description*
+* **options** ([pipelines.AddStageOpts](#aws-cdk-lib-pipelines-addstageopts)
) *No description*
+ * **post** (Array<[pipelines.Step](#aws-cdk-lib-pipelines-step)>
) Additional steps to run after all of the stacks in the stage. __*Default*__: No additional steps
+ * **pre** (Array<[pipelines.Step](#aws-cdk-lib-pipelines-step)>
) Additional steps to run before any of the stacks in the stage. __*Default*__: No additional steps
+ * **stackSteps** (Array<[pipelines.StackSteps](#aws-cdk-lib-pipelines-stacksteps)>
) Instructions for stack level steps. __*Default*__: No additional instructions
+
+__Returns__:
+* [pipelines.StageDeployment](#aws-cdk-lib-pipelines-stagedeployment)
+
+#### addStageWithGitHubOptions(stage, options?)
+
+Add a Stage to this wave.
+
+It will be deployed in parallel with all other stages in this
+wave.
+
+```ts
+addStageWithGitHubOptions(stage: Stage, options?: AddGitHubStageOptions): StageDeployment
+```
+
+* **stage** ([Stage](#aws-cdk-lib-stage)
) *No description*
+* **options** ([AddGitHubStageOptions](#cdk-pipelines-github-addgithubstageoptions)
) *No description*
+ * **post** (Array<[pipelines.Step](#aws-cdk-lib-pipelines-step)>
) Additional steps to run after all of the stacks in the stage. __*Default*__: No additional steps
+ * **pre** (Array<[pipelines.Step](#aws-cdk-lib-pipelines-step)>
) Additional steps to run before any of the stacks in the stage. __*Default*__: No additional steps
+ * **stackSteps** (Array<[pipelines.StackSteps](#aws-cdk-lib-pipelines-stacksteps)>
) Instructions for stack level steps. __*Default*__: No additional instructions
+ * **gitHubEnvironment** (string
) Run the stage in a specific GitHub Environment. __*Default*__: no GitHub environment
+ * **jobSettings** ([JobSettings](#cdk-pipelines-github-jobsettings)
) Job level settings that will be applied to all jobs in the stage. __*Optional*__
+ * **stackCapabilities** (Array<[StackCapabilities](#cdk-pipelines-github-stackcapabilities)>
) In some cases, you must explicitly acknowledge that your CloudFormation stack template contains certain capabilities in order for CloudFormation to create the stack. __*Default*__: ['CAPABILITY_IAM']
+
+__Returns__:
+* [pipelines.StageDeployment](#aws-cdk-lib-pipelines-stagedeployment)
@@ -430,6 +514,22 @@ Name | Type | Description
### Methods
+#### addGitHubWave(id, options?)
+
+
+
+```ts
+addGitHubWave(id: string, options?: WaveOptions): GitHubWave
+```
+
+* **id** (string
) *No description*
+* **options** ([pipelines.WaveOptions](#aws-cdk-lib-pipelines-waveoptions)
) *No description*
+ * **post** (Array<[pipelines.Step](#aws-cdk-lib-pipelines-step)>
) Additional steps to run after all of the stages in the wave. __*Default*__: No additional steps
+ * **pre** (Array<[pipelines.Step](#aws-cdk-lib-pipelines-step)>
) Additional steps to run before any of the stages in the wave. __*Default*__: No additional steps
+
+__Returns__:
+* [GitHubWave](#cdk-pipelines-github-githubwave)
+
#### addStageWithGitHubOptions(stage, options?)
Deploy a single Stage by itself with options for further GitHub configuration.
@@ -453,6 +553,59 @@ addStageWithGitHubOptions(stage: Stage, options?: AddGitHubStageOptions): StageD
__Returns__:
* [pipelines.StageDeployment](#aws-cdk-lib-pipelines-stagedeployment)
+#### addWave(id, options?)
+
+Add a Wave to the pipeline, for deploying multiple Stages in parallel.
+
+Use the return object of this method to deploy multiple stages in parallel.
+
+Example:
+
+```ts
+declare const pipeline: pipelines.CodePipeline;
+
+const wave = pipeline.addWave('MyWave');
+wave.addStage(new MyApplicationStage(this, 'Stage1'));
+wave.addStage(new MyApplicationStage(this, 'Stage2'));
+```
+
+```ts
+addWave(id: string, options?: WaveOptions): Wave
+```
+
+* **id** (string
) *No description*
+* **options** ([pipelines.WaveOptions](#aws-cdk-lib-pipelines-waveoptions)
) *No description*
+ * **post** (Array<[pipelines.Step](#aws-cdk-lib-pipelines-step)>
) Additional steps to run after all of the stages in the wave. __*Default*__: No additional steps
+ * **pre** (Array<[pipelines.Step](#aws-cdk-lib-pipelines-step)>
) Additional steps to run before any of the stages in the wave. __*Default*__: No additional steps
+
+__Returns__:
+* [pipelines.Wave](#aws-cdk-lib-pipelines-wave)
+
+#### addingStageFromWave(stage, stageDeployment, options?)
+
+Support adding stages with GitHub options to waves - should ONLY be called internally.
+
+Use `pipeline.addWave()` and it'll call this when `wave.addStage()` is called.
+
+`pipeline.addStage()` will also call this, since it calls `pipeline.addWave().addStage()`.
+
+```ts
+addingStageFromWave(stage: Stage, stageDeployment: StageDeployment, options?: AddGitHubStageOptions): void
+```
+
+* **stage** ([Stage](#aws-cdk-lib-stage)
) *No description*
+* **stageDeployment** ([pipelines.StageDeployment](#aws-cdk-lib-pipelines-stagedeployment)
) *No description*
+* **options** ([AddGitHubStageOptions](#cdk-pipelines-github-addgithubstageoptions)
) *No description*
+ * **post** (Array<[pipelines.Step](#aws-cdk-lib-pipelines-step)>
) Additional steps to run after all of the stacks in the stage. __*Default*__: No additional steps
+ * **pre** (Array<[pipelines.Step](#aws-cdk-lib-pipelines-step)>
) Additional steps to run before any of the stacks in the stage. __*Default*__: No additional steps
+ * **stackSteps** (Array<[pipelines.StackSteps](#aws-cdk-lib-pipelines-stacksteps)>
) Instructions for stack level steps. __*Default*__: No additional instructions
+ * **gitHubEnvironment** (string
) Run the stage in a specific GitHub Environment. __*Default*__: no GitHub environment
+ * **jobSettings** ([JobSettings](#cdk-pipelines-github-jobsettings)
) Job level settings that will be applied to all jobs in the stage. __*Optional*__
+ * **stackCapabilities** (Array<[StackCapabilities](#cdk-pipelines-github-stackcapabilities)>
) In some cases, you must explicitly acknowledge that your CloudFormation stack template contains certain capabilities in order for CloudFormation to create the stack. __*Default*__: ['CAPABILITY_IAM']
+
+
+
+
#### protected doBuildPipeline()
Implemented by subclasses to do the actual pipeline construction.
@@ -933,6 +1086,7 @@ Name | Type | Description
-----|------|-------------
**jobSteps** | Array<[JobStep](#cdk-pipelines-github-jobstep)>
| The Job steps.
**env**? | Map
| Environment variables to set.
__*Optional*__
+**if**? | string
| Add an addition `if` clause on the `job.*` step for this `GitHubActionStep`.
__*Optional*__
diff --git a/src/pipeline.ts b/src/pipeline.ts
index c4976746..aa3bb73b 100644
--- a/src/pipeline.ts
+++ b/src/pipeline.ts
@@ -2,12 +2,13 @@ import { mkdirSync, writeFileSync, readFileSync, existsSync } from 'fs';
import * as path from 'path';
import { Stage } from 'aws-cdk-lib';
import { EnvironmentPlaceholders } from 'aws-cdk-lib/cx-api';
-import { PipelineBase, PipelineBaseProps, ShellStep, StackAsset, StackDeployment, StackOutputReference, StageDeployment, Step } from 'aws-cdk-lib/pipelines';
+import { AddStageOpts, PipelineBase, PipelineBaseProps, ShellStep, StackAsset, StackDeployment, StackOutputReference, StageDeployment, Step, Wave, WaveOptions, WaveProps } from 'aws-cdk-lib/pipelines';
import { AGraphNode, PipelineGraph, Graph, isGraph } from 'aws-cdk-lib/pipelines/lib/helpers-internal';
import { Construct } from 'constructs';
import * as decamelize from 'decamelize';
import { AwsCredentials, AwsCredentialsProvider } from './aws-credentials';
import { DockerCredential } from './docker-credentials';
+import { GitHubStage } from './stage';
import { AddGitHubStageOptions } from './stage-options';
import { GitHubActionStep } from './steps/github-action-step';
import * as github from './workflows-model';
@@ -175,8 +176,13 @@ export class GitHubWorkflow extends PipelineBase {
private readonly assetHashMap: Record = {};
private readonly runner: github.Runner;
private readonly publishAssetsAuthRegion: string;
- private readonly stackProperties: Record> = {};
+ private readonly stackProperties: Record = {};
private readonly jobSettings?: JobSettings;
+ private builtGH = false; // cannot be `built` since that's defined in the parent as private
constructor(scope: Construct, id: string, props: GitHubWorkflowProps) {
super(scope, id, props);
@@ -256,6 +262,57 @@ export class GitHubWorkflow extends PipelineBase {
return stageDeployment;
}
+
+ /**
+ * Add a Wave to the pipeline, for deploying multiple Stages in parallel
+ *
+ * Use the return object of this method to deploy multiple stages in parallel.
+ *
+ * Example:
+ *
+ * ```ts
+ * declare const pipeline: pipelines.CodePipeline;
+ *
+ * const wave = pipeline.addWave('MyWave');
+ * wave.addStage(new MyApplicationStage(this, 'Stage1'));
+ * wave.addStage(new MyApplicationStage(this, 'Stage2'));
+ * ```
+ */
+ public addWave(id: string, options?: WaveOptions): Wave {
+ return this.addGitHubWave(id, options);
+ }
+
+ public addGitHubWave(id: string, options?: WaveOptions): GitHubWave {
+ if (this.builtGH) {
+ throw new Error('addWave: can\'t add Waves anymore after buildPipeline() has been called');
+ }
+
+ const wave = new GitHubWave(id, this, options);
+ this.waves.push(wave);
+ return wave;
+ }
+
+ /**
+ * Support adding stages with GitHub options to waves - should ONLY be called internally.
+ *
+ * Use `pipeline.addWave()` and it'll call this when `wave.addStage()` is called.
+ *
+ * `pipeline.addStage()` will also call this, since it calls `pipeline.addWave().addStage()`.
+ */
+ public addingStageFromWave(stage: Stage, stageDeployment: StageDeployment, options?: AddGitHubStageOptions) {
+ if (!(stage instanceof GitHubStage) && options === undefined) {
+ return;
+ }
+
+ const ghStage = stage instanceof GitHubStage ? stage : undefined;
+
+ // keep track of GitHub specific options
+ const stacks = stageDeployment.stacks;
+ this.addStackProps(stacks, 'environment', ghStage?.props?.gitHubEnvironment ?? options?.gitHubEnvironment);
+ this.addStackProps(stacks, 'capabilities', ghStage?.props?.stackCapabilities ?? options?.stackCapabilities);
+ this.addStackProps(stacks, 'settings', ghStage?.props?.jobSettings ?? options?.jobSettings);
+ }
+
private addStackProps(stacks: StackDeployment[], key: string, value: any) {
if (value === undefined) { return; }
for (const stack of stacks) {
@@ -267,6 +324,7 @@ export class GitHubWorkflow extends PipelineBase {
}
protected doBuildPipeline() {
+ this.builtGH = true;
const app = Stage.of(this);
if (!app) {
throw new Error('The GitHub Workflow must be defined in the scope of an App');
@@ -442,7 +500,7 @@ export class GitHubWorkflow extends PipelineBase {
id: jobId,
definition: {
name: `Publish Assets ${jobId}`,
- ...this.jobSettings,
+ ...this.renderJobSettingParameters(),
needs: this.renderDependencies(node),
permissions: {
contents: github.JobPermission.READ,
@@ -513,7 +571,7 @@ export class GitHubWorkflow extends PipelineBase {
id: node.uniqueId,
definition: {
name: `Deploy ${stack.stackArtifactId}`,
- ...this.jobSettings,
+ ...this.renderJobSettingParameters(),
...this.stackProperties[stack.stackArtifactId]?.settings,
permissions: {
contents: github.JobPermission.READ,
@@ -564,7 +622,7 @@ export class GitHubWorkflow extends PipelineBase {
id: node.uniqueId,
definition: {
name: 'Synthesize',
- ...this.jobSettings,
+ ...this.renderJobSettingParameters(true),
permissions: {
contents: github.JobPermission.READ,
// The Synthesize job does not use the GitHub Action Role on its own, but it's possible
@@ -658,7 +716,7 @@ export class GitHubWorkflow extends PipelineBase {
id: node.uniqueId,
definition: {
name: step.id,
- ...this.jobSettings,
+ ...this.renderJobSettingParameters(),
permissions: {
contents: github.JobPermission.READ,
},
@@ -683,7 +741,8 @@ export class GitHubWorkflow extends PipelineBase {
id: node.uniqueId,
definition: {
name: step.id,
- ...this.jobSettings,
+ ...this.renderJobSettingParameters(),
+ if: step.if,
permissions: {
contents: github.JobPermission.WRITE,
},
@@ -777,6 +836,16 @@ export class GitHubWorkflow extends PipelineBase {
return deps.map(x => x.uniqueId);
}
+
+ private renderJobSettingParameters(isBuildStep = false) {
+ if (isBuildStep) {
+ return {
+ if: this.jobSettings?.if,
+ };
+ }
+ // in the future, additional job settings may be rendered here
+ return {};
+ }
}
interface Context {
@@ -816,6 +885,63 @@ function snakeCaseKeys(obj: T, sep = '-'): T {
return result as any;
}
+/**
+ * Multiple stages that are deployed in parallel
+ *
+ * A `Wave`, but with addition GitHub options - created by `GitHubWorkflow.addWave()` or
+ * `GitHubWorkflow.addGitHubWave()` - DO NOT CREATE DIRECTLY
+ */
+export class GitHubWave extends Wave {
+ constructor(
+ /** Identifier for this Wave */
+ public readonly id: string,
+ /** GitHubWorkflow that this wave is part of */
+ private pipeline: GitHubWorkflow,
+ props: WaveProps = {},
+ ) {
+ super(id, props);
+ }
+
+ /**
+ * Add a Stage to this wave
+ *
+ * It will be deployed in parallel with all other stages in this
+ * wave.
+ */
+ public addStage(stage: Stage, options: AddStageOpts = {}) {
+ const stageDeployment = super.addStage(stage, options);
+ this.pipeline.addingStageFromWave(stage, stageDeployment);
+ return stageDeployment;
+ }
+
+ /**
+ * Add a Stage to this wave
+ *
+ * It will be deployed in parallel with all other stages in this
+ * wave.
+ */
+ public addStageWithGitHubOptions(stage: Stage, options?: AddGitHubStageOptions): StageDeployment {
+ const stageDeployment = super.addStage(stage, options);
+ this.pipeline.addingStageFromWave(stage, stageDeployment, options);
+ return stageDeployment;
+ }
+
+ // /**
+ // * Add an additional step to run before any of the stages in this wave
+ // */
+ // public addPre(...steps: Step[]) {
+ // this.pre.push(...steps);
+ // }
+
+ // /**
+ // * Add an additional step to run after all of the stages in this wave
+ // */
+ // public addPost(...steps: Step[]) {
+ // this.post.push(...steps);
+ // }
+}
+
+
/**
* Names of secrets for AWS credentials.
*/
diff --git a/src/stage.ts b/src/stage.ts
new file mode 100644
index 00000000..afeb3910
--- /dev/null
+++ b/src/stage.ts
@@ -0,0 +1,46 @@
+import { Stage, StageProps } from 'aws-cdk-lib';
+import { Construct } from 'constructs';
+import { JobSettings } from './pipeline';
+import { StackCapabilities } from './stage-options';
+
+
+export interface GitHubStageProps extends StageProps {
+ /**
+ * Run the stage in a specific GitHub Environment. If specified,
+ * any protection rules configured for the environment must pass
+ * before the job is set to a runner. For example, if the environment
+ * has a manual approval rule configured, then the workflow will
+ * wait for the approval before sending the job to the runner.
+ *
+ * Running a workflow that references an environment that does not
+ * exist will create an environment with the referenced name.
+ * @see https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment
+ *
+ * @default - no GitHub environment
+ */
+ readonly gitHubEnvironment?: string;
+
+ /**
+ * In some cases, you must explicitly acknowledge that your CloudFormation
+ * stack template contains certain capabilities in order for CloudFormation
+ * to create the stack.
+ *
+ * If insufficiently specified, CloudFormation returns an `InsufficientCapabilities`
+ * error.
+ *
+ * @default ['CAPABILITY_IAM']
+ */
+ readonly stackCapabilities?: StackCapabilities[];
+
+ /**
+ * Job level settings that will be applied to all jobs in the stage.
+ * Currently the only valid setting is 'if'.
+ */
+ readonly jobSettings?: JobSettings;
+}
+
+export class GitHubStage extends Stage {
+ constructor(scope: Construct, id: string, public readonly props?: GitHubStageProps) {
+ super(scope, id, props);
+ }
+}
\ No newline at end of file
diff --git a/src/steps/github-action-step.ts b/src/steps/github-action-step.ts
index e5bfa414..acd526c4 100644
--- a/src/steps/github-action-step.ts
+++ b/src/steps/github-action-step.ts
@@ -11,7 +11,28 @@ export interface GitHubActionStepProps {
* Environment variables to set.
*/
readonly env?: Record;
-}
+
+ /**
+ * Add an addition `if` clause on the `job.*` step for this `GitHubActionStep`
+ *
+ * Note that setting this may allow the job to run even if any of the jobs it depends on fails.
+ *
+ * In cases where it's only desired to run when previous jobs succeed, then use `success()`, such as:
+ *
+ * ```ts
+ * const postStep = new GitHubActionStep('PostDeployAction', {
+ * jobSteps: [
+ * // ...
+ * ],
+ * if: "success() && contains(github.event.issue.labels.*.name, 'cleanup')",
+ * });
+ * ```
+ *
+ * @see https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idif
+ * @see https://docs.github.com/en/actions/learn-github-actions/expressions#success
+ */
+ readonly if?: string;
+};
/**
* Specifies a GitHub Action as a step in the pipeline.
@@ -19,10 +40,12 @@ export interface GitHubActionStepProps {
export class GitHubActionStep extends Step {
public readonly env: Record;
public readonly jobSteps: JobStep[];
+ public readonly if?: string;
constructor(id: string, props: GitHubActionStepProps) {
super(id);
this.jobSteps = props.jobSteps;
this.env = props.env ?? {};
+ this.if = props.if;
}
}
diff --git a/test/__snapshots__/github.test.ts.snap b/test/__snapshots__/github.test.ts.snap
index aa795e17..d965e85c 100644
--- a/test/__snapshots__/github.test.ts.snap
+++ b/test/__snapshots__/github.test.ts.snap
@@ -440,7 +440,6 @@ jobs:
path: cdk.out
Assets-FileAsset1:
name: Publish Assets Assets-FileAsset1
- if: github.repository == 'account/repo'
needs:
- Build-Build
permissions:
@@ -470,7 +469,6 @@ jobs:
run: /bin/bash ./cdk.out/publish-Assets-FileAsset1-step.sh
Assets-FileAsset2:
name: Publish Assets Assets-FileAsset2
- if: github.repository == 'account/repo'
needs:
- Build-Build
permissions:
@@ -500,7 +498,6 @@ jobs:
run: /bin/bash ./cdk.out/publish-Assets-FileAsset2-step.sh
MyStack-MyStack-Deploy:
name: Deploy MyStack098574E7
- if: github.repository == 'account/repo'
permissions:
contents: read
id-token: none
diff --git a/test/__snapshots__/stage-options.test.ts.snap b/test/__snapshots__/stage-options.test.ts.snap
index 7a4f702c..e1892f16 100644
--- a/test/__snapshots__/stage-options.test.ts.snap
+++ b/test/__snapshots__/stage-options.test.ts.snap
@@ -11,8 +11,9 @@ on:
- main
workflow_dispatch: {}
jobs:
- Build-Build:
+ Build-Synth:
name: Synthesize
+ if: contains(fromJson('[\\"push\\", \\"pull_request\\"]'), github.event_name)
permissions:
contents: read
id-token: none
@@ -34,7 +35,7 @@ jobs:
Assets-FileAsset1:
name: Publish Assets Assets-FileAsset1
needs:
- - Build-Build
+ - Build-Synth
permissions:
contents: read
id-token: none
@@ -60,13 +61,13 @@ jobs:
- id: Publish
name: Publish Assets-FileAsset1
run: /bin/bash ./cdk.out/publish-Assets-FileAsset1-step.sh
- MyStack-PreDeployAction:
+ MyPrePostStack-PreDeployAction:
name: PreDeployAction
permissions:
contents: write
runs-on: ubuntu-latest
needs:
- - Build-Build
+ - Build-Synth
env: {}
steps:
- name: pre deploy action
@@ -74,15 +75,16 @@ jobs:
with:
app-id: 1234
secrets: my-secrets
- MyStack-MyStack-Deploy:
- name: Deploy MyStack098574E7
+ MyPrePostStack-MyStack-Deploy:
+ name: Deploy MyPrePostStackMyStack8AD5AF9E
+ if: success() && contains(github.event.issue.labels.*.name, 'deploy')
permissions:
contents: read
id-token: none
needs:
- - Build-Build
+ - Build-Synth
- Assets-FileAsset1
- - MyStack-PreDeployAction
+ - MyPrePostStack-PreDeployAction
runs-on: ubuntu-latest
steps:
- name: Authenticate Via GitHub Secrets
@@ -98,19 +100,20 @@ jobs:
- id: Deploy
uses: aws-actions/aws-cloudformation-github-deploy@v1
with:
- name: MyStack-MyStack
+ name: MyPrePostStack-MyStack
template: https://cdk-hnb659fds-assets-111111111111-us-east-1.s3.us-east-1.amazonaws.com/\${{
needs.Assets-FileAsset1.outputs.asset-hash }}.json
no-fail-on-empty-changeset: \\"1\\"
role-arn: arn:aws:iam::111111111111:role/cdk-hnb659fds-cfn-exec-role-111111111111-us-east-1
- MyStack-PostDeployAction:
+ MyPrePostStack-PostDeployAction:
name: PostDeployAction
+ if: failure() && contains(github.event.issue.labels.*.name, 'cleanupFailure')
permissions:
contents: write
runs-on: ubuntu-latest
needs:
- - MyStack-MyStack-Deploy
- - Build-Build
+ - MyPrePostStack-MyStack-Deploy
+ - Build-Synth
env: {}
steps:
- name: Checkout
@@ -429,6 +432,164 @@ jobs:
"
`;
+exports[`github stages in waves works 1`] = `
+"# AUTOMATICALLY GENERATED FILE, DO NOT EDIT MANUALLY.
+# Generated by AWS CDK and [cdk-pipelines-github](https://github.com/cdklabs/cdk-pipelines-github)
+
+name: deploy
+on:
+ push:
+ branches:
+ - main
+ workflow_dispatch: {}
+jobs:
+ Build-Build:
+ name: Synthesize
+ if: contains(github.event.issue.labels.*.name, 'deployToA') ||
+ contains(github.event.issue.labels.*.name, 'deployToB')
+ permissions:
+ contents: read
+ id-token: none
+ runs-on: ubuntu-latest
+ needs: []
+ env: {}
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v2
+ - name: Install
+ run: yarn
+ - name: Build
+ run: yarn build
+ - name: Upload cdk.out
+ uses: actions/upload-artifact@v2.1.1
+ with:
+ name: cdk.out
+ path: cdk.out
+ Assets-FileAsset1:
+ name: Publish Assets Assets-FileAsset1
+ needs:
+ - Build-Build
+ permissions:
+ contents: read
+ id-token: none
+ runs-on: ubuntu-latest
+ outputs:
+ asset-hash: \${{ steps.Publish.outputs.asset-hash }}
+ steps:
+ - name: Download cdk.out
+ uses: actions/download-artifact@v2
+ with:
+ name: cdk.out
+ path: stage.out
+ - name: Install
+ run: npm install --no-save cdk-assets
+ - name: Authenticate Via GitHub Secrets
+ uses: aws-actions/configure-aws-credentials@v1
+ with:
+ aws-region: us-west-2
+ role-duration-seconds: 1800
+ role-skip-session-tagging: true
+ aws-access-key-id: \${{ secrets.AWS_ACCESS_KEY_ID }}
+ aws-secret-access-key: \${{ secrets.AWS_SECRET_ACCESS_KEY }}
+ - id: Publish
+ name: Publish Assets-FileAsset1
+ run: /bin/bash ./cdk.out/publish-Assets-FileAsset1-step.sh
+ MyWave-PreWaveAction:
+ name: PreWaveAction
+ permissions:
+ contents: write
+ runs-on: ubuntu-latest
+ needs:
+ - Build-Build
+ env: {}
+ steps:
+ - name: pre wave action
+ uses: my-pre-wave-action@1.0.0
+ with:
+ app-id: 1234
+ secrets: my-secrets
+ MyWave-MyStageA-MyStackA-Deploy:
+ name: Deploy MyStageAMyStackA0F0BE321
+ if: success() && contains(github.event.issue.labels.*.name, 'deployToA')
+ permissions:
+ contents: read
+ id-token: none
+ needs:
+ - Build-Build
+ - Assets-FileAsset1
+ - MyWave-PreWaveAction
+ runs-on: ubuntu-latest
+ steps:
+ - name: Authenticate Via GitHub Secrets
+ uses: aws-actions/configure-aws-credentials@v1
+ with:
+ aws-region: us-east-1
+ role-duration-seconds: 1800
+ role-skip-session-tagging: true
+ aws-access-key-id: \${{ secrets.AWS_ACCESS_KEY_ID }}
+ aws-secret-access-key: \${{ secrets.AWS_SECRET_ACCESS_KEY }}
+ role-to-assume: arn:aws:iam::111111111111:role/cdk-hnb659fds-deploy-role-111111111111-us-east-1
+ role-external-id: Pipeline
+ - id: Deploy
+ uses: aws-actions/aws-cloudformation-github-deploy@v1
+ with:
+ name: MyStageA-MyStackA
+ template: https://cdk-hnb659fds-assets-111111111111-us-east-1.s3.us-east-1.amazonaws.com/\${{
+ needs.Assets-FileAsset1.outputs.asset-hash }}.json
+ no-fail-on-empty-changeset: \\"1\\"
+ role-arn: arn:aws:iam::111111111111:role/cdk-hnb659fds-cfn-exec-role-111111111111-us-east-1
+ MyWave-MyStageB-MyStackB-Deploy:
+ name: Deploy MyStageBMyStackBFE4B1ADE
+ if: success() && contains(github.event.issue.labels.*.name, 'deployToB')
+ permissions:
+ contents: read
+ id-token: none
+ needs:
+ - Build-Build
+ - Assets-FileAsset1
+ - MyWave-PreWaveAction
+ runs-on: ubuntu-latest
+ steps:
+ - name: Authenticate Via GitHub Secrets
+ uses: aws-actions/configure-aws-credentials@v1
+ with:
+ aws-region: us-east-1
+ role-duration-seconds: 1800
+ role-skip-session-tagging: true
+ aws-access-key-id: \${{ secrets.AWS_ACCESS_KEY_ID }}
+ aws-secret-access-key: \${{ secrets.AWS_SECRET_ACCESS_KEY }}
+ role-to-assume: arn:aws:iam::12345678901:role/cdk-hnb659fds-deploy-role-12345678901-us-east-1
+ role-external-id: Pipeline
+ - id: Deploy
+ uses: aws-actions/aws-cloudformation-github-deploy@v1
+ with:
+ name: MyStageB-MyStackB
+ template: https://cdk-hnb659fds-assets-12345678901-us-east-1.s3.us-east-1.amazonaws.com/\${{
+ needs.Assets-FileAsset1.outputs.asset-hash }}.json
+ no-fail-on-empty-changeset: \\"1\\"
+ role-arn: arn:aws:iam::12345678901:role/cdk-hnb659fds-cfn-exec-role-12345678901-us-east-1
+ MyWave-PostWaveAction:
+ name: PostWaveAction
+ if: failure() && contains(github.event.issue.labels.*.name, 'cleanupFailure')
+ permissions:
+ contents: write
+ runs-on: ubuntu-latest
+ needs:
+ - MyWave-MyStageA-MyStackA-Deploy
+ - MyWave-MyStageB-MyStackB-Deploy
+ - Build-Build
+ env: {}
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v2
+ - name: post wave action
+ uses: my-post-wave-action@1.0.0
+ with:
+ app-id: 4321
+ secrets: secrets
+"
+`;
+
exports[`job settings can specify job settings at stage level 1`] = `
"# AUTOMATICALLY GENERATED FILE, DO NOT EDIT MANUALLY.
# Generated by AWS CDK and [cdk-pipelines-github](https://github.com/cdklabs/cdk-pipelines-github)
@@ -520,3 +681,246 @@ jobs:
role-arn: arn:aws:iam::111111111111:role/cdk-hnb659fds-cfn-exec-role-111111111111-us-east-1
"
`;
+
+exports[`stages in github waves works 1`] = `
+"# AUTOMATICALLY GENERATED FILE, DO NOT EDIT MANUALLY.
+# Generated by AWS CDK and [cdk-pipelines-github](https://github.com/cdklabs/cdk-pipelines-github)
+
+name: deploy
+on:
+ push:
+ branches:
+ - main
+ workflow_dispatch: {}
+jobs:
+ Build-Build:
+ name: Synthesize
+ permissions:
+ contents: read
+ id-token: none
+ runs-on: ubuntu-latest
+ needs: []
+ env: {}
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v2
+ - name: Install
+ run: yarn
+ - name: Build
+ run: yarn build
+ - name: Upload cdk.out
+ uses: actions/upload-artifact@v2.1.1
+ with:
+ name: cdk.out
+ path: cdk.out
+ Assets-FileAsset1:
+ name: Publish Assets Assets-FileAsset1
+ needs:
+ - Build-Build
+ permissions:
+ contents: read
+ id-token: none
+ runs-on: ubuntu-latest
+ outputs:
+ asset-hash: \${{ steps.Publish.outputs.asset-hash }}
+ steps:
+ - name: Download cdk.out
+ uses: actions/download-artifact@v2
+ with:
+ name: cdk.out
+ path: stage.out
+ - name: Install
+ run: npm install --no-save cdk-assets
+ - name: Authenticate Via GitHub Secrets
+ uses: aws-actions/configure-aws-credentials@v1
+ with:
+ aws-region: us-west-2
+ role-duration-seconds: 1800
+ role-skip-session-tagging: true
+ aws-access-key-id: \${{ secrets.AWS_ACCESS_KEY_ID }}
+ aws-secret-access-key: \${{ secrets.AWS_SECRET_ACCESS_KEY }}
+ - id: Publish
+ name: Publish Assets-FileAsset1
+ run: /bin/bash ./cdk.out/publish-Assets-FileAsset1-step.sh
+ MyWave-MyStageA-MyStackA-Deploy:
+ name: Deploy MyStageAMyStackA0F0BE321
+ if: success() && contains(github.event.issue.labels.*.name, 'deployToA')
+ permissions:
+ contents: read
+ id-token: none
+ needs:
+ - Build-Build
+ - Assets-FileAsset1
+ runs-on: ubuntu-latest
+ steps:
+ - name: Authenticate Via GitHub Secrets
+ uses: aws-actions/configure-aws-credentials@v1
+ with:
+ aws-region: us-east-1
+ role-duration-seconds: 1800
+ role-skip-session-tagging: true
+ aws-access-key-id: \${{ secrets.AWS_ACCESS_KEY_ID }}
+ aws-secret-access-key: \${{ secrets.AWS_SECRET_ACCESS_KEY }}
+ role-to-assume: arn:aws:iam::111111111111:role/cdk-hnb659fds-deploy-role-111111111111-us-east-1
+ role-external-id: Pipeline
+ - id: Deploy
+ uses: aws-actions/aws-cloudformation-github-deploy@v1
+ with:
+ name: MyStageA-MyStackA
+ template: https://cdk-hnb659fds-assets-111111111111-us-east-1.s3.us-east-1.amazonaws.com/\${{
+ needs.Assets-FileAsset1.outputs.asset-hash }}.json
+ no-fail-on-empty-changeset: \\"1\\"
+ role-arn: arn:aws:iam::111111111111:role/cdk-hnb659fds-cfn-exec-role-111111111111-us-east-1
+ MyWave-MyStageB-MyStackB-Deploy:
+ name: Deploy MyStageBMyStackBFE4B1ADE
+ if: success() && contains(github.event.issue.labels.*.name, 'deployToB')
+ permissions:
+ contents: read
+ id-token: none
+ needs:
+ - Build-Build
+ - Assets-FileAsset1
+ runs-on: ubuntu-latest
+ steps:
+ - name: Authenticate Via GitHub Secrets
+ uses: aws-actions/configure-aws-credentials@v1
+ with:
+ aws-region: us-east-1
+ role-duration-seconds: 1800
+ role-skip-session-tagging: true
+ aws-access-key-id: \${{ secrets.AWS_ACCESS_KEY_ID }}
+ aws-secret-access-key: \${{ secrets.AWS_SECRET_ACCESS_KEY }}
+ role-to-assume: arn:aws:iam::12345678901:role/cdk-hnb659fds-deploy-role-12345678901-us-east-1
+ role-external-id: Pipeline
+ - id: Deploy
+ uses: aws-actions/aws-cloudformation-github-deploy@v1
+ with:
+ name: MyStageB-MyStackB
+ template: https://cdk-hnb659fds-assets-12345678901-us-east-1.s3.us-east-1.amazonaws.com/\${{
+ needs.Assets-FileAsset1.outputs.asset-hash }}.json
+ no-fail-on-empty-changeset: \\"1\\"
+ role-arn: arn:aws:iam::12345678901:role/cdk-hnb659fds-cfn-exec-role-12345678901-us-east-1
+"
+`;
+
+exports[`stages in pipeline works with \`if\` 1`] = `
+"# AUTOMATICALLY GENERATED FILE, DO NOT EDIT MANUALLY.
+# Generated by AWS CDK and [cdk-pipelines-github](https://github.com/cdklabs/cdk-pipelines-github)
+
+name: deploy
+on:
+ push:
+ branches:
+ - main
+ workflow_dispatch: {}
+jobs:
+ Build-Build:
+ name: Synthesize
+ permissions:
+ contents: read
+ id-token: none
+ runs-on: ubuntu-latest
+ needs: []
+ env: {}
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v2
+ - name: Install
+ run: yarn
+ - name: Build
+ run: yarn build
+ - name: Upload cdk.out
+ uses: actions/upload-artifact@v2.1.1
+ with:
+ name: cdk.out
+ path: cdk.out
+ Assets-FileAsset1:
+ name: Publish Assets Assets-FileAsset1
+ needs:
+ - Build-Build
+ permissions:
+ contents: read
+ id-token: none
+ runs-on: ubuntu-latest
+ outputs:
+ asset-hash: \${{ steps.Publish.outputs.asset-hash }}
+ steps:
+ - name: Download cdk.out
+ uses: actions/download-artifact@v2
+ with:
+ name: cdk.out
+ path: stage.out
+ - name: Install
+ run: npm install --no-save cdk-assets
+ - name: Authenticate Via GitHub Secrets
+ uses: aws-actions/configure-aws-credentials@v1
+ with:
+ aws-region: us-west-2
+ role-duration-seconds: 1800
+ role-skip-session-tagging: true
+ aws-access-key-id: \${{ secrets.AWS_ACCESS_KEY_ID }}
+ aws-secret-access-key: \${{ secrets.AWS_SECRET_ACCESS_KEY }}
+ - id: Publish
+ name: Publish Assets-FileAsset1
+ run: /bin/bash ./cdk.out/publish-Assets-FileAsset1-step.sh
+ MyStageA-MyStackA-Deploy:
+ name: Deploy MyStageAMyStackA0F0BE321
+ if: success() && contains(github.event.issue.labels.*.name, 'deployToA')
+ permissions:
+ contents: read
+ id-token: none
+ needs:
+ - Build-Build
+ - Assets-FileAsset1
+ runs-on: ubuntu-latest
+ steps:
+ - name: Authenticate Via GitHub Secrets
+ uses: aws-actions/configure-aws-credentials@v1
+ with:
+ aws-region: us-east-1
+ role-duration-seconds: 1800
+ role-skip-session-tagging: true
+ aws-access-key-id: \${{ secrets.AWS_ACCESS_KEY_ID }}
+ aws-secret-access-key: \${{ secrets.AWS_SECRET_ACCESS_KEY }}
+ role-to-assume: arn:aws:iam::111111111111:role/cdk-hnb659fds-deploy-role-111111111111-us-east-1
+ role-external-id: Pipeline
+ - id: Deploy
+ uses: aws-actions/aws-cloudformation-github-deploy@v1
+ with:
+ name: MyStageA-MyStackA
+ template: https://cdk-hnb659fds-assets-111111111111-us-east-1.s3.us-east-1.amazonaws.com/\${{
+ needs.Assets-FileAsset1.outputs.asset-hash }}.json
+ no-fail-on-empty-changeset: \\"1\\"
+ role-arn: arn:aws:iam::111111111111:role/cdk-hnb659fds-cfn-exec-role-111111111111-us-east-1
+ MyStageB-MyStackB-Deploy:
+ name: Deploy MyStageBMyStackBFE4B1ADE
+ if: success() && contains(github.event.issue.labels.*.name, 'deployToB')
+ permissions:
+ contents: read
+ id-token: none
+ needs:
+ - Build-Build
+ - Assets-FileAsset1
+ - MyStageA-MyStackA-Deploy
+ runs-on: ubuntu-latest
+ steps:
+ - name: Authenticate Via GitHub Secrets
+ uses: aws-actions/configure-aws-credentials@v1
+ with:
+ aws-region: us-east-1
+ role-duration-seconds: 1800
+ role-skip-session-tagging: true
+ aws-access-key-id: \${{ secrets.AWS_ACCESS_KEY_ID }}
+ aws-secret-access-key: \${{ secrets.AWS_SECRET_ACCESS_KEY }}
+ role-to-assume: arn:aws:iam::12345678901:role/cdk-hnb659fds-deploy-role-12345678901-us-east-1
+ role-external-id: Pipeline
+ - id: Deploy
+ uses: aws-actions/aws-cloudformation-github-deploy@v1
+ with:
+ name: MyStageB-MyStackB
+ template: https://cdk-hnb659fds-assets-12345678901-us-east-1.s3.us-east-1.amazonaws.com/\${{
+ needs.Assets-FileAsset1.outputs.asset-hash }}.json
+ no-fail-on-empty-changeset: \\"1\\"
+ role-arn: arn:aws:iam::12345678901:role/cdk-hnb659fds-cfn-exec-role-12345678901-us-east-1
+"
+`;
diff --git a/test/stage-options.test.ts b/test/stage-options.test.ts
index 7aedd4ab..52964211 100644
--- a/test/stage-options.test.ts
+++ b/test/stage-options.test.ts
@@ -1,7 +1,9 @@
import { readFileSync } from 'fs';
import { Stack, Stage } from 'aws-cdk-lib';
import { ShellStep } from 'aws-cdk-lib/pipelines';
-import { GitHubWorkflow, StackCapabilities, GitHubActionStep } from '../src';
+import * as YAML from 'yaml';
+import { GitHubWorkflow, StackCapabilities, GitHubActionStep, AddGitHubStageOptions } from '../src';
+import { GitHubStage, GitHubStageProps } from '../src/stage';
import { withTemporaryDirectory, TestApp } from './testutil';
let app: TestApp;
@@ -57,8 +59,9 @@ describe('github environment', () => {
const testStage = new Stage(app, 'MyStage1', {
env: { account: '111111111111', region: 'us-east-1' },
});
- const prodStage = new Stage(app, 'MyStage2', {
+ const prodStage = new GitHubStage(app, 'MyStage2', {
env: { account: '222222222222', region: 'us-west-2' },
+ gitHubEnvironment: 'prod',
});
// Two stacks
@@ -68,9 +71,7 @@ describe('github environment', () => {
pipeline.addStageWithGitHubOptions(testStage, {
gitHubEnvironment: 'test',
});
- pipeline.addStageWithGitHubOptions(prodStage, {
- gitHubEnvironment: 'prod',
- });
+ pipeline.addStage(prodStage);
app.synth();
@@ -175,7 +176,7 @@ describe('job settings', () => {
commands: ['yarn build'],
}),
jobSettings: {
- if: 'github.repository == \'another/repo\'',
+ if: 'github.repository == \'another/repoA\'',
},
});
@@ -187,13 +188,15 @@ describe('job settings', () => {
pipeline.addStageWithGitHubOptions(stage, {
jobSettings: {
- if: 'github.repository == \'github/repo\'',
+ if: 'github.repository == \'github/repoB\'',
},
});
app.synth();
- expect(readFileSync(pipeline.workflowPath, 'utf-8')).toContain('if: github.repository == \'github/repo\'\n');
+ const workflowFileContents = readFileSync(pipeline.workflowPath, 'utf-8');
+ expect(workflowFileContents).toContain('if: github.repository == \'another/repoA\'\n');
+ expect(workflowFileContents).toContain('if: github.repository == \'github/repoB\'\n');
});
});
});
@@ -202,48 +205,56 @@ test('can set pre/post github action job step', () => {
withTemporaryDirectory((dir) => {
const pipeline = new GitHubWorkflow(app, 'Pipeline', {
workflowPath: `${dir}/.github/workflows/deploy.yml`,
- synth: new ShellStep('Build', {
+ synth: new ShellStep('Synth', {
installCommands: ['yarn'],
commands: ['yarn build'],
}),
+ jobSettings: { if: 'contains(fromJson(\'["push", "pull_request"]\'), github.event_name)' },
});
- const stage = new Stage(app, 'MyStack', {
+ const stage = new GitHubStage(app, 'MyPrePostStack', {
env: { account: '111111111111', region: 'us-east-1' },
+ jobSettings: { if: "success() && contains(github.event.issue.labels.*.name, 'deploy')" },
});
new Stack(stage, 'MyStack');
- pipeline.addStageWithGitHubOptions(stage, {
- pre: [new GitHubActionStep('PreDeployAction', {
- jobSteps: [
- {
- name: 'pre deploy action',
- uses: 'my-pre-deploy-action@1.0.0',
- with: {
- 'app-id': 1234,
- 'secrets': 'my-secrets',
+ pipeline.addStage(stage, {
+ pre: [
+ new GitHubActionStep('PreDeployAction', {
+ jobSteps: [
+ {
+ name: 'pre deploy action',
+ uses: 'my-pre-deploy-action@1.0.0',
+ with: {
+ 'app-id': 1234,
+ 'secrets': 'my-secrets',
+ },
},
- },
- ],
- })],
-
- post: [new GitHubActionStep('PostDeployAction', {
- jobSteps: [
- {
- name: 'Checkout',
- uses: 'actions/checkout@v2',
- },
- {
- name: 'post deploy action',
- uses: 'my-post-deploy-action@1.0.0',
- with: {
- 'app-id': 4321,
- 'secrets': 'secrets',
+ ],
+ }),
+ ],
+
+
+ post: [
+ new GitHubActionStep('PostDeployAction', {
+ jobSteps: [
+ {
+ name: 'Checkout',
+ uses: 'actions/checkout@v2',
},
- },
- ],
- })],
+ {
+ name: 'post deploy action',
+ uses: 'my-post-deploy-action@1.0.0',
+ with: {
+ 'app-id': 4321,
+ 'secrets': 'secrets',
+ },
+ },
+ ],
+ if: "failure() && contains(github.event.issue.labels.*.name, 'cleanupFailure')",
+ }),
+ ],
});
app.synth();
@@ -253,5 +264,221 @@ test('can set pre/post github action job step', () => {
expect(workflowFileContents).toContain('my-pre-deploy-action\@1\.0\.0');
expect(workflowFileContents).toContain('my-post-deploy-action\@1\.0\.0');
expect(workflowFileContents).toContain('actions/checkout@v2');
+ expect(workflowFileContents).toContain('contains(fromJson(\'["push", "pull_request"]\'), github.event_name)');
+ expect(workflowFileContents).toContain("success() && contains(github.event.issue.labels.*.name, 'deploy')");
+ expect(workflowFileContents).toContain("failure() && contains(github.event.issue.labels.*.name, 'cleanupFailure')");
+ });
+});
+
+test('stages in github waves works', () => {
+ withTemporaryDirectory((dir) => {
+ const pipeline = new GitHubWorkflow(app, 'Pipeline', {
+ workflowPath: `${dir}/.github/workflows/deploy.yml`,
+ synth: new ShellStep('Build', {
+ installCommands: ['yarn'],
+ commands: ['yarn build'],
+ }),
+ });
+
+ const stageA = new Stage(app, 'MyStageA', {
+ env: { account: '111111111111', region: 'us-east-1' },
+ });
+
+ new Stack(stageA, 'MyStackA');
+
+ const wave = pipeline.addGitHubWave('MyWave');
+
+ const stageAOptions: AddGitHubStageOptions = {
+ jobSettings: {
+ if: "success() && contains(github.event.issue.labels.*.name, 'deployToA')",
+ },
+ };
+ wave.addStageWithGitHubOptions(stageA, stageAOptions);
+
+ const stageBOptions: GitHubStageProps = {
+ env: { account: '12345678901', region: 'us-east-1' },
+ jobSettings: {
+ if: "success() && contains(github.event.issue.labels.*.name, 'deployToB')",
+ },
+ };
+ const stageB = new GitHubStage(app, 'MyStageB', stageBOptions);
+
+ new Stack(stageB, 'MyStackB');
+
+ wave.addStage(stageB);
+
+ app.synth();
+
+ const workflowFileContents = readFileSync(pipeline.workflowPath, 'utf-8');
+ expect(workflowFileContents).toMatchSnapshot();
+
+ const yaml = YAML.parse(workflowFileContents);
+ expect(yaml).toMatchObject({
+ jobs: {
+ 'MyWave-MyStageA-MyStackA-Deploy': {
+ if: stageAOptions.jobSettings?.if,
+ },
+ 'MyWave-MyStageB-MyStackB-Deploy': {
+ if: stageBOptions.jobSettings?.if,
+ },
+ },
+ });
+ });
+});
+
+
+test('github stages in waves works', () => {
+ withTemporaryDirectory((dir) => {
+ const buildIfStatement = "contains(github.event.issue.labels.*.name, 'deployToA') || contains(github.event.issue.labels.*.name, 'deployToB')";
+ const pipeline = new GitHubWorkflow(app, 'Pipeline', {
+ workflowPath: `${dir}/.github/workflows/deploy.yml`,
+ synth: new ShellStep('Build', {
+ installCommands: ['yarn'],
+ commands: ['yarn build'],
+ }),
+ jobSettings: {
+ if: buildIfStatement,
+ },
+ });
+
+ const stageAOptions: GitHubStageProps = {
+ jobSettings: {
+ if: "success() && contains(github.event.issue.labels.*.name, 'deployToA')",
+ },
+ };
+ const stageA = new GitHubStage(app, 'MyStageA', {
+ env: { account: '111111111111', region: 'us-east-1' },
+ ...stageAOptions,
+ });
+
+ new Stack(stageA, 'MyStackA');
+
+ const stageBOptions: GitHubStageProps = {
+ env: { account: '12345678901', region: 'us-east-1' },
+ jobSettings: {
+ if: "success() && contains(github.event.issue.labels.*.name, 'deployToB')",
+ },
+ };
+ const stageB = new GitHubStage(app, 'MyStageB', stageBOptions);
+
+ new Stack(stageB, 'MyStackB');
+
+ // Make a wave to have the stages be parallel (not depend on each other)
+ const wave = pipeline.addWave('MyWave',
+ {
+ pre: [
+ new GitHubActionStep('PreWaveAction', {
+ jobSteps: [
+ {
+ name: 'pre wave action',
+ uses: 'my-pre-wave-action@1.0.0',
+ with: {
+ 'app-id': 1234,
+ 'secrets': 'my-secrets',
+ },
+ },
+ ],
+ }),
+ ],
+
+
+ post: [
+ new GitHubActionStep('PostWaveAction', {
+ jobSteps: [
+ {
+ name: 'Checkout',
+ uses: 'actions/checkout@v2',
+ },
+ {
+ name: 'post wave action',
+ uses: 'my-post-wave-action@1.0.0',
+ with: {
+ 'app-id': 4321,
+ 'secrets': 'secrets',
+ },
+ },
+ ],
+ if: "failure() && contains(github.event.issue.labels.*.name, 'cleanupFailure')",
+ }),
+ ],
+ },
+ );
+ wave.addStage(stageA);
+ wave.addStage(stageB);
+
+ app.synth();
+
+ const workflowFileContents = readFileSync(pipeline.workflowPath, 'utf-8');
+ expect(workflowFileContents).toMatchSnapshot();
+
+ const yaml = YAML.parse(workflowFileContents);
+ expect(yaml).toMatchObject({
+ jobs: {
+ 'Build-Build': {
+ if: buildIfStatement,
+ },
+ 'MyWave-MyStageA-MyStackA-Deploy': {
+ if: stageAOptions.jobSettings?.if,
+ },
+ 'MyWave-MyStageB-MyStackB-Deploy': {
+ if: stageBOptions.jobSettings?.if,
+ },
+ },
+ });
+ });
+});
+
+
+test('stages in pipeline works with `if`', () => {
+ withTemporaryDirectory((dir) => {
+ const pipeline = new GitHubWorkflow(app, 'Pipeline', {
+ workflowPath: `${dir}/.github/workflows/deploy.yml`,
+ synth: new ShellStep('Build', {
+ installCommands: ['yarn'],
+ commands: ['yarn build'],
+ }),
+ });
+
+ const stageA = new Stage(app, 'MyStageA', {
+ env: { account: '111111111111', region: 'us-east-1' },
+ });
+
+ new Stack(stageA, 'MyStackA');
+
+ const stageAOptions: AddGitHubStageOptions = {
+ jobSettings: {
+ if: "success() && contains(github.event.issue.labels.*.name, 'deployToA')",
+ },
+ };
+ pipeline.addStageWithGitHubOptions(stageA, stageAOptions);
+
+ const stageBOptions: GitHubStageProps = {
+ env: { account: '12345678901', region: 'us-east-1' },
+ jobSettings: {
+ if: "success() && contains(github.event.issue.labels.*.name, 'deployToB')",
+ },
+ };
+ const stageB = new GitHubStage(app, 'MyStageB', stageBOptions);
+
+ new Stack(stageB, 'MyStackB');
+
+ pipeline.addStage(stageB);
+
+ app.synth();
+
+ const workflowFileContents = readFileSync(pipeline.workflowPath, 'utf-8');
+ expect(workflowFileContents).toMatchSnapshot();
+
+ const yaml = YAML.parse(workflowFileContents);
+ expect(yaml).toMatchObject({
+ jobs: {
+ 'MyStageA-MyStackA-Deploy': {
+ if: stageAOptions.jobSettings?.if,
+ },
+ 'MyStageB-MyStackB-Deploy': {
+ if: stageBOptions.jobSettings?.if,
+ },
+ },
+ });
});
});