From 2753285b7722fdb47f0ebb2180e997cf72f65d1a Mon Sep 17 00:00:00 2001 From: Alex S Date: Sun, 29 Sep 2019 11:17:38 +0300 Subject: config editing through database --- lib/mix/tasks/pleroma/config.ex | 107 +++++++++++------- lib/pleroma/config/transfer_task.ex | 59 +++++----- lib/pleroma/docs/generator.ex | 120 +++++++++++++------- lib/pleroma/docs/json.ex | 20 ++-- .../activity_pub/mrf/media_proxy_warming_policy.ex | 60 ++++++++++ .../activity_pub/mrf/mediaproxy_warming_policy.ex | 60 ---------- lib/pleroma/web/activity_pub/mrf/no_op_policy.ex | 16 +++ lib/pleroma/web/activity_pub/mrf/noop_policy.ex | 16 --- .../web/activity_pub/mrf/user_allow_list_policy.ex | 44 ++++++++ .../web/activity_pub/mrf/user_allowlist_policy.ex | 44 -------- .../web/activity_pub/mrf/vocabulary_policy.ex | 3 +- lib/pleroma/web/admin_api/admin_api_controller.ex | 44 ++++++-- lib/pleroma/web/admin_api/config.ex | 122 ++++++++++++++------- lib/pleroma/web/router.ex | 1 + 14 files changed, 427 insertions(+), 289 deletions(-) create mode 100644 lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex delete mode 100644 lib/pleroma/web/activity_pub/mrf/mediaproxy_warming_policy.ex create mode 100644 lib/pleroma/web/activity_pub/mrf/no_op_policy.ex delete mode 100644 lib/pleroma/web/activity_pub/mrf/noop_policy.ex create mode 100644 lib/pleroma/web/activity_pub/mrf/user_allow_list_policy.ex delete mode 100644 lib/pleroma/web/activity_pub/mrf/user_allowlist_policy.ex (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex index 590c7a914..bb126463c 100644 --- a/lib/mix/tasks/pleroma/config.ex +++ b/lib/mix/tasks/pleroma/config.ex @@ -9,27 +9,29 @@ defmodule Mix.Tasks.Pleroma.Config do alias Pleroma.Web.AdminAPI.Config @shortdoc "Manages the location of the config" @moduledoc File.read!("docs/administration/CLI_tasks/config.md") + + @groups [ + :pleroma, + :logger, + :quack, + :mime, + :tesla, + :phoenix, + :cors_plug, + :auto_linker, + :esshd, + :ueberauth, + :prometheus, + :http_signatures, + :web_push_encryption, + :joken + ] + def run(["migrate_to_db"]) do start_pleroma() if Pleroma.Config.get([:instance, :dynamic_configuration]) do - Application.get_all_env(:pleroma) - |> Enum.reject(fn {k, _v} -> k in [Pleroma.Repo, :env] end) - |> Enum.each(fn {k, v} -> - key = to_string(k) |> String.replace("Elixir.", "") - - key = - if String.starts_with?(key, "Pleroma.") do - key - else - ":" <> key - end - - {:ok, _} = Config.update_or_create(%{group: "pleroma", key: key, value: v}) - Mix.shell().info("#{key} is migrated.") - end) - - Mix.shell().info("Settings migrated.") + Enum.each(@groups, &load_and_create(&1)) else Mix.shell().info( "Migration is not allowed by config. You can change this behavior in instance settings." @@ -37,38 +39,63 @@ defmodule Mix.Tasks.Pleroma.Config do end end - def run(["migrate_from_db", env, delete?]) do + def run(["migrate_from_db" | options]) do start_pleroma() - delete? = if delete? == "true", do: true, else: false - - if Pleroma.Config.get([:instance, :dynamic_configuration]) do - config_path = "config/#{env}.exported_from_db.secret.exs" + {opts, _} = + OptionParser.parse!(options, + strict: [env: :string, delete_from_db: :boolean], + aliases: [d: :delete_from_db] + ) - {:ok, file} = File.open(config_path, [:write, :utf8]) + with {:active?, true} <- {:active?, Pleroma.Config.get([:instance, :dynamic_configuration])}, + env_path when is_binary(env_path) <- opts[:env], + config_path <- "config/#{env_path}.exported_from_db.secret.exs", + {:ok, file} <- File.open(config_path, [:write, :utf8]) do IO.write(file, "use Mix.Config\r\n") - Repo.all(Config) - |> Enum.each(fn config -> - IO.write( - file, - "config :#{config.group}, #{config.key}, #{ - inspect(Config.from_binary(config.value), limit: :infinity) - }\r\n\r\n" - ) - - if delete? do - {:ok, _} = Repo.delete(config) - Mix.shell().info("#{config.key} deleted from DB.") - end - end) + Config + |> Repo.all() + |> Enum.each(&write_to_file_with_deletion(&1, file, opts[:delete_from_db])) File.close(file) System.cmd("mix", ["format", config_path]) else - Mix.shell().info( - "Migration is not allowed by config. You can change this behavior in instance settings." - ) + {:active?, false} -> + Mix.shell().info( + "migration is not allowed by config. You can change this behavior in instance settings." + ) + + error -> + Mix.shell().info("error occuried while opening file. #{inspect(error)}") + end + end + + defp load_and_create(group) do + group + |> Application.get_all_env() + |> Enum.reject(fn {k, _v} -> k in [Pleroma.Repo, :env] end) + |> Enum.each(fn {key, value} -> + key_str = inspect(key) + + {:ok, _} = Config.update_or_create(%{group: ":#{group}", key: key_str, value: value}) + Mix.shell().info("settings for key #{key_str} migrated.") + end) + + Mix.shell().info("settings for group :#{group} migrated.") + end + + defp write_to_file_with_deletion(config, file, with_deletion) do + IO.write( + file, + "config #{config.group}, #{config.key}, #{ + inspect(Config.from_binary(config.value), limit: :infinity) + }\r\n\r\n" + ) + + if with_deletion do + {:ok, _} = Repo.delete(config) + Mix.shell().info("#{config.key} deleted from DB.") end end end diff --git a/lib/pleroma/config/transfer_task.ex b/lib/pleroma/config/transfer_task.ex index 3214c9951..0bc4c4029 100644 --- a/lib/pleroma/config/transfer_task.ex +++ b/lib/pleroma/config/transfer_task.ex @@ -4,56 +4,59 @@ defmodule Pleroma.Config.TransferTask do use Task + + require Logger + + alias Pleroma.Repo alias Pleroma.Web.AdminAPI.Config def start_link(_) do load_and_update_env() - if Pleroma.Config.get(:env) == :test, do: Ecto.Adapters.SQL.Sandbox.checkin(Pleroma.Repo) + if Pleroma.Config.get(:env) == :test, do: Ecto.Adapters.SQL.Sandbox.checkin(Repo) :ignore end def load_and_update_env do - if Pleroma.Config.get([:instance, :dynamic_configuration]) and - Ecto.Adapters.SQL.table_exists?(Pleroma.Repo, "config") do - for_restart = - Pleroma.Repo.all(Config) - |> Enum.map(&update_env(&1)) - + with true <- Pleroma.Config.get([:instance, :dynamic_configuration]), + true <- Ecto.Adapters.SQL.table_exists?(Repo, "config"), + started_applications <- Application.started_applications() do # We need to restart applications for loaded settings take effect - for_restart - |> Enum.reject(&(&1 in [:pleroma, :ok])) - |> Enum.each(fn app -> - Application.stop(app) - :ok = Application.start(app) - end) + Config + |> Repo.all() + |> Enum.map(&update_env(&1)) + |> Enum.uniq() + # TODO: some problem with prometheus after restart! + |> Enum.reject(&(&1 in [:pleroma, nil, :prometheus])) + |> Enum.each(&restart(started_applications, &1)) end end defp update_env(setting) do try do - key = - if String.starts_with?(setting.key, "Pleroma.") do - "Elixir." <> setting.key - else - String.trim_leading(setting.key, ":") - end + key = Config.from_string(setting.key) + group = Config.from_string(setting.group) + value = Config.from_binary(setting.value) - group = String.to_existing_atom(setting.group) - - Application.put_env( - group, - String.to_existing_atom(key), - Config.from_binary(setting.value) - ) + :ok = Application.put_env(group, key, value) group rescue e -> - require Logger - Logger.warn( "updating env causes error, key: #{inspect(setting.key)}, error: #{inspect(e)}" ) + + nil + end + end + + defp restart(started_applications, app) do + with {^app, _, _} <- List.keyfind(started_applications, app, 0), + :ok <- Application.stop(app) do + :ok = Application.start(app) + else + nil -> Logger.warn("#{app} is not started.") + error -> Logger.warn(inspect(error)) end end end diff --git a/lib/pleroma/docs/generator.ex b/lib/pleroma/docs/generator.ex index aa578eee2..b57e47e8b 100644 --- a/lib/pleroma/docs/generator.ex +++ b/lib/pleroma/docs/generator.ex @@ -6,68 +6,108 @@ defmodule Pleroma.Docs.Generator do implementation.process(descriptions) end - @spec uploaders_list() :: [module()] - def uploaders_list do - {:ok, modules} = :application.get_key(:pleroma, :modules) + @spec list_modules_in_dir(String.t(), String.t()) :: [module()] + def list_modules_in_dir(dir, start) do + with {:ok, files} <- File.ls(dir) do + files + |> Enum.filter(&String.ends_with?(&1, ".ex")) + |> Enum.map(fn filename -> + module = filename |> String.trim_trailing(".ex") |> Macro.camelize() + String.to_existing_atom(start <> module) + end) + end + end + + @doc """ + Converts: + - atoms to strings with leading `:` + - module names to strings, without leading `Elixir.` + - add humanized labels to `keys` if label is not defined, e.g. `:instance` -> `Instance` + """ + @spec convert_to_strings([map()]) :: [map()] + def convert_to_strings(descriptions) do + Enum.map(descriptions, &format_entity(&1)) + end + + defp format_entity(entity) do + entity + |> format_key() + |> Map.put(:group, atom_to_string(entity[:group])) + |> format_children() + end - Enum.filter(modules, fn module -> - name_as_list = Module.split(module) + defp format_key(%{key: key} = entity) do + entity + |> Map.put(:key, atom_to_string(key)) + |> Map.put(:label, entity[:label] || humanize(key)) + end - List.starts_with?(name_as_list, ["Pleroma", "Uploaders"]) and - List.last(name_as_list) != "Uploader" - end) + defp format_key(%{group: group} = entity) do + Map.put(entity, :label, entity[:label] || humanize(group)) end - @spec filters_list() :: [module()] - def filters_list do - {:ok, modules} = :application.get_key(:pleroma, :modules) + defp format_key(entity), do: entity + + defp format_children(%{children: children} = entity) do + Map.put(entity, :children, Enum.map(children, &format_child(&1))) + end - Enum.filter(modules, fn module -> - name_as_list = Module.split(module) + defp format_children(entity), do: entity + + defp format_child(%{suggestions: suggestions} = entity) do + entity + |> Map.put(:suggestions, format_suggestions(suggestions)) + |> format_key() + |> format_children() + end - List.starts_with?(name_as_list, ["Pleroma", "Upload", "Filter"]) - end) + defp format_child(entity) do + entity + |> format_key() + |> format_children() end - @spec mrf_list() :: [module()] - def mrf_list do - {:ok, modules} = :application.get_key(:pleroma, :modules) + defp atom_to_string(entity) when is_binary(entity), do: entity - Enum.filter(modules, fn module -> - name_as_list = Module.split(module) + defp atom_to_string(entity) when is_atom(entity), do: inspect(entity) - List.starts_with?(name_as_list, ["Pleroma", "Web", "ActivityPub", "MRF"]) and - length(name_as_list) > 4 - end) + defp humanize(entity) do + string = inspect(entity) + + if String.starts_with?(string, ":"), + do: Phoenix.Naming.humanize(entity), + else: string end - @spec richmedia_parsers() :: [module()] - def richmedia_parsers do - {:ok, modules} = :application.get_key(:pleroma, :modules) + defp format_suggestions([]), do: [] + + defp format_suggestions([suggestion | tail]) do + [format_suggestion(suggestion) | format_suggestions(tail)] + end + + defp format_suggestion(entity) when is_atom(entity) do + atom_to_string(entity) + end - Enum.filter(modules, fn module -> - name_as_list = Module.split(module) + defp format_suggestion([head | tail] = entity) when is_list(entity) do + [format_suggestion(head) | format_suggestions(tail)] + end - List.starts_with?(name_as_list, ["Pleroma", "Web", "RichMedia", "Parsers"]) and - length(name_as_list) == 5 - end) + defp format_suggestion(entity) when is_tuple(entity) do + format_suggestions(Tuple.to_list(entity)) |> List.to_tuple() end + + defp format_suggestion(entity), do: entity end defimpl Jason.Encoder, for: Tuple do - def encode(tuple, opts) do - Jason.Encode.list(Tuple.to_list(tuple), opts) - end + def encode(tuple, opts), do: Jason.Encode.list(Tuple.to_list(tuple), opts) end defimpl Jason.Encoder, for: [Regex, Function] do - def encode(term, opts) do - Jason.Encode.string(inspect(term), opts) - end + def encode(term, opts), do: Jason.Encode.string(inspect(term), opts) end defimpl String.Chars, for: Regex do - def to_string(term) do - inspect(term) - end + def to_string(term), do: inspect(term) end diff --git a/lib/pleroma/docs/json.ex b/lib/pleroma/docs/json.ex index f2a56d845..f191b6013 100644 --- a/lib/pleroma/docs/json.ex +++ b/lib/pleroma/docs/json.ex @@ -3,18 +3,22 @@ defmodule Pleroma.Docs.JSON do @spec process(keyword()) :: {:ok, String.t()} def process(descriptions) do - config_path = "docs/generate_config.json" - - with {:ok, file} <- File.open(config_path, [:write, :utf8]), - json <- generate_json(descriptions), + with path <- "docs/generated_config.json", + {:ok, file} <- File.open(path, [:write, :utf8]), + formatted_descriptions <- + Pleroma.Docs.Generator.convert_to_strings(descriptions), + json <- Jason.encode!(formatted_descriptions), :ok <- IO.write(file, json), :ok <- File.close(file) do - {:ok, config_path} + {:ok, path} end end - @spec generate_json([keyword()]) :: String.t() - def generate_json(descriptions) do - Jason.encode!(descriptions) + def compile do + with {config, _paths} <- Mix.Config.eval!("config/description.exs") do + config[:pleroma][:config_description] + |> Pleroma.Docs.Generator.convert_to_strings() + |> Jason.encode!() + end end end diff --git a/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex b/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex new file mode 100644 index 000000000..df774b0f7 --- /dev/null +++ b/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex @@ -0,0 +1,60 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy do + @moduledoc "Preloads any attachments in the MediaProxy cache by prefetching them" + @behaviour Pleroma.Web.ActivityPub.MRF + + alias Pleroma.HTTP + alias Pleroma.Web.MediaProxy + alias Pleroma.Workers.BackgroundWorker + + require Logger + + @hackney_options [ + pool: :media, + recv_timeout: 10_000 + ] + + def perform(:prefetch, url) do + Logger.debug("Prefetching #{inspect(url)}") + + url + |> MediaProxy.url() + |> HTTP.get([], adapter: @hackney_options) + end + + def perform(:preload, %{"object" => %{"attachment" => attachments}} = _message) do + Enum.each(attachments, fn + %{"url" => url} when is_list(url) -> + url + |> Enum.each(fn + %{"href" => href} -> + BackgroundWorker.enqueue("media_proxy_prefetch", %{"url" => href}) + + x -> + Logger.debug("Unhandled attachment URL object #{inspect(x)}") + end) + + x -> + Logger.debug("Unhandled attachment #{inspect(x)}") + end) + end + + @impl true + def filter( + %{"type" => "Create", "object" => %{"attachment" => attachments} = _object} = message + ) + when is_list(attachments) and length(attachments) > 0 do + BackgroundWorker.enqueue("media_proxy_preload", %{"message" => message}) + + {:ok, message} + end + + @impl true + def filter(message), do: {:ok, message} + + @impl true + def describe, do: {:ok, %{}} +end diff --git a/lib/pleroma/web/activity_pub/mrf/mediaproxy_warming_policy.ex b/lib/pleroma/web/activity_pub/mrf/mediaproxy_warming_policy.ex deleted file mode 100644 index df774b0f7..000000000 --- a/lib/pleroma/web/activity_pub/mrf/mediaproxy_warming_policy.ex +++ /dev/null @@ -1,60 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2019 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy do - @moduledoc "Preloads any attachments in the MediaProxy cache by prefetching them" - @behaviour Pleroma.Web.ActivityPub.MRF - - alias Pleroma.HTTP - alias Pleroma.Web.MediaProxy - alias Pleroma.Workers.BackgroundWorker - - require Logger - - @hackney_options [ - pool: :media, - recv_timeout: 10_000 - ] - - def perform(:prefetch, url) do - Logger.debug("Prefetching #{inspect(url)}") - - url - |> MediaProxy.url() - |> HTTP.get([], adapter: @hackney_options) - end - - def perform(:preload, %{"object" => %{"attachment" => attachments}} = _message) do - Enum.each(attachments, fn - %{"url" => url} when is_list(url) -> - url - |> Enum.each(fn - %{"href" => href} -> - BackgroundWorker.enqueue("media_proxy_prefetch", %{"url" => href}) - - x -> - Logger.debug("Unhandled attachment URL object #{inspect(x)}") - end) - - x -> - Logger.debug("Unhandled attachment #{inspect(x)}") - end) - end - - @impl true - def filter( - %{"type" => "Create", "object" => %{"attachment" => attachments} = _object} = message - ) - when is_list(attachments) and length(attachments) > 0 do - BackgroundWorker.enqueue("media_proxy_preload", %{"message" => message}) - - {:ok, message} - end - - @impl true - def filter(message), do: {:ok, message} - - @impl true - def describe, do: {:ok, %{}} -end diff --git a/lib/pleroma/web/activity_pub/mrf/no_op_policy.ex b/lib/pleroma/web/activity_pub/mrf/no_op_policy.ex new file mode 100644 index 000000000..878c57925 --- /dev/null +++ b/lib/pleroma/web/activity_pub/mrf/no_op_policy.ex @@ -0,0 +1,16 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.MRF.NoOpPolicy do + @moduledoc "Does nothing (lets the messages go through unmodified)" + @behaviour Pleroma.Web.ActivityPub.MRF + + @impl true + def filter(object) do + {:ok, object} + end + + @impl true + def describe, do: {:ok, %{}} +end diff --git a/lib/pleroma/web/activity_pub/mrf/noop_policy.ex b/lib/pleroma/web/activity_pub/mrf/noop_policy.ex deleted file mode 100644 index 878c57925..000000000 --- a/lib/pleroma/web/activity_pub/mrf/noop_policy.ex +++ /dev/null @@ -1,16 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.ActivityPub.MRF.NoOpPolicy do - @moduledoc "Does nothing (lets the messages go through unmodified)" - @behaviour Pleroma.Web.ActivityPub.MRF - - @impl true - def filter(object) do - {:ok, object} - end - - @impl true - def describe, do: {:ok, %{}} -end diff --git a/lib/pleroma/web/activity_pub/mrf/user_allow_list_policy.ex b/lib/pleroma/web/activity_pub/mrf/user_allow_list_policy.ex new file mode 100644 index 000000000..7389d6a96 --- /dev/null +++ b/lib/pleroma/web/activity_pub/mrf/user_allow_list_policy.ex @@ -0,0 +1,44 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.MRF.UserAllowListPolicy do + alias Pleroma.Config + + @moduledoc "Accept-list of users from specified instances" + @behaviour Pleroma.Web.ActivityPub.MRF + + defp filter_by_list(object, []), do: {:ok, object} + + defp filter_by_list(%{"actor" => actor} = object, allow_list) do + if actor in allow_list do + {:ok, object} + else + {:reject, nil} + end + end + + @impl true + def filter(%{"actor" => actor} = object) do + actor_info = URI.parse(actor) + + allow_list = + Config.get( + [:mrf_user_allowlist, String.to_atom(actor_info.host)], + [] + ) + + filter_by_list(object, allow_list) + end + + def filter(object), do: {:ok, object} + + @impl true + def describe do + mrf_user_allowlist = + Config.get([:mrf_user_allowlist], []) + |> Enum.into(%{}, fn {k, v} -> {k, length(v)} end) + + {:ok, %{mrf_user_allowlist: mrf_user_allowlist}} + end +end diff --git a/lib/pleroma/web/activity_pub/mrf/user_allowlist_policy.ex b/lib/pleroma/web/activity_pub/mrf/user_allowlist_policy.ex deleted file mode 100644 index 7389d6a96..000000000 --- a/lib/pleroma/web/activity_pub/mrf/user_allowlist_policy.ex +++ /dev/null @@ -1,44 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.ActivityPub.MRF.UserAllowListPolicy do - alias Pleroma.Config - - @moduledoc "Accept-list of users from specified instances" - @behaviour Pleroma.Web.ActivityPub.MRF - - defp filter_by_list(object, []), do: {:ok, object} - - defp filter_by_list(%{"actor" => actor} = object, allow_list) do - if actor in allow_list do - {:ok, object} - else - {:reject, nil} - end - end - - @impl true - def filter(%{"actor" => actor} = object) do - actor_info = URI.parse(actor) - - allow_list = - Config.get( - [:mrf_user_allowlist, String.to_atom(actor_info.host)], - [] - ) - - filter_by_list(object, allow_list) - end - - def filter(object), do: {:ok, object} - - @impl true - def describe do - mrf_user_allowlist = - Config.get([:mrf_user_allowlist], []) - |> Enum.into(%{}, fn {k, v} -> {k, length(v)} end) - - {:ok, %{mrf_user_allowlist: mrf_user_allowlist}} - end -end diff --git a/lib/pleroma/web/activity_pub/mrf/vocabulary_policy.ex b/lib/pleroma/web/activity_pub/mrf/vocabulary_policy.ex index 4eaea00d8..9a03d67c0 100644 --- a/lib/pleroma/web/activity_pub/mrf/vocabulary_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/vocabulary_policy.ex @@ -19,8 +19,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.VocabularyPolicy do def filter(%{"type" => message_type} = message) do with accepted_vocabulary <- Pleroma.Config.get([:mrf_vocabulary, :accept]), rejected_vocabulary <- Pleroma.Config.get([:mrf_vocabulary, :reject]), - true <- - length(accepted_vocabulary) == 0 || Enum.member?(accepted_vocabulary, message_type), + true <- accepted_vocabulary == [] || Enum.member?(accepted_vocabulary, message_type), false <- length(rejected_vocabulary) > 0 && Enum.member?(rejected_vocabulary, message_type), {:ok, _} <- filter(message["object"]) do diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index c8abeff06..376f88061 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -4,6 +4,9 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do use Pleroma.Web, :controller + + import Pleroma.Web.ControllerHelper, only: [json_response: 3] + alias Pleroma.Activity alias Pleroma.ModerationLog alias Pleroma.Plugs.OAuthScopesPlug @@ -25,10 +28,11 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do alias Pleroma.Web.MastodonAPI.StatusView alias Pleroma.Web.Router - import Pleroma.Web.ControllerHelper, only: [json_response: 3] - require Logger + @descriptions_json Pleroma.Docs.JSON.compile() + @users_page_size 50 + plug( OAuthScopesPlug, %{scopes: ["read:accounts"], admin: true} @@ -93,8 +97,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do when action in [:relay_follow, :relay_unfollow, :config_update] ) - @users_page_size 50 - action_fallback(:errors) def user_delete(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do @@ -782,10 +784,22 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do end def migrate_from_db(conn, _params) do - Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "true"]) + Mix.Tasks.Pleroma.Config.run([ + "migrate_from_db", + "--env", + to_string(Pleroma.Config.get(:env)), + "-d" + ]) + json(conn, %{}) end + def config_descriptions(conn, _params) do + conn + |> Plug.Conn.put_resp_content_type("application/json") + |> Plug.Conn.send_resp(200, @descriptions_json) + end + def config_show(conn, _params) do configs = Pleroma.Repo.all(Config) @@ -800,17 +814,27 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do updated = Enum.map(configs, fn %{"group" => group, "key" => key, "delete" => "true"} = params -> - {:ok, config} = Config.delete(%{group: group, key: key, subkeys: params["subkeys"]}) - config + with {:ok, config} <- + Config.delete(%{group: group, key: key, subkeys: params["subkeys"]}) do + config + end %{"group" => group, "key" => key, "value" => value} -> - {:ok, config} = Config.update_or_create(%{group: group, key: key, value: value}) - config + with {:ok, config} <- + Config.update_or_create(%{group: group, key: key, value: value}) do + config + end end) |> Enum.reject(&is_nil(&1)) Pleroma.Config.TransferTask.load_and_update_env() - Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "false"]) + + Mix.Tasks.Pleroma.Config.run([ + "migrate_from_db", + "--env", + to_string(Pleroma.Config.get(:env)) + ]) + updated else [] diff --git a/lib/pleroma/web/admin_api/config.ex b/lib/pleroma/web/admin_api/config.ex index 1917a5580..a74acfbc6 100644 --- a/lib/pleroma/web/admin_api/config.ex +++ b/lib/pleroma/web/admin_api/config.ex @@ -24,6 +24,8 @@ defmodule Pleroma.Web.AdminAPI.Config do @spec changeset(Config.t(), map()) :: Changeset.t() def changeset(config, params \\ %{}) do + params = Map.put(params, :value, transform(params[:value])) + config |> cast(params, [:key, :group, :value]) |> validate_required([:key, :group, :value]) @@ -33,42 +35,43 @@ defmodule Pleroma.Web.AdminAPI.Config do @spec create(map()) :: {:ok, Config.t()} | {:error, Changeset.t()} def create(params) do %Config{} - |> changeset(Map.put(params, :value, transform(params[:value]))) + |> changeset(params) |> Repo.insert() end @spec update(Config.t(), map()) :: {:ok, Config} | {:error, Changeset.t()} def update(%Config{} = config, %{value: value}) do config - |> change(value: transform(value)) + |> changeset(%{value: value}) |> Repo.update() end @spec update_or_create(map()) :: {:ok, Config.t()} | {:error, Changeset.t()} def update_or_create(params) do - with %Config{} = config <- Config.get_by_params(Map.take(params, [:group, :key])) do + search_opts = Map.take(params, [:group, :key]) + + with %Config{} = config <- Config.get_by_params(search_opts) do Config.update(config, params) else nil -> Config.create(params) end end - @spec delete(map()) :: {:ok, Config.t()} | {:error, Changeset.t()} + @spec delete(map()) :: {:ok, Config.t()} | {:error, Changeset.t()} | {:ok, nil} def delete(params) do - with %Config{} = config <- Config.get_by_params(Map.delete(params, :subkeys)) do - if params[:subkeys] do - updated_value = - Keyword.drop( - :erlang.binary_to_term(config.value), - Enum.map(params[:subkeys], &do_transform_string(&1)) - ) - - Config.update(config, %{value: updated_value}) - else + search_opts = Map.delete(params, :subkeys) + + with %Config{} = config <- Config.get_by_params(search_opts), + {config, sub_keys} when is_list(sub_keys) <- {config, params[:subkeys]}, + old_value <- :erlang.binary_to_term(config.value), + keys <- Enum.map(sub_keys, &do_transform_string(&1)), + new_value <- Keyword.drop(old_value, keys) do + Config.update(config, %{value: new_value}) + else + {config, nil} -> Repo.delete(config) {:ok, nil} - end - else + nil -> err = dgettext("errors", "Config with params %{params} not found", params: inspect(params)) @@ -82,10 +85,22 @@ defmodule Pleroma.Web.AdminAPI.Config do @spec from_binary_with_convert(binary()) :: any() def from_binary_with_convert(binary) do - from_binary(binary) + binary + |> from_binary() |> do_convert() end + @spec from_string(String.t()) :: atom() | no_return() + def from_string(":" <> entity), do: String.to_existing_atom(entity) + + def from_string(entity) when is_binary(entity) do + if is_module_name?(entity) do + String.to_existing_atom("Elixir.#{entity}") + else + entity + end + end + defp do_convert(entity) when is_list(entity) do for v <- entity, into: [], do: do_convert(v) end @@ -97,6 +112,7 @@ defmodule Pleroma.Web.AdminAPI.Config do end defp do_convert({:dispatch, [entity]}), do: %{"tuple" => [":dispatch", [inspect(entity)]]} + # TODO: will become useless after removing hackney defp do_convert({:partial_chain, entity}), do: %{"tuple" => [":partial_chain", inspect(entity)]} defp do_convert(entity) when is_tuple(entity), @@ -105,21 +121,15 @@ defmodule Pleroma.Web.AdminAPI.Config do defp do_convert(entity) when is_boolean(entity) or is_number(entity) or is_nil(entity), do: entity - defp do_convert(entity) when is_atom(entity) do - string = to_string(entity) - - if String.starts_with?(string, "Elixir."), - do: do_convert(string), - else: ":" <> string - end - - defp do_convert("Elixir." <> module_name), do: module_name + defp do_convert(entity) when is_atom(entity), do: inspect(entity) defp do_convert(entity) when is_binary(entity), do: entity - @spec transform(any()) :: binary() + @spec transform(any()) :: binary() | no_return() def transform(entity) when is_binary(entity) or is_map(entity) or is_list(entity) do - :erlang.term_to_binary(do_transform(entity)) + entity + |> do_transform() + |> :erlang.term_to_binary() end def transform(entity), do: :erlang.term_to_binary(entity) @@ -131,6 +141,7 @@ defmodule Pleroma.Web.AdminAPI.Config do {:dispatch, [dispatch_settings]} end + # TODO: will become useless after removing hackney defp do_transform(%{"tuple" => [":partial_chain", entity]}) do {partial_chain, []} = do_eval(entity) {:partial_chain, partial_chain} @@ -149,34 +160,63 @@ defmodule Pleroma.Web.AdminAPI.Config do end defp do_transform(entity) when is_binary(entity) do - String.trim(entity) + entity + |> String.trim() |> do_transform_string() end defp do_transform(entity), do: entity - defp do_transform_string("~r/" <> pattern) do - modificator = String.split(pattern, "/") |> List.last() - pattern = String.trim_trailing(pattern, "/" <> modificator) + @delimiters ["/", "|", "\"", "'", {"(", ")"}, {"[", "]"}, {"{", "}"}, {"<", ">"}] + + defp find_valid_delimiter([], _string, _), + do: raise(ArgumentError, message: "valid delimiter for Regex expression not found") - case modificator do - "" -> ~r/#{pattern}/ - "i" -> ~r/#{pattern}/i - "u" -> ~r/#{pattern}/u - "s" -> ~r/#{pattern}/s + defp find_valid_delimiter([{leading, closing} = delimiter | others], pattern, regex_delimiter) + when is_tuple(delimiter) do + if String.contains?(pattern, closing) do + find_valid_delimiter(others, pattern, regex_delimiter) + else + {:ok, {leading, closing}} + end + end + + defp find_valid_delimiter([delimiter | others], pattern, regex_delimiter) do + if String.contains?(pattern, delimiter) do + find_valid_delimiter(others, pattern, regex_delimiter) + else + {:ok, {delimiter, delimiter}} + end + end + + @regex_parts ~r/^~r(?'delimiter'[\/|"'([{<]{1})(?'pattern'.+)[\/|"')\]}>]{1}(?'modifier'[uismxfU]*)/u + + defp do_transform_string("~r" <> _pattern = regex) do + with %{"modifier" => modifier, "pattern" => pattern, "delimiter" => regex_delimiter} <- + Regex.named_captures(@regex_parts, regex), + {:ok, {leading, closing}} <- find_valid_delimiter(@delimiters, pattern, regex_delimiter), + {result, _} <- Code.eval_string("~r#{leading}#{pattern}#{closing}#{modifier}") do + result end end defp do_transform_string(":" <> atom), do: String.to_atom(atom) defp do_transform_string(value) do - if String.starts_with?(value, "Pleroma") or String.starts_with?(value, "Phoenix"), - do: String.to_existing_atom("Elixir." <> value), - else: value + if is_module_name?(value) do + String.to_existing_atom("Elixir." <> value) + else + value + end + end + + @spec is_module_name?(String.t()) :: boolean() + def is_module_name?(string) do + Regex.match?(~r/^(Pleroma|Phoenix|Tesla)\./, string) or string in ["Oban", "Ueberauth"] end defp do_eval(entity) do cleaned_string = String.replace(entity, ~r/[^\w|^{:,[|^,|^[|^\]^}|^\/|^\.|^"]^\s/, "") - Code.eval_string(cleaned_string, [], requires: [], macros: []) + Code.eval_string(cleaned_string) end end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index f6c128283..a182e90e7 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -195,6 +195,7 @@ defmodule Pleroma.Web.Router do get("/config", AdminAPIController, :config_show) post("/config", AdminAPIController, :config_update) + get("/config/descriptions", AdminAPIController, :config_descriptions) get("/config/migrate_to_db", AdminAPIController, :migrate_to_db) get("/config/migrate_from_db", AdminAPIController, :migrate_from_db) -- cgit v1.2.3 From 0656816c77875d87d64d89e0e549f73104104cfb Mon Sep 17 00:00:00 2001 From: Alexander Date: Fri, 6 Dec 2019 08:21:30 +0300 Subject: tests for setttings without an explicit key --- lib/mix/tasks/pleroma/config.ex | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex index bb126463c..cef02b864 100644 --- a/lib/mix/tasks/pleroma/config.ex +++ b/lib/mix/tasks/pleroma/config.ex @@ -76,10 +76,10 @@ defmodule Mix.Tasks.Pleroma.Config do |> Application.get_all_env() |> Enum.reject(fn {k, _v} -> k in [Pleroma.Repo, :env] end) |> Enum.each(fn {key, value} -> - key_str = inspect(key) + key = inspect(key) + {:ok, _} = Config.update_or_create(%{group: inspect(group), key: key, value: value}) - {:ok, _} = Config.update_or_create(%{group: ":#{group}", key: key_str, value: value}) - Mix.shell().info("settings for key #{key_str} migrated.") + Mix.shell().info("settings for key #{key} migrated.") end) Mix.shell().info("settings for group :#{group} migrated.") -- cgit v1.2.3 From 5cacb988b99347b228a30743fbcf310c9479b3f9 Mon Sep 17 00:00:00 2001 From: Alexander Date: Fri, 6 Dec 2019 15:12:56 +0300 Subject: partially settings update --- lib/pleroma/web/admin_api/config.ex | 49 ++++++++++++++++++++++++++++++++----- 1 file changed, 43 insertions(+), 6 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/admin_api/config.ex b/lib/pleroma/web/admin_api/config.ex index a74acfbc6..e141a13da 100644 --- a/lib/pleroma/web/admin_api/config.ex +++ b/lib/pleroma/web/admin_api/config.ex @@ -46,14 +46,48 @@ defmodule Pleroma.Web.AdminAPI.Config do |> Repo.update() end + @full_key_update [ + {:pleroma, :ecto_repos}, + {:quack, :meta}, + {:mime, :types}, + {:cors_plug, [:max_age, :methods, :expose, :headers]}, + {:auto_linker, :opts}, + {:swarm, :node_blacklist} + ] + + defp only_full_update?(%Config{} = config) do + config_group = Config.from_string(config.group) + config_key = Config.from_string(config.key) + + Enum.any?(@full_key_update, fn + {group, key} when is_list(key) -> + config_group == group and config_key in key + + {group, key} -> + config_group == group and config_key == key + end) + end + + defp can_be_partially_updated?(%Config{} = config), do: not only_full_update?(config) + @spec update_or_create(map()) :: {:ok, Config.t()} | {:error, Changeset.t()} def update_or_create(params) do search_opts = Map.take(params, [:group, :key]) - with %Config{} = config <- Config.get_by_params(search_opts) do - Config.update(config, params) + with %Config{} = config <- Config.get_by_params(search_opts), + {:partial_update, true, config} <- + {:partial_update, can_be_partially_updated?(config), config}, + old_value <- from_binary(config.value), + transformed_value <- do_transform(params[:value]), + {:can_be_merged, true, config} <- {:can_be_merged, is_list(transformed_value), config}, + new_value <- Keyword.merge(old_value, transformed_value) do + Config.update(config, %{value: new_value, transformed?: true}) else - nil -> Config.create(params) + {reason, false, config} when reason in [:partial_update, :can_be_merged] -> + Config.update(config, params) + + nil -> + Config.create(params) end end @@ -63,7 +97,7 @@ defmodule Pleroma.Web.AdminAPI.Config do with %Config{} = config <- Config.get_by_params(search_opts), {config, sub_keys} when is_list(sub_keys) <- {config, params[:subkeys]}, - old_value <- :erlang.binary_to_term(config.value), + old_value <- from_binary(config.value), keys <- Enum.map(sub_keys, &do_transform_string(&1)), new_value <- Keyword.drop(old_value, keys) do Config.update(config, %{value: new_value}) @@ -129,10 +163,13 @@ defmodule Pleroma.Web.AdminAPI.Config do def transform(entity) when is_binary(entity) or is_map(entity) or is_list(entity) do entity |> do_transform() - |> :erlang.term_to_binary() + |> to_binary() end - def transform(entity), do: :erlang.term_to_binary(entity) + def transform(entity), do: to_binary(entity) + + @spec to_binary(any()) :: binary() + def to_binary(entity), do: :erlang.term_to_binary(entity) defp do_transform(%Regex{} = entity), do: entity -- cgit v1.2.3 From fea734ca703b686701b87c8c4c4969deb05d1f92 Mon Sep 17 00:00:00 2001 From: Alexander Date: Fri, 6 Dec 2019 17:50:53 +0300 Subject: errors on endpoints --- lib/pleroma/web/admin_api/admin_api_controller.ex | 110 ++++++++++++---------- 1 file changed, 58 insertions(+), 52 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index 376f88061..23dcbedba 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -778,71 +778,77 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do |> render("index.json", %{log: log}) end - def migrate_to_db(conn, _params) do - Mix.Tasks.Pleroma.Config.run(["migrate_to_db"]) - json(conn, %{}) - end - - def migrate_from_db(conn, _params) do - Mix.Tasks.Pleroma.Config.run([ - "migrate_from_db", - "--env", - to_string(Pleroma.Config.get(:env)), - "-d" - ]) - - json(conn, %{}) - end - def config_descriptions(conn, _params) do conn |> Plug.Conn.put_resp_content_type("application/json") |> Plug.Conn.send_resp(200, @descriptions_json) end - def config_show(conn, _params) do - configs = Pleroma.Repo.all(Config) + def migrate_from_db(conn, _params) do + with :ok <- check_dynamic_configuration(conn) do + Mix.Tasks.Pleroma.Config.run([ + "migrate_from_db", + "--env", + to_string(Pleroma.Config.get(:env)), + "-d" + ]) - conn - |> put_view(ConfigView) - |> render("index.json", %{configs: configs}) + json(conn, %{}) + end end - def config_update(conn, %{"configs" => configs}) do - updated = - if Pleroma.Config.get([:instance, :dynamic_configuration]) do - updated = - Enum.map(configs, fn - %{"group" => group, "key" => key, "delete" => "true"} = params -> - with {:ok, config} <- - Config.delete(%{group: group, key: key, subkeys: params["subkeys"]}) do - config - end - - %{"group" => group, "key" => key, "value" => value} -> - with {:ok, config} <- - Config.update_or_create(%{group: group, key: key, value: value}) do - config - end - end) - |> Enum.reject(&is_nil(&1)) - - Pleroma.Config.TransferTask.load_and_update_env() - - Mix.Tasks.Pleroma.Config.run([ - "migrate_from_db", - "--env", - to_string(Pleroma.Config.get(:env)) - ]) + def config_show(conn, _params) do + with :ok <- check_dynamic_configuration(conn) do + configs = Pleroma.Repo.all(Config) - updated + if configs == [] do + errors(conn, {:error, "To use dynamic configuration migrate your settings to database."}) else - [] + conn + |> put_view(ConfigView) + |> render("index.json", %{configs: configs}) end + end + end - conn - |> put_view(ConfigView) - |> render("index.json", %{configs: updated}) + def config_update(conn, %{"configs" => configs}) do + with :ok <- check_dynamic_configuration(conn) do + updated = + Enum.map(configs, fn + %{"group" => group, "key" => key, "delete" => "true"} = params -> + with {:ok, config} <- + Config.delete(%{group: group, key: key, subkeys: params["subkeys"]}) do + config + end + + %{"group" => group, "key" => key, "value" => value} -> + with {:ok, config} <- + Config.update_or_create(%{group: group, key: key, value: value}) do + config + end + end) + |> Enum.reject(&is_nil(&1)) + + Pleroma.Config.TransferTask.load_and_update_env() + + Mix.Tasks.Pleroma.Config.run([ + "migrate_from_db", + "--env", + to_string(Pleroma.Config.get(:env)) + ]) + + conn + |> put_view(ConfigView) + |> render("index.json", %{configs: updated}) + end + end + + defp check_dynamic_configuration(conn) do + if Pleroma.Config.get([:instance, :dynamic_configuration]) do + :ok + else + errors(conn, {:error, "To use this endpoint you need to enable dynamic configuration."}) + end end def reload_emoji(conn, _params) do -- cgit v1.2.3 From a71393dd29488eb86d6da23250b05a9b5b04eb81 Mon Sep 17 00:00:00 2001 From: Alexander Date: Tue, 10 Dec 2019 12:00:40 +0300 Subject: fix for endpoints after env update --- lib/mix/tasks/pleroma/config.ex | 4 +++- lib/pleroma/config/transfer_task.ex | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex index cef02b864..257a0dfe5 100644 --- a/lib/mix/tasks/pleroma/config.ex +++ b/lib/mix/tasks/pleroma/config.ex @@ -74,7 +74,9 @@ defmodule Mix.Tasks.Pleroma.Config do defp load_and_create(group) do group |> Application.get_all_env() - |> Enum.reject(fn {k, _v} -> k in [Pleroma.Repo, :env] end) + |> Enum.reject(fn {k, _v} -> + k in [Pleroma.Repo, :env] or (group == :phoenix and k == :serve_endpoints) + end) |> Enum.each(fn {key, value} -> key = inspect(key) {:ok, _} = Config.update_or_create(%{group: inspect(group), key: key, value: value}) diff --git a/lib/pleroma/config/transfer_task.ex b/lib/pleroma/config/transfer_task.ex index 0bc4c4029..c89c1fcc8 100644 --- a/lib/pleroma/config/transfer_task.ex +++ b/lib/pleroma/config/transfer_task.ex @@ -37,7 +37,9 @@ defmodule Pleroma.Config.TransferTask do group = Config.from_string(setting.group) value = Config.from_binary(setting.value) - :ok = Application.put_env(group, key, value) + if group != :phoenix and key != :serve_endpoints do + :ok = Application.put_env(group, key, value) + end group rescue -- cgit v1.2.3 From 583cee46072cda6b3ed07f4ce09b09db9e2b0af1 Mon Sep 17 00:00:00 2001 From: Alexander Date: Tue, 17 Dec 2019 19:51:01 +0300 Subject: parsing proxy url setting --- lib/pleroma/web/admin_api/config.ex | 45 +++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) (limited to 'lib') diff --git a/lib/pleroma/web/admin_api/config.ex b/lib/pleroma/web/admin_api/config.ex index e141a13da..acc5a5183 100644 --- a/lib/pleroma/web/admin_api/config.ex +++ b/lib/pleroma/web/admin_api/config.ex @@ -145,6 +145,33 @@ defmodule Pleroma.Web.AdminAPI.Config do for {k, v} <- entity, into: %{}, do: {do_convert(k), do_convert(v)} end + defp do_convert({:proxy_url, {type, :localhost, port}}) do + %{"tuple" => [":proxy_url", %{"tuple" => [do_convert(type), "localhost", port]}]} + end + + defp do_convert({:proxy_url, {type, host, port}}) when is_tuple(host) do + ip = + host + |> :inet_parse.ntoa() + |> to_string() + + %{ + "tuple" => [ + ":proxy_url", + %{"tuple" => [do_convert(type), ip, port]} + ] + } + end + + defp do_convert({:proxy_url, {type, host, port}}) do + %{ + "tuple" => [ + ":proxy_url", + %{"tuple" => [do_convert(type), to_string(host), port]} + ] + } + end + defp do_convert({:dispatch, [entity]}), do: %{"tuple" => [":dispatch", [inspect(entity)]]} # TODO: will become useless after removing hackney defp do_convert({:partial_chain, entity}), do: %{"tuple" => [":partial_chain", inspect(entity)]} @@ -173,6 +200,10 @@ defmodule Pleroma.Web.AdminAPI.Config do defp do_transform(%Regex{} = entity), do: entity + defp do_transform(%{"tuple" => [":proxy_url", %{"tuple" => [type, host, port]}]}) do + {:proxy_url, {do_transform_string(type), parse_host(host), port}} + end + defp do_transform(%{"tuple" => [":dispatch", [entity]]}) do {dispatch_settings, []} = do_eval(entity) {:dispatch, [dispatch_settings]} @@ -204,6 +235,20 @@ defmodule Pleroma.Web.AdminAPI.Config do defp do_transform(entity), do: entity + defp parse_host("localhost"), do: :localhost + + defp parse_host(host) do + charlist = to_charlist(host) + + case :inet.parse_address(charlist) do + {:error, :einval} -> + charlist + + {:ok, ip} -> + ip + end + end + @delimiters ["/", "|", "\"", "'", {"(", ")"}, {"[", "]"}, {"{", "}"}, {"<", ">"}] defp find_valid_delimiter([], _string, _), -- cgit v1.2.3 From 063ab6d9115ec17bd1907d42a998f335a0cd69c9 Mon Sep 17 00:00:00 2001 From: Alexander Date: Thu, 19 Dec 2019 10:19:56 +0300 Subject: logger backends fix --- lib/pleroma/web/admin_api/config.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/admin_api/config.ex b/lib/pleroma/web/admin_api/config.ex index acc5a5183..a8a698c89 100644 --- a/lib/pleroma/web/admin_api/config.ex +++ b/lib/pleroma/web/admin_api/config.ex @@ -52,7 +52,8 @@ defmodule Pleroma.Web.AdminAPI.Config do {:mime, :types}, {:cors_plug, [:max_age, :methods, :expose, :headers]}, {:auto_linker, :opts}, - {:swarm, :node_blacklist} + {:swarm, :node_blacklist}, + {:logger, :backends} ] defp only_full_update?(%Config{} = config) do -- cgit v1.2.3 From cda2c1fc630e455b3d419f5c3b22c366dc883ce1 Mon Sep 17 00:00:00 2001 From: Alexander Date: Fri, 20 Dec 2019 10:22:53 +0300 Subject: fix for subgroup tuple added settings for swoosh adapters local --- lib/pleroma/docs/generator.ex | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'lib') diff --git a/lib/pleroma/docs/generator.ex b/lib/pleroma/docs/generator.ex index b57e47e8b..6b12dcdd9 100644 --- a/lib/pleroma/docs/generator.ex +++ b/lib/pleroma/docs/generator.ex @@ -58,15 +58,23 @@ defmodule Pleroma.Docs.Generator do entity |> Map.put(:suggestions, format_suggestions(suggestions)) |> format_key() + |> format_group() |> format_children() end defp format_child(entity) do entity |> format_key() + |> format_group() |> format_children() end + defp format_group(%{group: group} = entity) do + Map.put(entity, :group, format_suggestion(group)) + end + + defp format_group(entity), do: entity + defp atom_to_string(entity) when is_binary(entity), do: entity defp atom_to_string(entity) when is_atom(entity), do: inspect(entity) -- cgit v1.2.3 From 9c1f3bfeffa7e40d319d931c975b948f33800c40 Mon Sep 17 00:00:00 2001 From: Alexander Date: Sat, 21 Dec 2019 13:54:22 +0300 Subject: fixes for logger backends --- lib/pleroma/web/admin_api/config.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/admin_api/config.ex b/lib/pleroma/web/admin_api/config.ex index a8a698c89..ef658e079 100644 --- a/lib/pleroma/web/admin_api/config.ex +++ b/lib/pleroma/web/admin_api/config.ex @@ -295,7 +295,8 @@ defmodule Pleroma.Web.AdminAPI.Config do @spec is_module_name?(String.t()) :: boolean() def is_module_name?(string) do - Regex.match?(~r/^(Pleroma|Phoenix|Tesla)\./, string) or string in ["Oban", "Ueberauth"] + Regex.match?(~r/^(Pleroma|Phoenix|Tesla|Quack)\./, string) or + string in ["Oban", "Ueberauth", "ExSyslogger"] end defp do_eval(entity) do -- cgit v1.2.3 From 0b020403276519da84dce51053240ac6637eb1b3 Mon Sep 17 00:00:00 2001 From: Alexander Date: Wed, 25 Dec 2019 15:31:51 +0300 Subject: little fixes and typos fix --- lib/pleroma/web/admin_api/config.ex | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'lib') diff --git a/lib/pleroma/web/admin_api/config.ex b/lib/pleroma/web/admin_api/config.ex index ef658e079..b55851602 100644 --- a/lib/pleroma/web/admin_api/config.ex +++ b/lib/pleroma/web/admin_api/config.ex @@ -183,6 +183,11 @@ defmodule Pleroma.Web.AdminAPI.Config do defp do_convert(entity) when is_boolean(entity) or is_number(entity) or is_nil(entity), do: entity + defp do_convert(entity) + when is_atom(entity) and entity in [:"tlsv1.1", :"tlsv1.2", :"tlsv1.3"] do + ":#{to_string(entity)}" + end + defp do_convert(entity) when is_atom(entity), do: inspect(entity) defp do_convert(entity) when is_binary(entity), do: entity -- cgit v1.2.3 From c841174de820c891929b206e3eb2604cb6368ae6 Mon Sep 17 00:00:00 2001 From: Alexander Date: Thu, 26 Dec 2019 10:05:30 +0300 Subject: flag for delete fix --- lib/pleroma/web/admin_api/admin_api_controller.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index 23dcbedba..d12ed459d 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -815,7 +815,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do with :ok <- check_dynamic_configuration(conn) do updated = Enum.map(configs, fn - %{"group" => group, "key" => key, "delete" => "true"} = params -> + %{"group" => group, "key" => key, "delete" => true} = params -> with {:ok, config} <- Config.delete(%{group: group, key: key, subkeys: params["subkeys"]}) do config -- cgit v1.2.3 From 88a16bb9fcd1f80b8a2634e815cb855d3a8346ee Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Mon, 6 Jan 2020 14:05:32 +0300 Subject: deep merge in config update --- lib/pleroma/web/admin_api/config.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/admin_api/config.ex b/lib/pleroma/web/admin_api/config.ex index b55851602..a01c28716 100644 --- a/lib/pleroma/web/admin_api/config.ex +++ b/lib/pleroma/web/admin_api/config.ex @@ -81,7 +81,7 @@ defmodule Pleroma.Web.AdminAPI.Config do old_value <- from_binary(config.value), transformed_value <- do_transform(params[:value]), {:can_be_merged, true, config} <- {:can_be_merged, is_list(transformed_value), config}, - new_value <- Keyword.merge(old_value, transformed_value) do + new_value <- DeepMerge.deep_merge(old_value, transformed_value) do Config.update(config, %{value: new_value, transformed?: true}) else {reason, false, config} when reason in [:partial_update, :can_be_merged] -> -- cgit v1.2.3 From 958d0452e4e52f44301e4d9211fc965c1c184363 Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Fri, 10 Jan 2020 18:18:09 +0300 Subject: fix for pleroma assets group update --- lib/pleroma/web/admin_api/config.ex | 1 + 1 file changed, 1 insertion(+) (limited to 'lib') diff --git a/lib/pleroma/web/admin_api/config.ex b/lib/pleroma/web/admin_api/config.ex index a01c28716..49f0f8223 100644 --- a/lib/pleroma/web/admin_api/config.ex +++ b/lib/pleroma/web/admin_api/config.ex @@ -48,6 +48,7 @@ defmodule Pleroma.Web.AdminAPI.Config do @full_key_update [ {:pleroma, :ecto_repos}, + {:pleroma, :assets}, {:quack, :meta}, {:mime, :types}, {:cors_plug, [:max_age, :methods, :expose, :headers]}, -- cgit v1.2.3 From 7d128ca2083d83486a05d8c4456aa4090006e781 Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Fri, 10 Jan 2020 19:34:19 +0300 Subject: dynamic_configuration renaming and moving it from instance settings --- lib/mix/tasks/pleroma/config.ex | 5 +++-- lib/pleroma/config/transfer_task.ex | 2 +- lib/pleroma/web/admin_api/admin_api_controller.ex | 10 +++++----- 3 files changed, 9 insertions(+), 8 deletions(-) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex index 257a0dfe5..3f80af5a7 100644 --- a/lib/mix/tasks/pleroma/config.ex +++ b/lib/mix/tasks/pleroma/config.ex @@ -30,7 +30,7 @@ defmodule Mix.Tasks.Pleroma.Config do def run(["migrate_to_db"]) do start_pleroma() - if Pleroma.Config.get([:instance, :dynamic_configuration]) do + if Pleroma.Config.get([:configurable_from_database]) do Enum.each(@groups, &load_and_create(&1)) else Mix.shell().info( @@ -48,7 +48,8 @@ defmodule Mix.Tasks.Pleroma.Config do aliases: [d: :delete_from_db] ) - with {:active?, true} <- {:active?, Pleroma.Config.get([:instance, :dynamic_configuration])}, + with {:active?, true} <- + {:active?, Pleroma.Config.get([:configurable_from_database])}, env_path when is_binary(env_path) <- opts[:env], config_path <- "config/#{env_path}.exported_from_db.secret.exs", {:ok, file} <- File.open(config_path, [:write, :utf8]) do diff --git a/lib/pleroma/config/transfer_task.ex b/lib/pleroma/config/transfer_task.ex index c89c1fcc8..75c4aa739 100644 --- a/lib/pleroma/config/transfer_task.ex +++ b/lib/pleroma/config/transfer_task.ex @@ -17,7 +17,7 @@ defmodule Pleroma.Config.TransferTask do end def load_and_update_env do - with true <- Pleroma.Config.get([:instance, :dynamic_configuration]), + with true <- Pleroma.Config.get([:configurable_from_database]), true <- Ecto.Adapters.SQL.table_exists?(Repo, "config"), started_applications <- Application.started_applications() do # We need to restart applications for loaded settings take effect diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index d12ed459d..849641113 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -785,7 +785,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do end def migrate_from_db(conn, _params) do - with :ok <- check_dynamic_configuration(conn) do + with :ok <- configurable_from_database(conn) do Mix.Tasks.Pleroma.Config.run([ "migrate_from_db", "--env", @@ -798,7 +798,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do end def config_show(conn, _params) do - with :ok <- check_dynamic_configuration(conn) do + with :ok <- configurable_from_database(conn) do configs = Pleroma.Repo.all(Config) if configs == [] do @@ -812,7 +812,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do end def config_update(conn, %{"configs" => configs}) do - with :ok <- check_dynamic_configuration(conn) do + with :ok <- configurable_from_database(conn) do updated = Enum.map(configs, fn %{"group" => group, "key" => key, "delete" => true} = params -> @@ -843,8 +843,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do end end - defp check_dynamic_configuration(conn) do - if Pleroma.Config.get([:instance, :dynamic_configuration]) do + defp configurable_from_database(conn) do + if Pleroma.Config.get([:configurable_from_database]) do :ok else errors(conn, {:error, "To use this endpoint you need to enable dynamic configuration."}) -- cgit v1.2.3 From eadb674c41dea27346aad43fa5514aa4f461273b Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Fri, 10 Jan 2020 19:44:30 +0300 Subject: don't migrate configurable_from_database setting --- lib/mix/tasks/pleroma/config.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex index 3f80af5a7..e7c6e8810 100644 --- a/lib/mix/tasks/pleroma/config.ex +++ b/lib/mix/tasks/pleroma/config.ex @@ -76,7 +76,8 @@ defmodule Mix.Tasks.Pleroma.Config do group |> Application.get_all_env() |> Enum.reject(fn {k, _v} -> - k in [Pleroma.Repo, :env] or (group == :phoenix and k == :serve_endpoints) + k in [Pleroma.Repo, :env, :configurable_from_database] or + (group == :phoenix and k == :serve_endpoints) end) |> Enum.each(fn {key, value} -> key = inspect(key) -- cgit v1.2.3 From d933fd3d61df2f9c346ab08fb2c95ddc12803858 Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Fri, 10 Jan 2020 19:49:40 +0300 Subject: more renamings --- lib/pleroma/web/admin_api/admin_api_controller.ex | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index 849641113..a13dd4775 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -802,7 +802,10 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do configs = Pleroma.Repo.all(Config) if configs == [] do - errors(conn, {:error, "To use dynamic configuration migrate your settings to database."}) + errors( + conn, + {:error, "To use configuration from database migrate your settings to database."} + ) else conn |> put_view(ConfigView) @@ -847,7 +850,10 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do if Pleroma.Config.get([:configurable_from_database]) do :ok else - errors(conn, {:error, "To use this endpoint you need to enable dynamic configuration."}) + errors( + conn, + {:error, "To use this endpoint you need to enable configuration from database."} + ) end end -- cgit v1.2.3 From 3453b27015dfa26bc6fd59899541eadfce2cacee Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Fri, 10 Jan 2020 21:24:06 +0300 Subject: don't migrate prometheus settings --- lib/mix/tasks/pleroma/config.ex | 1 - 1 file changed, 1 deletion(-) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex index e7c6e8810..b69c49b45 100644 --- a/lib/mix/tasks/pleroma/config.ex +++ b/lib/mix/tasks/pleroma/config.ex @@ -21,7 +21,6 @@ defmodule Mix.Tasks.Pleroma.Config do :auto_linker, :esshd, :ueberauth, - :prometheus, :http_signatures, :web_push_encryption, :joken -- cgit v1.2.3 From 66de2b159b2e03fcac7fece01d39e1238e02dbf7 Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Wed, 15 Jan 2020 17:08:45 +0300 Subject: don't migrate Pleroma.Web.EndPoint settings don't set Logger level to all tasks --- lib/mix/pleroma.ex | 7 ++++--- lib/mix/tasks/pleroma/config.ex | 5 +++-- 2 files changed, 7 insertions(+), 5 deletions(-) (limited to 'lib') diff --git a/lib/mix/pleroma.ex b/lib/mix/pleroma.ex index 73a076a53..ea9923440 100644 --- a/lib/mix/pleroma.ex +++ b/lib/mix/pleroma.ex @@ -4,11 +4,12 @@ defmodule Mix.Pleroma do @doc "Common functions to be reused in mix tasks" - def start_pleroma do + def start_pleroma(change_logger_level \\ true) do Application.put_env(:phoenix, :serve_endpoints, false, persistent: true) - if Pleroma.Config.get(:env) != :test do - Application.put_env(:logger, :console, level: :debug) + if Pleroma.Config.get(:env) != :test and change_logger_level do + require Logger + Logger.configure(level: :debug) end {:ok, _} = Application.ensure_all_started(:pleroma) diff --git a/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex index b69c49b45..92487dd51 100644 --- a/lib/mix/tasks/pleroma/config.ex +++ b/lib/mix/tasks/pleroma/config.ex @@ -27,7 +27,8 @@ defmodule Mix.Tasks.Pleroma.Config do ] def run(["migrate_to_db"]) do - start_pleroma() + # we want to save original logger level + start_pleroma(false) if Pleroma.Config.get([:configurable_from_database]) do Enum.each(@groups, &load_and_create(&1)) @@ -75,7 +76,7 @@ defmodule Mix.Tasks.Pleroma.Config do group |> Application.get_all_env() |> Enum.reject(fn {k, _v} -> - k in [Pleroma.Repo, :env, :configurable_from_database] or + k in [Pleroma.Repo, Pleroma.Web.Endpoint, :env, :configurable_from_database] or (group == :phoenix and k == :serve_endpoints) end) |> Enum.each(fn {key, value} -> -- cgit v1.2.3 From 59ba5c80b97b3cef2286716573d9697e82dd2a94 Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Wed, 15 Jan 2020 17:10:33 +0300 Subject: little cleanup --- lib/pleroma/config/transfer_task.ex | 2 +- lib/pleroma/web/admin_api/admin_api_controller.ex | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/config/transfer_task.ex b/lib/pleroma/config/transfer_task.ex index 75c4aa739..56abfe2a5 100644 --- a/lib/pleroma/config/transfer_task.ex +++ b/lib/pleroma/config/transfer_task.ex @@ -17,7 +17,7 @@ defmodule Pleroma.Config.TransferTask do end def load_and_update_env do - with true <- Pleroma.Config.get([:configurable_from_database]), + with true <- Pleroma.Config.get(:configurable_from_database), true <- Ecto.Adapters.SQL.table_exists?(Repo, "config"), started_applications <- Application.started_applications() do # We need to restart applications for loaded settings take effect diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index ce5411e8a..99fcc080c 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -847,7 +847,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do end defp configurable_from_database(conn) do - if Pleroma.Config.get([:configurable_from_database]) do + if Pleroma.Config.get(:configurable_from_database) do :ok else errors( -- cgit v1.2.3 From 29155137fdae15fccfaa68fb9c954e98078ce0c4 Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Thu, 16 Jan 2020 08:50:27 +0300 Subject: renaming Pleroma.Web.AdminAPI.Config -> Pleroma.ConfigDB --- lib/mix/tasks/pleroma/config.ex | 8 +- lib/pleroma/config/config_db.ex | 312 ++++++++++++++++++++++ lib/pleroma/config/transfer_task.ex | 14 +- lib/pleroma/web/admin_api/admin_api_controller.ex | 8 +- lib/pleroma/web/admin_api/config.ex | 312 ---------------------- lib/pleroma/web/admin_api/views/config_view.ex | 2 +- 6 files changed, 328 insertions(+), 328 deletions(-) create mode 100644 lib/pleroma/config/config_db.ex delete mode 100644 lib/pleroma/web/admin_api/config.ex (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex index 92487dd51..57952aeba 100644 --- a/lib/mix/tasks/pleroma/config.ex +++ b/lib/mix/tasks/pleroma/config.ex @@ -5,8 +5,8 @@ defmodule Mix.Tasks.Pleroma.Config do use Mix.Task import Mix.Pleroma + alias Pleroma.ConfigDB alias Pleroma.Repo - alias Pleroma.Web.AdminAPI.Config @shortdoc "Manages the location of the config" @moduledoc File.read!("docs/administration/CLI_tasks/config.md") @@ -55,7 +55,7 @@ defmodule Mix.Tasks.Pleroma.Config do {:ok, file} <- File.open(config_path, [:write, :utf8]) do IO.write(file, "use Mix.Config\r\n") - Config + ConfigDB |> Repo.all() |> Enum.each(&write_to_file_with_deletion(&1, file, opts[:delete_from_db])) @@ -81,7 +81,7 @@ defmodule Mix.Tasks.Pleroma.Config do end) |> Enum.each(fn {key, value} -> key = inspect(key) - {:ok, _} = Config.update_or_create(%{group: inspect(group), key: key, value: value}) + {:ok, _} = ConfigDB.update_or_create(%{group: inspect(group), key: key, value: value}) Mix.shell().info("settings for key #{key} migrated.") end) @@ -93,7 +93,7 @@ defmodule Mix.Tasks.Pleroma.Config do IO.write( file, "config #{config.group}, #{config.key}, #{ - inspect(Config.from_binary(config.value), limit: :infinity) + inspect(ConfigDB.from_binary(config.value), limit: :infinity) }\r\n\r\n" ) diff --git a/lib/pleroma/config/config_db.ex b/lib/pleroma/config/config_db.ex new file mode 100644 index 000000000..e433ce442 --- /dev/null +++ b/lib/pleroma/config/config_db.ex @@ -0,0 +1,312 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.ConfigDB do + use Ecto.Schema + import Ecto.Changeset + import Pleroma.Web.Gettext + alias __MODULE__ + alias Pleroma.Repo + + @type t :: %__MODULE__{} + + schema "config" do + field(:key, :string) + field(:group, :string) + field(:value, :binary) + + timestamps() + end + + @spec get_by_params(map()) :: ConfigDB.t() | nil + def get_by_params(params), do: Repo.get_by(ConfigDB, params) + + @spec changeset(ConfigDB.t(), map()) :: Changeset.t() + def changeset(config, params \\ %{}) do + params = Map.put(params, :value, transform(params[:value])) + + config + |> cast(params, [:key, :group, :value]) + |> validate_required([:key, :group, :value]) + |> unique_constraint(:key, name: :config_group_key_index) + end + + @spec create(map()) :: {:ok, ConfigDB.t()} | {:error, Changeset.t()} + def create(params) do + %ConfigDB{} + |> changeset(params) + |> Repo.insert() + end + + @spec update(ConfigDB.t(), map()) :: {:ok, ConfigDB.t()} | {:error, Changeset.t()} + def update(%ConfigDB{} = config, %{value: value}) do + config + |> changeset(%{value: value}) + |> Repo.update() + end + + @full_key_update [ + {:pleroma, :ecto_repos}, + {:pleroma, :assets}, + {:quack, :meta}, + {:mime, :types}, + {:cors_plug, [:max_age, :methods, :expose, :headers]}, + {:auto_linker, :opts}, + {:swarm, :node_blacklist}, + {:logger, :backends} + ] + + defp only_full_update?(%ConfigDB{} = config) do + config_group = ConfigDB.from_string(config.group) + config_key = ConfigDB.from_string(config.key) + + Enum.any?(@full_key_update, fn + {group, key} when is_list(key) -> + config_group == group and config_key in key + + {group, key} -> + config_group == group and config_key == key + end) + end + + defp can_be_partially_updated?(%ConfigDB{} = config), do: not only_full_update?(config) + + @spec update_or_create(map()) :: {:ok, ConfigDB.t()} | {:error, Changeset.t()} + def update_or_create(params) do + search_opts = Map.take(params, [:group, :key]) + + with %ConfigDB{} = config <- ConfigDB.get_by_params(search_opts), + {:partial_update, true, config} <- + {:partial_update, can_be_partially_updated?(config), config}, + old_value <- from_binary(config.value), + transformed_value <- do_transform(params[:value]), + {:can_be_merged, true, config} <- {:can_be_merged, is_list(transformed_value), config}, + new_value <- DeepMerge.deep_merge(old_value, transformed_value) do + ConfigDB.update(config, %{value: new_value, transformed?: true}) + else + {reason, false, config} when reason in [:partial_update, :can_be_merged] -> + ConfigDB.update(config, params) + + nil -> + ConfigDB.create(params) + end + end + + @spec delete(map()) :: {:ok, ConfigDB.t()} | {:error, Changeset.t()} | {:ok, nil} + def delete(params) do + search_opts = Map.delete(params, :subkeys) + + with %ConfigDB{} = config <- ConfigDB.get_by_params(search_opts), + {config, sub_keys} when is_list(sub_keys) <- {config, params[:subkeys]}, + old_value <- from_binary(config.value), + keys <- Enum.map(sub_keys, &do_transform_string(&1)), + new_value <- Keyword.drop(old_value, keys) do + ConfigDB.update(config, %{value: new_value}) + else + {config, nil} -> + Repo.delete(config) + {:ok, nil} + + nil -> + err = + dgettext("errors", "Config with params %{params} not found", params: inspect(params)) + + {:error, err} + end + end + + @spec from_binary(binary()) :: term() + def from_binary(binary), do: :erlang.binary_to_term(binary) + + @spec from_binary_with_convert(binary()) :: any() + def from_binary_with_convert(binary) do + binary + |> from_binary() + |> do_convert() + end + + @spec from_string(String.t()) :: atom() | no_return() + def from_string(":" <> entity), do: String.to_existing_atom(entity) + + def from_string(entity) when is_binary(entity) do + if is_module_name?(entity) do + String.to_existing_atom("Elixir.#{entity}") + else + entity + end + end + + defp do_convert(entity) when is_list(entity) do + for v <- entity, into: [], do: do_convert(v) + end + + defp do_convert(%Regex{} = entity), do: inspect(entity) + + defp do_convert(entity) when is_map(entity) do + for {k, v} <- entity, into: %{}, do: {do_convert(k), do_convert(v)} + end + + defp do_convert({:proxy_url, {type, :localhost, port}}) do + %{"tuple" => [":proxy_url", %{"tuple" => [do_convert(type), "localhost", port]}]} + end + + defp do_convert({:proxy_url, {type, host, port}}) when is_tuple(host) do + ip = + host + |> :inet_parse.ntoa() + |> to_string() + + %{ + "tuple" => [ + ":proxy_url", + %{"tuple" => [do_convert(type), ip, port]} + ] + } + end + + defp do_convert({:proxy_url, {type, host, port}}) do + %{ + "tuple" => [ + ":proxy_url", + %{"tuple" => [do_convert(type), to_string(host), port]} + ] + } + end + + defp do_convert({:dispatch, [entity]}), do: %{"tuple" => [":dispatch", [inspect(entity)]]} + # TODO: will become useless after removing hackney + defp do_convert({:partial_chain, entity}), do: %{"tuple" => [":partial_chain", inspect(entity)]} + + defp do_convert(entity) when is_tuple(entity), + do: %{"tuple" => do_convert(Tuple.to_list(entity))} + + defp do_convert(entity) when is_boolean(entity) or is_number(entity) or is_nil(entity), + do: entity + + defp do_convert(entity) + when is_atom(entity) and entity in [:"tlsv1.1", :"tlsv1.2", :"tlsv1.3"] do + ":#{to_string(entity)}" + end + + defp do_convert(entity) when is_atom(entity), do: inspect(entity) + + defp do_convert(entity) when is_binary(entity), do: entity + + @spec transform(any()) :: binary() | no_return() + def transform(entity) when is_binary(entity) or is_map(entity) or is_list(entity) do + entity + |> do_transform() + |> to_binary() + end + + def transform(entity), do: to_binary(entity) + + @spec to_binary(any()) :: binary() + def to_binary(entity), do: :erlang.term_to_binary(entity) + + defp do_transform(%Regex{} = entity), do: entity + + defp do_transform(%{"tuple" => [":proxy_url", %{"tuple" => [type, host, port]}]}) do + {:proxy_url, {do_transform_string(type), parse_host(host), port}} + end + + defp do_transform(%{"tuple" => [":dispatch", [entity]]}) do + {dispatch_settings, []} = do_eval(entity) + {:dispatch, [dispatch_settings]} + end + + # TODO: will become useless after removing hackney + defp do_transform(%{"tuple" => [":partial_chain", entity]}) do + {partial_chain, []} = do_eval(entity) + {:partial_chain, partial_chain} + end + + defp do_transform(%{"tuple" => entity}) do + Enum.reduce(entity, {}, fn val, acc -> Tuple.append(acc, do_transform(val)) end) + end + + defp do_transform(entity) when is_map(entity) do + for {k, v} <- entity, into: %{}, do: {do_transform(k), do_transform(v)} + end + + defp do_transform(entity) when is_list(entity) do + for v <- entity, into: [], do: do_transform(v) + end + + defp do_transform(entity) when is_binary(entity) do + entity + |> String.trim() + |> do_transform_string() + end + + defp do_transform(entity), do: entity + + defp parse_host("localhost"), do: :localhost + + defp parse_host(host) do + charlist = to_charlist(host) + + case :inet.parse_address(charlist) do + {:error, :einval} -> + charlist + + {:ok, ip} -> + ip + end + end + + @delimiters ["/", "|", "\"", "'", {"(", ")"}, {"[", "]"}, {"{", "}"}, {"<", ">"}] + + defp find_valid_delimiter([], _string, _), + do: raise(ArgumentError, message: "valid delimiter for Regex expression not found") + + defp find_valid_delimiter([{leading, closing} = delimiter | others], pattern, regex_delimiter) + when is_tuple(delimiter) do + if String.contains?(pattern, closing) do + find_valid_delimiter(others, pattern, regex_delimiter) + else + {:ok, {leading, closing}} + end + end + + defp find_valid_delimiter([delimiter | others], pattern, regex_delimiter) do + if String.contains?(pattern, delimiter) do + find_valid_delimiter(others, pattern, regex_delimiter) + else + {:ok, {delimiter, delimiter}} + end + end + + @regex_parts ~r/^~r(?'delimiter'[\/|"'([{<]{1})(?'pattern'.+)[\/|"')\]}>]{1}(?'modifier'[uismxfU]*)/u + + defp do_transform_string("~r" <> _pattern = regex) do + with %{"modifier" => modifier, "pattern" => pattern, "delimiter" => regex_delimiter} <- + Regex.named_captures(@regex_parts, regex), + {:ok, {leading, closing}} <- find_valid_delimiter(@delimiters, pattern, regex_delimiter), + {result, _} <- Code.eval_string("~r#{leading}#{pattern}#{closing}#{modifier}") do + result + end + end + + defp do_transform_string(":" <> atom), do: String.to_atom(atom) + + defp do_transform_string(value) do + if is_module_name?(value) do + String.to_existing_atom("Elixir." <> value) + else + value + end + end + + @spec is_module_name?(String.t()) :: boolean() + def is_module_name?(string) do + Regex.match?(~r/^(Pleroma|Phoenix|Tesla|Quack)\./, string) or + string in ["Oban", "Ueberauth", "ExSyslogger"] + end + + defp do_eval(entity) do + cleaned_string = String.replace(entity, ~r/[^\w|^{:,[|^,|^[|^\]^}|^\/|^\.|^"]^\s/, "") + Code.eval_string(cleaned_string) + end +end diff --git a/lib/pleroma/config/transfer_task.ex b/lib/pleroma/config/transfer_task.ex index 56abfe2a5..b39f9594e 100644 --- a/lib/pleroma/config/transfer_task.ex +++ b/lib/pleroma/config/transfer_task.ex @@ -5,10 +5,10 @@ defmodule Pleroma.Config.TransferTask do use Task - require Logger - + alias Pleroma.ConfigDB alias Pleroma.Repo - alias Pleroma.Web.AdminAPI.Config + + require Logger def start_link(_) do load_and_update_env() @@ -21,7 +21,7 @@ defmodule Pleroma.Config.TransferTask do true <- Ecto.Adapters.SQL.table_exists?(Repo, "config"), started_applications <- Application.started_applications() do # We need to restart applications for loaded settings take effect - Config + ConfigDB |> Repo.all() |> Enum.map(&update_env(&1)) |> Enum.uniq() @@ -33,9 +33,9 @@ defmodule Pleroma.Config.TransferTask do defp update_env(setting) do try do - key = Config.from_string(setting.key) - group = Config.from_string(setting.group) - value = Config.from_binary(setting.value) + key = ConfigDB.from_string(setting.key) + group = ConfigDB.from_string(setting.group) + value = ConfigDB.from_binary(setting.value) if group != :phoenix and key != :serve_endpoints do :ok = Application.put_env(group, key, value) diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index 99fcc080c..e1d16c30b 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -8,6 +8,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do import Pleroma.Web.ControllerHelper, only: [json_response: 3] alias Pleroma.Activity + alias Pleroma.ConfigDB alias Pleroma.ModerationLog alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.ReportNote @@ -17,7 +18,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do alias Pleroma.Web.ActivityPub.Relay alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.AdminAPI.AccountView - alias Pleroma.Web.AdminAPI.Config alias Pleroma.Web.AdminAPI.ConfigView alias Pleroma.Web.AdminAPI.ModerationLogView alias Pleroma.Web.AdminAPI.Report @@ -799,7 +799,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do def config_show(conn, _params) do with :ok <- configurable_from_database(conn) do - configs = Pleroma.Repo.all(Config) + configs = Pleroma.Repo.all(ConfigDB) if configs == [] do errors( @@ -820,13 +820,13 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do Enum.map(configs, fn %{"group" => group, "key" => key, "delete" => true} = params -> with {:ok, config} <- - Config.delete(%{group: group, key: key, subkeys: params["subkeys"]}) do + ConfigDB.delete(%{group: group, key: key, subkeys: params["subkeys"]}) do config end %{"group" => group, "key" => key, "value" => value} -> with {:ok, config} <- - Config.update_or_create(%{group: group, key: key, value: value}) do + ConfigDB.update_or_create(%{group: group, key: key, value: value}) do config end end) diff --git a/lib/pleroma/web/admin_api/config.ex b/lib/pleroma/web/admin_api/config.ex deleted file mode 100644 index 49f0f8223..000000000 --- a/lib/pleroma/web/admin_api/config.ex +++ /dev/null @@ -1,312 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.AdminAPI.Config do - use Ecto.Schema - import Ecto.Changeset - import Pleroma.Web.Gettext - alias __MODULE__ - alias Pleroma.Repo - - @type t :: %__MODULE__{} - - schema "config" do - field(:key, :string) - field(:group, :string) - field(:value, :binary) - - timestamps() - end - - @spec get_by_params(map()) :: Config.t() | nil - def get_by_params(params), do: Repo.get_by(Config, params) - - @spec changeset(Config.t(), map()) :: Changeset.t() - def changeset(config, params \\ %{}) do - params = Map.put(params, :value, transform(params[:value])) - - config - |> cast(params, [:key, :group, :value]) - |> validate_required([:key, :group, :value]) - |> unique_constraint(:key, name: :config_group_key_index) - end - - @spec create(map()) :: {:ok, Config.t()} | {:error, Changeset.t()} - def create(params) do - %Config{} - |> changeset(params) - |> Repo.insert() - end - - @spec update(Config.t(), map()) :: {:ok, Config} | {:error, Changeset.t()} - def update(%Config{} = config, %{value: value}) do - config - |> changeset(%{value: value}) - |> Repo.update() - end - - @full_key_update [ - {:pleroma, :ecto_repos}, - {:pleroma, :assets}, - {:quack, :meta}, - {:mime, :types}, - {:cors_plug, [:max_age, :methods, :expose, :headers]}, - {:auto_linker, :opts}, - {:swarm, :node_blacklist}, - {:logger, :backends} - ] - - defp only_full_update?(%Config{} = config) do - config_group = Config.from_string(config.group) - config_key = Config.from_string(config.key) - - Enum.any?(@full_key_update, fn - {group, key} when is_list(key) -> - config_group == group and config_key in key - - {group, key} -> - config_group == group and config_key == key - end) - end - - defp can_be_partially_updated?(%Config{} = config), do: not only_full_update?(config) - - @spec update_or_create(map()) :: {:ok, Config.t()} | {:error, Changeset.t()} - def update_or_create(params) do - search_opts = Map.take(params, [:group, :key]) - - with %Config{} = config <- Config.get_by_params(search_opts), - {:partial_update, true, config} <- - {:partial_update, can_be_partially_updated?(config), config}, - old_value <- from_binary(config.value), - transformed_value <- do_transform(params[:value]), - {:can_be_merged, true, config} <- {:can_be_merged, is_list(transformed_value), config}, - new_value <- DeepMerge.deep_merge(old_value, transformed_value) do - Config.update(config, %{value: new_value, transformed?: true}) - else - {reason, false, config} when reason in [:partial_update, :can_be_merged] -> - Config.update(config, params) - - nil -> - Config.create(params) - end - end - - @spec delete(map()) :: {:ok, Config.t()} | {:error, Changeset.t()} | {:ok, nil} - def delete(params) do - search_opts = Map.delete(params, :subkeys) - - with %Config{} = config <- Config.get_by_params(search_opts), - {config, sub_keys} when is_list(sub_keys) <- {config, params[:subkeys]}, - old_value <- from_binary(config.value), - keys <- Enum.map(sub_keys, &do_transform_string(&1)), - new_value <- Keyword.drop(old_value, keys) do - Config.update(config, %{value: new_value}) - else - {config, nil} -> - Repo.delete(config) - {:ok, nil} - - nil -> - err = - dgettext("errors", "Config with params %{params} not found", params: inspect(params)) - - {:error, err} - end - end - - @spec from_binary(binary()) :: term() - def from_binary(binary), do: :erlang.binary_to_term(binary) - - @spec from_binary_with_convert(binary()) :: any() - def from_binary_with_convert(binary) do - binary - |> from_binary() - |> do_convert() - end - - @spec from_string(String.t()) :: atom() | no_return() - def from_string(":" <> entity), do: String.to_existing_atom(entity) - - def from_string(entity) when is_binary(entity) do - if is_module_name?(entity) do - String.to_existing_atom("Elixir.#{entity}") - else - entity - end - end - - defp do_convert(entity) when is_list(entity) do - for v <- entity, into: [], do: do_convert(v) - end - - defp do_convert(%Regex{} = entity), do: inspect(entity) - - defp do_convert(entity) when is_map(entity) do - for {k, v} <- entity, into: %{}, do: {do_convert(k), do_convert(v)} - end - - defp do_convert({:proxy_url, {type, :localhost, port}}) do - %{"tuple" => [":proxy_url", %{"tuple" => [do_convert(type), "localhost", port]}]} - end - - defp do_convert({:proxy_url, {type, host, port}}) when is_tuple(host) do - ip = - host - |> :inet_parse.ntoa() - |> to_string() - - %{ - "tuple" => [ - ":proxy_url", - %{"tuple" => [do_convert(type), ip, port]} - ] - } - end - - defp do_convert({:proxy_url, {type, host, port}}) do - %{ - "tuple" => [ - ":proxy_url", - %{"tuple" => [do_convert(type), to_string(host), port]} - ] - } - end - - defp do_convert({:dispatch, [entity]}), do: %{"tuple" => [":dispatch", [inspect(entity)]]} - # TODO: will become useless after removing hackney - defp do_convert({:partial_chain, entity}), do: %{"tuple" => [":partial_chain", inspect(entity)]} - - defp do_convert(entity) when is_tuple(entity), - do: %{"tuple" => do_convert(Tuple.to_list(entity))} - - defp do_convert(entity) when is_boolean(entity) or is_number(entity) or is_nil(entity), - do: entity - - defp do_convert(entity) - when is_atom(entity) and entity in [:"tlsv1.1", :"tlsv1.2", :"tlsv1.3"] do - ":#{to_string(entity)}" - end - - defp do_convert(entity) when is_atom(entity), do: inspect(entity) - - defp do_convert(entity) when is_binary(entity), do: entity - - @spec transform(any()) :: binary() | no_return() - def transform(entity) when is_binary(entity) or is_map(entity) or is_list(entity) do - entity - |> do_transform() - |> to_binary() - end - - def transform(entity), do: to_binary(entity) - - @spec to_binary(any()) :: binary() - def to_binary(entity), do: :erlang.term_to_binary(entity) - - defp do_transform(%Regex{} = entity), do: entity - - defp do_transform(%{"tuple" => [":proxy_url", %{"tuple" => [type, host, port]}]}) do - {:proxy_url, {do_transform_string(type), parse_host(host), port}} - end - - defp do_transform(%{"tuple" => [":dispatch", [entity]]}) do - {dispatch_settings, []} = do_eval(entity) - {:dispatch, [dispatch_settings]} - end - - # TODO: will become useless after removing hackney - defp do_transform(%{"tuple" => [":partial_chain", entity]}) do - {partial_chain, []} = do_eval(entity) - {:partial_chain, partial_chain} - end - - defp do_transform(%{"tuple" => entity}) do - Enum.reduce(entity, {}, fn val, acc -> Tuple.append(acc, do_transform(val)) end) - end - - defp do_transform(entity) when is_map(entity) do - for {k, v} <- entity, into: %{}, do: {do_transform(k), do_transform(v)} - end - - defp do_transform(entity) when is_list(entity) do - for v <- entity, into: [], do: do_transform(v) - end - - defp do_transform(entity) when is_binary(entity) do - entity - |> String.trim() - |> do_transform_string() - end - - defp do_transform(entity), do: entity - - defp parse_host("localhost"), do: :localhost - - defp parse_host(host) do - charlist = to_charlist(host) - - case :inet.parse_address(charlist) do - {:error, :einval} -> - charlist - - {:ok, ip} -> - ip - end - end - - @delimiters ["/", "|", "\"", "'", {"(", ")"}, {"[", "]"}, {"{", "}"}, {"<", ">"}] - - defp find_valid_delimiter([], _string, _), - do: raise(ArgumentError, message: "valid delimiter for Regex expression not found") - - defp find_valid_delimiter([{leading, closing} = delimiter | others], pattern, regex_delimiter) - when is_tuple(delimiter) do - if String.contains?(pattern, closing) do - find_valid_delimiter(others, pattern, regex_delimiter) - else - {:ok, {leading, closing}} - end - end - - defp find_valid_delimiter([delimiter | others], pattern, regex_delimiter) do - if String.contains?(pattern, delimiter) do - find_valid_delimiter(others, pattern, regex_delimiter) - else - {:ok, {delimiter, delimiter}} - end - end - - @regex_parts ~r/^~r(?'delimiter'[\/|"'([{<]{1})(?'pattern'.+)[\/|"')\]}>]{1}(?'modifier'[uismxfU]*)/u - - defp do_transform_string("~r" <> _pattern = regex) do - with %{"modifier" => modifier, "pattern" => pattern, "delimiter" => regex_delimiter} <- - Regex.named_captures(@regex_parts, regex), - {:ok, {leading, closing}} <- find_valid_delimiter(@delimiters, pattern, regex_delimiter), - {result, _} <- Code.eval_string("~r#{leading}#{pattern}#{closing}#{modifier}") do - result - end - end - - defp do_transform_string(":" <> atom), do: String.to_atom(atom) - - defp do_transform_string(value) do - if is_module_name?(value) do - String.to_existing_atom("Elixir." <> value) - else - value - end - end - - @spec is_module_name?(String.t()) :: boolean() - def is_module_name?(string) do - Regex.match?(~r/^(Pleroma|Phoenix|Tesla|Quack)\./, string) or - string in ["Oban", "Ueberauth", "ExSyslogger"] - end - - defp do_eval(entity) do - cleaned_string = String.replace(entity, ~r/[^\w|^{:,[|^,|^[|^\]^}|^\/|^\.|^"]^\s/, "") - Code.eval_string(cleaned_string) - 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 49add0b6e..72b042ab7 100644 --- a/lib/pleroma/web/admin_api/views/config_view.ex +++ b/lib/pleroma/web/admin_api/views/config_view.ex @@ -15,7 +15,7 @@ defmodule Pleroma.Web.AdminAPI.ConfigView do %{ key: config.key, group: config.group, - value: Pleroma.Web.AdminAPI.Config.from_binary_with_convert(config.value) + value: Pleroma.ConfigDB.from_binary_with_convert(config.value) } end end -- cgit v1.2.3 From 60ba2339a244290f7353e8026032b1a5d185227c Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Fri, 17 Jan 2020 11:45:44 +0300 Subject: saving to DB only added by user settings --- lib/mix/tasks/pleroma/config.ex | 65 +++++++++++++---------- lib/pleroma/config/config_db.ex | 18 +++++++ lib/pleroma/config/holder.ex | 51 ++++++++++++++++++ lib/pleroma/config/transfer_task.ex | 22 ++++++-- lib/pleroma/web/admin_api/admin_api_controller.ex | 42 ++++++++++++++- lib/pleroma/web/router.ex | 1 - 6 files changed, 166 insertions(+), 33 deletions(-) create mode 100644 lib/pleroma/config/holder.ex (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex index 57952aeba..3157e7b20 100644 --- a/lib/mix/tasks/pleroma/config.ex +++ b/lib/mix/tasks/pleroma/config.ex @@ -4,42 +4,25 @@ defmodule Mix.Tasks.Pleroma.Config do use Mix.Task + import Mix.Pleroma + alias Pleroma.ConfigDB alias Pleroma.Repo + + require Logger + @shortdoc "Manages the location of the config" @moduledoc File.read!("docs/administration/CLI_tasks/config.md") - @groups [ - :pleroma, - :logger, - :quack, - :mime, - :tesla, - :phoenix, - :cors_plug, - :auto_linker, - :esshd, - :ueberauth, - :http_signatures, - :web_push_encryption, - :joken - ] - def run(["migrate_to_db"]) do # we want to save original logger level start_pleroma(false) - - if Pleroma.Config.get([:configurable_from_database]) do - Enum.each(@groups, &load_and_create(&1)) - else - Mix.shell().info( - "Migration is not allowed by config. You can change this behavior in instance settings." - ) - end + migrate_to_db() end def run(["migrate_from_db" | options]) do + # TODO: add support for releases start_pleroma() {opts, _} = @@ -72,10 +55,36 @@ defmodule Mix.Tasks.Pleroma.Config do end end - defp load_and_create(group) do - group - |> Application.get_all_env() - |> Enum.reject(fn {k, _v} -> + @spec migrate_to_db(Path.t() | nil) :: any() + def migrate_to_db(file_path \\ nil) do + if Pleroma.Config.get([:configurable_from_database]) do + # TODO: add support for releases + config_file = file_path || "config/#{Pleroma.Config.get(:env)}.secret.exs" + do_migrate_to_db(config_file) + else + Mix.shell().info( + "migration is not allowed by config. You can change this behavior in instance settings." + ) + end + end + + defp do_migrate_to_db(config_file) do + if File.exists?(config_file) do + {custom_config, _paths} = + if Code.ensure_loaded?(Config.Reader), + do: Config.Reader.read_imports!(config_file), + else: Mix.Config.eval!(config_file) + + custom_config + |> Keyword.keys() + |> Enum.each(&create(&1, custom_config[&1])) + else + Logger.warn("to migrate settings, you must define custom settings in #{config_file}") + end + end + + defp create(group, settings) do + Enum.reject(settings, fn {k, _v} -> k in [Pleroma.Repo, Pleroma.Web.Endpoint, :env, :configurable_from_database] or (group == :phoenix and k == :serve_endpoints) end) diff --git a/lib/pleroma/config/config_db.ex b/lib/pleroma/config/config_db.ex index e433ce442..294fbc3f0 100644 --- a/lib/pleroma/config/config_db.ex +++ b/lib/pleroma/config/config_db.ex @@ -5,6 +5,7 @@ defmodule Pleroma.ConfigDB do use Ecto.Schema import Ecto.Changeset + import Ecto.Query import Pleroma.Web.Gettext alias __MODULE__ alias Pleroma.Repo @@ -19,6 +20,20 @@ defmodule Pleroma.ConfigDB do timestamps() end + @spec get_all_as_keyword() :: keyword() + def get_all_as_keyword do + ConfigDB + |> select([c], {c.group, c.key, c.value}) + |> Repo.all() + |> Enum.reduce([], fn {group, key, value}, acc -> + group = ConfigDB.from_string(group) + key = ConfigDB.from_string(key) + value = from_binary(value) + + Keyword.update(acc, group, [{key, value}], &Keyword.merge(&1, [{key, value}])) + end) + end + @spec get_by_params(map()) :: ConfigDB.t() | nil def get_by_params(params), do: Repo.get_by(ConfigDB, params) @@ -137,6 +152,9 @@ defmodule Pleroma.ConfigDB do end end + @spec convert(any()) :: any() + def convert(entity), do: do_convert(entity) + defp do_convert(entity) when is_list(entity) do for v <- entity, into: [], do: do_convert(v) end diff --git a/lib/pleroma/config/holder.ex b/lib/pleroma/config/holder.ex new file mode 100644 index 000000000..aa35382b2 --- /dev/null +++ b/lib/pleroma/config/holder.ex @@ -0,0 +1,51 @@ +defmodule Pleroma.Config.Loader do + # TODO: add support for releases + if Code.ensure_loaded?(Config.Reader) do + @spec load() :: map() + def load do + config = Config.Reader.read!("config/config.exs") + env_config = Config.Reader.read!("config/#{Mix.env()}.exs") + + Config.Reader.merge(config, env_config) + end + else + # support for Elixir less than 1.9 + @spec load() :: map() + def load do + config = Mix.Config.eval!("config/config.exs") + env_config = Mix.Config.eval!("config/#{Mix.env()}.exs") + + Mix.Config.merge(config, env_config) + end + end +end + +defmodule Pleroma.Config.Holder do + @config Pleroma.Config.Loader.load() + + @spec config() :: keyword() + def config do + @config + |> Keyword.keys() + |> Enum.map(&filter(&1, config(&1))) + |> List.flatten() + end + + @spec config(atom()) :: any() + def config(group), do: @config[group] + + @spec config(atom(), atom()) :: any() + def config(group, key), do: @config[group][key] + + defp filter(group, settings) when group not in [:swarm] do + filtered = + Enum.reject(settings, fn {k, _v} -> + k in [Pleroma.Repo, Pleroma.Web.Endpoint, :env, :configurable_from_database] or + (group == :phoenix and k == :serve_endpoints) + end) + + {group, filtered} + end + + defp filter(_, _), do: [] +end diff --git a/lib/pleroma/config/transfer_task.ex b/lib/pleroma/config/transfer_task.ex index b39f9594e..828a297de 100644 --- a/lib/pleroma/config/transfer_task.ex +++ b/lib/pleroma/config/transfer_task.ex @@ -38,10 +38,18 @@ defmodule Pleroma.Config.TransferTask do value = ConfigDB.from_binary(setting.value) if group != :phoenix and key != :serve_endpoints do - :ok = Application.put_env(group, key, value) - end + default = Pleroma.Config.Holder.config(group, key) + + merged_value = + if can_be_merged?(default, value) do + DeepMerge.deep_merge(default, value) + else + value + end - group + :ok = Application.put_env(group, key, merged_value) + group + end rescue e -> Logger.warn( @@ -61,4 +69,12 @@ defmodule Pleroma.Config.TransferTask do error -> Logger.warn(inspect(error)) end end + + defp can_be_merged?(val1, val2) when is_map(val1) and is_map(val2), do: true + + defp can_be_merged?(val1, val2) when is_list(val1) and is_list(val2) do + Keyword.keyword?(val1) and Keyword.keyword?(val2) + end + + defp can_be_merged?(_val1, _val2), do: false end diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index e1d16c30b..cc93c2309 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -797,7 +797,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do end end - def config_show(conn, _params) do + def config_show(conn, %{"only_db" => true}) do with :ok <- configurable_from_database(conn) do configs = Pleroma.Repo.all(ConfigDB) @@ -814,6 +814,46 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do end end + def config_show(conn, _params) do + with :ok <- configurable_from_database(conn) do + configs = ConfigDB.get_all_as_keyword() + + if configs == [] do + errors( + conn, + {:error, "To use configuration from database migrate your settings to database."} + ) + else + merged = + Pleroma.Config.Holder.config() + |> DeepMerge.deep_merge(configs) + |> Enum.map(fn {group, value} -> + Enum.map(value, fn {key, value} -> + db = + if configs[group][key] do + if Keyword.keyword?(value) do + Keyword.keys(value) |> Enum.map(fn key -> ConfigDB.convert(key) end) + else + ConfigDB.convert(key) + end + end + + setting = %{ + group: ConfigDB.convert(group), + key: ConfigDB.convert(key), + value: ConfigDB.convert(value) + } + + if db, do: Map.put(setting, :db, db), else: setting + end) + end) + |> List.flatten() + + json(conn, %{configs: merged}) + end + end + end + def config_update(conn, %{"configs" => configs}) do with :ok <- configurable_from_database(conn) do updated = diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 8cd7e021a..ef6e5a565 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -196,7 +196,6 @@ defmodule Pleroma.Web.Router do get("/config", AdminAPIController, :config_show) post("/config", AdminAPIController, :config_update) get("/config/descriptions", AdminAPIController, :config_descriptions) - get("/config/migrate_to_db", AdminAPIController, :migrate_to_db) get("/config/migrate_from_db", AdminAPIController, :migrate_from_db) get("/moderation_log", AdminAPIController, :list_log) -- cgit v1.2.3 From 58e8963824bd3375ec1fa04b265187a888aaa5ea Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Fri, 17 Jan 2020 11:54:22 +0300 Subject: fix for config loader --- lib/pleroma/config/holder.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/config/holder.ex b/lib/pleroma/config/holder.ex index aa35382b2..c8badc52c 100644 --- a/lib/pleroma/config/holder.ex +++ b/lib/pleroma/config/holder.ex @@ -12,8 +12,8 @@ defmodule Pleroma.Config.Loader do # support for Elixir less than 1.9 @spec load() :: map() def load do - config = Mix.Config.eval!("config/config.exs") - env_config = Mix.Config.eval!("config/#{Mix.env()}.exs") + {config, _paths} = Mix.Config.eval!("config/config.exs") + {env_config, _paths} = Mix.Config.eval!("config/#{Mix.env()}.exs") Mix.Config.merge(config, env_config) end -- cgit v1.2.3 From b310feeccb03a9bc232b49d93fb03805e3d5c2d8 Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Fri, 17 Jan 2020 15:00:20 +0300 Subject: compile fix --- lib/mix/tasks/pleroma/config.ex | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex index 3157e7b20..d2abbd885 100644 --- a/lib/mix/tasks/pleroma/config.ex +++ b/lib/mix/tasks/pleroma/config.ex @@ -68,12 +68,15 @@ defmodule Mix.Tasks.Pleroma.Config do end end + if Code.ensure_loaded?(Config.Reader) do + defp read_file(config_file), do: Config.Reader.read_imports!(config_file) + else + defp read_file(config_file), do: Mix.Config.eval!(config_file) + end + defp do_migrate_to_db(config_file) do if File.exists?(config_file) do - {custom_config, _paths} = - if Code.ensure_loaded?(Config.Reader), - do: Config.Reader.read_imports!(config_file), - else: Mix.Config.eval!(config_file) + {custom_config, _paths} = read_file(config_file) custom_config |> Keyword.keys() -- cgit v1.2.3 From 9e10b90b5bdf0f1ae327a44f44187918ab8dbf4f Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Fri, 17 Jan 2020 15:06:42 +0300 Subject: configure only console backend --- lib/mix/pleroma.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/mix/pleroma.ex b/lib/mix/pleroma.ex index ea9923440..a6b00d4be 100644 --- a/lib/mix/pleroma.ex +++ b/lib/mix/pleroma.ex @@ -9,7 +9,7 @@ defmodule Mix.Pleroma do if Pleroma.Config.get(:env) != :test and change_logger_level do require Logger - Logger.configure(level: :debug) + Logger.configure(:console, level: :debug) end {:ok, _} = Application.ensure_all_started(:pleroma) -- cgit v1.2.3 From ce027fd0ef1080b03e7982e5939b0c7db6e3b783 Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Fri, 17 Jan 2020 15:13:24 +0300 Subject: revert non needable --- lib/mix/pleroma.ex | 7 +++---- lib/mix/tasks/pleroma/config.ex | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) (limited to 'lib') diff --git a/lib/mix/pleroma.ex b/lib/mix/pleroma.ex index a6b00d4be..73a076a53 100644 --- a/lib/mix/pleroma.ex +++ b/lib/mix/pleroma.ex @@ -4,12 +4,11 @@ defmodule Mix.Pleroma do @doc "Common functions to be reused in mix tasks" - def start_pleroma(change_logger_level \\ true) do + def start_pleroma do Application.put_env(:phoenix, :serve_endpoints, false, persistent: true) - if Pleroma.Config.get(:env) != :test and change_logger_level do - require Logger - Logger.configure(:console, level: :debug) + if Pleroma.Config.get(:env) != :test do + Application.put_env(:logger, :console, level: :debug) end {:ok, _} = Application.ensure_all_started(:pleroma) diff --git a/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex index d2abbd885..43039ae77 100644 --- a/lib/mix/tasks/pleroma/config.ex +++ b/lib/mix/tasks/pleroma/config.ex @@ -17,7 +17,7 @@ defmodule Mix.Tasks.Pleroma.Config do def run(["migrate_to_db"]) do # we want to save original logger level - start_pleroma(false) + start_pleroma() migrate_to_db() end -- cgit v1.2.3 From 7676ed82397d73a20aad1ae4b47690923ddfb162 Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Fri, 17 Jan 2020 16:28:44 +0300 Subject: some clean up --- lib/mix/tasks/pleroma/config.ex | 1 - lib/mix/tasks/pleroma/docs.ex | 2 +- lib/pleroma/config/config_db.ex | 17 +++++------------ lib/pleroma/config/holder.ex | 17 +++++++++++++---- lib/pleroma/config/transfer_task.ex | 15 ++++++++++++++- lib/pleroma/docs/json.ex | 2 +- 6 files changed, 34 insertions(+), 20 deletions(-) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex index 43039ae77..148d18141 100644 --- a/lib/mix/tasks/pleroma/config.ex +++ b/lib/mix/tasks/pleroma/config.ex @@ -16,7 +16,6 @@ defmodule Mix.Tasks.Pleroma.Config do @moduledoc File.read!("docs/administration/CLI_tasks/config.md") def run(["migrate_to_db"]) do - # we want to save original logger level start_pleroma() migrate_to_db() end diff --git a/lib/mix/tasks/pleroma/docs.ex b/lib/mix/tasks/pleroma/docs.ex index 0d2663648..3c870f876 100644 --- a/lib/mix/tasks/pleroma/docs.ex +++ b/lib/mix/tasks/pleroma/docs.ex @@ -28,7 +28,7 @@ defmodule Mix.Tasks.Pleroma.Docs do defp do_run(implementation) do start_pleroma() - with {descriptions, _paths} <- Mix.Config.eval!("config/description.exs"), + with descriptions <- Pleroma.Config.Loader.load("config/description.exs"), {:ok, file_path} <- Pleroma.Docs.Generator.process( implementation, diff --git a/lib/pleroma/config/config_db.ex b/lib/pleroma/config/config_db.ex index 294fbc3f0..6d48e0300 100644 --- a/lib/pleroma/config/config_db.ex +++ b/lib/pleroma/config/config_db.ex @@ -192,7 +192,6 @@ defmodule Pleroma.ConfigDB do } end - defp do_convert({:dispatch, [entity]}), do: %{"tuple" => [":dispatch", [inspect(entity)]]} # TODO: will become useless after removing hackney defp do_convert({:partial_chain, entity}), do: %{"tuple" => [":partial_chain", inspect(entity)]} @@ -229,14 +228,13 @@ defmodule Pleroma.ConfigDB do {:proxy_url, {do_transform_string(type), parse_host(host), port}} end - defp do_transform(%{"tuple" => [":dispatch", [entity]]}) do - {dispatch_settings, []} = do_eval(entity) - {:dispatch, [dispatch_settings]} - end - # TODO: will become useless after removing hackney defp do_transform(%{"tuple" => [":partial_chain", entity]}) do - {partial_chain, []} = do_eval(entity) + {partial_chain, []} = + entity + |> String.replace(~r/[^\w|^{:,[|^,|^[|^\]^}|^\/|^\.|^"]^\s/, "") + |> Code.eval_string() + {:partial_chain, partial_chain} end @@ -322,9 +320,4 @@ defmodule Pleroma.ConfigDB do Regex.match?(~r/^(Pleroma|Phoenix|Tesla|Quack)\./, string) or string in ["Oban", "Ueberauth", "ExSyslogger"] end - - defp do_eval(entity) do - cleaned_string = String.replace(entity, ~r/[^\w|^{:,[|^,|^[|^\]^}|^\/|^\.|^"]^\s/, "") - Code.eval_string(cleaned_string) - end end diff --git a/lib/pleroma/config/holder.ex b/lib/pleroma/config/holder.ex index c8badc52c..0d9cfef66 100644 --- a/lib/pleroma/config/holder.ex +++ b/lib/pleroma/config/holder.ex @@ -3,20 +3,29 @@ defmodule Pleroma.Config.Loader do if Code.ensure_loaded?(Config.Reader) do @spec load() :: map() def load do - config = Config.Reader.read!("config/config.exs") - env_config = Config.Reader.read!("config/#{Mix.env()}.exs") + config = load("config/config.exs") + env_config = load("config/#{Mix.env()}.exs") Config.Reader.merge(config, env_config) end + + @spec load(Path.t()) :: keyword() + def load(path), do: Config.Reader.read!(path) else # support for Elixir less than 1.9 @spec load() :: map() def load do - {config, _paths} = Mix.Config.eval!("config/config.exs") - {env_config, _paths} = Mix.Config.eval!("config/#{Mix.env()}.exs") + {config, _paths} = load("config/config.exs") + {env_config, _paths} = load("config/#{Mix.env()}.exs") Mix.Config.merge(config, env_config) end + + @spec load(Path.t()) :: keyword() + def load(path) do + {config, _paths} = Mix.Config.eval!(path) + config + end end end diff --git a/lib/pleroma/config/transfer_task.ex b/lib/pleroma/config/transfer_task.ex index 828a297de..70b51cb4d 100644 --- a/lib/pleroma/config/transfer_task.ex +++ b/lib/pleroma/config/transfer_task.ex @@ -48,7 +48,20 @@ defmodule Pleroma.Config.TransferTask do end :ok = Application.put_env(group, key, merged_value) - group + + if group != :logger do + group + else + # change logger configuration in runtime, without restart + if Keyword.keyword?(merged_value) and + key not in [:compile_time_application, :backends, :compile_time_purge_matching] do + Logger.configure_backend(key, merged_value) + else + Logger.configure([{key, merged_value}]) + end + + nil + end end rescue e -> diff --git a/lib/pleroma/docs/json.ex b/lib/pleroma/docs/json.ex index f191b6013..6508a7bdb 100644 --- a/lib/pleroma/docs/json.ex +++ b/lib/pleroma/docs/json.ex @@ -15,7 +15,7 @@ defmodule Pleroma.Docs.JSON do end def compile do - with {config, _paths} <- Mix.Config.eval!("config/description.exs") do + with config <- Pleroma.Config.Loader.load("config/description.exs") do config[:pleroma][:config_description] |> Pleroma.Docs.Generator.convert_to_strings() |> Jason.encode!() -- cgit v1.2.3 From 89e93fb33f6295428dd84a50c9ca44e26bd169c3 Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Fri, 17 Jan 2020 18:08:45 +0300 Subject: return db key on update requests --- lib/pleroma/config/config_db.ex | 19 ++++++++++++++++++- lib/pleroma/config/holder.ex | 4 ++-- lib/pleroma/config/transfer_task.ex | 4 +++- lib/pleroma/web/admin_api/admin_api_controller.ex | 13 ++++++------- lib/pleroma/web/admin_api/views/config_view.ex | 8 +++++++- 5 files changed, 36 insertions(+), 12 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/config/config_db.ex b/lib/pleroma/config/config_db.ex index 6d48e0300..93b02370c 100644 --- a/lib/pleroma/config/config_db.ex +++ b/lib/pleroma/config/config_db.ex @@ -16,6 +16,7 @@ defmodule Pleroma.ConfigDB do field(:key, :string) field(:group, :string) field(:value, :binary) + field(:db, {:array, :string}, virtual: true, default: []) timestamps() end @@ -61,6 +62,22 @@ defmodule Pleroma.ConfigDB do |> Repo.update() end + @spec get_db_keys(ConfigDB.t()) :: [String.t()] + def get_db_keys(%ConfigDB{} = config) do + config.value + |> ConfigDB.from_binary() + |> get_db_keys(config.key) + end + + @spec get_db_keys(keyword() | any()) :: [String.t()] + def get_db_keys(value, key) do + if Keyword.keyword?(value) do + value |> Keyword.keys() |> Enum.map(&convert(&1)) + else + [convert(key)] + end + end + @full_key_update [ {:pleroma, :ecto_repos}, {:pleroma, :assets}, @@ -317,7 +334,7 @@ defmodule Pleroma.ConfigDB do @spec is_module_name?(String.t()) :: boolean() def is_module_name?(string) do - Regex.match?(~r/^(Pleroma|Phoenix|Tesla|Quack)\./, string) or + Regex.match?(~r/^(Pleroma|Phoenix|Tesla|Quack|Ueberauth)\./, string) or string in ["Oban", "Ueberauth", "ExSyslogger"] end end diff --git a/lib/pleroma/config/holder.ex b/lib/pleroma/config/holder.ex index 0d9cfef66..7436df1a7 100644 --- a/lib/pleroma/config/holder.ex +++ b/lib/pleroma/config/holder.ex @@ -15,8 +15,8 @@ defmodule Pleroma.Config.Loader do # support for Elixir less than 1.9 @spec load() :: map() def load do - {config, _paths} = load("config/config.exs") - {env_config, _paths} = load("config/#{Mix.env()}.exs") + config = load("config/config.exs") + env_config = load("config/#{Mix.env()}.exs") Mix.Config.merge(config, env_config) end diff --git a/lib/pleroma/config/transfer_task.ex b/lib/pleroma/config/transfer_task.ex index 70b51cb4d..6e651c48b 100644 --- a/lib/pleroma/config/transfer_task.ex +++ b/lib/pleroma/config/transfer_task.ex @@ -66,7 +66,9 @@ defmodule Pleroma.Config.TransferTask do rescue e -> Logger.warn( - "updating env causes error, key: #{inspect(setting.key)}, error: #{inspect(e)}" + "updating env causes error, group: #{inspect(setting.group)}, key: #{ + inspect(setting.key) + }, value: #{inspect(ConfigDB.from_binary(setting.value))}, error: #{inspect(e)}" ) nil diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index cc93c2309..7572a6b65 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -827,15 +827,11 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do merged = Pleroma.Config.Holder.config() |> DeepMerge.deep_merge(configs) - |> Enum.map(fn {group, value} -> - Enum.map(value, fn {key, value} -> + |> Enum.map(fn {group, values} -> + Enum.map(values, fn {key, value} -> db = if configs[group][key] do - if Keyword.keyword?(value) do - Keyword.keys(value) |> Enum.map(fn key -> ConfigDB.convert(key) end) - else - ConfigDB.convert(key) - end + ConfigDB.get_db_keys(value, key) end setting = %{ @@ -871,6 +867,9 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do end end) |> Enum.reject(&is_nil(&1)) + |> Enum.map(fn config -> + Map.put(config, :db, ConfigDB.get_db_keys(config)) + end) Pleroma.Config.TransferTask.load_and_update_env() diff --git a/lib/pleroma/web/admin_api/views/config_view.ex b/lib/pleroma/web/admin_api/views/config_view.ex index 72b042ab7..23d97e847 100644 --- a/lib/pleroma/web/admin_api/views/config_view.ex +++ b/lib/pleroma/web/admin_api/views/config_view.ex @@ -12,10 +12,16 @@ defmodule Pleroma.Web.AdminAPI.ConfigView do end def render("show.json", %{config: config}) do - %{ + map = %{ key: config.key, group: config.group, value: Pleroma.ConfigDB.from_binary_with_convert(config.value) } + + if config.db != [] do + Map.put(map, :db, config.db) + else + map + end end end -- cgit v1.2.3 From e69986169095796f2845c4f859234d96f91bf9ff Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Sat, 18 Jan 2020 12:25:56 +0300 Subject: full update for some subkeys --- lib/pleroma/config/config_db.ex | 65 +++++++++++++++++++++-- lib/pleroma/config/transfer_task.ex | 2 +- lib/pleroma/web/admin_api/admin_api_controller.ex | 12 ++++- 3 files changed, 73 insertions(+), 6 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/config/config_db.ex b/lib/pleroma/config/config_db.ex index 93b02370c..85a049f8a 100644 --- a/lib/pleroma/config/config_db.ex +++ b/lib/pleroma/config/config_db.ex @@ -69,7 +69,7 @@ defmodule Pleroma.ConfigDB do |> get_db_keys(config.key) end - @spec get_db_keys(keyword() | any()) :: [String.t()] + @spec get_db_keys(keyword(), any()) :: [String.t()] def get_db_keys(value, key) do if Keyword.keyword?(value) do value |> Keyword.keys() |> Enum.map(&convert(&1)) @@ -78,9 +78,57 @@ defmodule Pleroma.ConfigDB do end end + @full_subkey_update [ + {:pleroma, :assets, :mascots}, + {:pleroma, :emoji, :groups}, + {:pleroma, :workers, :retries}, + {:pleroma, :mrf_subchain, :match_actor}, + {:pleroma, :mrf_keyword, :replace} + ] + + @spec deep_merge(atom(), atom(), keyword(), keyword()) :: keyword() + def deep_merge(group, key, old_value, new_value) do + old_keys = + old_value + |> Keyword.keys() + |> MapSet.new() + + new_keys = + new_value + |> Keyword.keys() + |> MapSet.new() + + intersect_keys = old_keys |> MapSet.intersection(new_keys) |> MapSet.to_list() + + subkeys = sub_key_full_update(group, key, intersect_keys) + + merged_value = DeepMerge.deep_merge(old_value, new_value) + + Enum.reduce(subkeys, merged_value, fn subkey, acc -> + Keyword.put(acc, subkey, new_value[subkey]) + end) + end + + @spec sub_key_full_update?(atom(), atom(), [Keyword.key()]) :: boolean() + def sub_key_full_update?(group, key, subkeys) do + Enum.any?(@full_subkey_update, fn {g, k, subkey} -> + g == group and k == key and subkey in subkeys + end) + end + + defp sub_key_full_update(group, key, subkeys) do + Enum.map(@full_subkey_update, fn + {g, k, subkey} when g == group and k == key -> + if subkey in subkeys, do: subkey, else: [] + + _ -> + [] + end) + |> List.flatten() + end + @full_key_update [ {:pleroma, :ecto_repos}, - {:pleroma, :assets}, {:quack, :meta}, {:mime, :types}, {:cors_plug, [:max_age, :methods, :expose, :headers]}, @@ -114,8 +162,14 @@ defmodule Pleroma.ConfigDB do old_value <- from_binary(config.value), transformed_value <- do_transform(params[:value]), {:can_be_merged, true, config} <- {:can_be_merged, is_list(transformed_value), config}, - new_value <- DeepMerge.deep_merge(old_value, transformed_value) do - ConfigDB.update(config, %{value: new_value, transformed?: true}) + new_value <- + deep_merge( + ConfigDB.from_string(config.group), + ConfigDB.from_string(config.key), + old_value, + transformed_value + ) do + ConfigDB.update(config, %{value: new_value}) else {reason, false, config} when reason in [:partial_update, :can_be_merged] -> ConfigDB.update(config, params) @@ -236,6 +290,9 @@ defmodule Pleroma.ConfigDB do def transform(entity), do: to_binary(entity) + @spec transform_with_out_binary(any()) :: any() + def transform_with_out_binary(entity), do: do_transform(entity) + @spec to_binary(any()) :: binary() def to_binary(entity), do: :erlang.term_to_binary(entity) diff --git a/lib/pleroma/config/transfer_task.ex b/lib/pleroma/config/transfer_task.ex index 6e651c48b..9b571d5d9 100644 --- a/lib/pleroma/config/transfer_task.ex +++ b/lib/pleroma/config/transfer_task.ex @@ -42,7 +42,7 @@ defmodule Pleroma.Config.TransferTask do merged_value = if can_be_merged?(default, value) do - DeepMerge.deep_merge(default, value) + ConfigDB.deep_merge(group, key, default, value) else value end diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index 7572a6b65..cc658dc0e 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -834,10 +834,20 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do ConfigDB.get_db_keys(value, key) end + db_value = configs[group][key] + + merged_value = + if !is_nil(db_value) and Keyword.keyword?(db_value) and + ConfigDB.sub_key_full_update?(group, key, Keyword.keys(db_value)) do + ConfigDB.deep_merge(group, key, value, db_value) + else + value + end + setting = %{ group: ConfigDB.convert(group), key: ConfigDB.convert(key), - value: ConfigDB.convert(value) + value: ConfigDB.convert(merged_value) } if db, do: Map.put(setting, :db, db), else: setting -- cgit v1.2.3 From efb8ef5abee1a8defa2bfba40ad1065db4c09ddf Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Sat, 18 Jan 2020 16:55:33 +0300 Subject: releases support --- lib/mix/tasks/pleroma/config.ex | 49 +++++++++++++++++++++++++++-------------- lib/pleroma/config/holder.ex | 33 +-------------------------- lib/pleroma/config/loader.ex | 39 ++++++++++++++++++++++++++++++++ 3 files changed, 73 insertions(+), 48 deletions(-) create mode 100644 lib/pleroma/config/loader.ex (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex index 148d18141..715b72dbe 100644 --- a/lib/mix/tasks/pleroma/config.ex +++ b/lib/mix/tasks/pleroma/config.ex @@ -10,8 +10,6 @@ defmodule Mix.Tasks.Pleroma.Config do alias Pleroma.ConfigDB alias Pleroma.Repo - require Logger - @shortdoc "Manages the location of the config" @moduledoc File.read!("docs/administration/CLI_tasks/config.md") @@ -32,10 +30,10 @@ defmodule Mix.Tasks.Pleroma.Config do with {:active?, true} <- {:active?, Pleroma.Config.get([:configurable_from_database])}, - env_path when is_binary(env_path) <- opts[:env], - config_path <- "config/#{env_path}.exported_from_db.secret.exs", + env when is_binary(env) <- opts[:env] || "prod", + config_path <- config_path(env), {:ok, file} <- File.open(config_path, [:write, :utf8]) do - IO.write(file, "use Mix.Config\r\n") + IO.write(file, config_header()) ConfigDB |> Repo.all() @@ -45,31 +43,50 @@ defmodule Mix.Tasks.Pleroma.Config do System.cmd("mix", ["format", config_path]) else {:active?, false} -> - Mix.shell().info( - "migration is not allowed by config. You can change this behavior in instance settings." + shell_info( + "Migration is not allowed by config. You can change this behavior in instance settings." ) error -> - Mix.shell().info("error occuried while opening file. #{inspect(error)}") + shell_info("Error occuried while opening file. #{inspect(error)}") end end + defp config_path(env) do + path = + if Pleroma.Config.get(:release) do + :config_path + |> Pleroma.Config.get() + |> Path.dirname() + else + "config" + end + + Path.join(path, "#{env}.exported_from_db.secret.exs") + end + @spec migrate_to_db(Path.t() | nil) :: any() def migrate_to_db(file_path \\ nil) do if Pleroma.Config.get([:configurable_from_database]) do - # TODO: add support for releases - config_file = file_path || "config/#{Pleroma.Config.get(:env)}.secret.exs" + user_config_file = + if Pleroma.Config.get(:release), + do: Pleroma.Config.get(:config_path), + else: "config/#{Pleroma.Config.get(:env)}.secret.exs" + + config_file = file_path || user_config_file do_migrate_to_db(config_file) else - Mix.shell().info( - "migration is not allowed by config. You can change this behavior in instance settings." + shell_info( + "Migration is not allowed by config. You can change this behavior in instance settings." ) end end if Code.ensure_loaded?(Config.Reader) do + defp config_header, do: "import Config\r\n\r\n" defp read_file(config_file), do: Config.Reader.read_imports!(config_file) else + defp config_header, do: "use Mix.Config\r\n\r\n" defp read_file(config_file), do: Mix.Config.eval!(config_file) end @@ -81,7 +98,7 @@ defmodule Mix.Tasks.Pleroma.Config do |> Keyword.keys() |> Enum.each(&create(&1, custom_config[&1])) else - Logger.warn("to migrate settings, you must define custom settings in #{config_file}") + shell_info("To migrate settings, you must define custom settings in #{config_file}.") end end @@ -94,10 +111,10 @@ defmodule Mix.Tasks.Pleroma.Config do key = inspect(key) {:ok, _} = ConfigDB.update_or_create(%{group: inspect(group), key: key, value: value}) - Mix.shell().info("settings for key #{key} migrated.") + shell_info("Settings for key #{key} migrated.") end) - Mix.shell().info("settings for group :#{group} migrated.") + shell_info("Settings for group :#{group} migrated.") end defp write_to_file_with_deletion(config, file, with_deletion) do @@ -110,7 +127,7 @@ defmodule Mix.Tasks.Pleroma.Config do if with_deletion do {:ok, _} = Repo.delete(config) - Mix.shell().info("#{config.key} deleted from DB.") + shell_info("#{config.key} deleted from DB.") end end end diff --git a/lib/pleroma/config/holder.ex b/lib/pleroma/config/holder.ex index 7436df1a7..0fa7c5bf7 100644 --- a/lib/pleroma/config/holder.ex +++ b/lib/pleroma/config/holder.ex @@ -1,36 +1,5 @@ -defmodule Pleroma.Config.Loader do - # TODO: add support for releases - if Code.ensure_loaded?(Config.Reader) do - @spec load() :: map() - def load do - config = load("config/config.exs") - env_config = load("config/#{Mix.env()}.exs") - - Config.Reader.merge(config, env_config) - end - - @spec load(Path.t()) :: keyword() - def load(path), do: Config.Reader.read!(path) - else - # support for Elixir less than 1.9 - @spec load() :: map() - def load do - config = load("config/config.exs") - env_config = load("config/#{Mix.env()}.exs") - - Mix.Config.merge(config, env_config) - end - - @spec load(Path.t()) :: keyword() - def load(path) do - {config, _paths} = Mix.Config.eval!(path) - config - end - end -end - defmodule Pleroma.Config.Holder do - @config Pleroma.Config.Loader.load() + @config Pleroma.Config.Loader.load_and_merge() @spec config() :: keyword() def config do diff --git a/lib/pleroma/config/loader.ex b/lib/pleroma/config/loader.ex new file mode 100644 index 000000000..b13b6eaf4 --- /dev/null +++ b/lib/pleroma/config/loader.ex @@ -0,0 +1,39 @@ +defmodule Pleroma.Config.Loader do + @paths ["config/config.exs", "config/#{Mix.env()}.exs"] + + if Code.ensure_loaded?(Config.Reader) do + @spec load(Path.t()) :: keyword() + def load(path), do: Config.Reader.read!(path) + + defp do_merge(conf1, conf2), do: Config.Reader.merge(conf1, conf2) + else + # support for Elixir less than 1.9 + @spec load(Path.t()) :: keyword() + def load(path) do + {config, _paths} = Mix.Config.eval!(path) + config + end + + defp do_merge(conf1, conf2), do: Mix.Config.merge(conf1, conf2) + end + + @spec load_and_merge() :: keyword() + def load_and_merge do + all_paths = + if Pleroma.Config.get(:release), + do: @paths ++ ["config/releases.exs"], + else: @paths + + all_paths + |> Enum.map(&load(&1)) + |> merge() + end + + @spec merge([keyword()], keyword()) :: keyword() + def merge(configs, acc \\ []) + def merge([], acc), do: acc + + def merge([config | others], acc) do + merge(others, do_merge(acc, config)) + end +end -- cgit v1.2.3 From 8023fa48dbc68fe696337a4da940b95fd209e304 Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Mon, 20 Jan 2020 18:25:00 +0300 Subject: merge fix --- lib/pleroma/config/config_db.ex | 20 +++++++++++++++++++- lib/pleroma/web/admin_api/admin_api_controller.ex | 2 +- 2 files changed, 20 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/config/config_db.ex b/lib/pleroma/config/config_db.ex index 85a049f8a..0091f7a1b 100644 --- a/lib/pleroma/config/config_db.ex +++ b/lib/pleroma/config/config_db.ex @@ -102,7 +102,7 @@ defmodule Pleroma.ConfigDB do subkeys = sub_key_full_update(group, key, intersect_keys) - merged_value = DeepMerge.deep_merge(old_value, new_value) + merged_value = ConfigDB.merge(old_value, new_value) Enum.reduce(subkeys, merged_value, fn subkey, acc -> Keyword.put(acc, subkey, new_value[subkey]) @@ -127,6 +127,24 @@ defmodule Pleroma.ConfigDB do |> List.flatten() end + def merge(config1, config2) when is_list(config1) and is_list(config2) do + Keyword.merge(config1, config2, fn _, app1, app2 -> + if Keyword.keyword?(app1) and Keyword.keyword?(app2) do + Keyword.merge(app1, app2, &deep_merge/3) + else + app2 + end + end) + end + + defp deep_merge(_key, value1, value2) do + if Keyword.keyword?(value1) and Keyword.keyword?(value2) do + Keyword.merge(value1, value2, &deep_merge/3) + else + value2 + end + end + @full_key_update [ {:pleroma, :ecto_repos}, {:quack, :meta}, diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index cc658dc0e..30343a9f1 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -826,7 +826,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do else merged = Pleroma.Config.Holder.config() - |> DeepMerge.deep_merge(configs) + |> ConfigDB.merge(configs) |> Enum.map(fn {group, values} -> Enum.map(values, fn {key, value} -> db = -- cgit v1.2.3 From dcae5914d1f7d540e3c20be5988d5e3547a349cd Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Tue, 21 Jan 2020 10:14:48 +0300 Subject: fix for db key --- lib/pleroma/config/config_db.ex | 7 ++++++- lib/pleroma/web/admin_api/admin_api_controller.ex | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/config/config_db.ex b/lib/pleroma/config/config_db.ex index 0091f7a1b..1afe294f4 100644 --- a/lib/pleroma/config/config_db.ex +++ b/lib/pleroma/config/config_db.ex @@ -205,9 +205,14 @@ defmodule Pleroma.ConfigDB do {config, sub_keys} when is_list(sub_keys) <- {config, params[:subkeys]}, old_value <- from_binary(config.value), keys <- Enum.map(sub_keys, &do_transform_string(&1)), - new_value <- Keyword.drop(old_value, keys) do + {:partial_remove, config, new_value} when new_value != [] <- + {:partial_remove, config, Keyword.drop(old_value, keys)} do ConfigDB.update(config, %{value: new_value}) else + {:partial_remove, config, []} -> + Repo.delete(config) + {:ok, nil} + {config, nil} -> Repo.delete(config) {:ok, nil} diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index 30343a9f1..d45b1f7a7 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -831,7 +831,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do Enum.map(values, fn {key, value} -> db = if configs[group][key] do - ConfigDB.get_db_keys(value, key) + ConfigDB.get_db_keys(configs[group][key], key) end db_value = configs[group][key] -- cgit v1.2.3 From f01ab6cd29aaae39fef6a95ec8490223fb692499 Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Tue, 21 Jan 2020 17:49:22 +0300 Subject: some refactor and tests --- lib/mix/tasks/pleroma/config.ex | 154 ++++++++++++---------- lib/pleroma/config/config_db.ex | 152 ++++++++++----------- lib/pleroma/config/holder.ex | 23 +--- lib/pleroma/config/loader.ex | 36 +++-- lib/pleroma/config/transfer_task.ex | 49 +++---- lib/pleroma/web/admin_api/admin_api_controller.ex | 2 +- 6 files changed, 221 insertions(+), 195 deletions(-) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex index 715b72dbe..861832451 100644 --- a/lib/mix/tasks/pleroma/config.ex +++ b/lib/mix/tasks/pleroma/config.ex @@ -19,94 +19,55 @@ defmodule Mix.Tasks.Pleroma.Config do end def run(["migrate_from_db" | options]) do - # TODO: add support for releases start_pleroma() {opts, _} = OptionParser.parse!(options, - strict: [env: :string, delete_from_db: :boolean], - aliases: [d: :delete_from_db] + strict: [env: :string, delete: :boolean], + aliases: [d: :delete] ) - with {:active?, true} <- - {:active?, Pleroma.Config.get([:configurable_from_database])}, - env when is_binary(env) <- opts[:env] || "prod", - config_path <- config_path(env), - {:ok, file} <- File.open(config_path, [:write, :utf8]) do - IO.write(file, config_header()) - - ConfigDB - |> Repo.all() - |> Enum.each(&write_to_file_with_deletion(&1, file, opts[:delete_from_db])) - - File.close(file) - System.cmd("mix", ["format", config_path]) - else - {:active?, false} -> - shell_info( - "Migration is not allowed by config. You can change this behavior in instance settings." - ) - - error -> - shell_info("Error occuried while opening file. #{inspect(error)}") - end - end - - defp config_path(env) do - path = - if Pleroma.Config.get(:release) do - :config_path - |> Pleroma.Config.get() - |> Path.dirname() - else - "config" - end - - Path.join(path, "#{env}.exported_from_db.secret.exs") + migrate_from_db(opts) end @spec migrate_to_db(Path.t() | nil) :: any() def migrate_to_db(file_path \\ nil) do if Pleroma.Config.get([:configurable_from_database]) do - user_config_file = - if Pleroma.Config.get(:release), - do: Pleroma.Config.get(:config_path), - else: "config/#{Pleroma.Config.get(:env)}.secret.exs" + config_file = + if file_path do + file_path + else + if Pleroma.Config.get(:release) do + Pleroma.Config.get(:config_path) + else + "config/#{Pleroma.Config.get(:env)}.secret.exs" + end + end - config_file = file_path || user_config_file do_migrate_to_db(config_file) else - shell_info( - "Migration is not allowed by config. You can change this behavior in instance settings." - ) + migration_error() end end - if Code.ensure_loaded?(Config.Reader) do - defp config_header, do: "import Config\r\n\r\n" - defp read_file(config_file), do: Config.Reader.read_imports!(config_file) - else - defp config_header, do: "use Mix.Config\r\n\r\n" - defp read_file(config_file), do: Mix.Config.eval!(config_file) - end - defp do_migrate_to_db(config_file) do if File.exists?(config_file) do - {custom_config, _paths} = read_file(config_file) + custom_config = + config_file + |> read_file() + |> elem(0) custom_config |> Keyword.keys() - |> Enum.each(&create(&1, custom_config[&1])) + |> Enum.each(&create(&1, custom_config)) else shell_info("To migrate settings, you must define custom settings in #{config_file}.") end end defp create(group, settings) do - Enum.reject(settings, fn {k, _v} -> - k in [Pleroma.Repo, Pleroma.Web.Endpoint, :env, :configurable_from_database] or - (group == :phoenix and k == :serve_endpoints) - end) + group + |> Pleroma.Config.Loader.filter_group(settings) |> Enum.each(fn {key, value} -> key = inspect(key) {:ok, _} = ConfigDB.update_or_create(%{group: inspect(group), key: key, value: value}) @@ -117,17 +78,70 @@ defmodule Mix.Tasks.Pleroma.Config do shell_info("Settings for group :#{group} migrated.") end - defp write_to_file_with_deletion(config, file, with_deletion) do - IO.write( - file, - "config #{config.group}, #{config.key}, #{ - inspect(ConfigDB.from_binary(config.value), limit: :infinity) - }\r\n\r\n" - ) + defp migrate_from_db(opts) do + if Pleroma.Config.get([:configurable_from_database]) do + env = opts[:env] || "prod" + + config_path = + if Pleroma.Config.get(:release) do + :config_path + |> Pleroma.Config.get() + |> Path.dirname() + else + "config" + end + |> Path.join("#{env}.exported_from_db.secret.exs") + + file = File.open!(config_path, [:write, :utf8]) + + IO.write(file, config_header()) + + ConfigDB + |> Repo.all() + |> Enum.each(&write_and_delete(&1, file, opts[:delete])) - if with_deletion do - {:ok, _} = Repo.delete(config) - shell_info("#{config.key} deleted from DB.") + :ok = File.close(file) + System.cmd("mix", ["format", config_path]) + else + migration_error() end end + + defp migration_error do + shell_error( + "Migration is not allowed in config. You can change this behavior by setting `configurable_from_database` to true." + ) + end + + if Code.ensure_loaded?(Config.Reader) do + defp config_header, do: "import Config\r\n\r\n" + defp read_file(config_file), do: Config.Reader.read_imports!(config_file) + else + defp config_header, do: "use Mix.Config\r\n\r\n" + defp read_file(config_file), do: Mix.Config.eval!(config_file) + end + + defp write_and_delete(config, file, delete?) do + config + |> write(file) + |> delete(delete?) + end + + defp write(config, file) do + value = + config.value + |> ConfigDB.from_binary() + |> inspect(limit: :infinity) + + IO.write(file, "config #{config.group}, #{config.key}, #{value}\r\n\r\n") + + config + end + + defp delete(config, true) do + {:ok, _} = Repo.delete(config) + shell_info("#{config.key} deleted from DB.") + end + + defp delete(_config, _), do: :ok end diff --git a/lib/pleroma/config/config_db.ex b/lib/pleroma/config/config_db.ex index 1afe294f4..102e26773 100644 --- a/lib/pleroma/config/config_db.ex +++ b/lib/pleroma/config/config_db.ex @@ -4,14 +4,38 @@ defmodule Pleroma.ConfigDB do use Ecto.Schema + import Ecto.Changeset import Ecto.Query import Pleroma.Web.Gettext + alias __MODULE__ alias Pleroma.Repo @type t :: %__MODULE__{} + @full_key_update [ + {:pleroma, :ecto_repos}, + {:quack, :meta}, + {:mime, :types}, + {:cors_plug, [:max_age, :methods, :expose, :headers]}, + {:auto_linker, :opts}, + {:swarm, :node_blacklist}, + {:logger, :backends} + ] + + @full_subkey_update [ + {:pleroma, :assets, :mascots}, + {:pleroma, :emoji, :groups}, + {:pleroma, :workers, :retries}, + {:pleroma, :mrf_subchain, :match_actor}, + {:pleroma, :mrf_keyword, :replace} + ] + + @regex ~r/^~r(?'delimiter'[\/|"'([{<]{1})(?'pattern'.+)[\/|"')\]}>]{1}(?'modifier'[uismxfU]*)/u + + @delimiters ["/", "|", "\"", "'", {"(", ")"}, {"[", "]"}, {"{", "}"}, {"<", ">"}] + schema "config" do field(:key, :string) field(:group, :string) @@ -78,37 +102,35 @@ defmodule Pleroma.ConfigDB do end end - @full_subkey_update [ - {:pleroma, :assets, :mascots}, - {:pleroma, :emoji, :groups}, - {:pleroma, :workers, :retries}, - {:pleroma, :mrf_subchain, :match_actor}, - {:pleroma, :mrf_keyword, :replace} - ] - - @spec deep_merge(atom(), atom(), keyword(), keyword()) :: keyword() - def deep_merge(group, key, old_value, new_value) do - old_keys = - old_value - |> Keyword.keys() - |> MapSet.new() + @spec merge_group(atom(), atom(), keyword(), keyword()) :: keyword() + def merge_group(group, key, old_value, new_value) do + new_keys = to_map_set(new_value) - new_keys = - new_value - |> Keyword.keys() - |> MapSet.new() - - intersect_keys = old_keys |> MapSet.intersection(new_keys) |> MapSet.to_list() - - subkeys = sub_key_full_update(group, key, intersect_keys) + intersect_keys = + old_value |> to_map_set() |> MapSet.intersection(new_keys) |> MapSet.to_list() merged_value = ConfigDB.merge(old_value, new_value) - Enum.reduce(subkeys, merged_value, fn subkey, acc -> + @full_subkey_update + |> Enum.map(fn + {g, k, subkey} when g == group and k == key -> + if subkey in intersect_keys, do: subkey, else: [] + + _ -> + [] + end) + |> List.flatten() + |> Enum.reduce(merged_value, fn subkey, acc -> Keyword.put(acc, subkey, new_value[subkey]) end) end + defp to_map_set(keyword) do + keyword + |> Keyword.keys() + |> MapSet.new() + end + @spec sub_key_full_update?(atom(), atom(), [Keyword.key()]) :: boolean() def sub_key_full_update?(group, key, subkeys) do Enum.any?(@full_subkey_update, fn {g, k, subkey} -> @@ -116,17 +138,7 @@ defmodule Pleroma.ConfigDB do end) end - defp sub_key_full_update(group, key, subkeys) do - Enum.map(@full_subkey_update, fn - {g, k, subkey} when g == group and k == key -> - if subkey in subkeys, do: subkey, else: [] - - _ -> - [] - end) - |> List.flatten() - end - + @spec merge(keyword(), keyword()) :: keyword() def merge(config1, config2) when is_list(config1) and is_list(config2) do Keyword.merge(config1, config2, fn _, app1, app2 -> if Keyword.keyword?(app1) and Keyword.keyword?(app2) do @@ -145,31 +157,6 @@ defmodule Pleroma.ConfigDB do end end - @full_key_update [ - {:pleroma, :ecto_repos}, - {:quack, :meta}, - {:mime, :types}, - {:cors_plug, [:max_age, :methods, :expose, :headers]}, - {:auto_linker, :opts}, - {:swarm, :node_blacklist}, - {:logger, :backends} - ] - - defp only_full_update?(%ConfigDB{} = config) do - config_group = ConfigDB.from_string(config.group) - config_key = ConfigDB.from_string(config.key) - - Enum.any?(@full_key_update, fn - {group, key} when is_list(key) -> - config_group == group and config_key in key - - {group, key} -> - config_group == group and config_key == key - end) - end - - defp can_be_partially_updated?(%ConfigDB{} = config), do: not only_full_update?(config) - @spec update_or_create(map()) :: {:ok, ConfigDB.t()} | {:error, Changeset.t()} def update_or_create(params) do search_opts = Map.take(params, [:group, :key]) @@ -181,7 +168,7 @@ defmodule Pleroma.ConfigDB do transformed_value <- do_transform(params[:value]), {:can_be_merged, true, config} <- {:can_be_merged, is_list(transformed_value), config}, new_value <- - deep_merge( + merge_group( ConfigDB.from_string(config.group), ConfigDB.from_string(config.key), old_value, @@ -197,6 +184,21 @@ defmodule Pleroma.ConfigDB do end end + defp can_be_partially_updated?(%ConfigDB{} = config), do: not only_full_update?(config) + + defp only_full_update?(%ConfigDB{} = config) do + config_group = ConfigDB.from_string(config.group) + config_key = ConfigDB.from_string(config.key) + + Enum.any?(@full_key_update, fn + {group, key} when is_list(key) -> + config_group == group and config_key in key + + {group, key} -> + config_group == group and config_key == key + end) + end + @spec delete(map()) :: {:ok, ConfigDB.t()} | {:error, Changeset.t()} | {:ok, nil} def delete(params) do search_opts = Map.delete(params, :subkeys) @@ -286,18 +288,24 @@ defmodule Pleroma.ConfigDB do } end - # TODO: will become useless after removing hackney defp do_convert({:partial_chain, entity}), do: %{"tuple" => [":partial_chain", inspect(entity)]} - defp do_convert(entity) when is_tuple(entity), - do: %{"tuple" => do_convert(Tuple.to_list(entity))} + defp do_convert(entity) when is_tuple(entity) do + value = + entity + |> Tuple.to_list() + |> do_convert() + + %{"tuple" => value} + end - defp do_convert(entity) when is_boolean(entity) or is_number(entity) or is_nil(entity), - do: entity + defp do_convert(entity) when is_boolean(entity) or is_number(entity) or is_nil(entity) do + entity + end defp do_convert(entity) when is_atom(entity) and entity in [:"tlsv1.1", :"tlsv1.2", :"tlsv1.3"] do - ":#{to_string(entity)}" + ":#{entity}" end defp do_convert(entity) when is_atom(entity), do: inspect(entity) @@ -325,7 +333,6 @@ defmodule Pleroma.ConfigDB do {:proxy_url, {do_transform_string(type), parse_host(host), port}} end - # TODO: will become useless after removing hackney defp do_transform(%{"tuple" => [":partial_chain", entity]}) do {partial_chain, []} = entity @@ -369,10 +376,9 @@ defmodule Pleroma.ConfigDB do end end - @delimiters ["/", "|", "\"", "'", {"(", ")"}, {"[", "]"}, {"{", "}"}, {"<", ">"}] - - defp find_valid_delimiter([], _string, _), - do: raise(ArgumentError, message: "valid delimiter for Regex expression not found") + defp find_valid_delimiter([], _string, _) do + raise(ArgumentError, message: "valid delimiter for Regex expression not found") + end defp find_valid_delimiter([{leading, closing} = delimiter | others], pattern, regex_delimiter) when is_tuple(delimiter) do @@ -391,11 +397,9 @@ defmodule Pleroma.ConfigDB do end end - @regex_parts ~r/^~r(?'delimiter'[\/|"'([{<]{1})(?'pattern'.+)[\/|"')\]}>]{1}(?'modifier'[uismxfU]*)/u - defp do_transform_string("~r" <> _pattern = regex) do with %{"modifier" => modifier, "pattern" => pattern, "delimiter" => regex_delimiter} <- - Regex.named_captures(@regex_parts, regex), + Regex.named_captures(@regex, regex), {:ok, {leading, closing}} <- find_valid_delimiter(@delimiters, pattern, regex_delimiter), {result, _} <- Code.eval_string("~r#{leading}#{pattern}#{closing}#{modifier}") do result diff --git a/lib/pleroma/config/holder.ex b/lib/pleroma/config/holder.ex index 0fa7c5bf7..d4fe892af 100644 --- a/lib/pleroma/config/holder.ex +++ b/lib/pleroma/config/holder.ex @@ -1,29 +1,16 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Config.Holder do @config Pleroma.Config.Loader.load_and_merge() @spec config() :: keyword() - def config do - @config - |> Keyword.keys() - |> Enum.map(&filter(&1, config(&1))) - |> List.flatten() - end + def config, do: @config @spec config(atom()) :: any() def config(group), do: @config[group] @spec config(atom(), atom()) :: any() def config(group, key), do: @config[group][key] - - defp filter(group, settings) when group not in [:swarm] do - filtered = - Enum.reject(settings, fn {k, _v} -> - k in [Pleroma.Repo, Pleroma.Web.Endpoint, :env, :configurable_from_database] or - (group == :phoenix and k == :serve_endpoints) - end) - - {group, filtered} - end - - defp filter(_, _), do: [] end diff --git a/lib/pleroma/config/loader.ex b/lib/pleroma/config/loader.ex index b13b6eaf4..68b247381 100644 --- a/lib/pleroma/config/loader.ex +++ b/lib/pleroma/config/loader.ex @@ -1,6 +1,19 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Config.Loader do @paths ["config/config.exs", "config/#{Mix.env()}.exs"] + @reject_keys [ + Pleroma.Repo, + Pleroma.Web.Endpoint, + :env, + :configurable_from_database, + :database, + :swarm + ] + if Code.ensure_loaded?(Config.Reader) do @spec load(Path.t()) :: keyword() def load(path), do: Config.Reader.read!(path) @@ -10,8 +23,9 @@ defmodule Pleroma.Config.Loader do # support for Elixir less than 1.9 @spec load(Path.t()) :: keyword() def load(path) do - {config, _paths} = Mix.Config.eval!(path) - config + path + |> Mix.Config.eval!() + |> elem(0) end defp do_merge(conf1, conf2), do: Mix.Config.merge(conf1, conf2) @@ -26,14 +40,20 @@ defmodule Pleroma.Config.Loader do all_paths |> Enum.map(&load(&1)) - |> merge() + |> Enum.reduce([], &do_merge(&2, &1)) + |> filter() end - @spec merge([keyword()], keyword()) :: keyword() - def merge(configs, acc \\ []) - def merge([], acc), do: acc + defp filter(configs) do + configs + |> Keyword.keys() + |> Enum.reduce([], &Keyword.put(&2, &1, filter_group(&1, configs))) + end - def merge([config | others], acc) do - merge(others, do_merge(acc, config)) + @spec filter_group(atom(), keyword()) :: keyword() + def filter_group(group, configs) do + Enum.reject(configs[group], fn {key, _v} -> + key in @reject_keys or (group == :phoenix and key == :serve_endpoints) + end) end end diff --git a/lib/pleroma/config/transfer_task.ex b/lib/pleroma/config/transfer_task.ex index 9b571d5d9..00c8790f8 100644 --- a/lib/pleroma/config/transfer_task.ex +++ b/lib/pleroma/config/transfer_task.ex @@ -37,31 +37,29 @@ defmodule Pleroma.Config.TransferTask do group = ConfigDB.from_string(setting.group) value = ConfigDB.from_binary(setting.value) - if group != :phoenix and key != :serve_endpoints do - default = Pleroma.Config.Holder.config(group, key) + default = Pleroma.Config.Holder.config(group, key) - merged_value = - if can_be_merged?(default, value) do - ConfigDB.deep_merge(group, key, default, value) - else - value - end + merged_value = + if can_be_merged?(default, value) do + ConfigDB.merge_group(group, key, default, value) + else + value + end - :ok = Application.put_env(group, key, merged_value) + :ok = Application.put_env(group, key, merged_value) - if group != :logger do - group + if group != :logger do + group + else + # change logger configuration in runtime, without restart + if Keyword.keyword?(merged_value) and + key not in [:compile_time_application, :backends, :compile_time_purge_matching] do + Logger.configure_backend(key, merged_value) else - # change logger configuration in runtime, without restart - if Keyword.keyword?(merged_value) and - key not in [:compile_time_application, :backends, :compile_time_purge_matching] do - Logger.configure_backend(key, merged_value) - else - Logger.configure([{key, merged_value}]) - end - - nil + Logger.configure([{key, merged_value}]) end + + nil end rescue e -> @@ -80,13 +78,16 @@ defmodule Pleroma.Config.TransferTask do :ok <- Application.stop(app) do :ok = Application.start(app) else - nil -> Logger.warn("#{app} is not started.") - error -> Logger.warn(inspect(error)) + nil -> + Logger.warn("#{app} is not started.") + + error -> + error + |> inspect() + |> Logger.warn() end end - defp can_be_merged?(val1, val2) when is_map(val1) and is_map(val2), do: true - defp can_be_merged?(val1, val2) when is_list(val1) and is_list(val2) do Keyword.keyword?(val1) and Keyword.keyword?(val2) end diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index d5230f9aa..1b09d137b 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -848,7 +848,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do merged_value = if !is_nil(db_value) and Keyword.keyword?(db_value) and ConfigDB.sub_key_full_update?(group, key, Keyword.keys(db_value)) do - ConfigDB.deep_merge(group, key, value, db_value) + ConfigDB.merge_group(group, key, value, db_value) else value end -- cgit v1.2.3 From d5f8a88a37cb4a2341f11d5e39adfaba024e3486 Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Wed, 22 Jan 2020 15:14:11 +0300 Subject: support for updating env after settings deletion --- lib/pleroma/config/config_db.ex | 4 +- lib/pleroma/config/transfer_task.ex | 58 +++++++++++++++-------- lib/pleroma/web/admin_api/admin_api_controller.ex | 24 +++++----- 3 files changed, 51 insertions(+), 35 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/config/config_db.ex b/lib/pleroma/config/config_db.ex index 102e26773..91a1aa0cc 100644 --- a/lib/pleroma/config/config_db.ex +++ b/lib/pleroma/config/config_db.ex @@ -199,7 +199,7 @@ defmodule Pleroma.ConfigDB do end) end - @spec delete(map()) :: {:ok, ConfigDB.t()} | {:error, Changeset.t()} | {:ok, nil} + @spec delete(map()) :: {:ok, ConfigDB.t()} | {:error, Changeset.t()} def delete(params) do search_opts = Map.delete(params, :subkeys) @@ -213,11 +213,9 @@ defmodule Pleroma.ConfigDB do else {:partial_remove, config, []} -> Repo.delete(config) - {:ok, nil} {config, nil} -> Repo.delete(config) - {:ok, nil} nil -> err = diff --git a/lib/pleroma/config/transfer_task.ex b/lib/pleroma/config/transfer_task.ex index 00c8790f8..d54f38ee4 100644 --- a/lib/pleroma/config/transfer_task.ex +++ b/lib/pleroma/config/transfer_task.ex @@ -16,37 +16,36 @@ defmodule Pleroma.Config.TransferTask do :ignore end - def load_and_update_env do + @spec load_and_update_env([ConfigDB.t()]) :: :ok | false + def load_and_update_env(deleted \\ []) do with true <- Pleroma.Config.get(:configurable_from_database), true <- Ecto.Adapters.SQL.table_exists?(Repo, "config"), started_applications <- Application.started_applications() do # We need to restart applications for loaded settings take effect - ConfigDB - |> Repo.all() - |> Enum.map(&update_env(&1)) + in_db = Repo.all(ConfigDB) + + with_deleted = in_db ++ deleted + + with_deleted + |> Enum.map(&merge_and_update(&1)) |> Enum.uniq() # TODO: some problem with prometheus after restart! |> Enum.reject(&(&1 in [:pleroma, nil, :prometheus])) |> Enum.each(&restart(started_applications, &1)) + + :ok end end - defp update_env(setting) do + defp merge_and_update(setting) do try do key = ConfigDB.from_string(setting.key) group = ConfigDB.from_string(setting.group) - value = ConfigDB.from_binary(setting.value) default = Pleroma.Config.Holder.config(group, key) + merged_value = merge_value(setting, default, group, key) - merged_value = - if can_be_merged?(default, value) do - ConfigDB.merge_group(group, key, default, value) - else - value - end - - :ok = Application.put_env(group, key, merged_value) + :ok = update_env(group, key, merged_value) if group != :logger do group @@ -62,17 +61,36 @@ defmodule Pleroma.Config.TransferTask do nil end rescue - e -> - Logger.warn( - "updating env causes error, group: #{inspect(setting.group)}, key: #{ - inspect(setting.key) - }, value: #{inspect(ConfigDB.from_binary(setting.value))}, error: #{inspect(e)}" - ) + error -> + error_msg = + "updating env causes error, group: " <> + inspect(setting.group) <> + " key: " <> + inspect(setting.key) <> + " value: " <> + inspect(ConfigDB.from_binary(setting.value)) <> " error: " <> inspect(error) + + Logger.warn(error_msg) nil end end + defp merge_value(%{__meta__: %{state: :deleted}}, default, _group, _key), do: default + + defp merge_value(setting, default, group, key) do + value = ConfigDB.from_binary(setting.value) + + if can_be_merged?(default, value) do + ConfigDB.merge_group(group, key, default, value) + else + value + end + end + + defp update_env(group, key, nil), do: Application.delete_env(group, key) + defp update_env(group, key, value), do: Application.put_env(group, key, value) + defp restart(started_applications, app) do with {^app, _, _} <- List.keyfind(started_applications, app, 0), :ok <- Application.stop(app) do diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index 1b09d137b..2314d3274 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -871,26 +871,26 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do def config_update(conn, %{"configs" => configs}) do with :ok <- configurable_from_database(conn) do - updated = + {_errors, results} = Enum.map(configs, fn %{"group" => group, "key" => key, "delete" => true} = params -> - with {:ok, config} <- - ConfigDB.delete(%{group: group, key: key, subkeys: params["subkeys"]}) do - config - end + ConfigDB.delete(%{group: group, key: key, subkeys: params["subkeys"]}) %{"group" => group, "key" => key, "value" => value} -> - with {:ok, config} <- - ConfigDB.update_or_create(%{group: group, key: key, value: value}) do - config - end + ConfigDB.update_or_create(%{group: group, key: key, value: value}) end) - |> Enum.reject(&is_nil(&1)) - |> Enum.map(fn config -> + |> Enum.split_with(fn result -> elem(result, 0) == :error end) + + {deleted, updated} = + results + |> Enum.map(fn {:ok, config} -> Map.put(config, :db, ConfigDB.get_db_keys(config)) end) + |> Enum.split_with(fn config -> + Ecto.get_meta(config, :state) == :deleted + end) - Pleroma.Config.TransferTask.load_and_update_env() + Pleroma.Config.TransferTask.load_and_update_env(deleted) Mix.Tasks.Pleroma.Config.run([ "migrate_from_db", -- cgit v1.2.3 From dd3fc50ea41871c6c02076cf2786c2488d4cf3ca Mon Sep 17 00:00:00 2001 From: lain Date: Wed, 22 Jan 2020 13:57:42 +0100 Subject: Emoji reactions: Change cache and reply format --- lib/pleroma/web/activity_pub/utils.ex | 64 ++++++++++++++-------- lib/pleroma/web/mastodon_api/views/status_view.ex | 7 +-- .../controllers/pleroma_api_controller.ex | 8 +-- 3 files changed, 47 insertions(+), 32 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index db7084246..4def431f1 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -312,19 +312,12 @@ defmodule Pleroma.Web.ActivityPub.Utils do |> Map.put("content", emoji) end - @spec update_element_in_object(String.t(), list(any), Object.t()) :: + @spec update_element_in_object(String.t(), list(any), Object.t(), integer() | nil) :: {:ok, Object.t()} | {:error, Ecto.Changeset.t()} - def update_element_in_object(property, element, object) do + def update_element_in_object(property, element, object, count \\ nil) do length = - if is_map(element) do - element - |> Map.values() - |> List.flatten() - |> length() - else - element - |> length() - end + count || + length(element) data = Map.merge( @@ -344,29 +337,52 @@ defmodule Pleroma.Web.ActivityPub.Utils do %Activity{data: %{"content" => emoji, "actor" => actor}}, object ) do - reactions = object.data["reactions"] || %{} - emoji_actors = reactions[emoji] || [] - new_emoji_actors = [actor | emoji_actors] |> Enum.uniq() - new_reactions = Map.put(reactions, emoji, new_emoji_actors) - update_element_in_object("reaction", new_reactions, object) + reactions = object.data["reactions"] || [] + + new_reactions = + case Enum.find_index(reactions, fn [candidate, _] -> emoji == candidate end) do + nil -> + reactions ++ [[emoji, [actor]]] + + index -> + List.update_at( + reactions, + index, + fn [emoji, users] -> [emoji, Enum.uniq([actor | users])] end + ) + end + + count = emoji_count(new_reactions) + + update_element_in_object("reaction", new_reactions, object, count) + end + + def emoji_count(reactions_list) do + Enum.reduce(reactions_list, 0, fn [_, users], acc -> acc + length(users) end) end def remove_emoji_reaction_from_object( %Activity{data: %{"content" => emoji, "actor" => actor}}, object ) do - reactions = object.data["reactions"] || %{} - emoji_actors = reactions[emoji] || [] - new_emoji_actors = List.delete(emoji_actors, actor) + reactions = object.data["reactions"] || [] new_reactions = - if new_emoji_actors == [] do - Map.delete(reactions, emoji) - else - Map.put(reactions, emoji, new_emoji_actors) + case Enum.find_index(reactions, fn [candidate, _] -> emoji == candidate end) do + nil -> + reactions + + index -> + List.update_at( + reactions, + index, + fn [emoji, users] -> [emoji, List.delete(users, actor)] end + ) + |> Enum.reject(fn [_, users] -> Enum.empty?(users) end) end - update_element_in_object("reaction", new_reactions, object) + count = emoji_count(new_reactions) + update_element_in_object("reaction", new_reactions, object, count) end @spec add_like_to_object(Activity.t(), Object.t()) :: diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index b59ac39bc..64a97896a 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -255,12 +255,11 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do emoji_reactions = with %{data: %{"reactions" => emoji_reactions}} <- object do - Enum.map(emoji_reactions, fn {emoji, users} -> - {emoji, length(users)} + Enum.map(emoji_reactions, fn [emoji, users] -> + [emoji, length(users)] end) - |> Enum.into(%{}) else - _ -> %{} + _ -> [] end %{ diff --git a/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex b/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex index 3285dc11b..bb19836ae 100644 --- a/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex @@ -43,21 +43,21 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do def emoji_reactions_by(%{assigns: %{user: user}} = conn, %{"id" => activity_id}) do with %Activity{} = activity <- Activity.get_by_id_with_object(activity_id), - %Object{data: %{"reactions" => emoji_reactions}} <- Object.normalize(activity) do + %Object{data: %{"reactions" => emoji_reactions}} when is_list(emoji_reactions) <- + Object.normalize(activity) do reactions = emoji_reactions - |> Enum.map(fn {emoji, users} -> + |> Enum.map(fn [emoji, users] -> users = Enum.map(users, &User.get_cached_by_ap_id/1) {emoji, AccountView.render("index.json", %{users: users, for: user, as: :user})} end) - |> Enum.into(%{}) conn |> json(reactions) else _e -> conn - |> json(%{}) + |> json([]) end end -- cgit v1.2.3