mirror of
https://git.pleroma.social/pleroma/pleroma.git
synced 2024-06-01 08:28:06 +00:00
config versioning
- added DynamicSupervisor, which starts Pleroma deps and restarts config dependent deps - added versioning for in database config. New version is created from changes which are passed to config update/delete endpoint. Every version contains backup with all changes added through update. Versioning supports rollbacks with N steps. With a rollback, all versions that come after the version on which the rollback was made are deleted.
This commit is contained in:
parent
745375bdcf
commit
2538c741c0
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -56,4 +56,7 @@ pleroma.iml
|
|||
|
||||
# Editor temp files
|
||||
/*~
|
||||
/*#
|
||||
/*#
|
||||
|
||||
# local iex
|
||||
.iex.exs
|
||||
|
|
30
CHANGELOG.md
30
CHANGELOG.md
|
@ -6,19 +6,38 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
|
||||
## Unreleased
|
||||
|
||||
### Changed
|
||||
|
||||
- The `application` metadata returned with statuses is no longer hardcoded. Apps that want to display these details will now have valid data for new posts after this change.
|
||||
- HTTPSecurityPlug now sends a response header to opt out of Google's FLoC (Federated Learning of Cohorts) targeted advertising.
|
||||
|
||||
### Added
|
||||
|
||||
- The `application` metadata returned with statuses is no longer hardcoded. Apps that want to display these details will now have valid data for new posts after this change.
|
||||
- MRF (`FollowBotPolicy`): New MRF Policy which makes a designated local Bot account attempt to follow all users in public Notes received by your instance. Users who require approving follower requests or have #nobot in their profile are excluded.
|
||||
- HTTPSecurityPlug now sends a response header to opt out of Google's FLoC (Federated Learning of Cohorts) targeted advertising.
|
||||
- Config Versioning.
|
||||
- MRF (`FollowBotPolicy`): New MRF Policy which makes a designated local Bot account attempt to follow all users in public Notes received by your instance. Users who require approving follower requests or have #nobot in their profile are excluded.
|
||||
- Return OAuth token `id` (primary key) in POST `/oauth/token`.
|
||||
|
||||
<details>
|
||||
<summary>API Changes</summary>
|
||||
|
||||
- Admin API: (`GET /api/pleroma/admin/config/versions`) - endpoint to get list of config versions.
|
||||
- Admin API: (`GET /api/pleroma/admin/config/versions/rollback/:id`) - endpoint to rollback config to specific version.
|
||||
|
||||
</details>
|
||||
|
||||
### Fixed
|
||||
|
||||
- Don't crash so hard when email settings are invalid.
|
||||
|
||||
### Changed
|
||||
|
||||
- Improved hashtag timeline performance (requires a background migration).
|
||||
|
||||
<details>
|
||||
<summary>API Changes</summary>
|
||||
|
||||
- **Breaking**: AdminAPI configs can be without key parameter.
|
||||
|
||||
</details>
|
||||
|
||||
## Unreleased (Patch)
|
||||
|
||||
### Fixed
|
||||
|
@ -71,7 +90,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
- Pleroma API: Reroute `/api/pleroma/*` to `/api/v1/pleroma/*`
|
||||
|
||||
</details>
|
||||
- Improved hashtag timeline performance (requires a background migration).
|
||||
|
||||
### Added
|
||||
|
||||
|
|
|
@ -1062,65 +1062,63 @@ config :pleroma, :config_description, [
|
|||
description:
|
||||
"Where logs will be sent, :console - send logs to stdout, { ExSyslogger, :ex_syslogger } - to syslog, Quack.Logger - to Slack.",
|
||||
suggestions: [:console, {ExSyslogger, :ex_syslogger}, Quack.Logger]
|
||||
}
|
||||
]
|
||||
},
|
||||
%{
|
||||
group: :logger,
|
||||
type: :group,
|
||||
key: :ex_syslogger,
|
||||
label: "ExSyslogger",
|
||||
description: "ExSyslogger-related settings",
|
||||
children: [
|
||||
%{
|
||||
key: :level,
|
||||
type: {:dropdown, :atom},
|
||||
description: "Log level",
|
||||
suggestions: [:debug, :info, :warn, :error]
|
||||
},
|
||||
%{
|
||||
key: :ident,
|
||||
type: :string,
|
||||
description:
|
||||
"A string that's prepended to every message, and is typically set to the app name",
|
||||
suggestions: ["pleroma"]
|
||||
key: :ex_syslogger,
|
||||
type: :keyword,
|
||||
label: "ExSyslogger",
|
||||
description: "ExSyslogger-related settings",
|
||||
children: [
|
||||
%{
|
||||
key: :level,
|
||||
type: {:dropdown, :atom},
|
||||
description: "Log level",
|
||||
suggestions: [:debug, :info, :warn, :error]
|
||||
},
|
||||
%{
|
||||
key: :ident,
|
||||
type: :string,
|
||||
description:
|
||||
"A string that's prepended to every message, and is typically set to the app name",
|
||||
suggestions: ["pleroma"]
|
||||
},
|
||||
%{
|
||||
key: :format,
|
||||
type: :string,
|
||||
description: "Default: \"$date $time [$level] $levelpad$node $metadata $message\"",
|
||||
suggestions: ["$metadata[$level] $message"]
|
||||
},
|
||||
%{
|
||||
key: :metadata,
|
||||
type: {:list, :atom},
|
||||
suggestions: [:request_id]
|
||||
}
|
||||
]
|
||||
},
|
||||
%{
|
||||
key: :format,
|
||||
type: :string,
|
||||
description: "Default: \"$date $time [$level] $levelpad$node $metadata $message\"",
|
||||
suggestions: ["$metadata[$level] $message"]
|
||||
},
|
||||
%{
|
||||
key: :metadata,
|
||||
type: {:list, :atom},
|
||||
suggestions: [:request_id]
|
||||
}
|
||||
]
|
||||
},
|
||||
%{
|
||||
group: :logger,
|
||||
type: :group,
|
||||
key: :console,
|
||||
label: "Console Logger",
|
||||
description: "Console logger settings",
|
||||
children: [
|
||||
%{
|
||||
key: :level,
|
||||
type: {:dropdown, :atom},
|
||||
description: "Log level",
|
||||
suggestions: [:debug, :info, :warn, :error]
|
||||
},
|
||||
%{
|
||||
key: :format,
|
||||
type: :string,
|
||||
description: "Default: \"$date $time [$level] $levelpad$node $metadata $message\"",
|
||||
suggestions: ["$metadata[$level] $message"]
|
||||
},
|
||||
%{
|
||||
key: :metadata,
|
||||
type: {:list, :atom},
|
||||
suggestions: [:request_id]
|
||||
key: :console,
|
||||
type: :keyword,
|
||||
label: "Console Logger",
|
||||
description: "Console logger settings",
|
||||
children: [
|
||||
%{
|
||||
key: :level,
|
||||
type: {:dropdown, :atom},
|
||||
description: "Log level",
|
||||
suggestions: [:debug, :info, :warn, :error]
|
||||
},
|
||||
%{
|
||||
key: :format,
|
||||
type: :string,
|
||||
description: "Default: \"$date $time [$level] $levelpad$node $metadata $message\"",
|
||||
suggestions: ["$metadata[$level] $message"]
|
||||
},
|
||||
%{
|
||||
key: :metadata,
|
||||
type: {:list, :atom},
|
||||
suggestions: [:request_id]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -1840,19 +1838,13 @@ config :pleroma, :config_description, [
|
|||
},
|
||||
%{
|
||||
group: :pleroma,
|
||||
key: :admin_token,
|
||||
label: "Pleroma Admin Token",
|
||||
type: :group,
|
||||
type: :string,
|
||||
description:
|
||||
"Allows setting a token that can be used to authenticate requests with admin privileges without a normal user account token. Append the `admin_token` parameter to requests to utilize it. (Please reconsider using HTTP Basic Auth or OAuth-based authentication if possible)",
|
||||
children: [
|
||||
%{
|
||||
key: :admin_token,
|
||||
type: :string,
|
||||
description: "Admin token",
|
||||
suggestions: [
|
||||
"Please use a high entropy string or UUID"
|
||||
]
|
||||
}
|
||||
suggestions: [
|
||||
"Please use a high entropy string or UUID"
|
||||
]
|
||||
},
|
||||
%{
|
||||
|
@ -2153,16 +2145,11 @@ config :pleroma, :config_description, [
|
|||
},
|
||||
%{
|
||||
group: :pleroma,
|
||||
key: Pleroma.Web.Auth.Authenticator,
|
||||
label: "Pleroma Authenticator",
|
||||
type: :group,
|
||||
type: :module,
|
||||
description: "Authenticator",
|
||||
children: [
|
||||
%{
|
||||
key: Pleroma.Web.Auth.Authenticator,
|
||||
type: :module,
|
||||
suggestions: [Pleroma.Web.Auth.PleromaAuthenticator, Pleroma.Web.Auth.LDAPAuthenticator]
|
||||
}
|
||||
]
|
||||
suggestions: [Pleroma.Web.Auth.PleromaAuthenticator, Pleroma.Web.Auth.LDAPAuthenticator]
|
||||
},
|
||||
%{
|
||||
group: :pleroma,
|
||||
|
|
|
@ -91,7 +91,9 @@ config :pleroma, Pleroma.ScheduledActivity,
|
|||
total_user_limit: 3,
|
||||
enabled: false
|
||||
|
||||
config :pleroma, :rate_limit, %{}
|
||||
# Hack to drop default settings from `config.exs`, because keywords are deeply merged, so there is no other way to do it.
|
||||
config :pleroma, :rate_limit, nil
|
||||
config :pleroma, :rate_limit, []
|
||||
|
||||
config :pleroma, :http_security, report_uri: "https://endpoint.com"
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
{! backend/administration/CLI_tasks/general_cli_task_info.include !}
|
||||
|
||||
## Transfer config from file to DB.
|
||||
## Transfer config from file to DB
|
||||
|
||||
!!! note
|
||||
You need to add the following to your config before executing this command:
|
||||
|
@ -154,4 +154,24 @@ This forcibly removes all saved values in the database.
|
|||
|
||||
```sh
|
||||
mix pleroma.config [--force] reset
|
||||
|
||||
## Rollback config version
|
||||
|
||||
!!! note
|
||||
You need to add the following to your config before executing this command:
|
||||
|
||||
```elixir
|
||||
config :pleroma, configurable_from_database: true
|
||||
```
|
||||
|
||||
Rollback will restore last backup by default. If you want to restore older version use `-s` parameter.
|
||||
|
||||
=== "OTP"
|
||||
```sh
|
||||
./bin/pleroma_ctl config rollback [-s 2]
|
||||
```
|
||||
|
||||
=== "From Source"
|
||||
```sh
|
||||
mix pleroma.config rollback [-s 2]
|
||||
```
|
||||
|
|
|
@ -282,7 +282,7 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
|
|||
- *optional* `with_reblogs`: `true`/`false` – allows to see reblogs (default is false)
|
||||
- Response:
|
||||
- On failure: `Not found`
|
||||
- On success: JSON, where:
|
||||
- On success: JSON, where:
|
||||
- `total`: total count of the statuses for the user
|
||||
- `activities`: list of the statuses for the user
|
||||
|
||||
|
@ -339,7 +339,7 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
|
|||
Params: none
|
||||
Response:
|
||||
|
||||
* On success: JSON array of relays
|
||||
- On success: JSON array of relays
|
||||
|
||||
```json
|
||||
[
|
||||
|
@ -354,11 +354,11 @@ Response:
|
|||
|
||||
Params:
|
||||
|
||||
* `relay_url`
|
||||
- `relay_url`
|
||||
|
||||
Response:
|
||||
|
||||
* On success: relay json object
|
||||
- On success: relay json object
|
||||
|
||||
```json
|
||||
{"actor": "https://example.com/relay", "followed_back": true}
|
||||
|
@ -374,7 +374,7 @@ Response:
|
|||
|
||||
Response:
|
||||
|
||||
* On success: URL of the unfollowed relay
|
||||
- On success: URL of the unfollowed relay
|
||||
|
||||
```json
|
||||
{"https://example.com/relay"}
|
||||
|
@ -472,7 +472,6 @@ Response:
|
|||
|
||||
### Get a password reset token for a given nickname
|
||||
|
||||
|
||||
- Params: none
|
||||
- Response:
|
||||
|
||||
|
@ -493,7 +492,7 @@ Response:
|
|||
|
||||
## PUT `/api/v1/pleroma/admin/users/disable_mfa`
|
||||
|
||||
### Disable mfa for user's account.
|
||||
### Disable MFA for user's account
|
||||
|
||||
- Params:
|
||||
- `nickname`
|
||||
|
@ -551,30 +550,30 @@ Response:
|
|||
|
||||
### Change the user's email, password, display and settings-related fields
|
||||
|
||||
* Params:
|
||||
* `email`
|
||||
* `password`
|
||||
* `name`
|
||||
* `bio`
|
||||
* `avatar`
|
||||
* `locked`
|
||||
* `no_rich_text`
|
||||
* `default_scope`
|
||||
* `banner`
|
||||
* `hide_follows`
|
||||
* `hide_followers`
|
||||
* `hide_followers_count`
|
||||
* `hide_follows_count`
|
||||
* `hide_favorites`
|
||||
* `allow_following_move`
|
||||
* `background`
|
||||
* `show_role`
|
||||
* `skip_thread_containment`
|
||||
* `fields`
|
||||
* `is_discoverable`
|
||||
* `actor_type`
|
||||
- Params:
|
||||
- `email`
|
||||
- `password`
|
||||
- `name`
|
||||
- `bio`
|
||||
- `avatar`
|
||||
- `locked`
|
||||
- `no_rich_text`
|
||||
- `default_scope`
|
||||
- `banner`
|
||||
- `hide_follows`
|
||||
- `hide_followers`
|
||||
- `hide_followers_count`
|
||||
- `hide_follows_count`
|
||||
- `hide_favorites`
|
||||
- `allow_following_move`
|
||||
- `background`
|
||||
- `show_role`
|
||||
- `skip_thread_containment`
|
||||
- `fields`
|
||||
- `is_discoverable`
|
||||
- `actor_type`
|
||||
|
||||
* Responses:
|
||||
- Responses:
|
||||
|
||||
Status: 200
|
||||
|
||||
|
@ -896,7 +895,7 @@ Status: 404
|
|||
- Params: none
|
||||
- Response:
|
||||
- On failure:
|
||||
- 400 Bad Request `"To use this endpoint you need to enable configuration from database."`
|
||||
- 400 Bad Request `"You must enable configurable_from_database in your config file."`
|
||||
|
||||
```json
|
||||
{}
|
||||
|
@ -909,6 +908,7 @@ Status: 404
|
|||
- Params: none
|
||||
- Response:
|
||||
- `need_reboot` - boolean
|
||||
|
||||
```json
|
||||
{
|
||||
"need_reboot": false
|
||||
|
@ -917,7 +917,7 @@ Status: 404
|
|||
|
||||
## `GET /api/v1/pleroma/admin/config`
|
||||
|
||||
### Get list of merged default settings with saved in database.
|
||||
### Get list of merged default settings with saved in database
|
||||
|
||||
*If `need_reboot` is `true`, instance must be restarted, so reboot time settings can take effect.*
|
||||
|
||||
|
@ -927,7 +927,7 @@ Status: 404
|
|||
- `only_db`: true (*optional*, get only saved in database settings)
|
||||
- Response:
|
||||
- On failure:
|
||||
- 400 Bad Request `"To use this endpoint you need to enable configuration from database."`
|
||||
- 400 Bad Request `"You must enable configurable_from_database in your config file."`
|
||||
|
||||
```json
|
||||
{
|
||||
|
@ -952,34 +952,34 @@ Status: 404
|
|||
|
||||
Some modifications are necessary to save the config settings correctly:
|
||||
|
||||
- strings which start with `Pleroma.`, `Phoenix.`, `Tesla.` or strings like `Oban`, `Ueberauth` will be converted to modules;
|
||||
```
|
||||
"Pleroma.Upload" -> Pleroma.Upload
|
||||
"Oban" -> Oban
|
||||
```
|
||||
- strings starting with `:` will be converted to atoms;
|
||||
```
|
||||
":pleroma" -> :pleroma
|
||||
```
|
||||
- objects with `tuple` key and array value will be converted to tuples;
|
||||
```
|
||||
{"tuple": ["string", "Pleroma.Upload", []]} -> {"string", Pleroma.Upload, []}
|
||||
```
|
||||
- arrays with *tuple objects* will be converted to keywords;
|
||||
```
|
||||
[{"tuple": [":key1", "value"]}, {"tuple": [":key2", "value"]}] -> [key1: "value", key2: "value"]
|
||||
```
|
||||
- strings which start with `Pleroma.`, `Phoenix.`, `Tesla.` or strings like `Oban`, `Ueberauth` will be converted to modules
|
||||
- `"Pleroma.Upload"` -> `Pleroma.Upload`
|
||||
- `"Oban"` -> `Oban`
|
||||
- strings starting with `:` will be converted to atoms
|
||||
- `":pleroma"` -> `:pleroma`
|
||||
- objects with `tuple` key and array value will be converted to tuples
|
||||
- `{"tuple": ["string", "Pleroma.Upload", []]}` -> `{"string", Pleroma.Upload, []}`
|
||||
- arrays with *tuple objects* will be converted to keywords
|
||||
- `[{"tuple": [":key1", "value"]}, {"tuple": [":key2", "value"]}]` -> `[key1: "value", key2: "value"]`
|
||||
|
||||
Most of the settings will be applied in `runtime`, this means that you don't need to restart the instance. But some settings are applied in `compile time` and require a reboot of the instance, such as:
|
||||
- all settings inside these keys:
|
||||
- `:hackney_pools`
|
||||
- `:connections_pool`
|
||||
- `:pools`
|
||||
Most of the settings will be applied in `runtime`, this means that changes will be applied immediately. But some settings are applied on `startup time` and will take effect after restart of the pleroma parts, such as:
|
||||
|
||||
- all settings inside these keys
|
||||
- `:chat`
|
||||
- `Oban`
|
||||
- `:rate_limit`
|
||||
- `:streamer`
|
||||
- `:pools`
|
||||
- `:connections_pool`
|
||||
- `:hackney_pools`
|
||||
- `:gopher`
|
||||
- `:eshhd`
|
||||
- `:ex_aws`
|
||||
- partially settings inside these keys:
|
||||
- `:seconds_valid` in `Pleroma.Captcha`
|
||||
- `:proxy_remote` in `Pleroma.Upload`
|
||||
- `:upload_limit` in `:instance`
|
||||
- `:enabled` in `:fed_sockets`
|
||||
|
||||
- Params:
|
||||
- `configs` - array of config objects
|
||||
|
@ -990,34 +990,33 @@ Most of the settings will be applied in `runtime`, this means that you don't nee
|
|||
- `delete` - true (*optional*, if setting must be deleted)
|
||||
- `subkeys` - array of strings (*optional*, only works when `delete=true` parameter is passed, otherwise will be ignored)
|
||||
|
||||
*When a value have several nested settings, you can delete only some nested settings by passing a parameter `subkeys`, without deleting all settings by key.*
|
||||
```
|
||||
[subkey: val1, subkey2: val2, subkey3: val3] \\ initial value
|
||||
{"group": ":pleroma", "key": "some_key", "delete": true, "subkeys": [":subkey", ":subkey3"]} \\ passing json for deletion
|
||||
[subkey2: val2] \\ value after deletion
|
||||
```
|
||||
#### Partial deletion
|
||||
|
||||
*Most of the settings can be partially updated through merge old values with new values, except settings value of which is list or is not keyword.*
|
||||
Keys inside value can be partially deleted by passing `subkeys` parameter. If after partial deleting an empty list remains, then the entire setting will be deleted.
|
||||
|
||||
Example:
|
||||
|
||||
Example of setting without keyword in value:
|
||||
```elixir
|
||||
config :tesla, :adapter, Tesla.Adapter.Hackney
|
||||
# initial value
|
||||
[subkey: :val1, subkey2: :val2, subkey3: :val3]
|
||||
```
|
||||
|
||||
```json
|
||||
// config object for deletion
|
||||
{"group": ":pleroma", "key": "some_key", "delete": true, "subkeys": [":subkey", ":subkey3"]}
|
||||
```
|
||||
|
||||
List of settings which support only full update by key:
|
||||
```elixir
|
||||
@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}
|
||||
]
|
||||
# value after deletion
|
||||
[subkey2: :val2]
|
||||
```
|
||||
|
||||
List of settings which support only full update by subkey:
|
||||
#### Partial update
|
||||
|
||||
Most settings can be partially updated: new values will be merged with existing ones.
|
||||
|
||||
The following settings are exceptions and should be fully updated:
|
||||
|
||||
```elixir
|
||||
@full_subkey_update [
|
||||
{:pleroma, :assets, :mascots},
|
||||
|
@ -1028,22 +1027,24 @@ List of settings which support only full update by subkey:
|
|||
]
|
||||
```
|
||||
|
||||
*Settings without explicit key must be sended in separate config object params.*
|
||||
#### Settings without explicit keys
|
||||
|
||||
Settings without explicit key must be sended in one config object with null value as key.
|
||||
|
||||
```elixir
|
||||
config :quack,
|
||||
level: :debug,
|
||||
meta: [:all],
|
||||
...
|
||||
meta: [:all]
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"configs": [
|
||||
{"group": ":quack", "key": ":level", "value": ":debug"},
|
||||
{"group": ":quack", "key": ":meta", "value": [":all"]},
|
||||
...
|
||||
{"group": ":quack", "key": null, "value": [{"tuple": [":level", ":debug"]}, {"tuple": [":meta", ":all"]}]}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
- Request:
|
||||
|
||||
```json
|
||||
|
@ -1077,7 +1078,8 @@ config :quack,
|
|||
|
||||
- Response:
|
||||
- On failure:
|
||||
- 400 Bad Request `"To use this endpoint you need to enable configuration from database."`
|
||||
- 400 Bad Request `"You must enable configurable_from_database in your config file."`
|
||||
|
||||
```json
|
||||
{
|
||||
"configs": [
|
||||
|
@ -1091,9 +1093,10 @@ config :quack,
|
|||
}
|
||||
```
|
||||
|
||||
## ` GET /api/v1/pleroma/admin/config/descriptions`
|
||||
## `GET /api/v1/pleroma/admin/config/descriptions`
|
||||
|
||||
### Get JSON with config descriptions
|
||||
|
||||
### Get JSON with config descriptions.
|
||||
Loads json generated from `config/descriptions.exs`.
|
||||
|
||||
- Params: none
|
||||
|
@ -1124,6 +1127,37 @@ Loads json generated from `config/descriptions.exs`.
|
|||
}]
|
||||
```
|
||||
|
||||
## `GET /api/v1/pleroma/admin/config/versions/rollback/:id`
|
||||
|
||||
### Rollback config changes for a given version
|
||||
|
||||
- Params:
|
||||
- `id` - version id for rollback
|
||||
- Response:
|
||||
- On success: `204`, empty response
|
||||
- On failure:
|
||||
- 400 Bad Request `"You must enable configurable_from_database in your config file."` or endpoint error
|
||||
- 404 Not found
|
||||
|
||||
## `GET /api/v1/pleroma/admin/config/versions`
|
||||
|
||||
### Get list of config versions
|
||||
|
||||
- Params: none
|
||||
- Response:
|
||||
|
||||
```json
|
||||
{
|
||||
"versions": [
|
||||
{
|
||||
"id": 1,
|
||||
"current": true,
|
||||
"inserted_at": "2020-04-21T15:11:46.000Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## `GET /api/v1/pleroma/admin/moderation_log`
|
||||
|
||||
### Get moderation log
|
||||
|
@ -1230,7 +1264,6 @@ Loads json generated from `config/descriptions.exs`.
|
|||
}
|
||||
```
|
||||
|
||||
|
||||
## `POST /api/v1/pleroma/admin/oauth_app`
|
||||
|
||||
### Create OAuth App
|
||||
|
@ -1257,6 +1290,7 @@ Loads json generated from `config/descriptions.exs`.
|
|||
```
|
||||
|
||||
- On failure:
|
||||
|
||||
```json
|
||||
{
|
||||
"redirect_uris": "can't be blank",
|
||||
|
@ -1269,11 +1303,11 @@ Loads json generated from `config/descriptions.exs`.
|
|||
### Update OAuth App
|
||||
|
||||
- Params:
|
||||
- *optional* `name`
|
||||
- *optional* `redirect_uris`
|
||||
- *optional* `scopes`
|
||||
- *optional* `website`
|
||||
- *optional* `trusted`
|
||||
- *optional* `name`
|
||||
- *optional* `redirect_uris`
|
||||
- *optional* `scopes`
|
||||
- *optional* `website`
|
||||
- *optional* `trusted`
|
||||
|
||||
- Response:
|
||||
|
||||
|
@ -1491,6 +1525,7 @@ Returns the content of the document
|
|||
```
|
||||
|
||||
## `PATCH /api/v1/pleroma/admin/instance_document/:document_name`
|
||||
|
||||
- Params:
|
||||
- `file` (the file to be uploaded, using multipart form data.)
|
||||
|
||||
|
|
40
docs/development/config_versioning.md
Normal file
40
docs/development/config_versioning.md
Normal file
|
@ -0,0 +1,40 @@
|
|||
# Config versioning
|
||||
|
||||
Database configuration supports simple versioning. Every change (list of changes or only one change) through adminFE creates new version with backup from config table. It is possible to do rollback on N steps (1 by default). Rollback will recreate `config` table from backup.
|
||||
|
||||
**IMPORTANT** Destructive operations with `Pleroma.ConfigDB` and `Pleroma.Config.Version` must be processed through `Pleroma.Config.Versioning` module for correct versioning work, especially migration changes.
|
||||
|
||||
Example:
|
||||
|
||||
* new config setting is added directly using `Pleroma.ConfigDB` module
|
||||
* user is doing rollback and setting is lost
|
||||
|
||||
## Creating new version
|
||||
|
||||
Creating new version is done with `Pleroma.Config.Versioning.new_version/1`, which accepts list of changes. Changes can include adding/updating/deleting operations in `config` table at the same time.
|
||||
|
||||
Process of creating new version:
|
||||
|
||||
* saving config changes in `config` table
|
||||
* saving new version with current configs
|
||||
* `backup` - keyword with all configs from `config` table (binary)
|
||||
* `current` - flag, which marks current version (boolean)
|
||||
|
||||
## Version rollback
|
||||
|
||||
Version control also supports a simple N steps back mechanism.
|
||||
|
||||
Rollback process:
|
||||
|
||||
* cleaning `config` table
|
||||
* splitting `backup` field into separate settings and inserting them into `config` table
|
||||
* removing subsequent versions
|
||||
|
||||
## Config migrations
|
||||
|
||||
Sometimes it becomes necessary to make changes to the configuration, which can be stored in the user's database. Config versioning makes this process more complicated, as we also must update this setting in versions backups.
|
||||
|
||||
Versioning module contains two functions for migrations:
|
||||
|
||||
* `Pleroma.Config.Versioning.migrate_namespace/2` - for simple renaming, e.g. group or key of the setting must be renamed.
|
||||
* `Pleroma.Config.Versioning.migrate_configs_and_versions/2` - abstract function for more complex migrations. Accepts two functions, the first one to make changes with configs, another to make changes with version backups.
|
|
@ -4,7 +4,6 @@
|
|||
|
||||
defmodule Mix.Pleroma do
|
||||
@apps [
|
||||
:restarter,
|
||||
:ecto,
|
||||
:ecto_sql,
|
||||
:postgrex,
|
||||
|
@ -16,11 +15,14 @@ defmodule Mix.Pleroma do
|
|||
:fast_html,
|
||||
:oban
|
||||
]
|
||||
|
||||
@cachex_children ["object", "user", "scrubber", "web_resp"]
|
||||
|
||||
@doc "Common functions to be reused in mix tasks"
|
||||
@spec start_pleroma() :: {:ok, pid()}
|
||||
def start_pleroma do
|
||||
Pleroma.Config.Holder.save_default()
|
||||
Pleroma.Config.Oban.warn()
|
||||
Pleroma.Config.DeprecationWarnings.check_oban_config()
|
||||
Pleroma.Application.limiters_setup()
|
||||
Application.put_env(:phoenix, :serve_endpoints, false, persistent: true)
|
||||
|
||||
|
@ -47,37 +49,27 @@ defmodule Mix.Pleroma do
|
|||
plugins: []
|
||||
]
|
||||
|
||||
children =
|
||||
[
|
||||
Pleroma.Repo,
|
||||
Pleroma.Emoji,
|
||||
{Pleroma.Config.TransferTask, false},
|
||||
Pleroma.Web.Endpoint,
|
||||
{Oban, oban_config},
|
||||
{Majic.Pool,
|
||||
[name: Pleroma.MajicPool, pool_size: Pleroma.Config.get([:majic_pool, :size], 2)]}
|
||||
] ++
|
||||
http_children(adapter)
|
||||
children = [
|
||||
Pleroma.Repo,
|
||||
Supervisor.child_spec({Task, &Pleroma.Application.Environment.load_from_db_and_update/0},
|
||||
id: :update_env
|
||||
),
|
||||
Pleroma.Web.Endpoint,
|
||||
Pleroma.Emoji,
|
||||
{Oban, oban_config},
|
||||
{Majic.Pool,
|
||||
[name: Pleroma.MajicPool, pool_size: Pleroma.Config.get([:majic_pool, :size], 2)]}
|
||||
]
|
||||
|
||||
cachex_children = Enum.map(@cachex_children, &Pleroma.Application.build_cachex(&1, []))
|
||||
children = [Pleroma.Application.StartUpDependencies.adapter_module() | children]
|
||||
|
||||
cachex_children =
|
||||
Enum.map(@cachex_children, &Pleroma.Application.StartUpDependencies.cachex_spec({&1, []}))
|
||||
|
||||
Supervisor.start_link(children ++ cachex_children,
|
||||
strategy: :one_for_one,
|
||||
name: Pleroma.Supervisor
|
||||
)
|
||||
|
||||
if Pleroma.Config.get(:env) not in [:test, :benchmark] do
|
||||
pleroma_rebooted?()
|
||||
end
|
||||
end
|
||||
|
||||
defp pleroma_rebooted? do
|
||||
if Restarter.Pleroma.rebooted?() do
|
||||
:ok
|
||||
else
|
||||
Process.sleep(10)
|
||||
pleroma_rebooted?()
|
||||
end
|
||||
end
|
||||
|
||||
def load_pleroma do
|
||||
|
@ -129,11 +121,4 @@ defmodule Mix.Pleroma do
|
|||
def escape_sh_path(path) do
|
||||
~S(') <> String.replace(path, ~S('), ~S(\')) <> ~S(')
|
||||
end
|
||||
|
||||
defp http_children(Tesla.Adapter.Gun) do
|
||||
Pleroma.Gun.ConnectionPool.children() ++
|
||||
[{Task, &Pleroma.HTTP.AdapterHelper.Gun.limiter_setup/0}]
|
||||
end
|
||||
|
||||
defp http_children(_), do: []
|
||||
end
|
||||
|
|
|
@ -14,10 +14,13 @@ defmodule Mix.Tasks.Pleroma.Config do
|
|||
@shortdoc "Manages the location of the config"
|
||||
@moduledoc File.read!("docs/administration/CLI_tasks/config.md")
|
||||
|
||||
def run(["migrate_to_db"]) do
|
||||
def run(["migrate_to_db" | options]) do
|
||||
check_configdb(fn ->
|
||||
start_pleroma()
|
||||
migrate_to_db()
|
||||
|
||||
{opts, _} = OptionParser.parse!(options, strict: [config: :string])
|
||||
|
||||
migrate_to_db(opts)
|
||||
end)
|
||||
end
|
||||
|
||||
|
@ -39,15 +42,13 @@ defmodule Mix.Tasks.Pleroma.Config do
|
|||
check_configdb(fn ->
|
||||
start_pleroma()
|
||||
|
||||
header = config_header()
|
||||
|
||||
settings =
|
||||
ConfigDB
|
||||
|> Repo.all()
|
||||
|> Enum.sort()
|
||||
|
||||
unless settings == [] do
|
||||
shell_info("#{header}")
|
||||
shell_info("#{Pleroma.Config.Loader.config_header()}")
|
||||
|
||||
Enum.each(settings, &dump(&1))
|
||||
else
|
||||
|
@ -73,9 +74,10 @@ defmodule Mix.Tasks.Pleroma.Config do
|
|||
check_configdb(fn ->
|
||||
start_pleroma()
|
||||
|
||||
group = maybe_atomize(group)
|
||||
|
||||
dump_group(group)
|
||||
group
|
||||
|> maybe_atomize()
|
||||
|> ConfigDB.get_all_by_group()
|
||||
|> Enum.each(&dump/1)
|
||||
end)
|
||||
end
|
||||
|
||||
|
@ -97,17 +99,11 @@ defmodule Mix.Tasks.Pleroma.Config do
|
|||
end)
|
||||
end
|
||||
|
||||
def run(["reset", "--force"]) do
|
||||
def run(["reset" | opts]) do
|
||||
check_configdb(fn ->
|
||||
start_pleroma()
|
||||
truncatedb()
|
||||
shell_info("The ConfigDB settings have been removed from the database.")
|
||||
end)
|
||||
end
|
||||
|
||||
def run(["reset"]) do
|
||||
check_configdb(fn ->
|
||||
start_pleroma()
|
||||
{opts, []} = OptionParser.parse!(opts, strict: [force: :boolean])
|
||||
|
||||
shell_info("The following settings will be permanently removed:")
|
||||
|
||||
|
@ -118,8 +114,8 @@ defmodule Mix.Tasks.Pleroma.Config do
|
|||
|
||||
shell_error("\nTHIS CANNOT BE UNDONE!")
|
||||
|
||||
if shell_prompt("Are you sure you want to continue?", "n") in ~w(Yn Y y) do
|
||||
truncatedb()
|
||||
if opts[:force] or shell_prompt("Are you sure you want to continue?", "n") in ~w(Yn Y y) do
|
||||
Pleroma.Config.Versioning.reset()
|
||||
|
||||
shell_info("The ConfigDB settings have been removed from the database.")
|
||||
else
|
||||
|
@ -128,55 +124,65 @@ defmodule Mix.Tasks.Pleroma.Config do
|
|||
end)
|
||||
end
|
||||
|
||||
def run(["delete", "--force", group, key]) do
|
||||
def run(["delete", group]), do: delete(group, force: false)
|
||||
def run(["delete", "--force", group]), do: delete(group, force: true)
|
||||
|
||||
def run(["delete", group, key]), do: delete(group, key, force: false)
|
||||
def run(["delete", "--force", group, key]), do: delete(group, key, force: true)
|
||||
|
||||
def run(["rollback" | options]) do
|
||||
check_configdb(fn ->
|
||||
start_pleroma()
|
||||
{opts, _} = OptionParser.parse!(options, strict: [steps: :integer], aliases: [s: :steps])
|
||||
|
||||
do_rollback(opts)
|
||||
end)
|
||||
end
|
||||
|
||||
defp delete(group, opts) do
|
||||
start_pleroma()
|
||||
|
||||
group = maybe_atomize(group)
|
||||
|
||||
configs = ConfigDB.get_all_by_group(group)
|
||||
|
||||
if configs != [] do
|
||||
shell_info("The following settings will be removed from ConfigDB:\n")
|
||||
Enum.each(configs, &dump/1)
|
||||
|
||||
if opts[:force] or shell_prompt("Are you sure you want to continue?", "n") in ~w(Yn Y y) do
|
||||
Enum.each(configs, fn config ->
|
||||
Pleroma.Config.Versioning.new_version(%{
|
||||
group: config.group,
|
||||
key: config.key,
|
||||
delete: true
|
||||
})
|
||||
end)
|
||||
else
|
||||
shell_error("No changes made.")
|
||||
end
|
||||
else
|
||||
shell_error("No settings in ConfigDB for #{inspect(group)}. Aborting.")
|
||||
end
|
||||
end
|
||||
|
||||
defp delete(group, key, opts) do
|
||||
start_pleroma()
|
||||
|
||||
group = maybe_atomize(group)
|
||||
key = maybe_atomize(key)
|
||||
|
||||
with true <- key_exists?(group, key) do
|
||||
with %ConfigDB{} = config <- ConfigDB.get_by_group_and_key(group, key) do
|
||||
shell_info("The following settings will be removed from ConfigDB:\n")
|
||||
|
||||
group
|
||||
|> ConfigDB.get_by_group_and_key(key)
|
||||
|> dump()
|
||||
dump(config)
|
||||
|
||||
delete_key(group, key)
|
||||
else
|
||||
_ ->
|
||||
shell_error("No settings in ConfigDB for #{inspect(group)}, #{inspect(key)}. Aborting.")
|
||||
end
|
||||
end
|
||||
|
||||
def run(["delete", "--force", group]) do
|
||||
start_pleroma()
|
||||
|
||||
group = maybe_atomize(group)
|
||||
|
||||
with true <- group_exists?(group) do
|
||||
shell_info("The following settings will be removed from ConfigDB:\n")
|
||||
dump_group(group)
|
||||
delete_group(group)
|
||||
else
|
||||
_ -> shell_error("No settings in ConfigDB for #{inspect(group)}. Aborting.")
|
||||
end
|
||||
end
|
||||
|
||||
def run(["delete", group, key]) do
|
||||
start_pleroma()
|
||||
|
||||
group = maybe_atomize(group)
|
||||
key = maybe_atomize(key)
|
||||
|
||||
with true <- key_exists?(group, key) do
|
||||
shell_info("The following settings will be removed from ConfigDB:\n")
|
||||
|
||||
group
|
||||
|> ConfigDB.get_by_group_and_key(key)
|
||||
|> dump()
|
||||
|
||||
if shell_prompt("Are you sure you want to continue?", "n") in ~w(Yn Y y) do
|
||||
delete_key(group, key)
|
||||
if opts[:force] or shell_prompt("Are you sure you want to continue?", "n") in ~w(Yn Y y) do
|
||||
Pleroma.Config.Versioning.new_version(%{
|
||||
group: config.group,
|
||||
key: config.key,
|
||||
delete: true
|
||||
})
|
||||
else
|
||||
shell_error("No changes made.")
|
||||
end
|
||||
|
@ -186,40 +192,36 @@ defmodule Mix.Tasks.Pleroma.Config do
|
|||
end
|
||||
end
|
||||
|
||||
def run(["delete", group]) do
|
||||
start_pleroma()
|
||||
defp do_rollback(opts) do
|
||||
steps = opts[:steps] || 1
|
||||
|
||||
group = maybe_atomize(group)
|
||||
case Pleroma.Config.Versioning.rollback(steps) do
|
||||
{:ok, _} ->
|
||||
shell_info("Success rollback")
|
||||
|
||||
with true <- group_exists?(group) do
|
||||
shell_info("The following settings will be removed from ConfigDB:\n")
|
||||
dump_group(group)
|
||||
{:error, :no_current_version} ->
|
||||
shell_error("No version to rollback")
|
||||
|
||||
if shell_prompt("Are you sure you want to continue?", "n") in ~w(Yn Y y) do
|
||||
delete_group(group)
|
||||
else
|
||||
shell_error("No changes made.")
|
||||
end
|
||||
else
|
||||
_ -> shell_error("No settings in ConfigDB for #{inspect(group)}. Aborting.")
|
||||
{:error, :rollback_not_possible} ->
|
||||
shell_error("Rollback not possible. Incorrect steps value.")
|
||||
|
||||
{:error, _, _, _} ->
|
||||
shell_error("Problem with backup. Rollback not possible.")
|
||||
|
||||
error ->
|
||||
shell_error("error occuried: #{inspect(error)}")
|
||||
end
|
||||
end
|
||||
|
||||
@spec migrate_to_db(Path.t() | nil) :: any()
|
||||
def migrate_to_db(file_path \\ nil) do
|
||||
defp migrate_to_db(opts) do
|
||||
with :ok <- Pleroma.Config.DeprecationWarnings.warn() do
|
||||
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 = opts[:config] || Pleroma.Application.config_path()
|
||||
|
||||
do_migrate_to_db(config_file)
|
||||
if File.exists?(config_file) do
|
||||
do_migrate_to_db(config_file)
|
||||
else
|
||||
shell_info("To migrate settings, you must define custom settings in #{config_file}.")
|
||||
end
|
||||
else
|
||||
_ ->
|
||||
shell_error("Migration is not allowed until all deprecation warnings have been resolved.")
|
||||
|
@ -227,33 +229,9 @@ defmodule Mix.Tasks.Pleroma.Config do
|
|||
end
|
||||
|
||||
defp do_migrate_to_db(config_file) do
|
||||
if File.exists?(config_file) do
|
||||
shell_info("Migrating settings from file: #{Path.expand(config_file)}")
|
||||
truncatedb()
|
||||
|
||||
custom_config =
|
||||
config_file
|
||||
|> read_file()
|
||||
|> elem(0)
|
||||
|
||||
custom_config
|
||||
|> Keyword.keys()
|
||||
|> 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
|
||||
group
|
||||
|> Pleroma.Config.Loader.filter_group(settings)
|
||||
|> Enum.each(fn {key, value} ->
|
||||
{:ok, _} = ConfigDB.update_or_create(%{group: group, key: key, value: value})
|
||||
|
||||
shell_info("Settings for key #{key} migrated.")
|
||||
end)
|
||||
|
||||
shell_info("Settings for group #{inspect(group)} migrated.")
|
||||
shell_info("Migrating settings from file: #{Path.expand(config_file)}")
|
||||
{:ok, _} = Pleroma.Config.Versioning.migrate(config_file)
|
||||
shell_info("Settings migrated.")
|
||||
end
|
||||
|
||||
defp migrate_from_db(opts) do
|
||||
|
@ -296,48 +274,47 @@ defmodule Mix.Tasks.Pleroma.Config do
|
|||
end
|
||||
|
||||
defp write_config(file, path, opts) do
|
||||
IO.write(file, config_header())
|
||||
IO.write(file, Pleroma.Config.Loader.config_header())
|
||||
|
||||
ConfigDB
|
||||
|> Repo.all()
|
||||
|> Enum.each(&write_and_delete(&1, file, opts[:delete]))
|
||||
changes =
|
||||
ConfigDB
|
||||
|> Repo.all()
|
||||
|> Enum.reduce([], fn %{group: group} = config, acc ->
|
||||
group_str = inspect(group)
|
||||
value = inspect(config.value, limit: :infinity)
|
||||
|
||||
msg =
|
||||
if group in ConfigDB.groups_without_keys() do
|
||||
IO.write(file, "config #{group_str}, #{value}\r\n\r\n")
|
||||
"config #{group_str} was deleted."
|
||||
else
|
||||
key_str = inspect(config.key)
|
||||
IO.write(file, "config #{group_str}, #{key_str}, #{value}\r\n\r\n")
|
||||
"config #{group_str}, #{key_str} was deleted."
|
||||
end
|
||||
|
||||
if opts[:delete] do
|
||||
shell_info(msg)
|
||||
|
||||
change =
|
||||
config
|
||||
|> Map.take([:group, :key])
|
||||
|> Map.put(:delete, true)
|
||||
|
||||
[change | acc]
|
||||
else
|
||||
acc
|
||||
end
|
||||
end)
|
||||
|
||||
if Keyword.get(opts, :delete, false) and changes != [] do
|
||||
Pleroma.Config.Versioning.new_version(changes)
|
||||
end
|
||||
|
||||
:ok = File.close(file)
|
||||
System.cmd("mix", ["format", path])
|
||||
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 = inspect(config.value, limit: :infinity)
|
||||
|
||||
IO.write(file, "config #{inspect(config.group)}, #{inspect(config.key)}, #{value}\r\n\r\n")
|
||||
|
||||
config
|
||||
end
|
||||
|
||||
defp delete(config, true) do
|
||||
{:ok, _} = Repo.delete(config)
|
||||
|
||||
shell_info(
|
||||
"config #{inspect(config.group)}, #{inspect(config.key)} was deleted from the ConfigDB."
|
||||
)
|
||||
end
|
||||
|
||||
defp delete(_config, _), do: :ok
|
||||
|
||||
defp dump(%ConfigDB{} = config) do
|
||||
value = inspect(config.value, limit: :infinity)
|
||||
|
||||
|
@ -346,31 +323,12 @@ defmodule Mix.Tasks.Pleroma.Config do
|
|||
|
||||
defp dump(_), do: :noop
|
||||
|
||||
defp dump_group(group) when is_atom(group) do
|
||||
group
|
||||
|> ConfigDB.get_all_by_group()
|
||||
|> Enum.each(&dump/1)
|
||||
end
|
||||
|
||||
defp group_exists?(group) do
|
||||
group
|
||||
|> ConfigDB.get_all_by_group()
|
||||
|> Enum.any?()
|
||||
end
|
||||
|
||||
defp key_exists?(group, key) do
|
||||
group
|
||||
|> ConfigDB.get_by_group_and_key(key)
|
||||
|> is_nil
|
||||
|> Kernel.!()
|
||||
end
|
||||
|
||||
defp maybe_atomize(arg) when is_atom(arg), do: arg
|
||||
|
||||
defp maybe_atomize(":" <> arg), do: maybe_atomize(arg)
|
||||
|
||||
defp maybe_atomize(arg) when is_binary(arg) do
|
||||
if ConfigDB.module_name?(arg) do
|
||||
if Pleroma.Config.Converter.module_name?(arg) do
|
||||
String.to_existing_atom("Elixir." <> arg)
|
||||
else
|
||||
String.to_atom(arg)
|
||||
|
@ -387,23 +345,4 @@ defmodule Mix.Tasks.Pleroma.Config do
|
|||
)
|
||||
end
|
||||
end
|
||||
|
||||
defp delete_key(group, key) do
|
||||
check_configdb(fn ->
|
||||
ConfigDB.delete(%{group: group, key: key})
|
||||
end)
|
||||
end
|
||||
|
||||
defp delete_group(group) do
|
||||
check_configdb(fn ->
|
||||
group
|
||||
|> ConfigDB.get_all_by_group()
|
||||
|> Enum.each(&ConfigDB.delete/1)
|
||||
end)
|
||||
end
|
||||
|
||||
defp truncatedb do
|
||||
Ecto.Adapters.SQL.query!(Repo, "TRUNCATE config;")
|
||||
Ecto.Adapters.SQL.query!(Repo, "ALTER SEQUENCE config_id_seq RESTART;")
|
||||
end
|
||||
end
|
||||
|
|
|
@ -32,7 +32,7 @@ defmodule Mix.Tasks.Pleroma.Docs do
|
|||
defp do_run(implementation) do
|
||||
start_pleroma()
|
||||
|
||||
with descriptions <- Pleroma.Config.Loader.read("config/description.exs"),
|
||||
with descriptions <- Pleroma.Config.Loader.read!("config/description.exs"),
|
||||
{:ok, file_path} <-
|
||||
Pleroma.Docs.Generator.process(
|
||||
implementation,
|
||||
|
|
|
@ -5,8 +5,6 @@
|
|||
defmodule Pleroma.Application do
|
||||
use Application
|
||||
|
||||
import Cachex.Spec
|
||||
|
||||
alias Pleroma.Config
|
||||
|
||||
require Logger
|
||||
|
@ -15,12 +13,17 @@ defmodule Pleroma.Application do
|
|||
@version Mix.Project.config()[:version]
|
||||
@repository Mix.Project.config()[:source_url]
|
||||
@mix_env Mix.env()
|
||||
@dynamic_supervisor Pleroma.Application.Supervisor
|
||||
|
||||
@type env() :: :test | :benchmark | :dev | :prod
|
||||
|
||||
def name, do: @name
|
||||
def version, do: @version
|
||||
def named_version, do: @name <> " " <> @version
|
||||
def repository, do: @repository
|
||||
def dynamic_supervisor, do: @dynamic_supervisor
|
||||
|
||||
@spec user_agent() :: String.t()
|
||||
def user_agent do
|
||||
if Process.whereis(Pleroma.Web.Endpoint) do
|
||||
case Config.get([:http, :user_agent], :default) do
|
||||
|
@ -37,9 +40,43 @@ defmodule Pleroma.Application do
|
|||
end
|
||||
end
|
||||
|
||||
# See http://elixir-lang.org/docs/stable/elixir/Application.html
|
||||
# for more information on OTP Applications
|
||||
@spec config_path() :: Path.t()
|
||||
def config_path do
|
||||
if Config.get(:release) do
|
||||
Config.get(:config_path)
|
||||
else
|
||||
Config.get(:config_path_in_test) || "config/#{@mix_env}.secret.exs"
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Under main supervisor is started DynamicSupervisor, which later starts pleroma startup dependencies.
|
||||
Pleroma start is splitted into three `phases`:
|
||||
- running prestart requirements (runtime compilation, warnings, deprecations, monitoring, etc.)
|
||||
- loading and updating environment (if database config is used and enabled)
|
||||
- starting dependencies
|
||||
"""
|
||||
@impl true
|
||||
def start(_type, _args) do
|
||||
children = [
|
||||
{DynamicSupervisor, strategy: :one_for_one, name: @dynamic_supervisor},
|
||||
{Pleroma.Application.ConfigDependentDeps, [dynamic_supervisor: @dynamic_supervisor]},
|
||||
Pleroma.Repo
|
||||
]
|
||||
|
||||
{:ok, main_supervisor} =
|
||||
Supervisor.start_link(children, strategy: :one_for_one, name: Pleroma.Supervisor)
|
||||
|
||||
run_prestart_requirements()
|
||||
|
||||
Pleroma.Application.Environment.load_from_db_and_update(pleroma_start: true)
|
||||
|
||||
Pleroma.Application.StartUpDependencies.start_all(@mix_env)
|
||||
|
||||
{:ok, main_supervisor}
|
||||
end
|
||||
|
||||
defp run_prestart_requirements do
|
||||
# Scrubbers are compiled at runtime and therefore will cause a conflict
|
||||
# every time the application is restarted, so we disable module
|
||||
# conflicts at runtime
|
||||
|
@ -47,72 +84,26 @@ defmodule Pleroma.Application do
|
|||
# Disable warnings_as_errors at runtime, it breaks Phoenix live reload
|
||||
# due to protocol consolidation warnings
|
||||
Code.compiler_options(warnings_as_errors: false)
|
||||
Pleroma.Telemetry.Logger.attach()
|
||||
Config.Holder.save_default()
|
||||
|
||||
# compilation in runtime
|
||||
Pleroma.HTML.compile_scrubbers()
|
||||
Pleroma.Config.Oban.warn()
|
||||
compile_custom_modules()
|
||||
Pleroma.Docs.JSON.compile()
|
||||
|
||||
# telemetry and prometheus
|
||||
Pleroma.Telemetry.Logger.attach()
|
||||
setup_instrumenters()
|
||||
|
||||
Config.Holder.save_default()
|
||||
|
||||
Config.DeprecationWarnings.warn()
|
||||
Pleroma.Web.Plugs.HTTPSecurityPlug.warn_if_disabled()
|
||||
Pleroma.ApplicationRequirements.verify!()
|
||||
setup_instrumenters()
|
||||
load_custom_modules()
|
||||
Pleroma.Docs.JSON.compile()
|
||||
|
||||
limiters_setup()
|
||||
|
||||
adapter = Application.get_env(:tesla, :adapter)
|
||||
|
||||
if adapter == Tesla.Adapter.Gun do
|
||||
if version = Pleroma.OTPVersion.version() do
|
||||
[major, minor] =
|
||||
version
|
||||
|> String.split(".")
|
||||
|> Enum.map(&String.to_integer/1)
|
||||
|> Enum.take(2)
|
||||
|
||||
if (major == 22 and minor < 2) or major < 22 do
|
||||
raise "
|
||||
!!!OTP VERSION WARNING!!!
|
||||
You are using gun adapter with OTP version #{version}, which doesn't support correct handling of unordered certificates chains. Please update your Erlang/OTP to at least 22.2.
|
||||
"
|
||||
end
|
||||
else
|
||||
raise "
|
||||
!!!OTP VERSION WARNING!!!
|
||||
To support correct handling of unordered certificates chains - OTP version must be > 22.2.
|
||||
"
|
||||
end
|
||||
end
|
||||
|
||||
# Define workers and child supervisors to be supervised
|
||||
children =
|
||||
[
|
||||
Pleroma.Repo,
|
||||
Config.TransferTask,
|
||||
Pleroma.Emoji,
|
||||
Pleroma.Web.Plugs.RateLimiter.Supervisor
|
||||
] ++
|
||||
cachex_children() ++
|
||||
http_children(adapter, @mix_env) ++
|
||||
[
|
||||
Pleroma.Stats,
|
||||
Pleroma.JobQueueMonitor,
|
||||
{Majic.Pool, [name: Pleroma.MajicPool, pool_size: Config.get([:majic_pool, :size], 2)]},
|
||||
{Oban, Config.get(Oban)},
|
||||
Pleroma.Web.Endpoint
|
||||
] ++
|
||||
task_children(@mix_env) ++
|
||||
dont_run_in_test(@mix_env) ++
|
||||
chat_child(chat_enabled?()) ++
|
||||
[Pleroma.Gopher.Server]
|
||||
|
||||
# See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
|
||||
# for other strategies and supported options
|
||||
opts = [strategy: :one_for_one, name: Pleroma.Supervisor]
|
||||
result = Supervisor.start_link(children, opts)
|
||||
|
||||
set_postgres_server_version()
|
||||
|
||||
result
|
||||
Pleroma.Application.Requirements.verify!()
|
||||
end
|
||||
|
||||
defp set_postgres_server_version do
|
||||
|
@ -132,7 +123,7 @@ defmodule Pleroma.Application do
|
|||
:persistent_term.put({Pleroma.Repo, :postgres_version}, version)
|
||||
end
|
||||
|
||||
def load_custom_modules do
|
||||
defp compile_custom_modules do
|
||||
dir = Config.get([:modules, :runtime_dir])
|
||||
|
||||
if dir && File.exists?(dir) do
|
||||
|
@ -177,128 +168,6 @@ defmodule Pleroma.Application do
|
|||
PrometheusPhx.setup()
|
||||
end
|
||||
|
||||
defp cachex_children do
|
||||
[
|
||||
build_cachex("used_captcha", ttl_interval: seconds_valid_interval()),
|
||||
build_cachex("user", default_ttl: 25_000, ttl_interval: 1000, limit: 2500),
|
||||
build_cachex("object", default_ttl: 25_000, ttl_interval: 1000, limit: 2500),
|
||||
build_cachex("rich_media", default_ttl: :timer.minutes(120), limit: 5000),
|
||||
build_cachex("scrubber", limit: 2500),
|
||||
build_cachex("idempotency", expiration: idempotency_expiration(), limit: 2500),
|
||||
build_cachex("web_resp", limit: 2500),
|
||||
build_cachex("emoji_packs", expiration: emoji_packs_expiration(), limit: 10),
|
||||
build_cachex("failed_proxy_url", limit: 2500),
|
||||
build_cachex("banned_urls", default_ttl: :timer.hours(24 * 30), limit: 5_000),
|
||||
build_cachex("chat_message_id_idempotency_key",
|
||||
expiration: chat_message_id_idempotency_key_expiration(),
|
||||
limit: 500_000
|
||||
)
|
||||
]
|
||||
end
|
||||
|
||||
defp emoji_packs_expiration,
|
||||
do: expiration(default: :timer.seconds(5 * 60), interval: :timer.seconds(60))
|
||||
|
||||
defp idempotency_expiration,
|
||||
do: expiration(default: :timer.seconds(6 * 60 * 60), interval: :timer.seconds(60))
|
||||
|
||||
defp chat_message_id_idempotency_key_expiration,
|
||||
do: expiration(default: :timer.minutes(2), interval: :timer.seconds(60))
|
||||
|
||||
defp seconds_valid_interval,
|
||||
do: :timer.seconds(Config.get!([Pleroma.Captcha, :seconds_valid]))
|
||||
|
||||
@spec build_cachex(String.t(), keyword()) :: map()
|
||||
def build_cachex(type, opts),
|
||||
do: %{
|
||||
id: String.to_atom("cachex_" <> type),
|
||||
start: {Cachex, :start_link, [String.to_atom(type <> "_cache"), opts]},
|
||||
type: :worker
|
||||
}
|
||||
|
||||
defp chat_enabled?, do: Config.get([:chat, :enabled])
|
||||
|
||||
defp dont_run_in_test(env) when env in [:test, :benchmark], do: []
|
||||
|
||||
defp dont_run_in_test(_) do
|
||||
[
|
||||
{Registry,
|
||||
[
|
||||
name: Pleroma.Web.Streamer.registry(),
|
||||
keys: :duplicate,
|
||||
partitions: System.schedulers_online()
|
||||
]}
|
||||
] ++ background_migrators()
|
||||
end
|
||||
|
||||
defp background_migrators do
|
||||
[
|
||||
Pleroma.Migrators.HashtagsTableMigrator
|
||||
]
|
||||
end
|
||||
|
||||
defp chat_child(true) do
|
||||
[
|
||||
Pleroma.Web.ChatChannel.ChatChannelState,
|
||||
{Phoenix.PubSub, [name: Pleroma.PubSub, adapter: Phoenix.PubSub.PG2]}
|
||||
]
|
||||
end
|
||||
|
||||
defp chat_child(_), do: []
|
||||
|
||||
defp task_children(:test) do
|
||||
[
|
||||
%{
|
||||
id: :web_push_init,
|
||||
start: {Task, :start_link, [&Pleroma.Web.Push.init/0]},
|
||||
restart: :temporary
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
defp task_children(_) do
|
||||
[
|
||||
%{
|
||||
id: :web_push_init,
|
||||
start: {Task, :start_link, [&Pleroma.Web.Push.init/0]},
|
||||
restart: :temporary
|
||||
},
|
||||
%{
|
||||
id: :internal_fetch_init,
|
||||
start: {Task, :start_link, [&Pleroma.Web.ActivityPub.InternalFetchActor.init/0]},
|
||||
restart: :temporary
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
# start hackney and gun pools in tests
|
||||
defp http_children(_, :test) do
|
||||
http_children(Tesla.Adapter.Hackney, nil) ++ http_children(Tesla.Adapter.Gun, nil)
|
||||
end
|
||||
|
||||
defp http_children(Tesla.Adapter.Hackney, _) do
|
||||
pools = [:federation, :media]
|
||||
|
||||
pools =
|
||||
if Config.get([Pleroma.Upload, :proxy_remote]) do
|
||||
[:upload | pools]
|
||||
else
|
||||
pools
|
||||
end
|
||||
|
||||
for pool <- pools do
|
||||
options = Config.get([:hackney_pools, pool])
|
||||
:hackney_pool.child_spec(pool, options)
|
||||
end
|
||||
end
|
||||
|
||||
defp http_children(Tesla.Adapter.Gun, _) do
|
||||
Pleroma.Gun.ConnectionPool.children() ++
|
||||
[{Task, &Pleroma.HTTP.AdapterHelper.Gun.limiter_setup/0}]
|
||||
end
|
||||
|
||||
defp http_children(_, _), do: []
|
||||
|
||||
@spec limiters_setup() :: :ok
|
||||
def limiters_setup do
|
||||
config = Config.get(ConcurrentLimiter, [])
|
||||
|
|
19
lib/pleroma/application/chat_supervisor.ex
Normal file
19
lib/pleroma/application/chat_supervisor.ex
Normal file
|
@ -0,0 +1,19 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Application.ChatSupervisor do
|
||||
use Supervisor
|
||||
|
||||
def start_link(_) do
|
||||
Supervisor.start_link(__MODULE__, :no_args)
|
||||
end
|
||||
|
||||
def init(_) do
|
||||
[
|
||||
Pleroma.Web.ChatChannel.ChatChannelState,
|
||||
{Phoenix.PubSub, [name: Pleroma.PubSub, adapter: Phoenix.PubSub.PG2]}
|
||||
]
|
||||
|> Supervisor.init(strategy: :one_for_one)
|
||||
end
|
||||
end
|
244
lib/pleroma/application/config_dependent_deps.ex
Normal file
244
lib/pleroma/application/config_dependent_deps.ex
Normal file
|
@ -0,0 +1,244 @@
|
|||
# # Pleroma: A lightweight social networking server
|
||||
# # Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||
# # SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Application.ConfigDependentDeps do
|
||||
use GenServer
|
||||
|
||||
require Logger
|
||||
|
||||
@config_path_mods_relation [
|
||||
{{:pleroma, :chat}, Pleroma.Application.ChatSupervisor},
|
||||
{{:pleroma, Oban}, Oban},
|
||||
{{:pleroma, :rate_limit}, Pleroma.Web.Plugs.RateLimiter.Supervisor},
|
||||
{{:pleroma, :streamer}, Pleroma.Web.Streamer.registry()},
|
||||
{{:pleroma, :pools}, Pleroma.Gun.GunSupervisor},
|
||||
{{:pleroma, :connections_pool}, Pleroma.Gun.GunSupervisor},
|
||||
{{:pleroma, :hackney_pools}, Pleroma.HTTP.HackneySupervisor},
|
||||
{{:pleroma, :gopher}, Pleroma.Gopher.Server},
|
||||
{{:pleroma, Pleroma.Captcha, [:seconds_valid]}, Pleroma.Web.Endpoint},
|
||||
{{:pleroma, Pleroma.Upload, [:proxy_remote]},
|
||||
Pleroma.Application.StartUpDependencies.adapter_module()},
|
||||
{{:pleroma, :instance, [:upload_limit]}, Pleroma.Web.Endpoint},
|
||||
{{:pleroma, :fed_sockets, [:enabled]}, Pleroma.Web.Endpoint},
|
||||
{:eshhd, :eshhd},
|
||||
{:ex_aws, :ex_aws}
|
||||
]
|
||||
|
||||
def start_link(opts) do
|
||||
opts = Keyword.put_new(opts, :relations, @config_path_mods_relation)
|
||||
|
||||
GenServer.start_link(__MODULE__, opts, name: opts[:name] || __MODULE__)
|
||||
end
|
||||
|
||||
@impl true
|
||||
def init(opts) do
|
||||
init_state = %{
|
||||
dynamic_supervisor: opts[:dynamic_supervisor],
|
||||
relations: opts[:relations],
|
||||
reboot_paths: [],
|
||||
pids: %{}
|
||||
}
|
||||
|
||||
{:ok, init_state}
|
||||
end
|
||||
|
||||
def start_dependency(module, server \\ __MODULE__) do
|
||||
GenServer.call(server, {:start_dependency, module})
|
||||
end
|
||||
|
||||
def need_reboot?(server \\ __MODULE__) do
|
||||
GenServer.call(server, :need_reboot?)
|
||||
end
|
||||
|
||||
def restart_dependencies(server \\ __MODULE__) do
|
||||
GenServer.call(server, :restart_dependencies)
|
||||
end
|
||||
|
||||
def clear_state(server \\ __MODULE__) do
|
||||
GenServer.call(server, :clear_state)
|
||||
end
|
||||
|
||||
def save_config_paths_for_restart(changes, server \\ __MODULE__) do
|
||||
GenServer.call(server, {:save_config_paths, changes})
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call({:start_dependency, module}, _, state) do
|
||||
{result, state} =
|
||||
with {pid, state} when is_pid(pid) <- start_module(module, state) do
|
||||
{{:ok, pid}, state}
|
||||
else
|
||||
error -> {error, state}
|
||||
end
|
||||
|
||||
{:reply, result, state}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call(:need_reboot?, _, state) do
|
||||
{:reply, state[:reboot_paths] != [], state}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call(:restart_dependencies, _, state) do
|
||||
{paths, state} = Map.get_and_update(state, :reboot_paths, &{&1, []})
|
||||
started_apps = Application.started_applications()
|
||||
|
||||
{result, state} =
|
||||
Enum.reduce_while(paths, {:ok, state}, fn
|
||||
path, {:ok, acc} when is_tuple(path) ->
|
||||
case restart(path, acc, acc[:pids][path], with_terminate: true) do
|
||||
{pid, state} when is_pid(pid) ->
|
||||
{:cont, {:ok, state}}
|
||||
|
||||
:ignore ->
|
||||
Logger.info("path #{inspect(path)} is ignored.")
|
||||
{:cont, {:ok, acc}}
|
||||
|
||||
error ->
|
||||
{:halt, {error, acc}}
|
||||
end
|
||||
|
||||
app, {:ok, acc}
|
||||
when is_atom(app) and app not in [:logger, :quack, :pleroma, :prometheus, :postgrex] ->
|
||||
restart_app(app, started_apps)
|
||||
{:cont, {:ok, acc}}
|
||||
end)
|
||||
|
||||
{:reply, result, state}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call(:clear_state, _, state) do
|
||||
state =
|
||||
state
|
||||
|> Map.put(:reboot_paths, [])
|
||||
|> Map.put(:pids, %{})
|
||||
|
||||
{:reply, :ok, state}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call({:save_config_paths, changes}, _, state) do
|
||||
paths =
|
||||
Enum.reduce(changes, state[:reboot_paths], fn
|
||||
%{group: group, key: key, value: value}, acc ->
|
||||
with {path, _} <- find_relation(state[:relations], group, key, value) do
|
||||
if path not in acc do
|
||||
[path | acc]
|
||||
else
|
||||
acc
|
||||
end
|
||||
else
|
||||
_ ->
|
||||
acc
|
||||
end
|
||||
end)
|
||||
|
||||
{:reply, paths, put_in(state[:reboot_paths], paths)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_info({:DOWN, _ref, :process, pid, _reason}, state) do
|
||||
updated_state =
|
||||
with {path, ^pid} <-
|
||||
Enum.find(state[:pids], fn {_, registered_pid} -> registered_pid == pid end) do
|
||||
{_new_pid, new_state} = restart(path, state, pid)
|
||||
new_state
|
||||
else
|
||||
_ -> state
|
||||
end
|
||||
|
||||
{:noreply, updated_state}
|
||||
end
|
||||
|
||||
defp start_module(module, state) do
|
||||
with {:ok, relations} <- find_relations(state[:relations], module) do
|
||||
start_module(module, relations, state)
|
||||
end
|
||||
end
|
||||
|
||||
defp start_module(module, relations, state) do
|
||||
spec =
|
||||
module
|
||||
|> Pleroma.Application.StartUpDependencies.spec()
|
||||
|> Supervisor.child_spec(restart: :temporary)
|
||||
|
||||
with {:ok, pid} <-
|
||||
DynamicSupervisor.start_child(
|
||||
state[:dynamic_supervisor],
|
||||
spec
|
||||
) do
|
||||
pids = Map.new(relations, fn {path, _} -> {path, pid} end)
|
||||
Process.monitor(pid)
|
||||
{pid, put_in(state[:pids], Map.merge(state[:pids], pids))}
|
||||
end
|
||||
end
|
||||
|
||||
defp restart(path, state, pid, opts \\ [])
|
||||
|
||||
defp restart(path, state, nil, _) do
|
||||
with {_, module} <- find_relation(state[:relations], path) do
|
||||
start_module(module, state)
|
||||
end
|
||||
end
|
||||
|
||||
defp restart(path, state, pid, opts) when is_pid(pid) do
|
||||
with {_, module} <- find_relation(state[:relations], path),
|
||||
{:ok, relations} <- find_relations(state[:relations], module) do
|
||||
if opts[:with_terminate] do
|
||||
:ok = DynamicSupervisor.terminate_child(state[:dynamic_supervisor], pid)
|
||||
end
|
||||
|
||||
paths_for_remove = Enum.map(relations, fn {path, _} -> path end)
|
||||
state = put_in(state[:pids], Map.drop(state[:pids], paths_for_remove))
|
||||
|
||||
start_module(module, relations, state)
|
||||
end
|
||||
end
|
||||
|
||||
defp restart_app(app, started_applications) do
|
||||
with {^app, _, _} <- List.keyfind(started_applications, app, 0) do
|
||||
:ok = Application.stop(app)
|
||||
:ok = Application.start(app)
|
||||
else
|
||||
nil ->
|
||||
Logger.info("#{app} is not started.")
|
||||
|
||||
error ->
|
||||
error
|
||||
|> inspect()
|
||||
|> Logger.error()
|
||||
end
|
||||
end
|
||||
|
||||
defp find_relations(relations, module) do
|
||||
case Enum.filter(relations, fn {_, mod} -> mod == module end) do
|
||||
[] ->
|
||||
{:error, :relations_not_found}
|
||||
|
||||
relations ->
|
||||
{:ok, relations}
|
||||
end
|
||||
end
|
||||
|
||||
defp find_relation(relations, group, key, value) do
|
||||
Enum.find(relations, fn
|
||||
{g, _} when is_atom(g) ->
|
||||
g == group
|
||||
|
||||
{{g, k}, _} ->
|
||||
g == group and k == key
|
||||
|
||||
{{g, k, subkeys}, _} ->
|
||||
g == group and k == key and Enum.any?(Keyword.keys(value), &(&1 in subkeys))
|
||||
end)
|
||||
end
|
||||
|
||||
def find_relation(relations, path) do
|
||||
with nil <- Enum.find(relations, fn {key, _} -> key == path end) do
|
||||
{:error, :relation_not_found}
|
||||
end
|
||||
end
|
||||
end
|
103
lib/pleroma/application/environment.ex
Normal file
103
lib/pleroma/application/environment.ex
Normal file
|
@ -0,0 +1,103 @@
|
|||
# # Pleroma: A lightweight social networking server
|
||||
# # Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||
# # SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Application.Environment do
|
||||
@moduledoc """
|
||||
Overwrites environment config with settings from config file or database.
|
||||
"""
|
||||
|
||||
require Logger
|
||||
|
||||
@doc """
|
||||
Method is called on pleroma start.
|
||||
Config dependent parts don't require restart, because are not started yet.
|
||||
But started apps need restart.
|
||||
"""
|
||||
@spec load_from_db_and_update(keyword()) :: :ok
|
||||
def load_from_db_and_update(opts \\ []) do
|
||||
Pleroma.ConfigDB.all()
|
||||
|> update(opts)
|
||||
end
|
||||
|
||||
@spec update([Pleroma.ConfigDB.t()], keyword()) :: :ok
|
||||
def update(changes, opts \\ []) when is_list(changes) do
|
||||
if Pleroma.Config.get(:configurable_from_database) do
|
||||
defaults = Pleroma.Config.Holder.default_config()
|
||||
|
||||
changes
|
||||
|> filter_logger()
|
||||
|> prepare_logger_changes(defaults)
|
||||
|> Enum.each(&configure_logger/1)
|
||||
|
||||
changes
|
||||
|> Pleroma.ConfigDB.merge_changes_with_defaults(defaults)
|
||||
|> Enum.each(&update_env(&1))
|
||||
|
||||
cond do
|
||||
opts[:pleroma_start] ->
|
||||
# restart only apps on pleroma start
|
||||
changes
|
||||
|> Enum.filter(fn %{group: group} ->
|
||||
group not in [:logger, :quack, :pleroma, :prometheus, :postgrex]
|
||||
end)
|
||||
|> Pleroma.Application.ConfigDependentDeps.save_config_paths_for_restart()
|
||||
|
||||
Pleroma.Application.ConfigDependentDeps.restart_dependencies()
|
||||
|
||||
opts[:only_update] ->
|
||||
Pleroma.Application.ConfigDependentDeps.save_config_paths_for_restart(changes)
|
||||
|
||||
true ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
defp filter_logger(changes) do
|
||||
Enum.filter(changes, fn %{group: group} -> group in [:logger, :quack] end)
|
||||
end
|
||||
|
||||
defp prepare_logger_changes(changes, defaults) do
|
||||
Enum.map(changes, fn %{group: group} = change ->
|
||||
{change, Pleroma.ConfigDB.merge_change_value_with_default(change, defaults[group])}
|
||||
end)
|
||||
end
|
||||
|
||||
defp configure_logger({%{group: :quack}, merged_value}) do
|
||||
Logger.configure_backend(Quack.Logger, merged_value)
|
||||
end
|
||||
|
||||
defp configure_logger({%{group: :logger} = change, merged_value}) do
|
||||
if change.value[:backends] do
|
||||
Enum.each(Application.get_env(:logger, :backends), &Logger.remove_backend/1)
|
||||
|
||||
Enum.each(merged_value[:backends], &Logger.add_backend/1)
|
||||
end
|
||||
|
||||
if change.value[:console] do
|
||||
console = merged_value[:console]
|
||||
console = put_in(console[:format], console[:format] <> "\n")
|
||||
|
||||
Logger.configure_backend(:console, console)
|
||||
end
|
||||
|
||||
if change.value[:ex_syslogger] do
|
||||
Logger.configure_backend({ExSyslogger, :ex_syslogger}, merged_value[:ex_syslogger])
|
||||
end
|
||||
|
||||
Logger.configure(merged_value)
|
||||
end
|
||||
|
||||
defp update_env(%{group: group, key: key, value: nil}), do: Application.delete_env(group, key)
|
||||
|
||||
defp update_env(%{group: group, value: config} = change) do
|
||||
if group in Pleroma.ConfigDB.groups_without_keys() do
|
||||
Application.put_all_env([{group, config}])
|
||||
else
|
||||
Application.put_env(group, change.key, config)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -2,7 +2,7 @@
|
|||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.ApplicationRequirements do
|
||||
defmodule Pleroma.Application.Requirements do
|
||||
@moduledoc """
|
||||
The module represents the collection of validations to runs before start server.
|
||||
"""
|
||||
|
@ -18,6 +18,8 @@ defmodule Pleroma.ApplicationRequirements do
|
|||
|
||||
@spec verify!() :: :ok | VerifyError.t()
|
||||
def verify! do
|
||||
adapter = Application.get_env(:tesla, :adapter)
|
||||
|
||||
:ok
|
||||
|> check_system_commands!()
|
||||
|> check_confirmation_accounts!()
|
||||
|
@ -25,11 +27,12 @@ defmodule Pleroma.ApplicationRequirements do
|
|||
|> check_welcome_message_config!()
|
||||
|> check_rum!()
|
||||
|> check_repo_pool_size!()
|
||||
|> handle_result()
|
||||
|> check_otp_version!(adapter)
|
||||
|> handle_result!()
|
||||
end
|
||||
|
||||
defp handle_result(:ok), do: :ok
|
||||
defp handle_result({:error, message}), do: raise(VerifyError, message: message)
|
||||
defp handle_result!(:ok), do: :ok
|
||||
defp handle_result!({:error, message}), do: raise(VerifyError, message: message)
|
||||
|
||||
defp check_welcome_message_config!(:ok) do
|
||||
if Pleroma.Config.get([:welcome, :email, :enabled], false) and
|
||||
|
@ -164,9 +167,9 @@ defmodule Pleroma.ApplicationRequirements do
|
|||
|
||||
defp check_system_commands!(:ok) do
|
||||
filter_commands_statuses = [
|
||||
check_filter(Pleroma.Upload.Filters.Exiftool, "exiftool"),
|
||||
check_filter(Pleroma.Upload.Filters.Mogrify, "mogrify"),
|
||||
check_filter(Pleroma.Upload.Filters.Mogrifun, "mogrify")
|
||||
check_filter!(Pleroma.Upload.Filters.Exiftool, "exiftool"),
|
||||
check_filter!(Pleroma.Upload.Filters.Mogrify, "mogrify"),
|
||||
check_filter!(Pleroma.Upload.Filters.Mogrifun, "mogrify")
|
||||
]
|
||||
|
||||
preview_proxy_commands_status =
|
||||
|
@ -217,7 +220,7 @@ defmodule Pleroma.ApplicationRequirements do
|
|||
|
||||
defp check_repo_pool_size!(result), do: result
|
||||
|
||||
defp check_filter(filter, command_required) do
|
||||
defp check_filter!(filter, command_required) do
|
||||
filters = Config.get([Pleroma.Upload, :filters])
|
||||
|
||||
if filter in filters and not Pleroma.Utils.command_available?(command_required) do
|
||||
|
@ -231,4 +234,32 @@ defmodule Pleroma.ApplicationRequirements do
|
|||
true
|
||||
end
|
||||
end
|
||||
|
||||
defp check_otp_version!(:ok, Tesla.Adapter.Gun) do
|
||||
if version = Pleroma.OTPVersion.version() do
|
||||
[major, minor] =
|
||||
version
|
||||
|> String.split(".")
|
||||
|> Enum.map(&String.to_integer/1)
|
||||
|> Enum.take(2)
|
||||
|
||||
if (major == 22 and minor < 2) or major < 22 do
|
||||
Logger.error("
|
||||
!!!OTP VERSION ERROR!!!
|
||||
You are using gun adapter with OTP version #{version}, which doesn't support correct handling of unordered certificates chains. Please update your Erlang/OTP to at least 22.2.
|
||||
")
|
||||
{:error, "OTP version error"}
|
||||
else
|
||||
:ok
|
||||
end
|
||||
else
|
||||
Logger.error("
|
||||
!!!OTP VERSION ERROR!!!
|
||||
To support correct handling of unordered certificates chains - OTP version must be > 22.2.
|
||||
")
|
||||
{:error, "OTP version error"}
|
||||
end
|
||||
end
|
||||
|
||||
defp check_otp_version!(result, _), do: result
|
||||
end
|
182
lib/pleroma/application/start_up_dependencies.ex
Normal file
182
lib/pleroma/application/start_up_dependencies.ex
Normal file
|
@ -0,0 +1,182 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Application.StartUpDependencies do
|
||||
alias Pleroma.Config
|
||||
alias Pleroma.Web.Endpoint
|
||||
|
||||
require Cachex.Spec
|
||||
require Logger
|
||||
|
||||
@type config_path() :: {atom(), atom()} | {atom(), atom(), [atom()]}
|
||||
@type relation() :: {config_path(), module()}
|
||||
|
||||
@spec start_all(Pleroma.Application.env()) ::
|
||||
:ok | {:error, {:already_started, pid()} | :max_children | term()}
|
||||
def start_all(env) do
|
||||
with :ok <- start_common_deps(env),
|
||||
:ok <- start_config_dependent_deps(env) do
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
@spec adapter_module() :: module()
|
||||
def adapter_module do
|
||||
if Application.get_env(:tesla, :adapter) == Tesla.Adapter.Gun do
|
||||
Pleroma.Gun.GunSupervisor
|
||||
else
|
||||
Pleroma.HTTP.HackneySupervisor
|
||||
end
|
||||
end
|
||||
|
||||
@spec spec(module()) :: module() | {module(), keyword()}
|
||||
def spec(Oban), do: {Oban, Config.get(Oban)}
|
||||
|
||||
def spec(Pleroma.Web.StreamerRegistry) do
|
||||
{Registry,
|
||||
[
|
||||
name: Pleroma.Web.Streamer.registry(),
|
||||
keys: :duplicate,
|
||||
partitions: System.schedulers_online()
|
||||
]}
|
||||
end
|
||||
|
||||
def spec(child), do: child
|
||||
|
||||
@spec cachex_spec({String.t(), keyword()}) :: :supervisor.child_spec()
|
||||
def cachex_spec({type, opts}) do
|
||||
%{
|
||||
id: String.to_atom("cachex_" <> type),
|
||||
start: {Cachex, :start_link, [String.to_atom(type <> "_cache"), opts]},
|
||||
type: :worker
|
||||
}
|
||||
end
|
||||
|
||||
defp start_common_deps(env) do
|
||||
fun = fn child ->
|
||||
DynamicSupervisor.start_child(Pleroma.Application.dynamic_supervisor(), spec(child))
|
||||
end
|
||||
|
||||
[
|
||||
Pleroma.Emoji,
|
||||
Pleroma.Stats,
|
||||
Pleroma.JobQueueMonitor,
|
||||
{Majic.Pool, [name: Pleroma.MajicPool, pool_size: Config.get([:majic_pool, :size], 2)]},
|
||||
%{
|
||||
id: :web_push_init,
|
||||
start: {Task, :start_link, [&Pleroma.Web.Push.init/0]},
|
||||
restart: :temporary
|
||||
}
|
||||
]
|
||||
|> add_cachex_deps()
|
||||
|> maybe_add_init_internal_fetch_actor_task(env)
|
||||
|> maybe_add_background_migrator(env)
|
||||
|> start_while(fun)
|
||||
end
|
||||
|
||||
defp start_config_dependent_deps(env) do
|
||||
fun = fn child -> Pleroma.Application.ConfigDependentDeps.start_dependency(child) end
|
||||
|
||||
[
|
||||
Pleroma.Web.Plugs.RateLimiter.Supervisor,
|
||||
Oban,
|
||||
Endpoint,
|
||||
Pleroma.Gopher.Server
|
||||
]
|
||||
|> add_http_children(env)
|
||||
|> maybe_add(:streamer, env)
|
||||
|> maybe_add_chat_child()
|
||||
|> start_while(fun)
|
||||
end
|
||||
|
||||
defp start_while(deps, fun) do
|
||||
Enum.reduce_while(deps, :ok, fn child, acc ->
|
||||
case fun.(child) do
|
||||
{:ok, _} ->
|
||||
{:cont, acc}
|
||||
|
||||
# consider this behavior is normal
|
||||
:ignore ->
|
||||
Logger.info("#{inspect(child)} is ignored.")
|
||||
{:cont, acc}
|
||||
|
||||
error ->
|
||||
Logger.error("Child #{inspect(child)} can't be started. #{inspect(error)}")
|
||||
{:halt, error}
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
@spec cachex_deps() :: [tuple()]
|
||||
def cachex_deps do
|
||||
captcha_clean_up_interval =
|
||||
[Pleroma.Captcha, :seconds_valid]
|
||||
|> Config.get!()
|
||||
|> :timer.seconds()
|
||||
|
||||
[
|
||||
{"used_captcha", expiration: Cachex.Spec.expiration(interval: captcha_clean_up_interval)},
|
||||
{"user", expiration: cachex_expiration(25_000, 1000), limit: 2500},
|
||||
{"object", expiration: cachex_expiration(25_000, 1000), limit: 2500},
|
||||
{"rich_media",
|
||||
expiration: Cachex.Spec.expiration(default: :timer.minutes(120)), limit: 5000},
|
||||
{"scrubber", limit: 2500},
|
||||
{"idempotency", expiration: cachex_expiration(21_600, 60), limit: 2500},
|
||||
{"web_resp", limit: 2500},
|
||||
{"emoji_packs", expiration: cachex_expiration(300, 60), limit: 10},
|
||||
{"failed_proxy_url", limit: 2500},
|
||||
{"banned_urls",
|
||||
expiration: Cachex.Spec.expiration(default: :timer.hours(24 * 30)), limit: 5_000},
|
||||
{"chat_message_id_idempotency_key",
|
||||
expiration: cachex_expiration(:timer.minutes(2), :timer.seconds(60)), limit: 500_000}
|
||||
]
|
||||
end
|
||||
|
||||
defp add_cachex_deps(application_deps) do
|
||||
cachex_deps()
|
||||
|> Enum.reduce(application_deps, fn cachex_init_args, acc ->
|
||||
[cachex_spec(cachex_init_args) | acc]
|
||||
end)
|
||||
end
|
||||
|
||||
defp cachex_expiration(default, interval) do
|
||||
Cachex.Spec.expiration(default: :timer.seconds(default), interval: :timer.seconds(interval))
|
||||
end
|
||||
|
||||
defp maybe_add_init_internal_fetch_actor_task(children, :test), do: children
|
||||
|
||||
defp maybe_add_init_internal_fetch_actor_task(children, _) do
|
||||
[
|
||||
%{
|
||||
id: :internal_fetch_init,
|
||||
start: {Task, :start_link, [&Pleroma.Web.ActivityPub.InternalFetchActor.init/0]},
|
||||
restart: :temporary
|
||||
}
|
||||
| children
|
||||
]
|
||||
end
|
||||
|
||||
defp maybe_add_background_migrator(children, env) when env in [:test, :benchmark], do: children
|
||||
|
||||
defp maybe_add_background_migrator(children, _) do
|
||||
[Pleroma.Migrators.HashtagsTableMigrator | children]
|
||||
end
|
||||
|
||||
defp maybe_add(children, _, env) when env in [:test, :benchmark], do: children
|
||||
defp maybe_add(children, :streamer, _), do: [Pleroma.Web.Streamer.registry() | children]
|
||||
|
||||
defp add_http_children(children, :test) do
|
||||
[Pleroma.HTTP.HackneySupervisor, Pleroma.Gun.GunSupervisor | children]
|
||||
end
|
||||
|
||||
defp add_http_children(children, _), do: [adapter_module() | children]
|
||||
|
||||
defp maybe_add_chat_child(children) do
|
||||
if Config.get([:chat, :enabled]) do
|
||||
[Pleroma.Application.ChatSupervisor | children]
|
||||
else
|
||||
children
|
||||
end
|
||||
end
|
||||
end
|
195
lib/pleroma/config/converter.ex
Normal file
195
lib/pleroma/config/converter.ex
Normal file
|
@ -0,0 +1,195 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Config.Converter do
|
||||
@moduledoc """
|
||||
Converts json structures into elixir structures and types and vice versa.
|
||||
"""
|
||||
@spec to_elixir_types(boolean() | String.t() | map() | list()) :: term()
|
||||
def to_elixir_types(%{"tuple" => [":args", args]}) when is_list(args) do
|
||||
arguments =
|
||||
Enum.map(args, fn arg ->
|
||||
if String.contains?(arg, ["{", "}"]) do
|
||||
{elem, []} = Code.eval_string(arg)
|
||||
elem
|
||||
else
|
||||
to_elixir_types(arg)
|
||||
end
|
||||
end)
|
||||
|
||||
{:args, arguments}
|
||||
end
|
||||
|
||||
def to_elixir_types(%{"tuple" => [":proxy_url", %{"tuple" => [type, host, port]}]}) do
|
||||
{:proxy_url, {string_to_elixir_types!(type), parse_host(host), port}}
|
||||
end
|
||||
|
||||
def to_elixir_types(%{"tuple" => [":partial_chain", entity]}) do
|
||||
{partial_chain, []} =
|
||||
entity
|
||||
|> String.replace(~r/[^\w|^{:,[|^,|^[|^\]^}|^\/|^\.|^"]^\s/, "")
|
||||
|> Code.eval_string()
|
||||
|
||||
{:partial_chain, partial_chain}
|
||||
end
|
||||
|
||||
def to_elixir_types(%{"tuple" => entity}) do
|
||||
Enum.reduce(entity, {}, &Tuple.append(&2, to_elixir_types(&1)))
|
||||
end
|
||||
|
||||
def to_elixir_types(entity) when is_map(entity) do
|
||||
Map.new(entity, fn {k, v} -> {to_elixir_types(k), to_elixir_types(v)} end)
|
||||
end
|
||||
|
||||
def to_elixir_types(entity) when is_list(entity) do
|
||||
Enum.map(entity, &to_elixir_types/1)
|
||||
end
|
||||
|
||||
def to_elixir_types(entity) when is_binary(entity) do
|
||||
entity
|
||||
|> String.trim()
|
||||
|> string_to_elixir_types!()
|
||||
end
|
||||
|
||||
def to_elixir_types(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
|
||||
|
||||
@spec string_to_elixir_types!(String.t()) ::
|
||||
atom() | Regex.t() | module() | String.t() | no_return()
|
||||
def string_to_elixir_types!("~r" <> _pattern = regex) do
|
||||
pattern =
|
||||
~r/^~r(?'delimiter'[\/|"'([{<]{1})(?'pattern'.+)[\/|"')\]}>]{1}(?'modifier'[uismxfU]*)/u
|
||||
|
||||
delimiters = ["/", "|", "\"", "'", {"(", ")"}, {"[", "]"}, {"{", "}"}, {"<", ">"}]
|
||||
|
||||
with %{"modifier" => modifier, "pattern" => pattern, "delimiter" => regex_delimiter} <-
|
||||
Regex.named_captures(pattern, regex),
|
||||
{:ok, {leading, closing}} <- find_valid_delimiter(delimiters, pattern, regex_delimiter),
|
||||
{result, _} <- Code.eval_string("~r#{leading}#{pattern}#{closing}#{modifier}") do
|
||||
result
|
||||
end
|
||||
end
|
||||
|
||||
def string_to_elixir_types!(":" <> atom), do: String.to_atom(atom)
|
||||
|
||||
def string_to_elixir_types!(value) do
|
||||
if module_name?(value) do
|
||||
String.to_existing_atom("Elixir." <> value)
|
||||
else
|
||||
value
|
||||
end
|
||||
end
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
@spec module_name?(String.t()) :: boolean()
|
||||
def module_name?(string) do
|
||||
Regex.match?(~r/^(Pleroma|Phoenix|Tesla|Quack|Ueberauth|Swoosh)\./, string) or
|
||||
string in ["Oban", "Ueberauth", "ExSyslogger", "ConcurrentLimiter"]
|
||||
end
|
||||
|
||||
@spec to_json_types(term()) :: map() | list() | boolean() | String.t() | integer()
|
||||
def to_json_types(entity) when is_list(entity) do
|
||||
Enum.map(entity, &to_json_types/1)
|
||||
end
|
||||
|
||||
def to_json_types(%Regex{} = entity), do: inspect(entity)
|
||||
|
||||
def to_json_types(entity) when is_map(entity) do
|
||||
Map.new(entity, fn {k, v} -> {to_json_types(k), to_json_types(v)} end)
|
||||
end
|
||||
|
||||
def to_json_types({:args, args}) when is_list(args) do
|
||||
arguments =
|
||||
Enum.map(args, fn
|
||||
arg when is_tuple(arg) -> inspect(arg)
|
||||
arg -> to_json_types(arg)
|
||||
end)
|
||||
|
||||
%{"tuple" => [":args", arguments]}
|
||||
end
|
||||
|
||||
def to_json_types({:proxy_url, {type, :localhost, port}}) do
|
||||
%{"tuple" => [":proxy_url", %{"tuple" => [to_json_types(type), "localhost", port]}]}
|
||||
end
|
||||
|
||||
def to_json_types({:proxy_url, {type, host, port}}) when is_tuple(host) do
|
||||
ip =
|
||||
host
|
||||
|> :inet_parse.ntoa()
|
||||
|> to_string()
|
||||
|
||||
%{
|
||||
"tuple" => [
|
||||
":proxy_url",
|
||||
%{"tuple" => [to_json_types(type), ip, port]}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
def to_json_types({:proxy_url, {type, host, port}}) do
|
||||
%{
|
||||
"tuple" => [
|
||||
":proxy_url",
|
||||
%{"tuple" => [to_json_types(type), to_string(host), port]}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
def to_json_types({:partial_chain, entity}),
|
||||
do: %{"tuple" => [":partial_chain", inspect(entity)]}
|
||||
|
||||
def to_json_types(entity) when is_tuple(entity) do
|
||||
value =
|
||||
entity
|
||||
|> Tuple.to_list()
|
||||
|> to_json_types()
|
||||
|
||||
%{"tuple" => value}
|
||||
end
|
||||
|
||||
def to_json_types(entity) when is_binary(entity), do: entity
|
||||
|
||||
def to_json_types(entity) when is_boolean(entity) or is_number(entity) or is_nil(entity) do
|
||||
entity
|
||||
end
|
||||
|
||||
def to_json_types(entity) when entity in [:"tlsv1.1", :"tlsv1.2", :"tlsv1.3"] do
|
||||
":#{entity}"
|
||||
end
|
||||
|
||||
def to_json_types(entity) when is_atom(entity), do: inspect(entity)
|
||||
end
|
|
@ -41,7 +41,8 @@ defmodule Pleroma.Config.DeprecationWarnings do
|
|||
:ok <- check_gun_pool_options(),
|
||||
:ok <- check_activity_expiration_config(),
|
||||
:ok <- check_remote_ip_plug_name(),
|
||||
:ok <- check_uploders_s3_public_endpoint() do
|
||||
:ok <- check_uploders_s3_public_endpoint(),
|
||||
:ok <- check_oban_config() do
|
||||
:ok
|
||||
else
|
||||
_ ->
|
||||
|
@ -79,7 +80,7 @@ defmodule Pleroma.Config.DeprecationWarnings do
|
|||
move_namespace_and_warn(@mrf_config_map, warning_preface)
|
||||
end
|
||||
|
||||
@spec move_namespace_and_warn([config_map()], String.t()) :: :ok | nil
|
||||
@spec move_namespace_and_warn([config_map()], String.t()) :: :ok | :error
|
||||
def move_namespace_and_warn(config_map, warning_preface) do
|
||||
warning =
|
||||
Enum.reduce(config_map, "", fn
|
||||
|
@ -102,7 +103,7 @@ defmodule Pleroma.Config.DeprecationWarnings do
|
|||
end
|
||||
end
|
||||
|
||||
@spec check_media_proxy_whitelist_config() :: :ok | nil
|
||||
@spec check_media_proxy_whitelist_config() :: :ok | :error
|
||||
def check_media_proxy_whitelist_config do
|
||||
whitelist = Config.get([:media_proxy, :whitelist])
|
||||
|
||||
|
@ -163,7 +164,7 @@ defmodule Pleroma.Config.DeprecationWarnings do
|
|||
end
|
||||
end
|
||||
|
||||
@spec check_activity_expiration_config() :: :ok | nil
|
||||
@spec check_activity_expiration_config() :: :ok | :error
|
||||
def check_activity_expiration_config do
|
||||
warning_preface = """
|
||||
!!!DEPRECATION WARNING!!!
|
||||
|
@ -215,4 +216,41 @@ defmodule Pleroma.Config.DeprecationWarnings do
|
|||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
@spec check_oban_config() :: :ok | :error
|
||||
def check_oban_config do
|
||||
oban_config = Config.get(Oban)
|
||||
|
||||
{crontab, changed?} =
|
||||
[
|
||||
Pleroma.Workers.Cron.StatsWorker,
|
||||
Pleroma.Workers.Cron.PurgeExpiredActivitiesWorker,
|
||||
Pleroma.Workers.Cron.ClearOauthTokenWorker
|
||||
]
|
||||
|> Enum.reduce({oban_config[:crontab], false}, fn removed_worker, {acc, changed?} ->
|
||||
with acc when is_list(acc) <- acc,
|
||||
setting when is_tuple(setting) <-
|
||||
Enum.find(acc, fn {_, worker} -> worker == removed_worker end) do
|
||||
"""
|
||||
!!!OBAN CONFIG WARNING!!!
|
||||
You are using old workers in Oban crontab settings, which were removed.
|
||||
Please, remove setting from crontab in your config file (prod.secret.exs): #{
|
||||
inspect(setting)
|
||||
}
|
||||
"""
|
||||
|> Logger.warn()
|
||||
|
||||
{List.delete(acc, setting), true}
|
||||
else
|
||||
_ -> {acc, changed?}
|
||||
end
|
||||
end)
|
||||
|
||||
if changed? do
|
||||
Config.put(Oban, Keyword.put(oban_config, :crontab, crontab))
|
||||
:error
|
||||
else
|
||||
:ok
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,57 +3,73 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Config.Loader do
|
||||
@reject_groups [
|
||||
:postgrex,
|
||||
:tesla,
|
||||
:phoenix,
|
||||
:tzdata,
|
||||
:http_signatures,
|
||||
:web_push_encryption,
|
||||
:floki,
|
||||
:pbkdf2_elixir
|
||||
]
|
||||
|
||||
@reject_keys [
|
||||
Pleroma.Repo,
|
||||
Pleroma.Web.Endpoint,
|
||||
:env,
|
||||
:configurable_from_database,
|
||||
:database,
|
||||
:swarm
|
||||
]
|
||||
|
||||
@reject_groups [
|
||||
:postgrex,
|
||||
:tesla
|
||||
:ecto_repos,
|
||||
Pleroma.Gun,
|
||||
Pleroma.ReverseProxy.Client,
|
||||
Pleroma.Web.Auth.Authenticator
|
||||
]
|
||||
|
||||
if Code.ensure_loaded?(Config.Reader) do
|
||||
@reader Config.Reader
|
||||
|
||||
def read(path), do: @reader.read!(path)
|
||||
@config_header "import Config\r\n\r\n"
|
||||
else
|
||||
# support for Elixir less than 1.9
|
||||
@reader Mix.Config
|
||||
def read(path) do
|
||||
path
|
||||
|> @reader.eval!()
|
||||
|> elem(0)
|
||||
end
|
||||
@config_header "use Mix.Config\r\n\r\n"
|
||||
end
|
||||
|
||||
@spec read(Path.t()) :: keyword()
|
||||
@spec read!(Path.t()) :: keyword()
|
||||
def read!(path), do: @reader.read!(path)
|
||||
|
||||
@spec merge(keyword(), keyword()) :: keyword()
|
||||
def merge(c1, c2), do: @reader.merge(c1, c2)
|
||||
|
||||
@spec config_header() :: String.t()
|
||||
def config_header, do: @config_header
|
||||
|
||||
@spec default_config() :: keyword()
|
||||
def default_config do
|
||||
"config/config.exs"
|
||||
|> read()
|
||||
|> filter()
|
||||
config =
|
||||
"config/config.exs"
|
||||
|> read!()
|
||||
|> filter()
|
||||
|
||||
logger_config =
|
||||
:logger
|
||||
|> Application.get_all_env()
|
||||
|> Enum.filter(fn {key, _} -> key in [:backends, :console, :ex_syslogger] end)
|
||||
|
||||
merge(config, logger: logger_config)
|
||||
end
|
||||
|
||||
defp filter(configs) do
|
||||
configs
|
||||
|> Keyword.keys()
|
||||
|> Enum.reduce([], &Keyword.put(&2, &1, filter_group(&1, configs)))
|
||||
end
|
||||
@spec filter(keyword()) :: keyword()
|
||||
def filter(configs) do
|
||||
Enum.reduce(configs, [], fn
|
||||
{group, _settings}, group_acc when group in @reject_groups ->
|
||||
group_acc
|
||||
|
||||
@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 in @reject_groups or
|
||||
(group == :phoenix and key == :serve_endpoints)
|
||||
{group, settings}, group_acc ->
|
||||
Enum.reduce(settings, group_acc, fn
|
||||
{key, _value}, acc when key in @reject_keys -> acc
|
||||
setting, acc -> Keyword.update(acc, group, [setting], &Keyword.merge(&1, [setting]))
|
||||
end)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Config.Oban do
|
||||
require Logger
|
||||
|
||||
def warn do
|
||||
oban_config = Pleroma.Config.get(Oban)
|
||||
|
||||
crontab =
|
||||
[
|
||||
Pleroma.Workers.Cron.StatsWorker,
|
||||
Pleroma.Workers.Cron.PurgeExpiredActivitiesWorker,
|
||||
Pleroma.Workers.Cron.ClearOauthTokenWorker
|
||||
]
|
||||
|> Enum.reduce(oban_config[:crontab], fn removed_worker, acc ->
|
||||
with acc when is_list(acc) <- acc,
|
||||
setting when is_tuple(setting) <-
|
||||
Enum.find(acc, fn {_, worker} -> worker == removed_worker end) do
|
||||
"""
|
||||
!!!OBAN CONFIG WARNING!!!
|
||||
You are using old workers in Oban crontab settings, which were removed.
|
||||
Please, remove setting from crontab in your config file (prod.secret.exs): #{
|
||||
inspect(setting)
|
||||
}
|
||||
"""
|
||||
|> Logger.warn()
|
||||
|
||||
List.delete(acc, setting)
|
||||
else
|
||||
_ -> acc
|
||||
end
|
||||
end)
|
||||
|
||||
Pleroma.Config.put(Oban, Keyword.put(oban_config, :crontab, crontab))
|
||||
end
|
||||
end
|
|
@ -1,201 +0,0 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Config.TransferTask do
|
||||
use Task
|
||||
|
||||
alias Pleroma.Config
|
||||
alias Pleroma.ConfigDB
|
||||
alias Pleroma.Repo
|
||||
|
||||
require Logger
|
||||
|
||||
@type env() :: :test | :benchmark | :dev | :prod
|
||||
|
||||
@reboot_time_keys [
|
||||
{:pleroma, :hackney_pools},
|
||||
{:pleroma, :chat},
|
||||
{:pleroma, Oban},
|
||||
{:pleroma, :rate_limit},
|
||||
{:pleroma, :markup},
|
||||
{:pleroma, :streamer},
|
||||
{:pleroma, :pools},
|
||||
{:pleroma, :connections_pool}
|
||||
]
|
||||
|
||||
@reboot_time_subkeys [
|
||||
{:pleroma, Pleroma.Captcha, [:seconds_valid]},
|
||||
{:pleroma, Pleroma.Upload, [:proxy_remote]},
|
||||
{:pleroma, :instance, [:upload_limit]},
|
||||
{:pleroma, :gopher, [:enabled]}
|
||||
]
|
||||
|
||||
def start_link(restart_pleroma? \\ true) do
|
||||
load_and_update_env([], restart_pleroma?)
|
||||
if Config.get(:env) == :test, do: Ecto.Adapters.SQL.Sandbox.checkin(Repo)
|
||||
:ignore
|
||||
end
|
||||
|
||||
@spec load_and_update_env([ConfigDB.t()], boolean()) :: :ok
|
||||
def load_and_update_env(deleted_settings \\ [], restart_pleroma? \\ true) do
|
||||
with {_, true} <- {:configurable, Config.get(:configurable_from_database)} do
|
||||
# We need to restart applications for loaded settings take effect
|
||||
|
||||
{logger, other} =
|
||||
(Repo.all(ConfigDB) ++ deleted_settings)
|
||||
|> Enum.map(&merge_with_default/1)
|
||||
|> Enum.split_with(fn {group, _, _, _} -> group in [:logger, :quack] end)
|
||||
|
||||
logger
|
||||
|> Enum.sort()
|
||||
|> Enum.each(&configure/1)
|
||||
|
||||
started_applications = Application.started_applications()
|
||||
|
||||
# TODO: some problem with prometheus after restart!
|
||||
reject = [nil, :prometheus, :postgrex]
|
||||
|
||||
reject =
|
||||
if restart_pleroma? do
|
||||
reject
|
||||
else
|
||||
[:pleroma | reject]
|
||||
end
|
||||
|
||||
other
|
||||
|> Enum.map(&update/1)
|
||||
|> Enum.uniq()
|
||||
|> Enum.reject(&(&1 in reject))
|
||||
|> maybe_set_pleroma_last()
|
||||
|> Enum.each(&restart(started_applications, &1, Config.get(:env)))
|
||||
|
||||
:ok
|
||||
else
|
||||
{:configurable, false} -> Restarter.Pleroma.rebooted()
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_set_pleroma_last(apps) do
|
||||
# to be ensured that pleroma will be restarted last
|
||||
if :pleroma in apps do
|
||||
apps
|
||||
|> List.delete(:pleroma)
|
||||
|> List.insert_at(-1, :pleroma)
|
||||
else
|
||||
Restarter.Pleroma.rebooted()
|
||||
apps
|
||||
end
|
||||
end
|
||||
|
||||
defp merge_with_default(%{group: group, key: key, value: value} = setting) do
|
||||
default = Config.Holder.default_config(group, key)
|
||||
|
||||
merged =
|
||||
cond do
|
||||
Ecto.get_meta(setting, :state) == :deleted -> default
|
||||
can_be_merged?(default, value) -> ConfigDB.merge_group(group, key, default, value)
|
||||
true -> value
|
||||
end
|
||||
|
||||
{group, key, value, merged}
|
||||
end
|
||||
|
||||
# change logger configuration in runtime, without restart
|
||||
defp configure({:quack, key, _, merged}) do
|
||||
Logger.configure_backend(Quack.Logger, [{key, merged}])
|
||||
:ok = update_env(:quack, key, merged)
|
||||
end
|
||||
|
||||
defp configure({_, :backends, _, merged}) do
|
||||
# removing current backends
|
||||
Enum.each(Application.get_env(:logger, :backends), &Logger.remove_backend/1)
|
||||
|
||||
Enum.each(merged, &Logger.add_backend/1)
|
||||
|
||||
:ok = update_env(:logger, :backends, merged)
|
||||
end
|
||||
|
||||
defp configure({_, key, _, merged}) when key in [:console, :ex_syslogger] do
|
||||
merged =
|
||||
if key == :console do
|
||||
put_in(merged[:format], merged[:format] <> "\n")
|
||||
else
|
||||
merged
|
||||
end
|
||||
|
||||
backend =
|
||||
if key == :ex_syslogger,
|
||||
do: {ExSyslogger, :ex_syslogger},
|
||||
else: key
|
||||
|
||||
Logger.configure_backend(backend, merged)
|
||||
:ok = update_env(:logger, key, merged)
|
||||
end
|
||||
|
||||
defp configure({_, key, _, merged}) do
|
||||
Logger.configure([{key, merged}])
|
||||
:ok = update_env(:logger, key, merged)
|
||||
end
|
||||
|
||||
defp update({group, key, value, merged}) do
|
||||
try do
|
||||
:ok = update_env(group, key, merged)
|
||||
|
||||
if group != :pleroma or pleroma_need_restart?(group, key, value), do: group
|
||||
rescue
|
||||
error ->
|
||||
error_msg =
|
||||
"updating env causes error, group: #{inspect(group)}, key: #{inspect(key)}, value: #{
|
||||
inspect(value)
|
||||
} error: #{inspect(error)}"
|
||||
|
||||
Logger.warn(error_msg)
|
||||
|
||||
nil
|
||||
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)
|
||||
|
||||
@spec pleroma_need_restart?(atom(), atom(), any()) :: boolean()
|
||||
def pleroma_need_restart?(group, key, value) do
|
||||
group_and_key_need_reboot?(group, key) or group_and_subkey_need_reboot?(group, key, value)
|
||||
end
|
||||
|
||||
defp group_and_key_need_reboot?(group, key) do
|
||||
Enum.any?(@reboot_time_keys, fn {g, k} -> g == group and k == key end)
|
||||
end
|
||||
|
||||
defp group_and_subkey_need_reboot?(group, key, value) do
|
||||
Keyword.keyword?(value) and
|
||||
Enum.any?(@reboot_time_subkeys, fn {g, k, subkeys} ->
|
||||
g == group and k == key and
|
||||
Enum.any?(Keyword.keys(value), &(&1 in subkeys))
|
||||
end)
|
||||
end
|
||||
|
||||
defp restart(_, :pleroma, env), do: Restarter.Pleroma.restart_after_boot(env)
|
||||
|
||||
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 ->
|
||||
error
|
||||
|> inspect()
|
||||
|> Logger.warn()
|
||||
end
|
||||
end
|
||||
|
||||
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
|
25
lib/pleroma/config/version.ex
Normal file
25
lib/pleroma/config/version.ex
Normal file
|
@ -0,0 +1,25 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Config.Version do
|
||||
@moduledoc """
|
||||
IMPORTANT!!!
|
||||
Before modifying records in the database directly, please read "Config versioning" in `docs/development/config_versioning.md`.
|
||||
"""
|
||||
|
||||
use Ecto.Schema
|
||||
|
||||
import Ecto.Query, only: [from: 2]
|
||||
|
||||
schema "config_versions" do
|
||||
field(:backup, Pleroma.EctoType.Config.BinaryValue)
|
||||
field(:current, :boolean, default: true)
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
||||
def all do
|
||||
from(v in __MODULE__, order_by: [desc: v.id]) |> Pleroma.Repo.all()
|
||||
end
|
||||
end
|
292
lib/pleroma/config/versioning.ex
Normal file
292
lib/pleroma/config/versioning.ex
Normal file
|
@ -0,0 +1,292 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Config.Versioning do
|
||||
@moduledoc """
|
||||
Module that manages versions of database configs.
|
||||
"""
|
||||
|
||||
import Ecto.Query, only: [from: 2]
|
||||
|
||||
alias Ecto.Multi
|
||||
alias Pleroma.Config.Version
|
||||
alias Pleroma.ConfigDB
|
||||
alias Pleroma.Repo
|
||||
|
||||
@type change :: %{
|
||||
optional(:delete) => boolean(),
|
||||
optional(:value) => any(),
|
||||
group: atom(),
|
||||
key: atom() | nil
|
||||
}
|
||||
|
||||
@doc """
|
||||
Creates new config version:
|
||||
- convert changes to elixir types
|
||||
- splits changes by type and processes them in `config` table
|
||||
- sets all pointers to false
|
||||
- gets all rows from `config` table and inserts them as keyword in `backup` field
|
||||
"""
|
||||
@spec new_version([change()] | change()) ::
|
||||
{:ok, map()} | {:error, :no_changes} | {:error, atom() | tuple(), any(), any()}
|
||||
def new_version([]), do: {:error, :empty_changes}
|
||||
def new_version(change) when is_map(change), do: new_version([change])
|
||||
|
||||
def new_version(changes) when is_list(changes) do
|
||||
changes
|
||||
|> Enum.reduce(Multi.new(), fn
|
||||
%{delete: true} = deletion, acc ->
|
||||
Multi.run(acc, {:delete_or_update, deletion[:group], deletion[:key]}, fn _, _ ->
|
||||
ConfigDB.delete_or_update(deletion)
|
||||
end)
|
||||
|
||||
operation, acc ->
|
||||
{name, fun} =
|
||||
if Keyword.keyword?(operation[:value]) or
|
||||
(operation[:group] == :pleroma and
|
||||
operation[:key] in ConfigDB.pleroma_not_keyword_values()) do
|
||||
{:insert_or_update,
|
||||
fn _, _ ->
|
||||
ConfigDB.update_or_create(operation)
|
||||
end}
|
||||
else
|
||||
{:error,
|
||||
fn _, _ ->
|
||||
{:error, {:value_must_be_keyword, operation}}
|
||||
end}
|
||||
end
|
||||
|
||||
Multi.run(acc, {name, operation[:group], operation[:key]}, fun)
|
||||
end)
|
||||
|> set_current_flag_false_for_all_versions()
|
||||
|> insert_new_version()
|
||||
|> Repo.transaction()
|
||||
end
|
||||
|
||||
def new_version(_), do: {:error, :bad_format}
|
||||
|
||||
defp set_current_flag_false_for_all_versions(multi) do
|
||||
Multi.update_all(multi, :update_all_versions, Version, set: [current: false])
|
||||
end
|
||||
|
||||
defp insert_new_version(multi) do
|
||||
Multi.run(multi, :insert_version, fn repo, _ ->
|
||||
%Version{
|
||||
backup: ConfigDB.all_as_keyword()
|
||||
}
|
||||
|> repo.insert()
|
||||
end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Rollbacks config version by N steps:
|
||||
- checks possibility for rollback
|
||||
- truncates config table and restarts pk
|
||||
- inserts config settings from backup
|
||||
- sets all pointers to false
|
||||
- sets current pointer to true for rollback version
|
||||
- deletes versions after current
|
||||
"""
|
||||
@spec rollback(pos_integer()) ::
|
||||
{:ok, map()}
|
||||
| {:error, atom() | tuple(), any(), any()}
|
||||
| {:error, :steps_format}
|
||||
| {:error, :no_current_version}
|
||||
| {:error, :rollback_not_possible}
|
||||
def rollback(steps \\ 1)
|
||||
|
||||
def rollback(steps) when is_integer(steps) and steps > 0 do
|
||||
with version_id when is_integer(version_id) <- get_current_version_id(),
|
||||
%Version{} = version <- get_version_by_steps(steps) do
|
||||
do_rollback(version)
|
||||
end
|
||||
end
|
||||
|
||||
def rollback(_), do: {:error, :steps_format}
|
||||
|
||||
@doc """
|
||||
Same as `rollback/1`, but rollbacks for a given version id.
|
||||
"""
|
||||
@spec rollback_by_id(pos_integer()) ::
|
||||
{:ok, map()}
|
||||
| {:error, atom() | tuple(), any(), any()}
|
||||
| {:error, :not_found}
|
||||
| {:error, :version_is_already_current}
|
||||
def rollback_by_id(id) when is_integer(id) do
|
||||
with %Version{current: false} = version <- get_version_by_id(id) do
|
||||
do_rollback(version)
|
||||
else
|
||||
%Version{current: true} -> {:error, :version_is_already_current}
|
||||
error -> error
|
||||
end
|
||||
end
|
||||
|
||||
defp get_current_version_id do
|
||||
query = from(v in Version, where: v.current == true)
|
||||
|
||||
with nil <- Repo.aggregate(query, :max, :id) do
|
||||
{:error, :no_current_version}
|
||||
end
|
||||
end
|
||||
|
||||
defp get_version_by_id(id) do
|
||||
with nil <- Repo.get(Version, id) do
|
||||
{:error, :not_found}
|
||||
end
|
||||
end
|
||||
|
||||
defp get_version_by_steps(steps) do
|
||||
query = from(v in Version, order_by: [desc: v.id], limit: 1, offset: ^steps)
|
||||
|
||||
with nil <- Repo.one(query) do
|
||||
{:error, :rollback_not_possible}
|
||||
end
|
||||
end
|
||||
|
||||
defp do_rollback(version) do
|
||||
multi =
|
||||
truncate_config_table()
|
||||
|> reset_pk_in_config_table()
|
||||
|
||||
version.backup
|
||||
|> ConfigDB.from_keyword_to_maps()
|
||||
|> add_insert_commands(multi)
|
||||
|> set_current_flag_false_for_all_versions()
|
||||
|> Multi.update(:move_current_pointer, Ecto.Changeset.change(version, current: true))
|
||||
|> Multi.delete_all(
|
||||
:delete_next_versions,
|
||||
from(v in Version, where: v.id > ^version.id)
|
||||
)
|
||||
|> Repo.transaction()
|
||||
end
|
||||
|
||||
defp truncate_config_table(multi \\ Multi.new()) do
|
||||
Multi.run(multi, :truncate_config_table, fn repo, _ ->
|
||||
repo.query("TRUNCATE config;")
|
||||
end)
|
||||
end
|
||||
|
||||
defp reset_pk_in_config_table(multi) do
|
||||
Multi.run(multi, :reset_pk, fn repo, _ ->
|
||||
repo.query("ALTER SEQUENCE config_id_seq RESTART;")
|
||||
end)
|
||||
end
|
||||
|
||||
defp add_insert_commands(changes, multi) do
|
||||
Enum.reduce(changes, multi, fn change, acc ->
|
||||
Multi.run(acc, {:insert, change[:group], change[:key]}, fn _, _ ->
|
||||
ConfigDB.update_or_create(change)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Resets config table and creates new empty version.
|
||||
"""
|
||||
@spec reset() :: {:ok, map()} | {:error, atom() | tuple(), any(), any()}
|
||||
def reset do
|
||||
truncate_config_table()
|
||||
|> reset_pk_in_config_table()
|
||||
|> set_current_flag_false_for_all_versions()
|
||||
|> insert_new_version()
|
||||
|> Repo.transaction()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Migrates settings from config file into database:
|
||||
- truncates config table and restarts pk
|
||||
- inserts settings from config file
|
||||
- sets all pointers to false
|
||||
- gets all rows from `config` table and inserts them as keyword in `backup` field
|
||||
"""
|
||||
@spec migrate(Path.t()) :: {:ok, map()} | {:error, atom() | tuple(), any(), any()}
|
||||
def migrate(config_path) do
|
||||
multi =
|
||||
truncate_config_table()
|
||||
|> reset_pk_in_config_table()
|
||||
|
||||
config_path
|
||||
|> Pleroma.Config.Loader.read!()
|
||||
|> Pleroma.Config.Loader.filter()
|
||||
|> ConfigDB.from_keyword_to_maps()
|
||||
|> add_insert_commands(multi)
|
||||
|> set_current_flag_false_for_all_versions()
|
||||
|> insert_new_version()
|
||||
|> Repo.transaction()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Common function to migrate old config namespace to the new one keeping the old value.
|
||||
"""
|
||||
@spec migrate_namespace({atom(), atom()}, {atom(), atom()}) ::
|
||||
{:ok, map()} | {:error, atom() | tuple(), any(), any()}
|
||||
def migrate_namespace({o_group, o_key}, {n_group, n_key}) do
|
||||
config = ConfigDB.get_by_params(%{group: o_group, key: o_key})
|
||||
|
||||
configs_changes_fun =
|
||||
if config do
|
||||
fn ->
|
||||
config
|
||||
|> Ecto.Changeset.change(group: n_group, key: n_key)
|
||||
|> Repo.update()
|
||||
end
|
||||
else
|
||||
fn -> {:ok, nil} end
|
||||
end
|
||||
|
||||
versions_changes_fun = fn %{backup: backup} = version ->
|
||||
with {value, rest} when not is_nil(value) <- pop_in(backup[o_group][o_key]) do
|
||||
rest =
|
||||
if rest[o_group] == [] do
|
||||
Keyword.delete(rest, o_group)
|
||||
else
|
||||
rest
|
||||
end
|
||||
|
||||
updated_backup =
|
||||
if Keyword.has_key?(rest, n_group) do
|
||||
put_in(rest[n_group][n_key], value)
|
||||
else
|
||||
Keyword.put(rest, n_group, [{n_key, value}])
|
||||
end
|
||||
|
||||
version
|
||||
|> Ecto.Changeset.change(backup: updated_backup)
|
||||
|> Repo.update()
|
||||
else
|
||||
_ -> {:ok, nil}
|
||||
end
|
||||
end
|
||||
|
||||
migrate_configs_and_versions(configs_changes_fun, versions_changes_fun)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Abstract function for config migrations to keep changes in config table and changes in versions backups in transaction.
|
||||
Accepts two functions:
|
||||
- first function makes changes to the configs
|
||||
- second function makes changes to the backups in versions
|
||||
"""
|
||||
@spec migrate_configs_and_versions(function(), function()) ::
|
||||
{:ok, map()} | {:error, atom() | tuple(), any(), any()}
|
||||
def migrate_configs_and_versions(configs_changes_fun, version_change_fun)
|
||||
when is_function(configs_changes_fun, 0) and
|
||||
is_function(version_change_fun, 1) do
|
||||
versions = Repo.all(Version)
|
||||
|
||||
multi =
|
||||
Multi.new()
|
||||
|> Multi.run(:configs_changes, fn _, _ ->
|
||||
configs_changes_fun.()
|
||||
end)
|
||||
|
||||
versions
|
||||
|> Enum.reduce(multi, fn version, acc ->
|
||||
Multi.run(acc, {:version_change, version.id}, fn _, _ ->
|
||||
version_change_fun.(version)
|
||||
end)
|
||||
end)
|
||||
|> Repo.transaction()
|
||||
end
|
||||
end
|
|
@ -6,8 +6,7 @@ defmodule Pleroma.ConfigDB do
|
|||
use Ecto.Schema
|
||||
|
||||
import Ecto.Changeset
|
||||
import Ecto.Query, only: [select: 3, from: 2]
|
||||
import Pleroma.Web.Gettext
|
||||
import Ecto.Query, only: [from: 2]
|
||||
|
||||
alias __MODULE__
|
||||
alias Pleroma.Repo
|
||||
|
@ -22,6 +21,10 @@ defmodule Pleroma.ConfigDB do
|
|||
{:pleroma, :mrf_keyword, :replace}
|
||||
]
|
||||
|
||||
@groups_without_keys [:quack, :mime, :cors_plug, :esshd, :ex_aws, :joken, :logger, :swoosh]
|
||||
|
||||
@pleroma_not_keyword_values [Pleroma.Web.Auth.Authenticator, :admin_token]
|
||||
|
||||
schema "config" do
|
||||
field(:key, Pleroma.EctoType.Config.Atom)
|
||||
field(:group, Pleroma.EctoType.Config.Atom)
|
||||
|
@ -31,13 +34,35 @@ 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 ->
|
||||
Keyword.update(acc, group, [{key, value}], &Keyword.merge(&1, [{key, value}]))
|
||||
@spec all() :: [t()]
|
||||
def all, do: Repo.all(ConfigDB)
|
||||
|
||||
@spec all_with_db() :: [t()]
|
||||
def all_with_db do
|
||||
all()
|
||||
|> Enum.map(fn
|
||||
%{group: :pleroma, key: key} = change when key in @pleroma_not_keyword_values ->
|
||||
%{change | db: [change.key]}
|
||||
|
||||
%{value: value} = change ->
|
||||
%{change | db: Keyword.keys(value)}
|
||||
end)
|
||||
end
|
||||
|
||||
@spec all_as_keyword() :: keyword()
|
||||
def all_as_keyword do
|
||||
all()
|
||||
|> as_keyword()
|
||||
end
|
||||
|
||||
@spec as_keyword([t()]) :: keyword()
|
||||
def as_keyword(changes) do
|
||||
Enum.reduce(changes, [], fn
|
||||
%{group: group, key: nil, value: value}, acc ->
|
||||
Keyword.update(acc, group, value, &Keyword.merge(&1, value))
|
||||
|
||||
%{group: group, key: key, value: value}, acc ->
|
||||
Keyword.update(acc, group, [{key, value}], &Keyword.merge(&1, [{key, value}]))
|
||||
end)
|
||||
end
|
||||
|
||||
|
@ -52,14 +77,22 @@ defmodule Pleroma.ConfigDB do
|
|||
end
|
||||
|
||||
@spec get_by_params(map()) :: ConfigDB.t() | nil
|
||||
def get_by_params(%{group: _, key: _} = params), do: Repo.get_by(ConfigDB, params)
|
||||
def get_by_params(%{group: group, key: key} = params)
|
||||
when not is_nil(key) and not is_nil(group) do
|
||||
Repo.get_by(ConfigDB, params)
|
||||
end
|
||||
|
||||
def get_by_params(%{group: group}) do
|
||||
from(c in ConfigDB, where: c.group == ^group and is_nil(c.key)) |> Repo.one()
|
||||
end
|
||||
|
||||
@spec changeset(ConfigDB.t(), map()) :: Changeset.t()
|
||||
def changeset(config, params \\ %{}) do
|
||||
config
|
||||
|> cast(params, [:key, :group, :value])
|
||||
|> validate_required([:key, :group, :value])
|
||||
|> validate_required([:group, :value])
|
||||
|> unique_constraint(:key, name: :config_group_key_index)
|
||||
|> unique_constraint(:key, name: :config_group__key_is_null_index)
|
||||
end
|
||||
|
||||
defp create(params) do
|
||||
|
@ -74,53 +107,104 @@ defmodule Pleroma.ConfigDB do
|
|||
|> Repo.update()
|
||||
end
|
||||
|
||||
@spec get_db_keys(keyword(), any()) :: [String.t()]
|
||||
def get_db_keys(value, key) do
|
||||
keys =
|
||||
if Keyword.keyword?(value) do
|
||||
Keyword.keys(value)
|
||||
else
|
||||
[key]
|
||||
end
|
||||
@doc """
|
||||
IMPORTANT!!!
|
||||
Before modifying records in the database directly, please read "Config versioning" in `docs/development/config_versioning.md`.
|
||||
"""
|
||||
@spec update_or_create(map()) :: {:ok, ConfigDB.t()} | {:error, Changeset.t()}
|
||||
def update_or_create(params) do
|
||||
search_opts = Map.take(params, [:group, :key])
|
||||
|
||||
Enum.map(keys, &to_json_types(&1))
|
||||
with %ConfigDB{} = config <- ConfigDB.get_by_params(search_opts) do
|
||||
new_value = merge_group(config.group, config.key, config.value, params[:value])
|
||||
|
||||
update(config, %{value: new_value})
|
||||
else
|
||||
nil ->
|
||||
create(params)
|
||||
end
|
||||
end
|
||||
|
||||
@spec merge_group(atom(), atom(), keyword(), keyword()) :: keyword()
|
||||
def merge_group(group, key, old_value, new_value) do
|
||||
@doc """
|
||||
IMPORTANT!!!
|
||||
Before modifying records in the database directly, please read "Config versioning" in `docs/development/config_versioning.md`.
|
||||
"""
|
||||
@spec delete(ConfigDB.t() | map()) :: {:ok, ConfigDB.t()} | {:error, Changeset.t()}
|
||||
def delete(%ConfigDB{} = config), do: Repo.delete(config)
|
||||
|
||||
@doc """
|
||||
IMPORTANT!!!
|
||||
Before modifying records in the database directly, please read "Config versioning" in `docs/development/config_versioning.md`.
|
||||
"""
|
||||
@spec delete_or_update(map()) :: {:ok, t()} | {:ok, nil} | {:error, Changeset.t()}
|
||||
def delete_or_update(%{group: _, key: key} = params) when not is_nil(key) do
|
||||
search_opts = Map.take(params, [:group, :key])
|
||||
|
||||
with %ConfigDB{} = config <- ConfigDB.get_by_params(search_opts) do
|
||||
do_delete_or_update(config, params[:subkeys])
|
||||
else
|
||||
_ -> {:ok, nil}
|
||||
end
|
||||
end
|
||||
|
||||
def delete_or_update(%{group: group}) do
|
||||
query = from(c in ConfigDB, where: c.group == ^group)
|
||||
|
||||
with {num, _} <- Repo.delete_all(query) do
|
||||
{:ok, num}
|
||||
end
|
||||
end
|
||||
|
||||
defp do_delete_or_update(%ConfigDB{} = config, subkeys)
|
||||
when is_list(subkeys) and subkeys != [] do
|
||||
new_value = Keyword.drop(config.value, subkeys)
|
||||
|
||||
if new_value == [] do
|
||||
delete(config)
|
||||
else
|
||||
update(config, %{value: new_value})
|
||||
end
|
||||
end
|
||||
|
||||
defp do_delete_or_update(%ConfigDB{} = config, _), do: delete(config)
|
||||
|
||||
defp merge_group(group, key, old_value, new_value)
|
||||
when is_list(old_value) and is_list(new_value) do
|
||||
new_keys = to_mapset(new_value)
|
||||
|
||||
intersect_keys = old_value |> to_mapset() |> MapSet.intersection(new_keys) |> MapSet.to_list()
|
||||
|
||||
merged_value = ConfigDB.merge(old_value, new_value)
|
||||
merged_value = deep_merge(old_value, new_value)
|
||||
|
||||
@full_subkey_update
|
||||
|> Enum.map(fn
|
||||
{g, k, subkey} when g == group and k == key ->
|
||||
if subkey in intersect_keys, do: subkey, else: []
|
||||
|> Enum.reduce([], fn
|
||||
{g, k, subkey}, acc when g == group and k == key ->
|
||||
if subkey in intersect_keys do
|
||||
[subkey | acc]
|
||||
else
|
||||
acc
|
||||
end
|
||||
|
||||
_ ->
|
||||
[]
|
||||
_, acc ->
|
||||
acc
|
||||
end)
|
||||
|> List.flatten()
|
||||
|> Enum.reduce(merged_value, &Keyword.put(&2, &1, new_value[&1]))
|
||||
end
|
||||
|
||||
defp to_mapset(keyword) do
|
||||
defp merge_group(_group, _key, _old_value, new_value) when is_list(new_value), do: new_value
|
||||
|
||||
defp merge_group(:pleroma, key, _old_value, new_value)
|
||||
when key in @pleroma_not_keyword_values do
|
||||
new_value
|
||||
end
|
||||
|
||||
defp to_mapset(keyword) when is_list(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} ->
|
||||
g == group and k == key and subkey in subkeys
|
||||
end)
|
||||
end
|
||||
|
||||
@spec merge(keyword(), keyword()) :: keyword()
|
||||
def merge(config1, config2) when is_list(config1) and is_list(config2) do
|
||||
defp deep_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)
|
||||
|
@ -138,255 +222,99 @@ defmodule Pleroma.ConfigDB do
|
|||
end
|
||||
end
|
||||
|
||||
@spec update_or_create(map()) :: {:ok, ConfigDB.t()} | {:error, Changeset.t()}
|
||||
def update_or_create(params) do
|
||||
params = Map.put(params, :value, to_elixir_types(params[:value]))
|
||||
search_opts = Map.take(params, [:group, :key])
|
||||
@spec reduce_defaults_and_merge_with_changes([t()], keyword()) :: {[t()], keyword()}
|
||||
def reduce_defaults_and_merge_with_changes(changes, defaults) do
|
||||
Enum.reduce(changes, {[], defaults}, &reduce_default_and_merge_with_change/2)
|
||||
end
|
||||
|
||||
with %ConfigDB{} = config <- ConfigDB.get_by_params(search_opts),
|
||||
{_, true, config} <- {:partial_update, can_be_partially_updated?(config), config},
|
||||
{_, true, config} <-
|
||||
{:can_be_merged, is_list(params[:value]) and is_list(config.value), config} do
|
||||
new_value = merge_group(config.group, config.key, config.value, params[:value])
|
||||
update(config, %{value: new_value})
|
||||
defp reduce_default_and_merge_with_change(%{group: group} = change, {acc, defaults})
|
||||
when group in @groups_without_keys do
|
||||
{default, remaining_defaults} = Keyword.pop(defaults, group)
|
||||
|
||||
change = merge_change_with_default(change, default)
|
||||
{[change | acc], remaining_defaults}
|
||||
end
|
||||
|
||||
defp reduce_default_and_merge_with_change(%{group: group, key: key} = change, {acc, defaults}) do
|
||||
if defaults[group] do
|
||||
{default, remaining_group_defaults} = Keyword.pop(defaults[group], key)
|
||||
|
||||
remaining_defaults =
|
||||
if remaining_group_defaults == [] do
|
||||
Keyword.delete(defaults, group)
|
||||
else
|
||||
Keyword.put(defaults, group, remaining_group_defaults)
|
||||
end
|
||||
|
||||
change = merge_change_with_default(change, default)
|
||||
|
||||
{[change | acc], remaining_defaults}
|
||||
else
|
||||
{reason, false, config} when reason in [:partial_update, :can_be_merged] ->
|
||||
update(config, params)
|
||||
|
||||
nil ->
|
||||
create(params)
|
||||
{[change | acc], defaults}
|
||||
end
|
||||
end
|
||||
|
||||
defp can_be_partially_updated?(%ConfigDB{} = config), do: not only_full_update?(config)
|
||||
@spec from_keyword_to_structs(keyword(), [] | [t()]) :: [t()]
|
||||
def from_keyword_to_structs(keyword, initial_acc \\ []) do
|
||||
Enum.reduce(keyword, initial_acc, &reduce_to_structs/2)
|
||||
end
|
||||
|
||||
defp only_full_update?(%ConfigDB{group: group, key: key}) do
|
||||
full_key_update = [
|
||||
{:pleroma, :ecto_repos},
|
||||
{:quack, :meta},
|
||||
{:mime, :types},
|
||||
{:cors_plug, [:max_age, :methods, :expose, :headers]},
|
||||
{:swarm, :node_blacklist},
|
||||
{:logger, :backends}
|
||||
]
|
||||
defp reduce_to_structs({group, config}, group_acc) when group in @groups_without_keys do
|
||||
[struct(%ConfigDB{}, to_map(group, config)) | group_acc]
|
||||
end
|
||||
|
||||
Enum.any?(full_key_update, fn
|
||||
{s_group, s_key} ->
|
||||
group == s_group and ((is_list(s_key) and key in s_key) or key == s_key)
|
||||
defp reduce_to_structs({group, config}, group_acc) do
|
||||
Enum.reduce(config, group_acc, fn {key, value}, acc ->
|
||||
[struct(%ConfigDB{}, to_map(group, key, value)) | acc]
|
||||
end)
|
||||
end
|
||||
|
||||
@spec delete(ConfigDB.t() | map()) :: {:ok, ConfigDB.t()} | {:error, Changeset.t()}
|
||||
def delete(%ConfigDB{} = config), do: Repo.delete(config)
|
||||
@spec from_keyword_to_maps(keyword(), [] | [map()]) :: [map()]
|
||||
def from_keyword_to_maps(keyword, initial_acc \\ []) do
|
||||
Enum.reduce(keyword, initial_acc, &reduce_to_maps/2)
|
||||
end
|
||||
|
||||
def delete(params) do
|
||||
search_opts = Map.delete(params, :subkeys)
|
||||
defp reduce_to_maps({group, config}, group_acc) when group in @groups_without_keys do
|
||||
[to_map(group, config) | group_acc]
|
||||
end
|
||||
|
||||
with %ConfigDB{} = config <- ConfigDB.get_by_params(search_opts),
|
||||
{config, sub_keys} when is_list(sub_keys) <- {config, params[:subkeys]},
|
||||
keys <- Enum.map(sub_keys, &string_to_elixir_types(&1)),
|
||||
{_, config, new_value} when new_value != [] <-
|
||||
{:partial_remove, config, Keyword.drop(config.value, keys)} do
|
||||
update(config, %{value: new_value})
|
||||
defp reduce_to_maps({group, config}, group_acc) do
|
||||
Enum.reduce(config, group_acc, fn {key, value}, acc ->
|
||||
[to_map(group, key, value) | acc]
|
||||
end)
|
||||
end
|
||||
|
||||
defp to_map(group, config), do: %{group: group, value: config}
|
||||
|
||||
defp to_map(group, key, value), do: %{group: group, key: key, value: value}
|
||||
|
||||
@spec merge_changes_with_defaults([t()], keyword()) :: [t()]
|
||||
def merge_changes_with_defaults(changes, defaults) when is_list(changes) do
|
||||
Enum.map(changes, fn
|
||||
%{group: group} = change when group in @groups_without_keys ->
|
||||
merge_change_with_default(change, defaults[group])
|
||||
|
||||
%{group: group, key: key} = change ->
|
||||
merge_change_with_default(change, defaults[group][key])
|
||||
end)
|
||||
end
|
||||
|
||||
defp merge_change_with_default(change, default) do
|
||||
%{change | value: merge_change_value_with_default(change, default)}
|
||||
end
|
||||
|
||||
@spec merge_change_value_with_default(t(), keyword()) :: keyword()
|
||||
def merge_change_value_with_default(change, default) do
|
||||
if Ecto.get_meta(change, :state) == :deleted do
|
||||
default
|
||||
else
|
||||
{:partial_remove, config, []} ->
|
||||
Repo.delete(config)
|
||||
|
||||
{config, nil} ->
|
||||
Repo.delete(config)
|
||||
|
||||
nil ->
|
||||
err =
|
||||
dgettext("errors", "Config with params %{params} not found", params: inspect(params))
|
||||
|
||||
{:error, err}
|
||||
merge_group(change.group, change.key, default, change.value)
|
||||
end
|
||||
end
|
||||
|
||||
@spec to_json_types(term()) :: map() | list() | boolean() | String.t()
|
||||
def to_json_types(entity) when is_list(entity) do
|
||||
Enum.map(entity, &to_json_types/1)
|
||||
end
|
||||
@spec groups_without_keys() :: [atom()]
|
||||
def groups_without_keys, do: @groups_without_keys
|
||||
|
||||
def to_json_types(%Regex{} = entity), do: inspect(entity)
|
||||
|
||||
def to_json_types(entity) when is_map(entity) do
|
||||
Map.new(entity, fn {k, v} -> {to_json_types(k), to_json_types(v)} end)
|
||||
end
|
||||
|
||||
def to_json_types({:args, args}) when is_list(args) do
|
||||
arguments =
|
||||
Enum.map(args, fn
|
||||
arg when is_tuple(arg) -> inspect(arg)
|
||||
arg -> to_json_types(arg)
|
||||
end)
|
||||
|
||||
%{"tuple" => [":args", arguments]}
|
||||
end
|
||||
|
||||
def to_json_types({:proxy_url, {type, :localhost, port}}) do
|
||||
%{"tuple" => [":proxy_url", %{"tuple" => [to_json_types(type), "localhost", port]}]}
|
||||
end
|
||||
|
||||
def to_json_types({:proxy_url, {type, host, port}}) when is_tuple(host) do
|
||||
ip =
|
||||
host
|
||||
|> :inet_parse.ntoa()
|
||||
|> to_string()
|
||||
|
||||
%{
|
||||
"tuple" => [
|
||||
":proxy_url",
|
||||
%{"tuple" => [to_json_types(type), ip, port]}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
def to_json_types({:proxy_url, {type, host, port}}) do
|
||||
%{
|
||||
"tuple" => [
|
||||
":proxy_url",
|
||||
%{"tuple" => [to_json_types(type), to_string(host), port]}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
def to_json_types({:partial_chain, entity}),
|
||||
do: %{"tuple" => [":partial_chain", inspect(entity)]}
|
||||
|
||||
def to_json_types(entity) when is_tuple(entity) do
|
||||
value =
|
||||
entity
|
||||
|> Tuple.to_list()
|
||||
|> to_json_types()
|
||||
|
||||
%{"tuple" => value}
|
||||
end
|
||||
|
||||
def to_json_types(entity) when is_binary(entity), do: entity
|
||||
|
||||
def to_json_types(entity) when is_boolean(entity) or is_number(entity) or is_nil(entity) do
|
||||
entity
|
||||
end
|
||||
|
||||
def to_json_types(entity) when entity in [:"tlsv1.1", :"tlsv1.2", :"tlsv1.3"] do
|
||||
":#{entity}"
|
||||
end
|
||||
|
||||
def to_json_types(entity) when is_atom(entity), do: inspect(entity)
|
||||
|
||||
@spec to_elixir_types(boolean() | String.t() | map() | list()) :: term()
|
||||
def to_elixir_types(%{"tuple" => [":args", args]}) when is_list(args) do
|
||||
arguments =
|
||||
Enum.map(args, fn arg ->
|
||||
if String.contains?(arg, ["{", "}"]) do
|
||||
{elem, []} = Code.eval_string(arg)
|
||||
elem
|
||||
else
|
||||
to_elixir_types(arg)
|
||||
end
|
||||
end)
|
||||
|
||||
{:args, arguments}
|
||||
end
|
||||
|
||||
def to_elixir_types(%{"tuple" => [":proxy_url", %{"tuple" => [type, host, port]}]}) do
|
||||
{:proxy_url, {string_to_elixir_types(type), parse_host(host), port}}
|
||||
end
|
||||
|
||||
def to_elixir_types(%{"tuple" => [":partial_chain", entity]}) do
|
||||
{partial_chain, []} =
|
||||
entity
|
||||
|> String.replace(~r/[^\w|^{:,[|^,|^[|^\]^}|^\/|^\.|^"]^\s/, "")
|
||||
|> Code.eval_string()
|
||||
|
||||
{:partial_chain, partial_chain}
|
||||
end
|
||||
|
||||
def to_elixir_types(%{"tuple" => entity}) do
|
||||
Enum.reduce(entity, {}, &Tuple.append(&2, to_elixir_types(&1)))
|
||||
end
|
||||
|
||||
def to_elixir_types(entity) when is_map(entity) do
|
||||
Map.new(entity, fn {k, v} -> {to_elixir_types(k), to_elixir_types(v)} end)
|
||||
end
|
||||
|
||||
def to_elixir_types(entity) when is_list(entity) do
|
||||
Enum.map(entity, &to_elixir_types/1)
|
||||
end
|
||||
|
||||
def to_elixir_types(entity) when is_binary(entity) do
|
||||
entity
|
||||
|> String.trim()
|
||||
|> string_to_elixir_types()
|
||||
end
|
||||
|
||||
def to_elixir_types(entity), do: entity
|
||||
|
||||
@spec string_to_elixir_types(String.t()) ::
|
||||
atom() | Regex.t() | module() | String.t() | no_return()
|
||||
def string_to_elixir_types("~r" <> _pattern = regex) do
|
||||
pattern =
|
||||
~r/^~r(?'delimiter'[\/|"'([{<]{1})(?'pattern'.+)[\/|"')\]}>]{1}(?'modifier'[uismxfU]*)/u
|
||||
|
||||
delimiters = ["/", "|", "\"", "'", {"(", ")"}, {"[", "]"}, {"{", "}"}, {"<", ">"}]
|
||||
|
||||
with %{"modifier" => modifier, "pattern" => pattern, "delimiter" => regex_delimiter} <-
|
||||
Regex.named_captures(pattern, regex),
|
||||
{:ok, {leading, closing}} <- find_valid_delimiter(delimiters, pattern, regex_delimiter),
|
||||
{result, _} <- Code.eval_string("~r#{leading}#{pattern}#{closing}#{modifier}") do
|
||||
result
|
||||
end
|
||||
end
|
||||
|
||||
def string_to_elixir_types(":" <> atom), do: String.to_atom(atom)
|
||||
|
||||
def string_to_elixir_types(value) do
|
||||
if module_name?(value) do
|
||||
String.to_existing_atom("Elixir." <> value)
|
||||
else
|
||||
value
|
||||
end
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
@spec module_name?(String.t()) :: boolean()
|
||||
def module_name?(string) do
|
||||
Regex.match?(~r/^(Pleroma|Phoenix|Tesla|Quack|Ueberauth|Swoosh)\./, string) or
|
||||
string in ["Oban", "Ueberauth", "ExSyslogger", "ConcurrentLimiter"]
|
||||
end
|
||||
@spec pleroma_not_keyword_values() :: [atom()]
|
||||
def pleroma_not_keyword_values, do: @pleroma_not_keyword_values
|
||||
end
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
defmodule Pleroma.Docs.JSON do
|
||||
@behaviour Pleroma.Docs.Generator
|
||||
@external_resource "config/description.exs"
|
||||
@raw_config Pleroma.Config.Loader.read("config/description.exs")
|
||||
@raw_config Pleroma.Config.Loader.read!("config/description.exs")
|
||||
@raw_descriptions @raw_config[:pleroma][:config_description]
|
||||
@term __MODULE__.Compiled
|
||||
|
||||
|
|
|
@ -12,13 +12,13 @@ defmodule Pleroma.EctoType.Config.Atom do
|
|||
end
|
||||
|
||||
def cast(key) when is_binary(key) do
|
||||
{:ok, Pleroma.ConfigDB.string_to_elixir_types(key)}
|
||||
{:ok, Pleroma.Config.Converter.string_to_elixir_types!(key)}
|
||||
end
|
||||
|
||||
def cast(_), do: :error
|
||||
|
||||
def load(key) do
|
||||
{:ok, Pleroma.ConfigDB.string_to_elixir_types(key)}
|
||||
{:ok, Pleroma.Config.Converter.string_to_elixir_types!(key)}
|
||||
end
|
||||
|
||||
def dump(key) when is_atom(key), do: {:ok, inspect(key)}
|
||||
|
|
|
@ -15,6 +15,10 @@ defmodule Pleroma.EctoType.Config.BinaryValue do
|
|||
end
|
||||
end
|
||||
|
||||
def cast(value) when is_map(value) or is_list(value) do
|
||||
{:ok, Pleroma.Config.Converter.to_elixir_types(value)}
|
||||
end
|
||||
|
||||
def cast(value), do: {:ok, value}
|
||||
|
||||
def load(value) when is_binary(value) do
|
||||
|
|
|
@ -12,13 +12,14 @@ defmodule Pleroma.Gopher.Server do
|
|||
port = Keyword.get(config, :port, 1234)
|
||||
|
||||
if Keyword.get(config, :enabled, false) do
|
||||
GenServer.start_link(__MODULE__, [ip, port], [])
|
||||
GenServer.start_link(__MODULE__, [ip, port])
|
||||
else
|
||||
Logger.info("Gopher server disabled")
|
||||
:ignore
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def init([ip, port]) do
|
||||
Logger.info("Starting gopher server on #{port}")
|
||||
|
||||
|
@ -31,8 +32,14 @@ defmodule Pleroma.Gopher.Server do
|
|||
[]
|
||||
)
|
||||
|
||||
Process.flag(:trap_exit, true)
|
||||
{:ok, %{ip: ip, port: port}}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def terminate(_reason, _state) do
|
||||
:ranch.stop_listener(:gopher)
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Pleroma.Gopher.Server.ProtocolHandler do
|
||||
|
|
19
lib/pleroma/gun/gun_supervisor.ex
Normal file
19
lib/pleroma/gun/gun_supervisor.ex
Normal file
|
@ -0,0 +1,19 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Gun.GunSupervisor do
|
||||
use Supervisor
|
||||
|
||||
def start_link(_) do
|
||||
Supervisor.start_link(__MODULE__, :no_args)
|
||||
end
|
||||
|
||||
def init(_) do
|
||||
children =
|
||||
Pleroma.Gun.ConnectionPool.children() ++
|
||||
[{Task, &Pleroma.HTTP.AdapterHelper.Gun.limiter_setup/0}]
|
||||
|
||||
Supervisor.init(children, strategy: :one_for_one)
|
||||
end
|
||||
end
|
30
lib/pleroma/http/hackney_supervisor.ex
Normal file
30
lib/pleroma/http/hackney_supervisor.ex
Normal file
|
@ -0,0 +1,30 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.HTTP.HackneySupervisor do
|
||||
use Supervisor
|
||||
|
||||
def start_link(_) do
|
||||
Supervisor.start_link(__MODULE__, :no_arg)
|
||||
end
|
||||
|
||||
def init(_) do
|
||||
pools = [:federation, :media]
|
||||
|
||||
pools =
|
||||
if Pleroma.Config.get([Pleroma.Upload, :proxy_remote]) do
|
||||
[:upload | pools]
|
||||
else
|
||||
pools
|
||||
end
|
||||
|
||||
children =
|
||||
for pool <- pools do
|
||||
options = Pleroma.Config.get([:hackney_pools, pool])
|
||||
:hackney_pool.child_spec(pool, options)
|
||||
end
|
||||
|
||||
Supervisor.init(children, strategy: :one_for_one)
|
||||
end
|
||||
end
|
|
@ -392,14 +392,14 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
|||
|
||||
def restart(conn, _params) do
|
||||
with :ok <- configurable_from_database() do
|
||||
Restarter.Pleroma.restart(Config.get(:env), 50)
|
||||
Task.start(Pleroma.Application.ConfigDependentDeps, :restart_dependencies, [])
|
||||
|
||||
json(conn, %{})
|
||||
end
|
||||
end
|
||||
|
||||
def need_reboot(conn, _params) do
|
||||
json(conn, %{need_reboot: Restarter.Pleroma.need_reboot?()})
|
||||
json(conn, %{need_reboot: Pleroma.Application.ConfigDependentDeps.need_reboot?()})
|
||||
end
|
||||
|
||||
defp configurable_from_database do
|
||||
|
|
|
@ -5,19 +5,24 @@
|
|||
defmodule Pleroma.Web.AdminAPI.ConfigController do
|
||||
use Pleroma.Web, :controller
|
||||
|
||||
import Pleroma.Web.ControllerHelper, only: [json_response: 3]
|
||||
|
||||
alias Pleroma.Application
|
||||
alias Pleroma.Config
|
||||
alias Pleroma.ConfigDB
|
||||
alias Pleroma.Web.Plugs.OAuthScopesPlug
|
||||
|
||||
plug(Pleroma.Web.ApiSpec.CastAndValidate)
|
||||
plug(OAuthScopesPlug, %{scopes: ["admin:write"]} when action == :update)
|
||||
plug(OAuthScopesPlug, %{scopes: ["admin:write"]} when action in [:update, :rollback])
|
||||
|
||||
plug(
|
||||
OAuthScopesPlug,
|
||||
%{scopes: ["admin:read"]}
|
||||
when action in [:show, :descriptions]
|
||||
when action in [:show, :descriptions, :versions]
|
||||
)
|
||||
|
||||
plug(:check_possibility_configuration_from_database when action != :descriptions)
|
||||
|
||||
action_fallback(Pleroma.Web.AdminAPI.FallbackController)
|
||||
|
||||
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.ConfigOperation
|
||||
|
@ -29,100 +34,110 @@ defmodule Pleroma.Web.AdminAPI.ConfigController do
|
|||
end
|
||||
|
||||
def show(conn, %{only_db: true}) do
|
||||
with :ok <- configurable_from_database() do
|
||||
configs = Pleroma.Repo.all(ConfigDB)
|
||||
configs = ConfigDB.all_with_db()
|
||||
|
||||
render(conn, "index.json", %{
|
||||
configs: configs,
|
||||
need_reboot: Restarter.Pleroma.need_reboot?()
|
||||
})
|
||||
end
|
||||
render(conn, "index.json", %{
|
||||
configs: configs,
|
||||
need_reboot: Application.ConfigDependentDeps.need_reboot?()
|
||||
})
|
||||
end
|
||||
|
||||
def show(conn, _params) do
|
||||
with :ok <- configurable_from_database() do
|
||||
configs = ConfigDB.get_all_as_keyword()
|
||||
defaults = Config.Holder.default_config()
|
||||
changes = ConfigDB.all_with_db()
|
||||
|
||||
merged =
|
||||
Config.Holder.default_config()
|
||||
|> ConfigDB.merge(configs)
|
||||
|> Enum.map(fn {group, values} ->
|
||||
Enum.map(values, fn {key, value} ->
|
||||
db =
|
||||
if configs[group][key] do
|
||||
ConfigDB.get_db_keys(configs[group][key], key)
|
||||
end
|
||||
{changes_values_merged_with_defaults, remaining_defaults} =
|
||||
ConfigDB.reduce_defaults_and_merge_with_changes(changes, defaults)
|
||||
|
||||
db_value = configs[group][key]
|
||||
changes_merged_with_defaults =
|
||||
ConfigDB.from_keyword_to_structs(remaining_defaults, changes_values_merged_with_defaults)
|
||||
|
||||
merged_value =
|
||||
if not is_nil(db_value) and Keyword.keyword?(db_value) and
|
||||
ConfigDB.sub_key_full_update?(group, key, Keyword.keys(db_value)) do
|
||||
ConfigDB.merge_group(group, key, value, db_value)
|
||||
else
|
||||
value
|
||||
end
|
||||
|
||||
%ConfigDB{
|
||||
group: group,
|
||||
key: key,
|
||||
value: merged_value
|
||||
}
|
||||
|> Pleroma.Maps.put_if_present(:db, db)
|
||||
end)
|
||||
end)
|
||||
|> List.flatten()
|
||||
|
||||
render(conn, "index.json", %{
|
||||
configs: merged,
|
||||
need_reboot: Restarter.Pleroma.need_reboot?()
|
||||
})
|
||||
end
|
||||
render(conn, "index.json", %{
|
||||
configs: changes_merged_with_defaults,
|
||||
need_reboot: Application.ConfigDependentDeps.need_reboot?()
|
||||
})
|
||||
end
|
||||
|
||||
def update(%{body_params: %{configs: configs}} = conn, _) do
|
||||
with :ok <- configurable_from_database() do
|
||||
results =
|
||||
configs
|
||||
|> Enum.filter(&whitelisted_config?/1)
|
||||
|> Enum.map(fn
|
||||
%{group: group, key: key, delete: true} = params ->
|
||||
ConfigDB.delete(%{group: group, key: key, subkeys: params[:subkeys]})
|
||||
result =
|
||||
configs
|
||||
|> Enum.filter(&whitelisted_config?/1)
|
||||
|> Enum.map(&Config.Converter.to_elixir_types/1)
|
||||
|> Config.Versioning.new_version()
|
||||
|
||||
%{group: group, key: key, value: value} ->
|
||||
ConfigDB.update_or_create(%{group: group, key: key, value: value})
|
||||
end)
|
||||
|> Enum.reject(fn {result, _} -> result == :error end)
|
||||
case result do
|
||||
{:ok, changes} ->
|
||||
inserts_and_deletions =
|
||||
Enum.reduce(changes, [], fn
|
||||
{{operation, _, _}, %ConfigDB{} = change}, acc
|
||||
when operation in [:insert_or_update, :delete_or_update] ->
|
||||
if Ecto.get_meta(change, :state) == :deleted do
|
||||
[change | acc]
|
||||
else
|
||||
if change.group == :pleroma and
|
||||
change.key in ConfigDB.pleroma_not_keyword_values() do
|
||||
[%{change | db: [change.key]} | acc]
|
||||
else
|
||||
[%{change | db: Keyword.keys(change.value)} | acc]
|
||||
end
|
||||
end
|
||||
|
||||
{deleted, updated} =
|
||||
results
|
||||
|> Enum.map(fn {:ok, %{key: key, value: value} = config} ->
|
||||
Map.put(config, :db, ConfigDB.get_db_keys(value, key))
|
||||
end)
|
||||
|> Enum.split_with(&(Ecto.get_meta(&1, :state) == :deleted))
|
||||
_, acc ->
|
||||
acc
|
||||
end)
|
||||
|
||||
Config.TransferTask.load_and_update_env(deleted, false)
|
||||
Application.Environment.update(inserts_and_deletions, only_update: true)
|
||||
|
||||
if not Restarter.Pleroma.need_reboot?() do
|
||||
changed_reboot_settings? =
|
||||
(updated ++ deleted)
|
||||
|> Enum.any?(&Config.TransferTask.pleroma_need_restart?(&1.group, &1.key, &1.value))
|
||||
render(conn, "index.json", %{
|
||||
configs: Enum.reject(inserts_and_deletions, &(Ecto.get_meta(&1, :state) == :deleted)),
|
||||
need_reboot: Application.ConfigDependentDeps.need_reboot?()
|
||||
})
|
||||
|
||||
if changed_reboot_settings?, do: Restarter.Pleroma.need_reboot()
|
||||
end
|
||||
{:error, error} ->
|
||||
{:error, "Updating config failed: #{inspect(error)}"}
|
||||
|
||||
render(conn, "index.json", %{
|
||||
configs: updated,
|
||||
need_reboot: Restarter.Pleroma.need_reboot?()
|
||||
})
|
||||
{:error, _, {error, operation}, _} ->
|
||||
{:error,
|
||||
"Updating config failed: #{inspect(error)}, group: #{operation[:group]}, key: #{
|
||||
operation[:key]
|
||||
}, value: #{inspect(operation[:value])}"}
|
||||
end
|
||||
end
|
||||
|
||||
defp configurable_from_database do
|
||||
def rollback(conn, %{id: id}) do
|
||||
case Config.Versioning.rollback_by_id(id) do
|
||||
{:ok, _} ->
|
||||
json_response(conn, :no_content, "")
|
||||
|
||||
{:error, :not_found} ->
|
||||
{:error, :not_found}
|
||||
|
||||
{:error, error} ->
|
||||
{:error, "Rollback is not possible: #{inspect(error)}"}
|
||||
|
||||
{:error, _, {error, operation}, _} ->
|
||||
{:error,
|
||||
"Rollback is not possible, backup restore error: #{inspect(error)}, operation error: #{
|
||||
inspect(operation)
|
||||
}"}
|
||||
end
|
||||
end
|
||||
|
||||
def versions(conn, _) do
|
||||
versions = Pleroma.Config.Version.all()
|
||||
|
||||
render(conn, "index.json", %{versions: versions})
|
||||
end
|
||||
|
||||
defp check_possibility_configuration_from_database(conn, _) do
|
||||
if Config.get(:configurable_from_database) do
|
||||
:ok
|
||||
conn
|
||||
else
|
||||
{:error, "You must enable configurable_from_database in your config file."}
|
||||
Pleroma.Web.AdminAPI.FallbackController.call(
|
||||
conn,
|
||||
{:error, "You must enable configurable_from_database in your config file."}
|
||||
)
|
||||
|> halt()
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -5,8 +5,6 @@
|
|||
defmodule Pleroma.Web.AdminAPI.ConfigView do
|
||||
use Pleroma.Web, :view
|
||||
|
||||
alias Pleroma.ConfigDB
|
||||
|
||||
def render("index.json", %{configs: configs} = params) do
|
||||
%{
|
||||
configs: render_many(configs, __MODULE__, "show.json", as: :config),
|
||||
|
@ -14,17 +12,23 @@ defmodule Pleroma.Web.AdminAPI.ConfigView do
|
|||
}
|
||||
end
|
||||
|
||||
def render("show.json", %{config: config}) do
|
||||
map = %{
|
||||
key: ConfigDB.to_json_types(config.key),
|
||||
group: ConfigDB.to_json_types(config.group),
|
||||
value: ConfigDB.to_json_types(config.value)
|
||||
def render("index.json", %{versions: versions}) do
|
||||
%{
|
||||
versions: render_many(versions, __MODULE__, "show.json", as: :version)
|
||||
}
|
||||
end
|
||||
|
||||
if config.db != [] do
|
||||
Map.put(map, :db, config.db)
|
||||
else
|
||||
map
|
||||
end
|
||||
def render("show.json", %{config: config}) do
|
||||
config
|
||||
|> Map.take([:group, :key, :value, :db])
|
||||
|> Map.new(fn
|
||||
{k, v} -> {k, Pleroma.Config.Converter.to_json_types(v)}
|
||||
end)
|
||||
end
|
||||
|
||||
def render("show.json", %{version: version}) do
|
||||
version
|
||||
|> Map.take([:id, :current])
|
||||
|> Map.put(:inserted_at, Pleroma.Web.CommonAPI.Utils.to_masto_date(version.inserted_at))
|
||||
end
|
||||
end
|
||||
|
|
|
@ -53,7 +53,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.ConfigOperation do
|
|||
type: :object,
|
||||
properties: %{
|
||||
group: %Schema{type: :string},
|
||||
key: %Schema{type: :string},
|
||||
key: %Schema{type: :string, nullable: true},
|
||||
value: any(),
|
||||
delete: %Schema{type: :boolean},
|
||||
subkeys: %Schema{type: :array, items: %Schema{type: :string}}
|
||||
|
@ -107,6 +107,56 @@ defmodule Pleroma.Web.ApiSpec.Admin.ConfigOperation do
|
|||
}
|
||||
end
|
||||
|
||||
def rollback_operation do
|
||||
%Operation{
|
||||
tags: ["Admin", "Config"],
|
||||
summary: "Rollback config changes.",
|
||||
operationId: "AdminAPI.ConfigController.rollback",
|
||||
security: [%{"oAuth" => ["write"]}],
|
||||
parameters: [
|
||||
Operation.parameter(:id, :path, %Schema{type: :integer}, "Version id to rollback",
|
||||
required: true
|
||||
)
|
||||
| admin_api_params()
|
||||
],
|
||||
responses: %{
|
||||
204 => no_content_response(),
|
||||
400 => Operation.response("Bad Request", "application/json", ApiError),
|
||||
404 => Operation.response("Not Found", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def versions_operation do
|
||||
%Operation{
|
||||
tags: ["Admin", "Config"],
|
||||
summary: "Get list with config versions.",
|
||||
operationId: "AdminAPI.ConfigController.versions",
|
||||
security: [%{"oAuth" => ["read"]}],
|
||||
parameters: admin_api_params(),
|
||||
responses: %{
|
||||
200 =>
|
||||
Operation.response("Config Version", "application/json", %Schema{
|
||||
type: :object,
|
||||
properties: %{
|
||||
versions: %Schema{
|
||||
type: :array,
|
||||
items: %Schema{
|
||||
type: :object,
|
||||
properties: %{
|
||||
id: %Schema{type: :integer},
|
||||
current: %Schema{type: :boolean},
|
||||
inserted_at: %Schema{type: :string, format: :"date-time"}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
400 => Operation.response("Bad Request", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
defp any do
|
||||
%Schema{
|
||||
oneOf: [
|
||||
|
@ -129,7 +179,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.ConfigOperation do
|
|||
type: :object,
|
||||
properties: %{
|
||||
group: %Schema{type: :string},
|
||||
key: %Schema{type: :string},
|
||||
key: %Schema{type: :string, nullable: true},
|
||||
value: any()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -232,6 +232,8 @@ defmodule Pleroma.Web.Router do
|
|||
get("/config", ConfigController, :show)
|
||||
post("/config", ConfigController, :update)
|
||||
get("/config/descriptions", ConfigController, :descriptions)
|
||||
get("/config/versions", ConfigController, :versions)
|
||||
get("/config/versions/rollback/:id", ConfigController, :rollback)
|
||||
get("/need_reboot", AdminAPIController, :need_reboot)
|
||||
get("/restart", AdminAPIController, :restart)
|
||||
|
||||
|
|
6
mix.exs
6
mix.exs
|
@ -77,9 +77,8 @@ defmodule Pleroma.Mixfile do
|
|||
:logger,
|
||||
:runtime_tools,
|
||||
:comeonin,
|
||||
:quack,
|
||||
:fast_sanitize,
|
||||
:ssl
|
||||
:ssl,
|
||||
:fast_sanitize
|
||||
],
|
||||
included_applications: [:ex_syslogger]
|
||||
]
|
||||
|
@ -192,7 +191,6 @@ defmodule Pleroma.Mixfile do
|
|||
{:captcha,
|
||||
git: "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git",
|
||||
ref: "e0f16822d578866e186a0974d65ad58cddc1e2ab"},
|
||||
{:restarter, path: "./restarter"},
|
||||
{:majic,
|
||||
git: "https://git.pleroma.social/pleroma/elixir-libraries/majic.git",
|
||||
ref: "289cda1b6d0d70ccb2ba508a2b0bd24638db2880"},
|
||||
|
|
|
@ -15,7 +15,7 @@ defmodule Pleroma.Repo.Migrations.AutolinkerToLinkify do
|
|||
|
||||
defp move_config(%{} = old, %{} = new) do
|
||||
{:ok, _} = ConfigDB.update_or_create(new)
|
||||
{:ok, _} = ConfigDB.delete(old)
|
||||
{:ok, _} = ConfigDB.delete_or_update(old)
|
||||
:ok
|
||||
end
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ defmodule Pleroma.Repo.Migrations.MoveActivityExpirationsToOban do
|
|||
import Ecto.Query, only: [from: 2]
|
||||
|
||||
def change do
|
||||
Pleroma.Config.Oban.warn()
|
||||
Pleroma.Config.DeprecationWarnings.check_oban_config()
|
||||
|
||||
Application.ensure_all_started(:oban)
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ defmodule Pleroma.Repo.Migrations.MoveTokensExpirationIntoOban do
|
|||
import Ecto.Query, only: [from: 2]
|
||||
|
||||
def change do
|
||||
Pleroma.Config.Oban.warn()
|
||||
Pleroma.Config.DeprecationWarnings.check_oban_config()
|
||||
|
||||
Application.ensure_all_started(:oban)
|
||||
|
||||
|
|
14
priv/repo/migrations/20201007131955_add_config_version.exs
Normal file
14
priv/repo/migrations/20201007131955_add_config_version.exs
Normal file
|
@ -0,0 +1,14 @@
|
|||
defmodule Pleroma.Repo.Migrations.AddConfigVersion do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
create_if_not_exists table(:config_versions) do
|
||||
add(:backup, :binary)
|
||||
add(:current, :boolean)
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
||||
create_if_not_exists(unique_index(:config_versions, [:current], where: "current = true"))
|
||||
end
|
||||
end
|
49
priv/repo/migrations/20201007154420_change_key_in_config.exs
Normal file
49
priv/repo/migrations/20201007154420_change_key_in_config.exs
Normal file
|
@ -0,0 +1,49 @@
|
|||
defmodule Pleroma.Repo.Migrations.ChangeKeyInConfig do
|
||||
use Ecto.Migration
|
||||
|
||||
import Ecto.Query
|
||||
|
||||
alias Pleroma.Repo
|
||||
|
||||
def up do
|
||||
alter table(:config) do
|
||||
modify(:key, :string, null: true)
|
||||
end
|
||||
|
||||
create_if_not_exists(unique_index(:config, [:group, "(key is null)"], where: "key IS NULL"))
|
||||
end
|
||||
|
||||
def down do
|
||||
query = from(c in "config", where: is_nil(c.key))
|
||||
|
||||
if Repo.aggregate(query, :count) == 0 do
|
||||
revert()
|
||||
else
|
||||
configs = Repo.all(query)
|
||||
|
||||
new_configs =
|
||||
Enum.reduce(configs, [], fn %{group: group, value: config}, group_acc ->
|
||||
Enum.reduce(config, group_acc, fn {key, value}, acc ->
|
||||
[%{group: group, key: key, value: value} | acc]
|
||||
end)
|
||||
end)
|
||||
|
||||
Enum.each(new_configs, fn config ->
|
||||
{:ok, _} = Pleroma.ConfigDB.update_or_create(config)
|
||||
end)
|
||||
|
||||
Enum.each(configs, &Repo.delete!(&1))
|
||||
|
||||
flush()
|
||||
revert()
|
||||
end
|
||||
end
|
||||
|
||||
defp revert do
|
||||
alter table(:config) do
|
||||
modify(:key, :string, null: false)
|
||||
end
|
||||
|
||||
drop_if_exists(unique_index(:config, [:group, "(key is null)"]))
|
||||
end
|
||||
end
|
|
@ -0,0 +1,29 @@
|
|||
defmodule Pleroma.Repo.Migrations.CombineSettingsWithoutKey do
|
||||
use Ecto.Migration
|
||||
|
||||
import Ecto.Query, only: [from: 2]
|
||||
|
||||
alias Pleroma.ConfigDB
|
||||
alias Pleroma.Repo
|
||||
|
||||
def change do
|
||||
groups = ConfigDB.groups_without_keys()
|
||||
|
||||
configs =
|
||||
from(c in ConfigDB, where: c.group in ^groups)
|
||||
|> Repo.all()
|
||||
|
||||
new_configs =
|
||||
configs
|
||||
|> Enum.reduce([], fn %{group: group, key: key, value: value}, acc ->
|
||||
Keyword.update(acc, group, [{key, value}], &Keyword.merge(&1, [{key, value}]))
|
||||
end)
|
||||
|> ConfigDB.from_keyword_to_maps()
|
||||
|
||||
Enum.each(new_configs, fn config ->
|
||||
{:ok, _} = ConfigDB.update_or_create(config)
|
||||
end)
|
||||
|
||||
Enum.each(configs, &Repo.delete!(&1))
|
||||
end
|
||||
end
|
|
@ -0,0 +1,15 @@
|
|||
defmodule Pleroma.Repo.Migrations.CreateBaseConfigVersion do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
configs = Pleroma.ConfigDB.all_as_keyword()
|
||||
|
||||
unless configs == [] do
|
||||
%Pleroma.Config.Version{
|
||||
backup: configs,
|
||||
current: true
|
||||
}
|
||||
|> Pleroma.Repo.insert!()
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,94 +0,0 @@
|
|||
defmodule Restarter.Pleroma do
|
||||
use GenServer
|
||||
|
||||
require Logger
|
||||
|
||||
@init_state %{need_reboot: false, rebooted: false, after_boot: false}
|
||||
|
||||
def start_link(_) do
|
||||
GenServer.start_link(__MODULE__, [], name: __MODULE__)
|
||||
end
|
||||
|
||||
def init(_), do: {:ok, @init_state}
|
||||
|
||||
def rebooted? do
|
||||
GenServer.call(__MODULE__, :rebooted?)
|
||||
end
|
||||
|
||||
def rebooted do
|
||||
GenServer.cast(__MODULE__, :rebooted)
|
||||
end
|
||||
|
||||
def need_reboot? do
|
||||
GenServer.call(__MODULE__, :need_reboot?)
|
||||
end
|
||||
|
||||
def need_reboot do
|
||||
GenServer.cast(__MODULE__, :need_reboot)
|
||||
end
|
||||
|
||||
def refresh do
|
||||
GenServer.cast(__MODULE__, :refresh)
|
||||
end
|
||||
|
||||
def restart(env, delay) do
|
||||
GenServer.cast(__MODULE__, {:restart, env, delay})
|
||||
end
|
||||
|
||||
def restart_after_boot(env) do
|
||||
GenServer.cast(__MODULE__, {:after_boot, env})
|
||||
end
|
||||
|
||||
def handle_call(:rebooted?, _from, state) do
|
||||
{:reply, state[:rebooted], state}
|
||||
end
|
||||
|
||||
def handle_call(:need_reboot?, _from, state) do
|
||||
{:reply, state[:need_reboot], state}
|
||||
end
|
||||
|
||||
def handle_cast(:rebooted, state) do
|
||||
{:noreply, Map.put(state, :rebooted, true)}
|
||||
end
|
||||
|
||||
def handle_cast(:need_reboot, %{need_reboot: true} = state), do: {:noreply, state}
|
||||
|
||||
def handle_cast(:need_reboot, state) do
|
||||
{:noreply, Map.put(state, :need_reboot, true)}
|
||||
end
|
||||
|
||||
def handle_cast(:refresh, _state) do
|
||||
{:noreply, @init_state}
|
||||
end
|
||||
|
||||
def handle_cast({:restart, :test, _}, state) do
|
||||
Logger.debug("pleroma manually restarted")
|
||||
{:noreply, Map.put(state, :need_reboot, false)}
|
||||
end
|
||||
|
||||
def handle_cast({:restart, _, delay}, state) do
|
||||
Process.sleep(delay)
|
||||
do_restart(:pleroma)
|
||||
{:noreply, Map.put(state, :need_reboot, false)}
|
||||
end
|
||||
|
||||
def handle_cast({:after_boot, _}, %{after_boot: true} = state), do: {:noreply, state}
|
||||
|
||||
def handle_cast({:after_boot, :test}, state) do
|
||||
Logger.debug("pleroma restarted after boot")
|
||||
state = %{state | after_boot: true, rebooted: true}
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_cast({:after_boot, _}, state) do
|
||||
do_restart(:pleroma)
|
||||
state = %{state | after_boot: true, rebooted: true}
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
defp do_restart(app) do
|
||||
:ok = Application.ensure_started(app)
|
||||
:ok = Application.stop(app)
|
||||
:ok = Application.start(app)
|
||||
end
|
||||
end
|
|
@ -1,8 +0,0 @@
|
|||
defmodule Restarter do
|
||||
use Application
|
||||
|
||||
def start(_, _) do
|
||||
opts = [strategy: :one_for_one, name: Restarter.Supervisor]
|
||||
Supervisor.start_link([Restarter.Pleroma], opts)
|
||||
end
|
||||
end
|
|
@ -1,21 +0,0 @@
|
|||
defmodule Restarter.MixProject do
|
||||
use Mix.Project
|
||||
|
||||
def project do
|
||||
[
|
||||
app: :restarter,
|
||||
version: "0.1.0",
|
||||
elixir: "~> 1.8",
|
||||
start_permanent: Mix.env() == :prod,
|
||||
deps: deps()
|
||||
]
|
||||
end
|
||||
|
||||
def application do
|
||||
[
|
||||
mod: {Restarter, []}
|
||||
]
|
||||
end
|
||||
|
||||
defp deps, do: []
|
||||
end
|
24
test/fixtures/config/temp.secret.exs
vendored
24
test/fixtures/config/temp.secret.exs
vendored
|
@ -12,6 +12,28 @@ config :quack, level: :info
|
|||
|
||||
config :pleroma, Pleroma.Repo, pool: Ecto.Adapters.SQL.Sandbox
|
||||
|
||||
config :postgrex, :json_library, Poison
|
||||
config :pleroma, Pleroma.Web.Endpoint, key: :val
|
||||
|
||||
config :pleroma, env: :test
|
||||
|
||||
config :pleroma, :database, rum_enabled: true
|
||||
|
||||
config :pleroma, configurable_from_database: false
|
||||
|
||||
config :pleroma, ecto_repos: [Pleroma.Repo]
|
||||
|
||||
config :pleroma, Pleroma.Gun, Pleroma.GunMock
|
||||
|
||||
config :pleroma, Pleroma.ReverseProxy.Client, Pleroma.ReverseProxy.Client
|
||||
|
||||
config :postgrex, :json_library, Poison
|
||||
|
||||
config :tesla, adapter: Tesla.Mock
|
||||
|
||||
config :tzdata, http_client: Pleroma.HTTP
|
||||
|
||||
config :http_signatures, key: :val
|
||||
|
||||
config :web_push_encryption, key: :val
|
||||
|
||||
config :floki, key: :val
|
||||
|
|
|
@ -29,24 +29,56 @@ defmodule Mix.Tasks.Pleroma.ConfigTest do
|
|||
|> Enum.sort()
|
||||
end
|
||||
|
||||
defp insert_config_record(group, key, value) do
|
||||
defp insert_config_record(group \\ nil, key, value) do
|
||||
insert(:config,
|
||||
group: group,
|
||||
group: group || :pleroma,
|
||||
key: key,
|
||||
value: value
|
||||
)
|
||||
end
|
||||
|
||||
test "error if file with custom settings doesn't exist" do
|
||||
MixTask.migrate_to_db("config/non_existent_config_file.exs")
|
||||
MixTask.run([
|
||||
"migrate_to_db",
|
||||
"--config",
|
||||
"config/not_existance_config_file.exs"
|
||||
])
|
||||
|
||||
msg =
|
||||
"To migrate settings, you must define custom settings in config/non_existent_config_file.exs."
|
||||
"ConfigDB not enabled. Please check the value of :configurable_from_database in your configuration."
|
||||
|
||||
assert_receive {:mix_shell, :info, [^msg]}, 15
|
||||
assert_receive {:mix_shell, :error, [^msg]}, 15
|
||||
end
|
||||
|
||||
describe "migrate_to_db/1" do
|
||||
test "migrate_to_db error if configurable_from_database is not enabled" do
|
||||
clear_config(:configurable_from_database, false)
|
||||
|
||||
MixTask.run([
|
||||
"migrate_to_db",
|
||||
"--config",
|
||||
"test/fixtures/config/temp.secret.exs"
|
||||
])
|
||||
|
||||
assert_received {:mix_shell, :error, [message]}
|
||||
|
||||
assert message =~
|
||||
"ConfigDB not enabled. Please check the value of :configurable_from_database in your configuration."
|
||||
end
|
||||
|
||||
test "migrate_from_db error if configurable_from_database is not enabled" do
|
||||
clear_config(:configurable_from_database, false)
|
||||
|
||||
MixTask.run([
|
||||
"migrate_from_db"
|
||||
])
|
||||
|
||||
assert_received {:mix_shell, :error, [message]}
|
||||
|
||||
assert message =~
|
||||
"ConfigDB not enabled. Please check the value of :configurable_from_database in your configuration."
|
||||
end
|
||||
|
||||
describe "migrate_to_db task" do
|
||||
setup do
|
||||
clear_config(:configurable_from_database, true)
|
||||
clear_config([:quack, :level])
|
||||
|
@ -57,7 +89,11 @@ defmodule Mix.Tasks.Pleroma.ConfigTest do
|
|||
clear_config([:media_proxy, :whitelist], ["domain_without_scheme.com"])
|
||||
assert config_records() == []
|
||||
|
||||
MixTask.migrate_to_db("test/fixtures/config/temp.secret.exs")
|
||||
MixTask.run([
|
||||
"migrate_to_db",
|
||||
"--config",
|
||||
"test/fixtures/config/temp.secret.exs"
|
||||
])
|
||||
|
||||
assert_received {:mix_shell, :error, [message]}
|
||||
|
||||
|
@ -68,25 +104,45 @@ defmodule Mix.Tasks.Pleroma.ConfigTest do
|
|||
test "filtered settings are migrated to db" do
|
||||
assert config_records() == []
|
||||
|
||||
MixTask.migrate_to_db("test/fixtures/config/temp.secret.exs")
|
||||
MixTask.run([
|
||||
"migrate_to_db",
|
||||
"--config",
|
||||
"test/fixtures/config/temp.secret.exs"
|
||||
])
|
||||
|
||||
config1 = ConfigDB.get_by_params(%{group: ":pleroma", key: ":first_setting"})
|
||||
config2 = ConfigDB.get_by_params(%{group: ":pleroma", key: ":second_setting"})
|
||||
config3 = ConfigDB.get_by_params(%{group: ":quack", key: ":level"})
|
||||
refute ConfigDB.get_by_params(%{group: ":pleroma", key: "Pleroma.Repo"})
|
||||
refute ConfigDB.get_by_params(%{group: ":postgrex", key: ":json_library"})
|
||||
refute ConfigDB.get_by_params(%{group: ":pleroma", key: ":database"})
|
||||
config1 = ConfigDB.get_by_params(%{group: :pleroma, key: :first_setting})
|
||||
config2 = ConfigDB.get_by_params(%{group: :pleroma, key: :second_setting})
|
||||
config3 = ConfigDB.get_by_params(%{group: :quack})
|
||||
refute ConfigDB.get_by_params(%{group: :pleroma, key: Pleroma.Repo})
|
||||
refute ConfigDB.get_by_params(%{group: :postgrex, key: :json_library})
|
||||
refute ConfigDB.get_by_params(%{group: :pleroma, key: :database})
|
||||
|
||||
assert config1.value == [key: "value", key2: [Repo]]
|
||||
assert config2.value == [key: "value2", key2: ["Activity"]]
|
||||
assert config3.value == :info
|
||||
assert config3.value == [level: :info]
|
||||
|
||||
assert Repo.aggregate(ConfigDB, :count) == 3
|
||||
|
||||
[version] = Repo.all(Pleroma.Config.Version)
|
||||
|
||||
assert version.backup == [
|
||||
pleroma: [
|
||||
second_setting: [key: "value2", key2: ["Activity"]],
|
||||
first_setting: [key: "value", key2: [Pleroma.Repo]]
|
||||
],
|
||||
quack: [level: :info]
|
||||
]
|
||||
end
|
||||
|
||||
test "config table is truncated before migration" do
|
||||
insert_config_record(:pleroma, :first_setting, key: "value", key2: ["Activity"])
|
||||
insert_config_record(:first_setting, key: "value", key2: ["Activity"])
|
||||
assert length(config_records()) == 1
|
||||
|
||||
MixTask.migrate_to_db("test/fixtures/config/temp.secret.exs")
|
||||
MixTask.run([
|
||||
"migrate_to_db",
|
||||
"--config",
|
||||
"test/fixtures/config/temp.secret.exs"
|
||||
])
|
||||
|
||||
config = ConfigDB.get_by_params(%{group: ":pleroma", key: ":first_setting"})
|
||||
assert config.value == [key: "value", key2: [Repo]]
|
||||
|
@ -106,9 +162,9 @@ defmodule Mix.Tasks.Pleroma.ConfigTest do
|
|||
end
|
||||
|
||||
test "settings are migrated to file and deleted from db", %{temp_file: temp_file} do
|
||||
insert_config_record(:pleroma, :setting_first, key: "value", key2: ["Activity"])
|
||||
insert_config_record(:pleroma, :setting_second, key: "value2", key2: [Repo])
|
||||
insert_config_record(:quack, :level, :info)
|
||||
insert_config_record(:setting_first, key: "value", key2: ["Activity"])
|
||||
insert_config_record(:setting_second, key: "value2", key2: [Repo])
|
||||
insert_config_record(:quack, nil, level: :info)
|
||||
|
||||
MixTask.run(["migrate_from_db", "--env", "temp", "-d"])
|
||||
|
||||
|
@ -117,7 +173,20 @@ defmodule Mix.Tasks.Pleroma.ConfigTest do
|
|||
file = File.read!(temp_file)
|
||||
assert file =~ "config :pleroma, :setting_first,"
|
||||
assert file =~ "config :pleroma, :setting_second,"
|
||||
assert file =~ "config :quack, :level, :info"
|
||||
assert file =~ "config :quack, level: :info"
|
||||
end
|
||||
|
||||
test "migrate_from_db with config path in env", %{temp_file: temp_file} do
|
||||
clear_config(:release, true)
|
||||
clear_config(:config_path, "config/temp.exported_from_db.secret.exs")
|
||||
|
||||
insert_config_record(:setting_first, key: "value", key2: ["Activity"])
|
||||
|
||||
MixTask.run(["migrate_from_db", "--env", "temp", "-d"])
|
||||
|
||||
assert Repo.all(ConfigDB) == []
|
||||
file = File.read!(temp_file)
|
||||
assert file =~ "config :pleroma, :setting_first,"
|
||||
end
|
||||
|
||||
test "load a settings with large values and pass to file", %{temp_file: temp_file} do
|
||||
|
@ -206,7 +275,7 @@ defmodule Mix.Tasks.Pleroma.ConfigTest do
|
|||
setup do
|
||||
insert_config_record(:pleroma, :setting_first, key: "value", key2: ["Activity"])
|
||||
insert_config_record(:pleroma, :setting_second, key: "value2", key2: [Repo])
|
||||
insert_config_record(:quack, :level, :info)
|
||||
insert_config_record(:quack, nil, level: :info)
|
||||
|
||||
path = "test/instance_static"
|
||||
file_path = Path.join(path, "temp.exported_from_db.secret.exs")
|
||||
|
@ -222,7 +291,7 @@ defmodule Mix.Tasks.Pleroma.ConfigTest do
|
|||
file = File.read!(file_path)
|
||||
assert file =~ "config :pleroma, :setting_first,"
|
||||
assert file =~ "config :pleroma, :setting_second,"
|
||||
assert file =~ "config :quack, :level, :info"
|
||||
assert file =~ "config :quack, level: :info"
|
||||
end
|
||||
|
||||
test "release", %{file_path: file_path} do
|
||||
|
@ -234,7 +303,7 @@ defmodule Mix.Tasks.Pleroma.ConfigTest do
|
|||
file = File.read!(file_path)
|
||||
assert file =~ "config :pleroma, :setting_first,"
|
||||
assert file =~ "config :pleroma, :setting_second,"
|
||||
assert file =~ "config :quack, :level, :info"
|
||||
assert file =~ "config :quack, level: :info"
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -242,7 +311,7 @@ defmodule Mix.Tasks.Pleroma.ConfigTest do
|
|||
setup do: clear_config(:configurable_from_database, true)
|
||||
|
||||
test "dumping a specific group" do
|
||||
insert_config_record(:pleroma, :instance, name: "Pleroma Test")
|
||||
insert_config_record(:instance, name: "Pleroma Test")
|
||||
|
||||
insert_config_record(:web_push_encryption, :vapid_details,
|
||||
subject: "mailto:administrator@example.com",
|
||||
|
@ -272,8 +341,8 @@ defmodule Mix.Tasks.Pleroma.ConfigTest do
|
|||
end
|
||||
|
||||
test "dumping a specific key in a group" do
|
||||
insert_config_record(:pleroma, :instance, name: "Pleroma Test")
|
||||
insert_config_record(:pleroma, Pleroma.Captcha, enabled: false)
|
||||
insert_config_record(:instance, name: "Pleroma Test")
|
||||
insert_config_record(Pleroma.Captcha, enabled: false)
|
||||
|
||||
MixTask.run(["dump", "pleroma", "Pleroma.Captcha"])
|
||||
|
||||
|
@ -285,8 +354,8 @@ defmodule Mix.Tasks.Pleroma.ConfigTest do
|
|||
end
|
||||
|
||||
test "dumps all configuration successfully" do
|
||||
insert_config_record(:pleroma, :instance, name: "Pleroma Test")
|
||||
insert_config_record(:pleroma, Pleroma.Captcha, enabled: false)
|
||||
insert_config_record(:instance, name: "Pleroma Test")
|
||||
insert_config_record(Pleroma.Captcha, enabled: false)
|
||||
|
||||
MixTask.run(["dump"])
|
||||
|
||||
|
@ -302,7 +371,7 @@ defmodule Mix.Tasks.Pleroma.ConfigTest do
|
|||
test "refuses to dump" do
|
||||
clear_config(:configurable_from_database, false)
|
||||
|
||||
insert_config_record(:pleroma, :instance, name: "Pleroma Test")
|
||||
insert_config_record(:instance, name: "Pleroma Test")
|
||||
|
||||
MixTask.run(["dump"])
|
||||
|
||||
|
@ -317,8 +386,8 @@ defmodule Mix.Tasks.Pleroma.ConfigTest do
|
|||
setup do: clear_config(:configurable_from_database, true)
|
||||
|
||||
setup do
|
||||
insert_config_record(:pleroma, :instance, name: "Pleroma Test")
|
||||
insert_config_record(:pleroma, Pleroma.Captcha, enabled: false)
|
||||
insert_config_record(:instance, name: "Pleroma Test")
|
||||
insert_config_record(Pleroma.Captcha, enabled: false)
|
||||
insert_config_record(:pleroma2, :key2, z: 1)
|
||||
|
||||
assert length(config_records()) == 3
|
||||
|
@ -347,4 +416,55 @@ defmodule Mix.Tasks.Pleroma.ConfigTest do
|
|||
assert config_records() == []
|
||||
end
|
||||
end
|
||||
|
||||
describe "rollback/1" do
|
||||
setup do: clear_config(:configurable_from_database, true)
|
||||
|
||||
test "configuration from database is not configured" do
|
||||
clear_config(:configurable_from_database, false)
|
||||
|
||||
MixTask.run(["rollback"])
|
||||
|
||||
assert_received {:mix_shell, :error, [message]}
|
||||
|
||||
assert message =~
|
||||
"ConfigDB not enabled. Please check the value of :configurable_from_database in your configuration."
|
||||
end
|
||||
|
||||
test "error" do
|
||||
MixTask.run(["rollback"])
|
||||
|
||||
assert_receive {:mix_shell, :error,
|
||||
[
|
||||
"No version to rollback"
|
||||
]},
|
||||
15
|
||||
end
|
||||
|
||||
test "success rollback" do
|
||||
insert(:config_version,
|
||||
backup: [pleroma: [instance: [name: "First name", email: "email@example.com"]]]
|
||||
)
|
||||
|
||||
insert(:config_version, current: true)
|
||||
|
||||
MixTask.run(["rollback"])
|
||||
|
||||
assert_received {:mix_shell, :info, ["Success rollback"]}
|
||||
|
||||
[config] = Repo.all(ConfigDB)
|
||||
|
||||
assert config.value == [name: "First name", email: "email@example.com"]
|
||||
end
|
||||
|
||||
test "rollback not possible error" do
|
||||
insert(:config_version, current: true)
|
||||
|
||||
MixTask.run(["rollback", "-s", "2"])
|
||||
|
||||
assert_received {:mix_shell, :error, [message]}
|
||||
|
||||
assert message =~ "Rollback not possible. Incorrect steps value."
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
149
test/pleroma/application/config_dependent_deps_test.exs
Normal file
149
test/pleroma/application/config_dependent_deps_test.exs
Normal file
|
@ -0,0 +1,149 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Application.ConfigDependentDepsTest do
|
||||
use ExUnit.Case
|
||||
|
||||
alias Pleroma.Application.ConfigDependentDeps
|
||||
|
||||
setup do
|
||||
{:ok, _} =
|
||||
DynamicSupervisor.start_link(
|
||||
strategy: :one_for_one,
|
||||
name: Pleroma.Application.DynamicSupervisorTest
|
||||
)
|
||||
|
||||
{:ok, pid} =
|
||||
Pleroma.Application.ConfigDependentDeps.start_link(
|
||||
dynamic_supervisor: Pleroma.Application.DynamicSupervisorTest,
|
||||
name: Pleroma.Application.ConfigDependentDepsTesting,
|
||||
relations: [
|
||||
{{:pleroma, :dummy_module1}, Pleroma.DummyModule1},
|
||||
{{:pleroma, :dummy_module2}, Pleroma.DummyModule2},
|
||||
{:dummy_group1, :dummy_group1},
|
||||
{:ex_aws, :ex_aws},
|
||||
{:not_started_app, :not_started_app}
|
||||
]
|
||||
)
|
||||
|
||||
[pid: pid]
|
||||
end
|
||||
|
||||
test "start_dependency/2", %{pid: pid} do
|
||||
{:ok, pid} = ConfigDependentDeps.start_dependency(Pleroma.DummyModule1, pid)
|
||||
assert Process.alive?(pid)
|
||||
end
|
||||
|
||||
describe "need_reboot?/1" do
|
||||
test "apps and paths", %{pid: pid} do
|
||||
changes = [
|
||||
%Pleroma.ConfigDB{group: :dummy_group1},
|
||||
%Pleroma.ConfigDB{group: :pleroma, key: :dummy_module1}
|
||||
]
|
||||
|
||||
assert ConfigDependentDeps.save_config_paths_for_restart(changes, pid) == [
|
||||
{:pleroma, :dummy_module1},
|
||||
:dummy_group1
|
||||
]
|
||||
|
||||
assert ConfigDependentDeps.need_reboot?(pid)
|
||||
end
|
||||
|
||||
test "app and path are not duplicated", %{pid: pid} do
|
||||
changes = [
|
||||
%Pleroma.ConfigDB{group: :dummy_group1},
|
||||
%Pleroma.ConfigDB{group: :dummy_group1},
|
||||
%Pleroma.ConfigDB{group: :pleroma, key: :dummy_module1},
|
||||
%Pleroma.ConfigDB{group: :pleroma, key: :dummy_module1}
|
||||
]
|
||||
|
||||
assert ConfigDependentDeps.save_config_paths_for_restart(changes, pid) == [
|
||||
{:pleroma, :dummy_module1},
|
||||
:dummy_group1
|
||||
]
|
||||
|
||||
assert ConfigDependentDeps.need_reboot?(pid)
|
||||
end
|
||||
end
|
||||
|
||||
describe "restart_dependencies/1" do
|
||||
test "started dependency", %{pid: pid} do
|
||||
{:ok, dummy_pid} = ConfigDependentDeps.start_dependency(Pleroma.DummyModule1, pid)
|
||||
|
||||
changes = [
|
||||
%Pleroma.ConfigDB{group: :ex_aws},
|
||||
%Pleroma.ConfigDB{group: :pleroma, key: :dummy_module1}
|
||||
]
|
||||
|
||||
assert ConfigDependentDeps.save_config_paths_for_restart(changes, pid) == [
|
||||
{:pleroma, :dummy_module1},
|
||||
:ex_aws
|
||||
]
|
||||
|
||||
assert :ok == ConfigDependentDeps.restart_dependencies(pid)
|
||||
|
||||
restarted = Process.whereis(Pleroma.DummyModule1)
|
||||
|
||||
refute dummy_pid == restarted
|
||||
end
|
||||
|
||||
test "not started process and app", %{pid: pid} do
|
||||
changes = [
|
||||
%Pleroma.ConfigDB{group: :pleroma, key: :dummy_module1},
|
||||
%Pleroma.ConfigDB{group: :not_started_app}
|
||||
]
|
||||
|
||||
assert ConfigDependentDeps.save_config_paths_for_restart(changes, pid) == [
|
||||
:not_started_app,
|
||||
{:pleroma, :dummy_module1}
|
||||
]
|
||||
|
||||
assert :ok == ConfigDependentDeps.restart_dependencies(pid)
|
||||
|
||||
started = Process.whereis(Pleroma.DummyModule1)
|
||||
|
||||
assert Process.alive?(started)
|
||||
end
|
||||
|
||||
test "ignored dependency", %{pid: pid} do
|
||||
changes = [
|
||||
%Pleroma.ConfigDB{group: :pleroma, key: :dummy_module2}
|
||||
]
|
||||
|
||||
assert ConfigDependentDeps.save_config_paths_for_restart(changes, pid) == [
|
||||
{:pleroma, :dummy_module2}
|
||||
]
|
||||
|
||||
assert :ok == ConfigDependentDeps.restart_dependencies(pid)
|
||||
|
||||
refute Process.whereis(Pleroma.DummyModule2)
|
||||
end
|
||||
end
|
||||
|
||||
test "process goes down", %{pid: pid} do
|
||||
{:ok, dummy_pid} = ConfigDependentDeps.start_dependency(Pleroma.DummyModule1, pid)
|
||||
|
||||
Process.exit(dummy_pid, :kill)
|
||||
|
||||
Process.sleep(10)
|
||||
restarted = Process.whereis(Pleroma.DummyModule1)
|
||||
refute restarted == dummy_pid
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Pleroma.DummyModule1 do
|
||||
use Agent
|
||||
|
||||
def start_link(_) do
|
||||
Agent.start_link(fn -> nil end, name: __MODULE__)
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Pleroma.DummyModule2 do
|
||||
use Agent
|
||||
|
||||
def start_link(_) do
|
||||
:ignore
|
||||
end
|
||||
end
|
248
test/pleroma/application/environment_test.exs
Normal file
248
test/pleroma/application/environment_test.exs
Normal file
|
@ -0,0 +1,248 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Application.EnvironmentTest do
|
||||
use Pleroma.DataCase
|
||||
|
||||
import Pleroma.Factory
|
||||
|
||||
alias Pleroma.Application.Environment
|
||||
|
||||
setup do: clear_config(:configurable_from_database, true)
|
||||
|
||||
describe "load_from_db_and_update/0" do
|
||||
test "transfer config values from db to env" do
|
||||
refute Application.get_env(:pleroma, :test_key)
|
||||
refute Application.get_env(:idna, :test_key)
|
||||
refute Application.get_env(:quack, :test_key)
|
||||
refute Application.get_env(:postgrex, :test_key)
|
||||
initial = Application.get_env(:logger, :level)
|
||||
|
||||
insert(:config, key: :test_key, value: [live: 2, com: 3])
|
||||
insert(:config, group: :idna, key: :test_key, value: [live: 15, com: 35])
|
||||
|
||||
insert(:config,
|
||||
group: :quack,
|
||||
key: nil,
|
||||
value: [test_key: [key1: :test_value1, key2: :test_value2]]
|
||||
)
|
||||
|
||||
insert(:config, group: :logger, key: nil, value: [level: :debug])
|
||||
|
||||
Environment.load_from_db_and_update()
|
||||
|
||||
assert Application.get_env(:pleroma, :test_key) == [live: 2, com: 3]
|
||||
assert Application.get_env(:idna, :test_key) == [live: 15, com: 35]
|
||||
assert Application.get_env(:quack, :test_key) == [key1: :test_value1, key2: :test_value2]
|
||||
assert Application.get_env(:logger, :level) == :debug
|
||||
|
||||
on_exit(fn ->
|
||||
Application.delete_env(:pleroma, :test_key)
|
||||
Application.delete_env(:idna, :test_key)
|
||||
Application.delete_env(:quack, :test_key)
|
||||
Application.delete_env(:postgrex, :test_key)
|
||||
Application.put_env(:logger, :level, initial)
|
||||
end)
|
||||
end
|
||||
|
||||
test "transfer config values for 1 group and some keys" do
|
||||
quack_env = Application.get_all_env(:quack)
|
||||
|
||||
insert(:config, group: :quack, key: nil, value: [level: :info, meta: [:none]])
|
||||
|
||||
Environment.load_from_db_and_update()
|
||||
|
||||
assert Application.get_env(:quack, :level) == :info
|
||||
assert Application.get_env(:quack, :meta) == [:none]
|
||||
default = Pleroma.Config.Holder.default_config(:quack, :webhook_url)
|
||||
assert Application.get_env(:quack, :webhook_url) == default
|
||||
|
||||
on_exit(fn ->
|
||||
Application.put_all_env(quack: quack_env)
|
||||
end)
|
||||
end
|
||||
|
||||
test "transfer config values with full subkey update" do
|
||||
clear_config(:emoji)
|
||||
clear_config(:assets)
|
||||
|
||||
insert(:config, key: :emoji, value: [groups: [a: 1, b: 2]])
|
||||
insert(:config, key: :assets, value: [mascots: [a: 1, b: 2]])
|
||||
|
||||
Environment.load_from_db_and_update()
|
||||
|
||||
emoji_env = Application.get_env(:pleroma, :emoji)
|
||||
assert emoji_env[:groups] == [a: 1, b: 2]
|
||||
assets_env = Application.get_env(:pleroma, :assets)
|
||||
assert assets_env[:mascots] == [a: 1, b: 2]
|
||||
end
|
||||
end
|
||||
|
||||
describe "update/2 :ex_syslogger" do
|
||||
setup do
|
||||
initial = Application.get_env(:logger, :ex_syslogger)
|
||||
|
||||
config =
|
||||
insert(:config,
|
||||
group: :logger,
|
||||
key: nil,
|
||||
value: [
|
||||
ex_syslogger: [
|
||||
level: :warn,
|
||||
ident: "pleroma",
|
||||
format: "$metadata[$level] $message",
|
||||
metadata: [:request_id, :key]
|
||||
]
|
||||
]
|
||||
)
|
||||
|
||||
on_exit(fn -> Application.put_env(:logger, :ex_syslogger, initial) end)
|
||||
[config: config, initial: initial]
|
||||
end
|
||||
|
||||
test "changing", %{config: config} do
|
||||
assert Environment.update([config]) == :ok
|
||||
|
||||
env = Application.get_env(:logger, :ex_syslogger)
|
||||
assert env[:level] == :warn
|
||||
assert env[:metadata] == [:request_id, :key]
|
||||
end
|
||||
|
||||
test "deletion", %{config: config, initial: initial} do
|
||||
assert Environment.update([config]) == :ok
|
||||
|
||||
{:ok, config} = Pleroma.ConfigDB.delete(config)
|
||||
assert Environment.update([config]) == :ok
|
||||
|
||||
env = Application.get_env(:logger, :ex_syslogger)
|
||||
|
||||
assert env == initial
|
||||
end
|
||||
end
|
||||
|
||||
describe "update/2 :console" do
|
||||
setup do
|
||||
initial = Application.get_env(:logger, :console)
|
||||
|
||||
config =
|
||||
insert(:config,
|
||||
group: :logger,
|
||||
key: nil,
|
||||
value: [
|
||||
console: [
|
||||
level: :info,
|
||||
format: "$time $metadata[$level]",
|
||||
metadata: [:request_id, :key]
|
||||
]
|
||||
]
|
||||
)
|
||||
|
||||
on_exit(fn -> Application.put_env(:logger, :console, initial) end)
|
||||
[config: config, initial: initial]
|
||||
end
|
||||
|
||||
test "change", %{config: config} do
|
||||
assert Environment.update([config]) == :ok
|
||||
env = Application.get_env(:logger, :console)
|
||||
assert env[:level] == :info
|
||||
assert env[:format] == "$time $metadata[$level]"
|
||||
assert env[:metadata] == [:request_id, :key]
|
||||
end
|
||||
|
||||
test "deletion", %{config: config, initial: initial} do
|
||||
assert Environment.update([config]) == :ok
|
||||
{:ok, config} = Pleroma.ConfigDB.delete(config)
|
||||
assert Environment.update([config]) == :ok
|
||||
|
||||
env = Application.get_env(:logger, :console)
|
||||
assert env == initial
|
||||
end
|
||||
end
|
||||
|
||||
describe "update/2 :backends" do
|
||||
setup do
|
||||
initial = Application.get_all_env(:logger)
|
||||
|
||||
config =
|
||||
insert(:config, group: :logger, key: nil, value: [backends: [:console, :ex_syslogger]])
|
||||
|
||||
on_exit(fn -> Application.put_all_env(logger: initial) end)
|
||||
|
||||
[config: config, initial: initial]
|
||||
end
|
||||
|
||||
test "change", %{config: config} do
|
||||
assert Environment.update([config]) == :ok
|
||||
env = Application.get_all_env(:logger)
|
||||
assert env[:backends] == [:console, :ex_syslogger]
|
||||
end
|
||||
|
||||
test "deletion", %{config: config, initial: initial} do
|
||||
assert Environment.update([config]) == :ok
|
||||
{:ok, config} = Pleroma.ConfigDB.delete(config)
|
||||
assert Environment.update([config])
|
||||
|
||||
env = Application.get_all_env(:logger)
|
||||
assert env == initial
|
||||
end
|
||||
end
|
||||
|
||||
describe "update/2 logger settings" do
|
||||
setup do
|
||||
initial = Application.get_all_env(:logger)
|
||||
|
||||
config =
|
||||
insert(:config,
|
||||
group: :logger,
|
||||
key: nil,
|
||||
value: [
|
||||
console: [
|
||||
level: :info,
|
||||
format: "$time $metadata[$level]",
|
||||
metadata: [:request_id, :key]
|
||||
],
|
||||
ex_syslogger: [
|
||||
level: :warn,
|
||||
ident: "pleroma",
|
||||
format: "$metadata[$level] $message",
|
||||
metadata: [:request_id, :key]
|
||||
],
|
||||
backends: [:console, :ex_syslogger]
|
||||
]
|
||||
)
|
||||
|
||||
on_exit(fn -> Application.put_all_env(logger: initial) end)
|
||||
[config: config]
|
||||
end
|
||||
|
||||
test "change", %{config: config} do
|
||||
assert Environment.update([config]) == :ok
|
||||
|
||||
env =
|
||||
:logger
|
||||
|> Application.get_all_env()
|
||||
|> Keyword.take([:backends, :console, :ex_syslogger])
|
||||
|
||||
assert env[:console] == config.value[:console]
|
||||
assert env[:ex_syslogger] == config.value[:ex_syslogger]
|
||||
assert env[:backends] == config.value[:backends]
|
||||
end
|
||||
end
|
||||
|
||||
test "update/2 for change without key :cors_plug" do
|
||||
config =
|
||||
insert(:config,
|
||||
group: :cors_plug,
|
||||
key: nil,
|
||||
value: [max_age: 300, methods: ["GET"]]
|
||||
)
|
||||
|
||||
assert Environment.update([config]) == :ok
|
||||
|
||||
env = Application.get_all_env(:cors_plug)
|
||||
|
||||
assert env[:max_age] == 300
|
||||
assert env[:methods] == ["GET"]
|
||||
end
|
||||
end
|
|
@ -2,24 +2,24 @@
|
|||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.ApplicationRequirementsTest do
|
||||
defmodule Pleroma.Application.RequirementsTest do
|
||||
use Pleroma.DataCase
|
||||
|
||||
import ExUnit.CaptureLog
|
||||
import Mock
|
||||
|
||||
alias Pleroma.ApplicationRequirements
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.Application.Requirements
|
||||
alias Pleroma.Emails.Mailer
|
||||
|
||||
describe "check_repo_pool_size!/1" do
|
||||
test "raises if the pool size is unexpected" do
|
||||
clear_config([Pleroma.Repo, :pool_size], 11)
|
||||
clear_config([:dangerzone, :override_repo_pool_size], false)
|
||||
|
||||
assert_raise Pleroma.ApplicationRequirements.VerifyError,
|
||||
assert_raise Requirements.VerifyError,
|
||||
"Repo.pool_size different than recommended value.",
|
||||
fn ->
|
||||
capture_log(&Pleroma.ApplicationRequirements.verify!/0)
|
||||
capture_log(&Requirements.verify!/0)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -27,27 +27,27 @@ defmodule Pleroma.ApplicationRequirementsTest do
|
|||
clear_config([Pleroma.Repo, :pool_size], 11)
|
||||
clear_config([:dangerzone, :override_repo_pool_size], true)
|
||||
|
||||
assert Pleroma.ApplicationRequirements.verify!() == :ok
|
||||
assert Requirements.verify!() == :ok
|
||||
end
|
||||
end
|
||||
|
||||
describe "check_welcome_message_config!/1" do
|
||||
setup do: clear_config([:welcome])
|
||||
setup do: clear_config([Pleroma.Emails.Mailer])
|
||||
setup do: clear_config([Mailer])
|
||||
|
||||
test "warns if welcome email enabled but mail disabled" do
|
||||
clear_config([:welcome, :email, :enabled], true)
|
||||
clear_config([Pleroma.Emails.Mailer, :enabled], false)
|
||||
clear_config([Mailer, :enabled], false)
|
||||
|
||||
assert capture_log(fn ->
|
||||
assert Pleroma.ApplicationRequirements.verify!() == :ok
|
||||
assert Requirements.verify!() == :ok
|
||||
end) =~ "Welcome emails will NOT be sent"
|
||||
end
|
||||
end
|
||||
|
||||
describe "check_confirmation_accounts!" do
|
||||
setup_with_mocks([
|
||||
{Pleroma.ApplicationRequirements, [:passthrough],
|
||||
{Requirements, [:passthrough],
|
||||
[
|
||||
check_migrations_applied!: fn _ -> :ok end
|
||||
]}
|
||||
|
@ -59,30 +59,29 @@ defmodule Pleroma.ApplicationRequirementsTest do
|
|||
|
||||
test "warns if account confirmation is required but mailer isn't enabled" do
|
||||
clear_config([:instance, :account_activation_required], true)
|
||||
clear_config([Pleroma.Emails.Mailer, :enabled], false)
|
||||
clear_config([Mailer, :enabled], false)
|
||||
|
||||
assert capture_log(fn ->
|
||||
assert Pleroma.ApplicationRequirements.verify!() == :ok
|
||||
assert Requirements.verify!() == :ok
|
||||
end) =~ "Users will NOT be able to confirm their accounts"
|
||||
end
|
||||
|
||||
test "doesn't do anything if account confirmation is disabled" do
|
||||
clear_config([:instance, :account_activation_required], false)
|
||||
clear_config([Pleroma.Emails.Mailer, :enabled], false)
|
||||
assert Pleroma.ApplicationRequirements.verify!() == :ok
|
||||
clear_config([Mailer, :enabled], false)
|
||||
assert Requirements.verify!() == :ok
|
||||
end
|
||||
|
||||
test "doesn't do anything if account confirmation is required and mailer is enabled" do
|
||||
clear_config([:instance, :account_activation_required], true)
|
||||
clear_config([Pleroma.Emails.Mailer, :enabled], true)
|
||||
assert Pleroma.ApplicationRequirements.verify!() == :ok
|
||||
clear_config([Mailer, :enabled], true)
|
||||
assert Requirements.verify!() == :ok
|
||||
end
|
||||
end
|
||||
|
||||
describe "check_rum!" do
|
||||
setup_with_mocks([
|
||||
{Pleroma.ApplicationRequirements, [:passthrough],
|
||||
[check_migrations_applied!: fn _ -> :ok end]}
|
||||
{Requirements, [:passthrough], [check_migrations_applied!: fn _ -> :ok end]}
|
||||
]) do
|
||||
:ok
|
||||
end
|
||||
|
@ -93,10 +92,10 @@ defmodule Pleroma.ApplicationRequirementsTest do
|
|||
clear_config([:database, :rum_enabled], true)
|
||||
|
||||
with_mocks([{Repo, [:passthrough], [exists?: fn _, _ -> false end]}]) do
|
||||
assert_raise ApplicationRequirements.VerifyError,
|
||||
assert_raise Requirements.VerifyError,
|
||||
"Unapplied RUM Migrations detected",
|
||||
fn ->
|
||||
capture_log(&ApplicationRequirements.verify!/0)
|
||||
capture_log(&Requirements.verify!/0)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -105,10 +104,10 @@ defmodule Pleroma.ApplicationRequirementsTest do
|
|||
clear_config([:database, :rum_enabled], false)
|
||||
|
||||
with_mocks([{Repo, [:passthrough], [exists?: fn _, _ -> true end]}]) do
|
||||
assert_raise ApplicationRequirements.VerifyError,
|
||||
assert_raise Requirements.VerifyError,
|
||||
"RUM Migrations detected",
|
||||
fn ->
|
||||
capture_log(&ApplicationRequirements.verify!/0)
|
||||
capture_log(&Requirements.verify!/0)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -117,7 +116,7 @@ defmodule Pleroma.ApplicationRequirementsTest do
|
|||
clear_config([:database, :rum_enabled], true)
|
||||
|
||||
with_mocks([{Repo, [:passthrough], [exists?: fn _, _ -> true end]}]) do
|
||||
assert ApplicationRequirements.verify!() == :ok
|
||||
assert Requirements.verify!() == :ok
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -125,12 +124,12 @@ defmodule Pleroma.ApplicationRequirementsTest do
|
|||
clear_config([:database, :rum_enabled], false)
|
||||
|
||||
with_mocks([{Repo, [:passthrough], [exists?: fn _, _ -> false end]}]) do
|
||||
assert ApplicationRequirements.verify!() == :ok
|
||||
assert Requirements.verify!() == :ok
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "check_migrations_applied!" do
|
||||
describe "check_migrations_applied" do
|
||||
setup_with_mocks([
|
||||
{Ecto.Migrator, [],
|
||||
[
|
||||
|
@ -150,17 +149,17 @@ defmodule Pleroma.ApplicationRequirementsTest do
|
|||
setup do: clear_config([:i_am_aware_this_may_cause_data_loss, :disable_migration_check])
|
||||
|
||||
test "raises if it detects unapplied migrations" do
|
||||
assert_raise ApplicationRequirements.VerifyError,
|
||||
assert_raise Requirements.VerifyError,
|
||||
"Unapplied Migrations detected",
|
||||
fn ->
|
||||
capture_log(&ApplicationRequirements.verify!/0)
|
||||
capture_log(&Requirements.verify!/0)
|
||||
end
|
||||
end
|
||||
|
||||
test "doesn't do anything if disabled" do
|
||||
clear_config([:i_am_aware_this_may_cause_data_loss, :disable_migration_check], true)
|
||||
|
||||
assert :ok == ApplicationRequirements.verify!()
|
||||
assert :ok == Requirements.verify!()
|
||||
end
|
||||
end
|
||||
end
|
432
test/pleroma/config/converter_test.exs
Normal file
432
test/pleroma/config/converter_test.exs
Normal file
|
@ -0,0 +1,432 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Config.ConverterTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
alias Pleroma.Config.Converter
|
||||
|
||||
describe "to_elixir_types/1" do
|
||||
test "string" do
|
||||
assert Converter.to_elixir_types("value as string") == "value as string"
|
||||
end
|
||||
|
||||
test "boolean" do
|
||||
assert Converter.to_elixir_types(false) == false
|
||||
end
|
||||
|
||||
test "nil" do
|
||||
assert Converter.to_elixir_types(nil) == nil
|
||||
end
|
||||
|
||||
test "integer" do
|
||||
assert Converter.to_elixir_types(150) == 150
|
||||
end
|
||||
|
||||
test "atom" do
|
||||
assert Converter.to_elixir_types(":atom") == :atom
|
||||
end
|
||||
|
||||
test "ssl options" do
|
||||
assert Converter.to_elixir_types([":tlsv1", ":tlsv1.1", ":tlsv1.2"]) == [
|
||||
:tlsv1,
|
||||
:"tlsv1.1",
|
||||
:"tlsv1.2"
|
||||
]
|
||||
end
|
||||
|
||||
test "pleroma module" do
|
||||
assert Converter.to_elixir_types("Pleroma.Bookmark") == Pleroma.Bookmark
|
||||
end
|
||||
|
||||
test "pleroma string" do
|
||||
assert Converter.to_elixir_types("Pleroma") == "Pleroma"
|
||||
end
|
||||
|
||||
test "phoenix module" do
|
||||
assert Converter.to_elixir_types("Phoenix.Socket.V1.JSONSerializer") ==
|
||||
Phoenix.Socket.V1.JSONSerializer
|
||||
end
|
||||
|
||||
test "tesla module" do
|
||||
assert Converter.to_elixir_types("Tesla.Adapter.Hackney") == Tesla.Adapter.Hackney
|
||||
end
|
||||
|
||||
test "ExSyslogger module" do
|
||||
assert Converter.to_elixir_types("ExSyslogger") == ExSyslogger
|
||||
end
|
||||
|
||||
test "Quack.Logger module" do
|
||||
assert Converter.to_elixir_types("Quack.Logger") == Quack.Logger
|
||||
end
|
||||
|
||||
test "Swoosh.Adapters modules" do
|
||||
assert Converter.to_elixir_types("Swoosh.Adapters.SMTP") == Swoosh.Adapters.SMTP
|
||||
assert Converter.to_elixir_types("Swoosh.Adapters.AmazonSES") == Swoosh.Adapters.AmazonSES
|
||||
end
|
||||
|
||||
test "sigil" do
|
||||
assert Converter.to_elixir_types("~r[comp[lL][aA][iI][nN]er]") == ~r/comp[lL][aA][iI][nN]er/
|
||||
end
|
||||
|
||||
test "link sigil" do
|
||||
assert Converter.to_elixir_types("~r/https:\/\/example.com/") == ~r/https:\/\/example.com/
|
||||
end
|
||||
|
||||
test "link sigil with um modifiers" do
|
||||
assert Converter.to_elixir_types("~r/https:\/\/example.com/um") ==
|
||||
~r/https:\/\/example.com/um
|
||||
end
|
||||
|
||||
test "link sigil with i modifier" do
|
||||
assert Converter.to_elixir_types("~r/https:\/\/example.com/i") == ~r/https:\/\/example.com/i
|
||||
end
|
||||
|
||||
test "link sigil with s modifier" do
|
||||
assert Converter.to_elixir_types("~r/https:\/\/example.com/s") == ~r/https:\/\/example.com/s
|
||||
end
|
||||
|
||||
test "raise if valid delimiter not found" do
|
||||
assert_raise ArgumentError, "valid delimiter for Regex expression not found", fn ->
|
||||
Converter.to_elixir_types("~r/https://[]{}<>\"'()|example.com/s")
|
||||
end
|
||||
end
|
||||
|
||||
test "2 child tuple" do
|
||||
assert Converter.to_elixir_types(%{"tuple" => ["v1", ":v2"]}) == {"v1", :v2}
|
||||
end
|
||||
|
||||
test "proxy tuple with localhost" do
|
||||
assert Converter.to_elixir_types(%{
|
||||
"tuple" => [":proxy_url", %{"tuple" => [":socks5", "localhost", 1234]}]
|
||||
}) == {:proxy_url, {:socks5, :localhost, 1234}}
|
||||
end
|
||||
|
||||
test "proxy tuple with domain" do
|
||||
assert Converter.to_elixir_types(%{
|
||||
"tuple" => [":proxy_url", %{"tuple" => [":socks5", "domain.com", 1234]}]
|
||||
}) == {:proxy_url, {:socks5, 'domain.com', 1234}}
|
||||
end
|
||||
|
||||
test "proxy tuple with ip" do
|
||||
assert Converter.to_elixir_types(%{
|
||||
"tuple" => [":proxy_url", %{"tuple" => [":socks5", "127.0.0.1", 1234]}]
|
||||
}) == {:proxy_url, {:socks5, {127, 0, 0, 1}, 1234}}
|
||||
end
|
||||
|
||||
test "tuple with n childs" do
|
||||
assert Converter.to_elixir_types(%{
|
||||
"tuple" => [
|
||||
"v1",
|
||||
":v2",
|
||||
"Pleroma.Bookmark",
|
||||
150,
|
||||
false,
|
||||
"Phoenix.Socket.V1.JSONSerializer"
|
||||
]
|
||||
}) == {"v1", :v2, Pleroma.Bookmark, 150, false, Phoenix.Socket.V1.JSONSerializer}
|
||||
end
|
||||
|
||||
test "map with string key" do
|
||||
assert Converter.to_elixir_types(%{"key" => "value"}) == %{"key" => "value"}
|
||||
end
|
||||
|
||||
test "map with atom key" do
|
||||
assert Converter.to_elixir_types(%{":key" => "value"}) == %{key: "value"}
|
||||
end
|
||||
|
||||
test "list of strings" do
|
||||
assert Converter.to_elixir_types(["v1", "v2", "v3"]) == ["v1", "v2", "v3"]
|
||||
end
|
||||
|
||||
test "list of modules" do
|
||||
assert Converter.to_elixir_types(["Pleroma.Repo", "Pleroma.Activity"]) == [
|
||||
Pleroma.Repo,
|
||||
Pleroma.Activity
|
||||
]
|
||||
end
|
||||
|
||||
test "list of atoms" do
|
||||
assert Converter.to_elixir_types([":v1", ":v2", ":v3"]) == [:v1, :v2, :v3]
|
||||
end
|
||||
|
||||
test "list of mixed values" do
|
||||
assert Converter.to_elixir_types([
|
||||
"v1",
|
||||
":v2",
|
||||
"Pleroma.Repo",
|
||||
"Phoenix.Socket.V1.JSONSerializer",
|
||||
15,
|
||||
false
|
||||
]) == [
|
||||
"v1",
|
||||
:v2,
|
||||
Pleroma.Repo,
|
||||
Phoenix.Socket.V1.JSONSerializer,
|
||||
15,
|
||||
false
|
||||
]
|
||||
end
|
||||
|
||||
test "simple keyword" do
|
||||
assert Converter.to_elixir_types([%{"tuple" => [":key", "value"]}]) == [key: "value"]
|
||||
end
|
||||
|
||||
test "keyword" do
|
||||
assert Converter.to_elixir_types([
|
||||
%{"tuple" => [":types", "Pleroma.PostgresTypes"]},
|
||||
%{"tuple" => [":telemetry_event", ["Pleroma.Repo.Instrumenter"]]},
|
||||
%{"tuple" => [":migration_lock", nil]},
|
||||
%{"tuple" => [":key1", 150]},
|
||||
%{"tuple" => [":key2", "string"]}
|
||||
]) == [
|
||||
types: Pleroma.PostgresTypes,
|
||||
telemetry_event: [Pleroma.Repo.Instrumenter],
|
||||
migration_lock: nil,
|
||||
key1: 150,
|
||||
key2: "string"
|
||||
]
|
||||
end
|
||||
|
||||
test "trandformed keyword" do
|
||||
assert Converter.to_elixir_types(a: 1, b: 2, c: "string") == [a: 1, b: 2, c: "string"]
|
||||
end
|
||||
|
||||
test "complex keyword with nested mixed childs" do
|
||||
assert Converter.to_elixir_types([
|
||||
%{"tuple" => [":uploader", "Pleroma.Uploaders.Local"]},
|
||||
%{"tuple" => [":filters", ["Pleroma.Upload.Filter.Dedupe"]]},
|
||||
%{"tuple" => [":link_name", true]},
|
||||
%{"tuple" => [":proxy_remote", false]},
|
||||
%{"tuple" => [":common_map", %{":key" => "value"}]},
|
||||
%{
|
||||
"tuple" => [
|
||||
":proxy_opts",
|
||||
[
|
||||
%{"tuple" => [":redirect_on_failure", false]},
|
||||
%{"tuple" => [":max_body_length", 1_048_576]},
|
||||
%{
|
||||
"tuple" => [
|
||||
":http",
|
||||
[
|
||||
%{"tuple" => [":follow_redirect", true]},
|
||||
%{"tuple" => [":pool", ":upload"]}
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
]) == [
|
||||
uploader: Pleroma.Uploaders.Local,
|
||||
filters: [Pleroma.Upload.Filter.Dedupe],
|
||||
link_name: true,
|
||||
proxy_remote: false,
|
||||
common_map: %{key: "value"},
|
||||
proxy_opts: [
|
||||
redirect_on_failure: false,
|
||||
max_body_length: 1_048_576,
|
||||
http: [
|
||||
follow_redirect: true,
|
||||
pool: :upload
|
||||
]
|
||||
]
|
||||
]
|
||||
end
|
||||
|
||||
test "common keyword" do
|
||||
assert Converter.to_elixir_types([
|
||||
%{"tuple" => [":level", ":warn"]},
|
||||
%{"tuple" => [":meta", [":all"]]},
|
||||
%{"tuple" => [":path", ""]},
|
||||
%{"tuple" => [":val", nil]},
|
||||
%{"tuple" => [":webhook_url", "https://hooks.slack.com/services/YOUR-KEY-HERE"]}
|
||||
]) == [
|
||||
level: :warn,
|
||||
meta: [:all],
|
||||
path: "",
|
||||
val: nil,
|
||||
webhook_url: "https://hooks.slack.com/services/YOUR-KEY-HERE"
|
||||
]
|
||||
end
|
||||
|
||||
test "complex keyword with sigil" do
|
||||
assert Converter.to_elixir_types([
|
||||
%{"tuple" => [":federated_timeline_removal", []]},
|
||||
%{"tuple" => [":reject", ["~r/comp[lL][aA][iI][nN]er/"]]},
|
||||
%{"tuple" => [":replace", []]}
|
||||
]) == [
|
||||
federated_timeline_removal: [],
|
||||
reject: [~r/comp[lL][aA][iI][nN]er/],
|
||||
replace: []
|
||||
]
|
||||
end
|
||||
|
||||
test "complex keyword with tuples with more than 2 values" do
|
||||
assert Converter.to_elixir_types([
|
||||
%{
|
||||
"tuple" => [
|
||||
":http",
|
||||
[
|
||||
%{
|
||||
"tuple" => [
|
||||
":key1",
|
||||
[
|
||||
%{
|
||||
"tuple" => [
|
||||
":_",
|
||||
[
|
||||
%{
|
||||
"tuple" => [
|
||||
"/api/v1/streaming",
|
||||
"Pleroma.Web.MastodonAPI.WebsocketHandler",
|
||||
[]
|
||||
]
|
||||
},
|
||||
%{
|
||||
"tuple" => [
|
||||
"/websocket",
|
||||
"Phoenix.Endpoint.CowboyWebSocket",
|
||||
%{
|
||||
"tuple" => [
|
||||
"Phoenix.Transports.WebSocket",
|
||||
%{
|
||||
"tuple" => [
|
||||
"Pleroma.Web.Endpoint",
|
||||
"Pleroma.Web.UserSocket",
|
||||
[]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
%{
|
||||
"tuple" => [
|
||||
":_",
|
||||
"Phoenix.Endpoint.Cowboy2Handler",
|
||||
%{"tuple" => ["Pleroma.Web.Endpoint", []]}
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
]) == [
|
||||
http: [
|
||||
key1: [
|
||||
{:_,
|
||||
[
|
||||
{"/api/v1/streaming", Pleroma.Web.MastodonAPI.WebsocketHandler, []},
|
||||
{"/websocket", Phoenix.Endpoint.CowboyWebSocket,
|
||||
{Phoenix.Transports.WebSocket,
|
||||
{Pleroma.Web.Endpoint, Pleroma.Web.UserSocket, []}}},
|
||||
{:_, Phoenix.Endpoint.Cowboy2Handler, {Pleroma.Web.Endpoint, []}}
|
||||
]}
|
||||
]
|
||||
]
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
describe "to_json_types" do
|
||||
test "list" do
|
||||
assert Converter.to_json_types(["0", 1, true, :atom, Pleroma.Upload]) == [
|
||||
"0",
|
||||
1,
|
||||
true,
|
||||
":atom",
|
||||
"Pleroma.Upload"
|
||||
]
|
||||
end
|
||||
|
||||
test "regex" do
|
||||
assert Converter.to_json_types(~r/regex/i) == "~r/regex/i"
|
||||
end
|
||||
|
||||
test "map" do
|
||||
assert Converter.to_json_types(%{"a" => "b", "c" => 1, "d" => true, "e" => :atom}) == %{
|
||||
"a" => "b",
|
||||
"c" => 1,
|
||||
"d" => true,
|
||||
"e" => ":atom"
|
||||
}
|
||||
end
|
||||
|
||||
test ":args list" do
|
||||
assert Converter.to_json_types({:args, [{1, "a"}, "string"]}) == %{
|
||||
"tuple" => [":args", ["{1, \"a\"}", "string"]]
|
||||
}
|
||||
end
|
||||
|
||||
test ":proxy_url tuple with localhost" do
|
||||
assert Converter.to_json_types({:proxy_url, {:socks, :localhost, 1234}}) == %{
|
||||
"tuple" => [":proxy_url", %{"tuple" => [":socks", "localhost", 1234]}]
|
||||
}
|
||||
end
|
||||
|
||||
test ":proxy_url tuple" do
|
||||
assert Converter.to_json_types({:proxy_url, {:socks, {127, 0, 0, 1}, 1234}}) == %{
|
||||
"tuple" => [":proxy_url", %{"tuple" => [":socks", "127.0.0.1", 1234]}]
|
||||
}
|
||||
end
|
||||
|
||||
test ":proxy_url tuple domain" do
|
||||
assert Converter.to_json_types({:proxy_url, {:socks5, "domain.com", 1234}}) == %{
|
||||
"tuple" => [":proxy_url", %{"tuple" => [":socks5", "domain.com", 1234]}]
|
||||
}
|
||||
end
|
||||
|
||||
test "tuple" do
|
||||
assert Converter.to_json_types({1, "a"}) == %{"tuple" => [1, "a"]}
|
||||
end
|
||||
|
||||
test "string" do
|
||||
assert Converter.to_json_types("string") == "string"
|
||||
end
|
||||
|
||||
test "boolean" do
|
||||
assert Converter.to_json_types(true) == true
|
||||
end
|
||||
|
||||
test "integer" do
|
||||
assert Converter.to_json_types(123) == 123
|
||||
end
|
||||
|
||||
test "nil" do
|
||||
assert Converter.to_json_types(nil) == nil
|
||||
end
|
||||
|
||||
test "ssl type" do
|
||||
assert Converter.to_json_types(:"tlsv1.1") == ":tlsv1.1"
|
||||
end
|
||||
|
||||
test "atom" do
|
||||
assert Converter.to_json_types(:atom) == ":atom"
|
||||
end
|
||||
end
|
||||
|
||||
describe "string_to_elixir_types!/1" do
|
||||
test "atom" do
|
||||
assert Converter.string_to_elixir_types!(":localhost") == :localhost
|
||||
end
|
||||
|
||||
test "module" do
|
||||
assert Converter.string_to_elixir_types!("Pleroma.Upload") == Pleroma.Upload
|
||||
end
|
||||
|
||||
test "regex" do
|
||||
assert Converter.string_to_elixir_types!("~r/regex/i") == ~r/regex/i
|
||||
end
|
||||
|
||||
test "string" do
|
||||
assert Converter.string_to_elixir_types!("string") == "string"
|
||||
end
|
||||
end
|
||||
end
|
|
@ -8,22 +8,38 @@ defmodule Pleroma.Config.LoaderTest do
|
|||
alias Pleroma.Config.Loader
|
||||
|
||||
test "read/1" do
|
||||
config = Loader.read("test/fixtures/config/temp.secret.exs")
|
||||
config = Loader.read!("test/fixtures/config/temp.secret.exs")
|
||||
assert config[:pleroma][:first_setting][:key] == "value"
|
||||
assert config[:pleroma][:first_setting][:key2] == [Pleroma.Repo]
|
||||
assert config[:quack][:level] == :info
|
||||
end
|
||||
|
||||
test "filter_group/2" do
|
||||
assert Loader.filter_group(:pleroma,
|
||||
pleroma: [
|
||||
{Pleroma.Repo, [a: 1, b: 2]},
|
||||
{Pleroma.Upload, [a: 1, b: 2]},
|
||||
{Pleroma.Web.Endpoint, []},
|
||||
env: :test,
|
||||
configurable_from_database: true,
|
||||
database: []
|
||||
]
|
||||
) == [{Pleroma.Upload, [a: 1, b: 2]}]
|
||||
test "filter/1" do
|
||||
config = Loader.read!("test/fixtures/config/temp.secret.exs")
|
||||
|
||||
filtered_config = Loader.filter(config)
|
||||
|
||||
refute filtered_config[:postgrex]
|
||||
refute filtered_config[:tesla]
|
||||
refute filtered_config[:phoenix]
|
||||
refute filtered_config[:tz_data]
|
||||
refute filtered_config[:http_signatures]
|
||||
refute filtered_config[:web_push_encryption]
|
||||
refute filtered_config[:floki]
|
||||
|
||||
refute filtered_config[:pleroma][Pleroma.Repo]
|
||||
refute filtered_config[:pleroma][Pleroma.Web.Endpoint]
|
||||
refute filtered_config[:pleroma][:env]
|
||||
refute filtered_config[:pleroma][:configurable_from_database]
|
||||
refute filtered_config[:pleroma][:database]
|
||||
refute filtered_config[:pleroma][:ecto_repos]
|
||||
refute filtered_config[:pleroma][Pleroma.Gun]
|
||||
refute filtered_config[:pleroma][Pleroma.ReverseProxy.Client]
|
||||
|
||||
assert config[:pleroma][:first_setting][:key] == "value"
|
||||
assert config[:pleroma][:first_setting][:key2] == [Pleroma.Repo]
|
||||
assert config[:quack][:level] == :info
|
||||
assert config[:pleroma][:second_setting][:key] == "value2"
|
||||
assert config[:pleroma][:second_setting][:key2] == ["Activity"]
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,120 +0,0 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Config.TransferTaskTest do
|
||||
use Pleroma.DataCase
|
||||
|
||||
import ExUnit.CaptureLog
|
||||
import Pleroma.Factory
|
||||
|
||||
alias Pleroma.Config.TransferTask
|
||||
|
||||
setup do: clear_config(:configurable_from_database, true)
|
||||
|
||||
test "transfer config values from db to env" do
|
||||
refute Application.get_env(:pleroma, :test_key)
|
||||
refute Application.get_env(:idna, :test_key)
|
||||
refute Application.get_env(:quack, :test_key)
|
||||
refute Application.get_env(:postgrex, :test_key)
|
||||
initial = Application.get_env(:logger, :level)
|
||||
|
||||
insert(:config, key: :test_key, value: [live: 2, com: 3])
|
||||
insert(:config, group: :idna, key: :test_key, value: [live: 15, com: 35])
|
||||
insert(:config, group: :quack, key: :test_key, value: [:test_value1, :test_value2])
|
||||
insert(:config, group: :postgrex, key: :test_key, value: :value)
|
||||
insert(:config, group: :logger, key: :level, value: :debug)
|
||||
|
||||
TransferTask.start_link([])
|
||||
|
||||
assert Application.get_env(:pleroma, :test_key) == [live: 2, com: 3]
|
||||
assert Application.get_env(:idna, :test_key) == [live: 15, com: 35]
|
||||
assert Application.get_env(:quack, :test_key) == [:test_value1, :test_value2]
|
||||
assert Application.get_env(:logger, :level) == :debug
|
||||
assert Application.get_env(:postgrex, :test_key) == :value
|
||||
|
||||
on_exit(fn ->
|
||||
Application.delete_env(:pleroma, :test_key)
|
||||
Application.delete_env(:idna, :test_key)
|
||||
Application.delete_env(:quack, :test_key)
|
||||
Application.delete_env(:postgrex, :test_key)
|
||||
Application.put_env(:logger, :level, initial)
|
||||
end)
|
||||
end
|
||||
|
||||
test "transfer config values for 1 group and some keys" do
|
||||
level = Application.get_env(:quack, :level)
|
||||
meta = Application.get_env(:quack, :meta)
|
||||
|
||||
insert(:config, group: :quack, key: :level, value: :info)
|
||||
insert(:config, group: :quack, key: :meta, value: [:none])
|
||||
|
||||
TransferTask.start_link([])
|
||||
|
||||
assert Application.get_env(:quack, :level) == :info
|
||||
assert Application.get_env(:quack, :meta) == [:none]
|
||||
default = Pleroma.Config.Holder.default_config(:quack, :webhook_url)
|
||||
assert Application.get_env(:quack, :webhook_url) == default
|
||||
|
||||
on_exit(fn ->
|
||||
Application.put_env(:quack, :level, level)
|
||||
Application.put_env(:quack, :meta, meta)
|
||||
end)
|
||||
end
|
||||
|
||||
test "transfer config values with full subkey update" do
|
||||
clear_config(:emoji)
|
||||
clear_config(:assets)
|
||||
|
||||
insert(:config, key: :emoji, value: [groups: [a: 1, b: 2]])
|
||||
insert(:config, key: :assets, value: [mascots: [a: 1, b: 2]])
|
||||
|
||||
TransferTask.start_link([])
|
||||
|
||||
emoji_env = Application.get_env(:pleroma, :emoji)
|
||||
assert emoji_env[:groups] == [a: 1, b: 2]
|
||||
assets_env = Application.get_env(:pleroma, :assets)
|
||||
assert assets_env[:mascots] == [a: 1, b: 2]
|
||||
end
|
||||
|
||||
describe "pleroma restart" do
|
||||
setup do
|
||||
on_exit(fn -> Restarter.Pleroma.refresh() end)
|
||||
end
|
||||
|
||||
test "don't restart if no reboot time settings were changed" do
|
||||
clear_config(:emoji)
|
||||
insert(:config, key: :emoji, value: [groups: [a: 1, b: 2]])
|
||||
|
||||
refute String.contains?(
|
||||
capture_log(fn -> TransferTask.start_link([]) end),
|
||||
"pleroma restarted"
|
||||
)
|
||||
end
|
||||
|
||||
test "on reboot time key" do
|
||||
clear_config(:chat)
|
||||
insert(:config, key: :chat, value: [enabled: false])
|
||||
assert capture_log(fn -> TransferTask.start_link([]) end) =~ "pleroma restarted"
|
||||
end
|
||||
|
||||
test "on reboot time subkey" do
|
||||
clear_config(Pleroma.Captcha)
|
||||
insert(:config, key: Pleroma.Captcha, value: [seconds_valid: 60])
|
||||
assert capture_log(fn -> TransferTask.start_link([]) end) =~ "pleroma restarted"
|
||||
end
|
||||
|
||||
test "don't restart pleroma on reboot time key and subkey if there is false flag" do
|
||||
clear_config(:chat)
|
||||
clear_config(Pleroma.Captcha)
|
||||
|
||||
insert(:config, key: :chat, value: [enabled: false])
|
||||
insert(:config, key: Pleroma.Captcha, value: [seconds_valid: 60])
|
||||
|
||||
refute String.contains?(
|
||||
capture_log(fn -> TransferTask.load_and_update_env([], false) end),
|
||||
"pleroma restarted"
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
414
test/pleroma/config/versioning_test.exs
Normal file
414
test/pleroma/config/versioning_test.exs
Normal file
|
@ -0,0 +1,414 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Config.VersioningTest do
|
||||
use Pleroma.DataCase, async: true
|
||||
|
||||
import Pleroma.Factory
|
||||
|
||||
alias Pleroma.Config.Version
|
||||
alias Pleroma.Config.Versioning
|
||||
alias Pleroma.ConfigDB
|
||||
alias Pleroma.Repo
|
||||
|
||||
@with_key %{
|
||||
group: :pleroma,
|
||||
key: :instance,
|
||||
value: [name: "Instance name"]
|
||||
}
|
||||
|
||||
@without_key %{
|
||||
group: :quack,
|
||||
key: nil,
|
||||
value: [
|
||||
level: :warn,
|
||||
meta: [:all],
|
||||
webhook_url: "https://hooks.slack.com/services/YOUR-KEY-HERE"
|
||||
]
|
||||
}
|
||||
|
||||
@value_not_keyword %{
|
||||
group: :pleroma,
|
||||
key: Pleroma.Web.Auth.Authenticator,
|
||||
value: Pleroma.Web.Auth.PleromaAuthenticator
|
||||
}
|
||||
|
||||
describe "new_version/1" do
|
||||
test "creates version" do
|
||||
changes = [@with_key, @without_key, @value_not_keyword]
|
||||
|
||||
{:ok,
|
||||
%{
|
||||
:insert_version => version,
|
||||
:update_all_versions => {0, nil},
|
||||
{:insert_or_update, :pleroma, :instance} => _,
|
||||
{:insert_or_update, :quack, nil} => _
|
||||
}} = Versioning.new_version(changes)
|
||||
|
||||
assert version.current
|
||||
assert backup_length(version) == 2
|
||||
|
||||
assert version.backup[:quack] == @without_key[:value]
|
||||
|
||||
assert version.backup[:pleroma][:instance] == @with_key[:value]
|
||||
|
||||
assert version.backup[:pleroma][Pleroma.Web.Auth.Authenticator] ==
|
||||
@value_not_keyword[:value]
|
||||
|
||||
assert Repo.aggregate(ConfigDB, :count) == 3
|
||||
assert Repo.aggregate(Version, :count) == 1
|
||||
end
|
||||
|
||||
test "creates several versions" do
|
||||
change1 = [@with_key]
|
||||
|
||||
{:ok,
|
||||
%{
|
||||
:insert_version => version1,
|
||||
:update_all_versions => {0, nil},
|
||||
{:insert_or_update, :pleroma, :instance} => _
|
||||
}} = Versioning.new_version(change1)
|
||||
|
||||
change2 = [@without_key]
|
||||
|
||||
{:ok,
|
||||
%{
|
||||
:insert_version => version2,
|
||||
:update_all_versions => {1, nil},
|
||||
{:insert_or_update, :quack, nil} => _
|
||||
}} = Versioning.new_version(change2)
|
||||
|
||||
version1 = refresh_record(version1)
|
||||
refute version1.current
|
||||
|
||||
assert backup_length(version1) == 1
|
||||
|
||||
version2 = refresh_record(version2)
|
||||
assert version2.current
|
||||
|
||||
assert backup_length(version2) == 2
|
||||
end
|
||||
|
||||
test "error on empty list" do
|
||||
assert Versioning.new_version([]) == {:error, :empty_changes}
|
||||
end
|
||||
|
||||
test "error on bad format" do
|
||||
assert Versioning.new_version(nil) == {:error, :bad_format}
|
||||
end
|
||||
|
||||
test "process changes as single map" do
|
||||
{:ok,
|
||||
%{
|
||||
:insert_version => _,
|
||||
:update_all_versions => {0, nil},
|
||||
{:insert_or_update, :pleroma, :instance} => _
|
||||
}} = Versioning.new_version(@with_key)
|
||||
|
||||
assert Repo.aggregate(ConfigDB, :count) == 1
|
||||
end
|
||||
|
||||
test "error if value is not keyword" do
|
||||
assert Versioning.new_version([
|
||||
%{group: :pleroma, key: :key, value: %{}}
|
||||
]) ==
|
||||
{:error, {:error, :pleroma, :key},
|
||||
{:value_must_be_keyword, %{group: :pleroma, key: :key, value: %{}}}, %{}}
|
||||
end
|
||||
|
||||
test "error if value is list" do
|
||||
assert Versioning.new_version([
|
||||
%{group: :pleroma, key: :key, value: [1]}
|
||||
]) ==
|
||||
{:error, {:error, :pleroma, :key},
|
||||
{:value_must_be_keyword, %{group: :pleroma, key: :key, value: [1]}}, %{}}
|
||||
end
|
||||
end
|
||||
|
||||
describe "rollback/1" do
|
||||
test "bad steps format" do
|
||||
assert Versioning.rollback(nil) == {:error, :steps_format}
|
||||
end
|
||||
|
||||
test "no versions" do
|
||||
assert Versioning.rollback() == {:error, :no_current_version}
|
||||
end
|
||||
|
||||
test "rollback not possible, because there is only one version" do
|
||||
{:ok, _} = Versioning.new_version(@with_key)
|
||||
|
||||
assert Versioning.rollback() == {:error, :rollback_not_possible}
|
||||
end
|
||||
|
||||
test "rollbacks to previous version" do
|
||||
{:ok, _} = Versioning.new_version(@with_key)
|
||||
|
||||
{:ok, _} = Versioning.new_version(@value_not_keyword)
|
||||
|
||||
{:ok, _} = Versioning.new_version(@without_key)
|
||||
|
||||
{:ok, _} = Versioning.rollback()
|
||||
|
||||
configs = ConfigDB.all()
|
||||
|
||||
Enum.each(configs, fn
|
||||
%{key: :instance} = config ->
|
||||
config.value == @with_key[:value]
|
||||
|
||||
%{key: Pleroma.Web.Auth.Authenticator} = config ->
|
||||
config.value == @value_not_keyword[:value]
|
||||
end)
|
||||
|
||||
assert Repo.aggregate(Version, :count) == 2
|
||||
|
||||
version = Repo.get_by(Version, current: true)
|
||||
|
||||
assert version.backup[:pleroma][:instance] == @with_key[:value]
|
||||
|
||||
assert version.backup[:pleroma][Pleroma.Web.Auth.Authenticator] ==
|
||||
@value_not_keyword[:value]
|
||||
end
|
||||
|
||||
test "rollbacks with 2 steps" do
|
||||
{:ok, _} = Versioning.new_version(@with_key)
|
||||
|
||||
{:ok, _} = Versioning.new_version(@without_key)
|
||||
|
||||
{:ok, _} =
|
||||
Versioning.new_version(%{
|
||||
group: :pleroma,
|
||||
key: :instance,
|
||||
value: [name: "New name"]
|
||||
})
|
||||
|
||||
assert Repo.aggregate(ConfigDB, :count) == 2
|
||||
assert Repo.aggregate(Version, :count) == 3
|
||||
{:ok, _} = Versioning.rollback(2)
|
||||
|
||||
assert Repo.aggregate(Version, :count) == 1
|
||||
|
||||
[with_key] = ConfigDB.all()
|
||||
|
||||
assert with_key.value == @with_key[:value]
|
||||
end
|
||||
|
||||
test "rollbacks with 2 steps and creates new version for new change" do
|
||||
{:ok, _} = Versioning.new_version(@with_key)
|
||||
|
||||
{:ok, _} = Versioning.new_version(@without_key)
|
||||
|
||||
{:ok, _} =
|
||||
Versioning.new_version(%{
|
||||
group: :pleroma,
|
||||
key: :instance,
|
||||
value: [name: "New name"]
|
||||
})
|
||||
|
||||
{:ok, _} = Versioning.rollback(2)
|
||||
|
||||
{:ok, _} =
|
||||
Versioning.new_version(%{
|
||||
group: :pleroma,
|
||||
key: :instance,
|
||||
value: [name: "Last name"]
|
||||
})
|
||||
|
||||
[with_key] = ConfigDB.all()
|
||||
assert with_key.value == [name: "Last name"]
|
||||
end
|
||||
|
||||
test "properly rollbacks with settings without keys" do
|
||||
{:ok, _} = Versioning.new_version(@with_key)
|
||||
|
||||
{:ok, _} = Versioning.new_version(@without_key)
|
||||
|
||||
{:ok, _} =
|
||||
Versioning.new_version(%{
|
||||
group: :pleroma,
|
||||
key: :instance,
|
||||
value: [name: "New name"]
|
||||
})
|
||||
|
||||
{:ok, _} = Versioning.rollback()
|
||||
|
||||
config = ConfigDB.get_by_params(%{group: :quack})
|
||||
assert config.value == @without_key[:value]
|
||||
end
|
||||
|
||||
test "properly rollbacks with logger settings" do
|
||||
{:ok, _} = Versioning.new_version(@with_key)
|
||||
|
||||
{:ok, _} =
|
||||
Versioning.new_version([
|
||||
%{
|
||||
group: :logger,
|
||||
value: [
|
||||
console: [
|
||||
level: :debug,
|
||||
format: "\n$time $metadata[$level] $message\n",
|
||||
metadata: [:request_id]
|
||||
],
|
||||
backends: [:console]
|
||||
]
|
||||
}
|
||||
])
|
||||
|
||||
{:ok, _} = Versioning.new_version(@without_key)
|
||||
|
||||
{:ok, _} = Versioning.rollback()
|
||||
|
||||
logger = ConfigDB.get_by_params(%{group: :logger})
|
||||
|
||||
assert logger.value == [
|
||||
console: [
|
||||
level: :debug,
|
||||
format: "\n$time $metadata[$level] $message\n",
|
||||
metadata: [:request_id]
|
||||
],
|
||||
backends: [:console]
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
describe "migrate/1" do
|
||||
test "migrates settings from config file" do
|
||||
{:ok, _} = Versioning.migrate("test/fixtures/config/temp.secret.exs")
|
||||
|
||||
assert Repo.aggregate(ConfigDB, :count) == 3
|
||||
|
||||
config1 = ConfigDB.get_by_params(%{group: :pleroma, key: :first_setting})
|
||||
config2 = ConfigDB.get_by_params(%{group: :pleroma, key: :second_setting})
|
||||
config3 = ConfigDB.get_by_params(%{group: :quack})
|
||||
|
||||
assert config1.value == [key: "value", key2: [Repo]]
|
||||
assert config2.value == [key: "value2", key2: ["Activity"]]
|
||||
assert config3.value == [level: :info]
|
||||
|
||||
[version] = Repo.all(Version)
|
||||
|
||||
assert version.backup == [
|
||||
pleroma: [
|
||||
second_setting: [key: "value2", key2: ["Activity"]],
|
||||
first_setting: [key: "value", key2: [Repo]]
|
||||
],
|
||||
quack: [level: :info]
|
||||
]
|
||||
end
|
||||
|
||||
test "truncates table on migration" do
|
||||
insert_list(4, :config)
|
||||
|
||||
assert Repo.aggregate(ConfigDB, :count) == 4
|
||||
|
||||
{:ok, _} = Versioning.migrate("test/fixtures/config/temp.secret.exs")
|
||||
|
||||
assert Repo.aggregate(ConfigDB, :count) == 3
|
||||
end
|
||||
end
|
||||
|
||||
describe "migrate_namespace/2" do
|
||||
test "common namespace rename" do
|
||||
value_before_migration = [name: "Name"]
|
||||
|
||||
{:ok, %{:insert_version => version1}} =
|
||||
Versioning.new_version(%{
|
||||
group: :pleroma,
|
||||
key: :key1,
|
||||
value: value_before_migration
|
||||
})
|
||||
|
||||
{:ok, %{:insert_version => version2}} =
|
||||
Versioning.new_version(%{
|
||||
group: :pleroma,
|
||||
key: :key2,
|
||||
value: [name: "Name"]
|
||||
})
|
||||
|
||||
{:ok, %{:insert_version => version3}} =
|
||||
Versioning.new_version(%{
|
||||
group: :pleroma,
|
||||
key: :key3,
|
||||
value: [name: "Name"]
|
||||
})
|
||||
|
||||
{:ok, _} = Versioning.migrate_namespace({:pleroma, :key1}, {:ex_aws, :new_key})
|
||||
|
||||
version1 = refresh_record(version1)
|
||||
assert version1.backup == [ex_aws: [new_key: [name: "Name"]]]
|
||||
version2 = refresh_record(version2)
|
||||
|
||||
assert version2.backup == [
|
||||
ex_aws: [new_key: [name: "Name"]],
|
||||
pleroma: [key2: [name: "Name"]]
|
||||
]
|
||||
|
||||
version3 = refresh_record(version3)
|
||||
|
||||
assert version3.backup == [
|
||||
ex_aws: [new_key: [name: "Name"]],
|
||||
pleroma: [key2: [name: "Name"], key3: [name: "Name"]]
|
||||
]
|
||||
|
||||
assert Repo.aggregate(from(c in ConfigDB, where: c.group == ^:pleroma), :count, :id) == 2
|
||||
config = ConfigDB.get_by_params(%{group: :ex_aws, key: :new_key})
|
||||
assert config.value == value_before_migration
|
||||
|
||||
{:ok, _} = Versioning.migrate_namespace({:pleroma, :key2}, {:pleroma, :new_key})
|
||||
|
||||
version1 = refresh_record(version1)
|
||||
assert version1.backup == [ex_aws: [new_key: [name: "Name"]]]
|
||||
version2 = refresh_record(version2)
|
||||
|
||||
assert version2.backup == [
|
||||
pleroma: [new_key: [name: "Name"]],
|
||||
ex_aws: [new_key: [name: "Name"]]
|
||||
]
|
||||
|
||||
version3 = refresh_record(version3)
|
||||
|
||||
assert version3.backup == [
|
||||
ex_aws: [new_key: [name: "Name"]],
|
||||
pleroma: [new_key: [name: "Name"], key3: [name: "Name"]]
|
||||
]
|
||||
end
|
||||
|
||||
test "old namespace exists in old backups" do
|
||||
{:ok, %{:insert_version => version1}} =
|
||||
Versioning.new_version(%{
|
||||
group: :pleroma,
|
||||
key: :key1,
|
||||
value: [name: "Name"]
|
||||
})
|
||||
|
||||
{:ok, %{:insert_version => version2}} =
|
||||
Versioning.new_version([
|
||||
%{
|
||||
group: :pleroma,
|
||||
key: :key2,
|
||||
value: [name: "Name"]
|
||||
},
|
||||
%{group: :pleroma, key: :key1, delete: true}
|
||||
])
|
||||
|
||||
{:ok, _} = Versioning.migrate_namespace({:pleroma, :key1}, {:ex_aws, :new_key})
|
||||
|
||||
version1 = refresh_record(version1)
|
||||
assert version1.backup == [ex_aws: [new_key: [name: "Name"]]]
|
||||
version2 = refresh_record(version2)
|
||||
|
||||
assert version2.backup == [
|
||||
pleroma: [key2: [name: "Name"]]
|
||||
]
|
||||
|
||||
assert Repo.aggregate(from(c in ConfigDB, where: c.group == ^:pleroma), :count, :id) == 1
|
||||
refute ConfigDB.get_by_params(%{group: :ex_aws, key: :new_key})
|
||||
end
|
||||
end
|
||||
|
||||
defp backup_length(%{backup: backup}) do
|
||||
backup
|
||||
|> Keyword.keys()
|
||||
|> length()
|
||||
end
|
||||
end
|
|
@ -14,7 +14,7 @@ defmodule Pleroma.ConfigDBTest do
|
|||
assert config == ConfigDB.get_by_params(%{group: config.group, key: config.key})
|
||||
end
|
||||
|
||||
test "get_all_as_keyword/0" do
|
||||
test "all_as_keyword/0" do
|
||||
saved = insert(:config)
|
||||
insert(:config, group: ":quack", key: ":level", value: :info)
|
||||
insert(:config, group: ":quack", key: ":meta", value: [:none])
|
||||
|
@ -25,7 +25,7 @@ defmodule Pleroma.ConfigDBTest do
|
|||
value: "https://hooks.slack.com/services/KEY/some_val"
|
||||
)
|
||||
|
||||
config = ConfigDB.get_all_as_keyword()
|
||||
config = ConfigDB.all_as_keyword()
|
||||
|
||||
assert config[:pleroma] == [
|
||||
{saved.key, saved.value}
|
||||
|
@ -38,12 +38,12 @@ defmodule Pleroma.ConfigDBTest do
|
|||
|
||||
describe "update_or_create/1" do
|
||||
test "common" do
|
||||
config = insert(:config)
|
||||
config1 = insert(:config, value: [])
|
||||
key2 = :another_key
|
||||
|
||||
params = [
|
||||
%{group: :pleroma, key: key2, value: "another_value"},
|
||||
%{group: :pleroma, key: config.key, value: [a: 1, b: 2, c: "new_value"]}
|
||||
%{group: :pleroma, key: config1.key, value: [a: 1, b: 2, c: "new_value"]},
|
||||
%{group: :pleroma, key: key2, value: [new_val: "another_value"]}
|
||||
]
|
||||
|
||||
assert Repo.all(ConfigDB) |> length() == 1
|
||||
|
@ -52,11 +52,11 @@ defmodule Pleroma.ConfigDBTest do
|
|||
|
||||
assert Repo.all(ConfigDB) |> length() == 2
|
||||
|
||||
config1 = ConfigDB.get_by_params(%{group: config.group, key: config.key})
|
||||
config1 = ConfigDB.get_by_params(%{group: config1.group, key: config1.key})
|
||||
config2 = ConfigDB.get_by_params(%{group: :pleroma, key: key2})
|
||||
|
||||
assert config1.value == [a: 1, b: 2, c: "new_value"]
|
||||
assert config2.value == "another_value"
|
||||
assert config2.value == [new_val: "another_value"]
|
||||
end
|
||||
|
||||
test "partial update" do
|
||||
|
@ -95,50 +95,18 @@ defmodule Pleroma.ConfigDBTest do
|
|||
assert updated.value[:key3] == :val3
|
||||
end
|
||||
|
||||
test "only full update for some keys" do
|
||||
config1 = insert(:config, key: :ecto_repos, value: [repo: Pleroma.Repo])
|
||||
|
||||
config2 = insert(:config, group: :cors_plug, key: :max_age, value: 18)
|
||||
|
||||
{:ok, _config} =
|
||||
ConfigDB.update_or_create(%{
|
||||
group: config1.group,
|
||||
key: config1.key,
|
||||
value: [another_repo: [Pleroma.Repo]]
|
||||
})
|
||||
|
||||
{:ok, _config} =
|
||||
ConfigDB.update_or_create(%{
|
||||
group: config2.group,
|
||||
key: config2.key,
|
||||
value: 777
|
||||
})
|
||||
|
||||
updated1 = ConfigDB.get_by_params(%{group: config1.group, key: config1.key})
|
||||
updated2 = ConfigDB.get_by_params(%{group: config2.group, key: config2.key})
|
||||
|
||||
assert updated1.value == [another_repo: [Pleroma.Repo]]
|
||||
assert updated2.value == 777
|
||||
end
|
||||
|
||||
test "full update if value is not keyword" do
|
||||
config =
|
||||
insert(:config,
|
||||
group: ":tesla",
|
||||
key: ":adapter",
|
||||
value: Tesla.Adapter.Hackney
|
||||
)
|
||||
test "only full update for groups without keys" do
|
||||
config = insert(:config, group: :cors_plug, key: nil, value: [max_age: 18])
|
||||
|
||||
{:ok, _config} =
|
||||
ConfigDB.update_or_create(%{
|
||||
group: config.group,
|
||||
key: config.key,
|
||||
value: Tesla.Adapter.Httpc
|
||||
key: nil,
|
||||
value: [max_age: 25, credentials: true]
|
||||
})
|
||||
|
||||
updated = ConfigDB.get_by_params(%{group: config.group, key: config.key})
|
||||
|
||||
assert updated.value == Tesla.Adapter.Httpc
|
||||
assert updated.value == [max_age: 25, credentials: true]
|
||||
end
|
||||
|
||||
test "only full update for some subkeys" do
|
||||
|
@ -176,15 +144,14 @@ defmodule Pleroma.ConfigDBTest do
|
|||
end
|
||||
end
|
||||
|
||||
describe "delete/1" do
|
||||
describe "delete_or_update/1" do
|
||||
test "error on deleting non existing setting" do
|
||||
{:error, error} = ConfigDB.delete(%{group: ":pleroma", key: ":key"})
|
||||
assert error =~ "Config with params %{group: \":pleroma\", key: \":key\"} not found"
|
||||
assert {:ok, nil} == ConfigDB.delete_or_update(%{group: :pleroma, key: :key})
|
||||
end
|
||||
|
||||
test "full delete" do
|
||||
config = insert(:config)
|
||||
{:ok, deleted} = ConfigDB.delete(%{group: config.group, key: config.key})
|
||||
{:ok, deleted} = ConfigDB.delete_or_update(%{group: config.group, key: config.key})
|
||||
assert Ecto.get_meta(deleted, :state) == :deleted
|
||||
refute ConfigDB.get_by_params(%{group: config.group, key: config.key})
|
||||
end
|
||||
|
@ -193,7 +160,7 @@ defmodule Pleroma.ConfigDBTest do
|
|||
config = insert(:config, value: [groups: [a: 1, b: 2], key: [a: 1]])
|
||||
|
||||
{:ok, deleted} =
|
||||
ConfigDB.delete(%{group: config.group, key: config.key, subkeys: [":groups"]})
|
||||
ConfigDB.delete_or_update(%{group: config.group, key: config.key, subkeys: [:groups]})
|
||||
|
||||
assert Ecto.get_meta(deleted, :state) == :loaded
|
||||
|
||||
|
@ -208,339 +175,246 @@ defmodule Pleroma.ConfigDBTest do
|
|||
config = insert(:config, value: [groups: [a: 1, b: 2]])
|
||||
|
||||
{:ok, deleted} =
|
||||
ConfigDB.delete(%{group: config.group, key: config.key, subkeys: [":groups"]})
|
||||
ConfigDB.delete_or_update(%{group: config.group, key: config.key, subkeys: [:groups]})
|
||||
|
||||
assert Ecto.get_meta(deleted, :state) == :deleted
|
||||
|
||||
refute ConfigDB.get_by_params(%{group: config.group, key: config.key})
|
||||
end
|
||||
|
||||
test "delete struct" do
|
||||
config = insert(:config)
|
||||
{:ok, config} = ConfigDB.delete(config)
|
||||
assert Ecto.get_meta(config, :state) == :deleted
|
||||
assert Pleroma.Repo.aggregate(ConfigDB, :count) == 0
|
||||
end
|
||||
end
|
||||
|
||||
describe "to_elixir_types/1" do
|
||||
test "string" do
|
||||
assert ConfigDB.to_elixir_types("value as string") == "value as string"
|
||||
test "all/0" do
|
||||
config = insert(:config)
|
||||
|
||||
assert [^config] = ConfigDB.all()
|
||||
end
|
||||
|
||||
describe "reduce_defaults_and_merge_with_changes/2" do
|
||||
test "common changes" do
|
||||
defaults = [
|
||||
pleroma: [
|
||||
key1: [k1: 1, k2: 1, k3: 1],
|
||||
key2: [k1: 2, k2: 2, k3: 2]
|
||||
],
|
||||
logger: [k1: 3, k2: 3]
|
||||
]
|
||||
|
||||
config1 = insert(:config, key: :key1, value: [k1: 4, k2: 4])
|
||||
config2 = insert(:config, key: :key2, value: [k1: 5, k2: 5])
|
||||
|
||||
{changes, [logger: [k1: 3, k2: 3]]} =
|
||||
ConfigDB.reduce_defaults_and_merge_with_changes([config1, config2], defaults)
|
||||
|
||||
Enum.each(changes, fn
|
||||
%{key: :key1, value: value} ->
|
||||
assert value == [k3: 1, k1: 4, k2: 4]
|
||||
|
||||
%{key: :key2, value: value} ->
|
||||
assert value == [k3: 2, k1: 5, k2: 5]
|
||||
end)
|
||||
end
|
||||
|
||||
test "boolean" do
|
||||
assert ConfigDB.to_elixir_types(false) == false
|
||||
end
|
||||
test "changes for group without key" do
|
||||
defaults = [
|
||||
cors_plug: [
|
||||
max_age: 86_400,
|
||||
methods: ["POST", "PUT", "DELETE", "GET", "PATCH", "OPTIONS"]
|
||||
],
|
||||
pleroma: [key1: [k1: 1, k2: 1, k3: 1]]
|
||||
]
|
||||
|
||||
test "nil" do
|
||||
assert ConfigDB.to_elixir_types(nil) == nil
|
||||
end
|
||||
config = insert(:config, group: :cors_plug, key: nil, value: [max_age: 60_000])
|
||||
|
||||
test "integer" do
|
||||
assert ConfigDB.to_elixir_types(150) == 150
|
||||
end
|
||||
{[change], [pleroma: [key1: [k1: 1, k2: 1, k3: 1]]]} =
|
||||
ConfigDB.reduce_defaults_and_merge_with_changes([config], defaults)
|
||||
|
||||
test "atom" do
|
||||
assert ConfigDB.to_elixir_types(":atom") == :atom
|
||||
end
|
||||
|
||||
test "ssl options" do
|
||||
assert ConfigDB.to_elixir_types([":tlsv1", ":tlsv1.1", ":tlsv1.2"]) == [
|
||||
:tlsv1,
|
||||
:"tlsv1.1",
|
||||
:"tlsv1.2"
|
||||
assert change.value == [
|
||||
methods: ["POST", "PUT", "DELETE", "GET", "PATCH", "OPTIONS"],
|
||||
max_age: 60_000
|
||||
]
|
||||
end
|
||||
|
||||
test "pleroma module" do
|
||||
assert ConfigDB.to_elixir_types("Pleroma.Bookmark") == Pleroma.Bookmark
|
||||
test "for logger backend setting and others" do
|
||||
defaults = [
|
||||
logger: [
|
||||
ex_syslogger: [k1: 1, k2: 1],
|
||||
console: [k1: 2, k2: 2],
|
||||
backends: [:ex_syslogger, :console],
|
||||
key: 1
|
||||
],
|
||||
pleroma: [key1: 1, key2: 2]
|
||||
]
|
||||
|
||||
logger =
|
||||
insert(:config,
|
||||
group: :logger,
|
||||
key: nil,
|
||||
value: [ex_syslogger: [k1: 3, k2: 4], backends: [:console]]
|
||||
)
|
||||
|
||||
{[change], [pleroma: [key1: 1, key2: 2]]} =
|
||||
ConfigDB.reduce_defaults_and_merge_with_changes([logger], defaults)
|
||||
|
||||
assert change.value == [
|
||||
console: [k1: 2, k2: 2],
|
||||
key: 1,
|
||||
ex_syslogger: [k1: 3, k2: 4],
|
||||
backends: [:console]
|
||||
]
|
||||
end
|
||||
|
||||
test "pleroma string" do
|
||||
assert ConfigDB.to_elixir_types("Pleroma") == "Pleroma"
|
||||
end
|
||||
test "with ex_syslogger, console and backends changes" do
|
||||
defaults = [
|
||||
logger: [
|
||||
ex_syslogger: [k1: 1, k2: 1],
|
||||
console: [k1: 2, k2: 2],
|
||||
backends: [:ex_syslogger, :console],
|
||||
key: 1
|
||||
],
|
||||
pleroma: [key1: 1, key2: 2]
|
||||
]
|
||||
|
||||
test "phoenix module" do
|
||||
assert ConfigDB.to_elixir_types("Phoenix.Socket.V1.JSONSerializer") ==
|
||||
Phoenix.Socket.V1.JSONSerializer
|
||||
end
|
||||
logger =
|
||||
insert(:config,
|
||||
group: :logger,
|
||||
key: nil,
|
||||
value: [console: [k1: 4, k2: 4], k1: 3, k2: 4, backends: [:console]]
|
||||
)
|
||||
|
||||
test "tesla module" do
|
||||
assert ConfigDB.to_elixir_types("Tesla.Adapter.Hackney") == Tesla.Adapter.Hackney
|
||||
end
|
||||
{[change], [pleroma: [key1: 1, key2: 2]]} =
|
||||
ConfigDB.reduce_defaults_and_merge_with_changes([logger], defaults)
|
||||
|
||||
test "ExSyslogger module" do
|
||||
assert ConfigDB.to_elixir_types("ExSyslogger") == ExSyslogger
|
||||
assert change.value == [
|
||||
ex_syslogger: [k1: 1, k2: 1],
|
||||
key: 1,
|
||||
console: [k1: 4, k2: 4],
|
||||
k1: 3,
|
||||
k2: 4,
|
||||
backends: [:console]
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
test "Quack.Logger module" do
|
||||
assert ConfigDB.to_elixir_types("Quack.Logger") == Quack.Logger
|
||||
end
|
||||
test "all_with_db/0" do
|
||||
config = insert(:config)
|
||||
[change] = ConfigDB.all_with_db()
|
||||
assert change.db == Keyword.keys(config.value)
|
||||
end
|
||||
|
||||
test "Swoosh.Adapters modules" do
|
||||
assert ConfigDB.to_elixir_types("Swoosh.Adapters.SMTP") == Swoosh.Adapters.SMTP
|
||||
assert ConfigDB.to_elixir_types("Swoosh.Adapters.AmazonSES") == Swoosh.Adapters.AmazonSES
|
||||
end
|
||||
test "from_keyword_to_structs/2" do
|
||||
keyword = [
|
||||
pleroma: [
|
||||
key1: [k1: 1, k2: 1, k3: 1],
|
||||
key2: [k1: 2, k2: 2, k3: 2]
|
||||
],
|
||||
logger: [k1: 3, k2: 3, ex_syslogger: [k1: 4, k2: 4], console: [k1: 5, k2: 5]]
|
||||
]
|
||||
|
||||
test "sigil" do
|
||||
assert ConfigDB.to_elixir_types("~r[comp[lL][aA][iI][nN]er]") == ~r/comp[lL][aA][iI][nN]er/
|
||||
end
|
||||
changes = ConfigDB.from_keyword_to_structs(keyword)
|
||||
|
||||
test "link sigil" do
|
||||
assert ConfigDB.to_elixir_types("~r/https:\/\/example.com/") == ~r/https:\/\/example.com/
|
||||
end
|
||||
Enum.each(changes, fn
|
||||
%{key: :key1} = change ->
|
||||
assert change.group == :pleroma
|
||||
assert change.value == [k1: 1, k2: 1, k3: 1]
|
||||
|
||||
test "link sigil with um modifiers" do
|
||||
assert ConfigDB.to_elixir_types("~r/https:\/\/example.com/um") ==
|
||||
~r/https:\/\/example.com/um
|
||||
end
|
||||
%{key: :key2} = change ->
|
||||
assert change.group == :pleroma
|
||||
assert change.value == [k1: 2, k2: 2, k3: 2]
|
||||
|
||||
test "link sigil with i modifier" do
|
||||
assert ConfigDB.to_elixir_types("~r/https:\/\/example.com/i") == ~r/https:\/\/example.com/i
|
||||
end
|
||||
%{key: nil} = change ->
|
||||
assert change.group == :logger
|
||||
|
||||
test "link sigil with s modifier" do
|
||||
assert ConfigDB.to_elixir_types("~r/https:\/\/example.com/s") == ~r/https:\/\/example.com/s
|
||||
end
|
||||
|
||||
test "raise if valid delimiter not found" do
|
||||
assert_raise ArgumentError, "valid delimiter for Regex expression not found", fn ->
|
||||
ConfigDB.to_elixir_types("~r/https://[]{}<>\"'()|example.com/s")
|
||||
end
|
||||
end
|
||||
|
||||
test "2 child tuple" do
|
||||
assert ConfigDB.to_elixir_types(%{"tuple" => ["v1", ":v2"]}) == {"v1", :v2}
|
||||
end
|
||||
|
||||
test "proxy tuple with localhost" do
|
||||
assert ConfigDB.to_elixir_types(%{
|
||||
"tuple" => [":proxy_url", %{"tuple" => [":socks5", "localhost", 1234]}]
|
||||
}) == {:proxy_url, {:socks5, :localhost, 1234}}
|
||||
end
|
||||
|
||||
test "proxy tuple with domain" do
|
||||
assert ConfigDB.to_elixir_types(%{
|
||||
"tuple" => [":proxy_url", %{"tuple" => [":socks5", "domain.com", 1234]}]
|
||||
}) == {:proxy_url, {:socks5, 'domain.com', 1234}}
|
||||
end
|
||||
|
||||
test "proxy tuple with ip" do
|
||||
assert ConfigDB.to_elixir_types(%{
|
||||
"tuple" => [":proxy_url", %{"tuple" => [":socks5", "127.0.0.1", 1234]}]
|
||||
}) == {:proxy_url, {:socks5, {127, 0, 0, 1}, 1234}}
|
||||
end
|
||||
|
||||
test "tuple with n childs" do
|
||||
assert ConfigDB.to_elixir_types(%{
|
||||
"tuple" => [
|
||||
"v1",
|
||||
":v2",
|
||||
"Pleroma.Bookmark",
|
||||
150,
|
||||
false,
|
||||
"Phoenix.Socket.V1.JSONSerializer"
|
||||
assert change.value == [
|
||||
k1: 3,
|
||||
k2: 3,
|
||||
ex_syslogger: [k1: 4, k2: 4],
|
||||
console: [k1: 5, k2: 5]
|
||||
]
|
||||
}) == {"v1", :v2, Pleroma.Bookmark, 150, false, Phoenix.Socket.V1.JSONSerializer}
|
||||
end)
|
||||
end
|
||||
|
||||
describe "merge_changes_with_defaults/2" do
|
||||
test "with existance changes" do
|
||||
defaults = [
|
||||
pleroma: [
|
||||
key1: [k1: 1, k2: 1, k3: 1],
|
||||
key2: [k1: 2, k2: 2, k3: 2]
|
||||
],
|
||||
logger: [k1: 3, k2: 3]
|
||||
]
|
||||
|
||||
config1 = insert(:config, key: :key1, value: [k1: 4, k2: 4])
|
||||
config2 = insert(:config, key: :key2, value: [k1: 5, k2: 5])
|
||||
|
||||
changes = ConfigDB.merge_changes_with_defaults([config1, config2], defaults)
|
||||
|
||||
Enum.each(changes, fn
|
||||
%{key: :key1} = change -> assert change.value == [k3: 1, k1: 4, k2: 4]
|
||||
%{key: :key2} = change -> assert change.value == [k3: 2, k1: 5, k2: 5]
|
||||
end)
|
||||
end
|
||||
|
||||
test "map with string key" do
|
||||
assert ConfigDB.to_elixir_types(%{"key" => "value"}) == %{"key" => "value"}
|
||||
end
|
||||
test "full subkey update and deep merge" do
|
||||
defaults = [
|
||||
pleroma: [
|
||||
assets: [
|
||||
mascots: [3, 4],
|
||||
subkey: [key1: [key: :val2, key2: :val2], key2: :val2],
|
||||
key: 5
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
test "map with atom key" do
|
||||
assert ConfigDB.to_elixir_types(%{":key" => "value"}) == %{key: "value"}
|
||||
end
|
||||
config =
|
||||
insert(:config,
|
||||
group: :pleroma,
|
||||
key: :assets,
|
||||
value: [mascots: [1, 2], subkey: [key1: [key: :val1, key2: :val1], key2: :val1]]
|
||||
)
|
||||
|
||||
test "list of strings" do
|
||||
assert ConfigDB.to_elixir_types(["v1", "v2", "v3"]) == ["v1", "v2", "v3"]
|
||||
end
|
||||
[merged] = ConfigDB.merge_changes_with_defaults([config], defaults)
|
||||
|
||||
test "list of modules" do
|
||||
assert ConfigDB.to_elixir_types(["Pleroma.Repo", "Pleroma.Activity"]) == [
|
||||
Pleroma.Repo,
|
||||
Pleroma.Activity
|
||||
assert merged.value == [
|
||||
mascots: [1, 2],
|
||||
key: 5,
|
||||
subkey: [key1: [key: :val1, key2: :val1], key2: :val1]
|
||||
]
|
||||
end
|
||||
|
||||
test "list of atoms" do
|
||||
assert ConfigDB.to_elixir_types([":v1", ":v2", ":v3"]) == [:v1, :v2, :v3]
|
||||
test "merge for other subkeys" do
|
||||
defaults = [pleroma: [assets: [key: 5]]]
|
||||
|
||||
config =
|
||||
insert(:config,
|
||||
group: :pleroma,
|
||||
key: :assets,
|
||||
value: [subkey: 3, default_mascot: :test_mascot]
|
||||
)
|
||||
|
||||
[merged] = ConfigDB.merge_changes_with_defaults([config], defaults)
|
||||
assert merged.value == [key: 5, subkey: 3, default_mascot: :test_mascot]
|
||||
end
|
||||
|
||||
test "list of mixed values" do
|
||||
assert ConfigDB.to_elixir_types([
|
||||
"v1",
|
||||
":v2",
|
||||
"Pleroma.Repo",
|
||||
"Phoenix.Socket.V1.JSONSerializer",
|
||||
15,
|
||||
false
|
||||
]) == [
|
||||
"v1",
|
||||
:v2,
|
||||
Pleroma.Repo,
|
||||
Phoenix.Socket.V1.JSONSerializer,
|
||||
15,
|
||||
false
|
||||
]
|
||||
end
|
||||
test "with change deletion" do
|
||||
defaults = [pleroma: [assets: [key: 5]]]
|
||||
|
||||
test "simple keyword" do
|
||||
assert ConfigDB.to_elixir_types([%{"tuple" => [":key", "value"]}]) == [key: "value"]
|
||||
end
|
||||
config =
|
||||
insert(:config,
|
||||
group: :pleroma,
|
||||
key: :assets,
|
||||
value: [subkey: 3, default_mascot: :test_mascot]
|
||||
)
|
||||
|
||||
test "keyword" do
|
||||
assert ConfigDB.to_elixir_types([
|
||||
%{"tuple" => [":types", "Pleroma.PostgresTypes"]},
|
||||
%{"tuple" => [":telemetry_event", ["Pleroma.Repo.Instrumenter"]]},
|
||||
%{"tuple" => [":migration_lock", nil]},
|
||||
%{"tuple" => [":key1", 150]},
|
||||
%{"tuple" => [":key2", "string"]}
|
||||
]) == [
|
||||
types: Pleroma.PostgresTypes,
|
||||
telemetry_event: [Pleroma.Repo.Instrumenter],
|
||||
migration_lock: nil,
|
||||
key1: 150,
|
||||
key2: "string"
|
||||
]
|
||||
end
|
||||
|
||||
test "trandformed keyword" do
|
||||
assert ConfigDB.to_elixir_types(a: 1, b: 2, c: "string") == [a: 1, b: 2, c: "string"]
|
||||
end
|
||||
|
||||
test "complex keyword with nested mixed childs" do
|
||||
assert ConfigDB.to_elixir_types([
|
||||
%{"tuple" => [":uploader", "Pleroma.Uploaders.Local"]},
|
||||
%{"tuple" => [":filters", ["Pleroma.Upload.Filter.Dedupe"]]},
|
||||
%{"tuple" => [":link_name", true]},
|
||||
%{"tuple" => [":proxy_remote", false]},
|
||||
%{"tuple" => [":common_map", %{":key" => "value"}]},
|
||||
%{
|
||||
"tuple" => [
|
||||
":proxy_opts",
|
||||
[
|
||||
%{"tuple" => [":redirect_on_failure", false]},
|
||||
%{"tuple" => [":max_body_length", 1_048_576]},
|
||||
%{
|
||||
"tuple" => [
|
||||
":http",
|
||||
[
|
||||
%{"tuple" => [":follow_redirect", true]},
|
||||
%{"tuple" => [":pool", ":upload"]}
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
]) == [
|
||||
uploader: Pleroma.Uploaders.Local,
|
||||
filters: [Pleroma.Upload.Filter.Dedupe],
|
||||
link_name: true,
|
||||
proxy_remote: false,
|
||||
common_map: %{key: "value"},
|
||||
proxy_opts: [
|
||||
redirect_on_failure: false,
|
||||
max_body_length: 1_048_576,
|
||||
http: [
|
||||
follow_redirect: true,
|
||||
pool: :upload
|
||||
]
|
||||
]
|
||||
]
|
||||
end
|
||||
|
||||
test "common keyword" do
|
||||
assert ConfigDB.to_elixir_types([
|
||||
%{"tuple" => [":level", ":warn"]},
|
||||
%{"tuple" => [":meta", [":all"]]},
|
||||
%{"tuple" => [":path", ""]},
|
||||
%{"tuple" => [":val", nil]},
|
||||
%{"tuple" => [":webhook_url", "https://hooks.slack.com/services/YOUR-KEY-HERE"]}
|
||||
]) == [
|
||||
level: :warn,
|
||||
meta: [:all],
|
||||
path: "",
|
||||
val: nil,
|
||||
webhook_url: "https://hooks.slack.com/services/YOUR-KEY-HERE"
|
||||
]
|
||||
end
|
||||
|
||||
test "complex keyword with sigil" do
|
||||
assert ConfigDB.to_elixir_types([
|
||||
%{"tuple" => [":federated_timeline_removal", []]},
|
||||
%{"tuple" => [":reject", ["~r/comp[lL][aA][iI][nN]er/"]]},
|
||||
%{"tuple" => [":replace", []]}
|
||||
]) == [
|
||||
federated_timeline_removal: [],
|
||||
reject: [~r/comp[lL][aA][iI][nN]er/],
|
||||
replace: []
|
||||
]
|
||||
end
|
||||
|
||||
test "complex keyword with tuples with more than 2 values" do
|
||||
assert ConfigDB.to_elixir_types([
|
||||
%{
|
||||
"tuple" => [
|
||||
":http",
|
||||
[
|
||||
%{
|
||||
"tuple" => [
|
||||
":key1",
|
||||
[
|
||||
%{
|
||||
"tuple" => [
|
||||
":_",
|
||||
[
|
||||
%{
|
||||
"tuple" => [
|
||||
"/api/v1/streaming",
|
||||
"Pleroma.Web.MastodonAPI.WebsocketHandler",
|
||||
[]
|
||||
]
|
||||
},
|
||||
%{
|
||||
"tuple" => [
|
||||
"/websocket",
|
||||
"Phoenix.Endpoint.CowboyWebSocket",
|
||||
%{
|
||||
"tuple" => [
|
||||
"Phoenix.Transports.WebSocket",
|
||||
%{
|
||||
"tuple" => [
|
||||
"Pleroma.Web.Endpoint",
|
||||
"Pleroma.Web.UserSocket",
|
||||
[]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
%{
|
||||
"tuple" => [
|
||||
":_",
|
||||
"Phoenix.Endpoint.Cowboy2Handler",
|
||||
%{"tuple" => ["Pleroma.Web.Endpoint", []]}
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
]) == [
|
||||
http: [
|
||||
key1: [
|
||||
{:_,
|
||||
[
|
||||
{"/api/v1/streaming", Pleroma.Web.MastodonAPI.WebsocketHandler, []},
|
||||
{"/websocket", Phoenix.Endpoint.CowboyWebSocket,
|
||||
{Phoenix.Transports.WebSocket,
|
||||
{Pleroma.Web.Endpoint, Pleroma.Web.UserSocket, []}}},
|
||||
{:_, Phoenix.Endpoint.Cowboy2Handler, {Pleroma.Web.Endpoint, []}}
|
||||
]}
|
||||
]
|
||||
]
|
||||
]
|
||||
{:ok, config} = ConfigDB.delete(config)
|
||||
[merged] = ConfigDB.merge_changes_with_defaults([config], defaults)
|
||||
assert merged.value == [key: 5]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,7 +6,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
|
|||
use Pleroma.Web.ConnCase
|
||||
use Oban.Testing, repo: Pleroma.Repo
|
||||
|
||||
import ExUnit.CaptureLog
|
||||
import Pleroma.Factory
|
||||
import Swoosh.TestAssertions
|
||||
|
||||
|
@ -322,28 +321,12 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
|
|||
setup do: clear_config(:configurable_from_database, true)
|
||||
|
||||
test "pleroma restarts", %{conn: conn} do
|
||||
capture_log(fn ->
|
||||
assert conn |> get("/api/pleroma/admin/restart") |> json_response(200) == %{}
|
||||
end) =~ "pleroma restarted"
|
||||
assert conn |> get("/api/pleroma/admin/restart") |> json_response(200) == %{}
|
||||
|
||||
refute Restarter.Pleroma.need_reboot?()
|
||||
refute Pleroma.Application.ConfigDependentDeps.need_reboot?()
|
||||
end
|
||||
end
|
||||
|
||||
test "need_reboot flag", %{conn: conn} do
|
||||
assert conn
|
||||
|> get("/api/pleroma/admin/need_reboot")
|
||||
|> json_response(200) == %{"need_reboot" => false}
|
||||
|
||||
Restarter.Pleroma.need_reboot()
|
||||
|
||||
assert conn
|
||||
|> get("/api/pleroma/admin/need_reboot")
|
||||
|> json_response(200) == %{"need_reboot" => true}
|
||||
|
||||
on_exit(fn -> Restarter.Pleroma.refresh() end)
|
||||
end
|
||||
|
||||
describe "GET /api/pleroma/admin/users/:nickname/statuses" do
|
||||
setup do
|
||||
user = insert(:user)
|
||||
|
@ -999,10 +982,3 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Needed for testing
|
||||
defmodule Pleroma.Web.Endpoint.NotReal do
|
||||
end
|
||||
|
||||
defmodule Pleroma.Captcha.NotReal do
|
||||
end
|
||||
|
|
|
@ -5,10 +5,9 @@
|
|||
defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do
|
||||
use Pleroma.Web.ConnCase
|
||||
|
||||
import ExUnit.CaptureLog
|
||||
import Pleroma.Factory
|
||||
|
||||
alias Pleroma.ConfigDB
|
||||
alias Pleroma.Config
|
||||
|
||||
setup do
|
||||
admin = insert(:user, is_admin: true)
|
||||
|
@ -18,7 +17,9 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do
|
|||
build_conn()
|
||||
|> assign(:user, admin)
|
||||
|> assign(:token, token)
|
||||
|> put_req_header("content-type", "application/json")
|
||||
|
||||
on_exit(fn -> Pleroma.Application.ConfigDependentDeps.clear_state() end)
|
||||
{:ok, %{admin: admin, token: token, conn: conn}}
|
||||
end
|
||||
|
||||
|
@ -27,9 +28,10 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do
|
|||
|
||||
test "when configuration from database is off", %{conn: conn} do
|
||||
clear_config(:configurable_from_database, false)
|
||||
conn = get(conn, "/api/pleroma/admin/config")
|
||||
|
||||
assert json_response_and_validate_schema(conn, 400) ==
|
||||
assert conn
|
||||
|> get("/api/pleroma/admin/config")
|
||||
|> json_response_and_validate_schema(400) ==
|
||||
%{
|
||||
"error" => "You must enable configurable_from_database in your config file."
|
||||
}
|
||||
|
@ -61,7 +63,7 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do
|
|||
end
|
||||
|
||||
test "db is added to settings that are in db", %{conn: conn} do
|
||||
_config = insert(:config, key: ":instance", value: [name: "Some name"])
|
||||
_config = insert(:config, key: :instance, value: [name: "Some name"])
|
||||
|
||||
%{"configs" => configs} =
|
||||
conn
|
||||
|
@ -76,6 +78,27 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do
|
|||
assert instance_config["db"] == [":name"]
|
||||
end
|
||||
|
||||
test "setting with value not keyword", %{conn: conn} do
|
||||
_config =
|
||||
insert(:config,
|
||||
key: Pleroma.Web.Auth.Authenticator,
|
||||
value: Pleroma.Web.Auth.LDAPAuthenticator
|
||||
)
|
||||
|
||||
%{"configs" => configs} =
|
||||
conn
|
||||
|> get("/api/pleroma/admin/config")
|
||||
|> json_response_and_validate_schema(200)
|
||||
|
||||
[instance_config] =
|
||||
Enum.filter(configs, fn %{"group" => group, "key" => key} ->
|
||||
group == ":pleroma" and key == "Pleroma.Web.Auth.Authenticator"
|
||||
end)
|
||||
|
||||
assert instance_config["db"] == ["Pleroma.Web.Auth.Authenticator"]
|
||||
assert instance_config["value"] == "Pleroma.Web.Auth.LDAPAuthenticator"
|
||||
end
|
||||
|
||||
test "merged default setting with db settings", %{conn: conn} do
|
||||
config1 = insert(:config)
|
||||
config2 = insert(:config)
|
||||
|
@ -94,30 +117,19 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do
|
|||
|
||||
saved_configs = [config1, config2, config3]
|
||||
keys = Enum.map(saved_configs, &inspect(&1.key))
|
||||
values = Map.new(saved_configs, fn config -> {config.key, config.value} end)
|
||||
|
||||
received_configs =
|
||||
Enum.filter(configs, fn %{"group" => group, "key" => key} ->
|
||||
configs =
|
||||
configs
|
||||
|> Enum.filter(fn %{"group" => group, "key" => key} ->
|
||||
group == ":pleroma" and key in keys
|
||||
end)
|
||||
|> Config.Converter.to_elixir_types()
|
||||
|
||||
assert length(received_configs) == 3
|
||||
assert length(configs) == 3
|
||||
|
||||
db_keys =
|
||||
config3.value
|
||||
|> Keyword.keys()
|
||||
|> ConfigDB.to_json_types()
|
||||
|
||||
keys = Enum.map(saved_configs -- [config3], &inspect(&1.key))
|
||||
|
||||
values = Enum.map(saved_configs, &ConfigDB.to_json_types(&1.value))
|
||||
|
||||
mapset_keys = MapSet.new(keys ++ db_keys)
|
||||
|
||||
Enum.each(received_configs, fn %{"value" => value, "db" => db} ->
|
||||
db = MapSet.new(db)
|
||||
assert MapSet.subset?(db, mapset_keys)
|
||||
|
||||
assert value in values
|
||||
Enum.each(configs, fn %{"key" => key, "value" => value} ->
|
||||
assert values[key] == value
|
||||
end)
|
||||
end
|
||||
|
||||
|
@ -145,8 +157,8 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do
|
|||
emoji = Enum.find(vals, fn %{"key" => key} -> key == ":emoji" end)
|
||||
assets = Enum.find(vals, fn %{"key" => key} -> key == ":assets" end)
|
||||
|
||||
emoji_val = ConfigDB.to_elixir_types(emoji["value"])
|
||||
assets_val = ConfigDB.to_elixir_types(assets["value"])
|
||||
emoji_val = Config.Converter.to_elixir_types(emoji["value"])
|
||||
assets_val = Config.Converter.to_elixir_types(assets["value"])
|
||||
|
||||
assert emoji_val[:groups] == [a: 1, b: 2]
|
||||
assert assets_val[:mascots] == [a: 1, b: 2]
|
||||
|
@ -188,13 +200,11 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do
|
|||
Application.delete_env(:pleroma, Pleroma.Captcha.NotReal)
|
||||
Application.put_env(:pleroma, :http, http)
|
||||
Application.put_env(:tesla, :adapter, Tesla.Mock)
|
||||
Restarter.Pleroma.refresh()
|
||||
end)
|
||||
end
|
||||
|
||||
setup do: clear_config(:configurable_from_database, true)
|
||||
|
||||
@tag capture_log: true
|
||||
test "create new config setting in db", %{conn: conn} do
|
||||
ueberauth = Application.get_env(:ueberauth, Ueberauth)
|
||||
on_exit(fn -> Application.put_env(:ueberauth, Ueberauth, ueberauth) end)
|
||||
|
@ -204,7 +214,7 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do
|
|||
|> put_req_header("content-type", "application/json")
|
||||
|> post("/api/pleroma/admin/config", %{
|
||||
configs: [
|
||||
%{group: ":pleroma", key: ":key1", value: "value1"},
|
||||
%{group: ":pleroma", key: ":key1", value: [%{"tuple" => [":key", "value1"]}]},
|
||||
%{
|
||||
group: ":ueberauth",
|
||||
key: "Ueberauth",
|
||||
|
@ -213,31 +223,40 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do
|
|||
%{
|
||||
group: ":pleroma",
|
||||
key: ":key2",
|
||||
value: %{
|
||||
":nested_1" => "nested_value1",
|
||||
":nested_2" => [
|
||||
%{":nested_22" => "nested_value222"},
|
||||
%{":nested_33" => %{":nested_44" => "nested_444"}}
|
||||
]
|
||||
}
|
||||
value: [
|
||||
%{"tuple" => [":nested_1", "nested_value1"]},
|
||||
%{
|
||||
"tuple" => [
|
||||
":nested_2",
|
||||
[
|
||||
%{":nested_22" => "nested_value222"},
|
||||
%{":nested_33" => %{":nested_44" => "nested_444"}}
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
%{
|
||||
group: ":pleroma",
|
||||
key: ":key3",
|
||||
value: [
|
||||
%{"nested_3" => ":nested_3", "nested_33" => "nested_33"},
|
||||
%{"nested_4" => true}
|
||||
%{"tuple" => [":key", ":nested_3"]},
|
||||
%{"tuple" => [":nested_33", "nested_33"]},
|
||||
%{"tuple" => [":key", true]}
|
||||
]
|
||||
},
|
||||
%{
|
||||
group: ":pleroma",
|
||||
key: ":key4",
|
||||
value: %{":nested_5" => ":upload", "endpoint" => "https://example.com"}
|
||||
value: [
|
||||
%{"tuple" => [":nested_5", ":upload"]},
|
||||
%{"tuple" => [":endpoint", "https://example.com"]}
|
||||
]
|
||||
},
|
||||
%{
|
||||
group: ":idna",
|
||||
key: ":key5",
|
||||
value: %{"tuple" => ["string", "Pleroma.Captcha.NotReal", []]}
|
||||
value: [%{"tuple" => [":string", "Pleroma.Captcha.NotReal"]}]
|
||||
}
|
||||
]
|
||||
})
|
||||
|
@ -245,86 +264,92 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do
|
|||
assert json_response_and_validate_schema(conn, 200) == %{
|
||||
"configs" => [
|
||||
%{
|
||||
"group" => ":pleroma",
|
||||
"key" => ":key1",
|
||||
"value" => "value1",
|
||||
"db" => [":key1"]
|
||||
},
|
||||
%{
|
||||
"db" => [":consumer_secret"],
|
||||
"group" => ":ueberauth",
|
||||
"key" => "Ueberauth",
|
||||
"value" => [%{"tuple" => [":consumer_secret", "aaaa"]}],
|
||||
"db" => [":consumer_secret"]
|
||||
"value" => [%{"tuple" => [":consumer_secret", "aaaa"]}]
|
||||
},
|
||||
%{
|
||||
"db" => [":nested_5", ":endpoint"],
|
||||
"group" => ":pleroma",
|
||||
"key" => ":key2",
|
||||
"value" => %{
|
||||
":nested_1" => "nested_value1",
|
||||
":nested_2" => [
|
||||
%{":nested_22" => "nested_value222"},
|
||||
%{":nested_33" => %{":nested_44" => "nested_444"}}
|
||||
]
|
||||
},
|
||||
"db" => [":key2"]
|
||||
"key" => ":key4",
|
||||
"value" => [
|
||||
%{"tuple" => [":nested_5", ":upload"]},
|
||||
%{"tuple" => [":endpoint", "https://example.com"]}
|
||||
]
|
||||
},
|
||||
%{
|
||||
"db" => [":key", ":nested_33", ":key"],
|
||||
"group" => ":pleroma",
|
||||
"key" => ":key3",
|
||||
"value" => [
|
||||
%{"nested_3" => ":nested_3", "nested_33" => "nested_33"},
|
||||
%{"nested_4" => true}
|
||||
],
|
||||
"db" => [":key3"]
|
||||
%{"tuple" => [":key", ":nested_3"]},
|
||||
%{"tuple" => [":nested_33", "nested_33"]},
|
||||
%{"tuple" => [":key", true]}
|
||||
]
|
||||
},
|
||||
%{
|
||||
"db" => [":nested_1", ":nested_2"],
|
||||
"group" => ":pleroma",
|
||||
"key" => ":key4",
|
||||
"value" => %{"endpoint" => "https://example.com", ":nested_5" => ":upload"},
|
||||
"db" => [":key4"]
|
||||
"key" => ":key2",
|
||||
"value" => [
|
||||
%{"tuple" => [":nested_1", "nested_value1"]},
|
||||
%{
|
||||
"tuple" => [
|
||||
":nested_2",
|
||||
[
|
||||
%{":nested_22" => "nested_value222"},
|
||||
%{":nested_33" => %{":nested_44" => "nested_444"}}
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
%{
|
||||
"db" => [":key"],
|
||||
"group" => ":pleroma",
|
||||
"key" => ":key1",
|
||||
"value" => [%{"tuple" => [":key", "value1"]}]
|
||||
},
|
||||
%{
|
||||
"db" => [":string"],
|
||||
"group" => ":idna",
|
||||
"key" => ":key5",
|
||||
"value" => %{"tuple" => ["string", "Pleroma.Captcha.NotReal", []]},
|
||||
"db" => [":key5"]
|
||||
"value" => [%{"tuple" => [":string", "Pleroma.Captcha.NotReal"]}]
|
||||
}
|
||||
],
|
||||
"need_reboot" => false
|
||||
}
|
||||
|
||||
assert Application.get_env(:pleroma, :key1) == "value1"
|
||||
assert Application.get_env(:pleroma, :key1) == [key: "value1"]
|
||||
|
||||
assert Application.get_env(:pleroma, :key2) == %{
|
||||
assert Application.get_env(:pleroma, :key2) == [
|
||||
nested_1: "nested_value1",
|
||||
nested_2: [
|
||||
%{nested_22: "nested_value222"},
|
||||
%{nested_33: %{nested_44: "nested_444"}}
|
||||
]
|
||||
}
|
||||
|
||||
assert Application.get_env(:pleroma, :key3) == [
|
||||
%{"nested_3" => :nested_3, "nested_33" => "nested_33"},
|
||||
%{"nested_4" => true}
|
||||
]
|
||||
|
||||
assert Application.get_env(:pleroma, :key4) == %{
|
||||
"endpoint" => "https://example.com",
|
||||
nested_5: :upload
|
||||
}
|
||||
assert Application.get_env(:pleroma, :key3) == [
|
||||
key: :nested_3,
|
||||
nested_33: "nested_33",
|
||||
key: true
|
||||
]
|
||||
|
||||
assert Application.get_env(:idna, :key5) == {"string", Pleroma.Captcha.NotReal, []}
|
||||
assert Application.get_env(:pleroma, :key4) == [
|
||||
nested_5: :upload,
|
||||
endpoint: "https://example.com"
|
||||
]
|
||||
|
||||
assert Application.get_env(:idna, :key5) == [string: Pleroma.Captcha.NotReal]
|
||||
end
|
||||
|
||||
test "save configs setting without explicit key", %{conn: conn} do
|
||||
level = Application.get_env(:quack, :level)
|
||||
meta = Application.get_env(:quack, :meta)
|
||||
webhook_url = Application.get_env(:quack, :webhook_url)
|
||||
test "save configs setting without key", %{conn: conn} do
|
||||
quack_env = Application.get_all_env(:quack)
|
||||
|
||||
on_exit(fn ->
|
||||
Application.put_env(:quack, :level, level)
|
||||
Application.put_env(:quack, :meta, meta)
|
||||
Application.put_env(:quack, :webhook_url, webhook_url)
|
||||
Application.put_all_env(quack: quack_env)
|
||||
end)
|
||||
|
||||
conn =
|
||||
|
@ -334,18 +359,11 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do
|
|||
configs: [
|
||||
%{
|
||||
group: ":quack",
|
||||
key: ":level",
|
||||
value: ":info"
|
||||
},
|
||||
%{
|
||||
group: ":quack",
|
||||
key: ":meta",
|
||||
value: [":none"]
|
||||
},
|
||||
%{
|
||||
group: ":quack",
|
||||
key: ":webhook_url",
|
||||
value: "https://hooks.slack.com/services/KEY"
|
||||
value: [
|
||||
%{"tuple" => [":level", ":info"]},
|
||||
%{"tuple" => [":meta", [":none"]]},
|
||||
%{"tuple" => [":webhook_url", "https://hooks.slack.com/services/KEY"]}
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
|
@ -354,21 +372,13 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do
|
|||
"configs" => [
|
||||
%{
|
||||
"group" => ":quack",
|
||||
"key" => ":level",
|
||||
"value" => ":info",
|
||||
"db" => [":level"]
|
||||
},
|
||||
%{
|
||||
"group" => ":quack",
|
||||
"key" => ":meta",
|
||||
"value" => [":none"],
|
||||
"db" => [":meta"]
|
||||
},
|
||||
%{
|
||||
"group" => ":quack",
|
||||
"key" => ":webhook_url",
|
||||
"value" => "https://hooks.slack.com/services/KEY",
|
||||
"db" => [":webhook_url"]
|
||||
"key" => nil,
|
||||
"value" => [
|
||||
%{"tuple" => [":level", ":info"]},
|
||||
%{"tuple" => [":meta", [":none"]]},
|
||||
%{"tuple" => [":webhook_url", "https://hooks.slack.com/services/KEY"]}
|
||||
],
|
||||
"db" => [":level", ":meta", ":webhook_url"]
|
||||
}
|
||||
],
|
||||
"need_reboot" => false
|
||||
|
@ -380,7 +390,7 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do
|
|||
end
|
||||
|
||||
test "saving config with partial update", %{conn: conn} do
|
||||
insert(:config, key: ":key1", value: :erlang.term_to_binary(key1: 1, key2: 2))
|
||||
insert(:config, key: ":key1", value: [key1: 1, key2: 2])
|
||||
|
||||
conn =
|
||||
conn
|
||||
|
@ -440,10 +450,8 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do
|
|||
|
||||
assert configs["need_reboot"]
|
||||
|
||||
capture_log(fn ->
|
||||
assert conn |> get("/api/pleroma/admin/restart") |> json_response(200) ==
|
||||
%{}
|
||||
end) =~ "pleroma restarted"
|
||||
assert conn |> get("/api/pleroma/admin/restart") |> json_response(200) ==
|
||||
%{}
|
||||
|
||||
configs =
|
||||
conn
|
||||
|
@ -499,10 +507,8 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do
|
|||
"need_reboot" => true
|
||||
}
|
||||
|
||||
capture_log(fn ->
|
||||
assert conn |> get("/api/pleroma/admin/restart") |> json_response(200) ==
|
||||
%{}
|
||||
end) =~ "pleroma restarted"
|
||||
assert conn |> get("/api/pleroma/admin/restart") |> json_response(200) ==
|
||||
%{}
|
||||
|
||||
configs =
|
||||
conn
|
||||
|
@ -610,136 +616,86 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do
|
|||
]
|
||||
end
|
||||
|
||||
test "saving full setting if value is in full_key_update list", %{conn: conn} do
|
||||
backends = Application.get_env(:logger, :backends)
|
||||
on_exit(fn -> Application.put_env(:logger, :backends, backends) end)
|
||||
test "update config setting & delete with fallback to default value", %{conn: conn} do
|
||||
ueberauth = Application.get_env(:ueberauth, Ueberauth)
|
||||
insert(:config, key: :keyaa1, value: [key: "value"])
|
||||
insert(:config, key: :keyaa2, value: [key: "value"])
|
||||
|
||||
insert(:config,
|
||||
group: :logger,
|
||||
key: :backends,
|
||||
value: []
|
||||
)
|
||||
|
||||
Pleroma.Config.TransferTask.load_and_update_env([], false)
|
||||
|
||||
assert Application.get_env(:logger, :backends) == []
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header("content-type", "application/json")
|
||||
|> post("/api/pleroma/admin/config", %{
|
||||
resp =
|
||||
post(conn, "/api/pleroma/admin/config", %{
|
||||
configs: [
|
||||
%{
|
||||
group: ":logger",
|
||||
key: ":backends",
|
||||
value: [":console"]
|
||||
group: ":pleroma",
|
||||
key: ":keyaa1",
|
||||
value: [
|
||||
%{"tuple" => [":key", "value2"]},
|
||||
%{"tuple" => [":key2", "value"]}
|
||||
]
|
||||
},
|
||||
%{group: ":pleroma", key: ":keyaa2", value: [%{"tuple" => [":key", "value2"]}]},
|
||||
%{
|
||||
group: ":ueberauth",
|
||||
key: "Ueberauth",
|
||||
value: [
|
||||
%{"tuple" => [":another_key", "somevalue"]},
|
||||
%{"tuple" => [":another", "somevalue"]}
|
||||
]
|
||||
},
|
||||
%{
|
||||
group: ":pleroma",
|
||||
key: "Pleroma.Uploaders.Local",
|
||||
delete: true
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
assert json_response_and_validate_schema(conn, 200) == %{
|
||||
assert json_response_and_validate_schema(resp, 200) == %{
|
||||
"configs" => [
|
||||
%{
|
||||
"group" => ":logger",
|
||||
"key" => ":backends",
|
||||
"db" => [":another_key", ":another"],
|
||||
"group" => ":ueberauth",
|
||||
"key" => "Ueberauth",
|
||||
"value" => [
|
||||
":console"
|
||||
],
|
||||
"db" => [":backends"]
|
||||
}
|
||||
],
|
||||
"need_reboot" => false
|
||||
}
|
||||
|
||||
assert Application.get_env(:logger, :backends) == [
|
||||
:console
|
||||
]
|
||||
end
|
||||
|
||||
test "saving full setting if value is not keyword", %{conn: conn} do
|
||||
insert(:config,
|
||||
group: :tesla,
|
||||
key: :adapter,
|
||||
value: Tesla.Adapter.Hackey
|
||||
)
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header("content-type", "application/json")
|
||||
|> post("/api/pleroma/admin/config", %{
|
||||
configs: [
|
||||
%{group: ":tesla", key: ":adapter", value: "Tesla.Adapter.Httpc"}
|
||||
]
|
||||
})
|
||||
|
||||
assert json_response_and_validate_schema(conn, 200) == %{
|
||||
"configs" => [
|
||||
%{
|
||||
"group" => ":tesla",
|
||||
"key" => ":adapter",
|
||||
"value" => "Tesla.Adapter.Httpc",
|
||||
"db" => [":adapter"]
|
||||
}
|
||||
],
|
||||
"need_reboot" => false
|
||||
}
|
||||
end
|
||||
|
||||
test "update config setting & delete with fallback to default value", %{
|
||||
conn: conn,
|
||||
admin: admin,
|
||||
token: token
|
||||
} do
|
||||
ueberauth = Application.get_env(:ueberauth, Ueberauth)
|
||||
insert(:config, key: :keyaa1)
|
||||
insert(:config, key: :keyaa2)
|
||||
|
||||
config3 =
|
||||
insert(:config,
|
||||
group: :ueberauth,
|
||||
key: Ueberauth
|
||||
)
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header("content-type", "application/json")
|
||||
|> post("/api/pleroma/admin/config", %{
|
||||
configs: [
|
||||
%{group: ":pleroma", key: ":keyaa1", value: "another_value"},
|
||||
%{group: ":pleroma", key: ":keyaa2", value: "another_value"}
|
||||
]
|
||||
})
|
||||
|
||||
assert json_response_and_validate_schema(conn, 200) == %{
|
||||
"configs" => [
|
||||
%{
|
||||
"group" => ":pleroma",
|
||||
"key" => ":keyaa1",
|
||||
"value" => "another_value",
|
||||
"db" => [":keyaa1"]
|
||||
%{"tuple" => [":another_key", "somevalue"]},
|
||||
%{"tuple" => [":another", "somevalue"]}
|
||||
]
|
||||
},
|
||||
%{
|
||||
"group" => ":pleroma",
|
||||
"key" => ":keyaa2",
|
||||
"value" => "another_value",
|
||||
"db" => [":keyaa2"]
|
||||
"value" => [
|
||||
%{"tuple" => [":key", "value2"]}
|
||||
],
|
||||
"db" => [":key"]
|
||||
},
|
||||
%{
|
||||
"group" => ":pleroma",
|
||||
"key" => ":keyaa1",
|
||||
"value" => [
|
||||
%{"tuple" => [":key", "value2"]},
|
||||
%{"tuple" => [":key2", "value"]}
|
||||
],
|
||||
"db" => [":key", ":key2"]
|
||||
}
|
||||
],
|
||||
"need_reboot" => false
|
||||
}
|
||||
|
||||
assert Application.get_env(:pleroma, :keyaa1) == "another_value"
|
||||
assert Application.get_env(:pleroma, :keyaa2) == "another_value"
|
||||
assert Application.get_env(:ueberauth, Ueberauth) == config3.value
|
||||
assert Application.get_env(:pleroma, :keyaa1) == [key: "value2", key2: "value"]
|
||||
assert Application.get_env(:pleroma, :keyaa2) == [key: "value2"]
|
||||
|
||||
conn =
|
||||
build_conn()
|
||||
|> assign(:user, admin)
|
||||
|> assign(:token, token)
|
||||
|> put_req_header("content-type", "application/json")
|
||||
|> post("/api/pleroma/admin/config", %{
|
||||
assert Application.get_env(:ueberauth, Ueberauth) == [
|
||||
base_path: "/oauth",
|
||||
providers: [],
|
||||
another_key: "somevalue",
|
||||
another: "somevalue"
|
||||
]
|
||||
|
||||
resp =
|
||||
post(conn, "/api/pleroma/admin/config", %{
|
||||
configs: [
|
||||
%{group: ":pleroma", key: ":keyaa2", delete: true},
|
||||
%{group: ":pleroma", key: ":keyaa1", delete: true, subkeys: [":key"]},
|
||||
%{
|
||||
group: ":ueberauth",
|
||||
key: "Ueberauth",
|
||||
|
@ -748,8 +704,15 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do
|
|||
]
|
||||
})
|
||||
|
||||
assert json_response_and_validate_schema(conn, 200) == %{
|
||||
"configs" => [],
|
||||
assert json_response_and_validate_schema(resp, 200) == %{
|
||||
"configs" => [
|
||||
%{
|
||||
"db" => [":key2"],
|
||||
"group" => ":pleroma",
|
||||
"key" => ":keyaa1",
|
||||
"value" => [%{"tuple" => [":key2", "value"]}]
|
||||
}
|
||||
],
|
||||
"need_reboot" => false
|
||||
}
|
||||
|
||||
|
@ -1023,34 +986,6 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do
|
|||
}
|
||||
end
|
||||
|
||||
test "value as map", %{conn: conn} do
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header("content-type", "application/json")
|
||||
|> post("/api/pleroma/admin/config", %{
|
||||
configs: [
|
||||
%{
|
||||
"group" => ":pleroma",
|
||||
"key" => ":key1",
|
||||
"value" => %{"key" => "some_val"}
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
assert json_response_and_validate_schema(conn, 200) ==
|
||||
%{
|
||||
"configs" => [
|
||||
%{
|
||||
"group" => ":pleroma",
|
||||
"key" => ":key1",
|
||||
"value" => %{"key" => "some_val"},
|
||||
"db" => [":key1"]
|
||||
}
|
||||
],
|
||||
"need_reboot" => false
|
||||
}
|
||||
end
|
||||
|
||||
test "queues key as atom", %{conn: conn} do
|
||||
conn =
|
||||
conn
|
||||
|
@ -1228,7 +1163,6 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do
|
|||
assert ":proxy_url" in db
|
||||
end
|
||||
|
||||
@tag capture_log: true
|
||||
test "doesn't set keys not in the whitelist", %{conn: conn} do
|
||||
clear_config(:database_config_whitelist, [
|
||||
{:pleroma, :key1},
|
||||
|
@ -1241,21 +1175,29 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do
|
|||
|> put_req_header("content-type", "application/json")
|
||||
|> post("/api/pleroma/admin/config", %{
|
||||
configs: [
|
||||
%{group: ":pleroma", key: ":key1", value: "value1"},
|
||||
%{group: ":pleroma", key: ":key2", value: "value2"},
|
||||
%{group: ":pleroma", key: ":key3", value: "value3"},
|
||||
%{group: ":pleroma", key: "Pleroma.Web.Endpoint.NotReal", value: "value4"},
|
||||
%{group: ":pleroma", key: "Pleroma.Captcha.NotReal", value: "value5"},
|
||||
%{group: ":not_real", key: ":anything", value: "value6"}
|
||||
%{group: ":pleroma", key: ":key1", value: [%{"tuple" => [":key", "value1"]}]},
|
||||
%{group: ":pleroma", key: ":key2", value: [%{"tuple" => [":key", "value2"]}]},
|
||||
%{group: ":pleroma", key: ":key3", value: [%{"tuple" => [":key", "value3"]}]},
|
||||
%{
|
||||
group: ":pleroma",
|
||||
key: "Pleroma.Web.Endpoint.NotReal",
|
||||
value: [%{"tuple" => [":key", "value4"]}]
|
||||
},
|
||||
%{
|
||||
group: ":pleroma",
|
||||
key: "Pleroma.Captcha.NotReal",
|
||||
value: [%{"tuple" => [":key", "value5"]}]
|
||||
},
|
||||
%{group: ":not_real", key: ":anything", value: [%{"tuple" => [":key", "value6"]}]}
|
||||
]
|
||||
})
|
||||
|
||||
assert Application.get_env(:pleroma, :key1) == "value1"
|
||||
assert Application.get_env(:pleroma, :key2) == "value2"
|
||||
assert Application.get_env(:pleroma, :key1) == [key: "value1"]
|
||||
assert Application.get_env(:pleroma, :key2) == [key: "value2"]
|
||||
assert Application.get_env(:pleroma, :key3) == nil
|
||||
assert Application.get_env(:pleroma, Pleroma.Web.Endpoint.NotReal) == nil
|
||||
assert Application.get_env(:pleroma, Pleroma.Captcha.NotReal) == "value5"
|
||||
assert Application.get_env(:not_real, :anything) == "value6"
|
||||
assert Application.get_env(:pleroma, Pleroma.Captcha.NotReal) == [key: "value5"]
|
||||
assert Application.get_env(:not_real, :anything) == [key: "value6"]
|
||||
end
|
||||
|
||||
test "args for Pleroma.Upload.Filter.Mogrify with custom tuples", %{conn: conn} do
|
||||
|
@ -1445,14 +1387,83 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do
|
|||
"need_reboot" => false
|
||||
}
|
||||
|
||||
_res =
|
||||
assert conn
|
||||
|> get("/api/v1/instance")
|
||||
|> json_response_and_validate_schema(200)
|
||||
assert conn
|
||||
|> get("/api/v1/instance")
|
||||
|> json_response_and_validate_schema(200)
|
||||
|
||||
assert res = %{"thumbnail" => "https://example.com/media/new_thumbnail.jpg"}
|
||||
end
|
||||
|
||||
test "value bad format error", %{conn: conn} do
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header("content-type", "application/json")
|
||||
|> post("/api/pleroma/admin/config", %{
|
||||
configs: [
|
||||
%{
|
||||
group: ":quack",
|
||||
value: %{}
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
assert json_response_and_validate_schema(conn, 400) == %{
|
||||
"error" =>
|
||||
"Updating config failed: :value_must_be_keyword, group: quack, key: , value: %{}"
|
||||
}
|
||||
end
|
||||
|
||||
test "error when value is list", %{conn: conn} do
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header("content-type", "application/json")
|
||||
|> post("/api/pleroma/admin/config", %{
|
||||
configs: [
|
||||
%{
|
||||
group: ":quack",
|
||||
value: [1]
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
assert json_response_and_validate_schema(conn, 400) == %{
|
||||
"error" =>
|
||||
"Updating config failed: :value_must_be_keyword, group: quack, key: , value: [1]"
|
||||
}
|
||||
end
|
||||
|
||||
test "saving pleroma group with value not a keyword", %{conn: conn} do
|
||||
clear_config(Pleroma.Web.Auth.Authenticator)
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header("content-type", "application/json")
|
||||
|> post("/api/pleroma/admin/config", %{
|
||||
configs: [
|
||||
%{
|
||||
group: ":pleroma",
|
||||
key: "Pleroma.Web.Auth.Authenticator",
|
||||
value: "Pleroma.Web.Auth.LDAPAuthenticator"
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
assert json_response_and_validate_schema(conn, 200) == %{
|
||||
"configs" => [
|
||||
%{
|
||||
"db" => ["Pleroma.Web.Auth.Authenticator"],
|
||||
"group" => ":pleroma",
|
||||
"key" => "Pleroma.Web.Auth.Authenticator",
|
||||
"value" => "Pleroma.Web.Auth.LDAPAuthenticator"
|
||||
}
|
||||
],
|
||||
"need_reboot" => false
|
||||
}
|
||||
|
||||
assert Application.get_env(:pleroma, Pleroma.Web.Auth.Authenticator) ==
|
||||
Pleroma.Web.Auth.LDAPAuthenticator
|
||||
end
|
||||
|
||||
test "Concurrent Limiter", %{conn: conn} do
|
||||
clear_config([ConcurrentLimiter])
|
||||
|
||||
|
@ -1529,4 +1540,94 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do
|
|||
assert esshd["children"]
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET /api/pleroma/admin/config/versions/rollback" do
|
||||
setup do: clear_config(:configurable_from_database, true)
|
||||
|
||||
test "success rollback", %{conn: conn} do
|
||||
version = insert(:config_version)
|
||||
insert(:config_version)
|
||||
insert(:config_version, current: true)
|
||||
|
||||
conn
|
||||
|> get("/api/pleroma/admin/config/versions/rollback/#{version.id}")
|
||||
|> json_response_and_validate_schema(204)
|
||||
|
||||
[config] = Pleroma.Repo.all(Pleroma.ConfigDB)
|
||||
assert config.value == version.backup[config.group][config.key]
|
||||
end
|
||||
|
||||
test "not found error", %{conn: conn} do
|
||||
assert conn
|
||||
|> get("/api/pleroma/admin/config/versions/rollback/1")
|
||||
|> json_response_and_validate_schema(404) == %{
|
||||
"error" => "Not found"
|
||||
}
|
||||
end
|
||||
|
||||
test "on rollback to version, which is current", %{conn: conn} do
|
||||
version = insert(:config_version, current: true)
|
||||
|
||||
assert conn
|
||||
|> get("/api/pleroma/admin/config/versions/rollback/#{version.id}")
|
||||
|> json_response_and_validate_schema(400) == %{
|
||||
"error" => "Rollback is not possible: :version_is_already_current"
|
||||
}
|
||||
end
|
||||
|
||||
test "when configuration from database is off", %{conn: conn} do
|
||||
clear_config(:configurable_from_database, false)
|
||||
|
||||
assert conn
|
||||
|> get("/api/pleroma/admin/config/versions/rollback/1")
|
||||
|> json_response_and_validate_schema(400) ==
|
||||
%{
|
||||
"error" => "You must enable configurable_from_database in your config file."
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET /api/pleroma/admin/config/versions" do
|
||||
setup do: clear_config(:configurable_from_database, true)
|
||||
|
||||
test "with no versions", %{conn: conn} do
|
||||
assert conn
|
||||
|> get("/api/pleroma/admin/config/versions")
|
||||
|> json_response_and_validate_schema(200) == %{"versions" => []}
|
||||
end
|
||||
|
||||
test "with versions", %{conn: conn} do
|
||||
version = insert(:config_version, current: true)
|
||||
|
||||
assert conn
|
||||
|> get("/api/pleroma/admin/config/versions")
|
||||
|> json_response_and_validate_schema(200) == %{
|
||||
"versions" => [
|
||||
%{
|
||||
"current" => true,
|
||||
"id" => version.id,
|
||||
"inserted_at" => Pleroma.Web.CommonAPI.Utils.to_masto_date(version.inserted_at)
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
test "when configuration from database is off", %{conn: conn} do
|
||||
clear_config(:configurable_from_database, false)
|
||||
|
||||
assert conn
|
||||
|> get("/api/pleroma/admin/config/versions")
|
||||
|> json_response_and_validate_schema(400) ==
|
||||
%{
|
||||
"error" => "You must enable configurable_from_database in your config file."
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Needed for testing
|
||||
defmodule Pleroma.Web.Endpoint.NotReal do
|
||||
end
|
||||
|
||||
defmodule Pleroma.Captcha.NotReal do
|
||||
end
|
||||
|
|
|
@ -48,19 +48,11 @@ defmodule Pleroma.DataCase do
|
|||
end
|
||||
|
||||
def clear_cachex do
|
||||
Pleroma.Supervisor
|
||||
|> Supervisor.which_children()
|
||||
|> Enum.each(fn
|
||||
{name, _, _, [Cachex]} ->
|
||||
name
|
||||
|> to_string
|
||||
|> String.trim_leading("cachex_")
|
||||
|> Kernel.<>("_cache")
|
||||
|> String.to_existing_atom()
|
||||
|> Cachex.clear()
|
||||
|
||||
_ ->
|
||||
nil
|
||||
Pleroma.Application.StartUpDependencies.cachex_deps()
|
||||
|> Enum.each(fn {name, _} ->
|
||||
"#{name}_cache"
|
||||
|> String.to_existing_atom()
|
||||
|> Cachex.clear()
|
||||
end)
|
||||
end
|
||||
|
||||
|
|
|
@ -504,7 +504,7 @@ defmodule Pleroma.Factory do
|
|||
value:
|
||||
sequence(
|
||||
:value,
|
||||
&%{another_key: "#{&1}somevalue", another: "#{&1}somevalue"}
|
||||
&[another_key: "#{&1}somevalue", another: "#{&1}somevalue"]
|
||||
)
|
||||
}
|
||||
|> merge_attributes(attrs)
|
||||
|
@ -536,4 +536,11 @@ defmodule Pleroma.Factory do
|
|||
context: ["home"]
|
||||
}
|
||||
end
|
||||
|
||||
def config_version_factory do
|
||||
%Pleroma.Config.Version{
|
||||
backup: sequence(:value, &[pleroma: [instance: [name: "Instance name #{&1}"]]]),
|
||||
current: false
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue