Compare commits

..

1 Commits

Author SHA1 Message Date
dependabot[bot]
0e1c841720 build(deps): bump js-yaml from 4.1.0 to 4.1.1
Bumps [js-yaml](https://github.com/nodeca/js-yaml) from 4.1.0 to 4.1.1.
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/compare/4.1.0...4.1.1)

---
updated-dependencies:
- dependency-name: js-yaml
  dependency-version: 4.1.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-13 05:02:26 +00:00
16 changed files with 121 additions and 345 deletions

View File

@@ -19,7 +19,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v5
-
name: Stop docker
run: |
@@ -43,7 +43,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v5
-
name: Login to GitHub Container Registry
uses: ./
@@ -60,7 +60,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v5
-
name: Login to GitHub Container Registry
uses: ./
@@ -85,7 +85,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v5
-
name: Login to ACR
uses: ./
@@ -105,7 +105,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v5
-
name: Login to Docker Hub
uses: ./
@@ -124,7 +124,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v5
-
name: Login to ECR
uses: ./
@@ -144,7 +144,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v5
-
name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v5
@@ -169,7 +169,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v5
-
name: Login to Public ECR
continue-on-error: ${{ matrix.os == 'windows-latest' }}
@@ -192,7 +192,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v5
-
name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v5
@@ -207,7 +207,7 @@ jobs:
with:
registry: public.ecr.aws
ghcr:
github-container:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
@@ -218,7 +218,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v5
-
name: Login to GitHub Container Registry
uses: ./
@@ -238,7 +238,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v5
-
name: Login to GitLab
uses: ./
@@ -258,7 +258,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v5
-
name: Login to Google Artifact Registry
uses: ./
@@ -278,7 +278,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v5
-
name: Login to Google Container Registry
uses: ./
@@ -292,7 +292,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v5
-
name: Login to registries
uses: ./
@@ -315,7 +315,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v5
-
name: Login to registries
uses: ./
@@ -336,7 +336,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v5
-
name: Login to registries
id: login
@@ -356,125 +356,3 @@ jobs:
echo "::error::Should have failed"
exit 1
fi
scope-dockerhub:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os:
- ubuntu-latest
- windows-latest
steps:
-
name: Checkout
uses: actions/checkout@v6
-
name: Login to Docker Hub
uses: ./
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
scope: '@push'
-
name: Print config.json files
shell: bash
run: |
shopt -s globstar nullglob
for file in ~/.docker/**/config.json; do
echo "## ${file}"
jq '(.auths[]?.auth) |= "REDACTED"' "$file"
echo ""
done
scope-dockerhub-repo:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os:
- ubuntu-latest
- windows-latest
steps:
-
name: Checkout
uses: actions/checkout@v6
-
name: Login to Docker Hub
uses: ./
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
scope: 'docker/buildx-bin@push'
-
name: Print config.json files
shell: bash
run: |
shopt -s globstar nullglob
for file in ~/.docker/**/config.json; do
echo "## ${file}"
jq '(.auths[]?.auth) |= "REDACTED"' "$file"
echo ""
done
scope-ghcr:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os:
- ubuntu-latest
- windows-latest
steps:
-
name: Checkout
uses: actions/checkout@v6
-
name: Login to GitHub Container Registry
uses: ./
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
scope: '@push'
-
name: Print config.json files
shell: bash
run: |
shopt -s globstar nullglob
for file in ~/.docker/**/config.json; do
echo "## ${file}"
jq '(.auths[]?.auth) |= "REDACTED"' "$file"
echo ""
done
scope-ghcr-repo:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os:
- ubuntu-latest
- windows-latest
steps:
-
name: Checkout
uses: actions/checkout@v6
-
name: Login to GitHub Container Registry
uses: ./
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
scope: 'docker/login-action@push'
-
name: Print config.json files
shell: bash
run: |
shopt -s globstar nullglob
for file in ~/.docker/**/config.json; do
echo "## ${file}"
jq '(.auths[]?.auth) |= "REDACTED"' "$file"
echo ""
done

View File

@@ -31,7 +31,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v5
-
name: Initialize CodeQL
uses: github/codeql-action/init@v4

View File

@@ -15,7 +15,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v5
-
name: Publish
uses: actions/publish-immutable-action@v0.0.4

View File

@@ -17,7 +17,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v5
-
name: Test
uses: docker/bake-action@v6

View File

@@ -19,7 +19,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v5
-
name: List targets
id: generate

View File

@@ -25,7 +25,6 @@ ___
* [Quay.io](#quayio)
* [DigitalOcean](#digitalocean-container-registry)
* [Authenticate to multiple registries](#authenticate-to-multiple-registries)
* [Set scopes for the authentication token](#set-scopes-for-the-authentication-token)
* [Customizing](#customizing)
* [inputs](#inputs)
* [Contributing](#contributing)
@@ -528,8 +527,8 @@ jobs:
```
You can also use the `registry-auth` input for raw authentication to
registries, defined as YAML objects. Each object have the same attributes as
current inputs (except `logout`):
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
@@ -558,60 +557,6 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }}
```
### Set scopes for the authentication token
The `scope` input allows limiting registry credentials to a specific repository
or namespace scope when building images with Buildx.
This is useful in GitHub Actions to avoid overriding the Docker Hub
authentication token embedded in GitHub-hosted runners, which is used for
pulling images without rate limits. By scoping credentials, you can
authenticate only where needed (typically for pushing), while keeping
unauthenticated pulls for base images.
When `scope` is set, credentials are written to the Buildx configuration
instead of the global Docker configuration. This means:
* Authentication applies only to the specified scope
* The default Docker Hub credentials remain available for pulls
* Credentials are used only by Buildx during the build
> [!IMPORTANT]
> Credentials written to the Buildx configuration are only accessible by Buildx.
> They are not available to `docker pull`, `docker push`, or any other Docker
> CLI commands outside Buildx.
> [!NOTE]
> This feature requires Buildx version 0.31.0 or later.
```yaml
name: ci
on:
push:
branches: main
jobs:
login:
runs-on: ubuntu-latest
steps:
-
name: Login to Docker Hub (scoped)
uses: docker/login-action@v3
with:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
scope: 'myorg/myimage@push'
-
name: Build and push
uses: docker/build-push-action@v6
with:
push: true
tags: myorg/myimage:latest
```
In this example, base images are pulled using the embedded GitHub-hosted runner
credentials, while authenticated access is used only to push `myorg/myimage`.
## Customizing
### inputs
@@ -623,13 +568,13 @@ The following inputs can be used as `step.with` keys:
| `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 |
| `scope` | String | | Scope for the authentication token |
| `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 cannot be used with other inputs except `logout`.
> The `registry-auth` input is mutually exclusive with `registry`, `username`,
> `password` and `ecr` inputs.
## Contributing

View File

@@ -50,7 +50,7 @@ test('logout calls exec', async () => {
const registry = 'https://ghcr.io';
await logout(registry, '');
await logout(registry);
expect(execSpy).toHaveBeenCalledTimes(1);
const callfunc = execSpy.mock.calls[0];

View File

@@ -19,9 +19,6 @@ inputs:
ecr:
description: 'Specifies whether the given registry is ECR (auto, true or false)'
required: false
scope:
description: 'Scope for the authentication token'
required: false
logout:
description: 'Log out from the Docker registry at the end of a job'
default: 'true'

24
dist/index.js generated vendored

File diff suppressed because one or more lines are too long

2
dist/index.js.map generated vendored

File diff suppressed because one or more lines are too long

View File

@@ -31,7 +31,7 @@
"@docker/actions-toolkit": "^0.63.0",
"http-proxy-agent": "^7.0.2",
"https-proxy-agent": "^7.0.6",
"js-yaml": "^4.1.0"
"js-yaml": "^4.1.1"
},
"devDependencies": {
"@types/js-yaml": "^4.0.9",

View File

@@ -1,82 +1,21 @@
import path from 'path';
import * as core from '@actions/core';
import * as yaml from 'js-yaml';
import {Buildx} from '@docker/actions-toolkit/lib/buildx/buildx';
export interface Inputs {
registry: string;
username: string;
password: string;
scope: string;
ecr: string;
logout: boolean;
registryAuth: string;
}
export interface Auth {
registry: string;
username: string;
password: string;
scope: string;
ecr: string;
configDir: string;
}
export function getInputs(): Inputs {
return {
registry: core.getInput('registry'),
username: core.getInput('username'),
password: core.getInput('password'),
scope: core.getInput('scope'),
ecr: core.getInput('ecr'),
logout: core.getBooleanInput('logout'),
registryAuth: core.getInput('registry-auth')
};
}
export function getAuthList(inputs: Inputs): Array<Auth> {
if (inputs.registryAuth && (inputs.registry || inputs.username || inputs.password || inputs.scope || inputs.ecr)) {
throw new Error('Cannot use registry-auth with other inputs');
}
let auths: Array<Auth> = [];
if (!inputs.registryAuth) {
auths.push({
registry: inputs.registry || 'docker.io',
username: inputs.username,
password: inputs.password,
scope: inputs.scope,
ecr: inputs.ecr || 'auto',
configDir: scopeToConfigDir(inputs.registry, inputs.scope)
});
} else {
auths = (yaml.load(inputs.registryAuth) as Array<Auth>).map(auth => {
core.setSecret(auth.password); // redacted in workflow logs
return {
registry: auth.registry || 'docker.io',
username: auth.username,
password: auth.password,
scope: auth.scope,
ecr: auth.ecr || 'auto',
configDir: scopeToConfigDir(auth.registry || 'docker.io', auth.scope)
};
});
}
if (auths.length == 0) {
throw new Error('No registry to login');
}
return auths;
}
export function scopeToConfigDir(registry: string, scope?: string): string {
if (!scope || scope === '') {
return '';
}
let configDir = path.join(Buildx.configDir, 'config', registry === 'docker.io' ? 'registry-1.docker.io' : registry);
if (scope.startsWith('@')) {
configDir += scope;
} else {
configDir = path.join(configDir, scope);
}
return configDir;
}

View File

@@ -1,31 +1,19 @@
import * as core from '@actions/core';
import * as aws from './aws';
import * as context from './context';
import * as core from '@actions/core';
import {Docker} from '@docker/actions-toolkit/lib/docker/docker';
export async function login(auth: context.Auth): Promise<void> {
if (/true/i.test(auth.ecr) || (auth.ecr == 'auto' && aws.isECR(auth.registry))) {
await loginECR(auth.registry, auth.username, auth.password, auth.scope);
export async function login(registry: string, username: string, password: string, ecr: string): Promise<void> {
if (/true/i.test(ecr) || (ecr == 'auto' && aws.isECR(registry))) {
await loginECR(registry, username, password);
} else {
await loginStandard(auth.registry, auth.username, auth.password, auth.scope);
await loginStandard(registry, username, password);
}
}
export async function logout(registry: string, configDir: string): Promise<void> {
let envs: {[key: string]: string} | undefined;
if (configDir !== '') {
envs = Object.assign({}, process.env, {
DOCKER_CONFIG: configDir
}) as {
[key: string]: string;
};
core.info(`Alternative config dir: ${configDir}`);
}
export async function logout(registry: string): Promise<void> {
await Docker.getExecOutput(['logout', registry], {
ignoreReturnCode: true,
env: envs
ignoreReturnCode: true
}).then(res => {
if (res.stderr.length > 0 && res.exitCode != 0) {
core.warning(res.stderr.trim());
@@ -33,7 +21,7 @@ export async function logout(registry: string, configDir: string): Promise<void>
});
}
export async function loginStandard(registry: string, username: string, password: string, scope?: string): Promise<void> {
export async function loginStandard(registry: string, username: string, password: string): Promise<void> {
if (!username && !password) {
throw new Error('Username and password required');
}
@@ -43,38 +31,38 @@ export async function loginStandard(registry: string, username: string, password
if (!password) {
throw new Error('Password required');
}
await loginExec(registry, username, password, scope);
}
export async function loginECR(registry: string, username: string, password: string, scope?: string): Promise<void> {
core.info(`Retrieving registries data through AWS SDK...`);
const regDatas = await aws.getRegistriesData(registry, username, password);
for (const regData of regDatas) {
await loginExec(regData.registry, regData.username, regData.password, scope);
}
}
const loginArgs: Array<string> = ['login', '--password-stdin'];
loginArgs.push('--username', username);
loginArgs.push(registry);
async function loginExec(registry: string, username: string, password: string, scope?: string): Promise<void> {
let envs: {[key: string]: string} | undefined;
if (scope && scope !== '') {
envs = Object.assign({}, process.env, {
DOCKER_CONFIG: context.scopeToConfigDir(registry, scope)
}) as {
[key: string]: string;
};
core.info(`Logging into ${registry} (scope ${scope})...`);
} else {
core.info(`Logging into ${registry}...`);
}
await Docker.getExecOutput(['login', '--password-stdin', '--username', username, registry], {
core.info(`Logging into ${registry}...`);
await Docker.getExecOutput(loginArgs, {
ignoreReturnCode: true,
silent: true,
input: Buffer.from(password),
env: envs
input: Buffer.from(password)
}).then(res => {
if (res.stderr.length > 0 && res.exitCode != 0) {
throw new Error(res.stderr.trim());
}
core.info('Login Succeeded!');
core.info(`Login Succeeded!`);
});
}
export async function loginECR(registry: string, username: string, password: string): Promise<void> {
core.info(`Retrieving registries data through AWS SDK...`);
const regDatas = await aws.getRegistriesData(registry, username, password);
for (const regData of regDatas) {
core.info(`Logging into ${regData.registry}...`);
await Docker.getExecOutput(['login', '--password-stdin', '--username', regData.username, regData.registry], {
ignoreReturnCode: true,
silent: true,
input: Buffer.from(regData.password)
}).then(res => {
if (res.stderr.length > 0 && res.exitCode != 0) {
throw new Error(res.stderr.trim());
}
core.info('Login Succeeded!');
});
}
}

View File

@@ -1,3 +1,4 @@
import * as yaml from 'js-yaml';
import * as core from '@actions/core';
import * as actionsToolkit from '@docker/actions-toolkit';
@@ -5,21 +6,45 @@ 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<void> {
const inputs: context.Inputs = context.getInputs();
stateHelper.setLogout(inputs.logout);
const auths = context.getAuthList(inputs);
stateHelper.setRegistries(Array.from(new Map(auths.map(auth => [`${auth.registry}|${auth.configDir}`, {registry: auth.registry, configDir: auth.configDir} as stateHelper.RegistryState])).values()));
if (inputs.registryAuth && (inputs.registry || inputs.username || inputs.password || inputs.ecr)) {
throw new Error('Cannot use registry-auth with other inputs');
}
if (auths.length === 1) {
await docker.login(auths[0]);
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) {
await core.group(`Login to ${auth.registry}`, async () => {
await docker.login(auth);
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');
});
}
}
@@ -28,10 +53,8 @@ async function post(): Promise<void> {
if (!stateHelper.logout) {
return;
}
for (const registryState of stateHelper.registries) {
await core.group(`Logout from ${registryState.registry}`, async () => {
await docker.logout(registryState.registry, registryState.configDir);
});
for (const registry of stateHelper.registries.split(',')) {
await docker.logout(registry);
}
}

View File

@@ -1,15 +1,10 @@
import * as core from '@actions/core';
export const registries = process.env['STATE_registries'] ? (JSON.parse(process.env['STATE_registries']) as Array<RegistryState>) : [];
export const registries = process.env['STATE_registries'] || '';
export const logout = /true/i.test(process.env['STATE_logout'] || '');
export interface RegistryState {
registry: string;
configDir: string;
}
export function setRegistries(registries: Array<RegistryState>) {
core.saveState('registries', JSON.stringify(registries));
export function setRegistries(registries: string[]) {
core.saveState('registries', registries.join(','));
}
export function setLogout(logout: boolean) {

View File

@@ -4455,7 +4455,7 @@ __metadata:
http-proxy-agent: "npm:^7.0.2"
https-proxy-agent: "npm:^7.0.6"
jest: "npm:^29.7.0"
js-yaml: "npm:^4.1.0"
js-yaml: "npm:^4.1.1"
prettier: "npm:^3.6.2"
ts-jest: "npm:^29.4.1"
ts-node: "npm:^10.9.2"
@@ -6224,6 +6224,17 @@ __metadata:
languageName: node
linkType: hard
"js-yaml@npm:^4.1.1":
version: 4.1.1
resolution: "js-yaml@npm:4.1.1"
dependencies:
argparse: "npm:^2.0.1"
bin:
js-yaml: bin/js-yaml.js
checksum: 10/a52d0519f0f4ef5b4adc1cde466cb54c50d56e2b4a983b9d5c9c0f2f99462047007a6274d7e95617a21d3c91fde3ee6115536ed70991cd645ba8521058b78f77
languageName: node
linkType: hard
"jsbn@npm:1.1.0":
version: 1.1.0
resolution: "jsbn@npm:1.1.0"