From Elixir Mix configuration to release configuration - Alchemy 101 Part 2

2017-01-19 by Thomas Hutchinson

This is Part 2 in our Alchemy 101 series. Catch up on Part 1: Elixir Module Attributes and Part 3: Fault Tolerance Doesn’t Come Out Of The Box.

Today we will be looking at what happens to your Mix configuration when you perform a release. Take a look at the Set up section and then proceed to Application Configuration.

Set up

You can follow along with the examples. You will require elixir and to perform the following steps.

First create a new mix project.

mix new my_app --module Twitter cd my_app

Next add distillery (for creating releases) to mix.exs as a dependency.

defp deps do [{:distillery, "~> 0.10.1"}] end

Then download distillery and create the release configuration.

mix deps.get mix release.init

The rest of the blog assumes that you are in the my_app directory.

Application Configuration

When creating an Elixir OTP Application you will most probably need some configuration. There are 4 ways to supply application configuration on startup.

1. In your mix.exs file. Here you can specify default application environment variables. This file is used to generate the .app file which is used to start your application. Run ‘mix help compile.app’ for more information

2. In your config/config.exs file, this compiles down to sys.config. Alternatively with distillery you can supply your own sys.config file.

3. With an additional .config file. From what I can see distillery and exrm don’t seem to support this out the box. You can find out more here on how to use it.

4. You can supply it to the Erlang VM when it starts up. Distillery supports this via erl_opts in rel/config.exs. Simply add “-Application Key Value” to it for each application configuration variable e.g. set erl_opts: “-my_app magic_number 42”.

From what I have seen most people tend to go with the option 2, supplying configuration via config/config.exs. As configuration is an Elixir script it gives us the potential to be very creative. When creating a release (with exrm or distillery) config.exs (by default) is evaluated and the result is written to rel/$app/releases/$version/sys.config which is picked up by your application on startup. Not knowing this can lead to confusion. Here comes another example where this can happen.

Open lib/twitter_client.ex and add the following to it.

defmodule Twitter do require Logger def log_twitter_url do url = Application.get_env(:my_app, :twitter_url) Logger.info("Using #{url}") end end

Now add the following to config/config.exs.

config :my_app, twitter_url: System.get_env("TWITTER_URL")

Pretty nice eh? It appears like we can get TWITTER_URL at runtime. Lets create the release and inspect it. Run the following.

export TWITTER_URL="https://api.mock.com" MIX_ENV=prod mix release ./rel/my_app/bin/my_app console iex(my_app@127.0.0.1)1> Twitter.log_twitter_url() 17:26:05.270 [info] Using https://api.mock.twitter.com

Perfect! Everything looks good, the url to the mock is being used. Just what I want during development. Now I want to test the integration with the real twitter API, time to change TWITTER_URL.

export TWITTER_URL="https://api.twitter.com/1.1"

Start your release in the console and invoke Twitter.log_twitter_url/0.

./rel/my_app/bin/my_app console iex(my_app@127.0.0.1)1> Twitter.log_twitter_url() 17:26:05.270 [info] Using https://api.mock.com

Strange! It is still using the mock url, but why? As mentioned before when creating a release the configuration is evaluated and written to sys.config. Lets take a look.

cat rel/my_app/releases/0.1.0/sys.config [{sasl,[{errlog_type,error}]}, {my_app,[{twitter_url,<<"https://api.mock.com">>}]}].

As you can see twitter_url is “https://api.mock.com”. When the release is being created config.exs is evaluated and the results are placed in sys.config. Part of this involved executing System.get_env(“TWITTER_URL”) and having “https://api.mock.com” returned.

This doesn’t have to be a problem though as you can set Application configuration at runtime via Application.put_env/3. Using this you could create a function that reads the OS environmental variable and adds it to the application’s configuration.

def os_env_config_to_app_env_config do twitter_url = System.get_env("TWITTER_URL") Application.put_env(:my_app, :twitter_url, twitter_url) :ok end

Note that this function would have to be called before any initialisation in your application takes place. I’m sure there are other ways to handle this scenario, if so feel free to mention them in the comments section.

Hope you enjoyed reading, tune in next time where I’ll be talking about what it means to be fault tolerant.

This is Part 2 in our Alchemy 101 series. Catch up on Part 1: Elixir Module Attributes and Part 3: Fault Tolerance Doesn’t Come Out Of The Box.