Current section

14 Versions

Jump to

Compare versions

12 files changed
+194 additions
-206 deletions
  @@ -19,3 +19,6 @@ CHANGELOG
19 19
20 20 ---- 0.1.2 / 2014-09-17 / metadata-credentials ---------------------------------
21 21 * retrieve access keys from IAM roles when running in EC2
22 +
23 + ---- 0.1.3 / 2014-09-17 / refactor-config --------------------------------------
24 + * allow multiple configs instead of one global config
  @@ -16,18 +16,15 @@ Install the [Hex.pm](http://hex.pm) package
16 16 ```elixir
17 17 def deps do
18 18 [
19 - {:simplex, "0.1.2"}
19 + {:simplex, "0.2.1"}
20 20 ]
21 21 end
22 22 ```
23 23
24 - 2. Add `:simplex` to your application dependencies:
25 24
26 - ```elixir
27 - def application do
28 - [applications: [:simplex]]
29 - end
30 - ```
25 + ### Configuration
26 +
27 + `{:ok, simplex} = Simplex.new`
31 28
32 29 #### AWS Keys
33 30
  @@ -37,13 +34,28 @@ There are two ways to provide your keys to the Simplex library:
37 34
38 35 1. Set them from within your application
39 36 ```elixir
40 - Simplex.aws_access_key "your-access-key"
41 - Simplex.aws_secret_access_key "your-secret-access-key"
37 + {:ok, simplex} = Simplex.new("your-access-key", "your-secret-access-key")
42 38 ```
43 39
44 - 2. Set them as the environment variables `SIMPLEX_AWS_ACCESS_KEY` and `SIMPLEX_AWS_SECRET_ACCESS_KEY`
40 + or
41 +
42 + ```elixir
43 + {:ok, simplex} = Simplex.new
44 + Simplex.aws_access_key(simplex, "your-access-key")
45 + Simplex.aws_secret_access_key(simplex, "your-secret-access-key")
45 46 ```
46 - SIMPLEX_AWS_ACCESS_KEY=your-access-key SIMPLEX_AWS_SECRET_ACCESS_KEY=your-secret-access-key iex -S mix
47 +
48 + 2. Set them as the environment variables `AWS_ACCESS_KEY` and `AWS_SECRET_ACCESS_KEY`
49 + ```
50 + AWS_ACCESS_KEY=your-access-key AWS_SECRET_ACCESS_KEY=your-secret-access-key iex -S mix
51 +
52 + iex(1)> {:ok, simplex} = Simplex.new
53 + {:ok, #PID<0.164.0>}
54 + iex(2)> Simplex.aws_access_key(simplex)
55 + "your-access-key"
56 + iex(3)> Simplex.aws_secret_access_key(simplex)
57 + "your-secret-access-key"
58 +
47 59 ```
48 60
49 61 3. If not provided by the above two methods Simplex will attempt to retrieve keys from [instance metadata](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html) if it's running in EC2 and you launched your instance with an IAM role with permission to access SimpleDB.
  @@ -54,12 +66,18 @@ By default Simplex will send all requests to the us-east-1 SimpleDB url: `https:
54 66
55 67 1. Setting it from within your application
56 68 ```elixir
57 - Simplex.simpledb_url "https://sdb.us-west-1.amazonaws.com"
69 + {:ok, simplex} = Simplex.new
70 + Simplex.simpledb_url(simplex, "https://sdb.us-west-1.amazonaws.com")
58 71 ```
59 72
60 - 2. Set it as the environment variable `SIMPLEX_SIMPLEDB_URL`
73 + 2. Set it as the environment variable `SIMPLEDB_URL`
61 74 ```
62 - SIMPLEX_SIMPLEDB_URL=https://sdb.us-west-1.amazonaws.com iex -S mix
75 + SIMPLEDB_URL=https://sdb.us-west-1.amazonaws.com iex -S mix
76 +
77 + iex(1)> {:ok, simplex} = Simplex.new
78 + {:ok, #PID<0.164.0>}
79 + iex(2)> Simplex.simpledb_url(simplex)
80 + "https://sdb.us-west-1.amazonaws.com"
63 81 ```
64 82
65 83 ## Responses
  @@ -80,7 +98,7 @@ A Simplex response (third element of the tuple above) has the following fields:
80 98 You can pattern match to determine how to handle the response:
81 99
82 100 ```elixir
83 - case Simplex.Domains.create "new_domain" do
101 + case Simplex.Domains.create(simplex, "new_domain") do
84 102 {:ok, result, response} ->
85 103 # some happy path stuff here
86 104 {:error, messages, response} ->
  @@ -93,21 +111,21 @@ You can pattern match to determine how to handle the response:
93 111 [Create](http://docs.aws.amazon.com/AmazonSimpleDB/latest/DeveloperGuide/SDB_API_CreateDomain.html) a new domain.
94 112
95 113 ````elixir
96 - Simplex.Domains.create "new_domain"
114 + Simplex.Domains.create(simplex, "new_domain")
97 115 ````
98 116
99 117 [List](http://docs.aws.amazon.com/AmazonSimpleDB/latest/DeveloperGuide/SDB_API_ListDomains.html) domains.
100 118
101 119 ````elixir
102 - Simplex.Domains.list
120 + Simplex.Domains.list(simplex)
103 121
104 - Simplex.Domains.list(%{"MaxNumberOfDomains" => "10", "NextToken" => "token-from-previous-list-response"})
122 + Simplex.Domains.list(simplex, %{"MaxNumberOfDomains" => "10", "NextToken" => "token-from-previous-list-response"})
105 123 ````
106 124
107 125 [Delete](http://docs.aws.amazon.com/AmazonSimpleDB/latest/DeveloperGuide/SDB_API_DeleteDomain.html) a domain.
108 126
109 127 ````elixir
110 - Simplex.Domains.delete "domain_to_delete"
128 + Simplex.Domains.delete(simplex, "domain_to_delete")
111 129 ````
112 130
113 131 ## Attributes
  @@ -115,9 +133,9 @@ You can pattern match to determine how to handle the response:
115 133 [Get](http://docs.aws.amazon.com/AmazonSimpleDB/latest/DeveloperGuide/SDB_API_GetAttributes.html) attributes of an item.
116 134
117 135 ````elixir
118 - Simplex.Attributes.get("your_domain", "your_item_name")
136 + Simplex.Attributes.get(simplex, "your_domain", "your_item_name")
119 137
120 - Simplex.Attributes.get("your_domain", "your_item_name", %{"AttributeName" => "some_attribute", "ConsistentRead" => "true"})
138 + Simplex.Attributes.get(simplex, "your_domain", "your_item_name", %{"AttributeName" => "some_attribute", "ConsistentRead" => "true"})
121 139 ````
122 140
123 141 [Put](http://docs.aws.amazon.com/AmazonSimpleDB/latest/DeveloperGuide/SDB_API_PutAttributes.html) attributes on an item (or create a new item).
  @@ -128,7 +146,8 @@ You can pattern match to determine how to handle the response:
128 146 # The replace tuple indicates that the value should replace the existing
129 147 # value for that attribute, rather than be added to its values
130 148
131 - Simplex.Attributes.put("your_domain",
149 + Simplex.Attributes.put(simplex,
150 + "your_domain",
132 151 "your_item_name",
133 152 %{"some_key" => "some_value",
134 153 "another_key" => ["a", "list", "of", "values"],
  @@ -137,7 +156,8 @@ You can pattern match to determine how to handle the response:
137 156
138 157 # put "some_value" in the "some_key" attribute only if
139 158 # "other_key" has the "other_value" value
140 - Simplex.Attributes.put("your_domain",
159 + Simplex.Attributes.put(simplex,
160 + "your_domain",
141 161 "your_item_name",
142 162 %{"some_key" => "some_value"},
143 163 %{"Name" => "other_key", "Value" => "other_value"})
  @@ -147,12 +167,14 @@ You can pattern match to determine how to handle the response:
147 167
148 168 ````elixir
149 169 # Delete the "some_value" value from the "some_key" attribute
150 - Simplex.Attributes.delete("your_domain",
170 + Simplex.Attributes.delete(simplex,
171 + "your_domain",
151 172 "your_item_name",
152 173 %{"some_key" => "some_value"})
153 174
154 175 # Delete "your_item_name" if it doesn't have the "some_key" attribute
155 - Simplex.Attributes.delete("your_domain",
176 + Simplex.Attributes.delete(simplex,
177 + "your_domain",
156 178 "your_item_name",
157 179 %{},
158 180 %{"Name" => "some_key", "Exists" => "false"})
  @@ -163,7 +185,7 @@ You can pattern match to determine how to handle the response:
163 185 [Select](http://docs.aws.amazon.com/AmazonSimpleDB/latest/DeveloperGuide/SDB_API_Select.html) attributes from items matching an expression.
164 186
165 187 ````elixir
166 - Simplex.Select.select("select * from your_domain where some_key = 'some_value'")
188 + Simplex.Select.select(simplex, "select * from your_domain where some_key = 'some_value'")
167 189 ````
168 190
169 191 ----
  @@ -1,5 +1,5 @@
1 1 ---
2 2 major: 0
3 - minor: 1
4 - patch: 2
3 + minor: 2
4 + patch: 1
5 5 pre:
  @@ -5,10 +5,9 @@
5 5 <<"An Elixir library for interacting with the Amazon SimpleDB API.">>}.
6 6 {<<"elixir">>,<<"~> 1.0.0">>}.
7 7 {<<"files">>,
8 - [<<"lib/simplex/attributes.ex">>,<<"lib/simplex/config.ex">>,
9 - <<"lib/simplex/domains.ex">>,<<"lib/simplex/parameters.ex">>,
10 - <<"lib/simplex/request.ex">>,<<"lib/simplex/response.ex">>,
11 - <<"lib/simplex/select.ex">>,<<"lib/simplex/supervisor.ex">>,
8 + [<<"lib/simplex/attributes.ex">>,<<"lib/simplex/domains.ex">>,
9 + <<"lib/simplex/parameters.ex">>,<<"lib/simplex/request.ex">>,
10 + <<"lib/simplex/response.ex">>,<<"lib/simplex/select.ex">>,
12 11 <<"lib/simplex.ex">>,<<"mix.exs">>,<<"README.md">>,<<"LICENSE.md">>,
13 12 <<"CHANGELOG.md">>,<<"VERSION.yml">>]}.
14 13 {<<"licenses">>,[<<"MIT">>]}.
  @@ -18,4 +17,4 @@
18 17 <<"poison">> => #{<<"optional">> => nil,<<"requirement">> => <<"~> 1.2.0">>},
19 18 <<"sweet_xml">> => #{<<"optional">> => nil,<<"requirement">> => <<"~> 0.1.1">>},
20 19 <<"timex">> => #{<<"optional">> => nil,<<"requirement">> => <<"~> 0.12.5">>}}}.
21 - {<<"version">>,<<"0.1.2">>}.
20 + {<<"version">>,<<"0.2.1">>}.
  @@ -1,33 +1,129 @@
1 1 defmodule Simplex do
2 - use Application
3 - alias Simplex.Config
2 + use GenServer
3 + use Timex
4 4
5 - def start(_type, _args) do
6 - Simplex.Supervisor.start_link
5 + def new(access_key \\ nil, secret_access_key \\ nil) do
6 + config = %{
7 + :aws_access_key => access_key || System.get_env("AWS_ACCESS_KEY"),
8 + :aws_secret_access_key => secret_access_key || System.get_env("AWS_SECRET_ACCESS_KEY"),
9 + :simpledb_url => System.get_env("SIMPLEDB_URL") || "https://sdb.amazonaws.com"
10 + }
11 +
12 + GenServer.start_link(__MODULE__, config, [])
7 13 end
8 14
9 - def aws_access_key do
10 - Config.aws_access_key || System.get_env("SIMPLEX_AWS_ACCESS_KEY")
15 + def aws_access_key(simplex) do
16 + GenServer.call(simplex, :get_aws_access_key)
11 17 end
12 18
13 - def aws_access_key(key) do
14 - Config.aws_access_key(key)
19 + def aws_access_key(simplex, access_key) do
20 + GenServer.call(simplex, {:set_aws_access_key, access_key})
15 21 end
16 22
17 - def aws_secret_access_key do
18 - Config.aws_secret_access_key || System.get_env("SIMPLEX_AWS_SECRET_ACCESS_KEY")
23 + def aws_secret_access_key(simplex) do
24 + GenServer.call(simplex, :get_aws_secret_access_key)
19 25 end
20 26
21 - def aws_secret_access_key(key) do
22 - Config.aws_secret_access_key(key)
27 + def aws_secret_access_key(simplex, secret_access_key) do
28 + GenServer.call(simplex, {:set_aws_secret_access_key, secret_access_key})
23 29 end
24 30
25 - def simpledb_url do
26 - Config.simpledb_url || System.get_env("SIMPLEX_SIMPLEDB_URL") || "https://sdb.amazonaws.com"
31 + def simpledb_url(simplex) do
32 + GenServer.call(simplex, :get_simpledb_url)
27 33 end
28 34
29 - def simpledb_url(url) do
30 - Config.simpledb_url(url)
35 + def simpledb_url(simplex, url) do
36 + GenServer.call(simplex, {:set_simpledb_url, url})
37 + end
38 +
39 + defp needs_refresh?(:aws_access_key, config) do
40 + expiring?(config) or !config[:aws_access_key]
41 + end
42 +
43 + defp needs_refresh?(:aws_secret_access_key, config) do
44 + expiring?(config) or !config[:aws_secret_access_key]
45 + end
46 +
47 + # keys expired or expiring within the next 60 seconds
48 + defp expiring?(%{:expires_at => nil}), do: false
49 + defp expiring?(%{:expires_at => expires_at}) do
50 + expires_at = DateFormat.parse!(expires_at, "{ISOz}")
51 + Date.shift(Date.now, secs: 60) > expires_at
52 + end
53 + defp expiring?(_config), do: false
54 +
55 + defp load_credentials_from_metadata do
56 + try do
57 + %HTTPoison.Response{:body => role_name} = HTTPoison.get("http://169.254.169.254/latest/meta-data/iam/security-credentials/", [], [timeout: 500])
58 + %HTTPoison.Response{:body => body} = HTTPoison.get("http://169.254.169.254/latest/meta-data/iam/security-credentials/#{role_name}", [], [timeout: 500])
59 + Poison.decode!(body)
60 + rescue
61 + _ ->
62 + %{}
63 + end
64 + end
65 +
66 + defp refresh(config) do
67 + credentials_from_metadata = load_credentials_from_metadata
68 + update = %{
69 + :aws_access_key => credentials_from_metadata["AccessKeyId"],
70 + :aws_secret_access_key => credentials_from_metadata["SecretAccessKey"],
71 + :expires_at => credentials_from_metadata["Expiration"]
72 + }
73 + Map.merge(config, update)
74 + end
75 +
76 +
77 + ###################
78 + # Server Callbacks
79 +
80 + def init(config) do
81 + {:ok, config}
82 + end
83 +
84 + def handle_call(:get_aws_access_key, _from, config) do
85 + if needs_refresh?(:aws_access_key, config) do
86 + config = refresh(config)
87 + {:reply, config[:aws_access_key], config}
88 + else
89 + {:reply, config[:aws_access_key], config}
90 + end
91 + end
92 +
93 + def handle_call(:get_aws_secret_access_key, _from, config) do
94 + if needs_refresh?(:aws_secret_access_key, config) do
95 + config = refresh(config)
96 + {:reply, config[:aws_secret_access_key], config}
97 + else
98 + {:reply, config[:aws_secret_access_key], config}
99 + end
100 + end
101 +
102 + def handle_call(:get_simpledb_url, _from, config) do
103 + {:reply, config[:simpledb_url], config}
104 + end
105 +
106 + def handle_call({:set_aws_access_key, access_key}, _from, config) do
107 + config = config
108 + |> Map.put(:aws_access_key, access_key)
109 + |> Map.delete(:expires_at)
110 + {:reply, config[:aws_access_key], config}
111 + end
112 +
113 + def handle_call({:set_aws_secret_access_key, secret_access_key}, _from, config) do
114 + config = config
115 + |> Map.put(:aws_secret_access_key, secret_access_key)
116 + |> Map.delete(:expires_at)
117 + {:reply, config[:aws_secret_access_key], config}
118 + end
119 +
120 + def handle_call({:set_simpledb_url, url}, _from, config) do
121 + config = Map.put(config, :simpledb_url, url)
122 + {:reply, config[:simpledb_url], config}
123 + end
124 +
125 + def handle_info(_msg, config) do
126 + {:noreply, config}
31 127 end
32 128
33 129 end
Loading more files…