Skip to content

Commit be5bc43

Browse files
authored
refactor registryCompatibilityOCI to iterate over several registries
1 parent efcd6a2 commit be5bc43

3 files changed

Lines changed: 62 additions & 41 deletions

File tree

src/spec-configuration/httpOCIRegistry.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,8 @@ export async function requestEnsureAuthenticated(params: CommonParams, httpOptio
136136
// This credential is used to offer the registry in exchange for a Bearer token.
137137
// These may be:
138138
// - parsed out of a special DEVCONTAINERS_OCI_AUTH environment variable
139-
// - Read from a docker config file
140139
// - Read from a docker credential helper (https://docs.docker.com/engine/reference/commandline/login/#credentials-store)
140+
// - Read from a docker config file
141141
// - Crafted from the GITHUB_TOKEN environment variable
142142
// Returns:
143143
// - undefined: No credential was found.
@@ -219,13 +219,15 @@ async function getCredentialFromDockerConfigOrCredentialHelper(params: CommonPar
219219
const dockerConfig: DockerConfigFile = jsonc.parse((await readLocalFile(dockerConfigFilePath)).toString());
220220

221221
configContainsAuth = Object.keys(dockerConfig.credHelpers || {}).length > 0 || !!dockerConfig.credsStore || Object.keys(dockerConfig.auths || {}).length > 0;
222+
// https://docs.docker.com/engine/reference/commandline/login/#credential-helpers
222223
if (dockerConfig.credHelpers && dockerConfig.credHelpers[registry]) {
223224
const credHelper = dockerConfig.credHelpers[registry];
224225
output.write(`[httpOci] Found credential helper '${credHelper}' in '${dockerConfigFilePath}' registry '${registry}'`, LogLevel.Trace);
225226
const auth = await getCredentialFromHelper(params, registry, credHelper);
226227
if (auth) {
227228
return auth;
228229
}
230+
// https://docs.docker.com/engine/reference/commandline/login/#credentials-store
229231
} else if (dockerConfig.credsStore) {
230232
output.write(`[httpOci] Invoking credsStore credential helper '${dockerConfig.credsStore}'`, LogLevel.Trace);
231233
const auth = await getCredentialFromHelper(params, registry, dockerConfig.credsStore);
@@ -280,7 +282,7 @@ async function getCredentialFromDockerConfigOrCredentialHelper(params: CommonPar
280282
}
281283
}
282284

283-
// No auth found.
285+
// No auth found from docker config or credential helper.
284286
return;
285287
}
286288

src/test/container-features/configs/azure-container-registry/.devcontainer.json

Lines changed: 0 additions & 8 deletions
This file was deleted.

src/test/container-features/registryCompatibilityOCI.test.ts

Lines changed: 58 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,34 @@ import { devContainerDown, devContainerUp, shellExec } from '../testUtils';
99

1010
const pkg = require('../../../package.json');
1111

12-
describe('Registry Compatibility', function () {
13-
this.timeout('120s');
12+
interface TestPlan {
13+
name: string;
14+
configName: string;
15+
testFeatureId: string;
16+
testCommand?: string;
17+
testCommandResult?: RegExp;
18+
}
19+
20+
const defaultTestPlan = {
21+
testCommand: 'color',
22+
testCommandResult: /my favorite color is pink/,
23+
};
1424

25+
const registryCompatibilityTestPlan: TestPlan[] = [
26+
{
27+
name: 'Anonymous access of Azure Container Registry',
28+
configName: 'azure-anonymous',
29+
testFeatureId: 'devcontainercli.azurecr.io/features/color',
30+
},
31+
{
32+
name: 'Anonymous access of GHCR',
33+
configName: 'github-anonymous',
34+
testFeatureId: 'ghcr.io/devcontainers/feature-starter/color',
35+
}
36+
];
37+
38+
describe('Registry Compatibility', function () {
39+
this.timeout('1200s');
1540
const tmp = path.relative(process.cwd(), path.join(__dirname, 'tmp'));
1641
const cli = `npx --prefix ${tmp} devcontainer`;
1742

@@ -21,41 +46,43 @@ describe('Registry Compatibility', function () {
2146
await shellExec(`npm --prefix ${tmp} install devcontainers-cli-${pkg.version}.tgz`);
2247
});
2348

24-
// TODO: Matrix this test against all tested registries
25-
describe('Azure Container Registry', () => {
49+
registryCompatibilityTestPlan.forEach(({ name, configName, testFeatureId, testCommand, testCommandResult }) => {
50+
this.timeout('120s');
51+
describe(name, () => {
52+
describe('devcontainer up', () => {
53+
let containerId: string | null = null;
54+
const testFolder = `${__dirname}/configs/registry-compatibility/${configName}`;
2655

27-
describe(`'devcontainer up' with a Feature anonymously pulled from ACR`, () => {
28-
let containerId: string | null = null;
29-
const testFolder = `${__dirname}/configs/azure-container-registry`;
56+
before(async () => containerId = (await devContainerUp(cli, testFolder, { 'logLevel': 'trace' })).containerId);
57+
after(async () => await devContainerDown({ containerId }));
3058

31-
before(async () => containerId = (await devContainerUp(cli, testFolder, { 'logLevel': 'trace' })).containerId);
32-
after(async () => await devContainerDown({ containerId }));
59+
const cmd = testCommand ?? defaultTestPlan.testCommand;
60+
const expected = testCommandResult ?? defaultTestPlan.testCommandResult;
3361

34-
it('should exec the color command', async () => {
35-
const res = await shellExec(`${cli} exec --workspace-folder ${testFolder} color`);
36-
assert.strictEqual(res.error, null);
37-
assert.match(res.stdout, /my favorite color is pink/);
62+
it(`should exec the ${cmd} command`, async () => {
63+
const res = await shellExec(`${cli} exec --workspace-folder ${testFolder} ${cmd} `);
64+
assert.strictEqual(res.error, null);
65+
assert.match(res.stdout, expected);
66+
});
3867
});
39-
});
40-
41-
describe(`devcontainer features info manifest`, async () => {
42-
43-
it('fetches manifest anonymously from ACR', async () => {
4468

45-
let infoManifestResult: { stdout: string; stderr: string } | null = null;
46-
let success = false;
47-
try {
48-
infoManifestResult = await shellExec(`${cli} features info manifest devcontainercli.azurecr.io/features/hello --log-level trace`);
49-
success = true;
50-
} catch (error) {
51-
assert.fail('features info tags sub-command should not throw');
52-
}
69+
describe(`devcontainer features info manifest`, async () => {
70+
it('fetches manifest', async () => {
71+
let infoManifestResult: { stdout: string; stderr: string } | null = null;
72+
let success = false;
73+
try {
74+
infoManifestResult = await shellExec(`${cli} features info manifest ${testFeatureId} --log-level trace`);
75+
success = true;
76+
} catch (error) {
77+
assert.fail('features info tags sub-command should not throw');
78+
}
5379

54-
assert.isTrue(success);
55-
assert.isDefined(infoManifestResult);
56-
const manifest = infoManifestResult.stdout;
57-
const regex = /application\/vnd\.devcontainers\.layer\.v1\+tar/;
58-
assert.match(manifest, regex);
80+
assert.isTrue(success);
81+
assert.isDefined(infoManifestResult);
82+
const manifest = infoManifestResult.stdout;
83+
const regex = /application\/vnd\.devcontainers\.layer\.v1\+tar/;
84+
assert.match(manifest, regex);
85+
});
5986
});
6087
});
6188
});

0 commit comments

Comments
 (0)