Feature: Enable Multi Registry login Authentication

Signed-off-by: unknown <arunkumarthiyagarajan95@gmail.com>
This commit is contained in:
unknown 2025-08-04 11:04:11 +05:30
parent ef38ec311a
commit 8d9860ef0e
6 changed files with 5287 additions and 8077 deletions

View File

@ -24,6 +24,7 @@ ___
* [OCI Oracle Cloud Infrastructure Registry (OCIR)](#oci-oracle-cloud-infrastructure-registry-ocir) * [OCI Oracle Cloud Infrastructure Registry (OCIR)](#oci-oracle-cloud-infrastructure-registry-ocir)
* [Quay.io](#quayio) * [Quay.io](#quayio)
* [DigitalOcean](#digitalocean-container-registry) * [DigitalOcean](#digitalocean-container-registry)
* [Multiple Registries](#multiple-registries)
* [Customizing](#customizing) * [Customizing](#customizing)
* [inputs](#inputs) * [inputs](#inputs)
* [Contributing](#contributing) * [Contributing](#contributing)
@ -494,6 +495,34 @@ jobs:
password: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }} password: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }}
``` ```
### Multiple Registries
You can login to multiple registries by providing a multiline string for the registry input. Each registry will be processed sequentically using the same username and password.
```yaml
name: ci
on:
push:
branches: main
jobs:
login:
runs-on: ubuntu-latest
steps:
-
name: Login to Multiple Regsitries
uses: docker/login-action@v7
with:
registry: |
ghcr.io
docker.io
registry.gitlab.com
# for single registry,
# registry: "ghcr.io"
username: ${{ vars.REGISTRY_USERNAME }}
password: ${{ secrets.REGISTRY_PASSWORD }}
```
## Customizing ## Customizing
### inputs ### inputs
@ -502,7 +531,7 @@ The following inputs can be used as `step.with` keys:
| Name | Type | Default | Description | | Name | Type | Default | Description |
|------------|--------|---------|-------------------------------------------------------------------------------| |------------|--------|---------|-------------------------------------------------------------------------------|
| `registry` | String | | Server address of Docker registry. If not set then will default to Docker Hub | | `registry` | String | | Server address of Docker registry. If not set then will default to Docker Hub. Supports multiline string for multiplr registries. |
| `username` | String | | Username for authenticating to the Docker registry | | `username` | String | | Username for authenticating to the Docker registry |
| `password` | String | | Password or personal access token for authenticating 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`) | | `ecr` | String | `auto` | Specifies whether the given registry is ECR (`auto`, `true` or `false`) |

View File

@ -20,17 +20,34 @@ test('loginStandard calls exec', async () => {
const username = 'dbowie'; const username = 'dbowie';
const password = 'groundcontrol'; const password = 'groundcontrol';
const registry = 'https://ghcr.io'; // Define const registry as multiline input
const registry = `https://ghcr.io
https://docker.io`;
await loginStandard(registry, username, password); const registryArray = registry.split('\n').map(r => r.trim());
expect(execSpy).toHaveBeenCalledTimes(1); for (const reg of registryArray) {
const callfunc = execSpy.mock.calls[0]; await loginStandard(reg, username, password);
if (callfunc && callfunc[1]) {
// we don't want to check env opt
callfunc[1].env = undefined;
} }
expect(execSpy).toHaveBeenCalledWith(['login', '--password-stdin', '--username', username, registry], {
expect(execSpy).toHaveBeenCalledTimes(2);
const firstcall = execSpy.mock.calls[0];
if (firstcall && firstcall[1]) {
// we don't want to check env opt
firstcall[1].env = undefined;
}
expect(execSpy).toHaveBeenCalledWith(['login', '--password-stdin', '--username', username, registryArray[0]], {
input: Buffer.from(password),
silent: true,
ignoreReturnCode: true
});
const secondcall = execSpy.mock.calls[1];
if (secondcall && secondcall[1]) {
// we don't want to check env opt
secondcall[1].env = undefined;
}
expect(execSpy).toHaveBeenCalledWith(['login', '--password-stdin', '--username', username, registryArray[1]], {
input: Buffer.from(password), input: Buffer.from(password),
silent: true, silent: true,
ignoreReturnCode: true ignoreReturnCode: true
@ -48,17 +65,32 @@ test('logout calls exec', async () => {
}; };
}); });
const registry = 'https://ghcr.io'; const registry = `https://ghcr.io
https://docker.io`;
await logout(registry); const registryArray = registry.split('\n').map(r => r.trim());
expect(execSpy).toHaveBeenCalledTimes(1); for (const reg of registryArray) {
const callfunc = execSpy.mock.calls[0]; await logout(reg);
if (callfunc && callfunc[1]) {
// we don't want to check env opt
callfunc[1].env = undefined;
} }
expect(execSpy).toHaveBeenCalledWith(['logout', registry], {
expect(execSpy).toHaveBeenCalledTimes(2);
const firstcall = execSpy.mock.calls[0];
if (firstcall && firstcall[1]) {
// we don't want to check env opt
firstcall[1].env = undefined;
}
expect(execSpy).toHaveBeenCalledWith(['logout', registryArray[0]], {
ignoreReturnCode: true
});
const secondcall = execSpy.mock.calls[1];
if (secondcall && secondcall[1]) {
// we don't want to check env opt
secondcall[1].env = undefined;
}
expect(execSpy).toHaveBeenCalledWith(['logout', registryArray[1]], {
ignoreReturnCode: true ignoreReturnCode: true
}); });
}); });

View File

@ -1,7 +1,7 @@
import * as core from '@actions/core'; import * as core from '@actions/core';
export interface Inputs { export interface Inputs {
registry: string; registry: string[];
username: string; username: string;
password: string; password: string;
ecr: string; ecr: string;
@ -10,7 +10,7 @@ export interface Inputs {
export function getInputs(): Inputs { export function getInputs(): Inputs {
return { return {
registry: core.getInput('registry'), registry: core.getMultilineInput('registry'),
username: core.getInput('username'), username: core.getInput('username'),
password: core.getInput('password'), password: core.getInput('password'),
ecr: core.getInput('ecr'), ecr: core.getInput('ecr'),

View File

@ -4,18 +4,23 @@ import * as context from './context';
import * as docker from './docker'; import * as docker from './docker';
import * as stateHelper from './state-helper'; import * as stateHelper from './state-helper';
const input: context.Inputs = context.getInputs();
export async function main(): Promise<void> { export async function main(): Promise<void> {
const input: context.Inputs = context.getInputs();
stateHelper.setRegistry(input.registry); stateHelper.setRegistry(input.registry);
stateHelper.setLogout(input.logout); stateHelper.setLogout(input.logout);
await docker.login(input.registry, input.username, input.password, input.ecr); for (const reg of input.registry) {
await docker.login(reg, input.username, input.password, input.ecr);
}
} }
async function post(): Promise<void> { async function post(): Promise<void> {
if (!stateHelper.logout) { if (!stateHelper.logout) {
return; return;
} }
await docker.logout(stateHelper.registry); for (const reg of input.registry) {
await docker.logout(reg);
}
} }
actionsToolkit.run(main, post); actionsToolkit.run(main, post);

View File

@ -3,8 +3,9 @@ import * as core from '@actions/core';
export const registry = process.env['STATE_registry'] || ''; export const registry = process.env['STATE_registry'] || '';
export const logout = /true/i.test(process.env['STATE_logout'] || ''); export const logout = /true/i.test(process.env['STATE_logout'] || '');
export function setRegistry(registry: string) { export function setRegistry(registry: string[]) {
core.saveState('registry', registry); core.info(`Setting registry: ${registry.join(',')}`);
core.saveState('registry', registry.join(','));
} }
export function setLogout(logout: boolean) { export function setLogout(logout: boolean) {

13249
yarn.lock

File diff suppressed because it is too large Load Diff