Skip to content

Commit 7db14b1

Browse files
authored
fix(credential-provider-node): read config and credentials files only once (#2045)
* chore(credential-provider-node): refactor provider chain * fix(credential-provider-node): read config files only once * docs(credential-provider-*): mark laodedConfigs param internal * chore(credential-provider-node): make functions arrow
1 parent 76a9bd3 commit 7db14b1

File tree

6 files changed

+54
-12
lines changed

6 files changed

+54
-12
lines changed

packages/credential-provider-ini/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ export interface FromIniInit extends SharedConfigInit {
5353
/**
5454
* A promise that will be resolved with loaded and parsed credentials files.
5555
* Used to avoid loading shared config files multiple times.
56+
*
57+
* @internal
5658
*/
5759
loadedConfig?: Promise<SharedConfigFiles>;
5860

packages/credential-provider-node/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"@aws-sdk/credential-provider-ini": "3.4.1",
3232
"@aws-sdk/credential-provider-process": "3.4.1",
3333
"@aws-sdk/property-provider": "3.4.1",
34+
"@aws-sdk/shared-ini-file-loader": "3.4.1",
3435
"@aws-sdk/types": "3.4.1",
3536
"tslib": "^1.8.0"
3637
},

packages/credential-provider-node/src/index.spec.ts

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,17 @@ jest.mock("@aws-sdk/credential-provider-env", () => {
1010
});
1111
import { fromEnv } from "@aws-sdk/credential-provider-env";
1212

13+
const loadedConfig = {
14+
credentialsFile: {
15+
foo: { aws_access_key_id: "key", aws_secret_access_key: "secret" },
16+
},
17+
configFile: { bar: { aws_access_key_id: "key", aws_secret_access_key: "secret" } },
18+
};
19+
jest.mock("@aws-sdk/shared-ini-file-loader", () => ({
20+
loadSharedConfigFiles: jest.fn().mockReturnValue(loadedConfig),
21+
}));
22+
import { loadSharedConfigFiles } from "@aws-sdk/shared-ini-file-loader";
23+
1324
jest.mock("@aws-sdk/credential-provider-ini", () => {
1425
const iniProvider = jest.fn();
1526
return {
@@ -39,6 +50,7 @@ jest.mock("@aws-sdk/credential-provider-imds", () => {
3950
ENV_CMDS_RELATIVE_URI: "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI",
4051
};
4152
});
53+
4254
import {
4355
ENV_CMDS_FULL_URI,
4456
ENV_CMDS_RELATIVE_URI,
@@ -78,6 +90,7 @@ beforeEach(() => {
7890
(fromProcess as any).mockClear();
7991
(fromContainerMetadata as any).mockClear();
8092
(fromInstanceMetadata as any).mockClear();
93+
(loadSharedConfigFiles as any).mockClear();
8194
});
8295

8396
afterAll(() => {
@@ -200,6 +213,23 @@ describe("defaultProvider", () => {
200213
expect((fromInstanceMetadata() as any).mock.calls.length).toBe(0);
201214
});
202215

216+
it("should read config files only once for all providers", async () => {
217+
const creds = {
218+
accessKeyId: "foo",
219+
secretAccessKey: "bar",
220+
};
221+
222+
(fromEnv() as any).mockImplementation(() => Promise.reject(new ProviderError("Keep moving!")));
223+
(fromIni() as any).mockImplementation(() => Promise.reject(new ProviderError("Nothing here!")));
224+
(fromProcess() as any).mockImplementation(() => Promise.reject(new ProviderError("Nor here!")));
225+
(fromInstanceMetadata() as any).mockImplementation(() => Promise.resolve(creds));
226+
227+
await expect(defaultProvider()()).resolves;
228+
expect((loadSharedConfigFiles as any).mock.calls.length).toBe(1);
229+
expect((fromIni as any).mock.calls[1][0]).toMatchObject({ loadedConfig: loadSharedConfigFiles() });
230+
expect((fromProcess as any).mock.calls[1][0]).toMatchObject({ loadedConfig: loadSharedConfigFiles() });
231+
});
232+
203233
it("should pass configuration on to the ini provider", async () => {
204234
const iniConfig: FromIniInit = {
205235
profile: "foo",
@@ -226,7 +256,7 @@ describe("defaultProvider", () => {
226256
await expect(defaultProvider(iniConfig)()).resolves;
227257

228258
expect((fromIni as any).mock.calls.length).toBe(1);
229-
expect((fromIni as any).mock.calls[0][0]).toBe(iniConfig);
259+
expect((fromIni as any).mock.calls[0][0]).toEqual({ ...iniConfig, loadedConfig });
230260
});
231261

232262
it("should pass configuration on to the process provider", async () => {
@@ -249,7 +279,7 @@ describe("defaultProvider", () => {
249279
await expect(defaultProvider(processConfig)()).resolves;
250280
expect((fromProcess as any).mock.calls.length).toBe(1);
251281
expect((fromProcess as any).mock.calls.length).toBe(1);
252-
expect((fromProcess as any).mock.calls[0][0]).toBe(processConfig);
282+
expect((fromProcess as any).mock.calls[0][0]).toEqual({ ...processConfig, loadedConfig });
253283
});
254284

255285
it("should pass configuration on to the IMDS provider", async () => {
@@ -273,7 +303,7 @@ describe("defaultProvider", () => {
273303
await expect(defaultProvider(imdsConfig)()).resolves;
274304

275305
expect((fromInstanceMetadata as any).mock.calls.length).toBe(1);
276-
expect((fromInstanceMetadata as any).mock.calls[0][0]).toBe(imdsConfig);
306+
expect((fromInstanceMetadata as any).mock.calls[0][0]).toEqual({ ...imdsConfig, loadedConfig });
277307
});
278308

279309
it("should pass configuration on to the ECS IMDS provider", async () => {
@@ -298,7 +328,7 @@ describe("defaultProvider", () => {
298328
await expect(defaultProvider(ecsImdsConfig)()).resolves;
299329

300330
expect((fromContainerMetadata as any).mock.calls.length).toBe(1);
301-
expect((fromContainerMetadata as any).mock.calls[0][0]).toBe(ecsImdsConfig);
331+
expect((fromContainerMetadata as any).mock.calls[0][0]).toEqual({ ...ecsImdsConfig, loadedConfig });
302332
});
303333

304334
it("should return the same promise across invocations", async () => {

packages/credential-provider-node/src/index.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
import { ENV_PROFILE, fromIni, FromIniInit } from "@aws-sdk/credential-provider-ini";
1010
import { fromProcess, FromProcessInit } from "@aws-sdk/credential-provider-process";
1111
import { chain, memoize, ProviderError } from "@aws-sdk/property-provider";
12+
import { loadSharedConfigFiles } from "@aws-sdk/shared-ini-file-loader";
1213
import { CredentialProvider } from "@aws-sdk/types";
1314

1415
export const ENV_IMDS_DISABLED = "AWS_EC2_METADATA_DISABLED";
@@ -41,20 +42,21 @@ export const ENV_IMDS_DISABLED = "AWS_EC2_METADATA_DISABLED";
4142
* @see fromContainerMetadata The function used to source credentials from the
4243
* ECS Container Metadata Service
4344
*/
44-
export function defaultProvider(init: FromIniInit & RemoteProviderInit & FromProcessInit = {}): CredentialProvider {
45-
const { profile = process.env[ENV_PROFILE] } = init;
46-
const providerChain = profile
47-
? chain(fromIni(init), fromProcess(init))
48-
: chain(fromEnv(), fromIni(init), fromProcess(init), remoteProvider(init));
45+
export const defaultProvider = (init: FromIniInit & RemoteProviderInit & FromProcessInit = {}): CredentialProvider => {
46+
const options = { profile: process.env[ENV_PROFILE], ...init };
47+
if (!options.loadedConfig) options.loadedConfig = loadSharedConfigFiles(init);
48+
const providers = [fromIni(options), fromProcess(options), remoteProvider(options)];
49+
if (!options.profile) providers.unshift(fromEnv());
50+
const providerChain = chain(...providers);
4951

5052
return memoize(
5153
providerChain,
5254
(credentials) => credentials.expiration !== undefined && credentials.expiration.getTime() - Date.now() < 300000,
5355
(credentials) => credentials.expiration !== undefined
5456
);
55-
}
57+
};
5658

57-
function remoteProvider(init: RemoteProviderInit): CredentialProvider {
59+
const remoteProvider = (init: RemoteProviderInit): CredentialProvider => {
5860
if (process.env[ENV_CMDS_RELATIVE_URI] || process.env[ENV_CMDS_FULL_URI]) {
5961
return fromContainerMetadata(init);
6062
}
@@ -64,4 +66,4 @@ function remoteProvider(init: RemoteProviderInit): CredentialProvider {
6466
}
6567

6668
return fromInstanceMetadata(init);
67-
}
69+
};

packages/credential-provider-process/src/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import { ParsedIniData, SharedConfigFiles, SharedConfigInit } from "@aws-sdk/sha
44
import { CredentialProvider, Credentials } from "@aws-sdk/types";
55
import { exec } from "child_process";
66

7+
/**
8+
* @internal
9+
*/
710
export const ENV_PROFILE = "AWS_PROFILE";
811

912
export interface FromProcessInit extends SharedConfigInit {
@@ -15,6 +18,8 @@ export interface FromProcessInit extends SharedConfigInit {
1518
/**
1619
* A promise that will be resolved with loaded and parsed credentials files.
1720
* Used to avoid loading shared config files multiple times.
21+
*
22+
* @internal
1823
*/
1924
loadedConfig?: Promise<SharedConfigFiles>;
2025
}

packages/node-config-provider/src/fromSharedConfigFiles.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ export interface SharedConfigInit extends BaseSharedConfigInit {
2626
/**
2727
* A promise that will be resolved with loaded and parsed credentials files.
2828
* Used to avoid loading shared config files multiple times.
29+
*
30+
* @internal
2931
*/
3032
loadedConfig?: Promise<SharedConfigFiles>;
3133
}

0 commit comments

Comments
 (0)