Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add new api to unlock github-specific stage-level properties #178

Merged
merged 8 commits into from
Apr 28, 2022
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions API.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Name|Description

Name|Description
----|-----------
[AddGitHubStageOptions](#cdk-pipelines-github-addgithubstageoptions)|Options to pass to `addStageWithGitHubOpts`.
[AwsCredentialsSecrets](#cdk-pipelines-github-awscredentialssecrets)|Names of secrets for AWS credentials.
[CheckRunOptions](#cdk-pipelines-github-checkrunoptions)|Check run options.
[CheckSuiteOptions](#cdk-pipelines-github-checksuiteoptions)|Check suite options.
Expand Down Expand Up @@ -268,6 +269,27 @@ Name | Type | Description
### Methods


#### addStageWithGitHubOptions(stage, options?) <a id="cdk-pipelines-github-githubworkflow-addstagewithgithuboptions"></a>

Deploy a single Stage by itself with options for further GitHub configuration.

Add a Stage to the pipeline, to be deployed in sequence with other Stages added to the pipeline.
All Stacks in the stage will be deployed in an order automatically determined by their relative dependencies.

```ts
addStageWithGitHubOptions(stage: Stage, options?: AddGitHubStageOptions): StageDeployment
```

* **stage** (<code>[Stage](#aws-cdk-lib-stage)</code>) *No description*
* **options** (<code>[AddGitHubStageOptions](#cdk-pipelines-github-addgithubstageoptions)</code>) *No description*
* **post** (<code>Array<[pipelines.Step](#aws-cdk-lib-pipelines-step)></code>) Additional steps to run after all of the stacks in the stage. __*Default*__: No additional steps
* **pre** (<code>Array<[pipelines.Step](#aws-cdk-lib-pipelines-step)></code>) Additional steps to run before any of the stacks in the stage. __*Default*__: No additional steps
* **stackSteps** (<code>Array<[pipelines.StackSteps](#aws-cdk-lib-pipelines-stacksteps)></code>) Instructions for stack level steps. __*Default*__: No additional instructions
* **gitHubEnvironment** (<code>string</code>) Run the stage in a specific GitHub Environment. __*Default*__: no GitHub environment

__Returns__:
* <code>[pipelines.StageDeployment](#aws-cdk-lib-pipelines-stagedeployment)</code>

#### protected doBuildPipeline() <a id="cdk-pipelines-github-githubworkflow-dobuildpipeline"></a>

Implemented by subclasses to do the actual pipeline construction.
Expand Down Expand Up @@ -321,6 +343,22 @@ __Returns__:



## struct AddGitHubStageOptions <a id="cdk-pipelines-github-addgithubstageoptions"></a>


Options to pass to `addStageWithGitHubOpts`.



Name | Type | Description
-----|------|-------------
**gitHubEnvironment**? | <code>string</code> | Run the stage in a specific GitHub Environment.<br/>__*Default*__: no GitHub environment
**post**? | <code>Array<[pipelines.Step](#aws-cdk-lib-pipelines-step)></code> | Additional steps to run after all of the stacks in the stage.<br/>__*Default*__: No additional steps
**pre**? | <code>Array<[pipelines.Step](#aws-cdk-lib-pipelines-step)></code> | Additional steps to run before any of the stacks in the stage.<br/>__*Default*__: No additional steps
**stackSteps**? | <code>Array<[pipelines.StackSteps](#aws-cdk-lib-pipelines-stacksteps)></code> | Instructions for stack level steps.<br/>__*Default*__: No additional instructions



## struct AwsCredentialsSecrets <a id="cdk-pipelines-github-awscredentialssecrets"></a>


Expand Down
62 changes: 62 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ Workflows.
- [Runner Types](#runner-types)
- [GitHub Hosted Runner](#github-hosted-runner)
- [Self Hosted Runner](#self-hosted-runner)
- [Additional Features](#additional-features)
- [Configure GitHub Environment](#configure-github-environment)
- [Manual Approval Step](#manual-approval-step)
- [Tutorial](#tutorial)
- [Not supported yet](#not-supported-yet)
- [Contributing](#contributing)
Expand Down Expand Up @@ -329,6 +332,65 @@ const pipeline = new GitHubWorkflow(app, 'Pipeline', {
});
```

## Additional Features

Below is a compilation of additional features available for GitHub Workflows.

### Configure GitHub Environment

You can run your GitHub Workflow in select
[GitHub Environments](https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment).
Via the GitHub UI, you can configure environments with protection rules and secrets, and reference
those environments in your CDK app. A workflow that references an environment must follow any
protection rules for the environment before running or accessing the environment's secrets.

Assuming (just like in the main [example](#usage)) you have a
[`Stage`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.Stage.html)
called `MyStage` that includes CDK stacks for your app and you want to deploy it
to two AWS environments (`BETA_ENV` and `PROD_ENV`) as well as GitHub Environments
`beta` and `prod`:

```ts
import { App } from 'aws-cdk-lib';
import { ShellStep } from 'aws-cdk-lib/pipelines';
import { GitHubWorkflow } from 'cdk-pipelines-github';

const app = new App();

const pipeline = new GitHubWorkflow(app, 'Pipeline', {
synth: new ShellStep('Build', {
commands: [
'yarn install',
'yarn build',
],
}),
gitHubActionRoleArn: 'arn:aws:iam::<account-id>:role/GitHubActionRole',
});

pipeline.addStageWithGitHubOptions(new MyStage(this, 'Beta', {
env: BETA_ENV,
gitHubEnvironment: 'beta',
}));
pipeline.addStageWithGitHubOptions(new MyStage(this, 'Prod', {
env: PROD_ENV,
gitHubEnvironment: 'prod',
}));

app.synth();
```

#### Manual Approval Step

One use case for using GitHub Environments with your CDK Pipeline is to create a
manual approval step for specific environments via Environment protection rules.
From the GitHub UI, you can specify up to 5 required reviewers that must approve
before the deployment can proceed:

<img width="1134" alt="require-reviewers" src="https://user-images.githubusercontent.com/7248260/163494925-627f5ca7-a34e-48fa-bec7-1e4924ab6c0c.png">

For more information and a tutorial for how to set this up, see this
[discussion](https://github.com/cdklabs/cdk-pipelines-github/issues/162).

## Tutorial

You can find an example usage in [test/example-app.ts](./test/example-app.ts)
Expand Down
43 changes: 42 additions & 1 deletion src/pipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ 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, Step } from 'aws-cdk-lib/pipelines';
import { AddStageOpts, PipelineBase, PipelineBaseProps, ShellStep, StackAsset, StackDeployment, StackOutputReference, StageDeployment, Step } 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';
Expand All @@ -14,6 +14,26 @@ import * as github from './workflows-model';
const CDKOUT_ARTIFACT = 'cdk.out';
const ASSET_HASH_NAME = 'asset-hash';

/**
* Options to pass to `addStageWithGitHubOpts`.
*/
export interface AddGitHubStageOptions extends AddStageOpts {
/**
* 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;
}

/**
* Props for `GitHubWorkflow`.
*/
Expand Down Expand Up @@ -142,6 +162,7 @@ export class GitHubWorkflow extends PipelineBase {
private readonly assetHashMap: Record<string, string> = {};
private readonly runner: github.Runner;
private readonly publishAssetsAuthRegion: string;
private readonly stackEnvs: Record<string, string> = {};

constructor(scope: Construct, id: string, props: GitHubWorkflowProps) {
super(scope, id, props);
Expand Down Expand Up @@ -179,6 +200,25 @@ export class GitHubWorkflow extends PipelineBase {
this.publishAssetsAuthRegion = props.publishAssetsAuthRegion ?? 'us-west-2';
}

/**
* Deploy a single Stage by itself with options for further GitHub configuration.
*
* Add a Stage to the pipeline, to be deployed in sequence with other Stages added to the pipeline.
* All Stacks in the stage will be deployed in an order automatically determined by their relative dependencies.
*/
public addStageWithGitHubOptions(stage: Stage, options?: AddGitHubStageOptions): StageDeployment {
const stageDeployment = this.addStage(stage, options);

// keep track of GitHub specific options
if (options?.gitHubEnvironment) {
for (const stack of stageDeployment.stacks) {
this.stackEnvs[stack.stackArtifactId] = options.gitHubEnvironment;
}
}

return stageDeployment;
}

protected doBuildPipeline() {
const app = Stage.of(this);
if (!app) {
Expand Down Expand Up @@ -421,6 +461,7 @@ export class GitHubWorkflow extends PipelineBase {
contents: github.JobPermission.READ,
idToken: this.useGitHubActionRole ? github.JobPermission.WRITE : github.JobPermission.NONE,
},
environment: this.stackEnvs[stack.stackArtifactId],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kaizencc, I just tried the new feature and found one thing.
If ppl use the normal AddStage method, the output deploy.yml will have environment: null. Wonder if we shall skip adding the environment key if this.stackEnvs[stack.stackArtifactId] is null.
Possibliy:

...(this.stackEnvs[stack.stackArtifactId] && {
          environment: this.stackEnvs[stack.stackArtifactId],
        }),

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @julianiag, caught me at a good time lol. Does that create an environment for you when you deploy? Or is it simply ignored? I agree it's not great. Maybe I can change it up tomorrow :).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or, if you add that in as a PR I will approve it, but you'll have to run yarn build to update the snapshots.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

needs: this.renderDependencies(node),
runsOn: this.runner.runsOn,
steps: [
Expand Down
129 changes: 129 additions & 0 deletions test/__snapshots__/github.test.ts.snap

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading