Elixir Phoenix not loading env variable at runtime

Viewed 2643

I am trying to load an API key as a system env from my mac when starting up the phoenix server. What am I getting wrong? these are my steps:

  1. On my mac terminal:

    export API_NOTIFICATION_KEY=1234
    
  2. in my config.exs

    config :app, App.Notifications,
    notification_api_key: {:system, "API_NOTIFICATION_KEY"}
    
  3. in my module where I use it

    @api_notification_key Application.get_env(:app, App.Notifications)[:notification_api_key]
    
  4. start my phoenix server

    mix phx.server
    

And then When I try to make the API call it is showing as nil. Is there a step I am missing to get it properly loaded ?

2 Answers

Attributes are evaluated during compilation, so:

@api_notification_key Application.get_env(:app, App.Notifications)[:notification_api_key]

will have its value set at compile time. I assume that's not what you want, so you'll be better off using a function:

defp api_notification_key() do
  case Application.get_env(:test, App.Notifications)[:notification_api_key] do
    {:system, var_name} -> System.get_env(var_name)
    value -> value
  end
end

When I use your configuration, then try to access the key in a controller with:

Application.get_env(:app, App.Notifications)[:notification_api_key]

I get:

{:system, "API_NOTIFICATION_KEY"}

so phoenix is not looking up the env variable. On the other hand, if I manually set the key in the config, like this:

config :app, App.Notifications,
    notification_api_key: 1234

then I get 1234 in the controller. I read something that said reading environment variables with {:system, "ENV_VAR"} was going to be deprecated at some point, and I don't see any mention of :system in the docs. I'm using phoenix 1.4.6.

According to this article, you can define an init/2 function in endpoint.ex, which will be called at run time--just before your phoenix app starts--which means you can directly look up environment variables at run time with System.get_env/1. The init/2 function is described in the Phoenix.Endpoint docs:

Dynamic configuration

For dynamically configuring the endpoint, such as loading data from environment variables or configuration files, Phoenix invokes the init/2 callback on the endpoint, passing a :supervisor atom as first argument and the endpoint configuration as second.

Here's an example that I came up with:

  def init(:supervisor, config) do
    #IO.inspect config, label: "config"
    #IO.inspect Application.get_all_env(:app1), label: "all"

    Application.put_env(:app1, :notification_api_key, 
        System.get_env("API_NOTIFICATION_KEY"),
        persistent: true
    )

    {:ok, config}
  end

Then in my controller:

key = Application.get_env(
        :app1, App1.Notifications 
      )[:notification_api_key]

IO.inspect key, label: "key" 

And, in the server window I see:

key: 1234

You should also realize that setting module attributes happens at compile time, so this line:

@api_notification_key Application.get_env(:app, App.Notifications)[:notification_api_key]

retrieves the env variable for the system on which the app is compiled.

I also tried setting a module attribute in my controller:

@attr Application.get_env(
        :app1, App1.Notifications 
      )[:notification_api_key]

and init/2 defined in endpoint.ex as above, and in my controller @attr was nil--as expected. That's because at compile time, init/2 hasn't been called yet, so no value for :notification_api_key has been set.

To me, it doesn't feel right calling Application.put_env() in init/2:

  def init(:supervisor, config) do

    Application.put_env(:app1, :notification_api_key, 
        System.get_env("API_NOTIFICATION_KEY"),
        persistent: true
    )

    {:ok, config}
  end

I think I should be doing something to config. Here's another example:

  def init(:supervisor, config) do

    IO.inspect config, label: "config"

    key = System.get_env("API_NOTIFICATION_KEY")
    new_config = Keyword.put_new(config, :notification_api_key, key)

    IO.inspect new_config, label: "inside init/2: new_config"

    {:ok, new_config}
  end

Then in my controller:

IO.inspect(
  App1Web.Endpoint.config(:notification_api_key, :not_set), 
  label: "config"
)

In my server window, I see:

config: "1234"

so that works, too.

Related