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', {