Packages

Fetch, cache, and refresh AWS IAM Credentials

Current section

Files

Jump to
ex_aws_metadata lib aws_metadata.ex
Raw

lib/aws_metadata.ex

defmodule AWSMetadata do
@moduledoc """
Public API. Coordinates fetching and caching credentials, as well as refreshing
the credentials based upon the expiration time passed along.
"""
use GenServer
require Logger
defstruct [:client, :metadata_client]
@alert_before_expiry -5
@retry_delay 5_000
def get_client do
GenServer.call(__MODULE__, :get_client)
end
def start_link(args) do
GenServer.start_link(__MODULE__, args, name: __MODULE__)
end
def init(args) do
state = %AWSMetadata{
metadata_client: Keyword.get(args, :metadata_client, AWSMetadata.Client)
}
{:ok, client} = fetch_client(state.metadata_client)
{:ok, %{state | client: client}}
end
def handle_call(:get_client, _from, %{client: client} = state) do
{:reply, client, state}
end
def handle_info(:refresh_client, %{metadata_client: metadata_client} = state) do
{:ok, client} = fetch_client(metadata_client)
{:noreply, %{state | client: client}}
end
defp fetch_client(metadata_client) do
{:ok, client, expiration_time} = metadata_client.fetch()
setup_update_callback(expiration_time)
{:ok, client}
rescue
e ->
Logger.info(":ex_aws_metadata ignoring exception fetching client: #{inspect(e)}")
Logger.info(inspect(System.stacktrace, pretty: true))
setup_retry_callback()
{:ok, nil}
end
defp setup_update_callback(expiration_time) do
exp_time = Timex.shift(expiration_time, minutes: @alert_before_expiry)
refresh_ms = Timex.diff(exp_time, DateTime.utc_now, :milliseconds)
Process.send_after(__MODULE__, :refresh_client, refresh_ms)
end
defp setup_retry_callback do
Process.send_after(__MODULE__, :refresh_client, @retry_delay)
end
end