Skip to content

Commit 6607543

Browse files
committed
Make AWSConfig its own type
Rather than defining `AWSConfig` as a `SymbolDict` with no restrictions on the contents, we can make it its own type, which gives more control over how things are stored and accessed. Fixes #53
1 parent b7f7b90 commit 6607543

File tree

4 files changed

+151
-113
lines changed

4 files changed

+151
-113
lines changed

src/AWSCore.jl

Lines changed: 132 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,134 @@ using DataStructures: OrderedDict
2222
using JSON
2323
using LazyJSON
2424

25+
# NOTE: This needs to be defined before AWSConfig. Methods defined on AWSCredentials are
26+
# in src/AWSCredentials.jl.
27+
"""
28+
AWSCredentials
29+
30+
A type which holds AWS credentials.
31+
When you interact with AWS, you specify your
32+
[AWS Security Credentials](http://docs.aws.amazon.com/general/latest/gr/aws-security-credentials.html)
33+
to verify who you are and whether you have permission to access the resources that you are
34+
requesting. AWS uses the security credentials to authenticate and authorize your requests.
35+
36+
The fields `access_key_id` and `secret_key` hold the access keys used to authenticate API
37+
requests (see [Creating, Modifying, and Viewing Access
38+
Keys](http://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html#Using_CreateAccessKey)).
39+
40+
[Temporary Security Credentials](http://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp.html)
41+
require the extra session `token` field.
42+
43+
The `user_arn` and `account_number` fields are used to cache the result of the
44+
[`aws_user_arn`](@ref) and [`aws_account_number`](@ref) functions.
2545
46+
The `AWSCredentials()` constructor tries to load local credentials from:
47+
48+
* `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`
49+
[environment variables](http://docs.aws.amazon.com/cli/latest/userguide/cli-environment.html),
50+
* [`~/.aws/credentials`](http://docs.aws.amazon.com/cli/latest/userguide/cli-config-files.html), or
51+
* [EC2 Instance Credentials](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html#instance-metadata-security-credentials).
52+
53+
To specify the profile to use from `~/.aws/credentials`, do, for example,
54+
`AWSCredentials(profile="profile-name")`.
55+
56+
A `~/.aws/credentials` file can be created using the
57+
[AWS CLI](https://aws.amazon.com/cli/) command `aws configrue`.
58+
Or it can be created manually:
59+
60+
```ini
61+
[default]
62+
aws_access_key_id = AKIAXXXXXXXXXXXXXXXX
63+
aws_secret_access_key = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
64+
```
65+
66+
If your `~/.aws/credentials` file contains multiple profiles you can pass the
67+
profile name as a string to the `profile` keyword argument (`nothing` by
68+
default) or select a profile by setting the `AWS_PROFILE` environment variable.
2669
"""
27-
Most `AWSCore` functions take a `AWSConfig` dictionary as the first argument.
28-
This dictionary holds [`AWSCredentials`](@ref) and AWS region configuration.
70+
mutable struct AWSCredentials
71+
access_key_id::String
72+
secret_key::String
73+
token::String
74+
user_arn::String
75+
account_number::String
76+
77+
function AWSCredentials(access_key_id, secret_key,
78+
token="", user_arn="", account_number="")
79+
new(access_key_id, secret_key, token, user_arn, account_number)
80+
end
81+
end
82+
83+
"""
84+
AWSConfig
85+
86+
Most `AWSCore` functions take an `AWSConfig` object as the first argument.
87+
This type holds [`AWSCredentials`](@ref), region, and output configuration.
88+
89+
# Constructors
90+
91+
AWSConfig(; profile, creds, region, output)
2992
30-
```julia
31-
aws = AWSConfig(:creds => AWSCredentials(), :region => "us-east-1")`
93+
Construct an `AWSConfig` object with the given profile, credentials, region, and output
94+
format. All keyword arguments have default values and are thus optional.
95+
96+
* `profile`: Profile name passed to [`AWSCredentials`](@ref), or `nothing` (default)
97+
* `creds`: `AWSCredentials` object, constructed using `profile` if not provided
98+
* `region`: Region, read from `AWS_DEFAULT_REGION` if present, otherwise `"us-east-1"`
99+
* `output`: Output format, defaulting to JSON (`"json"`)
100+
101+
# Examples
102+
103+
```julia-repl
104+
julia> AWSConfig(profile="example", region="ap-southeast-2")
105+
AWSConfig((AKIDEXAMPLE, wJa...)
106+
, "ap-southeast-2", "json")
107+
108+
julia> AWSConfig(creds=AWSCredentials("AKIDEXAMPLE", "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY"))
109+
AWSConfig((AKIDEXAMPLE, wJa...)
110+
, "us-east-1", "json")
32111
```
33112
"""
34-
const AWSConfig = SymbolDict
113+
mutable struct AWSConfig
114+
creds::AWSCredentials
115+
region::String
116+
output::String
117+
end
118+
119+
function AWSConfig(; profile=nothing,
120+
creds=AWSCredentials(profile=profile),
121+
region=get(ENV, "AWS_DEFAULT_REGION", "us-east-1"),
122+
output="json")
123+
AWSConfig(creds, region, output)
124+
end
35125

126+
# Relics of using SymbolDict
127+
import Base: getindex, setindex!
128+
Base.@deprecate AWSConfig(pairs::Pair...) AWSConfig(; pairs...)
129+
Base.@deprecate getindex(conf::AWSConfig, x::Symbol) getfield(conf, x)
130+
Base.@deprecate setindex!(conf::AWSConfig, val, var::Symbol) setfield!(conf, var, val)
131+
Base.@deprecate aws_config AWSConfig
132+
function Base.get(conf::AWSConfig, field::Symbol, alternative)
133+
Base.depwarn("get(::AWSConf, a, b) is deprecated; access fields directly instead", :get)
134+
if Base.fieldindex(AWSConfig, field, false) > 0
135+
getfield(conf, field)
136+
else
137+
alternative
138+
end
139+
end
140+
function Base.merge(conf::AWSConfig, d::AbstractDict{Symbol,<:Any})
141+
Base.depwarn("merge(::AWSConf, dict) is deprecated; set fields directly instead", :merge)
142+
for (k, v) in d
143+
setfield!(conf, k, v)
144+
end
145+
conf
146+
end
147+
function Base.merge(d::AbstractDict{K,V}, conf::AWSConfig) where {K,V}
148+
for f in fieldnames(AWSConfig)
149+
d[convert(K, f)] = getfield(conf, f)
150+
end
151+
d
152+
end
36153

37154
"""
38155
The `AWSRequest` dictionary describes a single API request:
@@ -57,78 +174,24 @@ include("names.jl")
57174
include("mime.jl")
58175

59176

60-
61177
#------------------------------------------------------------------------------#
62178
# Configuration.
63179
#------------------------------------------------------------------------------#
64180

65-
"""
66-
The `aws_config` function provides a simple way to creates an
67-
[`AWSConfig`](@ref) configuration dictionary.
68-
69-
```julia
70-
>aws = aws_config()
71-
>aws = aws_config(creds = my_credentials)
72-
>aws = aws_config(region = "ap-southeast-2")
73-
>aws = aws_config(profile = "profile-name")
74-
```
75-
76-
By default, the `aws_config` attempts to load AWS credentials from:
77-
78-
- `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` [environemnt variables](http://docs.aws.amazon.com/cli/latest/userguide/cli-environment.html),
79-
- [`~/.aws/credentials`](http://docs.aws.amazon.com/cli/latest/userguide/cli-config-files.html) or
80-
- [EC2 Instance Credentials](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html#instance-metadata-security-credentials).
81-
82-
A `~/.aws/credentials` file can be created using the
83-
[AWS CLI](https://aws.amazon.com/cli/) command `aws configrue`.
84-
Or it can be created manually:
85-
86-
```ini
87-
[default]
88-
aws_access_key_id = AKIAXXXXXXXXXXXXXXXX
89-
aws_secret_access_key = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
90-
```
91-
92-
If your `~/.aws/credentials` file contains multiple profiles you can pass the
93-
profile name as a string to the `profile` keyword argument (`nothing` by
94-
default) or select a profile by setting the `AWS_PROFILE` environment variable.
95-
96-
`aws_config` understands the following [AWS CLI environment
97-
variables](http://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html#cli-environment):
98-
`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_SESSION_TOKEN`,
99-
`AWS_DEFAULT_REGION`, `AWS_PROFILE` and `AWS_CONFIG_FILE`.
100-
101-
102-
An configuration dictionary can also be created directly from a key pair
103-
as follows. However, putting access credentials in source code is discouraged.
104-
105-
```julia
106-
aws = aws_config(creds = AWSCredentials("AKIAXXXXXXXXXXXXXXXX",
107-
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"))
108-
```
181+
global _default_aws_config = Ref{Union{AWSConfig,Nothing}}(nothing)
109182

110183
"""
111-
function aws_config(;profile=nothing,
112-
creds=AWSCredentials(profile=profile),
113-
region=get(ENV, "AWS_DEFAULT_REGION", "us-east-1"),
114-
args...)
115-
@SymDict(creds, region, args...)
116-
end
184+
default_aws_config()
117185
118-
119-
global _default_aws_config = nothing # Union{AWSConfig,Nothing}
120-
121-
122-
"""
123-
`default_aws_config` returns a global shared [`AWSConfig`](@ref) object
124-
obtained by calling [`aws_config`](@ref) with no optional arguments.
186+
Return the global shared [`AWSConfig`](@ref) object obtained by calling
187+
[`AWSConfig()`](@ref) with no arguments.
125188
"""
126189
function default_aws_config()
127190
global _default_aws_config
128-
if _default_aws_config === nothing
129-
_default_aws_config = aws_config()
191+
if _default_aws_config[] === nothing
192+
_default_aws_config[] = AWSConfig()
130193
end
131-
return _default_aws_config
194+
return _default_aws_config[]
132195
end
133196

134197

@@ -201,7 +264,7 @@ Service endpoint URL for `request`.
201264
"""
202265
function service_url(aws, request)
203266
endpoint = get(request, :endpoint, request[:service])
204-
region = "." * aws[:region]
267+
region = "." * aws.region
205268
if endpoint == "iam" || (endpoint == "sdb" && region == ".us-east-1")
206269
region = ""
207270
end
@@ -220,7 +283,7 @@ function service_query(aws::AWSConfig; args...)
220283
request = Dict{Symbol,Any}(args)
221284

222285
request[:verb] = "POST"
223-
request[:resource] = get(aws, :resource, "/")
286+
request[:resource] = "/" # get(aws, :resource, "/") XXX how could config ever have that
224287
request[:url] = service_url(aws, request)
225288
request[:headers] = Dict("Content-Type" =>
226289
"application/x-www-form-urlencoded; charset=utf-8")
@@ -230,7 +293,7 @@ function service_query(aws::AWSConfig; args...)
230293
request[:query]["Version"] = request[:version]
231294

232295
if request[:service] == "iam"
233-
aws = merge(aws, Dict(:region => "us-east-1"))
296+
aws.region = "us-east-1"
234297
end
235298
if request[:service] in ["iam", "sts", "sqs", "sns"]
236299
request[:query]["ContentType"] = "JSON"

src/AWSCredentials.jl

Lines changed: 4 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -17,32 +17,7 @@ export AWSCredentials,
1717
aws_account_number
1818

1919

20-
"""
21-
When you interact with AWS, you specify your [AWS Security Credentials](http://docs.aws.amazon.com/general/latest/gr/aws-security-credentials.html) to verify who you are and whether you have permission to access the resources that you are requesting. AWS uses the security credentials to authenticate and authorize your requests.
22-
23-
The fields `access_key_id` and `secret_key` hold the access keys used to authenticate API requests (see [Creating, Modifying, and Viewing Access Keys](http://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html#Using_CreateAccessKey)).
24-
25-
[Temporary Security Credentials](http://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp.html) require the extra session `token` field.
26-
27-
28-
The `user_arn` and `account_number` fields are used to cache the result of the [`aws_user_arn`](@ref) and [`aws_account_number`](@ref) functions.
29-
30-
The `AWSCredentials()` constructor tries to load local Credentials from
31-
environment variables, `~/.aws/credentials`, `~/.aws/config` or EC2 instance credentials.
32-
To specify the profile to use from `~/.aws/credentials`, do, for example, `AWSCredentials(profile="profile-name")`.
33-
"""
34-
mutable struct AWSCredentials
35-
access_key_id::String
36-
secret_key::String
37-
token::String
38-
user_arn::String
39-
account_number::String
40-
41-
function AWSCredentials(access_key_id, secret_key,
42-
token="", user_arn="", account_number="")
43-
new(access_key_id, secret_key, token, user_arn, account_number)
44-
end
45-
end
20+
# AWSCredentials type defined in src/AWSCore.jl
4621

4722
function Base.show(io::IO,c::AWSCredentials)
4823
println(io, string(c.user_arn,
@@ -145,7 +120,7 @@ e.g. `"arn:aws:iam::account-ID-without-hyphens:user/Bob"`
145120
"""
146121
function aws_user_arn(aws::AWSConfig)
147122

148-
creds = aws[:creds]
123+
creds = aws.creds
149124

150125
if creds.user_arn == ""
151126

@@ -164,7 +139,7 @@ end
164139
12-digit [AWS Account Number](http://docs.aws.amazon.com/general/latest/gr/acct-identifiers.html).
165140
"""
166141
function aws_account_number(aws::AWSConfig)
167-
creds = aws[:creds]
142+
creds = aws.creds
168143
if creds.account_number == ""
169144
aws_user_arn(aws)
170145
end
@@ -322,7 +297,7 @@ function aws_get_role(role::AbstractString, ini::Inifile)
322297
end
323298
credentials = dot_aws_credentials(source_profile)
324299

325-
config = AWSConfig(:creds=>credentials, :region=>aws_get_region(source_profile, ini))
300+
config = AWSConfig(creds=credentials, region=aws_get_region(source_profile, ini))
326301

327302
role = Services.sts(
328303
config,

src/names.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export arn, is_arn,
2121
Generate an [Amazon Resource Name](http://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html) for `service` and `resource`.
2222
"""
2323
function arn(service, resource,
24-
region=get(default_aws_config(), :region, ""),
24+
region=default_aws_config().region,
2525
account=aws_account_number(default_aws_config()))
2626

2727
if service == "s3"
@@ -38,7 +38,7 @@ end
3838
function arn(aws::AWSConfig,
3939
service,
4040
resource,
41-
region=get(aws, :region, ""),
41+
region=aws.region,
4242
account=aws_account_number(aws))
4343

4444
arn(service, resource, region, account)

0 commit comments

Comments
 (0)