diff options
author | Alexander Strizhakov <alex.strizhakov@gmail.com> | 2020-07-16 19:57:27 +0300 |
---|---|---|
committer | Alexander Strizhakov <alex.strizhakov@gmail.com> | 2021-05-11 18:12:33 +0300 |
commit | 2538c741c0d1bf9c2d9c8e02953d3d6e63220e8f (patch) | |
tree | 85139b44fc541482a2d923985bff58f1c2ccbdb7 /lib/pleroma/web | |
parent | 745375bdcf2679ff803dd4ebc4a8313a7b5fb157 (diff) | |
download | pleroma-feature/config-versioning.tar.gz |
config versioningfeature/config-versioning
- added DynamicSupervisor, which starts Pleroma deps and restarts config dependent deps
- added versioning for in database config. New version is created from
changes which are passed to config update/delete endpoint. Every version
contains backup with all changes added through update. Versioning
supports rollbacks with N steps. With a rollback, all versions that
come after the version on which the rollback was made are deleted.
Diffstat (limited to 'lib/pleroma/web')
-rw-r--r-- | lib/pleroma/web/admin_api/controllers/admin_api_controller.ex | 4 | ||||
-rw-r--r-- | lib/pleroma/web/admin_api/controllers/config_controller.ex | 169 | ||||
-rw-r--r-- | lib/pleroma/web/admin_api/views/config_view.ex | 28 | ||||
-rw-r--r-- | lib/pleroma/web/api_spec/operations/admin/config_operation.ex | 54 | ||||
-rw-r--r-- | lib/pleroma/web/router.ex | 2 |
5 files changed, 164 insertions, 93 deletions
diff --git a/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex b/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex index 839ac1a8d..daa4c451c 100644 --- a/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex @@ -392,14 +392,14 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do def restart(conn, _params) do with :ok <- configurable_from_database() do - Restarter.Pleroma.restart(Config.get(:env), 50) + Task.start(Pleroma.Application.ConfigDependentDeps, :restart_dependencies, []) json(conn, %{}) end end def need_reboot(conn, _params) do - json(conn, %{need_reboot: Restarter.Pleroma.need_reboot?()}) + json(conn, %{need_reboot: Pleroma.Application.ConfigDependentDeps.need_reboot?()}) end defp configurable_from_database do diff --git a/lib/pleroma/web/admin_api/controllers/config_controller.ex b/lib/pleroma/web/admin_api/controllers/config_controller.ex index a718d7b8d..88ae9ed99 100644 --- a/lib/pleroma/web/admin_api/controllers/config_controller.ex +++ b/lib/pleroma/web/admin_api/controllers/config_controller.ex @@ -5,19 +5,24 @@ defmodule Pleroma.Web.AdminAPI.ConfigController do use Pleroma.Web, :controller + import Pleroma.Web.ControllerHelper, only: [json_response: 3] + + alias Pleroma.Application alias Pleroma.Config alias Pleroma.ConfigDB alias Pleroma.Web.Plugs.OAuthScopesPlug plug(Pleroma.Web.ApiSpec.CastAndValidate) - plug(OAuthScopesPlug, %{scopes: ["admin:write"]} when action == :update) + plug(OAuthScopesPlug, %{scopes: ["admin:write"]} when action in [:update, :rollback]) plug( OAuthScopesPlug, %{scopes: ["admin:read"]} - when action in [:show, :descriptions] + when action in [:show, :descriptions, :versions] ) + plug(:check_possibility_configuration_from_database when action != :descriptions) + action_fallback(Pleroma.Web.AdminAPI.FallbackController) defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.ConfigOperation @@ -29,100 +34,110 @@ defmodule Pleroma.Web.AdminAPI.ConfigController do end def show(conn, %{only_db: true}) do - with :ok <- configurable_from_database() do - configs = Pleroma.Repo.all(ConfigDB) + configs = ConfigDB.all_with_db() - render(conn, "index.json", %{ - configs: configs, - need_reboot: Restarter.Pleroma.need_reboot?() - }) - end + render(conn, "index.json", %{ + configs: configs, + need_reboot: Application.ConfigDependentDeps.need_reboot?() + }) end def show(conn, _params) do - with :ok <- configurable_from_database() do - configs = ConfigDB.get_all_as_keyword() - - merged = - Config.Holder.default_config() - |> ConfigDB.merge(configs) - |> Enum.map(fn {group, values} -> - Enum.map(values, fn {key, value} -> - db = - if configs[group][key] do - ConfigDB.get_db_keys(configs[group][key], key) - end + defaults = Config.Holder.default_config() + changes = ConfigDB.all_with_db() + + {changes_values_merged_with_defaults, remaining_defaults} = + ConfigDB.reduce_defaults_and_merge_with_changes(changes, defaults) - db_value = configs[group][key] + changes_merged_with_defaults = + ConfigDB.from_keyword_to_structs(remaining_defaults, changes_values_merged_with_defaults) - merged_value = - if not is_nil(db_value) and Keyword.keyword?(db_value) and - ConfigDB.sub_key_full_update?(group, key, Keyword.keys(db_value)) do - ConfigDB.merge_group(group, key, value, db_value) + render(conn, "index.json", %{ + configs: changes_merged_with_defaults, + need_reboot: Application.ConfigDependentDeps.need_reboot?() + }) + end + + def update(%{body_params: %{configs: configs}} = conn, _) do + result = + configs + |> Enum.filter(&whitelisted_config?/1) + |> Enum.map(&Config.Converter.to_elixir_types/1) + |> Config.Versioning.new_version() + + case result do + {:ok, changes} -> + inserts_and_deletions = + Enum.reduce(changes, [], fn + {{operation, _, _}, %ConfigDB{} = change}, acc + when operation in [:insert_or_update, :delete_or_update] -> + if Ecto.get_meta(change, :state) == :deleted do + [change | acc] else - value + if change.group == :pleroma and + change.key in ConfigDB.pleroma_not_keyword_values() do + [%{change | db: [change.key]} | acc] + else + [%{change | db: Keyword.keys(change.value)} | acc] + end end - %ConfigDB{ - group: group, - key: key, - value: merged_value - } - |> Pleroma.Maps.put_if_present(:db, db) + _, acc -> + acc end) - end) - |> List.flatten() - render(conn, "index.json", %{ - configs: merged, - need_reboot: Restarter.Pleroma.need_reboot?() - }) + Application.Environment.update(inserts_and_deletions, only_update: true) + + render(conn, "index.json", %{ + configs: Enum.reject(inserts_and_deletions, &(Ecto.get_meta(&1, :state) == :deleted)), + need_reboot: Application.ConfigDependentDeps.need_reboot?() + }) + + {:error, error} -> + {:error, "Updating config failed: #{inspect(error)}"} + + {:error, _, {error, operation}, _} -> + {:error, + "Updating config failed: #{inspect(error)}, group: #{operation[:group]}, key: #{ + operation[:key] + }, value: #{inspect(operation[:value])}"} end end - def update(%{body_params: %{configs: configs}} = conn, _) do - with :ok <- configurable_from_database() do - results = - configs - |> Enum.filter(&whitelisted_config?/1) - |> Enum.map(fn - %{group: group, key: key, delete: true} = params -> - ConfigDB.delete(%{group: group, key: key, subkeys: params[:subkeys]}) - - %{group: group, key: key, value: value} -> - ConfigDB.update_or_create(%{group: group, key: key, value: value}) - end) - |> Enum.reject(fn {result, _} -> result == :error end) - - {deleted, updated} = - results - |> Enum.map(fn {:ok, %{key: key, value: value} = config} -> - Map.put(config, :db, ConfigDB.get_db_keys(value, key)) - end) - |> Enum.split_with(&(Ecto.get_meta(&1, :state) == :deleted)) - - Config.TransferTask.load_and_update_env(deleted, false) - - if not Restarter.Pleroma.need_reboot?() do - changed_reboot_settings? = - (updated ++ deleted) - |> Enum.any?(&Config.TransferTask.pleroma_need_restart?(&1.group, &1.key, &1.value)) - - if changed_reboot_settings?, do: Restarter.Pleroma.need_reboot() - end - - render(conn, "index.json", %{ - configs: updated, - need_reboot: Restarter.Pleroma.need_reboot?() - }) + def rollback(conn, %{id: id}) do + case Config.Versioning.rollback_by_id(id) do + {:ok, _} -> + json_response(conn, :no_content, "") + + {:error, :not_found} -> + {:error, :not_found} + + {:error, error} -> + {:error, "Rollback is not possible: #{inspect(error)}"} + + {:error, _, {error, operation}, _} -> + {:error, + "Rollback is not possible, backup restore error: #{inspect(error)}, operation error: #{ + inspect(operation) + }"} end end - defp configurable_from_database do + def versions(conn, _) do + versions = Pleroma.Config.Version.all() + + render(conn, "index.json", %{versions: versions}) + end + + defp check_possibility_configuration_from_database(conn, _) do if Config.get(:configurable_from_database) do - :ok + conn else - {:error, "You must enable configurable_from_database in your config file."} + Pleroma.Web.AdminAPI.FallbackController.call( + conn, + {:error, "You must enable configurable_from_database in your config file."} + ) + |> halt() end end diff --git a/lib/pleroma/web/admin_api/views/config_view.ex b/lib/pleroma/web/admin_api/views/config_view.ex index d29b4963d..6115c3405 100644 --- a/lib/pleroma/web/admin_api/views/config_view.ex +++ b/lib/pleroma/web/admin_api/views/config_view.ex @@ -5,8 +5,6 @@ defmodule Pleroma.Web.AdminAPI.ConfigView do use Pleroma.Web, :view - alias Pleroma.ConfigDB - def render("index.json", %{configs: configs} = params) do %{ configs: render_many(configs, __MODULE__, "show.json", as: :config), @@ -14,17 +12,23 @@ defmodule Pleroma.Web.AdminAPI.ConfigView do } end - def render("show.json", %{config: config}) do - map = %{ - key: ConfigDB.to_json_types(config.key), - group: ConfigDB.to_json_types(config.group), - value: ConfigDB.to_json_types(config.value) + def render("index.json", %{versions: versions}) do + %{ + versions: render_many(versions, __MODULE__, "show.json", as: :version) } + end + + def render("show.json", %{config: config}) do + config + |> Map.take([:group, :key, :value, :db]) + |> Map.new(fn + {k, v} -> {k, Pleroma.Config.Converter.to_json_types(v)} + end) + end - if config.db != [] do - Map.put(map, :db, config.db) - else - map - end + def render("show.json", %{version: version}) do + version + |> Map.take([:id, :current]) + |> Map.put(:inserted_at, Pleroma.Web.CommonAPI.Utils.to_masto_date(version.inserted_at)) end end diff --git a/lib/pleroma/web/api_spec/operations/admin/config_operation.ex b/lib/pleroma/web/api_spec/operations/admin/config_operation.ex index 30c3433b7..6d22191f1 100644 --- a/lib/pleroma/web/api_spec/operations/admin/config_operation.ex +++ b/lib/pleroma/web/api_spec/operations/admin/config_operation.ex @@ -53,7 +53,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.ConfigOperation do type: :object, properties: %{ group: %Schema{type: :string}, - key: %Schema{type: :string}, + key: %Schema{type: :string, nullable: true}, value: any(), delete: %Schema{type: :boolean}, subkeys: %Schema{type: :array, items: %Schema{type: :string}} @@ -107,6 +107,56 @@ defmodule Pleroma.Web.ApiSpec.Admin.ConfigOperation do } end + def rollback_operation do + %Operation{ + tags: ["Admin", "Config"], + summary: "Rollback config changes.", + operationId: "AdminAPI.ConfigController.rollback", + security: [%{"oAuth" => ["write"]}], + parameters: [ + Operation.parameter(:id, :path, %Schema{type: :integer}, "Version id to rollback", + required: true + ) + | admin_api_params() + ], + responses: %{ + 204 => no_content_response(), + 400 => Operation.response("Bad Request", "application/json", ApiError), + 404 => Operation.response("Not Found", "application/json", ApiError) + } + } + end + + def versions_operation do + %Operation{ + tags: ["Admin", "Config"], + summary: "Get list with config versions.", + operationId: "AdminAPI.ConfigController.versions", + security: [%{"oAuth" => ["read"]}], + parameters: admin_api_params(), + responses: %{ + 200 => + Operation.response("Config Version", "application/json", %Schema{ + type: :object, + properties: %{ + versions: %Schema{ + type: :array, + items: %Schema{ + type: :object, + properties: %{ + id: %Schema{type: :integer}, + current: %Schema{type: :boolean}, + inserted_at: %Schema{type: :string, format: :"date-time"} + } + } + } + } + }), + 400 => Operation.response("Bad Request", "application/json", ApiError) + } + } + end + defp any do %Schema{ oneOf: [ @@ -129,7 +179,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.ConfigOperation do type: :object, properties: %{ group: %Schema{type: :string}, - key: %Schema{type: :string}, + key: %Schema{type: :string, nullable: true}, value: any() } } diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index ccf2ef796..101c9fdff 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -232,6 +232,8 @@ defmodule Pleroma.Web.Router do get("/config", ConfigController, :show) post("/config", ConfigController, :update) get("/config/descriptions", ConfigController, :descriptions) + get("/config/versions", ConfigController, :versions) + get("/config/versions/rollback/:id", ConfigController, :rollback) get("/need_reboot", AdminAPIController, :need_reboot) get("/restart", AdminAPIController, :restart) |