gabrielpra

gabrielpra

Import runtime config from apps in umbrella app root

Our umbrella application had two releases with no overlapping apps: Our “main release” and our “importer release”. By providing runtime_config_path in the releases configuration, we created one runtime.exs file for each of these releases and everything worked perfectly.

Then, we added one common app to both releases, which seems to be a valid use case: mix release — Mix v1.11.3. The problem we are facing now is how to configure the same app in the 2 releases without duplicating configuration across the 2 runtime.exs files, since that could very easily lead to bugs.

Our initial idea was to create a runtime.exs config file inside the common app’s config directory and import it in the 2 root runtime configuration files. However, the mix release documentation says: "It MUST NOT import any other configuration file via import_config". Trying to understand why is that, we found this comment from @josevalim, explaining that this is discouraged because the other config files are not copied to the release by default. Having understood the rule, we thought we were ready to break it, so we copied the files manually by adding an extra step in the release process and imported it in our runtime.exs.

However, since v1.11, the “MUST NOT” rule is enforced by code: elixir/config.ex at master · elixir-lang/elixir · GitHub, so it seems that the only way to configure this common app is by duplicating its configuration across both root runtime.exs files. Is that really the recommended approach?

Has anyone faced this problem and solved it in a different way? I do believe that there should be a way to solve this problem without duplication.

Marked As Solved

josevalim

josevalim

Creator of Elixir

Keep all of the shared configuration in config/runtime.exs. Then for each node, add their own specific configuration via config providers:

https://hexdocs.pm/elixir/Config.Reader.html#module-as-a-provider

You can often keep the extra configurations in the overlay directory and then point to them accordingly.

Also Liked

gabrielpra

gabrielpra

Sorry! I ended up forgetting to report back, but the solution was what @josevalim suggested (thank you, by the way!). We now have one base runtime.exs in the umbrella root, as well as one runtime.exs for each app that needs custom runtime configuration.

The code in mix.exs looks like this, with some explanation added:

  defp releases do
    [
      release_1: [
        version: "1.0.0",
        applications: [
          app_1: :permanent,
          app_2: :permanent,
          app_3: :permanent,
          app_4: :permanent, # assuming this app doesn't have runtime config
        ],
        include_executables_for: [:unix],
        config_providers: config_providers_for_apps([
          :app_1,
          :app_2,
          :app_3,
        ]),
        steps: [:assemble, &copy_configs/1],
      ],
      release_2: [
        version: "0.0.1",
        applications: [
          app_1: :permanent,
          app_2: :permanent,
          app_5: :permanent,
        ],
        include_executables_for: [:unix],
        runtime_config_path: "apps/app_5/config/runtime.exs", # In this case the base runtime is not the one from root, but the one in app_5
        config_providers: config_providers_for_apps([
          :app_1,
          :app_2,
        ]),
        steps: [:assemble, &copy_configs/1],
      ]
    ]
  end

  # Add the runtime.exs configuration for each provided app
  defp config_providers_for_apps(apps) do
    for app <- apps do
      {Config.Reader, path: {:system, "RELEASE_ROOT", "/apps/#{app}/config/runtime.exs"}, env: Mix.env()}
    end
  end

  # When assembling the release, we copy all the runtime.exs files defined in `config_providers` to it, keeping the relative app path to avoid collisions.
  defp copy_configs(%Mix.Release{path: release_directory_path, config_providers: config_providers} = release) do
    for {_module, path: {_context, _root, config_file_path}, env: _} <- config_providers do
      config_directory = Path.join(release_directory_path, Path.dirname(config_file_path))

      # Clean the config directory to make sure we are only including the files defined in the config_providers
      File.rm_rf!(config_directory)
      File.mkdir_p!(config_directory)

      File.cp!(Path.relative(config_file_path), Path.join(config_directory, Path.basename(config_file_path)))
    end

    release
  end

Does that help you @dylan-chong ?

josevalim

josevalim

Creator of Elixir

Elixir has long moved away from configuration per application because it is confusing and error prone.

Regarding the original inquiry in this thread, note it is also possible to set up a base configuration and then have each individual release override only the parts it needs. This is preferred to having each configuration scattered around per application:

https://hexdocs.pm/elixir/1.12/Config.Provider.html#module-multiple-config-files

dylan-chong

dylan-chong

Thank you for the comments @josevalim and @gabrielpra! That was indeed very helpful

Where Next?

Popular in Questions Top

Tee
can someone please explain to me how Enum.reduce works with maps
New
lessless
I believe there are people here who are dealing with CSV files import on the daily basis, and since Excel is a really popular tool there ...
New
ycv005
I have followed this StackOverflow post to install the specific version of Erlang. And When I am running mix ecto.setup then getting fol...
New
freewebwithme
Using vs code and installed ElixirLS: support and debugger. And I got an error popped up on start up says Failed to run ‘elixir’ comma...
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
mathew4509
I have a list say x = ["23gh", "56kh", "97mh"] I would like to pass each element to Val in each iteration. Say, in iteration 1 -------...
New
WestKeys
Currently suffering from paralysis by [HTTP client] analysis. This is rather unusual in Elixirland as there tends to be consensus on the ...
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID&lt;0.412.0&gt; terminating ** (Postgrex.Error) FATAL...
New
jay1
Why is it that the mnesia database isn’t the most preferred database for use in Elixir/Phoenix?
New
idi527
I’ve been re-reading swift book again and noticed that multiline strings there don’t have a trailing line break, unlike in elixir iex(2)...
New

Other popular topics Top

yurko
Here are few pieces of (common) Linux knowledge that we use for reasonably small one server apps. We use Ubuntu but this should work for ...
New
Tee
can someone please explain to me how Enum.reduce works with maps
New
chrismccord
This release brings a number of exciting features, including integration with the new Phoenix LiveDashboard and Phoenix LiveView. There h...
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
vac
Hi, I'm quite new in Elixir and I'm trying to format a string to a PEM format. I have the certificate value like MIIDBTCCAe2...... and ...
New
freewebwithme
Using vs code and installed ElixirLS: support and debugger. And I got an error popped up on start up says Failed to run ‘elixir’ comma...
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
msaraiva
Surface is an experimental library built on top of Phoenix LiveView and its new LiveComponent API that aims to provide a more declarative...
564 42633 214
New
ovidiubadita
Hey all, I discovered Elixir and I love it. I always wanted to learn a functional programming and I intended to go for Haskell, but afte...
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

We're in Beta

About us Mission Statement