Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ describe.sequential("CredentialProvider", () => {
});

it("should return the auth config for a registry", async () => {
mockExecReturns(JSON.stringify({ registry: "username" }));
mockSpawnReturns(
0,
JSON.stringify({
Expand All @@ -40,24 +39,7 @@ describe.sequential("CredentialProvider", () => {
});
});

it("should not return auth config for registry which is a partial match", async () => {
mockExecReturns(JSON.stringify({ "https://registry.example.com": "username" }));
mockSpawnReturns(
0,
JSON.stringify({
ServerURL: "https://registry.example.com",
Username: "username",
Secret: "secret",
})
);

expect(
await credentialProvider.getAuthConfig("https://registry.example.co", containerRuntimeConfig)
).toBeUndefined();
});

it("should default to the registry url when the server url is not returned", async () => {
mockExecReturns(JSON.stringify({ "https://registry.example.com": "username" }));
mockSpawnReturns(
0,
JSON.stringify({
Expand All @@ -66,55 +48,20 @@ describe.sequential("CredentialProvider", () => {
})
);

expect(await credentialProvider.getAuthConfig("https://registry.example.com", containerRuntimeConfig)).toEqual({
registryAddress: "https://registry.example.com",
expect(await credentialProvider.getAuthConfig("registry.example.com", containerRuntimeConfig)).toEqual({
registryAddress: "registry.example.com",
username: "username",
password: "secret",
});
});

it("should return undefined when no auth config found for registry", async () => {
mockExecReturns(JSON.stringify({ registry2: "username" }));

const credentials = await credentialProvider.getAuthConfig("registry1", containerRuntimeConfig);

expect(credentials).toBeUndefined();
});

it("should return undefined when provider name not provided", async () => {
const credentialProvider = new TestCredentialProvider("name", undefined!);
const credentialProvider = new TestCredentialProvider("name", undefined);

expect(await credentialProvider.getAuthConfig("registry", containerRuntimeConfig)).toBeUndefined();
});

it("should throw when list credentials fails", async () => {
mockExecThrows();

await expect(() => credentialProvider.getAuthConfig("registry", containerRuntimeConfig)).rejects.toThrow(
"An error occurred listing credentials"
);
});

it("should throw when list credentials output cannot be parsed", async () => {
mockExecReturns("CANNOT_PARSE");

await expect(() => credentialProvider.getAuthConfig("registry", containerRuntimeConfig)).rejects.toThrow(
"Unexpected response from Docker credential provider LIST command"
);
});

it("should not throw when list credentials command is not implemented", async () => {
mockExec.mockImplementationOnce((command, callback) => {
return callback(new Error(), null, "list is unimplemented\n");
});

const credentials = await credentialProvider.getAuthConfig("registry", containerRuntimeConfig);

expect(credentials).toBeUndefined();
});

it("should throw when get credentials fails", async () => {
mockExecReturns(JSON.stringify({ registry: "username" }));
mockSpawnReturns(
1,
JSON.stringify({
Expand All @@ -130,7 +77,6 @@ describe.sequential("CredentialProvider", () => {
});

it("should throw when get credentials output cannot be parsed", async () => {
mockExecReturns(JSON.stringify({ registry: "username" }));
mockSpawnReturns(0, "CANNOT_PARSE");

await expect(() => credentialProvider.getAuthConfig("registry", containerRuntimeConfig)).rejects.toThrow(
Expand All @@ -139,18 +85,6 @@ describe.sequential("CredentialProvider", () => {
});
});

function mockExecReturns(stdout: string) {
mockExec.mockImplementationOnce((command, callback) => {
return callback(null, stdout);
});
}

function mockExecThrows() {
mockExec.mockImplementationOnce((command, callback) => {
return callback("An error occurred");
});
}

function mockSpawnReturns(exitCode: number, stdout: string) {
const sink = new EventEmitter() as ChildProcess;

Expand All @@ -173,7 +107,7 @@ function mockSpawnReturns(exitCode: number, stdout: string) {
class TestCredentialProvider extends CredentialProvider {
constructor(
private readonly name: string,
private readonly credentialProviderName: string
private readonly credentialProviderName: string | undefined
) {
super();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
import { exec, spawn } from "child_process";
import { spawn } from "child_process";
import { log } from "../../common";
import { RegistryAuthLocator } from "./registry-auth-locator";
import { registryMatches } from "./registry-matches";
import {
AuthConfig,
ContainerRuntimeConfig,
CredentialProviderGetResponse,
CredentialProviderListResponse,
} from "./types";
import { AuthConfig, ContainerRuntimeConfig, CredentialProviderGetResponse } from "./types";

export abstract class CredentialProvider implements RegistryAuthLocator {
abstract getName(): string;
Expand All @@ -23,45 +17,15 @@ export abstract class CredentialProvider implements RegistryAuthLocator {
const programName = `docker-credential-${credentialProviderName}`;
log.debug(`Executing Docker credential provider "${programName}"`);

const credentials = await this.listCredentials(programName);

const credentialForRegistry = Object.keys(credentials).find((aRegistry) => registryMatches(aRegistry, registry));
if (!credentialForRegistry) {
log.debug(`No credential found for registry "${registry}"`);
return undefined;
}

const response = await this.runCredentialProvider(registry, programName);

return {
username: response.Username,
password: response.Secret,
registryAddress: response.ServerURL ?? credentialForRegistry,
registryAddress: response.ServerURL ?? registry,
};
}

private listCredentials(providerName: string): Promise<CredentialProviderListResponse> {
return new Promise((resolve, reject) => {
exec(`${providerName} list`, (err, stdout, stderr) => {
if (err) {
if (stderr === "list is unimplemented\n") {
return resolve({});
}

log.error(`An error occurred listing credentials: ${err}`);
return reject(new Error("An error occurred listing credentials"));
}
try {
const response = JSON.parse(stdout);
return resolve(response);
} catch (e) {
log.error(`Unexpected response from Docker credential provider LIST command: "${stdout}"`);
return reject(new Error("Unexpected response from Docker credential provider LIST command"));
}
});
});
}

private runCredentialProvider(registry: string, providerName: string): Promise<CredentialProviderGetResponse> {
return new Promise((resolve, reject) => {
const sink = spawn(providerName, ["get"]);
Expand Down
Loading