Skip to content

Commit c243f8e

Browse files
Return empty registry credentials when none found (#1153)
1 parent 8d320dc commit c243f8e

File tree

3 files changed

+44
-20
lines changed

3 files changed

+44
-20
lines changed

packages/testcontainers/src/container-runtime/auth/credential-provider.test.ts

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ describe.sequential("CredentialProvider", () => {
2121
});
2222

2323
it("should return the auth config for a registry", async () => {
24-
mockSpawnReturns(
24+
mockSpawnEmitsData(
2525
0,
2626
JSON.stringify({
2727
ServerURL: "registry",
@@ -40,7 +40,7 @@ describe.sequential("CredentialProvider", () => {
4040
});
4141

4242
it("should default to the registry url when the server url is not returned", async () => {
43-
mockSpawnReturns(
43+
mockSpawnEmitsData(
4444
0,
4545
JSON.stringify({
4646
Username: "username",
@@ -61,8 +61,8 @@ describe.sequential("CredentialProvider", () => {
6161
expect(await credentialProvider.getAuthConfig("registry", containerRuntimeConfig)).toBeUndefined();
6262
});
6363

64-
it("should throw when get credentials fails", async () => {
65-
mockSpawnReturns(
64+
it("should return undefined when get credentials fails because we lookup optimistically", async () => {
65+
mockSpawnEmitsData(
6666
1,
6767
JSON.stringify({
6868
ServerURL: "registry",
@@ -71,21 +71,27 @@ describe.sequential("CredentialProvider", () => {
7171
})
7272
);
7373

74+
expect(await credentialProvider.getAuthConfig("registry", containerRuntimeConfig)).toBeUndefined();
75+
});
76+
77+
it("should throw when credential provider emits error", async () => {
78+
mockSpawnEmitsError("ERROR");
79+
7480
await expect(() => credentialProvider.getAuthConfig("registry", containerRuntimeConfig)).rejects.toThrow(
75-
"An error occurred getting a credential"
81+
"Error from Docker credential provider: Error: ERROR"
7682
);
7783
});
7884

7985
it("should throw when get credentials output cannot be parsed", async () => {
80-
mockSpawnReturns(0, "CANNOT_PARSE");
86+
mockSpawnEmitsData(0, "CANNOT_PARSE");
8187

8288
await expect(() => credentialProvider.getAuthConfig("registry", containerRuntimeConfig)).rejects.toThrow(
8389
"Unexpected response from Docker credential provider GET command"
8490
);
8591
});
8692
});
8793

88-
function mockSpawnReturns(exitCode: number, stdout: string) {
94+
function mockSpawnEmitsData(exitCode: number, stdout: string) {
8995
const sink = new EventEmitter() as ChildProcess;
9096

9197
sink.stdout = new Readable({
@@ -104,6 +110,21 @@ function mockSpawnReturns(exitCode: number, stdout: string) {
104110
mockSpawn.mockReturnValueOnce(sink);
105111
}
106112

113+
function mockSpawnEmitsError(message: string) {
114+
const sink = new EventEmitter() as ChildProcess;
115+
116+
sink.kill = () => true;
117+
sink.stdout = new Readable({ read() {} });
118+
sink.stdin = new Writable({
119+
write(_chunk, _enc, cb) {
120+
sink.emit("error", new Error(message));
121+
cb?.();
122+
},
123+
});
124+
125+
mockSpawn.mockReturnValueOnce(sink);
126+
}
127+
107128
class TestCredentialProvider extends CredentialProvider {
108129
constructor(
109130
private readonly name: string,

packages/testcontainers/src/container-runtime/auth/credential-provider.ts

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { spawn } from "child_process";
22
import { log } from "../../common";
33
import { RegistryAuthLocator } from "./registry-auth-locator";
4-
import { AuthConfig, ContainerRuntimeConfig, CredentialProviderGetResponse } from "./types";
4+
import { AuthConfig, ContainerRuntimeConfig } from "./types";
55

66
export abstract class CredentialProvider implements RegistryAuthLocator {
77
abstract getName(): string;
@@ -17,32 +17,35 @@ export abstract class CredentialProvider implements RegistryAuthLocator {
1717
const programName = `docker-credential-${credentialProviderName}`;
1818
log.debug(`Executing Docker credential provider "${programName}"`);
1919

20-
const response = await this.runCredentialProvider(registry, programName);
21-
22-
return {
23-
username: response.Username,
24-
password: response.Secret,
25-
registryAddress: response.ServerURL ?? registry,
26-
};
20+
return await this.runCredentialProvider(registry, programName);
2721
}
2822

29-
private runCredentialProvider(registry: string, providerName: string): Promise<CredentialProviderGetResponse> {
23+
private runCredentialProvider(registry: string, providerName: string): Promise<AuthConfig | undefined> {
3024
return new Promise((resolve, reject) => {
3125
const sink = spawn(providerName, ["get"]);
3226

3327
const chunks: string[] = [];
3428
sink.stdout.on("data", (chunk) => chunks.push(chunk));
3529

30+
sink.on("error", (err) => {
31+
log.error(`Error from Docker credential provider: ${err}`);
32+
sink.kill("SIGKILL");
33+
reject(new Error(`Error from Docker credential provider: ${err}`));
34+
});
35+
3636
sink.on("close", (code) => {
3737
if (code !== 0) {
38-
log.error(`An error occurred getting a credential: ${code}`);
39-
return reject(new Error("An error occurred getting a credential"));
38+
return resolve(undefined);
4039
}
4140

4241
const response = chunks.join("");
4342
try {
4443
const parsedResponse = JSON.parse(response);
45-
return resolve(parsedResponse);
44+
return resolve({
45+
username: parsedResponse.Username,
46+
password: parsedResponse.Secret,
47+
registryAddress: parsedResponse.ServerURL ?? registry,
48+
});
4649
} catch (e) {
4750
log.error(`Unexpected response from Docker credential provider GET command: "${response}"`);
4851
return reject(new Error("Unexpected response from Docker credential provider GET command"));

packages/testcontainers/src/container-runtime/auth/get-auth-config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ export const getAuthConfig = async (registry: string): Promise<AuthConfig | unde
5555
}
5656
}
5757

58-
log.debug(`No registry auth locator found for registry "${registry}"`);
58+
log.debug(`No auth config found for registry "${registry}"`);
5959
authsCache.set(registry, undefined);
6060
return undefined;
6161
};

0 commit comments

Comments
 (0)