Current section
14 Versions
Jump to
Current section
14 Versions
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…