arcanemachine

arcanemachine

Can I use `Code.eval_file/1` to dynamically import runtime config?

Preamble: I am aware of other methods of dynamic runtime configuration (e.g. Config.Provider), and am currently a happy user of Dotenvy. The title of this thread is not “How to dynamically configure runtime config?”, but is specifically pertains to the usage of Code.eval_file/1 as a strategy to do so.

I am experimenting with different strategies to dynamically import runtime config, similar to how Config.import_config/1 works for compile-time config.

I have come up with a solution that appears to work. I am bringing the topic here for discussion, to see if there are any valid technical reasons why I should not use this strategy.

The strategy

Here is an example config/runtime.exs that uses Code.eval_file/1 to import a local runtime config file, if one exists:

config/runtime.exs

import Config

config :my_project, :hello, :world

# Maybe import local runtime config file(s)
for local_runtime_config_file_name <-
    ["runtime.local.exs", "runtime.#{config_env()}.local.exs"] do
  local_runtime_config_file_path = Path.join(__DIR__, local_runtime_config_file_name)

  if File.exists?(local_runtime_config_file_path) do
    IO.puts("Importing local runtime config from `#{local_runtime_config_file_path}`...")

    Code.eval_file(local_runtime_config_file_path)
  end
end

This allows one or more (presumably gitignored) local runtime config files to be added, without having to modify the main runtime config file:

config/runtime.local.exs

import Config

config :my_project, :hello, :another_world

The above example shows how a local runtime config file could be loaded for 1) all configuration environments, and/or 2) a specific configuration environment (e.g. :dev, :test, etc.), and supports cascading (i.e. the global runtime.local.exs would be overridden by e.g. runtime.dev.local.exs). If any of the local files do not exist, then that is fine also, and no custom config would be loaded.


I have this running, and it appears to work. But I haven’t seen this strategy documented anywhere, so I’m a little wary of using it.

Does this appear to be a viable strategy, or is there some hidden footgun here that I am not aware of?

Most Liked

LostKobrakai

LostKobrakai

Instead of answering the question I’ll answer why this isn’t a feature in the first place:

Runtime config needs to be copied into releases, so the config can be executed when the application boots. That copying means mix needs to know which files to copy in the first place statically – it cannot run the config file to figure it out, because conditions might change. Therefore to keep things easy elixir limits runtime config to a single file by default, which has to exist with a known name at a known location. All this happens under the premise that a release once assembled can be copied as is and is startable.

Now you can manually make sure additional files are also copied into the release and then you could read them. It would retain all the properties elixir/mix tries to uphold from it’s pov knowling less details about your project. I’ll leave the judgement if that’s a good idea to yourself though.

LostKobrakai

LostKobrakai

Personally I skip file in the equation. All the things that need to be set from the outside at runtime need to come in as system env variables. runtime.exs reads those. But elixir/mix/releases are not at all involved at setting those variables. For that I tend to use direnv locally / in development and whatever tool my deployment target provides to set environment variables. Those may or may not be able to source from files committed to the repo itself - e.g. .envrc for direnv can be commited, but a systemd unit might live elsewhere – so first and foremost I want to provide documentation (and good errors) around the set of expected env variables, rather than trying to prescribe how downstream people have to provide values. The runtime.exs essentially becomes a mapping from system env input to elixir config values for the minimal set of what should be configurable at runtime.

mudasobwa

mudasobwa

Creator of Cure

I might be missing something, but what would be wrong with plain old good Config.import_config/2?

for config <- ["runtime.local.exs", "runtime.#{config_env()}.local.exs"] |> Path.expand(__DIR__) |> Path.wildcard() do
  if File.exists?(config), do: import_config(config)
end
tfwright

tfwright

I infer from your preable that you are specifically looking for technical answers, but since you mention this purpose I am curious why in such a situation you wouldn’t just use a single default “example” file and modify/copy it to the “real location” as needed, which I think is a pretty standard way to handle this kind of thing?

I suspect any real answer to your question would benefit from similar details, because answers to a question like “can I” generally come with a lot of “depends”…

arcanemachine

arcanemachine

I thought that didn’t work for runtime config?

I need to look into this again…

EDIT: Got this error: ** (RuntimeError) import_config/1 is not enabled for this configuration file.

Where Next?

Popular in Questions Top

itssasanka
Hi all, Trying to get some more clarity over utc_datetime and naive_datetime for Ecto: https://hexdocs.pm/ecto/Ecto.Schema.html#module-...
New
New
quazar
How to set Jason to encode all fields in ecto schema, I don’t care about security and implementing only is taking long list of attributes...
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New
mgjohns61585
Could someone help me? I'm making my first elixir program, number guessing game. I can't figure out how to convert the user's guess from ...
New
minhajuddin
I have seen a lot of code which picks the first element from a list using Enum.at(0) instead of List.first. Is there a reason why people ...
New
chensan
I have a User schema with a :from_id field set to type :string: defmodule TweetBot.Repo.Migrations.CreateUsers do use Ecto.Migration ...
New
Fl4m3Ph03n1x
Background Let’s assume I have a typical GenServer that receives messages as requests, does some operation in a DB and returns responses....
New
belgoros
I’m not a pro in using Regex and can’t figure out why the following behaviour happens, especially if we take into account the difference ...
New
vrod
I am using the Starship cross-shell prompt – it seems pretty nice, but I get some errors: [WARN] - (starship::utils): Executing command ...
New

Other popular topics Top

peerreynders
Manning 2016 Halloween weekend sale via Deal of the Day Friday, October 28 - Half off all MEAPs - code WM102816LT Saturday, October 29 ...
326 29600 154
New
hariharasudhan94
I would like to know what is the best IDE for elixir development?
New
script
If I have a string “1000 cfu/ml” . I want to remove the characters and / and space . So the string is like this "1000" What is the ...
New
chrismccord
As promised, the first release candidate of Phoenix 1.3.0 is out! This release focuses on code generators with improved project structure...
New
aadeshere1
I have a another noob question about loop. Since elixir is immutable, while loop is not directly possible. total = 10 while total != 0 ...
New
baxterw3b
Hi guys, i’m new in the Elixir world, and i have to say, that i love it! i’m having some problem to understand anonymous functions with ...
New
TunkShif
This post is an instruction guide to help you setup your Neovim for Elixir development from scratch. It includes general information on h...
273 38985 115
New
AstonJ
We’ve put together this wiki for Phoenix LiveView - please feel free to add any info you feel is worth including. What is Phoenix LiveV...
New
aesmail
Hello guys, I have finally made it. I created an admin interface for a framework. It’s been on my todo list for years and with the curre...
New
Nvim
Elixir appears to be a superior language to Python. I don’t see any advantage of Python over Elixir. Are there any?
New

We're in Beta

About us Mission Statement