From 9fc23e88e3c8f8e15c9fd446949a027bb64f884b Mon Sep 17 00:00:00 2001 From: CrazyMax <1951866+crazy-max@users.noreply.github.com> Date: Fri, 5 Sep 2025 18:43:09 +0200 Subject: [PATCH] raw authentication to registries Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com> --- .github/workflows/ci.yml | 70 ++++++++++++++++++++++++++++++++++ README.md | 82 ++++++++++++++++++++++++++++++++++++---- action.yml | 4 +- package.json | 4 +- src/context.ts | 4 +- src/docker.ts | 6 +-- src/main.ts | 50 +++++++++++++++++++++--- src/state-helper.ts | 6 +-- yarn.lock | 9 +++++ 9 files changed, 212 insertions(+), 23 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 51e73ee..84299a5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -286,3 +286,73 @@ jobs: registry: gcr.io username: _json_key password: ${{ secrets.GCR_JSON_KEY }} + + registry-auth: + runs-on: ubuntu-latest + steps: + - + name: Checkout + uses: actions/checkout@v5 + - + name: Login to registries + uses: ./ + with: + registry-auth: | + - username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - registry: public.ecr.aws + username: ${{ secrets.AWS_ACCESS_KEY_ID }} + password: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + - registry: registry.gitlab.com + username: ${{ secrets.GITLAB_USERNAME }} + password: ${{ secrets.GITLAB_TOKEN }} + + registry-auth-dup: + runs-on: ubuntu-latest + steps: + - + name: Checkout + uses: actions/checkout@v5 + - + name: Login to registries + uses: ./ + with: + registry-auth: | + - registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - registry: public.ecr.aws + username: ${{ secrets.AWS_ACCESS_KEY_ID }} + password: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + - registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + registry-auth-exclusive: + runs-on: ubuntu-latest + steps: + - + name: Checkout + uses: actions/checkout@v5 + - + name: Login to registries + id: login + continue-on-error: true + uses: ./ + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + registry-auth: | + - username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - + name: Check + run: | + if [ "${{ steps.login.outcome }}" != "failure" ] || [ "${{ steps.login.conclusion }}" != "success" ]; then + echo "::error::Should have failed" + exit 1 + fi diff --git a/README.md b/README.md index c00061b..b57be30 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ ___ * [OCI Oracle Cloud Infrastructure Registry (OCIR)](#oci-oracle-cloud-infrastructure-registry-ocir) * [Quay.io](#quayio) * [DigitalOcean](#digitalocean-container-registry) + * [Authenticate to multiple registries](#authenticate-to-multiple-registries) * [Customizing](#customizing) * [inputs](#inputs) * [Contributing](#contributing) @@ -494,19 +495,86 @@ jobs: password: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }} ``` +### Authenticate to multiple registries + +To authenticate against multiple registries, you can specify the login-action +step multiple times in your workflow: + +```yaml +name: ci + +on: + push: + branches: main + +jobs: + login: + runs-on: ubuntu-latest + steps: + - + name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ vars.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - + name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} +``` + +You can also use the `registry-auth` input for raw authentication to +registries, defined as YAML objects. Each object can contain `registry`, +`username`, `password` and `ecr` keys similar to current inputs: + +> [!WARNING] +> We don't recommend using this method, it's better to use the action multiple +> times as shown above. + +```yaml +name: ci + +on: + push: + branches: main + +jobs: + login: + runs-on: ubuntu-latest + steps: + - + name: Login to registries + uses: docker/login-action@v3 + with: + registry-auth: | + - username: ${{ vars.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} +``` + ## Customizing ### inputs The following inputs can be used as `step.with` keys: -| Name | Type | Default | Description | -|------------|--------|---------|-------------------------------------------------------------------------------| -| `registry` | String | | Server address of Docker registry. If not set then will default to Docker Hub | -| `username` | String | | Username for authenticating to the Docker registry | -| `password` | String | | Password or personal access token for authenticating the Docker registry | -| `ecr` | String | `auto` | Specifies whether the given registry is ECR (`auto`, `true` or `false`) | -| `logout` | Bool | `true` | Log out from the Docker registry at the end of a job | +| Name | Type | Default | Description | +|-----------------|--------|-------------|-------------------------------------------------------------------------------| +| `registry` | String | `docker.io` | Server address of Docker registry. If not set then will default to Docker Hub | +| `username` | String | | Username for authenticating to the Docker registry | +| `password` | String | | Password or personal access token for authenticating the Docker registry | +| `ecr` | String | `auto` | Specifies whether the given registry is ECR (`auto`, `true` or `false`) | +| `logout` | Bool | `true` | Log out from the Docker registry at the end of a job | +| `registry-auth` | YAML | | Raw authentication to registries, defined as YAML objects | + +> [!NOTE] +> The `registry-auth` input is mutually exclusive with `registry`, `username`, +> `password` and `ecr` inputs. ## Contributing diff --git a/action.yml b/action.yml index 3a0856d..ebd3e4b 100644 --- a/action.yml +++ b/action.yml @@ -18,12 +18,14 @@ inputs: required: false ecr: description: 'Specifies whether the given registry is ECR (auto, true or false)' - default: 'auto' required: false logout: description: 'Log out from the Docker registry at the end of a job' default: 'true' required: false + registry-auth: + description: 'Raw authentication to registries, defined as YAML objects' + required: false runs: using: 'node20' diff --git a/package.json b/package.json index 5f16a77..ff20db4 100644 --- a/package.json +++ b/package.json @@ -30,9 +30,11 @@ "@aws-sdk/client-ecr-public": "^3.859.0", "@docker/actions-toolkit": "^0.62.1", "http-proxy-agent": "^7.0.2", - "https-proxy-agent": "^7.0.6" + "https-proxy-agent": "^7.0.6", + "js-yaml": "^4.1.0" }, "devDependencies": { + "@types/js-yaml": "^4.0.9", "@types/node": "^20.12.12", "@typescript-eslint/eslint-plugin": "^7.9.0", "@typescript-eslint/parser": "^7.9.0", diff --git a/src/context.ts b/src/context.ts index 8a38168..c030622 100644 --- a/src/context.ts +++ b/src/context.ts @@ -6,6 +6,7 @@ export interface Inputs { password: string; ecr: string; logout: boolean; + registryAuth: string; } export function getInputs(): Inputs { @@ -14,6 +15,7 @@ export function getInputs(): Inputs { username: core.getInput('username'), password: core.getInput('password'), ecr: core.getInput('ecr'), - logout: core.getBooleanInput('logout') + logout: core.getBooleanInput('logout'), + registryAuth: core.getInput('registry-auth') }; } diff --git a/src/docker.ts b/src/docker.ts index 5e2c1d5..681b2b5 100644 --- a/src/docker.ts +++ b/src/docker.ts @@ -36,11 +36,7 @@ export async function loginStandard(registry: string, username: string, password loginArgs.push('--username', username); loginArgs.push(registry); - if (registry) { - core.info(`Logging into ${registry}...`); - } else { - core.info(`Logging into Docker Hub...`); - } + core.info(`Logging into ${registry}...`); await Docker.getExecOutput(loginArgs, { ignoreReturnCode: true, silent: true, diff --git a/src/main.ts b/src/main.ts index f35fa21..0e2d000 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,21 +1,61 @@ +import * as yaml from 'js-yaml'; +import * as core from '@actions/core'; import * as actionsToolkit from '@docker/actions-toolkit'; import * as context from './context'; import * as docker from './docker'; import * as stateHelper from './state-helper'; +interface Auth { + registry: string; + username: string; + password: string; + ecr: string; +} + export async function main(): Promise { - const input: context.Inputs = context.getInputs(); - stateHelper.setRegistry(input.registry); - stateHelper.setLogout(input.logout); - await docker.login(input.registry, input.username, input.password, input.ecr); + const inputs: context.Inputs = context.getInputs(); + stateHelper.setLogout(inputs.logout); + + if (inputs.registryAuth && (inputs.registry || inputs.username || inputs.password || inputs.ecr)) { + throw new Error('Cannot use registry-auth with other inputs'); + } + + if (!inputs.registryAuth) { + stateHelper.setRegistries([inputs.registry || 'docker.io']); + await docker.login(inputs.registry || 'docker.io', inputs.username, inputs.password, inputs.ecr || 'auto'); + return; + } + + const auths = yaml.load(inputs.registryAuth) as Auth[]; + if (auths.length == 0) { + throw new Error('No registry to login'); + } + + const registries: string[] = []; + for (const auth of auths) { + if (!auth.registry) { + registries.push('docker.io'); + } else { + registries.push(auth.registry); + } + } + stateHelper.setRegistries(registries.filter((value, index, self) => self.indexOf(value) === index)); + + for (const auth of auths) { + await core.group(`Login to ${auth.registry || 'docker.io'}`, async () => { + await docker.login(auth.registry || 'docker.io', auth.username, auth.password, auth.ecr || 'auto'); + }); + } } async function post(): Promise { if (!stateHelper.logout) { return; } - await docker.logout(stateHelper.registry); + for (const registry of stateHelper.registries.split(',')) { + await docker.logout(registry); + } } actionsToolkit.run(main, post); diff --git a/src/state-helper.ts b/src/state-helper.ts index 3732717..2120881 100644 --- a/src/state-helper.ts +++ b/src/state-helper.ts @@ -1,10 +1,10 @@ import * as core from '@actions/core'; -export const registry = process.env['STATE_registry'] || ''; +export const registries = process.env['STATE_registries'] || ''; export const logout = /true/i.test(process.env['STATE_logout'] || ''); -export function setRegistry(registry: string) { - core.saveState('registry', registry); +export function setRegistries(registries: string[]) { + core.saveState('registries', registries.join(',')); } export function setLogout(logout: boolean) { diff --git a/yarn.lock b/yarn.lock index e20b677..a8698a0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3236,6 +3236,13 @@ __metadata: languageName: node linkType: hard +"@types/js-yaml@npm:^4.0.9": + version: 4.0.9 + resolution: "@types/js-yaml@npm:4.0.9" + checksum: e5e5e49b5789a29fdb1f7d204f82de11cb9e8f6cb24ab064c616da5d6e1b3ccfbf95aa5d1498a9fbd3b9e745564e69b4a20b6c530b5a8bbb2d4eb830cda9bc69 + languageName: node + linkType: hard + "@types/node-fetch@npm:^2.5.0": version: 2.6.4 resolution: "@types/node-fetch@npm:2.6.4" @@ -4315,6 +4322,7 @@ __metadata: "@aws-sdk/client-ecr": ^3.859.0 "@aws-sdk/client-ecr-public": ^3.859.0 "@docker/actions-toolkit": ^0.62.1 + "@types/js-yaml": ^4.0.9 "@types/node": ^20.12.12 "@typescript-eslint/eslint-plugin": ^7.9.0 "@typescript-eslint/parser": ^7.9.0 @@ -4326,6 +4334,7 @@ __metadata: http-proxy-agent: ^7.0.2 https-proxy-agent: ^7.0.6 jest: ^29.7.0 + js-yaml: ^4.1.0 prettier: ^3.2.5 ts-jest: ^29.1.2 ts-node: ^10.9.2