From 2535ddd121d6527f950a8355eca0a1e29c571315 Mon Sep 17 00:00:00 2001 From: Cory Hall <43035978+corymhall@users.noreply.github.com> Date: Thu, 1 Feb 2024 10:54:38 -0500 Subject: [PATCH] feat: authenticate with ghcr docker credentials (#854) This adds the ability to authenticate with the GitHub Container Registry (GHCR) in docker asset publishing steps. closes #843 --- API.md | 30 ++++++++++++++++++++++++------ README.md | 3 +++ src/docker-credentials.ts | 32 +++++++++++++++++++++++++++----- src/pipeline.ts | 19 ++++++++++++++----- test/docker.test.ts | 22 ++++++++++++++++++++++ 5 files changed, 90 insertions(+), 16 deletions(-) diff --git a/API.md b/API.md index e2a9c317..bba33603 100644 --- a/API.md +++ b/API.md @@ -340,6 +340,9 @@ const pipeline = new GitHubWorkflow(app, 'Pipeline', { // Authenticate to ECR DockerCredential.ecr('.dkr.ecr..amazonaws.com'), + // Authenticate to GHCR + DockerCredential.ghcr(), + // Authenticate to DockerHub DockerCredential.dockerHub({ // These properties are defaults; feel free to omit @@ -5126,6 +5129,7 @@ Uses the official Docker Login GitHub Action to authenticate. | customRegistry | Create a credential for a custom registry. | | dockerHub | Reference credential secrets to authenticate to DockerHub. | | ecr | Create a credential for ECR. | +| ghcr | Create a credential for the GitHub Container Registry (GHCR). | --- @@ -5207,14 +5211,28 @@ result in failures when using these credentials in the pipeline. --- +##### `ghcr` + +```typescript +import { DockerCredential } from 'cdk-pipelines-github' + +DockerCredential.ghcr() +``` + +Create a credential for the GitHub Container Registry (GHCR). + +For more information on authenticating to GHCR, + +> [https://docs.github.com/en/packages/managing-github-packages-using-github-actions-workflows/publishing-and-installing-a-package-with-github-actions](https://docs.github.com/en/packages/managing-github-packages-using-github-actions-workflows/publishing-and-installing-a-package-with-github-actions) + #### Properties | **Name** | **Type** | **Description** | | --- | --- | --- | | name | string | *No description.* | -| passwordKey | string | *No description.* | +| password | string | *No description.* | | registry | string | *No description.* | -| usernameKey | string | *No description.* | +| username | string | *No description.* | --- @@ -5228,10 +5246,10 @@ public readonly name: string; --- -##### `passwordKey`Optional +##### `password`Optional ```typescript -public readonly passwordKey: string; +public readonly password: string; ``` - *Type:* string @@ -5248,10 +5266,10 @@ public readonly registry: string; --- -##### `usernameKey`Optional +##### `username`Optional ```typescript -public readonly usernameKey: string; +public readonly username: string; ``` - *Type:* string diff --git a/README.md b/README.md index 93ff971c..04c7c5e9 100644 --- a/README.md +++ b/README.md @@ -340,6 +340,9 @@ const pipeline = new GitHubWorkflow(app, 'Pipeline', { // Authenticate to ECR DockerCredential.ecr('.dkr.ecr..amazonaws.com'), + // Authenticate to GHCR + DockerCredential.ghcr(), + // Authenticate to DockerHub DockerCredential.dockerHub({ // These properties are defaults; feel free to omit diff --git a/src/docker-credentials.ts b/src/docker-credentials.ts index 75ede3e9..e49eb7a4 100644 --- a/src/docker-credentials.ts +++ b/src/docker-credentials.ts @@ -15,11 +15,13 @@ export class DockerCredential { * found in your GitHub Secrets under these default keys. */ public static dockerHub(creds: DockerHubCredentialSecrets = {}): DockerCredential { + const username = creds.usernameKey ?? 'DOCKERHUB_USERNAME'; + const password = creds.personalAccessTokenKey ?? 'DOCKERHUB_TOKEN'; return new DockerCredential( 'docker', undefined, - creds.usernameKey ?? 'DOCKERHUB_USERNAME', - creds.personalAccessTokenKey ?? 'DOCKERHUB_TOKEN', + `\${{ secrets.${username} }}`, + `\${{ secrets.${password} }}`, ); } @@ -38,6 +40,21 @@ export class DockerCredential { return new DockerCredential('ecr', registry); } + /** + * Create a credential for the GitHub Container Registry (GHCR). + * + * For more information on authenticating to GHCR, + * @see https://docs.github.com/en/packages/managing-github-packages-using-github-actions-workflows/publishing-and-installing-a-package-with-github-actions + */ + public static ghcr(): DockerCredential { + return new DockerCredential( + 'ghcr', + 'ghcr.io', + '\${{ github.actor }}', + '\${{ secrets.GITHUB_TOKEN }}', + ); + } + /** * Create a credential for a custom registry. This method assumes that you will have long-lived * GitHub Secrets stored under the usernameKey and passwordKey that will authenticate to the @@ -46,14 +63,19 @@ export class DockerCredential { * @see https://github.com/marketplace/actions/docker-login */ public static customRegistry(registry: string, creds: ExternalDockerCredentialSecrets): DockerCredential { - return new DockerCredential('custom', registry, creds.usernameKey, creds.passwordKey); + return new DockerCredential( + 'custom', + registry, + `\${{ secrets.${creds.usernameKey} }}`, + `\${{ secrets.${creds.passwordKey} }}`, + ); } private constructor( readonly name: string, readonly registry?: string, - readonly usernameKey?: string, - readonly passwordKey?: string, + readonly username?: string, + readonly password?: string, ) {} } diff --git a/src/pipeline.ts b/src/pipeline.ts index c2a2c613..ae8ecc47 100644 --- a/src/pipeline.ts +++ b/src/pipeline.ts @@ -212,7 +212,7 @@ export class GitHubWorkflow extends PipelineBase { } > = {}; private readonly jobSettings?: JobSettings; - private readonly dockerAssetJobSettings?: DockerAssetJobSettings; + private 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; @@ -826,8 +826,8 @@ export class GitHubWorkflow extends PipelineBase { if (dockerCredential.name === 'docker') { params = { - username: `\${{ secrets.${dockerCredential.usernameKey} }}`, - password: `\${{ secrets.${dockerCredential.passwordKey} }}`, + username: dockerCredential.username, + password: dockerCredential.password, }; } else if (dockerCredential.name === 'ecr') { params = { @@ -836,8 +836,17 @@ export class GitHubWorkflow extends PipelineBase { } else { params = { registry: dockerCredential.registry, - username: `\${{ secrets.${dockerCredential.usernameKey} }}`, - password: `\${{ secrets.${dockerCredential.passwordKey} }}`, + username: dockerCredential.username, + password: dockerCredential.password, + }; + } + if (dockerCredential.name === 'ghcr') { + this.dockerAssetJobSettings = { + ...this.dockerAssetJobSettings, + permissions: { + ...this.dockerAssetJobSettings?.permissions, + packages: github.JobPermission.READ, + }, }; } diff --git a/test/docker.test.ts b/test/docker.test.ts index 7b22e095..31e8dbee 100644 --- a/test/docker.test.ts +++ b/test/docker.test.ts @@ -134,6 +134,28 @@ describe('correct format for docker credentials:', () => { }); 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', + }); + }); + + test('ghcr registry', () => { + const github = createDockerGithubWorkflow(app, [DockerCredential.ghcr()]); + const file = fs.readFileSync(github.workflowPath, 'utf-8'); + const workflow = YAML.parse(file); + const steps = findStepByJobAndUses(workflow, 'Assets-DockerAsset1', 'docker/login-action@v2'); + + expect(steps[0]).toEqual({ + uses: 'docker/login-action@v2', + with: { + registry: 'ghcr.io', + username: '${{ github.actor }}', + password: '${{ secrets.GITHUB_TOKEN }}', + }, + }); const permissions = workflow.jobs['Assets-DockerAsset1'].permissions; expect(permissions).toEqual({