Importing custom functions for use in config.exs/releases.exs/runtime.exs

I have a complex app configuration that I’ve made much simpler via a custom module. I don’t want to implement it as a config provider because I have to use it during development as well. But trying to access it from config.exs leads to a bit of a chicken-and-egg situation because config.exs is loaded before the app is compiled.

I’ve tricked Elixir into making this work in both mix mode and release mode, but it’s really hacky and I kind of hate it. I’d love to know if there’s a better way to accomplish what I’m doing. Here’s how it goes:

lib/my_configurator.ex

Define the module if it’s not already defined.

if !function_exported?(:MyConfigurator, :__info__, 1) do
  defmodule MyConfigurator do
    def prefix, do: System.get_env("CONFIG_PREFIX", "")
    def prefix(str), do: prefix() <> "-" <> str
    ...
  end
end

mix.exs

At the top of mix.exs I added

Code.require_file("lib/my_configurator.ex")

Yeah. Like I said. Ugly. Anyway, All of that makes MyConfigurator available from within config.exs and all of the files it loads via import_config when run via mix. And it’s available from releases.exs in the release by virtue of having been compiled from lib. But you can see why I’d appreciate a more elegant solution.

Is there one?

You shouldn’t really read env vars from config.exs, because the var will be read when the code is compiled. Then if you change the var, the code won’t be recompiled, and it will behave as before.

My suggestion is then to move all runtime configuration (such as env vars) to runtime.exs, where your code already works as expected!

I guess my example was overly simplified – the environment variable is a stand-in for reading data from AWS Secrets Manager. I’m actually using the Hush package to read configuration from a few different sources (including Secrets Manager), but I have some things that need to be configured very differently in dev and test than in the release. I tried consolidating everything into runtime.exs, but it became even more of a mess than what I ended up with. :slight_smile:

My custom module eliminates a lot of repetition and creates the Hush tuples in a way that my team and I find much more maintainable than the {:hush, SystemEnvironment, "PORT", [cast: :integer]} format that Hush uses internally.