mirror of
https://github.com/docker/login-action.git
synced 2026-10-23 16:57:17 +00:00
use registry hosts for scoped credentials
Parse registry URLs so scoped ECR credentials use the same config directory for login, logout, and Buildx lookup. Preserve explicit default ports. Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
This commit is contained in:
@@ -3,7 +3,7 @@ import * as path from 'path';
|
|||||||
|
|
||||||
import {Buildx} from '@docker/actions-toolkit/lib/buildx/buildx.js';
|
import {Buildx} from '@docker/actions-toolkit/lib/buildx/buildx.js';
|
||||||
|
|
||||||
import {getAuthList, getInputs} from '../src/context.js';
|
import {getAuthList, getInputs, scopeToConfigDir} from '../src/context.js';
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
vi.restoreAllMocks();
|
vi.restoreAllMocks();
|
||||||
@@ -188,3 +188,23 @@ test.each([
|
|||||||
});
|
});
|
||||||
expect(auth.ecr).toBe(expected);
|
expect(auth.ecr).toBe(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// prettier-ignore
|
||||||
|
test.each([
|
||||||
|
['ghcr.io', 'ghcr.io'],
|
||||||
|
['ghcr.io/', 'ghcr.io'],
|
||||||
|
['https://ghcr.io', 'ghcr.io'],
|
||||||
|
['https://ghcr.io/', 'ghcr.io'],
|
||||||
|
['https://ghcr.io/v2/', 'ghcr.io'],
|
||||||
|
['http://localhost:5000', 'localhost:5000'],
|
||||||
|
['http://localhost:80', 'localhost:80'],
|
||||||
|
['https://ghcr.io:443', 'ghcr.io:443'],
|
||||||
|
['https://012345678910.dkr.ecr.eu-west-3.amazonaws.com', '012345678910.dkr.ecr.eu-west-3.amazonaws.com'],
|
||||||
|
['https://docker.io', 'registry-1.docker.io']
|
||||||
|
])('scopeToConfigDir uses the registry host for %s', (registry, host) => {
|
||||||
|
expect(scopeToConfigDir(registry, 'myorg/myimage@push')).toBe(path.join(Buildx.configDir, 'config', host, 'myorg', 'myimage') + '@push');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('scopeToConfigDir rejects a URL whose host escapes the config directory', () => {
|
||||||
|
expect(() => scopeToConfigDir('https://../', '@push')).toThrow(/Invalid registry/);
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
import {afterEach, expect, test, vi} from 'vitest';
|
import {afterEach, expect, test, vi} from 'vitest';
|
||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
|
import {Buildx} from '@docker/actions-toolkit/lib/buildx/buildx.js';
|
||||||
import {Docker} from '@docker/actions-toolkit/lib/docker/docker.js';
|
import {Docker} from '@docker/actions-toolkit/lib/docker/docker.js';
|
||||||
|
|
||||||
import {login, loginStandard, logout} from '../src/docker.js';
|
import * as aws from '../src/aws.js';
|
||||||
|
import {getAuthList} from '../src/context.js';
|
||||||
|
import {login, loginECR, loginStandard, logout} from '../src/docker.js';
|
||||||
import * as dockerhub from '../src/dockerhub.js';
|
import * as dockerhub from '../src/dockerhub.js';
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -92,3 +96,29 @@ test('logout calls exec', async () => {
|
|||||||
ignoreReturnCode: true
|
ignoreReturnCode: true
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('loginECR and logout use the config dir of the registry host', async () => {
|
||||||
|
const execSpy = vi.spyOn(Docker, 'getExecOutput').mockResolvedValue({
|
||||||
|
exitCode: 0,
|
||||||
|
stdout: '',
|
||||||
|
stderr: ''
|
||||||
|
});
|
||||||
|
const registry = '012345678910.dkr.ecr.eu-west-3.amazonaws.com';
|
||||||
|
vi.spyOn(aws, 'getRegistriesData').mockResolvedValue([
|
||||||
|
{
|
||||||
|
registry: `https://${registry}`,
|
||||||
|
username: 'AWS',
|
||||||
|
password: 'world'
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
const scope = 'myapp@push';
|
||||||
|
const [auth] = getAuthList({registry, username: 'CAFEBABE', password: 'DEADBEEF', scope, ecr: 'auto', logout: true, registryAuth: ''});
|
||||||
|
await loginECR(auth.registry, auth.username, auth.password, auth.scope);
|
||||||
|
await logout(auth.registry, auth.configDir);
|
||||||
|
|
||||||
|
const expectedConfigDir = path.join(Buildx.configDir, 'config', registry, 'myapp@push');
|
||||||
|
expect(execSpy).toHaveBeenCalledTimes(2);
|
||||||
|
expect(execSpy.mock.calls[0][1]?.env?.DOCKER_CONFIG).toBe(expectedConfigDir);
|
||||||
|
expect(execSpy.mock.calls[1][1]?.env?.DOCKER_CONFIG).toBe(expectedConfigDir);
|
||||||
|
});
|
||||||
|
|||||||
+4
-1
@@ -77,8 +77,11 @@ export function scopeToConfigDir(registry: string, scope?: string): string {
|
|||||||
if (scopeDisabled() || !scope || scope === '') {
|
if (scopeDisabled() || !scope || scope === '') {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
const schemeEnd = registry.indexOf('://');
|
||||||
|
// A neutral scheme preserves explicit ports, including HTTP(S) default ports.
|
||||||
|
const registryHost = schemeEnd === -1 ? registry : new URL(`registry://${registry.slice(schemeEnd + 3)}`).host;
|
||||||
const configRoot = path.resolve(Buildx.configDir, 'config');
|
const configRoot = path.resolve(Buildx.configDir, 'config');
|
||||||
const registryDir = path.resolve(configRoot, registry === 'docker.io' ? 'registry-1.docker.io' : registry);
|
const registryDir = path.resolve(configRoot, registryHost === 'docker.io' ? 'registry-1.docker.io' : registryHost);
|
||||||
if (!isChildPath(configRoot, registryDir)) {
|
if (!isChildPath(configRoot, registryDir)) {
|
||||||
throw new Error(`Invalid registry '${registry}': resolved config path escapes the Buildx config directory`);
|
throw new Error(`Invalid registry '${registry}': resolved config path escapes the Buildx config directory`);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user