diff options
author | José Valim <jose.valim@plataformatec.com.br> | 2019-12-30 14:05:55 +0100 |
---|---|---|
committer | José Valim <jose.valim@plataformatec.com.br> | 2019-12-30 14:06:05 +0100 |
commit | 7f86ea33346feca490c5f4df9922dd475a73fc73 (patch) | |
tree | 243527ea8512ac933dd6e5991acf7a92a2acb947 | |
parent | a54e90147996f402777ef740b31b83e12257a218 (diff) | |
download | elixir-7f86ea33346feca490c5f4df9922dd475a73fc73.tar.gz |
Allow reboot to be disabled in releases, closes #9552
-rw-r--r-- | lib/elixir/lib/config/provider.ex | 45 | ||||
-rw-r--r-- | lib/elixir/test/elixir/config/provider_test.exs | 43 | ||||
-rw-r--r-- | lib/elixir/test/elixir/fixtures/configs/kernel.exs | 3 | ||||
-rw-r--r-- | lib/mix/lib/mix/release.ex | 26 | ||||
-rw-r--r-- | lib/mix/lib/mix/tasks/release.ex | 9 | ||||
-rw-r--r-- | lib/mix/test/mix/release_test.exs | 12 |
6 files changed, 117 insertions, 21 deletions
diff --git a/lib/elixir/lib/config/provider.ex b/lib/elixir/lib/config/provider.ex index 8f9433a3b..4c97976a4 100644 --- a/lib/elixir/lib/config/provider.ex +++ b/lib/elixir/lib/config/provider.ex @@ -105,7 +105,13 @@ defmodule Config.Provider do @callback load(config, state) :: config @doc false - defstruct [:providers, :config_path, extra_config: [], prune_after_boot: false] + defstruct [ + :providers, + :config_path, + extra_config: [], + prune_after_boot: false, + reboot_after_config: true + ] @doc """ Validates a `t:config_path/0`. @@ -163,13 +169,40 @@ defmodule Config.Provider do {:ok, %Config.Provider{} = provider} -> path = resolve_config_path!(provider.config_path) validate_no_cyclic_boot!(path) + loaded_applications = :application.loaded_applications() + original_config = read_config!(path) - read_config!(path) - |> Config.__merge__([{app, [{key, booted_key(provider, path)}]} | provider.extra_config]) - |> run_providers(provider) - |> write_config!(path) + config = + original_config + |> Config.__merge__(provider.extra_config) + |> run_providers(provider) - restart_fun.() + if provider.reboot_after_config do + config + |> Config.__merge__([{app, [{key, booted_key(provider, path)}]}]) + |> write_config!(path) + + restart_fun.() + else + for {app, _, _} <- loaded_applications, config[app] != original_config[app] do + abort(""" + Cannot configure #{inspect(app)} because :reboot_after_config has been set \ + to false and #{inspect(app)} has already been loaded, meaning any further \ + configuration won't have an effect. + + The configuration for #{inspect(app)} before config providers was: + + #{inspect(original_config[app])} + + The configuration for #{inspect(app)} after config providers was: + + #{inspect(config[app])} + """) + end + + _ = Application.put_all_env(config, persistent: true) + :ok + end {:ok, {:booted, path}} -> File.rm(path) diff --git a/lib/elixir/test/elixir/config/provider_test.exs b/lib/elixir/test/elixir/config/provider_test.exs index 1b6a7ec71..72cda6f5a 100644 --- a/lib/elixir/test/elixir/config/provider_test.exs +++ b/lib/elixir/test/elixir/config/provider_test.exs @@ -9,21 +9,18 @@ defmodule Config.ProviderTest do import ExUnit.CaptureIO @tmp_path tmp_path("config_provider") - @env_var "ELIXIR_CONFIG_PROVIDER_PATH" + @env_var "ELIXIR_CONFIG_PROVIDER_BOOTED" @config_app :config_app @sys_config Path.join(@tmp_path, "sys.config") setup context do - System.put_env(@env_var, @tmp_path) - File.rm_rf(@tmp_path) File.mkdir_p!(@tmp_path) - File.write!(@sys_config, :io_lib.format("~tw.~n", [context[:sys_config] || []]), [:utf8]) + write_sys_config!(context[:sys_config] || []) on_exit(fn -> Application.delete_env(@config_app, :config_providers) System.delete_env(@env_var) - System.delete_env("ELIXIR_CONFIG_PROVIDER_BOOTED") end) end @@ -65,8 +62,15 @@ defmodule Config.ProviderTest do end test "resolve!" do - assert Provider.resolve_config_path!("/foo") == "/foo" - assert Provider.resolve_config_path!({:system, @env_var, "/bar"}) == @tmp_path <> "/bar" + env_var = "ELIXIR_CONFIG_PROVIDER_PATH" + + try do + System.put_env(env_var, @tmp_path) + assert Provider.resolve_config_path!("/foo") == "/foo" + assert Provider.resolve_config_path!({:system, env_var, "/bar"}) == @tmp_path <> "/bar" + after + System.delete_env(env_var) + end end end @@ -116,6 +120,27 @@ defmodule Config.ProviderTest do init_and_assert_boot() end) =~ "Got infinite loop when running Config.Provider" end + + test "returns without rebooting" do + reader = {Config.Reader, fixture_path("configs/kernel.exs")} + init = Config.Provider.init([reader], @sys_config, reboot_after_config: false) + Application.put_env(@config_app, :config_providers, init) + + assert capture_abort(fn -> + Provider.boot(@config_app, :config_providers, fn -> + raise "should not be called" + end) + end) =~ "Cannot configure :kernel because :reboot_after_config has been set to false" + + # Make sure values before and after match + write_sys_config!(kernel: [elixir_reboot: true]) + Application.put_env(@config_app, :config_providers, init) + System.delete_env(@env_var) + + Provider.boot(@config_app, :config_providers, fn -> raise "should not be called" end) + assert Application.get_env(:kernel, :elixir_reboot) == true + assert Application.get_env(:elixir_reboot, :key) == :value + end end defp init(opts) do @@ -145,4 +170,8 @@ defmodule Config.ProviderTest do assert_raise ErlangError, fun end) end + + defp write_sys_config!(data) do + File.write!(@sys_config, :io_lib.format("~tw.~n", [data]), [:utf8]) + end end diff --git a/lib/elixir/test/elixir/fixtures/configs/kernel.exs b/lib/elixir/test/elixir/fixtures/configs/kernel.exs new file mode 100644 index 000000000..5020372a5 --- /dev/null +++ b/lib/elixir/test/elixir/fixtures/configs/kernel.exs @@ -0,0 +1,3 @@ +import Config +config :kernel, :elixir_reboot, true +config :elixir_reboot, :key, :value diff --git a/lib/mix/lib/mix/release.ex b/lib/mix/lib/mix/release.ex index cb6e46c70..3966f137b 100644 --- a/lib/mix/lib/mix/release.ex +++ b/lib/mix/lib/mix/release.ex @@ -381,6 +381,7 @@ defmodule Mix.Release do It uses the following release options to customize its behaviour: + * `:reboot_system_after_config` * `:start_distribution_during_config` * `:prune_runtime_sys_config_after_boot` @@ -389,10 +390,12 @@ defmodule Mix.Release do @spec make_sys_config(t, keyword(), Config.Provider.config_path()) :: :ok | {:error, String.t()} def make_sys_config(release, sys_config, config_provider_path) do - {sys_config, runtime?} = merge_provider_config(release, sys_config, config_provider_path) + {sys_config, runtime_config?} = + merge_provider_config(release, sys_config, config_provider_path) + path = Path.join(release.version_path, "sys.config") - args = [runtime?, sys_config] + args = [runtime_config?, sys_config] format = "%% coding: utf-8~n%% RUNTIME_CONFIG=~s~n~tw.~n" File.mkdir_p!(Path.dirname(path)) File.write!(path, :io_lib.format(format, args), [:utf8]) @@ -412,18 +415,27 @@ defmodule Mix.Release do defp merge_provider_config(%{config_providers: []}, sys_config, _), do: {sys_config, false} defp merge_provider_config(release, sys_config, config_path) do - {extra_config, initial_config} = start_distribution(release) + {reboot?, extra_config, initial_config} = start_distribution(release) prune_after_boot = Keyword.get(release.options, :prune_runtime_sys_config_after_boot, false) - opts = [extra_config: initial_config, prune_after_boot: prune_after_boot] + + opts = [ + extra_config: initial_config, + prune_after_boot: prune_after_boot, + reboot_after_config: reboot? + ] + init = Config.Provider.init(release.config_providers, config_path, opts) {Config.Reader.merge(sys_config, [elixir: [config_providers: init]] ++ extra_config), true} end defp start_distribution(%{options: opts}) do - if Keyword.get(opts, :start_distribution_during_config, false) do - {[], []} + reboot? = Keyword.get(opts, :reboot_system_after_config, true) + early_distribution? = Keyword.get(opts, :start_distribution_during_config, false) + + if not reboot? or early_distribution? do + {reboot?, [], []} else - {[kernel: [start_distribution: false]], [kernel: [start_distribution: true]]} + {true, [kernel: [start_distribution: false]], [kernel: [start_distribution: true]]} end end diff --git a/lib/mix/lib/mix/tasks/release.ex b/lib/mix/lib/mix/tasks/release.ex index 5b09416cb..087ac9a34 100644 --- a/lib/mix/lib/mix/tasks/release.ex +++ b/lib/mix/lib/mix/tasks/release.ex @@ -609,13 +609,20 @@ defmodule Mix.Tasks.Release do are evaluated. You can set it to `true` if you need distribution during configuration. Defaults to `false`. + * `:reboot_system_after_config` - every time your release is configured, + the system is rebooted to allow the new configuration to take place. + You can set this option to `false` to disable the rebooting for applications + that are sensitive to boot time but, in doing so, note you won't be able + to configure system applications, such as `:kernel`, `:stdlib` and `:elixir` + itself. Defaults to `true`. + * `:prune_runtime_sys_config_after_boot` - every time your system boots, the release will write a config file to your tmp directory. These configuration files are generally small. But if you are concerned with disk space or if you have other restrictions, you can ask the system to remove said config files after boot. The downside is that you will no longer be able to restart the system internally (neither via - `System.restart/0` nor `bin/RELEASE_NAME start`). If you need a restart, + `System.restart/0` nor `bin/RELEASE_NAME restart`). If you need a restart, you will have to terminate the Operating System process and start a new one. Defaults to `false`. diff --git a/lib/mix/test/mix/release_test.exs b/lib/mix/test/mix/release_test.exs index 9f43f5ad0..75e7af3a0 100644 --- a/lib/mix/test/mix/release_test.exs +++ b/lib/mix/test/mix/release_test.exs @@ -506,11 +506,23 @@ defmodule Mix.ReleaseTest do assert File.read!(@sys_config) =~ "%% RUNTIME_CONFIG=true" {:ok, [config]} = :file.consult(@sys_config) assert %Config.Provider{} = provider = config[:elixir][:config_providers] + assert provider.reboot_after_config assert provider.prune_after_boot assert provider.extra_config == [] assert config[:kernel] == [key: :value] end + test "writes the given sys_config without reboot" do + release = release(config_providers: @providers, reboot_system_after_config: false) + assert make_sys_config(release, [kernel: [key: :value]], "/foo/bar/bat") == :ok + assert File.read!(@sys_config) =~ "%% RUNTIME_CONFIG=true" + {:ok, [config]} = :file.consult(@sys_config) + assert %Config.Provider{} = provider = config[:elixir][:config_providers] + refute provider.reboot_after_config + assert provider.extra_config == [] + assert config[:kernel] == [key: :value] + end + test "errors on bad config" do assert {:error, "Could not read configuration file." <> _} = make_sys_config(release([]), [foo: self()], "unused/runtime/path") |