3

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 ?

john
  • 1,057
  • 1
  • 17
  • 28

2 Answers2

4

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
Paweł Obrok
  • 22,568
  • 8
  • 74
  • 70
  • *Attributes are evaluated during compilation* -- Wouldn't that only cause a problem if the op ran the app on a system different from the system on which the app was compiled? In other words, the op claimed that the env variable was set before compile time, then the app was compiled. Therefore the env variable baked into the app at compile time should be available at run time, yet the op gets `nil`. Even if the op ran the app on a different system than the system on which the app was compiled, the op should still get `1234` as the value of the environment variable.... – 7stud Jun 27 '19 at 18:40
  • ...It's just that the value `1234` won't be the value of the env variable on the system on which the app was run. – 7stud Jun 27 '19 at 18:44
  • Well, I was just getting ready to write a function like that, and I looked at your answer again. I have searched hi and lo, and articles like this: https://www.amberbit.com/blog/2018/9/27/elixir-runtime-vs-compile-time-configuration/ make it seem like phoenix has a native function like yours. Nowhere, that I can find, does it say that when you use ***system tuples*** in your config that you need to write your own function for detecting `:system` in the `value` for a `key`. Everything I've read makes it seem like if you use a system tuple, then certain applications will.... – 7stud Jun 28 '19 at 03:39
  • ...automatically look up the env variable for you at runtime. I finally concluded that `Application.get_env()` is just a dumb lookup function, and it doesn't care whether you use `:system` or anything else as a value for a key, and it's the application, like phoenix, that has to handle :system--if it wants to. – 7stud Jun 28 '19 at 03:42
0

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.

7stud
  • 46,922
  • 14
  • 101
  • 127