Skip to content

Commit

Permalink
feat: configure additional job settings for docker asset publishing j…
Browse files Browse the repository at this point in the history
…obs (#846)

Currently if you want to change any of the job settings for the docker asset publishing jobs you can use escape hatches. This PR adds a new property `dockerAssetJobSettings` which allow you to provide additional settings when you create the workflow so you don't have to use escape hatches.

Currently I've only implemented support for two things:
1. `setupSteps` - These are additional steps to run just prior to building/publishing the image. The use case I've run into for this is the need to setup docker `buildx` prior to running the build.
2. `permissions` - Allow setting/overriding permissions for docker asset steps. For example, when interacting with GitHub packages you have to set the `packages: read` permission.

Fixes #
  • Loading branch information
corymhall authored Jan 22, 2024
1 parent fbd298c commit d0e55af
Show file tree
Hide file tree
Showing 6 changed files with 256 additions and 18 deletions.
110 changes: 110 additions & 0 deletions API.md

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

49 changes: 49 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ Workflows.
- [Waves for Parallel Builds](#waves-for-parallel-builds)
- [Manual Approval Step](#manual-approval-step)
- [Pipeline YAML Comments](#pipeline-yaml-comments)
- [Common Configuration for Docker Asset Publishing Steps](#common-configuration-for-docker-asset-publishing)
- [Tutorial](#tutorial)
- [Not supported yet](#not-supported-yet)
- [Contributing](#contributing)
Expand Down Expand Up @@ -592,6 +593,54 @@ on:
< the rest of the pipeline YAML contents>
```

### Common Configuration for Docker Asset Publishing Steps

You can provide common job configuration for all of the docker asset publishing
jobs using the `dockerAssetJobSettings` property. You can use this to:

- Set additional `permissions` at the job level
- Run additional steps prior to the docker build/push step

Below is an example of example of configuration an additional `permission` which
allows the job to authenticate against GitHub packages. It also shows
configuration additional `setupSteps`, in this case setup steps to configure
docker `buildx` and `QEMU` to enable building images for arm64 architecture.

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

const app = new App();

const pipeline = new GitHubWorkflow(app, 'Pipeline', {
synth: new ShellStep('Build', {
commands: [
'yarn install',
'yarn build',
],
}),
dockerAssetJobSettings: {
permissions: {
packages: JobPermission.READ,
},
setupSteps: [
{
name: 'Setup Docker QEMU',
uses: 'docker/setup-qemu-action@v3',
},
{
name: 'Setup Docker buildx',
uses: 'docker/setup-buildx-action@v3',
},
],
},
awsCreds: AwsCredentials.fromOpenIdConnect({
gitHubActionRoleArn: 'arn:aws:iam::<account-id>:role/GitHubActionRole',
}),
});

app.synth();
```

## Tutorial

You can find an example usage in [test/example-app.ts](./test/example-app.ts)
Expand Down
3 changes: 2 additions & 1 deletion rosetta/default.ts-fixture
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
JsonPatch,
Runner,
DockerCredential,
JobPermission,
} from 'cdk-pipelines-github';

const BETA_ENV = {
Expand All @@ -34,4 +35,4 @@ class Fixture extends Stack {

/// here
}
}
}
55 changes: 48 additions & 7 deletions src/pipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,25 @@ import { YamlFile } from './yaml-file';
const CDKOUT_ARTIFACT = 'cdk.out';
const ASSET_HASH_NAME = 'asset-hash';

/**
* Job level settings applied to all docker asset publishing jobs in the workflow.
*/
export interface DockerAssetJobSettings {
/**
* GitHub workflow steps to execute before building and publishing the image.
*
* @default []
*/
readonly setupSteps?: github.JobStep[];

/**
* Additional permissions to grant to the docker image publishing job.
*
* @default - no additional permissions
*/
readonly permissions?: github.JobPermissions;
}

/**
* Job level settings applied to all jobs in the workflow.
*/
Expand Down Expand Up @@ -155,6 +174,13 @@ export interface GitHubWorkflowProps extends PipelineBaseProps {
* @see https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#example-only-run-job-for-specific-repository
*/
readonly jobSettings?: JobSettings;

/**
* Job level settings applied to all docker asset publishing jobs in the workflow.
*
* @default - no additional settings
*/
readonly dockerAssetJobSettings?: DockerAssetJobSettings;
}

/**
Expand Down Expand Up @@ -186,6 +212,7 @@ export class GitHubWorkflow extends PipelineBase {
}
> = {};
private readonly jobSettings?: JobSettings;
private readonly dockerAssetJobSettings?: DockerAssetJobSettings;
// in order to keep track of if this pipeline has been built so we can
// catch later calls to addWave() or addStage()
private builtGH = false;
Expand All @@ -199,6 +226,7 @@ export class GitHubWorkflow extends PipelineBase {
this.preBuildSteps = props.preBuildSteps ?? [];
this.postBuildSteps = props.postBuildSteps ?? [];
this.jobSettings = props.jobSettings;
this.dockerAssetJobSettings = props.dockerAssetJobSettings;

this.awsCredentials = this.getAwsCredentials(props);

Expand Down Expand Up @@ -492,13 +520,28 @@ export class GitHubWorkflow extends PipelineBase {
const cdkoutDir = options.assemblyDir;
const jobId = node.uniqueId;
const assetId = assets[0].assetId;
const preBuildSteps: github.JobStep[] = [];
let permissions: github.JobPermissions = {
contents: github.JobPermission.READ,
idToken: this.awsCredentials.jobPermission(),
};

// check if asset is docker asset and if we have docker credentials
const dockerLoginSteps: github.JobStep[] = [];
if (node.uniqueId.includes('DockerAsset') && this.dockerCredentials.length > 0) {
for (const creds of this.dockerCredentials) {
dockerLoginSteps.push(...this.stepsToConfigureDocker(creds));
if (node.uniqueId.includes('DockerAsset')) {
if (this.dockerCredentials.length > 0) {
for (const creds of this.dockerCredentials) {
dockerLoginSteps.push(...this.stepsToConfigureDocker(creds));
}
}
if (this.dockerAssetJobSettings?.setupSteps) {
preBuildSteps.push(...this.dockerAssetJobSettings.setupSteps);
}

permissions = {
...permissions,
...this.dockerAssetJobSettings?.permissions,
};
}

// create one file and make one step
Expand Down Expand Up @@ -527,10 +570,7 @@ export class GitHubWorkflow extends PipelineBase {
name: `Publish Assets ${jobId}`,
...this.renderJobSettingParameters(),
needs: this.renderDependencies(node),
permissions: {
contents: github.JobPermission.READ,
idToken: this.awsCredentials.jobPermission(),
},
permissions,
runsOn: this.runner.runsOn,
outputs: {
[ASSET_HASH_NAME]: `\${{ steps.Publish.outputs.${ASSET_HASH_NAME} }}`,
Expand All @@ -543,6 +583,7 @@ export class GitHubWorkflow extends PipelineBase {
},
...this.stepsToConfigureAws(this.publishAssetsAuthRegion),
...dockerLoginSteps,
...preBuildSteps,
publishStep,
],
},
Expand Down
41 changes: 39 additions & 2 deletions test/docker.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import * as codebuild from 'aws-cdk-lib/aws-codebuild';
import { ShellStep } from 'aws-cdk-lib/pipelines';
import * as YAML from 'yaml';
import { TestApp } from './testutil';
import { DockerCredential, GitHubWorkflow } from '../src';
import { DockerAssetJobSettings, DockerCredential, GitHubWorkflow, JobPermission } from '../src';

const dockers = join(__dirname, 'demo-image');

Expand Down Expand Up @@ -106,16 +106,53 @@ describe('correct format for docker credentials:', () => {
},
});
});

test('with setup job steps', () => {
const github = createDockerGithubWorkflow(app, [DockerCredential.dockerHub()], {
setupSteps: [
{
name: 'Setup Docker buildx',
uses: 'docker/setup-buildx-action@v3',
},
],
});
const file = fs.readFileSync(github.workflowPath, 'utf-8');
const workflow = YAML.parse(file);
const steps = findStepByJobAndUses(workflow, 'Assets-DockerAsset1', 'docker/setup-buildx-action@v3');
expect(steps.length).toEqual(1);
expect(steps[0]).toEqual({
name: 'Setup Docker buildx',
uses: 'docker/setup-buildx-action@v3',
});
});

test('with permissions', () => {
const github = createDockerGithubWorkflow(app, [DockerCredential.dockerHub()], {
permissions: {
packages: JobPermission.READ,
},
});
const file = fs.readFileSync(github.workflowPath, 'utf-8');
const workflow = YAML.parse(file);

const permissions = workflow.jobs['Assets-DockerAsset1'].permissions;
expect(permissions).toEqual({
'contents': 'read',
'id-token': 'none',
'packages': 'read',
});
});
});

function createDockerGithubWorkflow(app: App, dockerCredentials: DockerCredential[]) {
function createDockerGithubWorkflow(app: App, dockerCredentials: DockerCredential[], dockerAssetJobSettings?: DockerAssetJobSettings) {
const github = new GitHubWorkflow(app, 'Pipeline', {
workflowPath: `${mkoutdir()}/.github/workflows/deploy.yml`,
synth: new ShellStep('Build', {
installCommands: ['yarn'],
commands: ['yarn build'],
}),
dockerCredentials,
dockerAssetJobSettings,
});

github.addStage(new MyDockerStage(app, 'MyStage', {
Expand Down
Loading

0 comments on commit d0e55af

Please sign in to comment.