From 663957830ab5f8942a163c728d86bdceed7a85f0 Mon Sep 17 00:00:00 2001
From: Matt Wise <768067+diranged@users.noreply.github.com>
Date: Sun, 8 Oct 2023 15:48:24 -0700
Subject: [PATCH] feat(pipeline): allow the build step to have its own runner
configuration (#10)
This change allows the `build` (synth) step to have its own Github
Runner configuration that is separate from the deployment steps. This
allows the build step to have faster/larger hardware without impacting
the cost of the other steps. I've also added in the ability to pass in
runner groups.
---
API.md | 57 +++++++++++++++----
src/pipeline.ts | 15 ++++-
src/workflows-model.ts | 30 +++++++++-
test/__snapshots__/github.test.ts.snap | 79 ++++++++++++++++++++++++++
test/github.test.ts | 36 ++++++++++++
5 files changed, 201 insertions(+), 16 deletions(-)
diff --git a/API.md b/API.md
index 9f2d3f98..da498f36 100644
--- a/API.md
+++ b/API.md
@@ -2313,12 +2313,13 @@ const gitHubWorkflowProps: GitHubWorkflowProps = { ... }
| synth
| aws-cdk-lib.pipelines.IFileSetProducer
| The build step that produces the CDK Cloud Assembly. |
| awsCreds
| AwsCredentialsProvider
| Configure provider for AWS credentials used for deployment. |
| buildContainer
| ContainerOptions
| Build container options. |
+| buildRunner
| Runner
| The type of Github Runner that the build workflow runs on. |
| cdkCliVersion
| string
| Version of the CDK CLI to use. |
| diffFirst
| boolean
| Whether or not to run a "diff" job first. |
| jobSettings
| JobSettings
| Job level settings that will be applied to all jobs in the workflow, including synth and asset deploy jobs. |
| postBuildSteps
| JobStep[]
| GitHub workflow steps to execute after build. |
| preBuildSteps
| JobStep[]
| GitHub workflow steps to execute before build. |
-| runner
| Runner
| The type of runner to run the job on. |
+| runner
| Runner
| The type of runner that the entire workflow runs on. |
| workflowName
| string
| Name of the workflow. |
| workflowPath
| string
| File path for the GitHub workflow. |
| workflowTriggers
| WorkflowTriggers
| GitHub workflow triggers. |
@@ -2369,6 +2370,19 @@ Build container options.
---
+##### `buildRunner`Optional
+
+```typescript
+public readonly buildRunner: Runner;
+```
+
+- *Type:* Runner
+- *Default:* Runner.UBUNTU_LATEST
+
+The type of Github Runner that the build workflow runs on.
+
+---
+
##### `cdkCliVersion`Optional
```typescript
@@ -2449,10 +2463,10 @@ public readonly runner: Runner;
- *Type:* Runner
- *Default:* Runner.UBUNTU_LATEST
-The type of runner to run the job on.
+The type of runner that the entire workflow runs on.
-The runner can be either a
-GitHub-hosted runner or a self-hosted runner.
+You can also set the
+runner specifically for the `build` (synth) step separately.
---
@@ -2589,7 +2603,7 @@ const job: Job = { ... }
| **Name** | **Type** | **Description** |
| --- | --- | --- |
| permissions
| JobPermissions
| You can modify the default permissions granted to the GITHUB_TOKEN, adding or removing access as required, so that you only allow the minimum required access. |
-| runsOn
| string \| string[]
| The type of machine to run the job on. |
+| runsOn
| string \| string[] \| {[ key: string ]: string \| string[]}
| The type of machine to run the job on. |
| steps
| JobStep[]
| A job contains a sequence of tasks called steps. |
| concurrency
| any
| Concurrency ensures that only a single job or workflow using the same concurrency group will run at a time. |
| container
| ContainerOptions
| A container to run any steps in a job that don't already specify a container. |
@@ -2630,10 +2644,10 @@ access.
##### `runsOn`Required
```typescript
-public readonly runsOn: string | string[];
+public readonly runsOn: string | string[] | {[ key: string ]: string | string[]};
```
-- *Type:* string | string[]
+- *Type:* string | string[] | {[ key: string ]: string | string[]}
The type of machine to run the job on.
@@ -5436,10 +5450,33 @@ In case of self-hosted, a list of labels can be supplied.
| **Name** | **Description** |
| --- | --- |
+| onGroup
| Creates a runner that sets runsOn to a Github-hosted runner Group. |
| selfHosted
| Creates a runner instance that sets runsOn to `self-hosted`. |
---
+##### `onGroup`
+
+```typescript
+import { Runner } from '@nextdoor/cdk-pipelines-github'
+
+Runner.onGroup(name: string, labels?: string[])
+```
+
+Creates a runner that sets runsOn to a Github-hosted runner Group.
+
+###### `name`Required
+
+- *Type:* string
+
+---
+
+###### `labels`Optional
+
+- *Type:* string[]
+
+---
+
##### `selfHosted`
```typescript
@@ -5462,17 +5499,17 @@ Additional labels can be supplied. There is no need to supply `self-hosted` as a
| **Name** | **Type** | **Description** |
| --- | --- | --- |
-| runsOn
| string \| string[]
| *No description.* |
+| runsOn
| string \| string[] \| {[ key: string ]: string \| string[]}
| *No description.* |
---
##### `runsOn`Required
```typescript
-public readonly runsOn: string | string[];
+public readonly runsOn: string | string[] | {[ key: string ]: string | string[]};
```
-- *Type:* string | string[]
+- *Type:* string | string[] | {[ key: string ]: string | string[]}
---
diff --git a/src/pipeline.ts b/src/pipeline.ts
index a826c348..443c9f87 100644
--- a/src/pipeline.ts
+++ b/src/pipeline.ts
@@ -83,6 +83,13 @@ export interface GitHubWorkflowProps extends PipelineBaseProps {
*/
readonly buildContainer?: github.ContainerOptions;
+ /**
+ * The type of Github Runner that the build workflow runs on.
+ *
+ * @default Runner.UBUNTU_LATEST
+ */
+ readonly buildRunner?: github.Runner;
+
/**
* GitHub workflow steps to execute before build.
*
@@ -104,8 +111,8 @@ export interface GitHubWorkflowProps extends PipelineBaseProps {
readonly diffFirst?: boolean;
/**
- * The type of runner to run the job on. The runner can be either a
- * GitHub-hosted runner or a self-hosted runner.
+ * The type of runner that the entire workflow runs on. You can also set the
+ * runner specifically for the `build` (synth) step separately.
*
* @default Runner.UBUNTU_LATEST
*/
@@ -132,6 +139,7 @@ export class GitHubWorkflow extends PipelineBase {
private readonly awsCredentials: AwsCredentialsProvider;
private readonly workflowTriggers: github.WorkflowTriggers;
private readonly buildContainer?: github.ContainerOptions;
+ private readonly buildRunner: github.Runner;
private readonly preBuildSteps: github.JobStep[];
private readonly postBuildSteps: github.JobStep[];
private readonly diffFirst: boolean;
@@ -176,6 +184,7 @@ export class GitHubWorkflow extends PipelineBase {
};
this.runner = props.runner ?? github.Runner.UBUNTU_LATEST;
+ this.buildRunner = props.buildRunner ?? this.runner;
}
/**
@@ -484,7 +493,7 @@ export class GitHubWorkflow extends PipelineBase {
// that it is being used in the preBuildSteps.
idToken: this.awsCredentials.jobPermission(),
},
- runsOn: this.runner.runsOn,
+ runsOn: this.buildRunner.runsOn,
needs: this.renderDependencies(node),
env: step.env,
container: this.buildContainer,
diff --git a/src/workflows-model.ts b/src/workflows-model.ts
index 7e012edc..a5b09024 100644
--- a/src/workflows-model.ts
+++ b/src/workflows-model.ts
@@ -12,7 +12,7 @@ export interface Job {
*
* @example ["ubuntu-latest"]
*/
- readonly runsOn: string[] | string;
+ readonly runsOn: string[] | string | Record;
/**
* A job contains a sequence of tasks called steps. Steps can run commands,
@@ -225,15 +225,39 @@ export class Runner {
}
}
- public get runsOn(): string[] | string {
+ /**
+ * Creates a runner that sets runsOn to a Github-hosted runner Group.
+ */
+ public static onGroup(name: string, labels?: string[]) {
+ return new Runner(labels ?? [], name);
+ }
+
+ public get runsOn(): string[] | string | Record {
+ /**
+ * If the group was set - then we return in this format:
+ *
+ * https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#example-combining-groups-and-labels
+ */
+ if (this.group) {
+ return {
+ group: this.group,
+ labels: this.labels,
+ };
+ }
+
+ /** Otherwise, return in string format... */
if (this.labels[0] === 'self-hosted') {
+ /** https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#choosing-self-hosted-runners */
return this.labels;
} else {
return this.labels[0];
}
}
- private constructor(private readonly labels: string[]) {}
+ private constructor(
+ private readonly labels: string[],
+ private readonly group?: string,
+ ) {}
}
/**
diff --git a/test/__snapshots__/github.test.ts.snap b/test/__snapshots__/github.test.ts.snap
index ba5d4669..7623c29a 100644
--- a/test/__snapshots__/github.test.ts.snap
+++ b/test/__snapshots__/github.test.ts.snap
@@ -240,6 +240,85 @@ jobs:
"
`;
+exports[`pipeline with a dedicated buildRunner setting 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:
+ group: test-group
+ labels: []
+ needs: []
+ env: {}
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+ - name: Install
+ run: yarn
+ - name: Build
+ run: yarn build
+ - name: Package
+ run: tar -zcf /tmp/workspace.tgz .
+ - name: Upload
+ uses: actions/upload-artifact@v3
+ with:
+ name: source
+ path: /tmp/workspace.tgz
+ if-no-files-found: error
+"
+`;
+
+exports[`pipeline with a dedicated buildRunner setting and labels 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:
+ group: test-group
+ labels:
+ - withLabel
+ needs: []
+ env: {}
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+ - name: Install
+ run: yarn
+ - name: Build
+ run: yarn build
+ - name: Package
+ run: tar -zcf /tmp/workspace.tgz .
+ - name: Upload
+ uses: actions/upload-artifact@v3
+ with:
+ name: source
+ path: /tmp/workspace.tgz
+ if-no-files-found: error
+"
+`;
+
exports[`pipeline with aws credentials in custom secrets 1`] = `
"# AUTOMATICALLY GENERATED FILE, DO NOT EDIT MANUALLY.
# Generated by AWS CDK and [cdk-pipelines-github](https://github.com/cdklabs/cdk-pipelines-github)
diff --git a/test/github.test.ts b/test/github.test.ts
index aa84e22f..71fc6fe4 100644
--- a/test/github.test.ts
+++ b/test/github.test.ts
@@ -40,6 +40,42 @@ test('pipeline with only a synth step', () => {
});
});
+test('pipeline with a dedicated buildRunner setting', () => {
+ withTemporaryDirectory((dir) => {
+ const github = new GitHubWorkflow(app, 'Pipeline', {
+ workflowPath: `${dir}/.github/workflows/deploy.yml`,
+ awsCreds: AwsCredentials.runnerHasPreconfiguredCreds(),
+ buildRunner: Runner.onGroup('test-group'),
+ synth: new ShellStep('Build', {
+ installCommands: ['yarn'],
+ commands: ['yarn build'],
+ }),
+ });
+
+ app.synth();
+
+ expect(readFileSync(github.workflowPath, 'utf-8')).toMatchSnapshot();
+ });
+});
+
+test('pipeline with a dedicated buildRunner setting and labels', () => {
+ withTemporaryDirectory((dir) => {
+ const github = new GitHubWorkflow(app, 'Pipeline', {
+ workflowPath: `${dir}/.github/workflows/deploy.yml`,
+ awsCreds: AwsCredentials.runnerHasPreconfiguredCreds(),
+ buildRunner: Runner.onGroup('test-group', ['withLabel']),
+ synth: new ShellStep('Build', {
+ installCommands: ['yarn'],
+ commands: ['yarn build'],
+ }),
+ });
+
+ app.synth();
+
+ expect(readFileSync(github.workflowPath, 'utf-8')).toMatchSnapshot();
+ });
+});
+
test('pipeline with aws credentials using awsCreds', () => {
withTemporaryDirectory((dir) => {
const github = new GitHubWorkflow(app, 'Pipeline', {