Skip to content

Commit 3e73060

Browse files
committed
Load configuration and credentials from ~/.aws/config or path specified in AWS_CONFIG_FILE if AWS_SDK_LOAD_CONFIG is set
1 parent 2843d0e commit 3e73060

9 files changed

+234
-62
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"type": "feature",
3+
"category": "EnvironmentVariable",
4+
"description": "Load config from ~/.aws/config if AWS_SDK_LOAD_CONFIG is set"
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"type": "feature",
3+
"category": "EnvironmentVariable",
4+
"description": "Add support for specifying the location of the shared config file via the AWS_CONFIG_FILE environment variable. This variable is only honored if AWS_SDK_LOAD_CONFIG is set to a truthy value."
5+
}

lib/credentials/shared_ini_file_credentials.js

+21-48
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@ var path = require('path');
33
var STS = require('../../clients/sts');
44

55
var configOptInEnv = 'AWS_SDK_LOAD_CONFIG';
6+
var sharedFileEnv = 'AWS_SHARED_CREDENTIALS_FILE';
67
var defaultProfile = 'default';
78

89
/**
910
* Represents credentials loaded from shared credentials file
1011
* (defaulting to ~/.aws/credentials or defined by the
11-
* `AWS_CREDENTIAL_PROFILES_FILE` environment variable).
12+
* `AWS_SHARED_CREDENTIALS_FILE` environment variable).
1213
*
1314
* ## Using the shared credentials file
1415
*
@@ -44,7 +45,7 @@ AWS.SharedIniFileCredentials = AWS.util.inherit(AWS.Credentials, {
4445
* @option options profile [String] (AWS_PROFILE env var or 'default')
4546
* the name of the profile to load.
4647
* @option options filename [String] ('~/.aws/credentials' or defined by
47-
* AWS_CREDENTIAL_PROFILES_FILE process env var)
48+
* AWS_SHARED_CREDENTIALS_FILE process env var)
4849
* the filename to use when loading credentials.
4950
* @option options disableAssumeRole [Boolean] (false) True to disable
5051
* support for profiles that assume an IAM role. If true, and an assume
@@ -75,21 +76,23 @@ AWS.SharedIniFileCredentials = AWS.util.inherit(AWS.Credentials, {
7576
refresh: function refresh(callback) {
7677
if (!callback) callback = function(err) { if (err) throw err; };
7778
try {
78-
if (!this.filename) this.loadDefaultFilename();
7979
var profile = {};
8080
if (process.env[configOptInEnv]) {
81-
var configProfileName = this.profile === defaultProfile ?
82-
'default' : 'profile ' + this.profile;
83-
var config = AWS.util.ini
84-
.parse(AWS.util.readFileSync(this.configFilename));
85-
profile = AWS.util.merge(profile, config[configProfileName]);
81+
var config = new AWS.SharedIniFile({
82+
isConfig: true,
83+
filename: process.env.AWS_CONFIG_FILE
84+
});
85+
profile = AWS.util.merge(profile, config.getProfile(this.profile));
8686
}
87-
var creds = AWS.util.ini.parse(AWS.util.readFileSync(this.filename));
88-
profile = AWS.util.merge(profile, creds[this.profile]);
87+
var creds = new AWS.SharedIniFile({
88+
filename: this.filename ||
89+
(process.env[configOptInEnv] && process.env[sharedFileEnv])
90+
});
91+
profile = AWS.util.merge(profile, creds.getProfile(this.profile));
8992

9093
if (Object.keys(profile).length === 0) {
9194
throw AWS.util.error(
92-
new Error('Profile ' + this.profile + ' not found in ' + this.filename),
95+
new Error('Profile ' + this.profile + ' not found'),
9396
{ code: 'SharedIniFileCredentialsProviderFailure' }
9497
);
9598
}
@@ -105,8 +108,7 @@ AWS.SharedIniFileCredentials = AWS.util.inherit(AWS.Credentials, {
105108

106109
if (!this.accessKeyId || !this.secretAccessKey) {
107110
throw AWS.util.error(
108-
new Error('Credentials not set in ' + this.filename +
109-
' using profile ' + this.profile),
111+
new Error('Credentials not set for profile ' + this.profile),
110112
{ code: 'SharedIniFileCredentialsProviderFailure' }
111113
);
112114
}
@@ -124,8 +126,7 @@ AWS.SharedIniFileCredentials = AWS.util.inherit(AWS.Credentials, {
124126
if (this.disableAssumeRole) {
125127
throw AWS.util.error(
126128
new Error('Role assumption profiles are disabled. ' +
127-
'Failed to load profile ' + this.profile + ' from ' +
128-
this.filename),
129+
'Failed to load profile ' + this.profile),
129130
{ code: 'SharedIniFileCredentialsProviderFailure' }
130131
);
131132
}
@@ -138,19 +139,17 @@ AWS.SharedIniFileCredentials = AWS.util.inherit(AWS.Credentials, {
138139

139140
if (!sourceProfileName) {
140141
throw AWS.util.error(
141-
new Error('source_profile is not set in ' + this.filename +
142-
' using profile ' + this.profile),
142+
new Error('source_profile is not set using profile ' + this.profile),
143143
{ code: 'SharedIniFileCredentialsProviderFailure' }
144144
);
145145
}
146146

147-
var sourceProfile = creds[sourceProfileName];
147+
var sourceProfile = creds.getProfile(sourceProfileName);
148148

149149
if (typeof sourceProfile !== 'object') {
150150
throw AWS.util.error(
151-
new Error('source_profile ' + sourceProfileName + ' set in ' +
152-
this.filename + ' using profile ' + this.profile +
153-
' does not exist'),
151+
new Error('source_profile ' + sourceProfileName + ' using profile '
152+
+ this.profile + ' does not exist'),
154153
{ code: 'SharedIniFileCredentialsProviderFailure' }
155154
);
156155
}
@@ -166,8 +165,7 @@ AWS.SharedIniFileCredentials = AWS.util.inherit(AWS.Credentials, {
166165
if (!sourceCredentials.accessKeyId || !sourceCredentials.secretAccessKey) {
167166
throw AWS.util.error(
168167
new Error('Credentials not set in source_profile ' +
169-
sourceProfileName + ' set in ' + this.filename +
170-
' using profile ' + this.profile),
168+
sourceProfileName + ' using profile ' + this.profile),
171169
{ code: 'SharedIniFileCredentialsProviderFailure' }
172170
);
173171
}
@@ -197,30 +195,5 @@ AWS.SharedIniFileCredentials = AWS.util.inherit(AWS.Credentials, {
197195
self.expireTime = data.Credentials.Expiration;
198196
callback();
199197
});
200-
},
201-
202-
/**
203-
* @api private
204-
*/
205-
loadDefaultFilename: function loadDefaultFilename() {
206-
var env = process.env;
207-
208-
var home = env.HOME ||
209-
env.USERPROFILE ||
210-
(env.HOMEPATH ? ((env.HOMEDRIVE || 'C:/') + env.HOMEPATH) : null);
211-
if (!home) {
212-
throw AWS.util.error(
213-
new Error('Cannot load credentials, HOME path not set'),
214-
{ code: 'SharedIniFileCredentialsProviderFailure' }
215-
);
216-
}
217-
218-
this.filename = path.join(home, '.aws', 'credentials');
219-
220-
if (env[configOptInEnv]) {
221-
this.filename = env.AWS_SHARED_CREDENTIALS_FILE || this.filename;
222-
this.configFilename = env.AWS_CONFIG_FILE ||
223-
path.join(home, '.aws', 'config');
224-
}
225198
}
226199
});

lib/node_loader.js

+16-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ AWS.XML.Parser = require('./xml/node_parser');
1919
// Load Node HTTP client
2020
require('./http/node');
2121

22+
// Load Node shared ini file loader
23+
require('./shared_ini');
24+
2225
// Load custom credential providers
2326
require('./credentials/ec2_metadata_credentials');
2427
require('./credentials/ecs_credentials');
@@ -59,7 +62,19 @@ AWS.util.update(AWS.Config.prototype.keys, {
5962
return new AWS.CredentialProviderChain();
6063
},
6164
region: function() {
62-
return process.env.AWS_REGION || process.env.AMAZON_REGION;
65+
var env = process.env;
66+
var region = env.AWS_REGION || env.AMAZON_REGION;
67+
if (!region && env.AWS_SDK_LOAD_CONFIG) {
68+
var configFile = new AWS.SharedIniFile({
69+
isConfig: true,
70+
filename: process.env.AWS_CONFIG_FILE
71+
});
72+
var profile = configFile.getProfile(
73+
env.AWS_PROFILE || AWS.util.defaultProfile
74+
);
75+
region = profile && profile.region;
76+
}
77+
return region;
6378
}
6479
});
6580

lib/shared_ini.js

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
var AWS = require('./core');
2+
var os = require('os');
3+
var path = require('path');
4+
5+
/**
6+
* @api private
7+
*/
8+
AWS.SharedIniFile = AWS.util.inherit({
9+
constructor: function SharedIniFile(options) {
10+
options = options || {};
11+
12+
this.filename = options.filename;
13+
this.isConfig = options.isConfig === true;
14+
},
15+
16+
ensureFileLoaded: function loadFile() {
17+
if (!this.parsedContents) {
18+
this.parsedContents = AWS.util.ini.parse(
19+
AWS.util.readFileSync(this.filename || this.getDefaultFilepath())
20+
);
21+
}
22+
},
23+
24+
getDefaultFilepath: function getDefaultFilepath() {
25+
return path.join(
26+
this.getHomeDir(),
27+
'.aws',
28+
this.isConfig ? 'config' : 'credentials'
29+
);
30+
},
31+
32+
getHomeDir: function getHomeDir() {
33+
if (typeof os.homedir === 'function') {
34+
return os.homedir();
35+
}
36+
37+
var env = process.env;
38+
var home = env.HOME ||
39+
env.USERPROFILE ||
40+
(env.HOMEPATH ? ((env.HOMEDRIVE || 'C:/') + env.HOMEPATH) : null);
41+
42+
if (home) {
43+
return home;
44+
}
45+
46+
throw AWS.util.error(
47+
new Error('Cannot load credentials, HOME path not set')
48+
);
49+
},
50+
51+
getProfile: function loadProfile(profile) {
52+
this.ensureFileLoaded();
53+
54+
var profileIndex = profile !== AWS.util.defaultProfile && this.isConfig ?
55+
'profile ' + profile : profile;
56+
57+
return this.parsedContents[profileIndex];
58+
}
59+
});

lib/util.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -880,7 +880,12 @@ var util = {
880880
v4: function uuidV4() {
881881
return require('uuid').v4();
882882
}
883-
}
883+
},
884+
885+
/**
886+
* @api private
887+
*/
888+
defaultProfile: 'default'
884889

885890
};
886891

test/config.spec.coffee

+59-1
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,64 @@ describe 'AWS.Config', ->
5050
config = new AWS.Config()
5151
expect(config.region).to.equal('us-west-2')
5252

53+
describe 'shared config file', ->
54+
beforeEach ->
55+
os = require('os')
56+
helpers.spyOn(os, 'homedir').andReturn('/home/user')
57+
58+
it 'grabs region from shared config if AWS_SDK_LOAD_CONFIG is set', ->
59+
process.env.AWS_SDK_LOAD_CONFIG = '1'
60+
mock = '''
61+
[default]
62+
region = us-west-2
63+
'''
64+
helpers.spyOn(AWS.util, 'readFileSync').andReturn(mock)
65+
66+
config = new AWS.Config()
67+
expect(config.region).to.equal('us-west-2')
68+
expect(AWS.util.readFileSync.calls[0].arguments[0]).to.match(/[\/\\]home[\/\\]user[\/\\].aws[\/\\]config/)
69+
70+
it 'loads file from path specified in AWS_CONFIG_FILE if AWS_SDK_LOAD_CONFIG is set', ->
71+
process.env.AWS_SDK_LOAD_CONFIG = '1'
72+
process.env.AWS_CONFIG_FILE = '/path/to/user/config/file'
73+
mock = '''
74+
[default]
75+
region = us-west-2
76+
'''
77+
helpers.spyOn(AWS.util, 'readFileSync').andReturn(mock)
78+
79+
config = new AWS.Config()
80+
expect(config.region).to.equal('us-west-2')
81+
expect(AWS.util.readFileSync.calls[0].arguments[0]).to.equal('/path/to/user/config/file')
82+
83+
it 'uses the profile specified in AWS_PROFILE', ->
84+
process.env.AWS_SDK_LOAD_CONFIG = '1'
85+
process.env.AWS_PROFILE = 'foo'
86+
mock = '''
87+
[default]
88+
region = us-west-1
89+
90+
[profile foo]
91+
region = us-west-2
92+
'''
93+
helpers.spyOn(AWS.util, 'readFileSync').andReturn(mock)
94+
95+
config = new AWS.Config()
96+
expect(config.region).to.equal('us-west-2')
97+
expect(AWS.util.readFileSync.calls[0].arguments[0]).to.match(/[\/\\]home[\/\\]user[\/\\].aws[\/\\]config/)
98+
99+
it 'prefers AWS_REGION to the shared config file', ->
100+
process.env.AWS_REGION = 'us-east-1'
101+
process.env.AWS_SDK_LOAD_CONFIG = '1'
102+
mock = '''
103+
[default]
104+
region = us-west-2
105+
'''
106+
helpers.spyOn(AWS.util, 'readFileSync').andReturn(mock)
107+
108+
config = new AWS.Config()
109+
expect(config.region).to.equal('us-east-1')
110+
53111
it 'can be set to a string', ->
54112
expect(configure(region: 'us-west-1').region).to.equal('us-west-1')
55113

@@ -276,4 +334,4 @@ describe 'AWS.config', ->
276334
P = ->
277335
AWS.config.setPromisesDependency(P)
278336
dep = AWS.config.getPromisesDependency()
279-
expect(dep).to.equal(P)
337+
expect(dep).to.equal(P)

0 commit comments

Comments
 (0)