From f3e8a0b72fc7b7f1fe5a00700c7cdd3556fc41cb Mon Sep 17 00:00:00 2001 From: Tim Scheuermann Date: Fri, 16 Dec 2022 16:29:26 +0100 Subject: [PATCH 01/37] [MM-49003] Close the pipe reader when done reading (#21854) --- jobs/export_process/worker.go | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/jobs/export_process/worker.go b/jobs/export_process/worker.go index 2697b65980..ddf9462b14 100644 --- a/jobs/export_process/worker.go +++ b/jobs/export_process/worker.go @@ -44,26 +44,24 @@ func MakeWorker(jobServer *jobs.JobServer, app AppIface) model.Worker { rd, wr := io.Pipe() - errCh := make(chan *model.AppError, 1) go func() { - defer close(errCh) - // Try to write without a timeout _, appErr := app.WriteFileContext(context.Background(), rd, filepath.Join(outPath, exportFilename)) - errCh <- appErr + if appErr != nil { + // we close the reader here to prevent a deadlock when the bulk exporter tries to + // write into the pipe while app.WriteFile has already returned. The error will be + // returned by the writer part of the pipe when app.BulkExport tries to call + // wr.Write() on it. + rd.CloseWithError(appErr) // CloseWithError never returns an error + } }() appErr := app.BulkExport(request.EmptyContext(app.Log()), wr, outPath, opts) - if err := wr.Close(); err != nil { - mlog.Warn("Worker: error closing writer") - } + wr.Close() // Close never returns an error if appErr != nil { return appErr } - if appErr := <-errCh; appErr != nil { - return appErr - } return nil } worker := jobs.NewSimpleWorker(jobName, jobServer, execute, isEnabled) From bef1f1cf601d0cd3d7bd0400bd1f8e34d1bc6ef7 Mon Sep 17 00:00:00 2001 From: Tim Scheuermann Date: Fri, 16 Dec 2022 16:44:51 +0100 Subject: [PATCH 02/37] Close pipes on file writer error (#21900) --- app/file.go | 1 + app/upload.go | 1 + 2 files changed, 2 insertions(+) diff --git a/app/file.go b/app/file.go index a47e817003..7a66d25010 100644 --- a/app/file.go +++ b/app/file.go @@ -824,6 +824,7 @@ func (t *UploadFileTask) postprocessImage(file io.Reader) { _, aerr := t.writeFile(r, path) if aerr != nil { mlog.Error("Unable to upload", mlog.String("path", path), mlog.Err(aerr)) + r.CloseWithError(aerr) // always returns nil return } } diff --git a/app/upload.go b/app/upload.go index ef4bff2b71..318e3ede89 100644 --- a/app/upload.go +++ b/app/upload.go @@ -93,6 +93,7 @@ func (a *App) runPluginsHook(c request.CTX, info *model.FileInfo, file io.Reader if fileErr := a.RemoveFile(tmpPath); fileErr != nil { mlog.Warn("Failed to remove file", mlog.Err(fileErr)) } + r.CloseWithError(err) // always returns nil return err } From 529fdd2643a32be5f67ea2b1d9b05d82b048658c Mon Sep 17 00:00:00 2001 From: Daniel Schalla Date: Fri, 16 Dec 2022 16:50:06 +0100 Subject: [PATCH 03/37] [MM-49170] Implement Auditable Interface for Patch Post Struct (#21898) * Implement Auditable Function for Patch Post Endpoint * Call auditable explicitly for patchPost patch parameter * Delete audit-config.json * Include HasReactions in Audit Log Entry for Patch --- api4/post.go | 3 ++- model/post.go | 11 ++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/api4/post.go b/api4/post.go index ff707c178c..34bcf21c2b 100644 --- a/api4/post.go +++ b/api4/post.go @@ -802,7 +802,8 @@ func patchPost(c *Context, w http.ResponseWriter, r *http.Request) { } auditRec := c.MakeAuditRecord("patchPost", audit.Fail) - auditRec.AddEventParameter("patch", post) + auditRec.AddEventParameter("id", c.Params.PostId) + auditRec.AddEventParameter("patch", post.Auditable()) defer c.LogAuditRecWithLevel(auditRec, app.LevelContent) // Updating the file_ids of a post is not a supported operation and will be ignored diff --git a/model/post.go b/model/post.go index 992188ac8e..2a7c4538e0 100644 --- a/model/post.go +++ b/model/post.go @@ -119,7 +119,7 @@ type Post struct { } func (o *Post) Auditable() map[string]interface{} { - return map[string]interface{}{ // TODO check this + return map[string]interface{}{ "id": o.Id, "create_at": o.CreateAt, "update_at": o.UpdateAt, @@ -195,6 +195,15 @@ func (o *PostPatch) WithRewrittenImageURLs(f func(string) string) *PostPatch { return © } +func (o *PostPatch) Auditable() map[string]interface{} { + return map[string]interface{}{ + "is_pinned": o.IsPinned, + "props": o.Props, + "file_ids": o.FileIds, + "has_reactions": o.HasReactions, + } +} + type PostForExport struct { Post TeamName string From f140b1863fd0674ba31e3516e5b60b2e141cf1c4 Mon Sep 17 00:00:00 2001 From: emmyni <44761757+emmyni@users.noreply.github.com> Date: Fri, 16 Dec 2022 16:34:40 -0500 Subject: [PATCH 04/37] [MM 48128]: Fix Spacing problem on "Payment Failed" email (#21881) --- templates/payment_failed_no_card_body.html | 27 ++++++++-------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/templates/payment_failed_no_card_body.html b/templates/payment_failed_no_card_body.html index 6966bf296b..ec7d4d5de6 100644 --- a/templates/payment_failed_no_card_body.html +++ b/templates/payment_failed_no_card_body.html @@ -1,7 +1,7 @@ {{define "payment_failed_no_card_body"}} -
@@ -18,23 +18,16 @@
- + style="border-collapse: collapse"> +
+ style="padding: 20px 0 0; text-align: center; margin: 0 auto; max-width: 443px"> -
- - - - -
-

- {{ .Props.Title }}

-
+
+

+ {{ .Props.Title }}

{{ .Props.Info1 }}

@@ -50,7 +43,7 @@
- +
@@ -65,7 +58,7 @@
- +
From 686c2cc84beb92a67db6d16fb89201cf7c73da18 Mon Sep 17 00:00:00 2001 From: MArtin Johnson Date: Mon, 19 Dec 2022 16:23:28 +0100 Subject: [PATCH 05/37] Translated using Weblate (Swedish) Currently translated at 100.0% (2429 of 2429 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/sv/ --- i18n/sv.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/i18n/sv.json b/i18n/sv.json index ec2aa98509..3d3d19a447 100644 --- a/i18n/sv.json +++ b/i18n/sv.json @@ -9725,5 +9725,9 @@ { "id": "api.templates.cloud_upgrade_confirmation_monthly.subtitle", "translation": "Din arbetsyta {{.WorkspaceName}} har nu uppgraderats. Du kommer att faktureras från och med {{.Date}}" + }, + { + "id": "model.draft.is_valid.props.app_error", + "translation": "Ogiltiga attribut." } ] From f58740f23fb7d22341da18f34829c50da5698ef4 Mon Sep 17 00:00:00 2001 From: jprusch Date: Mon, 19 Dec 2022 16:23:29 +0100 Subject: [PATCH 06/37] Translated using Weblate (German) Currently translated at 100.0% (2430 of 2430 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/de/ --- i18n/de.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/i18n/de.json b/i18n/de.json index d2ebc4bbd5..7daafc82da 100644 --- a/i18n/de.json +++ b/i18n/de.json @@ -9729,5 +9729,9 @@ { "id": "api.user.get_users.validation.app_error", "translation": "Fehler beim Abrufen von Rollen während der Validierung." + }, + { + "id": "api.server.hosted_signup_unavailable.error", + "translation": "Das Portal ist für selbst gehostete Anmeldungen nicht verfügbar." } ] From 43f89d75565b6e6aa828e8c8eb38b36577b88828 Mon Sep 17 00:00:00 2001 From: master7 Date: Mon, 19 Dec 2022 16:23:29 +0100 Subject: [PATCH 07/37] Translated using Weblate (Polish) Currently translated at 100.0% (2430 of 2430 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/pl/ --- i18n/pl.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/i18n/pl.json b/i18n/pl.json index 2489a694c5..cc23eeb202 100644 --- a/i18n/pl.json +++ b/i18n/pl.json @@ -9730,5 +9730,9 @@ { "id": "api.templates.cloud_upgrade_confirmation_monthly.subtitle", "translation": "Twoja {{.WorkspaceName}} została zaktualizowana. Opłaty będą naliczane od {{.Date}}" + }, + { + "id": "api.server.hosted_signup_unavailable.error", + "translation": "Portal niedostępny dla samodzielnej rejestracji." } ] From d9f339cb4c2acd78c418f22bf69e05b9efb136cd Mon Sep 17 00:00:00 2001 From: Konstantin Date: Mon, 19 Dec 2022 16:23:29 +0100 Subject: [PATCH 08/37] Translated using Weblate (Russian) Currently translated at 100.0% (2430 of 2430 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/ru/ --- i18n/ru.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/i18n/ru.json b/i18n/ru.json index 406b4220b0..b31289ad9c 100644 --- a/i18n/ru.json +++ b/i18n/ru.json @@ -9730,5 +9730,9 @@ { "id": "api.user.get_users.validation.app_error", "translation": "Ошибка при получении ролей во время проверки." + }, + { + "id": "api.server.hosted_signup_unavailable.error", + "translation": "Портал недоступен для самостоятельной регистрации." } ] From 5f6fc3150d9faf57743bad526c69eb6474a82e21 Mon Sep 17 00:00:00 2001 From: kaakaa Date: Mon, 19 Dec 2022 16:23:30 +0100 Subject: [PATCH 09/37] Translated using Weblate (Japanese) Currently translated at 100.0% (2430 of 2430 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/ja/ --- i18n/ja.json | 102 ++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 77 insertions(+), 25 deletions(-) diff --git a/i18n/ja.json b/i18n/ja.json index 4df39843dd..8e854aa2e9 100644 --- a/i18n/ja.json +++ b/i18n/ja.json @@ -289,7 +289,7 @@ }, { "id": "api.channel.update_channel_member_roles.scheme_role.app_error", - "translation": "与えられた役割はスキームによって管理されているため、チャンネルメンバーへ直接適用することはできません。" + "translation": "与えられたロールはスキームによって管理されているため、チャンネルメンバーへ直接適用することはできません。" }, { "id": "api.channel.update_channel_scheme.license.error", @@ -301,7 +301,7 @@ }, { "id": "api.channel.update_team_member_roles.scheme_role.app_error", - "translation": "与えられた役割はスキームによって管理されているため、チームメンバーへ直接適用することはできません。" + "translation": "与えられたロールはスキームによって管理されているため、チームメンバーへ直接適用することはできません。" }, { "id": "api.command.admin_only.app_error", @@ -2657,19 +2657,19 @@ }, { "id": "app.import.validate_role_import_data.description_invalid.error", - "translation": "役割の説明が不正です。" + "translation": "ロールの説明が不正です。" }, { "id": "app.import.validate_role_import_data.display_name_invalid.error", - "translation": "役割の表示名が不正です。" + "translation": "ロールの表示名が不正です。" }, { "id": "app.import.validate_role_import_data.invalid_permission.error", - "translation": "権限もしくは役割が不正です。" + "translation": "ロールもしくは役割が不正です。" }, { "id": "app.import.validate_role_import_data.name_invalid.error", - "translation": "役割の名前が不正です。" + "translation": "ロールの名前が不正です。" }, { "id": "app.import.validate_scheme_import_data.description_invalid.error", @@ -2693,7 +2693,7 @@ }, { "id": "app.import.validate_scheme_import_data.wrong_roles_for_scope.error", - "translation": "このスコープのスキームに誤った役割が与えられました。" + "translation": "このスコープのスキームに誤ったロールが与えられました。" }, { "id": "app.import.validate_team_import_data.description_length.error", @@ -2753,7 +2753,7 @@ }, { "id": "app.import.validate_user_channels_import_data.invalid_roles.error", - "translation": "ユーザーのチャネルメンバーシップの役割が不正です。" + "translation": "ユーザーのチャネルメンバーシップのロールが不正です。" }, { "id": "app.import.validate_user_import_data.auth_data_and_password.error", @@ -2825,7 +2825,7 @@ }, { "id": "app.import.validate_user_import_data.roles_invalid.error", - "translation": "ユーザーの役割が正しくありません。" + "translation": "ユーザーのロールが正しくありません。" }, { "id": "app.import.validate_user_import_data.username_invalid.error", @@ -2837,7 +2837,7 @@ }, { "id": "app.import.validate_user_teams_import_data.invalid_roles.error", - "translation": "ユーザーのチームメンバーシップの役割が不正です。" + "translation": "ユーザーのチームメンバーシップのロールが不正です。" }, { "id": "app.import.validate_user_teams_import_data.team_name_missing.error", @@ -2933,7 +2933,7 @@ }, { "id": "app.role.check_roles_exist.role_not_found", - "translation": "指定された役割は存在しません" + "translation": "指定されたロールは存在しません" }, { "id": "app.save_config.app_error", @@ -5709,7 +5709,7 @@ }, { "id": "api.channel.update_team_member_roles.changing_guest_role.app_error", - "translation": "不正なチームメンバ更新: 手動でゲストの役割を追加/削除することはできません。" + "translation": "不正なチームメンバ更新: 手動でゲストのロールを追加/削除することはできません。" }, { "id": "api.channel.update_channel_privacy.default_channel_error", @@ -5721,7 +5721,7 @@ }, { "id": "api.channel.update_channel_member_roles.changing_guest_role.app_error", - "translation": "不正なチャンネルメンバー更新: 手動でゲストの役割を追加/削除することはできません。" + "translation": "不正なチャンネルメンバー更新: 手動でゲストのロールを追加/削除することはできません。" }, { "id": "api.channel.update_channel.typechange.app_error", @@ -6437,27 +6437,27 @@ }, { "id": "app.role.save.invalid_role.app_error", - "translation": "役割が不正です。" + "translation": "ロールが不正です。" }, { "id": "app.role.save.insert.app_error", - "translation": "新しい役割を保存できませんでした。" + "translation": "新しいロールを保存できませんでした。" }, { "id": "app.role.permanent_delete_all.app_error", - "translation": "すべての役割を完全に削除できませんでした。" + "translation": "すべてのロールを完全に削除できませんでした。" }, { "id": "app.role.get_by_names.app_error", - "translation": "役割を取得できませんでした。" + "translation": "ロールを取得できませんでした。" }, { "id": "app.role.get_by_name.app_error", - "translation": "役割を取得できませんでした。" + "translation": "ロールを取得できませんでした。" }, { "id": "app.role.get.app_error", - "translation": "役割を取得できませんでした。" + "translation": "ロールを取得できませんでした。" }, { "id": "model.config.is_valid.directory.app_error", @@ -7181,7 +7181,7 @@ }, { "id": "api.server.warn_metric.number_of_channels_50.start_trial.notification_body", - "translation": "チャンネルはコミュニケーションの改善をサポートするものですが、Mattermost全体でチャンネルの作成や参加が多くなるにつれ、システムを整理されたものにし続けることが課題になってきます。高度な権限設定により、どのユーザー、もしくはどの役割の人が何のアクションを実行可能かを設定することができます。例えば、チャンネル設定やメンバーの管理や、@channel、@hereなどのタグによるグループへの発信、新たなウェブフックの作成などを制限できます。\n\n詳しくは[高度な権限設定の利用に関する説明](https://www.mattermost.com/docs-advanced-permissions/?utm_medium=product&utm_source=mattermost-advisor-bot&utm_content=advanced-permissions)を参照してください\n\nトライアル開始 をクリックすると、[Mattermost Software Evaluation Agreement](https://mattermost.com/software-evaluation-agreement/) と [プライバシーポリシー](https://mattermost.com/privacy-policy/)に同意したことになり、製品に関する電子メールを受信するようになります。" + "translation": "チャンネルはコミュニケーションの改善をサポートするものですが、Mattermost全体でチャンネルの作成や参加が多くなるにつれ、システムを整理されたものにし続けることが課題になってきます。高度な権限設定により、どのユーザー、もしくはどのロールの人が何のアクションを実行可能かを設定することができます。例えば、チャンネル設定やメンバーの管理や、@channel、@hereなどのタグによるグループへの発信、新たなウェブフックの作成などを制限できます。\n\n詳しくは[高度な権限設定の利用に関する説明](https://www.mattermost.com/docs-advanced-permissions/?utm_medium=product&utm_source=mattermost-advisor-bot&utm_content=advanced-permissions)を参照してください\n\nトライアル開始 をクリックすると、[Mattermost Software Evaluation Agreement](https://mattermost.com/software-evaluation-agreement/) と [プライバシーポリシー](https://mattermost.com/privacy-policy/)に同意したことになり、製品に関する電子メールを受信するようになります。" }, { "id": "api.server.warn_metric.number_of_channels_50.contact_us.email_body", @@ -7189,7 +7189,7 @@ }, { "id": "api.server.warn_metric.number_of_channels_50.notification_body", - "translation": "チャンネルはコミュニケーションの改善をサポートするものですが、Mattermost全体でチャンネルの作成や参加が多くなるにつれ、システムを整理されたものにし続けることが課題になってきます。高度な権限設定により、どのユーザー、もしくはどの役割の人が何のアクションを実行可能かを設定することができます。例えば、チャンネル設定やメンバーの管理や、@channel、@hereなどのタグによるグループへの発信、新たなウェブフックの作成などを制限できます。\n\n詳しくは[高度な権限設定の利用に関する説明](https://www.mattermost.com/docs-advanced-permissions/?utm_medium=product&utm_source=mattermost-advisor-bot&utm_content=advanced-permissions)を参照してください\n\n問い合わせ をクリックすると、あなたの情報が Mattermost, Inc. へ共有されます。詳しくは[説明文書](https://mattermost.com/pl/default-admin-advisory)を参照してください" + "translation": "チャンネルはコミュニケーションの改善をサポートするものですが、Mattermost全体でチャンネルの作成や参加が多くなるにつれ、システムを整理されたものにし続けることが課題になってきます。高度な権限設定により、どのユーザー、もしくはどのロールの人が何のアクションを実行可能かを設定することができます。例えば、チャンネル設定やメンバーの管理や、@channel、@hereなどのタグによるグループへの発信、新たなウェブフックの作成などを制限できます。\n\n詳しくは[高度な権限設定の利用に関する説明](https://www.mattermost.com/docs-advanced-permissions/?utm_medium=product&utm_source=mattermost-advisor-bot&utm_content=advanced-permissions)を参照してください\n\n問い合わせ をクリックすると、あなたの情報が Mattermost, Inc. へ共有されます。詳しくは[説明文書](https://mattermost.com/pl/default-admin-advisory)を参照してください" }, { "id": "api.server.warn_metric.number_of_channels_50.notification_title", @@ -8885,11 +8885,11 @@ }, { "id": "model.user.is_valid.roles_limit.app_error", - "translation": "{{.Limit}}文字以上の不正なユーザーの役割です。" + "translation": "{{.Limit}}文字以上の不正なユーザーのロールです。" }, { "id": "model.team_member.is_valid.roles_limit.app_error", - "translation": "{{.Limit}} 文字より長い不正なチームメンバーの役割です。" + "translation": "{{.Limit}} 文字より長い不正なチームメンバーのロールです。" }, { "id": "model.session.is_valid.user_id.app_error", @@ -8897,7 +8897,7 @@ }, { "id": "model.channel_member.is_valid.roles_limit.app_error", - "translation": "{{.Limit}} 文字より長い不正なチャンネルメンバーの役割です。" + "translation": "{{.Limit}} 文字より長い不正なチャンネルメンバーのロールです。" }, { "id": "model.session.is_valid.roles_limit.app_error", @@ -8937,7 +8937,7 @@ }, { "id": "app.role.get_all.app_error", - "translation": "全ての役割を取得できませんでした。" + "translation": "全てのロールを取得できませんでした。" }, { "id": "api.user.view_archived_channels.get_users_in_channel.app_error", @@ -9674,5 +9674,57 @@ { "id": "api.acknowledgement.delete.archived_channel.app_error", "translation": "アーカイブされたチャンネルでは、確認応答を削除することはできません。" + }, + { + "id": "worktemplate.product_teams.feature_release.description.playbook", + "translation": "開発チーム間で透明性の高いワークフローを作成し、機能開発プロセスをシームレスにすることができます。" + }, + { + "id": "worktemplate.product_teams.feature_release.description.integration", + "translation": "Jira BotやGitHub Botと統合し、生産性を高めましょう。これらはあなたのためにダウンロードされます。" + }, + { + "id": "worktemplate.product_teams.feature_release.description.channel", + "translation": "Boards、Playbooks、Botと簡単に接続できる Feature Release チャンネルでチームとチャットできます。" + }, + { + "id": "worktemplate.product_teams.feature_release.description.board", + "translation": "スタンドアップなどの定期的なミーティングには Meeting Agenda ボードテンプレート、タスクの進捗管理には Project Task ボードをご利用ください。" + }, + { + "id": "worktemplate.category.product_teams", + "translation": "製品チーム" + }, + { + "id": "model.draft.is_valid.priority.app_error", + "translation": "不正な優先度" + }, + { + "id": "app.worktemplates.get_templates.app_error", + "translation": "作業テンプレートを取得できませんでした" + }, + { + "id": "app.worktemplates.get_categories.app_error", + "translation": "作業テンプレートのカテゴリを取得できませんでした" + }, + { + "id": "api.user.get_users.validation.app_error", + "translation": "検証中のロール取得時にエラーが発生しました。" + }, + { + "id": "api.templates.cloud_welcome_email.yearly_plan_button", + "translation": "請求書を見る" + }, + { + "id": "api.templates.cloud_upgrade_confirmation_yearly.subtitle", + "translation": "ワークスペース {{.WorkspaceName}} がアップグレードされました。" + }, + { + "id": "api.templates.cloud_upgrade_confirmation_monthly.subtitle", + "translation": "ワークスペース {{.WorkspaceName}} がアップグレードされました。{{.Date}} から課金されます" + }, + { + "id": "api.server.hosted_signup_unavailable.error", + "translation": "セルフホスティングの利用登録では、ポータルは利用できません。" } ] From 79193240e9a5767ad2201bce3ef7f744320c3b7b Mon Sep 17 00:00:00 2001 From: Shivashis Padhi Date: Mon, 19 Dec 2022 22:31:59 +0530 Subject: [PATCH 10/37] [MM-44842] Add restore_group permission (#21806) * Add restore_group permission * Fix tests failing due to new permission in groups * Add new migration to add custom_group_restore permission * Add mock for new migration function * Fix tests Co-authored-by: Mattermod --- api4/group.go | 4 ++-- api4/group_test.go | 6 ++++++ app/app_test.go | 2 ++ app/permissions_migrations.go | 25 +++++++++++++++++++++++++ model/migration.go | 1 + model/permission.go | 9 +++++++++ model/role.go | 2 ++ testlib/store.go | 1 + 8 files changed, 48 insertions(+), 2 deletions(-) diff --git a/api4/group.go b/api4/group.go index 72c78e7179..59b9ad12c1 100644 --- a/api4/group.go +++ b/api4/group.go @@ -1185,8 +1185,8 @@ func restoreGroup(c *Context, w http.ResponseWriter, r *http.Request) { return } - if !c.App.SessionHasPermissionToGroup(*c.AppContext.Session(), c.Params.GroupId, model.PermissionDeleteCustomGroup) { - c.SetPermissionError(model.PermissionDeleteCustomGroup) + if !c.App.SessionHasPermissionToGroup(*c.AppContext.Session(), c.Params.GroupId, model.PermissionRestoreCustomGroup) { + c.SetPermissionError(model.PermissionRestoreCustomGroup) return } diff --git a/api4/group_test.go b/api4/group_test.go index a612291d8c..7fdf38147e 100644 --- a/api4/group_test.go +++ b/api4/group_test.go @@ -231,7 +231,13 @@ func TestUndeleteGroup(t *testing.T) { _, response, err := th.Client.DeleteGroup(validGroup.Id) require.NoError(t, err) CheckOKStatus(t, response) + th.RemovePermissionFromRole(model.PermissionRestoreCustomGroup.Id, model.SystemUserRoleId) + // shouldn't allow restoring unless user has required permission + _, response, err = th.Client.RestoreGroup(validGroup.Id, "") + require.Error(t, err) + CheckForbiddenStatus(t, response) + th.AddPermissionToRole(model.PermissionRestoreCustomGroup.Id, model.SystemUserRoleId) _, response, err = th.Client.RestoreGroup(validGroup.Id, "") require.NoError(t, err) CheckOKStatus(t, response) diff --git a/app/app_test.go b/app/app_test.go index 9f4d2fa0a9..05e12792b0 100644 --- a/app/app_test.go +++ b/app/app_test.go @@ -168,6 +168,7 @@ func TestDoAdvancedPermissionsMigration(t *testing.T) { model.PermissionCreateCustomGroup.Id, model.PermissionEditCustomGroup.Id, model.PermissionDeleteCustomGroup.Id, + model.PermissionRestoreCustomGroup.Id, model.PermissionManageCustomGroupMembers.Id, }, "system_post_all": { @@ -228,6 +229,7 @@ func TestDoEmojisPermissionsMigration(t *testing.T) { model.PermissionEditCustomGroup.Id, model.PermissionDeleteCustomGroup.Id, model.PermissionManageCustomGroupMembers.Id, + model.PermissionRestoreCustomGroup.Id, model.PermissionListPublicTeams.Id, model.PermissionJoinPublicTeams.Id, model.PermissionCreateDirectChannel.Id, diff --git a/app/permissions_migrations.go b/app/permissions_migrations.go index 34661b5d11..3bd85521fe 100644 --- a/app/permissions_migrations.go +++ b/app/permissions_migrations.go @@ -994,6 +994,30 @@ func (a *App) getAddCustomUserGroupsPermissions() (permissionsMap, error) { return t, nil } +func (a *App) getAddCustomUserGroupsPermissionRestore() (permissionsMap, error) { + t := []permissionTransformation{} + + customGroupPermissions := []string{ + model.PermissionRestoreCustomGroup.Id, + } + + t = append(t, permissionTransformation{ + On: isExactRole(model.SystemUserRoleId), + Add: customGroupPermissions, + }) + + t = append(t, permissionTransformation{ + On: isExactRole(model.SystemAdminRoleId), + Add: customGroupPermissions, + }) + + t = append(t, permissionTransformation{ + On: isExactRole(model.SystemCustomGroupAdminRoleId), + Add: customGroupPermissions, + }) + return t, nil +} + func (a *App) getAddPlaybooksPermissions() (permissionsMap, error) { transformations := []permissionTransformation{} @@ -1110,6 +1134,7 @@ func (s *Server) doPermissionsMigrations() error { {Key: model.MigrationKeyAddCustomUserGroupsPermissions, Migration: a.getAddCustomUserGroupsPermissions}, {Key: model.MigrationKeyAddPlayboosksManageRolesPermissions, Migration: a.getPlaybooksPermissionsAddManageRoles}, {Key: model.MigrationKeyAddProductsBoardsPermissions, Migration: a.getProductsBoardsPermissions}, + {Key: model.MigrationKeyAddCustomUserGroupsPermissionRestore, Migration: a.getAddCustomUserGroupsPermissionRestore}, } roles, err := s.Store().Role().GetAll() diff --git a/model/migration.go b/model/migration.go index e0e9ae2267..766e51598a 100644 --- a/model/migration.go +++ b/model/migration.go @@ -39,4 +39,5 @@ const ( MigrationKeyAddCustomUserGroupsPermissions = "custom_groups_permissions" MigrationKeyAddPlayboosksManageRolesPermissions = "playbooks_manage_roles" MigrationKeyAddProductsBoardsPermissions = "products_boards" + MigrationKeyAddCustomUserGroupsPermissionRestore = "custom_groups_permission_restore" ) diff --git a/model/permission.go b/model/permission.go index 76cf07c872..a44a566964 100644 --- a/model/permission.go +++ b/model/permission.go @@ -366,6 +366,7 @@ var PermissionCreateCustomGroup *Permission var PermissionManageCustomGroupMembers *Permission var PermissionEditCustomGroup *Permission var PermissionDeleteCustomGroup *Permission +var PermissionRestoreCustomGroup *Permission var AllPermissions []*Permission var DeprecatedPermissions []*Permission @@ -1960,6 +1961,13 @@ func initializePermissions() { PermissionScopeGroup, } + PermissionRestoreCustomGroup = &Permission{ + "restore_custom_group", + "authentication.permissions.restore_custom_group.name", + "authentication.permissions.restore_custom_group.description", + PermissionScopeGroup, + } + // Playbooks PermissionPublicPlaybookCreate = &Permission{ "playbook_public_create", @@ -2340,6 +2348,7 @@ func initializePermissions() { PermissionManageCustomGroupMembers, PermissionEditCustomGroup, PermissionDeleteCustomGroup, + PermissionRestoreCustomGroup, } DeprecatedPermissions = []*Permission{ diff --git a/model/role.go b/model/role.go index ac3fa3204e..b4a1825537 100644 --- a/model/role.go +++ b/model/role.go @@ -348,6 +348,7 @@ func init() { PermissionCreateCustomGroup.Id, PermissionEditCustomGroup.Id, PermissionDeleteCustomGroup.Id, + PermissionRestoreCustomGroup.Id, PermissionManageCustomGroupMembers.Id, } @@ -953,6 +954,7 @@ func MakeDefaultRoles() map[string]*Role { PermissionCreateCustomGroup.Id, PermissionEditCustomGroup.Id, PermissionDeleteCustomGroup.Id, + PermissionRestoreCustomGroup.Id, PermissionManageCustomGroupMembers.Id, }, SchemeManaged: true, diff --git a/testlib/store.go b/testlib/store.go index 800764da07..9f70dfa8cd 100644 --- a/testlib/store.go +++ b/testlib/store.go @@ -68,6 +68,7 @@ func GetMockStoreForSetupFunctions() *mocks.Store { systemStore.On("GetByName", model.MigrationKeyAddPlaybooksPermissions).Return(&model.System{Name: model.MigrationKeyAddPlaybooksPermissions, Value: "true"}, nil) systemStore.On("GetByName", model.MigrationKeyAddCustomUserGroupsPermissions).Return(&model.System{Name: model.MigrationKeyAddCustomUserGroupsPermissions, Value: "true"}, nil) systemStore.On("GetByName", model.MigrationKeyAddPlayboosksManageRolesPermissions).Return(&model.System{Name: model.MigrationKeyAddPlayboosksManageRolesPermissions, Value: "true"}, nil) + systemStore.On("GetByName", model.MigrationKeyAddCustomUserGroupsPermissionRestore).Return(&model.System{Name: model.MigrationKeyAddCustomUserGroupsPermissionRestore, Value: "true"}, nil) systemStore.On("GetByName", "CustomGroupAdminRoleCreationMigrationComplete").Return(&model.System{Name: model.MigrationKeyAddPlayboosksManageRolesPermissions, Value: "true"}, nil) systemStore.On("GetByName", "products_boards").Return(&model.System{Name: "products_boards", Value: "true"}, nil) systemStore.On("InsertIfExists", mock.AnythingOfType("*model.System")).Return(&model.System{}, nil).Once() From 0ce7c5e3da8b9a508f0467b31167cd65e50ed2a2 Mon Sep 17 00:00:00 2001 From: Tim Scheuermann Date: Tue, 20 Dec 2022 08:59:40 +0100 Subject: [PATCH 11/37] Use path rather than filepath for embedded files (#21896) Co-authored-by: Tim Scheuermann --- store/sqlstore/store.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/store/sqlstore/store.go b/store/sqlstore/store.go index 4c1bf1f05b..630e7705d3 100644 --- a/store/sqlstore/store.go +++ b/store/sqlstore/store.go @@ -9,7 +9,7 @@ import ( dbsql "database/sql" "fmt" "log" - "path/filepath" + "path" "strconv" "strings" "sync" @@ -1041,7 +1041,7 @@ func (ss *SqlStore) hasLicense() bool { func (ss *SqlStore) migrate(direction migrationDirection) error { assets := db.Assets() - assetsList, err := assets.ReadDir(filepath.Join("migrations", ss.DriverName())) + assetsList, err := assets.ReadDir(path.Join("migrations", ss.DriverName())) if err != nil { return err } @@ -1054,7 +1054,7 @@ func (ss *SqlStore) migrate(direction migrationDirection) error { src, err := mbindata.WithInstance(&mbindata.AssetSource{ Names: assetNamesForDriver, AssetFunc: func(name string) ([]byte, error) { - return assets.ReadFile(filepath.Join("migrations", ss.DriverName(), name)) + return assets.ReadFile(path.Join("migrations", ss.DriverName(), name)) }, }) if err != nil { From dcf499b51df70cdcb7133da13ddda3e3e6c3b740 Mon Sep 17 00:00:00 2001 From: Ibrahim Serdar Acikgoz Date: Wed, 21 Dec 2022 16:11:57 +0300 Subject: [PATCH 12/37] [MM-48626] Move plugins environment out of Channels (#21730) --- api4/plugin.go | 4 +- api4/plugin_test.go | 2 +- api4/websocket.go | 2 +- app/app_iface.go | 1 + app/channel.go | 10 +- app/channels.go | 74 +----- app/cluster_handlers.go | 11 +- app/collection.go | 16 +- app/download.go | 6 +- app/file.go | 2 +- app/integration_action.go | 6 +- app/login.go | 4 +- app/onboarding.go | 9 +- app/opentracing/opentracing_layer.go | 17 ++ app/plugin.go | 380 +++++++++++++++++---------- app/plugin_api.go | 4 +- app/plugin_api_test.go | 24 +- app/plugin_commands.go | 72 +++-- app/plugin_commands_test.go | 10 +- app/plugin_db_driver.go | 13 +- app/plugin_db_driver_test.go | 2 +- app/plugin_event.go | 6 +- app/plugin_hooks_test.go | 14 +- app/plugin_install.go | 118 ++++----- app/plugin_install_test.go | 8 +- app/plugin_requests.go | 28 +- app/plugin_requests_test.go | 2 +- app/plugin_shutdown_test.go | 2 +- app/plugin_signature.go | 8 +- app/plugin_statuses.go | 36 +-- app/plugin_test.go | 46 ++-- app/post.go | 8 +- app/reaction.go | 4 +- app/server.go | 14 +- app/team.go | 4 +- app/upload.go | 2 +- app/user.go | 2 +- app/web_conn.go | 2 +- cmd/mattermost/commands/init.go | 7 +- web/web_test.go | 4 +- 40 files changed, 537 insertions(+), 447 deletions(-) diff --git a/api4/plugin.go b/api4/plugin.go index be5b298d02..475aa62f21 100644 --- a/api4/plugin.go +++ b/api4/plugin.go @@ -155,7 +155,7 @@ func installMarketplacePlugin(c *Context, w http.ResponseWriter, r *http.Request // https://mattermost.atlassian.net/browse/MM-41981 pluginRequest.Version = "" - manifest, appErr := c.App.Channels().InstallMarketplacePlugin(pluginRequest) + manifest, appErr := c.App.PluginService().InstallMarketplacePlugin(pluginRequest) if appErr != nil { c.Err = appErr return @@ -235,7 +235,7 @@ func removePlugin(c *Context, w http.ResponseWriter, r *http.Request) { return } - err := c.App.Channels().RemovePlugin(c.Params.PluginId) + err := c.App.PluginService().RemovePlugin(c.Params.PluginId) if err != nil { c.Err = err return diff --git a/api4/plugin_test.go b/api4/plugin_test.go index 1967f9a617..3b656c209a 100644 --- a/api4/plugin_test.go +++ b/api4/plugin_test.go @@ -94,7 +94,7 @@ func TestPlugin(t *testing.T) { assert.Equal(t, "testplugin", manifest.Id) }) - th.App.Channels().RemovePlugin(manifest.Id) + th.App.PluginService().RemovePlugin(manifest.Id) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.Enable = false }) diff --git a/api4/websocket.go b/api4/websocket.go index d2c1c10f44..d8236e49b5 100644 --- a/api4/websocket.go +++ b/api4/websocket.go @@ -61,7 +61,7 @@ func connectWebSocket(c *Context, w http.ResponseWriter, r *http.Request) { } } - wc := c.App.Srv().Platform().NewWebConn(cfg, c.App, c.App.Srv().Channels()) + wc := c.App.Srv().Platform().NewWebConn(cfg, c.App, c.App.Srv()) if c.AppContext.Session().UserId != "" { c.App.Srv().Platform().HubRegister(wc) } diff --git a/app/app_iface.go b/app/app_iface.go index f10ca2d8b9..2674801101 100644 --- a/app/app_iface.go +++ b/app/app_iface.go @@ -936,6 +936,7 @@ type AppIface interface { PermanentDeleteTeamId(c request.CTX, teamID string) *model.AppError PermanentDeleteUser(c *request.Context, user *model.User) *model.AppError PluginCommandsForTeam(teamID string) []*model.Command + PluginService() *PluginService PostActionCookieSecret() []byte PostAddToChannelMessage(c request.CTX, user *model.User, addedUser *model.User, channel *model.Channel, postRootId string) *model.AppError PostPatchWithProxyRemovedFromImageURLs(patch *model.PostPatch) *model.PostPatch diff --git a/app/channel.go b/app/channel.go index fdc8d17da3..31a8673383 100644 --- a/app/channel.go +++ b/app/channel.go @@ -345,7 +345,7 @@ func (a *App) CreateChannel(c request.CTX, channel *model.Channel, addMember boo a.Srv().Go(func() { pluginContext := pluginContext(c) - a.ch.RunMultiHook(func(hooks plugin.Hooks) bool { + a.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { hooks.ChannelHasBeenCreated(pluginContext, sc) return true }, plugin.ChannelHasBeenCreatedID) @@ -429,7 +429,7 @@ func (a *App) handleCreationEvent(c request.CTX, userID, otherUserID string, cha a.Srv().Go(func() { pluginContext := pluginContext(c) - a.ch.RunMultiHook(func(hooks plugin.Hooks) bool { + a.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { hooks.ChannelHasBeenCreated(pluginContext, channel) return true }, plugin.ChannelHasBeenCreatedID) @@ -1597,7 +1597,7 @@ func (a *App) AddChannelMember(c request.CTX, userID string, channel *model.Chan a.Srv().Go(func() { pluginContext := pluginContext(c) - a.ch.RunMultiHook(func(hooks plugin.Hooks) bool { + a.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { hooks.UserHasJoinedChannel(pluginContext, cm, userRequestor) return true }, plugin.UserHasJoinedChannelID) @@ -2173,7 +2173,7 @@ func (a *App) JoinChannel(c request.CTX, channel *model.Channel, userID string) a.Srv().Go(func() { pluginContext := pluginContext(c) - a.ch.RunMultiHook(func(hooks plugin.Hooks) bool { + a.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { hooks.UserHasJoinedChannel(pluginContext, cm, nil) return true }, plugin.UserHasJoinedChannelID) @@ -2483,7 +2483,7 @@ func (a *App) removeUserFromChannel(c request.CTX, userIDToRemove string, remove a.Srv().Go(func() { pluginContext := pluginContext(c) - a.ch.RunMultiHook(func(hooks plugin.Hooks) bool { + a.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { hooks.UserHasLeftChannel(pluginContext, cm, actorUser) return true }, plugin.UserHasLeftChannelID) diff --git a/app/channels.go b/app/channels.go index 7ab024e45a..c9771f25d6 100644 --- a/app/channels.go +++ b/app/channels.go @@ -6,14 +6,11 @@ package app import ( "fmt" "runtime" - "strings" "sync" "github.com/pkg/errors" "github.com/mattermost/mattermost-server/v6/app/imaging" - "github.com/mattermost/mattermost-server/v6/app/request" - "github.com/mattermost/mattermost-server/v6/config" "github.com/mattermost/mattermost-server/v6/einterfaces" "github.com/mattermost/mattermost-server/v6/model" "github.com/mattermost/mattermost-server/v6/plugin" @@ -42,12 +39,6 @@ type Channels struct { postActionCookieSecret []byte - pluginCommandsLock sync.RWMutex - pluginCommands []*PluginCommand - pluginsLock sync.RWMutex - pluginsEnvironment *plugin.Environment - pluginConfigListenerID string - imageProxy *imageproxy.ImageProxy // cached counts that are used during notice condition validation @@ -79,12 +70,6 @@ type Channels struct { postReminderMut sync.Mutex postReminderTask *model.ScheduledTask - - // collectionTypes maps from collection types to the registering plugin id - collectionTypes map[string]string - // topicTypes maps from topic types to collection types - topicTypes map[string]string - collectionAndTopicTypesMut sync.Mutex } func init() { @@ -107,11 +92,9 @@ func NewChannels(services map[product.ServiceKey]any) (*Channels, error) { return nil, errors.New("server not passed") } ch := &Channels{ - srv: s, - imageProxy: imageproxy.MakeImageProxy(s.platform, s.httpService, s.Log()), - uploadLockMap: map[string]bool{}, - collectionTypes: map[string]string{}, - topicTypes: map[string]string{}, + srv: s, + imageProxy: imageproxy.MakeImageProxy(s.platform, s.httpService, s.Log()), + uploadLockMap: map[string]bool{}, } // To get another service: @@ -208,10 +191,6 @@ func NewChannels(services map[product.ServiceKey]any) (*Channels, error) { services[product.RouterKey] = ch.routerSvc // Setup routes. - pluginsRoute := ch.srv.Router.PathPrefix("/plugins/{plugin_id:[A-Za-z0-9\\_\\-\\.]+}").Subrouter() - pluginsRoute.HandleFunc("", ch.ServePluginRequest) - pluginsRoute.HandleFunc("/public/{public_file:.*}", ch.ServePluginPublicRequest) - pluginsRoute.HandleFunc("/{anything:.*}", ch.ServePluginRequest) services[product.PostKey] = &postServiceWrapper{ app: &App{ch: ch}, @@ -243,39 +222,6 @@ func NewChannels(services map[product.ServiceKey]any) (*Channels, error) { } func (ch *Channels) Start() error { - // Start plugins - ctx := request.EmptyContext(ch.srv.Log()) - ch.initPlugins(ctx, *ch.cfgSvc.Config().PluginSettings.Directory, *ch.cfgSvc.Config().PluginSettings.ClientDirectory) - - ch.AddConfigListener(func(prevCfg, cfg *model.Config) { - // We compute the difference between configs - // to ensure we don't re-init plugins unnecessarily. - diffs, err := config.Diff(prevCfg, cfg) - if err != nil { - ch.srv.Log().Warn("Error in comparing configs", mlog.Err(err)) - return - } - - hasDiff := false - // TODO: This could be a method on ConfigDiffs itself - for _, diff := range diffs { - if strings.HasPrefix(diff.Path, "PluginSettings.") { - hasDiff = true - break - } - } - - // Do only if some plugin related settings has changed. - if hasDiff { - if *cfg.PluginSettings.Enable { - ch.initPlugins(ctx, *cfg.PluginSettings.Directory, *ch.cfgSvc.Config().PluginSettings.ClientDirectory) - } else { - ch.ShutDownPlugins() - } - } - - }) - // TODO: This should be moved to the platform service. if err := ch.srv.platform.EnsureAsymmetricSigningKey(); err != nil { return errors.Wrapf(err, "unable to ensure asymmetric signing key") @@ -289,8 +235,6 @@ func (ch *Channels) Start() error { } func (ch *Channels) Stop() error { - ch.ShutDownPlugins() - ch.dndTaskMut.Lock() if ch.dndTask != nil { ch.dndTask.Cancel() @@ -332,18 +276,18 @@ func (s *hooksService) RegisterHooks(productID string, hooks any) error { return s.ch.srv.hooksManager.AddProduct(productID, hooks) } -func (ch *Channels) RunMultiHook(hookRunnerFunc func(hooks plugin.Hooks) bool, hookId int) { - if env := ch.GetPluginsEnvironment(); env != nil { +func (s *Server) RunMultiHook(hookRunnerFunc func(hooks plugin.Hooks) bool, hookId int) { + if env := s.pluginService.GetPluginsEnvironment(); env != nil { env.RunMultiPluginHook(hookRunnerFunc, hookId) } // run hook for the products - ch.srv.hooksManager.RunMultiHook(hookRunnerFunc, hookId) + s.hooksManager.RunMultiHook(hookRunnerFunc, hookId) } -func (ch *Channels) HooksForPluginOrProduct(id string) (plugin.Hooks, error) { +func (s *Server) HooksForPluginOrProduct(id string) (plugin.Hooks, error) { var hooks plugin.Hooks - if env := ch.GetPluginsEnvironment(); env != nil { + if env := s.pluginService.GetPluginsEnvironment(); env != nil { // we intentionally ignore the error here, because the id can be a product id // we are going to check if we have the hooks or not hooks, _ = env.HooksForPlugin(id) @@ -352,7 +296,7 @@ func (ch *Channels) HooksForPluginOrProduct(id string) (plugin.Hooks, error) { } } - hooks = ch.srv.hooksManager.HooksForProduct(id) + hooks = s.hooksManager.HooksForProduct(id) if hooks != nil { return hooks, nil } diff --git a/app/cluster_handlers.go b/app/cluster_handlers.go index 3fa90abf1e..1cebe5596d 100644 --- a/app/cluster_handlers.go +++ b/app/cluster_handlers.go @@ -16,7 +16,7 @@ func (s *Server) clusterInstallPluginHandler(msg *model.ClusterMessage) { if jsonErr := json.Unmarshal(msg.Data, &data); jsonErr != nil { mlog.Warn("Failed to decode from JSON", mlog.Err(jsonErr)) } - s.Channels().installPluginFromData(data) + s.pluginService.installPluginFromData(data) } func (s *Server) clusterRemovePluginHandler(msg *model.ClusterMessage) { @@ -24,7 +24,7 @@ func (s *Server) clusterRemovePluginHandler(msg *model.ClusterMessage) { if jsonErr := json.Unmarshal(msg.Data, &data); jsonErr != nil { mlog.Warn("Failed to decode from JSON", mlog.Err(jsonErr)) } - s.Channels().removePluginFromData(data) + s.pluginService.removePluginFromData(data) } func (s *Server) clusterPluginEventHandler(msg *model.ClusterMessage) { @@ -44,12 +44,7 @@ func (s *Server) clusterPluginEventHandler(msg *model.ClusterMessage) { return } - channels, ok := s.products["channels"].(*Channels) - if !ok { - return - } - - hooks, err := channels.HooksForPluginOrProduct(pluginID) + hooks, err := s.HooksForPluginOrProduct(pluginID) if err != nil { mlog.Warn("Getting hooks for plugin failed", mlog.String("plugin_id", pluginID), mlog.Err(err)) return diff --git a/app/collection.go b/app/collection.go index 9b895e3bc0..ff489a18db 100644 --- a/app/collection.go +++ b/app/collection.go @@ -10,26 +10,26 @@ import ( "github.com/mattermost/mattermost-server/v6/shared/mlog" ) -func (a *App) registerCollectionAndTopic(pluginID, collectionType, topicType string) error { +func (s *PluginService) registerCollectionAndTopic(pluginID, collectionType, topicType string) error { // we have a race condition due to multiple plugins calling this method - a.ch.collectionAndTopicTypesMut.Lock() - defer a.ch.collectionAndTopicTypesMut.Unlock() + s.collectionAndTopicTypesMut.Lock() + defer s.collectionAndTopicTypesMut.Unlock() // check if collectionType was already registered by other plugin - existingPluginID, ok := a.ch.collectionTypes[collectionType] + existingPluginID, ok := s.collectionTypes[collectionType] if ok && existingPluginID != pluginID { return model.NewAppError("registerCollectionAndTopic", "app.collection.add_collection.exists.app_error", nil, "", http.StatusBadRequest) } // check if topicType was already registered to other collection - existingCollectionType, ok := a.ch.topicTypes[topicType] + existingCollectionType, ok := s.topicTypes[topicType] if ok && existingCollectionType != collectionType { return model.NewAppError("registerCollectionAndTopic", "app.collection.add_topic.exists.app_error", nil, "", http.StatusBadRequest) } - a.ch.collectionTypes[collectionType] = pluginID - a.ch.topicTypes[topicType] = collectionType + s.collectionTypes[collectionType] = pluginID + s.topicTypes[topicType] = collectionType - a.ch.srv.Log().Info("registered collection and topic type", mlog.String("plugin_id", pluginID), mlog.String("collection_type", collectionType), mlog.String("topic_type", topicType)) + s.platform.Log().Info("registered collection and topic type", mlog.String("plugin_id", pluginID), mlog.String("collection_type", collectionType), mlog.String("topic_type", topicType)) return nil } diff --git a/app/download.go b/app/download.go index 449f787c46..56438507cc 100644 --- a/app/download.go +++ b/app/download.go @@ -22,10 +22,10 @@ const ( ) func (a *App) DownloadFromURL(downloadURL string) ([]byte, error) { - return a.Srv().downloadFromURL(downloadURL) + return a.Srv().pluginService.downloadFromURL(downloadURL) } -func (s *Server) downloadFromURL(downloadURL string) ([]byte, error) { +func (s *PluginService) downloadFromURL(downloadURL string) ([]byte, error) { if !model.IsValidHTTPURL(downloadURL) { return nil, errors.Errorf("invalid url %s", downloadURL) } @@ -38,7 +38,7 @@ func (s *Server) downloadFromURL(downloadURL string) ([]byte, error) { return nil, errors.Errorf("insecure url not allowed %s", downloadURL) } - client := s.HTTPService().MakeClient(true) + client := s.httpService.MakeClient(true) client.Timeout = HTTPRequestTimeout var resp *http.Response diff --git a/app/file.go b/app/file.go index 7a66d25010..e1acafa827 100644 --- a/app/file.go +++ b/app/file.go @@ -926,7 +926,7 @@ func (a *App) DoUploadFileExpectModification(c request.CTX, now time.Time, rawTe var rejectionError *model.AppError pluginContext := pluginContext(c) - a.ch.RunMultiHook(func(hooks plugin.Hooks) bool { + a.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { var newBytes bytes.Buffer replacementInfo, rejectionReason := hooks.FileWillBeUploaded(pluginContext, info, bytes.NewReader(data), &newBytes) if rejectionReason != "" { diff --git a/app/integration_action.go b/app/integration_action.go index 4ae7e97e07..51bef2b86f 100644 --- a/app/integration_action.go +++ b/app/integration_action.go @@ -375,10 +375,10 @@ func (w *LocalResponseWriter) WriteHeader(statusCode int) { } func (a *App) doPluginRequest(c *request.Context, method, rawURL string, values url.Values, body []byte) (*http.Response, *model.AppError) { - return a.ch.doPluginRequest(c, method, rawURL, values, body) + return a.ch.srv.pluginService.doPluginRequest(c, method, rawURL, values, body) } -func (ch *Channels) doPluginRequest(c *request.Context, method, rawURL string, values url.Values, body []byte) (*http.Response, *model.AppError) { +func (s *PluginService) doPluginRequest(c *request.Context, method, rawURL string, values url.Values, body []byte) (*http.Response, *model.AppError) { rawURL = strings.TrimPrefix(rawURL, "/") inURL, err := url.Parse(rawURL) if err != nil { @@ -427,7 +427,7 @@ func (ch *Channels) doPluginRequest(c *request.Context, method, rawURL string, v params["plugin_id"] = pluginID r = mux.SetURLVars(r, params) - ch.ServePluginRequest(w, r) + s.ServePluginRequest(w, r) resp := &http.Response{ StatusCode: w.status, diff --git a/app/login.go b/app/login.go index e0854bef37..af322bd13b 100644 --- a/app/login.go +++ b/app/login.go @@ -159,7 +159,7 @@ func (a *App) GetUserForLogin(id, loginId string) (*model.User, *model.AppError) func (a *App) DoLogin(c *request.Context, w http.ResponseWriter, r *http.Request, user *model.User, deviceID string, isMobile, isOAuthUser, isSaml bool) *model.AppError { var rejectionReason string pluginContext := pluginContext(c) - a.ch.RunMultiHook(func(hooks plugin.Hooks) bool { + a.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { rejectionReason = hooks.UserWillLogIn(pluginContext, user) return rejectionReason == "" }, plugin.UserWillLogInID) @@ -225,7 +225,7 @@ func (a *App) DoLogin(c *request.Context, w http.ResponseWriter, r *http.Request } a.Srv().Go(func() { - a.ch.RunMultiHook(func(hooks plugin.Hooks) bool { + a.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { hooks.UserHasLoggedIn(pluginContext, user) return true }, plugin.UserHasLoggedInID) diff --git a/app/onboarding.go b/app/onboarding.go index d76525f017..fe40e9d997 100644 --- a/app/onboarding.go +++ b/app/onboarding.go @@ -28,11 +28,6 @@ func (a *App) markAdminOnboardingComplete(c *request.Context) *model.AppError { } func (a *App) CompleteOnboarding(c *request.Context, request *model.CompleteOnboardingRequest) *model.AppError { - pluginsEnvironment := a.Channels().GetPluginsEnvironment() - if pluginsEnvironment == nil { - return a.markAdminOnboardingComplete(c) - } - pluginContext := pluginContext(c) for _, pluginID := range request.InstallPlugins { @@ -41,7 +36,7 @@ func (a *App) CompleteOnboarding(c *request.Context, request *model.CompleteOnbo installRequest := &model.InstallMarketplacePluginRequest{ Id: id, } - _, appErr := a.Channels().InstallMarketplacePlugin(installRequest) + _, appErr := a.Srv().pluginService.InstallMarketplacePlugin(installRequest) if appErr != nil { mlog.Error("Failed to install plugin for onboarding", mlog.String("id", id), mlog.Err(appErr)) return @@ -53,7 +48,7 @@ func (a *App) CompleteOnboarding(c *request.Context, request *model.CompleteOnbo return } - hooks, err := a.ch.HooksForPluginOrProduct(id) + hooks, err := a.Srv().HooksForPluginOrProduct(id) if err != nil { mlog.Warn("Getting hooks for plugin failed", mlog.String("plugin_id", id), mlog.Err(err)) return diff --git a/app/opentracing/opentracing_layer.go b/app/opentracing/opentracing_layer.go index 367d98ec39..3d6c2b5ad5 100644 --- a/app/opentracing/opentracing_layer.go +++ b/app/opentracing/opentracing_layer.go @@ -13125,6 +13125,23 @@ func (a *OpenTracingAppLayer) PluginCommandsForTeam(teamID string) []*model.Comm return resultVar0 } +func (a *OpenTracingAppLayer) PluginService() *app.PluginService { + origCtx := a.ctx + span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.PluginService") + + a.ctx = newCtx + a.app.Srv().Store().SetContext(newCtx) + defer func() { + a.app.Srv().Store().SetContext(origCtx) + a.ctx = origCtx + }() + + defer span.Finish() + resultVar0 := a.app.PluginService() + + return resultVar0 +} + func (a *OpenTracingAppLayer) PopulateWebConnConfig(s *model.Session, cfg *platform.WebConnConfig, seqVal string) (*platform.WebConnConfig, error) { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.PopulateWebConnConfig") diff --git a/app/plugin.go b/app/plugin.go index 679576e723..d985455e5b 100644 --- a/app/plugin.go +++ b/app/plugin.go @@ -20,16 +20,37 @@ import ( svg "github.com/h2non/go-is-svg" "github.com/pkg/errors" + "github.com/mattermost/mattermost-server/v6/app/platform" "github.com/mattermost/mattermost-server/v6/app/request" + "github.com/mattermost/mattermost-server/v6/config" "github.com/mattermost/mattermost-server/v6/model" "github.com/mattermost/mattermost-server/v6/plugin" "github.com/mattermost/mattermost-server/v6/product" + "github.com/mattermost/mattermost-server/v6/services/httpservice" "github.com/mattermost/mattermost-server/v6/services/marketplace" "github.com/mattermost/mattermost-server/v6/shared/filestore" "github.com/mattermost/mattermost-server/v6/shared/mlog" "github.com/mattermost/mattermost-server/v6/utils/fileutils" ) +type PluginService struct { + platform *platform.PlatformService + channels *Channels + fileStore filestore.FileBackend + httpService httpservice.HTTPService + + pluginCommandsLock sync.RWMutex + pluginCommands []*PluginCommand + pluginsLock sync.RWMutex + pluginsEnvironment *plugin.Environment + pluginConfigListenerID string + // collectionTypes maps from collection types to the registering plugin id + collectionTypes map[string]string + // topicTypes maps from topic types to collection types + topicTypes map[string]string + collectionAndTopicTypesMut sync.Mutex +} + const prepackagedPluginsDir = "prepackaged_plugins" type pluginSignaturePath struct { @@ -63,20 +84,91 @@ func (rs *routerService) getHandler(productID string) (http.Handler, bool) { return handler, ok } +func (a *App) PluginService() *PluginService { + return a.ch.srv.pluginService +} + +func (s *Server) InitializePluginService() error { + product, ok := s.products["channels"] + if !ok { + return errors.New("unable to find channels product") + } + channels, ok := product.(*Channels) + if !ok { + return errors.New("unable to cast product to channels product") + } + + ps := &PluginService{ + platform: s.platform, + channels: channels, + fileStore: s.platform.FileBackend(), + httpService: s.httpService, + collectionTypes: make(map[string]string), + topicTypes: make(map[string]string), + } + s.pluginService = ps + + pluginsRoute := s.Router.PathPrefix("/plugins/{plugin_id:[A-Za-z0-9\\_\\-\\.]+}").Subrouter() + pluginsRoute.HandleFunc("", ps.ServePluginRequest) + pluginsRoute.HandleFunc("/public/{public_file:.*}", ps.ServePluginPublicRequest) + pluginsRoute.HandleFunc("/{anything:.*}", ps.ServePluginRequest) + + ps.initPlugins(request.EmptyContext(s.platform.Log()), *s.platform.Config().PluginSettings.Directory, *s.platform.Config().PluginSettings.ClientDirectory) + + // Start plugins + ctx := request.EmptyContext(s.platform.Log()) + + // Add the config listener to enable/disable plugins + s.platform.AddConfigListener(func(prevCfg, cfg *model.Config) { + // We compute the difference between configs + // to ensure we don't re-init plugins unnecessarily. + diffs, err := config.Diff(prevCfg, cfg) + if err != nil { + s.platform.Log().Warn("Error in comparing configs", mlog.Err(err)) + return + } + + hasDiff := false + // TODO: This could be a method on ConfigDiffs itself + for _, diff := range diffs { + if strings.HasPrefix(diff.Path, "PluginSettings.") { + hasDiff = true + break + } + } + + // Do only if some plugin related settings has changed. + if hasDiff { + if *cfg.PluginSettings.Enable { + s.pluginService.initPlugins(ctx, *cfg.PluginSettings.Directory, *s.Config().PluginSettings.ClientDirectory) + } else { + s.pluginService.ShutDownPlugins() + } + } + + }) + + return nil +} + +func (s *Server) GetPluginsEnvironment() *plugin.Environment { + return s.pluginService.GetPluginsEnvironment() +} + // GetPluginsEnvironment returns the plugin environment for use if plugins are enabled and // initialized. // // To get the plugins environment when the plugins are disabled, manually acquire the plugins // lock instead. -func (ch *Channels) GetPluginsEnvironment() *plugin.Environment { - if !*ch.cfgSvc.Config().PluginSettings.Enable { +func (s *PluginService) GetPluginsEnvironment() *plugin.Environment { + if !*s.platform.Config().PluginSettings.Enable { return nil } - ch.pluginsLock.RLock() - defer ch.pluginsLock.RUnlock() + s.pluginsLock.RLock() + defer s.pluginsLock.RUnlock() - return ch.pluginsEnvironment + return s.pluginsEnvironment } // GetPluginsEnvironment returns the plugin environment for use if plugins are enabled and @@ -85,33 +177,39 @@ func (ch *Channels) GetPluginsEnvironment() *plugin.Environment { // To get the plugins environment when the plugins are disabled, manually acquire the plugins // lock instead. func (a *App) GetPluginsEnvironment() *plugin.Environment { - return a.ch.GetPluginsEnvironment() + // TODO: Telemetry service starts before products start, so we need to check if the plugin service is initialized. + // Move the telemetry service to start after products start. + if a.ch.srv.pluginService == nil { + return nil + } + + return a.ch.srv.pluginService.GetPluginsEnvironment() } -func (ch *Channels) SetPluginsEnvironment(pluginsEnvironment *plugin.Environment) { - ch.pluginsLock.Lock() - defer ch.pluginsLock.Unlock() +func (s *PluginService) SetPluginsEnvironment(pluginsEnvironment *plugin.Environment) { + s.pluginsLock.Lock() + defer s.pluginsLock.Unlock() - ch.pluginsEnvironment = pluginsEnvironment - ch.srv.Platform().SetPluginsEnvironment(ch) + s.pluginsEnvironment = pluginsEnvironment + s.platform.SetPluginsEnvironment(s.channels.srv) } -func (ch *Channels) syncPluginsActiveState() { +func (s *PluginService) syncPluginsActiveState() { // Acquiring lock manually, as plugins might be disabled. See GetPluginsEnvironment. - ch.pluginsLock.RLock() - pluginsEnvironment := ch.pluginsEnvironment - ch.pluginsLock.RUnlock() + s.pluginsLock.RLock() + pluginsEnvironment := s.pluginsEnvironment + s.pluginsLock.RUnlock() if pluginsEnvironment == nil { return } - config := ch.cfgSvc.Config().PluginSettings + config := s.platform.Config().PluginSettings if *config.Enable { availablePlugins, err := pluginsEnvironment.Available() if err != nil { - ch.srv.Log().Error("Unable to get available plugins", mlog.Err(err)) + s.platform.Log().Error("Unable to get available plugins", mlog.Err(err)) return } @@ -125,24 +223,24 @@ func (ch *Channels) syncPluginsActiveState() { pluginEnabled = state.Enable } - if hasOverride, value := ch.getPluginStateOverride(pluginID); hasOverride { + if hasOverride, value := s.getPluginStateOverride(pluginID); hasOverride { pluginEnabled = value } if pluginEnabled { // Disable focalboard in product mode. - if pluginID == model.PluginIdFocalboard && ch.cfgSvc.Config().FeatureFlags.BoardsProduct { + if pluginID == model.PluginIdFocalboard && s.platform.Config().FeatureFlags.BoardsProduct { msg := "Plugin cannot run in product mode. Disabling." mlog.Warn(msg, mlog.String("plugin_id", model.PluginIdFocalboard)) // This is a mini-version of ch.disablePlugin. // We don't call that directly, because that will recursively call // this method. - ch.cfgSvc.UpdateConfig(func(cfg *model.Config) { + s.platform.UpdateConfig(func(cfg *model.Config) { cfg.PluginSettings.PluginStates[pluginID] = &model.PluginState{Enable: false} }) pluginsEnvironment.SetPluginError(pluginID, msg) - ch.unregisterPluginCommands(pluginID) + s.unregisterPluginCommands(pluginID) disabledPlugins = append(disabledPlugins, plugin) continue } @@ -166,7 +264,7 @@ func (ch *Channels) syncPluginsActiveState() { if deactivated && plugin.Manifest.HasClient() { message := model.NewWebSocketEvent(model.WebsocketEventPluginDisabled, "", "", "", nil, "") message.Add("manifest", plugin.Manifest.ClientManifest()) - ch.srv.platform.Publish(message) + s.platform.Publish(message) } }(plugin) } @@ -180,14 +278,14 @@ func (ch *Channels) syncPluginsActiveState() { pluginID := plugin.Manifest.Id updatedManifest, activated, err := pluginsEnvironment.Activate(pluginID) if err != nil { - plugin.WrapLogger(ch.srv.Log()).Error("Unable to activate plugin", mlog.Err(err)) + plugin.WrapLogger(s.platform.Log().(*mlog.Logger)).Error("Unable to activate plugin", mlog.Err(err)) return } if activated { // Notify all cluster clients if ready - if err := ch.notifyPluginEnabled(updatedManifest); err != nil { - ch.srv.Log().Error("Failed to notify cluster on plugin enable", mlog.Err(err)) + if err := s.notifyPluginEnabled(updatedManifest); err != nil { + s.platform.Log().Error("Failed to notify cluster on plugin enable", mlog.Err(err)) } } }(plugin) @@ -197,7 +295,7 @@ func (ch *Channels) syncPluginsActiveState() { pluginsEnvironment.Shutdown() } - if err := ch.notifyPluginStatusesChanged(); err != nil { + if err := s.notifyPluginStatusesChanged(); err != nil { mlog.Warn("failed to notify plugin status changed", mlog.Err(err)) } } @@ -207,27 +305,29 @@ func (a *App) NewPluginAPI(c *request.Context, manifest *model.Manifest) plugin. } func (a *App) InitPlugins(c *request.Context, pluginDir, webappPluginDir string) { - a.ch.initPlugins(c, pluginDir, webappPluginDir) + a.ch.srv.pluginService.initPlugins(c, pluginDir, webappPluginDir) } -func (ch *Channels) initPlugins(c *request.Context, pluginDir, webappPluginDir string) { +func (s *PluginService) initPlugins(c *request.Context, pluginDir, webappPluginDir string) { // Acquiring lock manually, as plugins might be disabled. See GetPluginsEnvironment. defer func() { - ch.srv.Platform().SetPluginsEnvironment(ch) + // platform service requires plugins environment to be initialized + // so that it can use it in cluster service initialization + s.platform.SetPluginsEnvironment(s.channels.srv) }() - ch.pluginsLock.RLock() - pluginsEnvironment := ch.pluginsEnvironment - ch.pluginsLock.RUnlock() - if pluginsEnvironment != nil || !*ch.cfgSvc.Config().PluginSettings.Enable { - ch.syncPluginsActiveState() + s.pluginsLock.RLock() + pluginsEnvironment := s.pluginsEnvironment + s.pluginsLock.RUnlock() + if pluginsEnvironment != nil || !*s.platform.Config().PluginSettings.Enable { + s.syncPluginsActiveState() if pluginsEnvironment != nil { - pluginsEnvironment.TogglePluginHealthCheckJob(*ch.cfgSvc.Config().PluginSettings.EnableHealthCheck) + pluginsEnvironment.TogglePluginHealthCheckJob(*s.platform.Config().PluginSettings.EnableHealthCheck) } return } - ch.srv.Log().Info("Starting up plugins") + s.platform.Log().Info("Starting up plugins") if err := os.Mkdir(pluginDir, 0744); err != nil && !os.IsExist(err) { mlog.Error("Failed to start up plugins", mlog.Err(err)) @@ -240,77 +340,77 @@ func (ch *Channels) initPlugins(c *request.Context, pluginDir, webappPluginDir s } newAPIFunc := func(manifest *model.Manifest) plugin.API { - return New(ServerConnector(ch)).NewPluginAPI(c, manifest) + return New(ServerConnector(s.channels)).NewPluginAPI(c, manifest) } env, err := plugin.NewEnvironment( newAPIFunc, - NewDriverImpl(ch.srv), + NewDriverImpl(s.platform), pluginDir, webappPluginDir, - *ch.cfgSvc.Config().ExperimentalSettings.PatchPluginsReactDOM, - ch.srv.Log(), - ch.srv.GetMetrics(), + *s.platform.Config().ExperimentalSettings.PatchPluginsReactDOM, + s.platform.Logger(), + s.platform.Metrics(), ) if err != nil { mlog.Error("Failed to start up plugins", mlog.Err(err)) return } - ch.pluginsLock.Lock() - ch.pluginsEnvironment = env - ch.pluginsLock.Unlock() + s.pluginsLock.Lock() + s.pluginsEnvironment = env + s.pluginsLock.Unlock() - ch.pluginsEnvironment.TogglePluginHealthCheckJob(*ch.cfgSvc.Config().PluginSettings.EnableHealthCheck) + s.pluginsEnvironment.TogglePluginHealthCheckJob(*s.platform.Config().PluginSettings.EnableHealthCheck) - if err := ch.syncPlugins(); err != nil { + if err := s.syncPlugins(); err != nil { mlog.Error("Failed to sync plugins from the file store", mlog.Err(err)) } - plugins := ch.processPrepackagedPlugins(prepackagedPluginsDir) - pluginsEnvironment = ch.GetPluginsEnvironment() + plugins := s.processPrepackagedPlugins(prepackagedPluginsDir) + pluginsEnvironment = s.GetPluginsEnvironment() if pluginsEnvironment == nil { mlog.Info("Plugins environment not found, server is likely shutting down") return } pluginsEnvironment.SetPrepackagedPlugins(plugins) - ch.installFeatureFlagPlugins() + s.installFeatureFlagPlugins() // Sync plugin active state when config changes. Also notify plugins. - ch.pluginsLock.Lock() - ch.RemoveConfigListener(ch.pluginConfigListenerID) - ch.pluginConfigListenerID = ch.AddConfigListener(func(old, new *model.Config) { + s.pluginsLock.Lock() + s.platform.RemoveConfigListener(s.pluginConfigListenerID) + s.pluginConfigListenerID = s.platform.AddConfigListener(func(old, new *model.Config) { // If plugin status remains unchanged, only then run this. // Because (*App).InitPlugins is already run as a config change hook. if *old.PluginSettings.Enable == *new.PluginSettings.Enable { - ch.installFeatureFlagPlugins() - ch.syncPluginsActiveState() + s.installFeatureFlagPlugins() + s.syncPluginsActiveState() } - ch.RunMultiHook(func(hooks plugin.Hooks) bool { + s.pluginsEnvironment.RunMultiPluginHook(func(hooks plugin.Hooks) bool { if err := hooks.OnConfigurationChange(); err != nil { - ch.srv.Log().Error("Plugin OnConfigurationChange hook failed", mlog.Err(err)) + s.platform.Log().Error("Plugin OnConfigurationChange hook failed", mlog.Err(err)) } return true }, plugin.OnConfigurationChangeID) }) - ch.pluginsLock.Unlock() + s.pluginsLock.Unlock() - ch.syncPluginsActiveState() + s.syncPluginsActiveState() } // SyncPlugins synchronizes the plugins installed locally // with the plugin bundles available in the file store. func (a *App) SyncPlugins() *model.AppError { - return a.ch.syncPlugins() + return a.ch.srv.pluginService.syncPlugins() } // SyncPlugins synchronizes the plugins installed locally // with the plugin bundles available in the file store. -func (ch *Channels) syncPlugins() *model.AppError { +func (s *PluginService) syncPlugins() *model.AppError { mlog.Info("Syncing plugins from the file store") - pluginsEnvironment := ch.GetPluginsEnvironment() + pluginsEnvironment := s.GetPluginsEnvironment() if pluginsEnvironment == nil { return model.NewAppError("SyncPlugins", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented) } @@ -326,14 +426,14 @@ func (ch *Channels) syncPlugins() *model.AppError { go func(pluginID string) { defer wg.Done() // Only handle managed plugins with .filestore flag file. - _, err := os.Stat(filepath.Join(*ch.cfgSvc.Config().PluginSettings.Directory, pluginID, managedPluginFileName)) + _, err := os.Stat(filepath.Join(*s.platform.Config().PluginSettings.Directory, pluginID, managedPluginFileName)) if os.IsNotExist(err) { mlog.Warn("Skipping sync for unmanaged plugin", mlog.String("plugin_id", pluginID)) } else if err != nil { mlog.Error("Skipping sync for plugin after failure to check if managed", mlog.String("plugin_id", pluginID), mlog.Err(err)) } else { mlog.Debug("Removing local installation of managed plugin before sync", mlog.String("plugin_id", pluginID)) - if err := ch.removePluginLocally(pluginID); err != nil { + if err := s.removePluginLocally(pluginID); err != nil { mlog.Error("Failed to remove local installation of managed plugin before sync", mlog.String("plugin_id", pluginID), mlog.Err(err)) } } @@ -342,7 +442,7 @@ func (ch *Channels) syncPlugins() *model.AppError { wg.Wait() // Install plugins from the file store. - pluginSignaturePathMap, appErr := ch.getPluginsFromFolder() + pluginSignaturePathMap, appErr := s.getPluginsFromFolder() if appErr != nil { return appErr } @@ -351,7 +451,7 @@ func (ch *Channels) syncPlugins() *model.AppError { wg.Add(1) go func(plugin *pluginSignaturePath) { defer wg.Done() - reader, appErr := ch.srv.fileReader(plugin.path) + reader, appErr := s.fileStore.Reader(plugin.path) if appErr != nil { mlog.Error("Failed to open plugin bundle from file store.", mlog.String("bundle", plugin.path), mlog.Err(appErr)) return @@ -359,8 +459,8 @@ func (ch *Channels) syncPlugins() *model.AppError { defer reader.Close() var signature filestore.ReadCloseSeeker - if *ch.cfgSvc.Config().PluginSettings.RequirePluginSignature { - signature, appErr = ch.srv.fileReader(plugin.signaturePath) + if *s.platform.Config().PluginSettings.RequirePluginSignature { + signature, appErr = s.fileStore.Reader(plugin.signaturePath) if appErr != nil { mlog.Error("Failed to open plugin signature from file store.", mlog.Err(appErr)) return @@ -369,7 +469,7 @@ func (ch *Channels) syncPlugins() *model.AppError { } mlog.Info("Syncing plugin from file store", mlog.String("bundle", plugin.path)) - if _, err := ch.installPluginLocally(reader, signature, installPluginLocallyAlways); err != nil { + if _, err := s.installPluginLocally(reader, signature, installPluginLocallyAlways); err != nil { mlog.Error("Failed to sync plugin from file store", mlog.String("bundle", plugin.path), mlog.Err(err)) } }(plugin) @@ -379,11 +479,11 @@ func (ch *Channels) syncPlugins() *model.AppError { return nil } -func (ch *Channels) ShutDownPlugins() { +func (s *PluginService) ShutDownPlugins() { // Acquiring lock manually, as plugins might be disabled. See GetPluginsEnvironment. - ch.pluginsLock.RLock() - pluginsEnvironment := ch.pluginsEnvironment - ch.pluginsLock.RUnlock() + s.pluginsLock.RLock() + pluginsEnvironment := s.pluginsEnvironment + s.pluginsLock.RUnlock() if pluginsEnvironment == nil { return } @@ -392,14 +492,14 @@ func (ch *Channels) ShutDownPlugins() { pluginsEnvironment.Shutdown() - ch.RemoveConfigListener(ch.pluginConfigListenerID) - ch.pluginConfigListenerID = "" + s.platform.RemoveConfigListener(s.pluginConfigListenerID) + s.pluginConfigListenerID = "" // Acquiring lock manually before cleaning up PluginsEnvironment. - ch.pluginsLock.Lock() - defer ch.pluginsLock.Unlock() - if ch.pluginsEnvironment == pluginsEnvironment { - ch.pluginsEnvironment = nil + s.pluginsLock.Lock() + defer s.pluginsLock.Unlock() + if s.pluginsEnvironment == pluginsEnvironment { + s.pluginsEnvironment = nil } else { mlog.Warn("Another PluginsEnvironment detected while shutting down plugins.") } @@ -425,11 +525,11 @@ func (a *App) GetActivePluginManifests() ([]*model.Manifest, *model.AppError) { // activation if inactive anywhere in the cluster. // Notifies cluster peers through config change. func (a *App) EnablePlugin(id string) *model.AppError { - return a.ch.enablePlugin(id) + return a.PluginService().enablePlugin(id) } -func (ch *Channels) enablePlugin(id string) *model.AppError { - pluginsEnvironment := ch.GetPluginsEnvironment() +func (s *PluginService) enablePlugin(id string) *model.AppError { + pluginsEnvironment := s.GetPluginsEnvironment() if pluginsEnvironment == nil { return model.NewAppError("EnablePlugin", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented) } @@ -453,16 +553,16 @@ func (ch *Channels) enablePlugin(id string) *model.AppError { return model.NewAppError("EnablePlugin", "app.plugin.not_installed.app_error", nil, "", http.StatusNotFound) } - if id == model.PluginIdFocalboard && ch.cfgSvc.Config().FeatureFlags.BoardsProduct { + if id == model.PluginIdFocalboard && s.platform.Config().FeatureFlags.BoardsProduct { return model.NewAppError("EnablePlugin", "app.plugin.product_mode.app_error", map[string]any{"Name": model.PluginIdFocalboard}, "", http.StatusBadRequest) } - ch.cfgSvc.UpdateConfig(func(cfg *model.Config) { + s.platform.UpdateConfig(func(cfg *model.Config) { cfg.PluginSettings.PluginStates[id] = &model.PluginState{Enable: true} }) // This call will implicitly invoke SyncPluginsActiveState which will activate enabled plugins. - if _, _, err := ch.cfgSvc.SaveConfig(ch.cfgSvc.Config(), true); err != nil { + if _, _, err := s.platform.SaveConfig(s.platform.Config(), true); err != nil { if err.Id == "ent.cluster.save_config.error" { return model.NewAppError("EnablePlugin", "app.plugin.cluster.save_config.app_error", nil, "", http.StatusInternalServerError) } @@ -475,7 +575,7 @@ func (ch *Channels) enablePlugin(id string) *model.AppError { // DisablePlugin will set the config for an installed plugin to disabled, triggering deactivation if active. // Notifies cluster peers through config change. func (a *App) DisablePlugin(id string) *model.AppError { - appErr := a.ch.disablePlugin(id) + appErr := a.ch.srv.pluginService.disablePlugin(id) if appErr != nil { return appErr } @@ -483,22 +583,22 @@ func (a *App) DisablePlugin(id string) *model.AppError { return nil } -func (ch *Channels) disablePlugin(id string) *model.AppError { +func (s *PluginService) disablePlugin(id string) *model.AppError { // find all collectionTypes registered by plugin - for collectionTypeToRemove, existingPluginId := range ch.collectionTypes { + for collectionTypeToRemove, existingPluginId := range s.collectionTypes { if existingPluginId != id { continue } // find all topicTypes for existing collectionType - for topicTypeToRemove, existingCollectionType := range ch.topicTypes { + for topicTypeToRemove, existingCollectionType := range s.topicTypes { if existingCollectionType == collectionTypeToRemove { - delete(ch.topicTypes, topicTypeToRemove) + delete(s.topicTypes, topicTypeToRemove) } } - delete(ch.collectionTypes, collectionTypeToRemove) + delete(s.collectionTypes, collectionTypeToRemove) } - pluginsEnvironment := ch.GetPluginsEnvironment() + pluginsEnvironment := s.GetPluginsEnvironment() if pluginsEnvironment == nil { return model.NewAppError("DisablePlugin", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented) } @@ -522,13 +622,13 @@ func (ch *Channels) disablePlugin(id string) *model.AppError { return model.NewAppError("DisablePlugin", "app.plugin.not_installed.app_error", nil, "", http.StatusNotFound) } - ch.cfgSvc.UpdateConfig(func(cfg *model.Config) { + s.platform.UpdateConfig(func(cfg *model.Config) { cfg.PluginSettings.PluginStates[id] = &model.PluginState{Enable: false} }) - ch.unregisterPluginCommands(id) + s.unregisterPluginCommands(id) // This call will implicitly invoke SyncPluginsActiveState which will deactivate disabled plugins. - if _, _, err := ch.cfgSvc.SaveConfig(ch.cfgSvc.Config(), true); err != nil { + if _, _, err := s.platform.SaveConfig(s.platform.Config(), true); err != nil { return model.NewAppError("DisablePlugin", "app.plugin.config.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } @@ -615,8 +715,8 @@ func (a *App) GetMarketplacePlugins(filter *model.MarketplacePluginFilter) ([]*m // getPrepackagedPlugin returns a pre-packaged plugin. // // If version is empty, the first matching plugin is returned. -func (ch *Channels) getPrepackagedPlugin(pluginID, version string) (*plugin.PrepackagedPlugin, *model.AppError) { - pluginsEnvironment := ch.GetPluginsEnvironment() +func (s *PluginService) getPrepackagedPlugin(pluginID, version string) (*plugin.PrepackagedPlugin, *model.AppError) { + pluginsEnvironment := s.GetPluginsEnvironment() if pluginsEnvironment == nil { return nil, model.NewAppError("getPrepackagedPlugin", "app.plugin.config.app_error", nil, "plugin environment is nil", http.StatusInternalServerError) } @@ -634,16 +734,16 @@ func (ch *Channels) getPrepackagedPlugin(pluginID, version string) (*plugin.Prep // getRemoteMarketplacePlugin returns plugin from marketplace-server. // // If version is empty, the latest compatible version is used. -func (ch *Channels) getRemoteMarketplacePlugin(pluginID, version string) (*model.BaseMarketplacePlugin, *model.AppError) { +func (s *PluginService) getRemoteMarketplacePlugin(pluginID, version string) (*model.BaseMarketplacePlugin, *model.AppError) { marketplaceClient, err := marketplace.NewClient( - *ch.cfgSvc.Config().PluginSettings.MarketplaceURL, - ch.srv.HTTPService(), + *s.platform.Config().PluginSettings.MarketplaceURL, + s.httpService, ) if err != nil { return nil, model.NewAppError("GetMarketplacePlugin", "app.plugin.marketplace_client.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } - filter := ch.getBaseMarketplaceFilter() + filter := s.getBaseMarketplaceFilter() filter.PluginId = pluginID var plugin *model.BaseMarketplacePlugin @@ -798,15 +898,15 @@ func (a *App) mergeLocalPlugins(remoteMarketplacePlugins map[string]*model.Marke } func (a *App) getBaseMarketplaceFilter() *model.MarketplacePluginFilter { - return a.ch.getBaseMarketplaceFilter() + return a.ch.srv.pluginService.getBaseMarketplaceFilter() } -func (ch *Channels) getBaseMarketplaceFilter() *model.MarketplacePluginFilter { +func (s *PluginService) getBaseMarketplaceFilter() *model.MarketplacePluginFilter { filter := &model.MarketplacePluginFilter{ ServerVersion: model.CurrentVersion, } - license := ch.srv.License() + license := s.platform.License() if license != nil && license.HasEnterpriseMarketplacePlugins() { filter.EnterprisePlugins = true } @@ -853,8 +953,8 @@ func pluginMatchesFilter(manifest *model.Manifest, filter string) bool { // it will notify all connected websocket clients (across all peers) to trigger the (re-)installation. // There is a small chance that this never occurs, because the last server to finish installing dies before it can announce. // There is also a chance that multiple servers notify, but the webapp handles this idempotently. -func (ch *Channels) notifyPluginEnabled(manifest *model.Manifest) error { - pluginsEnvironment := ch.GetPluginsEnvironment() +func (s *PluginService) notifyPluginEnabled(manifest *model.Manifest) error { + pluginsEnvironment := s.GetPluginsEnvironment() if pluginsEnvironment == nil { return errors.New("pluginsEnvironment is nil") } @@ -864,15 +964,15 @@ func (ch *Channels) notifyPluginEnabled(manifest *model.Manifest) error { var statuses model.PluginStatuses - if ch.srv.platform.Cluster() != nil { + if s.platform.Cluster() != nil { var err *model.AppError - statuses, err = ch.srv.platform.Cluster().GetPluginStatuses() + statuses, err = s.platform.Cluster().GetPluginStatuses() if err != nil { return err } } - localStatus, err := ch.GetPluginStatus(manifest.Id) + localStatus, err := s.GetPluginStatus(manifest.Id) if err != nil { return err } @@ -892,26 +992,26 @@ func (ch *Channels) notifyPluginEnabled(manifest *model.Manifest) error { // Notify all cluster peer clients. message := model.NewWebSocketEvent(model.WebsocketEventPluginEnabled, "", "", "", nil, "") message.Add("manifest", manifest.ClientManifest()) - ch.srv.platform.Publish(message) + s.platform.Publish(message) return nil } -func (ch *Channels) getPluginsFromFolder() (map[string]*pluginSignaturePath, *model.AppError) { - fileStorePaths, appErr := ch.srv.listDirectory(fileStorePluginFolder, false) +func (s *PluginService) getPluginsFromFolder() (map[string]*pluginSignaturePath, *model.AppError) { + fileStorePaths, appErr := s.fileStore.ListDirectory(fileStorePluginFolder) if appErr != nil { return nil, model.NewAppError("getPluginsFromDir", "app.plugin.sync.list_filestore.app_error", nil, "", http.StatusInternalServerError).Wrap(appErr) } - return ch.getPluginsFromFilePaths(fileStorePaths), nil + return s.getPluginsFromFilePaths(fileStorePaths), nil } -func (ch *Channels) getPluginsFromFilePaths(fileStorePaths []string) map[string]*pluginSignaturePath { +func (s *PluginService) getPluginsFromFilePaths(fileStorePaths []string) map[string]*pluginSignaturePath { pluginSignaturePathMap := make(map[string]*pluginSignaturePath) fsPrefix := "" - if *ch.cfgSvc.Config().FileSettings.DriverName == model.ImageDriverS3 { - ptr := ch.cfgSvc.Config().FileSettings.AmazonS3PathPrefix + if *s.platform.Config().FileSettings.DriverName == model.ImageDriverS3 { + ptr := s.platform.Config().FileSettings.AmazonS3PathPrefix if ptr != nil && *ptr != "" { fsPrefix = *ptr + "/" } @@ -944,7 +1044,7 @@ func (ch *Channels) getPluginsFromFilePaths(fileStorePaths []string) map[string] return pluginSignaturePathMap } -func (ch *Channels) processPrepackagedPlugins(pluginsDir string) []*plugin.PrepackagedPlugin { +func (s *PluginService) processPrepackagedPlugins(pluginsDir string) []*plugin.PrepackagedPlugin { prepackagedPluginsDir, found := fileutils.FindDir(pluginsDir) if !found { return nil @@ -960,7 +1060,7 @@ func (ch *Channels) processPrepackagedPlugins(pluginsDir string) []*plugin.Prepa return nil } - pluginSignaturePathMap := ch.getPluginsFromFilePaths(fileStorePaths) + pluginSignaturePathMap := s.getPluginsFromFilePaths(fileStorePaths) plugins := make([]*plugin.PrepackagedPlugin, 0, len(pluginSignaturePathMap)) prepackagedPlugins := make(chan *plugin.PrepackagedPlugin, len(pluginSignaturePathMap)) @@ -969,7 +1069,7 @@ func (ch *Channels) processPrepackagedPlugins(pluginsDir string) []*plugin.Prepa wg.Add(1) go func(psPath *pluginSignaturePath) { defer wg.Done() - p, err := ch.processPrepackagedPlugin(psPath) + p, err := s.processPrepackagedPlugin(psPath) if err != nil { mlog.Error("Failed to install prepackaged plugin", mlog.String("path", psPath.path), mlog.Err(err)) return @@ -990,7 +1090,7 @@ func (ch *Channels) processPrepackagedPlugins(pluginsDir string) []*plugin.Prepa // processPrepackagedPlugin will return the prepackaged plugin metadata and will also // install the prepackaged plugin if it had been previously enabled and AutomaticPrepackagedPlugins is true. -func (ch *Channels) processPrepackagedPlugin(pluginPath *pluginSignaturePath) (*plugin.PrepackagedPlugin, error) { +func (s *PluginService) processPrepackagedPlugin(pluginPath *pluginSignaturePath) (*plugin.PrepackagedPlugin, error) { mlog.Debug("Processing prepackaged plugin", mlog.String("path", pluginPath.path)) fileReader, err := os.Open(pluginPath.path) @@ -1011,18 +1111,18 @@ func (ch *Channels) processPrepackagedPlugin(pluginPath *pluginSignaturePath) (* } // Skip installing the plugin at all if automatic prepackaged plugins is disabled - if !*ch.cfgSvc.Config().PluginSettings.AutomaticPrepackagedPlugins { + if !*s.platform.Config().PluginSettings.AutomaticPrepackagedPlugins { return plugin, nil } // Skip installing if the plugin is has not been previously enabled. - pluginState := ch.cfgSvc.Config().PluginSettings.PluginStates[plugin.Manifest.Id] + pluginState := s.platform.Config().PluginSettings.PluginStates[plugin.Manifest.Id] if pluginState == nil || !pluginState.Enable { return plugin, nil } mlog.Debug("Installing prepackaged plugin", mlog.String("path", pluginPath.path)) - if _, err := ch.installExtractedPlugin(plugin.Manifest, pluginDir, installPluginLocallyOnlyIfNewOrUpgrade); err != nil { + if _, err := s.installExtractedPlugin(plugin.Manifest, pluginDir, installPluginLocallyOnlyIfNewOrUpgrade); err != nil { return nil, errors.Wrapf(err, "Failed to install extracted prepackaged plugin %s", pluginPath.path) } @@ -1030,24 +1130,24 @@ func (ch *Channels) processPrepackagedPlugin(pluginPath *pluginSignaturePath) (* } // installFeatureFlagPlugins handles the automatic installation/upgrade of plugins from feature flags -func (ch *Channels) installFeatureFlagPlugins() { - ffControledPlugins := ch.cfgSvc.Config().FeatureFlags.Plugins() +func (s *PluginService) installFeatureFlagPlugins() { + ffControledPlugins := s.platform.Config().FeatureFlags.Plugins() // Respect the automatic prepackaged disable setting - if !*ch.cfgSvc.Config().PluginSettings.AutomaticPrepackagedPlugins { + if !*s.platform.Config().PluginSettings.AutomaticPrepackagedPlugins { return } for pluginID, version := range ffControledPlugins { // Skip installing if the plugin has been previously disabled. - pluginState := ch.cfgSvc.Config().PluginSettings.PluginStates[pluginID] + pluginState := s.platform.Config().PluginSettings.PluginStates[pluginID] if pluginState != nil && !pluginState.Enable { - ch.srv.Log().Debug("Not auto installing/upgrade because plugin was disabled", mlog.String("plugin_id", pluginID), mlog.String("version", version)) + s.platform.Log().Debug("Not auto installing/upgrade because plugin was disabled", mlog.String("plugin_id", pluginID), mlog.String("version", version)) continue } // Check if we already installed this version as InstallMarketplacePlugin can't handle re-installs well. - pluginStatus, err := ch.GetPluginStatus(pluginID) + pluginStatus, err := s.GetPluginStatus(pluginID) pluginExists := err == nil if pluginExists && pluginStatus.Version == version { continue @@ -1055,37 +1155,37 @@ func (ch *Channels) installFeatureFlagPlugins() { if version != "" && version != "control" { // If we are on-prem skip installation if this is a downgrade - license := ch.srv.License() + license := s.platform.License() inCloud := license != nil && *license.Features.Cloud if !inCloud && pluginExists { parsedVersion, err := semver.Parse(version) if err != nil { - ch.srv.Log().Debug("Bad version from feature flag", mlog.String("plugin_id", pluginID), mlog.Err(err), mlog.String("version", version)) + s.platform.Log().Debug("Bad version from feature flag", mlog.String("plugin_id", pluginID), mlog.Err(err), mlog.String("version", version)) return } parsedExistingVersion, err := semver.Parse(pluginStatus.Version) if err != nil { - ch.srv.Log().Debug("Bad version from plugin manifest", mlog.String("plugin_id", pluginID), mlog.Err(err), mlog.String("version", pluginStatus.Version)) + s.platform.Log().Debug("Bad version from plugin manifest", mlog.String("plugin_id", pluginID), mlog.Err(err), mlog.String("version", pluginStatus.Version)) return } if parsedVersion.LTE(parsedExistingVersion) { - ch.srv.Log().Debug("Skip installation because given version was a downgrade and on-prem installations should not downgrade.", mlog.String("plugin_id", pluginID), mlog.Err(err), mlog.String("version", pluginStatus.Version)) + s.platform.Log().Debug("Skip installation because given version was a downgrade and on-prem installations should not downgrade.", mlog.String("plugin_id", pluginID), mlog.Err(err), mlog.String("version", pluginStatus.Version)) return } } - _, err := ch.InstallMarketplacePlugin(&model.InstallMarketplacePluginRequest{ + _, err := s.InstallMarketplacePlugin(&model.InstallMarketplacePluginRequest{ Id: pluginID, Version: version, }) if err != nil { - ch.srv.Log().Debug("Unable to install plugin from FF manifest", mlog.String("plugin_id", pluginID), mlog.Err(err), mlog.String("version", version)) + s.platform.Log().Debug("Unable to install plugin from FF manifest", mlog.String("plugin_id", pluginID), mlog.Err(err), mlog.String("version", version)) } else { - if err := ch.enablePlugin(pluginID); err != nil { - ch.srv.Log().Debug("Unable to enable plugin installed from feature flag.", mlog.String("plugin_id", pluginID), mlog.Err(err), mlog.String("version", version)) + if err := s.enablePlugin(pluginID); err != nil { + s.platform.Log().Debug("Unable to enable plugin installed from feature flag.", mlog.String("plugin_id", pluginID), mlog.Err(err), mlog.String("version", version)) } else { - ch.srv.Log().Debug("Installed and enabled plugin.", mlog.String("plugin_id", pluginID), mlog.String("version", version)) + s.platform.Log().Debug("Installed and enabled plugin.", mlog.String("plugin_id", pluginID), mlog.String("version", version)) } } } @@ -1140,15 +1240,15 @@ func getIcon(iconPath string) (string, error) { return fmt.Sprintf("data:image/svg+xml;base64,%s", base64.StdEncoding.EncodeToString(icon)), nil } -func (ch *Channels) getPluginStateOverride(pluginID string) (bool, bool) { +func (s *PluginService) getPluginStateOverride(pluginID string) (bool, bool) { switch pluginID { case model.PluginIdApps: // Tie Apps proxy disabled status to the feature flag. - if !ch.cfgSvc.Config().FeatureFlags.AppsEnabled { + if !s.platform.Config().FeatureFlags.AppsEnabled { return true, false } case model.PluginIdCalls: - if !ch.cfgSvc.Config().FeatureFlags.CallsEnabled { + if !s.platform.Config().FeatureFlags.CallsEnabled { return true, false } } diff --git a/app/plugin_api.go b/app/plugin_api.go index 5c8ee9fad1..57ee5b4192 100644 --- a/app/plugin_api.go +++ b/app/plugin_api.go @@ -886,7 +886,7 @@ func (api *PluginAPI) DisablePlugin(id string) *model.AppError { } func (api *PluginAPI) RemovePlugin(id string) *model.AppError { - return api.app.Channels().RemovePlugin(id) + return api.app.Srv().pluginService.RemovePlugin(id) } func (api *PluginAPI) GetPluginStatus(id string) (*model.PluginStatus, *model.AppError) { @@ -1235,7 +1235,7 @@ func (api *PluginAPI) GetCloudLimits() (*model.ProductLimits, error) { // RegisterCollectionAndTopic informs the server that this plugin handles // the given collection and topic types. func (api *PluginAPI) RegisterCollectionAndTopic(collectionType, topicType string) error { - return api.app.registerCollectionAndTopic(api.id, collectionType, topicType) + return api.app.Srv().pluginService.registerCollectionAndTopic(api.id, collectionType, topicType) } func (api *PluginAPI) CreateUploadSession(us *model.UploadSession) (*model.UploadSession, error) { diff --git a/app/plugin_api_test.go b/app/plugin_api_test.go index 759613781b..cec5736eb3 100644 --- a/app/plugin_api_test.go +++ b/app/plugin_api_test.go @@ -92,7 +92,7 @@ func setupMultiPluginAPITest(t *testing.T, pluginCodes []string, pluginManifests return app.NewPluginAPI(c, manifest) } - env, err := plugin.NewEnvironment(newPluginAPI, NewDriverImpl(app.Srv()), pluginDir, webappPluginDir, false, app.Log(), nil) + env, err := plugin.NewEnvironment(newPluginAPI, NewDriverImpl(app.Srv().Platform()), pluginDir, webappPluginDir, false, app.Log(), nil) require.NoError(t, err) require.Equal(t, len(pluginCodes), len(pluginIDs)) @@ -119,7 +119,7 @@ func setupMultiPluginAPITest(t *testing.T, pluginCodes []string, pluginManifests }) } - app.ch.SetPluginsEnvironment(env) + app.PluginService().SetPluginsEnvironment(env) return pluginDir } @@ -849,7 +849,7 @@ func TestPluginAPIGetPlugins(t *testing.T) { defer os.RemoveAll(pluginDir) defer os.RemoveAll(webappPluginDir) - env, err := plugin.NewEnvironment(th.NewPluginAPI, NewDriverImpl(th.Server), pluginDir, webappPluginDir, false, th.App.Log(), nil) + env, err := plugin.NewEnvironment(th.NewPluginAPI, NewDriverImpl(th.Server.Platform()), pluginDir, webappPluginDir, false, th.App.Log(), nil) require.NoError(t, err) pluginIDs := []string{"pluginid1", "pluginid2", "pluginid3"} @@ -866,7 +866,7 @@ func TestPluginAPIGetPlugins(t *testing.T) { require.True(t, activated) pluginManifests = append(pluginManifests, manifest) } - th.App.ch.SetPluginsEnvironment(env) + th.App.PluginService().SetPluginsEnvironment(env) // Deactivate the last one for testing success := env.Deactivate(pluginIDs[len(pluginIDs)-1]) @@ -937,10 +937,10 @@ func TestInstallPlugin(t *testing.T) { return app.NewPluginAPI(c, manifest) } - env, err := plugin.NewEnvironment(newPluginAPI, NewDriverImpl(app.Srv()), pluginDir, webappPluginDir, false, app.Log(), nil) + env, err := plugin.NewEnvironment(newPluginAPI, NewDriverImpl(app.Srv().Platform()), pluginDir, webappPluginDir, false, app.Log(), nil) require.NoError(t, err) - app.ch.SetPluginsEnvironment(env) + app.PluginService().SetPluginsEnvironment(env) backend := filepath.Join(pluginDir, pluginID, "backend.exe") utils.CompileGo(t, pluginCode, backend) @@ -1632,10 +1632,10 @@ func TestAPIMetrics(t *testing.T) { defer os.RemoveAll(pluginDir) defer os.RemoveAll(webappPluginDir) - env, err := plugin.NewEnvironment(th.NewPluginAPI, NewDriverImpl(th.Server), pluginDir, webappPluginDir, false, th.App.Log(), metricsMock) + env, err := plugin.NewEnvironment(th.NewPluginAPI, NewDriverImpl(th.Server.Platform()), pluginDir, webappPluginDir, false, th.App.Log(), metricsMock) require.NoError(t, err) - th.App.ch.SetPluginsEnvironment(env) + th.App.PluginService().SetPluginsEnvironment(env) pluginID := model.NewId() backend := filepath.Join(pluginDir, pluginID, "backend.exe") @@ -2079,10 +2079,10 @@ func TestRegisterCollectionAndTopic(t *testing.T) { return th.App.NewPluginAPI(th.Context, manifest) } - env, err := plugin.NewEnvironment(newPluginAPI, NewDriverImpl(th.App.Srv()), pluginDir, webappPluginDir, false, th.App.Log(), nil) + env, err := plugin.NewEnvironment(newPluginAPI, NewDriverImpl(th.Server.Platform()), pluginDir, webappPluginDir, false, th.App.Log(), nil) require.NoError(t, err) - th.App.ch.SetPluginsEnvironment(env) + th.App.PluginService().SetPluginsEnvironment(env) pluginID := "testplugin" pluginManifest := `{"id": "testplugin", "server": {"executable": "backend.exe"}}` @@ -2179,10 +2179,10 @@ func TestPluginUploadsAPI(t *testing.T) { newPluginAPI := func(manifest *model.Manifest) plugin.API { return th.App.NewPluginAPI(th.Context, manifest) } - env, err := plugin.NewEnvironment(newPluginAPI, NewDriverImpl(th.App.Srv()), pluginDir, webappPluginDir, false, th.App.Log(), nil) + env, err := plugin.NewEnvironment(newPluginAPI, NewDriverImpl(th.Server.Platform()), pluginDir, webappPluginDir, false, th.App.Log(), nil) require.NoError(t, err) - th.App.ch.SetPluginsEnvironment(env) + th.App.PluginService().SetPluginsEnvironment(env) pluginID := "testplugin" pluginManifest := `{"id": "testplugin", "server": {"executable": "backend.exe"}}` diff --git a/app/plugin_commands.go b/app/plugin_commands.go index e70c69a38a..8d034931e9 100644 --- a/app/plugin_commands.go +++ b/app/plugin_commands.go @@ -22,6 +22,10 @@ type PluginCommand struct { } func (a *App) RegisterPluginCommand(pluginID string, command *model.Command) error { + return a.Srv().pluginService.registerPluginCommand(pluginID, command) +} + +func (s *PluginService) registerPluginCommand(pluginID string, command *model.Command) error { if command.Trigger == "" { return errors.New("invalid command") } @@ -55,10 +59,10 @@ func (a *App) RegisterPluginCommand(pluginID string, command *model.Command) err AutocompleteIconData: command.AutocompleteIconData, } - a.ch.pluginCommandsLock.Lock() - defer a.ch.pluginCommandsLock.Unlock() + s.pluginCommandsLock.Lock() + defer s.pluginCommandsLock.Unlock() - for _, pc := range a.ch.pluginCommands { + for _, pc := range s.pluginCommands { if pc.Command.Trigger == command.Trigger && pc.Command.TeamId == command.TeamId { if pc.PluginId == pluginID { pc.Command = command @@ -67,7 +71,7 @@ func (a *App) RegisterPluginCommand(pluginID string, command *model.Command) err } } - a.ch.pluginCommands = append(a.ch.pluginCommands, &PluginCommand{ + s.pluginCommands = append(s.pluginCommands, &PluginCommand{ Command: command, PluginId: pluginID, }) @@ -75,39 +79,47 @@ func (a *App) RegisterPluginCommand(pluginID string, command *model.Command) err } func (a *App) UnregisterPluginCommand(pluginID, teamID, trigger string) { + a.Srv().pluginService.unregisterPluginCommand(pluginID, teamID, trigger) +} + +func (s *PluginService) unregisterPluginCommand(pluginID, teamID, trigger string) { trigger = strings.ToLower(trigger) - a.ch.pluginCommandsLock.Lock() - defer a.ch.pluginCommandsLock.Unlock() + s.pluginCommandsLock.Lock() + defer s.pluginCommandsLock.Unlock() var remaining []*PluginCommand - for _, pc := range a.ch.pluginCommands { + for _, pc := range s.pluginCommands { if pc.Command.TeamId != teamID || pc.Command.Trigger != trigger { remaining = append(remaining, pc) } } - a.ch.pluginCommands = remaining + s.pluginCommands = remaining } -func (ch *Channels) unregisterPluginCommands(pluginID string) { - ch.pluginCommandsLock.Lock() - defer ch.pluginCommandsLock.Unlock() +func (s *PluginService) unregisterPluginCommands(pluginID string) { + s.pluginCommandsLock.Lock() + defer s.pluginCommandsLock.Unlock() var remaining []*PluginCommand - for _, pc := range ch.pluginCommands { + for _, pc := range s.pluginCommands { if pc.PluginId != pluginID { remaining = append(remaining, pc) } } - ch.pluginCommands = remaining + s.pluginCommands = remaining } func (a *App) PluginCommandsForTeam(teamID string) []*model.Command { - a.ch.pluginCommandsLock.RLock() - defer a.ch.pluginCommandsLock.RUnlock() + return a.Srv().pluginService.PluginCommandsForTeam(teamID) +} + +func (s *PluginService) PluginCommandsForTeam(teamID string) []*model.Command { + s.pluginCommandsLock.RLock() + defer s.pluginCommandsLock.RUnlock() var commands []*model.Command - for _, pc := range a.ch.pluginCommands { + for _, pc := range s.pluginCommands { if pc.Command.TeamId == "" || pc.Command.TeamId == teamID { commands = append(commands, pc.Command) } @@ -115,6 +127,24 @@ func (a *App) PluginCommandsForTeam(teamID string) []*model.Command { return commands } +func (s *PluginService) getPluginCommandFromArgs(args *model.CommandArgs) *PluginCommand { + parts := strings.Split(args.Command, " ") + trigger := parts[0][1:] + trigger = strings.ToLower(trigger) + + var matched *PluginCommand + s.pluginCommandsLock.RLock() + for _, pc := range s.pluginCommands { + if (pc.Command.TeamId == "" || pc.Command.TeamId == args.TeamId) && pc.Command.Trigger == trigger { + matched = pc + break + } + } + s.pluginCommandsLock.RUnlock() + + return matched +} + // tryExecutePluginCommand attempts to run a command provided by a plugin based on the given arguments. If no such // command can be found, returns nil for all arguments. func (a *App) tryExecutePluginCommand(c request.CTX, args *model.CommandArgs) (*model.Command, *model.CommandResponse, *model.AppError) { @@ -122,15 +152,7 @@ func (a *App) tryExecutePluginCommand(c request.CTX, args *model.CommandArgs) (* trigger := parts[0][1:] trigger = strings.ToLower(trigger) - var matched *PluginCommand - a.ch.pluginCommandsLock.RLock() - for _, pc := range a.ch.pluginCommands { - if (pc.Command.TeamId == "" || pc.Command.TeamId == args.TeamId) && pc.Command.Trigger == trigger { - matched = pc - break - } - } - a.ch.pluginCommandsLock.RUnlock() + matched := a.Srv().pluginService.getPluginCommandFromArgs(args) if matched == nil { return nil, nil, nil } diff --git a/app/plugin_commands_test.go b/app/plugin_commands_test.go index e56cf74718..1cf9751d9a 100644 --- a/app/plugin_commands_test.go +++ b/app/plugin_commands_test.go @@ -106,7 +106,7 @@ func TestPluginCommand(t *testing.T) { require.NotEqual(t, "plugin", commands.Trigger) } - th.App.ch.RemovePlugin(pluginIDs[0]) + th.App.PluginService().RemovePlugin(pluginIDs[0]) }) t.Run("re-entrant command registration on config change", func(t *testing.T) { @@ -207,7 +207,7 @@ func TestPluginCommand(t *testing.T) { killed = true } - th.App.ch.RemovePlugin(pluginIDs[0]) + th.App.PluginService().RemovePlugin(pluginIDs[0]) require.False(t, killed, "execute command appears to have deadlocked") }) @@ -285,7 +285,7 @@ func TestPluginCommand(t *testing.T) { require.Equal(t, model.CommandResponseTypeEphemeral, resp.ResponseType) require.Equal(t, "text", resp.Text) - th.App.ch.RemovePlugin(pluginIDs[0]) + th.App.PluginService().RemovePlugin(pluginIDs[0]) }) t.Run("plugin has crashed before execution of command", func(t *testing.T) { tearDown, pluginIDs, activationErrors := SetAppEnvironmentWithPlugins(t, []string{` @@ -329,7 +329,7 @@ func TestPluginCommand(t *testing.T) { require.Nil(t, resp) require.NotNil(t, err) require.Equal(t, err.Id, "model.plugin_command_error.error.app_error") - th.App.ch.RemovePlugin(pluginIDs[0]) + th.App.PluginService().RemovePlugin(pluginIDs[0]) }) t.Run("plugin has crashed due to the execution of the command", func(t *testing.T) { @@ -374,7 +374,7 @@ func TestPluginCommand(t *testing.T) { require.Nil(t, resp) require.NotNil(t, err) require.Equal(t, err.Id, "model.plugin_command_crash.error.app_error") - th.App.ch.RemovePlugin(pluginIDs[0]) + th.App.PluginService().RemovePlugin(pluginIDs[0]) }) t.Run("plugin returning status code 0", func(t *testing.T) { diff --git a/app/plugin_db_driver.go b/app/plugin_db_driver.go index a29fa7467f..0b74577f36 100644 --- a/app/plugin_db_driver.go +++ b/app/plugin_db_driver.go @@ -10,6 +10,7 @@ import ( "sync" "time" + "github.com/mattermost/mattermost-server/v6/app/platform" "github.com/mattermost/mattermost-server/v6/model" "github.com/mattermost/mattermost-server/v6/plugin" ) @@ -19,7 +20,7 @@ import ( // a new entry tracked centrally in a map. Further requests operate on the // object ID. type DriverImpl struct { - s *Server + ps *platform.PlatformService connMut sync.RWMutex connMap map[string]*sql.Conn txMut sync.Mutex @@ -30,9 +31,9 @@ type DriverImpl struct { rowsMap map[string]driver.Rows } -func NewDriverImpl(s *Server) *DriverImpl { +func NewDriverImpl(s *platform.PlatformService) *DriverImpl { return &DriverImpl{ - s: s, + ps: s, connMap: make(map[string]*sql.Conn), txMap: make(map[string]driver.Tx), stMap: make(map[string]driver.Stmt), @@ -41,11 +42,11 @@ func NewDriverImpl(s *Server) *DriverImpl { } func (d *DriverImpl) Conn(isMaster bool) (string, error) { - dbFunc := d.s.Platform().Store.GetInternalMasterDB + dbFunc := d.ps.Store.GetInternalMasterDB if !isMaster { - dbFunc = d.s.Platform().Store.GetInternalReplicaDB + dbFunc = d.ps.Store.GetInternalReplicaDB } - timeout := time.Duration(*d.s.Config().SqlSettings.QueryTimeout) * time.Second + timeout := time.Duration(*d.ps.Config().SqlSettings.QueryTimeout) * time.Second ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() conn, err := dbFunc().Conn(ctx) diff --git a/app/plugin_db_driver_test.go b/app/plugin_db_driver_test.go index a2c428fb6f..797f677d59 100644 --- a/app/plugin_db_driver_test.go +++ b/app/plugin_db_driver_test.go @@ -15,7 +15,7 @@ func TestConnCreateTimeout(t *testing.T) { *th.App.Config().SqlSettings.QueryTimeout = 0 - d := NewDriverImpl(th.Server) + d := NewDriverImpl(th.Server.platform) _, err := d.Conn(true) require.Error(t, err) } diff --git a/app/plugin_event.go b/app/plugin_event.go index c30e2d1af5..19d9f1dabc 100644 --- a/app/plugin_event.go +++ b/app/plugin_event.go @@ -9,10 +9,10 @@ import ( "github.com/mattermost/mattermost-server/v6/model" ) -func (ch *Channels) notifyClusterPluginEvent(event model.ClusterEvent, data model.PluginEventData) { +func (s *PluginService) notifyClusterPluginEvent(event model.ClusterEvent, data model.PluginEventData) { buf, _ := json.Marshal(data) - if ch.srv.platform.Cluster() != nil { - ch.srv.platform.Cluster().SendClusterMessage(&model.ClusterMessage{ + if s.platform.Cluster() != nil { + s.platform.Cluster().SendClusterMessage(&model.ClusterMessage{ Event: event, SendType: model.ClusterSendReliable, WaitForAllToSend: true, diff --git a/app/plugin_hooks_test.go b/app/plugin_hooks_test.go index 161994a1ab..105982a7b2 100644 --- a/app/plugin_hooks_test.go +++ b/app/plugin_hooks_test.go @@ -33,10 +33,10 @@ func SetAppEnvironmentWithPlugins(t *testing.T, pluginCode []string, app *App, a webappPluginDir, err := os.MkdirTemp("", "") require.NoError(t, err) - env, err := plugin.NewEnvironment(apiFunc, NewDriverImpl(app.Srv()), pluginDir, webappPluginDir, false, app.Log(), nil) + env, err := plugin.NewEnvironment(apiFunc, NewDriverImpl(app.Srv().Platform()), pluginDir, webappPluginDir, false, app.Log(), nil) require.NoError(t, err) - app.ch.SetPluginsEnvironment(env) + app.PluginService().SetPluginsEnvironment(env) pluginIDs := []string{} activationErrors := []error{} for _, code := range pluginCode { @@ -1030,10 +1030,10 @@ func TestHookMetrics(t *testing.T) { defer os.RemoveAll(pluginDir) defer os.RemoveAll(webappPluginDir) - env, err := plugin.NewEnvironment(th.NewPluginAPI, NewDriverImpl(th.Server), pluginDir, webappPluginDir, false, th.App.Log(), metricsMock) + env, err := plugin.NewEnvironment(th.NewPluginAPI, NewDriverImpl(th.Server.Platform()), pluginDir, webappPluginDir, false, th.App.Log(), metricsMock) require.NoError(t, err) - th.App.ch.SetPluginsEnvironment(env) + th.App.PluginService().SetPluginsEnvironment(env) pluginID := model.NewId() backend := filepath.Join(pluginDir, pluginID, "backend.exe") @@ -1234,7 +1234,7 @@ func TestHookRunDataRetention(t *testing.T) { require.True(t, th.App.GetPluginsEnvironment().IsActive(pluginID)) hookCalled := false - th.App.Channels().RunMultiHook(func(hooks plugin.Hooks) bool { + th.App.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { n, _ := hooks.RunDataRetention(0, 0) // Ensure return it correct assert.Equal(t, int64(100), n) @@ -1278,7 +1278,7 @@ func TestHookOnSendDailyTelemetry(t *testing.T) { require.True(t, th.App.GetPluginsEnvironment().IsActive(pluginID)) hookCalled := false - th.App.Channels().RunMultiHook(func(hooks plugin.Hooks) bool { + th.App.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { hooks.OnSendDailyTelemetry() hookCalled = true @@ -1322,7 +1322,7 @@ func TestHookOnCloudLimitsUpdated(t *testing.T) { require.True(t, th.App.GetPluginsEnvironment().IsActive(pluginID)) hookCalled := false - th.App.Channels().RunMultiHook(func(hooks plugin.Hooks) bool { + th.App.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { hooks.OnCloudLimitsUpdated(nil) hookCalled = true diff --git a/app/plugin_install.go b/app/plugin_install.go index 431c3abebe..f0c54a0da4 100644 --- a/app/plugin_install.go +++ b/app/plugin_install.go @@ -58,10 +58,10 @@ const managedPluginFileName = ".filestore" // fileStorePluginFolder is the folder name in the file store of the plugin bundles installed. const fileStorePluginFolder = "plugins" -func (ch *Channels) installPluginFromData(data model.PluginEventData) { +func (s *PluginService) installPluginFromData(data model.PluginEventData) { mlog.Debug("Installing plugin as per cluster message", mlog.String("plugin_id", data.Id)) - pluginSignaturePathMap, appErr := ch.getPluginsFromFolder() + pluginSignaturePathMap, appErr := s.getPluginsFromFolder() if appErr != nil { mlog.Error("Failed to get plugin signatures from filestore. Can't install plugin from data.", mlog.Err(appErr)) return @@ -72,53 +72,53 @@ func (ch *Channels) installPluginFromData(data model.PluginEventData) { return } - reader, appErr := ch.srv.fileReader(plugin.path) - if appErr != nil { - mlog.Error("Failed to open plugin bundle from file store.", mlog.String("bundle", plugin.path), mlog.Err(appErr)) + reader, err := s.fileStore.Reader(plugin.path) + if err != nil { + mlog.Error("Failed to open plugin bundle from file store.", mlog.String("bundle", plugin.path), mlog.Err(err)) return } defer reader.Close() var signature filestore.ReadCloseSeeker - if *ch.cfgSvc.Config().PluginSettings.RequirePluginSignature { - signature, appErr = ch.srv.fileReader(plugin.signaturePath) - if appErr != nil { - mlog.Error("Failed to open plugin signature from file store.", mlog.Err(appErr)) + if *s.platform.Config().PluginSettings.RequirePluginSignature { + signature, err = s.fileStore.Reader(plugin.signaturePath) + if err != nil { + mlog.Error("Failed to open plugin signature from file store.", mlog.Err(err)) return } defer signature.Close() } - manifest, appErr := ch.installPluginLocally(reader, signature, installPluginLocallyAlways) + manifest, appErr := s.installPluginLocally(reader, signature, installPluginLocallyAlways) if appErr != nil { mlog.Error("Failed to sync plugin from file store", mlog.String("bundle", plugin.path), mlog.Err(appErr)) return } - if err := ch.notifyPluginEnabled(manifest); err != nil { - mlog.Error("Failed notify plugin enabled", mlog.Err(err)) + if err2 := s.notifyPluginEnabled(manifest); err2 != nil { + mlog.Error("Failed notify plugin enabled", mlog.Err(err2)) } - if err := ch.notifyPluginStatusesChanged(); err != nil { - mlog.Error("Failed to notify plugin status changed", mlog.Err(err)) + if err2 := s.notifyPluginStatusesChanged(); err2 != nil { + mlog.Error("Failed to notify plugin status changed", mlog.Err(err2)) } } -func (ch *Channels) removePluginFromData(data model.PluginEventData) { +func (s *PluginService) removePluginFromData(data model.PluginEventData) { mlog.Debug("Removing plugin as per cluster message", mlog.String("plugin_id", data.Id)) - if err := ch.removePluginLocally(data.Id); err != nil { + if err := s.removePluginLocally(data.Id); err != nil { mlog.Warn("Failed to remove plugin locally", mlog.Err(err), mlog.String("id", data.Id)) } - if err := ch.notifyPluginStatusesChanged(); err != nil { + if err := s.notifyPluginStatusesChanged(); err != nil { mlog.Warn("failed to notify plugin status changed", mlog.Err(err)) } } // InstallPluginWithSignature verifies and installs plugin. -func (ch *Channels) installPluginWithSignature(pluginFile, signature io.ReadSeeker) (*model.Manifest, *model.AppError) { - return ch.installPlugin(pluginFile, signature, installPluginLocallyAlways) +func (s *PluginService) installPluginWithSignature(pluginFile, signature io.ReadSeeker) (*model.Manifest, *model.AppError) { + return s.installPlugin(pluginFile, signature, installPluginLocallyAlways) } // InstallPlugin unpacks and installs a plugin but does not enable or activate it. @@ -132,40 +132,40 @@ func (a *App) InstallPlugin(pluginFile io.ReadSeeker, replace bool) (*model.Mani } func (a *App) installPlugin(pluginFile, signature io.ReadSeeker, installationStrategy pluginInstallationStrategy) (*model.Manifest, *model.AppError) { - return a.ch.installPlugin(pluginFile, signature, installationStrategy) + return a.ch.srv.pluginService.installPlugin(pluginFile, signature, installationStrategy) } -func (ch *Channels) installPlugin(pluginFile, signature io.ReadSeeker, installationStrategy pluginInstallationStrategy) (*model.Manifest, *model.AppError) { - manifest, appErr := ch.installPluginLocally(pluginFile, signature, installationStrategy) +func (s *PluginService) installPlugin(pluginFile, signature io.ReadSeeker, installationStrategy pluginInstallationStrategy) (*model.Manifest, *model.AppError) { + manifest, appErr := s.installPluginLocally(pluginFile, signature, installationStrategy) if appErr != nil { return nil, appErr } if signature != nil { signature.Seek(0, 0) - if _, appErr = ch.srv.writeFile(signature, getSignatureStorePath(manifest.Id)); appErr != nil { - return nil, model.NewAppError("saveSignature", "app.plugin.store_signature.app_error", nil, "", http.StatusInternalServerError).Wrap(appErr) + if _, err := s.fileStore.WriteFile(signature, getSignatureStorePath(manifest.Id)); err != nil { + return nil, model.NewAppError("saveSignature", "app.plugin.store_signature.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } } // Store bundle in the file store to allow access from other servers. pluginFile.Seek(0, 0) - if _, appErr := ch.srv.writeFile(pluginFile, getBundleStorePath(manifest.Id)); appErr != nil { + if _, appErr := s.fileStore.WriteFile(pluginFile, getBundleStorePath(manifest.Id)); appErr != nil { return nil, model.NewAppError("uploadPlugin", "app.plugin.store_bundle.app_error", nil, "", http.StatusInternalServerError).Wrap(appErr) } - ch.notifyClusterPluginEvent( + s.notifyClusterPluginEvent( model.ClusterEventInstallPlugin, model.PluginEventData{ Id: manifest.Id, }, ) - if err := ch.notifyPluginEnabled(manifest); err != nil { + if err := s.notifyPluginEnabled(manifest); err != nil { mlog.Warn("Failed notify plugin enabled", mlog.Err(err)) } - if err := ch.notifyPluginStatusesChanged(); err != nil { + if err := s.notifyPluginStatusesChanged(); err != nil { mlog.Warn("Failed to notify plugin status changed", mlog.Err(err)) } @@ -174,10 +174,10 @@ func (ch *Channels) installPlugin(pluginFile, signature io.ReadSeeker, installat // InstallMarketplacePlugin installs a plugin listed in the marketplace server. It will get the plugin bundle // from the prepackaged folder, if available, or remotely if EnableRemoteMarketplace is true. -func (ch *Channels) InstallMarketplacePlugin(request *model.InstallMarketplacePluginRequest) (*model.Manifest, *model.AppError) { +func (s *PluginService) InstallMarketplacePlugin(request *model.InstallMarketplacePluginRequest) (*model.Manifest, *model.AppError) { var pluginFile, signatureFile io.ReadSeeker - prepackagedPlugin, appErr := ch.getPrepackagedPlugin(request.Id, request.Version) + prepackagedPlugin, appErr := s.getPrepackagedPlugin(request.Id, request.Version) if appErr != nil && appErr.Id != "app.plugin.marketplace_plugins.not_found.app_error" { return nil, appErr } @@ -192,9 +192,9 @@ func (ch *Channels) InstallMarketplacePlugin(request *model.InstallMarketplacePl signatureFile = bytes.NewReader(prepackagedPlugin.Signature) } - if *ch.cfgSvc.Config().PluginSettings.EnableRemoteMarketplace { + if *s.platform.Config().PluginSettings.EnableRemoteMarketplace { var plugin *model.BaseMarketplacePlugin - plugin, appErr = ch.getRemoteMarketplacePlugin(request.Id, request.Version) + plugin, appErr = s.getRemoteMarketplacePlugin(request.Id, request.Version) if appErr != nil { return nil, appErr } @@ -214,7 +214,7 @@ func (ch *Channels) InstallMarketplacePlugin(request *model.InstallMarketplacePl } if prepackagedVersion.LT(marketplaceVersion) { // Always true if no prepackaged plugin was found - downloadedPluginBytes, err := ch.srv.downloadFromURL(plugin.DownloadURL) + downloadedPluginBytes, err := s.downloadFromURL(plugin.DownloadURL) if err != nil { return nil, model.NewAppError("InstallMarketplacePlugin", "app.plugin.install_marketplace_plugin.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } @@ -234,7 +234,7 @@ func (ch *Channels) InstallMarketplacePlugin(request *model.InstallMarketplacePl return nil, model.NewAppError("InstallMarketplacePlugin", "app.plugin.marketplace_plugins.signature_not_found.app_error", nil, "", http.StatusInternalServerError) } - manifest, appErr := ch.installPluginWithSignature(pluginFile, signatureFile) + manifest, appErr := s.installPluginWithSignature(pluginFile, signatureFile) if appErr != nil { return nil, appErr } @@ -253,15 +253,15 @@ const ( installPluginLocallyAlways ) -func (ch *Channels) installPluginLocally(pluginFile, signature io.ReadSeeker, installationStrategy pluginInstallationStrategy) (*model.Manifest, *model.AppError) { - pluginsEnvironment := ch.GetPluginsEnvironment() +func (s *PluginService) installPluginLocally(pluginFile, signature io.ReadSeeker, installationStrategy pluginInstallationStrategy) (*model.Manifest, *model.AppError) { + pluginsEnvironment := s.GetPluginsEnvironment() if pluginsEnvironment == nil { return nil, model.NewAppError("installPluginLocally", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented) } // verify signature if signature != nil { - if err := ch.verifyPlugin(pluginFile, signature); err != nil { + if err := s.verifyPlugin(pluginFile, signature); err != nil { return nil, err } } @@ -277,7 +277,7 @@ func (ch *Channels) installPluginLocally(pluginFile, signature io.ReadSeeker, in return nil, appErr } - manifest, appErr = ch.installExtractedPlugin(manifest, pluginDir, installationStrategy) + manifest, appErr = s.installExtractedPlugin(manifest, pluginDir, installationStrategy) if appErr != nil { return nil, appErr } @@ -312,8 +312,8 @@ func extractPlugin(pluginFile io.ReadSeeker, extractDir string) (*model.Manifest return manifest, extractDir, nil } -func (ch *Channels) installExtractedPlugin(manifest *model.Manifest, fromPluginDir string, installationStrategy pluginInstallationStrategy) (*model.Manifest, *model.AppError) { - pluginsEnvironment := ch.GetPluginsEnvironment() +func (s *PluginService) installExtractedPlugin(manifest *model.Manifest, fromPluginDir string, installationStrategy pluginInstallationStrategy) (*model.Manifest, *model.AppError) { + pluginsEnvironment := s.GetPluginsEnvironment() if pluginsEnvironment == nil { return nil, model.NewAppError("installExtractedPlugin", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented) } @@ -360,12 +360,12 @@ func (ch *Channels) installExtractedPlugin(manifest *model.Manifest, fromPluginD // Otherwise remove the existing installation prior to install below. mlog.Debug("Removing existing installation of plugin before local install", mlog.String("plugin_id", existingManifest.Id), mlog.String("version", existingManifest.Version)) - if err := ch.removePluginLocally(existingManifest.Id); err != nil { + if err := s.removePluginLocally(existingManifest.Id); err != nil { return nil, model.NewAppError("installExtractedPlugin", "app.plugin.install_id_failed_remove.app_error", nil, "", http.StatusBadRequest) } } - pluginPath := filepath.Join(*ch.cfgSvc.Config().PluginSettings.Directory, manifest.Id) + pluginPath := filepath.Join(*s.platform.Config().PluginSettings.Directory, manifest.Id) err = utils.CopyDir(fromPluginDir, pluginPath) if err != nil { return nil, model.NewAppError("installExtractedPlugin", "app.plugin.mvdir.app_error", nil, "", http.StatusInternalServerError).Wrap(err) @@ -387,9 +387,9 @@ func (ch *Channels) installExtractedPlugin(manifest *model.Manifest, fromPluginD } // Activate the plugin if enabled. - pluginState := ch.cfgSvc.Config().PluginSettings.PluginStates[manifest.Id] + pluginState := s.platform.Config().PluginSettings.PluginStates[manifest.Id] if pluginState != nil && pluginState.Enable { - if hasOverride, enabled := ch.getPluginStateOverride(manifest.Id); hasOverride && !enabled { + if hasOverride, enabled := s.getPluginStateOverride(manifest.Id); hasOverride && !enabled { return manifest, nil } @@ -405,49 +405,49 @@ func (ch *Channels) installExtractedPlugin(manifest *model.Manifest, fromPluginD return manifest, nil } -func (ch *Channels) RemovePlugin(id string) *model.AppError { +func (s *PluginService) RemovePlugin(id string) *model.AppError { // Disable plugin before removal to make sure this // plugin remains disabled on re-install. - if err := ch.disablePlugin(id); err != nil { + if err := s.disablePlugin(id); err != nil { return err } - if err := ch.removePluginLocally(id); err != nil { + if err := s.removePluginLocally(id); err != nil { return err } // Remove bundle from the file store. storePluginFileName := getBundleStorePath(id) - bundleExist, err := ch.srv.fileExists(storePluginFileName) + bundleExist, err := s.fileStore.FileExists(storePluginFileName) if err != nil { return model.NewAppError("removePlugin", "app.plugin.remove_bundle.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } if !bundleExist { return nil } - if err = ch.srv.removeFile(storePluginFileName); err != nil { + if err = s.fileStore.RemoveFile(storePluginFileName); err != nil { return model.NewAppError("removePlugin", "app.plugin.remove_bundle.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } - if err = ch.removeSignature(id); err != nil { - mlog.Warn("Can't remove signature", mlog.Err(err)) + if err2 := s.removeSignature(id); err2 != nil { + mlog.Warn("Can't remove signature", mlog.Err(err2)) } - ch.notifyClusterPluginEvent( + s.notifyClusterPluginEvent( model.ClusterEventRemovePlugin, model.PluginEventData{ Id: id, }, ) - if err := ch.notifyPluginStatusesChanged(); err != nil { + if err := s.notifyPluginStatusesChanged(); err != nil { mlog.Warn("Failed to notify plugin status changed", mlog.Err(err)) } return nil } -func (ch *Channels) removePluginLocally(id string) *model.AppError { - pluginsEnvironment := ch.GetPluginsEnvironment() +func (s *PluginService) removePluginLocally(id string) *model.AppError { + pluginsEnvironment := s.GetPluginsEnvironment() if pluginsEnvironment == nil { return model.NewAppError("removePlugin", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented) } @@ -473,7 +473,7 @@ func (ch *Channels) removePluginLocally(id string) *model.AppError { pluginsEnvironment.Deactivate(id) pluginsEnvironment.RemovePlugin(id) - ch.unregisterPluginCommands(id) + s.unregisterPluginCommands(id) if err := os.RemoveAll(pluginPath); err != nil { return model.NewAppError("removePlugin", "app.plugin.remove.app_error", nil, "", http.StatusInternalServerError).Wrap(err) @@ -482,9 +482,9 @@ func (ch *Channels) removePluginLocally(id string) *model.AppError { return nil } -func (ch *Channels) removeSignature(pluginID string) *model.AppError { +func (s *PluginService) removeSignature(pluginID string) *model.AppError { filePath := getSignatureStorePath(pluginID) - exists, err := ch.srv.fileExists(filePath) + exists, err := s.fileStore.FileExists(filePath) if err != nil { return model.NewAppError("removeSignature", "app.plugin.remove_bundle.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } @@ -492,7 +492,7 @@ func (ch *Channels) removeSignature(pluginID string) *model.AppError { mlog.Debug("no plugin signature to remove", mlog.String("plugin_id", pluginID)) return nil } - if err = ch.srv.removeFile(filePath); err != nil { + if err = s.fileStore.RemoveFile(filePath); err != nil { return model.NewAppError("removeSignature", "app.plugin.remove_bundle.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } return nil diff --git a/app/plugin_install_test.go b/app/plugin_install_test.go index a3eb6cfb2d..ca15747865 100644 --- a/app/plugin_install_test.go +++ b/app/plugin_install_test.go @@ -73,7 +73,7 @@ func TestInstallPluginLocally(t *testing.T) { th := Setup(t) defer th.TearDown() - actualManifest, appErr := th.App.ch.installPluginLocally(&nilReadSeeker{}, nil, installPluginLocallyOnlyIfNew) + actualManifest, appErr := th.App.PluginService().installPluginLocally(&nilReadSeeker{}, nil, installPluginLocallyOnlyIfNew) require.NotNil(t, appErr) assert.Equal(t, "app.plugin.extract.app_error", appErr.Id, appErr.Error()) require.Nil(t, actualManifest) @@ -87,7 +87,7 @@ func TestInstallPluginLocally(t *testing.T) { {"test", "test file"}, }) - actualManifest, appErr := th.App.ch.installPluginLocally(reader, nil, installPluginLocallyOnlyIfNew) + actualManifest, appErr := th.App.PluginService().installPluginLocally(reader, nil, installPluginLocallyOnlyIfNew) require.NotNil(t, appErr) assert.Equal(t, "app.plugin.manifest.app_error", appErr.Id, appErr.Error()) require.Nil(t, actualManifest) @@ -106,7 +106,7 @@ func TestInstallPluginLocally(t *testing.T) { {"plugin.json", string(manifestJSON)}, }) - actualManifest, appError := th.App.ch.installPluginLocally(reader, nil, installationStrategy) + actualManifest, appError := th.App.PluginService().installPluginLocally(reader, nil, installationStrategy) if actualManifest != nil { require.Equal(t, manifest, actualManifest) } @@ -134,7 +134,7 @@ func TestInstallPluginLocally(t *testing.T) { require.NoError(t, err) for _, bundleInfo := range bundleInfos { - err := th.App.ch.removePluginLocally(bundleInfo.Manifest.Id) + err := th.App.PluginService().removePluginLocally(bundleInfo.Manifest.Id) require.Nilf(t, err, "failed to remove existing plugin %s", bundleInfo.Manifest.Id) } } diff --git a/app/plugin_requests.go b/app/plugin_requests.go index 1ccc966822..208adb11f4 100644 --- a/app/plugin_requests.go +++ b/app/plugin_requests.go @@ -20,16 +20,16 @@ import ( "github.com/mattermost/mattermost-server/v6/utils" ) -func (ch *Channels) ServePluginRequest(w http.ResponseWriter, r *http.Request) { +func (s *PluginService) ServePluginRequest(w http.ResponseWriter, r *http.Request) { params := mux.Vars(r) - if handler, ok := ch.routerSvc.getHandler(params["plugin_id"]); ok { - ch.servePluginRequest(w, r, func(*plugin.Context, http.ResponseWriter, *http.Request) { + if handler, ok := s.channels.routerSvc.getHandler(params["plugin_id"]); ok { + s.servePluginRequest(w, r, func(*plugin.Context, http.ResponseWriter, *http.Request) { handler.ServeHTTP(w, r) }) return } - pluginsEnvironment := ch.GetPluginsEnvironment() + pluginsEnvironment := s.GetPluginsEnvironment() if pluginsEnvironment == nil { err := model.NewAppError("ServePluginRequest", "app.plugin.disabled.app_error", nil, "Enable plugins to serve plugin requests", http.StatusNotImplemented) mlog.Error(err.Error()) @@ -49,11 +49,11 @@ func (ch *Channels) ServePluginRequest(w http.ResponseWriter, r *http.Request) { return } - ch.servePluginRequest(w, r, hooks.ServeHTTP) + s.servePluginRequest(w, r, hooks.ServeHTTP) } func (a *App) ServeInterPluginRequest(w http.ResponseWriter, r *http.Request, sourcePluginId, destinationPluginId string) { - pluginsEnvironment := a.ch.GetPluginsEnvironment() + pluginsEnvironment := a.ch.srv.pluginService.GetPluginsEnvironment() if pluginsEnvironment == nil { err := model.NewAppError("ServeInterPluginRequest", "app.plugin.disabled.app_error", nil, "Plugin environment not found.", http.StatusNotImplemented) a.Log().Error(err.Error()) @@ -87,7 +87,7 @@ func (a *App) ServeInterPluginRequest(w http.ResponseWriter, r *http.Request, so // ServePluginPublicRequest serves public plugin files // at the URL http(s)://$SITE_URL/plugins/$PLUGIN_ID/public/{anything} -func (ch *Channels) ServePluginPublicRequest(w http.ResponseWriter, r *http.Request) { +func (s *PluginService) ServePluginPublicRequest(w http.ResponseWriter, r *http.Request) { if strings.HasSuffix(r.URL.Path, "/") { http.NotFound(w, r) return @@ -97,7 +97,7 @@ func (ch *Channels) ServePluginPublicRequest(w http.ResponseWriter, r *http.Requ vars := mux.Vars(r) pluginID := vars["plugin_id"] - pluginsEnv := ch.GetPluginsEnvironment() + pluginsEnv := s.GetPluginsEnvironment() // Check if someone has nullified the pluginsEnv in the meantime if pluginsEnv == nil { @@ -121,11 +121,11 @@ func (ch *Channels) ServePluginPublicRequest(w http.ResponseWriter, r *http.Requ http.ServeFile(w, r, publicFile) } -func (ch *Channels) servePluginRequest(w http.ResponseWriter, r *http.Request, handler func(*plugin.Context, http.ResponseWriter, *http.Request)) { +func (s *PluginService) servePluginRequest(w http.ResponseWriter, r *http.Request, handler func(*plugin.Context, http.ResponseWriter, *http.Request)) { token := "" context := &plugin.Context{ RequestId: model.NewId(), - IPAddress: utils.GetIPAddress(r, ch.cfgSvc.Config().ServiceSettings.TrustedProxyIPHeader), + IPAddress: utils.GetIPAddress(r, s.platform.Config().ServiceSettings.TrustedProxyIPHeader), AcceptLanguage: r.Header.Get("Accept-Language"), UserAgent: r.UserAgent(), } @@ -148,8 +148,8 @@ func (ch *Channels) servePluginRequest(w http.ResponseWriter, r *http.Request, h r.Header.Del("Mattermost-User-Id") if token != "" { - session, err := New(ServerConnector(ch)).GetSession(token) - defer ch.srv.platform.ReturnSessionToPool(session) + session, err := New(ServerConnector(s.channels)).GetSession(token) + defer s.platform.ReturnSessionToPool(session) csrfCheckPassed := false @@ -190,7 +190,7 @@ func (ch *Channels) servePluginRequest(w http.ResponseWriter, r *http.Request, h mlog.String("user_id", userID), } - if *ch.cfgSvc.Config().ServiceSettings.ExperimentalStrictCSRFEnforcement { + if *s.platform.Config().ServiceSettings.ExperimentalStrictCSRFEnforcement { mlog.Warn(csrfErrorMessage, fields...) } else { mlog.Debug(csrfErrorMessage, fields...) @@ -219,7 +219,7 @@ func (ch *Channels) servePluginRequest(w http.ResponseWriter, r *http.Request, h params := mux.Vars(r) - subpath, _ := utils.GetSubpathFromConfig(ch.cfgSvc.Config()) + subpath, _ := utils.GetSubpathFromConfig(s.platform.Config()) newQuery := r.URL.Query() newQuery.Del("access_token") diff --git a/app/plugin_requests_test.go b/app/plugin_requests_test.go index c41c70be6d..e457d8e5f1 100644 --- a/app/plugin_requests_test.go +++ b/app/plugin_requests_test.go @@ -24,7 +24,7 @@ func TestServePluginPublicRequest(t *testing.T) { require.NoError(t, err) rr := httptest.NewRecorder() - handler := http.HandlerFunc(th.App.ch.ServePluginPublicRequest) + handler := http.HandlerFunc(th.App.PluginService().ServePluginPublicRequest) handler.ServeHTTP(rr, req) assert.Equal(t, http.StatusNotFound, rr.Code) diff --git a/app/plugin_shutdown_test.go b/app/plugin_shutdown_test.go index 293d882f1f..1c77fc3814 100644 --- a/app/plugin_shutdown_test.go +++ b/app/plugin_shutdown_test.go @@ -63,7 +63,7 @@ func TestPluginShutdownTest(t *testing.T) { done := make(chan bool) go func() { defer close(done) - th.App.ch.ShutDownPlugins() + th.App.PluginService().ShutDownPlugins() }() select { diff --git a/app/plugin_signature.go b/app/plugin_signature.go index 0903aa08fc..928a9687b8 100644 --- a/app/plugin_signature.go +++ b/app/plugin_signature.go @@ -73,16 +73,16 @@ func (a *App) DeletePublicKey(name string) *model.AppError { // VerifyPlugin checks that the given signature corresponds to the given plugin and matches a trusted certificate. func (a *App) VerifyPlugin(plugin, signature io.ReadSeeker) *model.AppError { - return a.ch.verifyPlugin(plugin, signature) + return a.ch.srv.pluginService.verifyPlugin(plugin, signature) } -func (ch *Channels) verifyPlugin(plugin, signature io.ReadSeeker) *model.AppError { +func (s *PluginService) verifyPlugin(plugin, signature io.ReadSeeker) *model.AppError { if err := verifySignature(bytes.NewReader(mattermostPluginPublicKey), plugin, signature); err == nil { return nil } - publicKeys := ch.cfgSvc.Config().PluginSettings.SignaturePublicKeyFiles + publicKeys := s.platform.Config().PluginSettings.SignaturePublicKeyFiles for _, pk := range publicKeys { - pkBytes, appErr := ch.srv.getPublicKey(pk) + pkBytes, appErr := s.platform.GetConfigFile(pk) if appErr != nil { mlog.Warn("Unable to get public key for ", mlog.String("filename", pk)) continue diff --git a/app/plugin_statuses.go b/app/plugin_statuses.go index 399d58e5b2..2b27d7520c 100644 --- a/app/plugin_statuses.go +++ b/app/plugin_statuses.go @@ -10,8 +10,8 @@ import ( ) // GetPluginStatus returns the status for a plugin installed on this server. -func (ch *Channels) GetPluginStatus(id string) (*model.PluginStatus, *model.AppError) { - pluginsEnvironment := ch.GetPluginsEnvironment() +func (s *PluginService) GetPluginStatus(id string) (*model.PluginStatus, *model.AppError) { + pluginsEnvironment := s.GetPluginsEnvironment() if pluginsEnvironment == nil { return nil, model.NewAppError("GetPluginStatus", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented) } @@ -24,8 +24,8 @@ func (ch *Channels) GetPluginStatus(id string) (*model.PluginStatus, *model.AppE for _, status := range pluginStatuses { if status.PluginId == id { // Add our cluster ID - if ch.srv.platform.Cluster() != nil { - status.ClusterId = ch.srv.platform.Cluster().GetClusterId() + if s.platform.Cluster() != nil { + status.ClusterId = s.platform.Cluster().GetClusterId() } return status, nil @@ -37,12 +37,12 @@ func (ch *Channels) GetPluginStatus(id string) (*model.PluginStatus, *model.AppE // GetPluginStatus returns the status for a plugin installed on this server. func (a *App) GetPluginStatus(id string) (*model.PluginStatus, *model.AppError) { - return a.ch.GetPluginStatus(id) + return a.ch.srv.pluginService.GetPluginStatus(id) } // GetPluginStatuses returns the status for plugins installed on this server. -func (ch *Channels) GetPluginStatuses() (model.PluginStatuses, *model.AppError) { - pluginsEnvironment := ch.GetPluginsEnvironment() +func (s *PluginService) GetPluginStatuses() (model.PluginStatuses, *model.AppError) { + pluginsEnvironment := s.GetPluginsEnvironment() if pluginsEnvironment == nil { return nil, model.NewAppError("GetPluginStatuses", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented) } @@ -54,8 +54,8 @@ func (ch *Channels) GetPluginStatuses() (model.PluginStatuses, *model.AppError) // Add our cluster ID for _, status := range pluginStatuses { - if ch.srv.platform.Cluster() != nil { - status.ClusterId = ch.srv.platform.Cluster().GetClusterId() + if s.platform.Cluster() != nil { + status.ClusterId = s.platform.Cluster().GetClusterId() } else { status.ClusterId = "" } @@ -66,22 +66,22 @@ func (ch *Channels) GetPluginStatuses() (model.PluginStatuses, *model.AppError) // GetPluginStatuses returns the status for plugins installed on this server. func (a *App) GetPluginStatuses() (model.PluginStatuses, *model.AppError) { - return a.ch.GetPluginStatuses() + return a.ch.srv.pluginService.GetPluginStatuses() } // GetClusterPluginStatuses returns the status for plugins installed anywhere in the cluster. func (a *App) GetClusterPluginStatuses() (model.PluginStatuses, *model.AppError) { - return a.ch.getClusterPluginStatuses() + return a.ch.srv.pluginService.getClusterPluginStatuses() } -func (ch *Channels) getClusterPluginStatuses() (model.PluginStatuses, *model.AppError) { - pluginStatuses, err := ch.GetPluginStatuses() +func (s *PluginService) getClusterPluginStatuses() (model.PluginStatuses, *model.AppError) { + pluginStatuses, err := s.GetPluginStatuses() if err != nil { return nil, err } - if ch.srv.platform.Cluster() != nil && *ch.cfgSvc.Config().ClusterSettings.Enable { - clusterPluginStatuses, err := ch.srv.platform.Cluster().GetPluginStatuses() + if s.platform.Cluster() != nil && *s.platform.Config().ClusterSettings.Enable { + clusterPluginStatuses, err := s.platform.Cluster().GetPluginStatuses() if err != nil { return nil, model.NewAppError("GetClusterPluginStatuses", "app.plugin.get_cluster_plugin_statuses.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } @@ -92,8 +92,8 @@ func (ch *Channels) getClusterPluginStatuses() (model.PluginStatuses, *model.App return pluginStatuses, nil } -func (ch *Channels) notifyPluginStatusesChanged() error { - pluginStatuses, err := ch.getClusterPluginStatuses() +func (s *PluginService) notifyPluginStatusesChanged() error { + pluginStatuses, err := s.getClusterPluginStatuses() if err != nil { return err } @@ -102,7 +102,7 @@ func (ch *Channels) notifyPluginStatusesChanged() error { message := model.NewWebSocketEvent(model.WebsocketEventPluginStatusesChanged, "", "", "", nil, "") message.Add("plugin_statuses", pluginStatuses) message.GetBroadcast().ContainsSensitiveData = true - ch.srv.platform.Publish(message) + s.platform.Publish(message) return nil } diff --git a/app/plugin_test.go b/app/plugin_test.go index 57802c67ac..0d3ec65431 100644 --- a/app/plugin_test.go +++ b/app/plugin_test.go @@ -346,7 +346,7 @@ func TestServePluginRequest(t *testing.T) { w := httptest.NewRecorder() r := httptest.NewRequest("GET", "/plugins/foo/bar", nil) - th.App.ch.ServePluginRequest(w, r) + th.App.PluginService().ServePluginRequest(w, r) assert.Equal(t, http.StatusNotImplemented, w.Result().StatusCode) } @@ -390,7 +390,7 @@ func TestPrivateServePluginRequest(t *testing.T) { request = mux.SetURLVars(request, map[string]string{"plugin_id": "id"}) - th.App.ch.servePluginRequest(recorder, request, handler) + th.App.PluginService().servePluginRequest(recorder, request, handler) }) } @@ -413,7 +413,7 @@ func TestHandlePluginRequest(t *testing.T) { var assertions func(*http.Request) router := mux.NewRouter() router.HandleFunc("/plugins/{plugin_id:[A-Za-z0-9\\_\\-\\.]+}/{anything:.*}", func(_ http.ResponseWriter, r *http.Request) { - th.App.ch.servePluginRequest(nil, r, func(_ *plugin.Context, _ http.ResponseWriter, r *http.Request) { + th.App.PluginService().servePluginRequest(nil, r, func(_ *plugin.Context, _ http.ResponseWriter, r *http.Request) { assertions(r) }) }) @@ -625,7 +625,7 @@ func TestPluginSync(t *testing.T) { appErr = th.App.DeletePublicKey("pub_key") checkNoError(t, appErr) - appErr = th.App.ch.RemovePlugin("testplugin") + appErr = th.App.PluginService().RemovePlugin("testplugin") checkNoError(t, appErr) }) }) @@ -642,7 +642,7 @@ func TestChannelsPluginsInit(t *testing.T) { path, _ := fileutils.FindDir("tests") require.NotPanics(t, func() { - th.Server.Channels().initPlugins(ctx, path, path) + th.Server.pluginService.initPlugins(ctx, path, path) }) } @@ -763,7 +763,7 @@ func TestPluginPanicLogs(t *testing.T) { th.TestLogger.Flush() // We shutdown plugins first so that the read on the log buffer is race-free. - th.App.ch.ShutDownPlugins() + th.App.PluginService().ShutDownPlugins() tearDown() testlib.AssertLog(t, th.LogBuffer, mlog.LvlDebug.Name, "panic: some text from panic") @@ -831,7 +831,7 @@ func TestProcessPrepackagedPlugins(t *testing.T) { require.NoError(t, err) require.NotNil(t, pluginBytes) - manifest, appErr := th.App.ch.installPluginLocally(bytes.NewReader(pluginBytes), nil, installPluginLocallyAlways) + manifest, appErr := th.App.PluginService().installPluginLocally(bytes.NewReader(pluginBytes), nil, installPluginLocallyAlways) require.Nil(t, appErr) require.Equal(t, "testplugin", manifest.Id) @@ -848,7 +848,7 @@ func TestProcessPrepackagedPlugins(t *testing.T) { *cfg.PluginSettings.EnableRemoteMarketplace = false }) - plugins := th.App.ch.processPrepackagedPlugins(prepackagedPluginsDir) + plugins := th.App.PluginService().processPrepackagedPlugins(prepackagedPluginsDir) require.Len(t, plugins, 1) require.Equal(t, plugins[0].Manifest.Id, "testplugin") require.Empty(t, plugins[0].Signature, 0) @@ -858,7 +858,7 @@ func TestProcessPrepackagedPlugins(t *testing.T) { require.Len(t, pluginStatus, 1) require.Equal(t, pluginStatus[0].PluginId, "testplugin") - appErr = th.App.ch.RemovePlugin("testplugin") + appErr = th.App.PluginService().RemovePlugin("testplugin") checkNoError(t, appErr) pluginStatus, err = env.Statuses() @@ -875,7 +875,7 @@ func TestProcessPrepackagedPlugins(t *testing.T) { env := th.App.GetPluginsEnvironment() - plugins := th.App.ch.processPrepackagedPlugins(prepackagedPluginsDir) + plugins := th.App.PluginService().processPrepackagedPlugins(prepackagedPluginsDir) require.Len(t, plugins, 1) require.Equal(t, plugins[0].Manifest.Id, "testplugin") require.Empty(t, plugins[0].Signature, 0) @@ -908,7 +908,7 @@ func TestProcessPrepackagedPlugins(t *testing.T) { err = testlib.CopyFile(testPlugin2SignaturePath, filepath.Join(prepackagedPluginsDir, "testplugin2.tar.gz.sig")) require.NoError(t, err) - plugins := th.App.ch.processPrepackagedPlugins(prepackagedPluginsDir) + plugins := th.App.PluginService().processPrepackagedPlugins(prepackagedPluginsDir) require.Len(t, plugins, 2) require.Contains(t, []string{"testplugin", "testplugin2"}, plugins[0].Manifest.Id) require.NotEmpty(t, plugins[0].Signature) @@ -939,7 +939,7 @@ func TestProcessPrepackagedPlugins(t *testing.T) { require.NoError(t, err) require.NotNil(t, pluginBytes) - manifest, appErr := th.App.ch.installPluginLocally(bytes.NewReader(pluginBytes), nil, installPluginLocallyAlways) + manifest, appErr := th.App.PluginService().installPluginLocally(bytes.NewReader(pluginBytes), nil, installPluginLocallyAlways) require.Nil(t, appErr) require.Equal(t, "testplugin", manifest.Id) @@ -957,7 +957,7 @@ func TestProcessPrepackagedPlugins(t *testing.T) { err = testlib.CopyFile(testPlugin2SignaturePath, filepath.Join(prepackagedPluginsDir, "testplugin2.tar.gz.sig")) require.NoError(t, err) - plugins := th.App.ch.processPrepackagedPlugins(prepackagedPluginsDir) + plugins := th.App.PluginService().processPrepackagedPlugins(prepackagedPluginsDir) require.Len(t, plugins, 2) require.Contains(t, []string{"testplugin", "testplugin2"}, plugins[0].Manifest.Id) require.NotEmpty(t, plugins[0].Signature) @@ -969,7 +969,7 @@ func TestProcessPrepackagedPlugins(t *testing.T) { require.Len(t, pluginStatus, 1) require.Equal(t, pluginStatus[0].PluginId, "testplugin") - appErr = th.App.ch.RemovePlugin("testplugin") + appErr = th.App.PluginService().RemovePlugin("testplugin") checkNoError(t, appErr) pluginStatus, err = env.Statuses() @@ -994,7 +994,7 @@ func TestProcessPrepackagedPlugins(t *testing.T) { err = testlib.CopyFile(testPlugin2SignaturePath, filepath.Join(prepackagedPluginsDir, "testplugin2.tar.gz.sig")) require.NoError(t, err) - plugins := th.App.ch.processPrepackagedPlugins(prepackagedPluginsDir) + plugins := th.App.PluginService().processPrepackagedPlugins(prepackagedPluginsDir) require.Len(t, plugins, 2) require.Contains(t, []string{"testplugin", "testplugin2"}, plugins[0].Manifest.Id) require.NotEmpty(t, plugins[0].Signature) @@ -1071,14 +1071,14 @@ func TestGetPluginStateOverride(t *testing.T) { defer th.TearDown() t.Run("no override", func(t *testing.T) { - overrides, value := th.App.ch.getPluginStateOverride("focalboard") + overrides, value := th.App.PluginService().getPluginStateOverride("focalboard") require.False(t, overrides) require.False(t, value) }) t.Run("calls override", func(t *testing.T) { t.Run("on-prem", func(t *testing.T) { - overrides, value := th.App.ch.getPluginStateOverride("com.mattermost.calls") + overrides, value := th.App.PluginService().getPluginStateOverride("com.mattermost.calls") require.False(t, overrides) require.False(t, value) }) @@ -1086,7 +1086,7 @@ func TestGetPluginStateOverride(t *testing.T) { t.Run("Cloud, without enabled flag", func(t *testing.T) { os.Setenv("MM_CLOUD_INSTALLATION_ID", "test") defer os.Unsetenv("MM_CLOUD_INSTALLATION_ID") - overrides, value := th.App.ch.getPluginStateOverride("com.mattermost.calls") + overrides, value := th.App.PluginService().getPluginStateOverride("com.mattermost.calls") require.False(t, overrides) require.False(t, value) }) @@ -1100,7 +1100,7 @@ func TestGetPluginStateOverride(t *testing.T) { th2 := Setup(t) defer th2.TearDown() - overrides, value := th2.App.ch.getPluginStateOverride("com.mattermost.calls") + overrides, value := th2.App.PluginService().getPluginStateOverride("com.mattermost.calls") require.False(t, overrides) require.False(t, value) }) @@ -1114,7 +1114,7 @@ func TestGetPluginStateOverride(t *testing.T) { th2 := Setup(t) defer th2.TearDown() - overrides, value := th2.App.ch.getPluginStateOverride("com.mattermost.calls") + overrides, value := th2.App.PluginService().getPluginStateOverride("com.mattermost.calls") require.True(t, overrides) require.False(t, value) }) @@ -1126,7 +1126,7 @@ func TestGetPluginStateOverride(t *testing.T) { th2 := Setup(t) defer th2.TearDown() - overrides, value := th2.App.ch.getPluginStateOverride("com.mattermost.calls") + overrides, value := th2.App.PluginService().getPluginStateOverride("com.mattermost.calls") require.True(t, overrides) require.False(t, value) }) @@ -1134,7 +1134,7 @@ func TestGetPluginStateOverride(t *testing.T) { t.Run("apps override", func(t *testing.T) { t.Run("without enabled flag", func(t *testing.T) { - overrides, value := th.App.ch.getPluginStateOverride("com.mattermost.apps") + overrides, value := th.App.PluginService().getPluginStateOverride("com.mattermost.apps") require.False(t, overrides) require.False(t, value) }) @@ -1146,7 +1146,7 @@ func TestGetPluginStateOverride(t *testing.T) { th2 := Setup(t) defer th2.TearDown() - overrides, value := th2.App.ch.getPluginStateOverride("com.mattermost.apps") + overrides, value := th2.App.PluginService().getPluginStateOverride("com.mattermost.apps") require.True(t, overrides) require.False(t, value) }) diff --git a/app/post.go b/app/post.go index 095494d080..b98cb13759 100644 --- a/app/post.go +++ b/app/post.go @@ -269,7 +269,7 @@ func (a *App) CreatePost(c request.CTX, post *model.Post, channel *model.Channel } var rejectionError *model.AppError pluginContext := pluginContext(c) - a.ch.RunMultiHook(func(hooks plugin.Hooks) bool { + a.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { replacementPost, rejectionReason := hooks.MessageWillBePosted(pluginContext, post.ForPlugin()) if rejectionReason != "" { id := "Post rejected by plugin. " + rejectionReason @@ -328,7 +328,7 @@ func (a *App) CreatePost(c request.CTX, post *model.Post, channel *model.Channel // and to remove the non-GOB-encodable Metadata from it. pluginPost := rpost.ForPlugin() a.Srv().Go(func() { - a.ch.RunMultiHook(func(hooks plugin.Hooks) bool { + a.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { hooks.MessageHasBeenPosted(pluginContext, pluginPost) return true }, plugin.MessageHasBeenPostedID) @@ -655,7 +655,7 @@ func (a *App) UpdatePost(c *request.Context, post *model.Post, safeUpdate bool) var rejectionReason string pluginContext := pluginContext(c) - a.ch.RunMultiHook(func(hooks plugin.Hooks) bool { + a.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { newPost, rejectionReason = hooks.MessageWillBeUpdated(pluginContext, newPost.ForPlugin(), oldPost.ForPlugin()) return post != nil }, plugin.MessageWillBeUpdatedID) @@ -680,7 +680,7 @@ func (a *App) UpdatePost(c *request.Context, post *model.Post, safeUpdate bool) pluginOldPost := oldPost.ForPlugin() pluginNewPost := newPost.ForPlugin() a.Srv().Go(func() { - a.ch.RunMultiHook(func(hooks plugin.Hooks) bool { + a.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { hooks.MessageHasBeenUpdated(pluginContext, pluginNewPost, pluginOldPost) return true }, plugin.MessageHasBeenUpdatedID) diff --git a/app/reaction.go b/app/reaction.go index fc6d54699f..c79036b443 100644 --- a/app/reaction.go +++ b/app/reaction.go @@ -45,7 +45,7 @@ func (a *App) SaveReactionForPost(c *request.Context, reaction *model.Reaction) pluginContext := pluginContext(c) a.Srv().Go(func() { - a.ch.RunMultiHook(func(hooks plugin.Hooks) bool { + a.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { hooks.ReactionHasBeenAdded(pluginContext, reaction) return true }, plugin.ReactionHasBeenAddedID) @@ -142,7 +142,7 @@ func (a *App) DeleteReactionForPost(c *request.Context, reaction *model.Reaction pluginContext := pluginContext(c) a.Srv().Go(func() { - a.ch.RunMultiHook(func(hooks plugin.Hooks) bool { + a.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { hooks.ReactionHasBeenRemoved(pluginContext, reaction) return true }, plugin.ReactionHasBeenRemovedID) diff --git a/app/server.go b/app/server.go index fbd433a6db..22f9d24c1c 100644 --- a/app/server.go +++ b/app/server.go @@ -119,6 +119,7 @@ type Server struct { telemetryService *telemetry.TelemetryService userService *users.UserService teamService *teams.TeamService + pluginService *PluginService serviceMux sync.RWMutex remoteClusterService remotecluster.RemoteClusterServiceIFace @@ -718,6 +719,10 @@ func (s *Server) Shutdown() { } } + // Stop the plugin service, we need to stop plugin service before stopping the + // product as products are being consumed by this service. + s.pluginService.ShutDownPlugins() + // Stop products. // This needs to happen last because products are dependent // on parent services. @@ -824,11 +829,18 @@ func stripPort(hostport string) string { func (s *Server) Start() error { // Start products. // This needs to happen before because products are dependent on the HTTP server. - // make sure channels starts first if err := s.products["channels"].Start(); err != nil { return errors.Wrap(err, "Unable to start channels") } + + // This should actually be started after products, but we have a product hooks + // dependency for now, once that get sorted out, this should be moved to the appropriate + // order. + if err := s.InitializePluginService(); err != nil { + return errors.Wrap(err, "Unable to start plugin service") + } + for name, product := range s.products { if name == "channels" { continue diff --git a/app/team.go b/app/team.go index 222d10e442..5724f683c2 100644 --- a/app/team.go +++ b/app/team.go @@ -853,7 +853,7 @@ func (a *App) JoinUserToTeam(c request.CTX, team *model.Team, user *model.User, a.Srv().Go(func() { pluginContext := pluginContext(c) - a.ch.RunMultiHook(func(hooks plugin.Hooks) bool { + a.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { hooks.UserHasJoinedTeam(pluginContext, teamMember, actor) return true }, plugin.UserHasJoinedTeamID) @@ -1225,7 +1225,7 @@ func (a *App) postProcessTeamMemberLeave(c request.CTX, teamMember *model.TeamMe a.Srv().Go(func() { pluginContext := pluginContext(c) - a.ch.RunMultiHook(func(hooks plugin.Hooks) bool { + a.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { hooks.UserHasLeftTeam(pluginContext, teamMember, actor) return true }, plugin.UserHasLeftTeamID) diff --git a/app/upload.go b/app/upload.go index 318e3ede89..b60f9be57b 100644 --- a/app/upload.go +++ b/app/upload.go @@ -62,7 +62,7 @@ func (a *App) runPluginsHook(c request.CTX, info *model.FileInfo, file io.Reader var rejErr *model.AppError var once sync.Once pluginContext := pluginContext(c) - a.ch.RunMultiHook(func(hooks plugin.Hooks) bool { + a.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { once.Do(func() { hookHasRunCh <- struct{}{} }) diff --git a/app/user.go b/app/user.go index 6e12bda34b..81faa5b57d 100644 --- a/app/user.go +++ b/app/user.go @@ -310,7 +310,7 @@ func (a *App) createUserOrGuest(c request.CTX, user *model.User, guest bool) (*m pluginContext := pluginContext(c) a.Srv().Go(func() { - a.ch.RunMultiHook(func(hooks plugin.Hooks) bool { + a.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { hooks.UserHasBeenCreated(pluginContext, ruser) return true }, plugin.UserHasBeenCreatedID) diff --git a/app/web_conn.go b/app/web_conn.go index cdf59eb31e..1db5866e0a 100644 --- a/app/web_conn.go +++ b/app/web_conn.go @@ -16,5 +16,5 @@ func (a *App) PopulateWebConnConfig(s *model.Session, cfg *platform.WebConnConfi // NewWebConn returns a new WebConn instance. func (a *App) NewWebConn(cfg *platform.WebConnConfig) *platform.WebConn { - return a.Srv().Platform().NewWebConn(cfg, a, a.ch) + return a.Srv().Platform().NewWebConn(cfg, a, a.Srv()) } diff --git a/cmd/mattermost/commands/init.go b/cmd/mattermost/commands/init.go index e93d9640fe..a90b4b02b3 100644 --- a/cmd/mattermost/commands/init.go +++ b/cmd/mattermost/commands/init.go @@ -7,7 +7,6 @@ import ( "github.com/spf13/cobra" "github.com/mattermost/mattermost-server/v6/app" - "github.com/mattermost/mattermost-server/v6/app/request" "github.com/mattermost/mattermost-server/v6/config" "github.com/mattermost/mattermost-server/v6/model" "github.com/mattermost/mattermost-server/v6/shared/i18n" @@ -21,7 +20,11 @@ func initDBCommandContextCobra(command *cobra.Command, readOnlyConfigStore bool) panic(err) } - a.InitPlugins(request.EmptyContext(a.Log()), *a.Config().PluginSettings.Directory, *a.Config().PluginSettings.ClientDirectory) + err = a.Srv().InitializePluginService() + if err != nil { + return nil, err + } + a.DoAppMigrations() return a, nil diff --git a/web/web_test.go b/web/web_test.go index 4214649031..b57e3888bd 100644 --- a/web/web_test.go +++ b/web/web_test.go @@ -282,7 +282,7 @@ func TestPublicFilesRequest(t *testing.T) { defer os.RemoveAll(pluginDir) defer os.RemoveAll(webappPluginDir) - env, err := plugin.NewEnvironment(th.NewPluginAPI, app.NewDriverImpl(th.Server), pluginDir, webappPluginDir, false, th.App.Log(), nil) + env, err := plugin.NewEnvironment(th.NewPluginAPI, app.NewDriverImpl(th.Server.Platform()), pluginDir, webappPluginDir, false, th.App.Log(), nil) require.NoError(t, err) pluginID := "com.mattermost.sample" @@ -329,7 +329,7 @@ func TestPublicFilesRequest(t *testing.T) { require.NotNil(t, manifest) require.True(t, activated) - th.App.Channels().SetPluginsEnvironment(env) + th.App.PluginService().SetPluginsEnvironment(env) req, _ := http.NewRequest("GET", "/plugins/com.mattermost.sample/public/hello.html", nil) res := httptest.NewRecorder() From 45159a6b09765e46668bd2124b04170dd080691c Mon Sep 17 00:00:00 2001 From: mattermod Date: Wed, 21 Dec 2022 15:28:13 +0000 Subject: [PATCH 13/37] Update latest version to 7.5.2 --- build/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/Dockerfile b/build/Dockerfile index d373e54fdc..aaa82b99ca 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -8,7 +8,7 @@ SHELL ["/bin/bash", "-o", "pipefail", "-c"] ENV PATH="/mattermost/bin:${PATH}" ARG PUID=2000 ARG PGID=2000 -ARG MM_PACKAGE="https://releases.mattermost.com/7.5.1/mattermost-7.5.1-linux-amd64.tar.gz?src=docker" +ARG MM_PACKAGE="https://releases.mattermost.com/7.5.2/mattermost-7.5.2-linux-amd64.tar.gz?src=docker" # # Install needed packages and indirect dependencies RUN apt-get update \ From c25270414086fc4ae1be89b09db0cd714a954962 Mon Sep 17 00:00:00 2001 From: Ibrahim Serdar Acikgoz Date: Wed, 21 Dec 2022 22:10:26 +0300 Subject: [PATCH 14/37] Revert "[MM-48626] Move plugins environment out of Channels (#21730)" (#21934) --- api4/plugin.go | 4 +- api4/plugin_test.go | 2 +- api4/websocket.go | 2 +- app/app_iface.go | 1 - app/channel.go | 10 +- app/channels.go | 74 +++++- app/cluster_handlers.go | 11 +- app/collection.go | 16 +- app/download.go | 6 +- app/file.go | 2 +- app/integration_action.go | 6 +- app/login.go | 4 +- app/onboarding.go | 9 +- app/opentracing/opentracing_layer.go | 17 -- app/plugin.go | 380 ++++++++++----------------- app/plugin_api.go | 4 +- app/plugin_api_test.go | 24 +- app/plugin_commands.go | 72 ++--- app/plugin_commands_test.go | 10 +- app/plugin_db_driver.go | 13 +- app/plugin_db_driver_test.go | 2 +- app/plugin_event.go | 6 +- app/plugin_hooks_test.go | 14 +- app/plugin_install.go | 118 ++++----- app/plugin_install_test.go | 8 +- app/plugin_requests.go | 28 +- app/plugin_requests_test.go | 2 +- app/plugin_shutdown_test.go | 2 +- app/plugin_signature.go | 8 +- app/plugin_statuses.go | 36 +-- app/plugin_test.go | 46 ++-- app/post.go | 8 +- app/reaction.go | 4 +- app/server.go | 14 +- app/team.go | 4 +- app/upload.go | 2 +- app/user.go | 2 +- app/web_conn.go | 2 +- cmd/mattermost/commands/init.go | 7 +- web/web_test.go | 4 +- 40 files changed, 447 insertions(+), 537 deletions(-) diff --git a/api4/plugin.go b/api4/plugin.go index 475aa62f21..be5b298d02 100644 --- a/api4/plugin.go +++ b/api4/plugin.go @@ -155,7 +155,7 @@ func installMarketplacePlugin(c *Context, w http.ResponseWriter, r *http.Request // https://mattermost.atlassian.net/browse/MM-41981 pluginRequest.Version = "" - manifest, appErr := c.App.PluginService().InstallMarketplacePlugin(pluginRequest) + manifest, appErr := c.App.Channels().InstallMarketplacePlugin(pluginRequest) if appErr != nil { c.Err = appErr return @@ -235,7 +235,7 @@ func removePlugin(c *Context, w http.ResponseWriter, r *http.Request) { return } - err := c.App.PluginService().RemovePlugin(c.Params.PluginId) + err := c.App.Channels().RemovePlugin(c.Params.PluginId) if err != nil { c.Err = err return diff --git a/api4/plugin_test.go b/api4/plugin_test.go index 3b656c209a..1967f9a617 100644 --- a/api4/plugin_test.go +++ b/api4/plugin_test.go @@ -94,7 +94,7 @@ func TestPlugin(t *testing.T) { assert.Equal(t, "testplugin", manifest.Id) }) - th.App.PluginService().RemovePlugin(manifest.Id) + th.App.Channels().RemovePlugin(manifest.Id) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.Enable = false }) diff --git a/api4/websocket.go b/api4/websocket.go index d8236e49b5..d2c1c10f44 100644 --- a/api4/websocket.go +++ b/api4/websocket.go @@ -61,7 +61,7 @@ func connectWebSocket(c *Context, w http.ResponseWriter, r *http.Request) { } } - wc := c.App.Srv().Platform().NewWebConn(cfg, c.App, c.App.Srv()) + wc := c.App.Srv().Platform().NewWebConn(cfg, c.App, c.App.Srv().Channels()) if c.AppContext.Session().UserId != "" { c.App.Srv().Platform().HubRegister(wc) } diff --git a/app/app_iface.go b/app/app_iface.go index 2674801101..f10ca2d8b9 100644 --- a/app/app_iface.go +++ b/app/app_iface.go @@ -936,7 +936,6 @@ type AppIface interface { PermanentDeleteTeamId(c request.CTX, teamID string) *model.AppError PermanentDeleteUser(c *request.Context, user *model.User) *model.AppError PluginCommandsForTeam(teamID string) []*model.Command - PluginService() *PluginService PostActionCookieSecret() []byte PostAddToChannelMessage(c request.CTX, user *model.User, addedUser *model.User, channel *model.Channel, postRootId string) *model.AppError PostPatchWithProxyRemovedFromImageURLs(patch *model.PostPatch) *model.PostPatch diff --git a/app/channel.go b/app/channel.go index 31a8673383..fdc8d17da3 100644 --- a/app/channel.go +++ b/app/channel.go @@ -345,7 +345,7 @@ func (a *App) CreateChannel(c request.CTX, channel *model.Channel, addMember boo a.Srv().Go(func() { pluginContext := pluginContext(c) - a.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { + a.ch.RunMultiHook(func(hooks plugin.Hooks) bool { hooks.ChannelHasBeenCreated(pluginContext, sc) return true }, plugin.ChannelHasBeenCreatedID) @@ -429,7 +429,7 @@ func (a *App) handleCreationEvent(c request.CTX, userID, otherUserID string, cha a.Srv().Go(func() { pluginContext := pluginContext(c) - a.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { + a.ch.RunMultiHook(func(hooks plugin.Hooks) bool { hooks.ChannelHasBeenCreated(pluginContext, channel) return true }, plugin.ChannelHasBeenCreatedID) @@ -1597,7 +1597,7 @@ func (a *App) AddChannelMember(c request.CTX, userID string, channel *model.Chan a.Srv().Go(func() { pluginContext := pluginContext(c) - a.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { + a.ch.RunMultiHook(func(hooks plugin.Hooks) bool { hooks.UserHasJoinedChannel(pluginContext, cm, userRequestor) return true }, plugin.UserHasJoinedChannelID) @@ -2173,7 +2173,7 @@ func (a *App) JoinChannel(c request.CTX, channel *model.Channel, userID string) a.Srv().Go(func() { pluginContext := pluginContext(c) - a.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { + a.ch.RunMultiHook(func(hooks plugin.Hooks) bool { hooks.UserHasJoinedChannel(pluginContext, cm, nil) return true }, plugin.UserHasJoinedChannelID) @@ -2483,7 +2483,7 @@ func (a *App) removeUserFromChannel(c request.CTX, userIDToRemove string, remove a.Srv().Go(func() { pluginContext := pluginContext(c) - a.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { + a.ch.RunMultiHook(func(hooks plugin.Hooks) bool { hooks.UserHasLeftChannel(pluginContext, cm, actorUser) return true }, plugin.UserHasLeftChannelID) diff --git a/app/channels.go b/app/channels.go index c9771f25d6..7ab024e45a 100644 --- a/app/channels.go +++ b/app/channels.go @@ -6,11 +6,14 @@ package app import ( "fmt" "runtime" + "strings" "sync" "github.com/pkg/errors" "github.com/mattermost/mattermost-server/v6/app/imaging" + "github.com/mattermost/mattermost-server/v6/app/request" + "github.com/mattermost/mattermost-server/v6/config" "github.com/mattermost/mattermost-server/v6/einterfaces" "github.com/mattermost/mattermost-server/v6/model" "github.com/mattermost/mattermost-server/v6/plugin" @@ -39,6 +42,12 @@ type Channels struct { postActionCookieSecret []byte + pluginCommandsLock sync.RWMutex + pluginCommands []*PluginCommand + pluginsLock sync.RWMutex + pluginsEnvironment *plugin.Environment + pluginConfigListenerID string + imageProxy *imageproxy.ImageProxy // cached counts that are used during notice condition validation @@ -70,6 +79,12 @@ type Channels struct { postReminderMut sync.Mutex postReminderTask *model.ScheduledTask + + // collectionTypes maps from collection types to the registering plugin id + collectionTypes map[string]string + // topicTypes maps from topic types to collection types + topicTypes map[string]string + collectionAndTopicTypesMut sync.Mutex } func init() { @@ -92,9 +107,11 @@ func NewChannels(services map[product.ServiceKey]any) (*Channels, error) { return nil, errors.New("server not passed") } ch := &Channels{ - srv: s, - imageProxy: imageproxy.MakeImageProxy(s.platform, s.httpService, s.Log()), - uploadLockMap: map[string]bool{}, + srv: s, + imageProxy: imageproxy.MakeImageProxy(s.platform, s.httpService, s.Log()), + uploadLockMap: map[string]bool{}, + collectionTypes: map[string]string{}, + topicTypes: map[string]string{}, } // To get another service: @@ -191,6 +208,10 @@ func NewChannels(services map[product.ServiceKey]any) (*Channels, error) { services[product.RouterKey] = ch.routerSvc // Setup routes. + pluginsRoute := ch.srv.Router.PathPrefix("/plugins/{plugin_id:[A-Za-z0-9\\_\\-\\.]+}").Subrouter() + pluginsRoute.HandleFunc("", ch.ServePluginRequest) + pluginsRoute.HandleFunc("/public/{public_file:.*}", ch.ServePluginPublicRequest) + pluginsRoute.HandleFunc("/{anything:.*}", ch.ServePluginRequest) services[product.PostKey] = &postServiceWrapper{ app: &App{ch: ch}, @@ -222,6 +243,39 @@ func NewChannels(services map[product.ServiceKey]any) (*Channels, error) { } func (ch *Channels) Start() error { + // Start plugins + ctx := request.EmptyContext(ch.srv.Log()) + ch.initPlugins(ctx, *ch.cfgSvc.Config().PluginSettings.Directory, *ch.cfgSvc.Config().PluginSettings.ClientDirectory) + + ch.AddConfigListener(func(prevCfg, cfg *model.Config) { + // We compute the difference between configs + // to ensure we don't re-init plugins unnecessarily. + diffs, err := config.Diff(prevCfg, cfg) + if err != nil { + ch.srv.Log().Warn("Error in comparing configs", mlog.Err(err)) + return + } + + hasDiff := false + // TODO: This could be a method on ConfigDiffs itself + for _, diff := range diffs { + if strings.HasPrefix(diff.Path, "PluginSettings.") { + hasDiff = true + break + } + } + + // Do only if some plugin related settings has changed. + if hasDiff { + if *cfg.PluginSettings.Enable { + ch.initPlugins(ctx, *cfg.PluginSettings.Directory, *ch.cfgSvc.Config().PluginSettings.ClientDirectory) + } else { + ch.ShutDownPlugins() + } + } + + }) + // TODO: This should be moved to the platform service. if err := ch.srv.platform.EnsureAsymmetricSigningKey(); err != nil { return errors.Wrapf(err, "unable to ensure asymmetric signing key") @@ -235,6 +289,8 @@ func (ch *Channels) Start() error { } func (ch *Channels) Stop() error { + ch.ShutDownPlugins() + ch.dndTaskMut.Lock() if ch.dndTask != nil { ch.dndTask.Cancel() @@ -276,18 +332,18 @@ func (s *hooksService) RegisterHooks(productID string, hooks any) error { return s.ch.srv.hooksManager.AddProduct(productID, hooks) } -func (s *Server) RunMultiHook(hookRunnerFunc func(hooks plugin.Hooks) bool, hookId int) { - if env := s.pluginService.GetPluginsEnvironment(); env != nil { +func (ch *Channels) RunMultiHook(hookRunnerFunc func(hooks plugin.Hooks) bool, hookId int) { + if env := ch.GetPluginsEnvironment(); env != nil { env.RunMultiPluginHook(hookRunnerFunc, hookId) } // run hook for the products - s.hooksManager.RunMultiHook(hookRunnerFunc, hookId) + ch.srv.hooksManager.RunMultiHook(hookRunnerFunc, hookId) } -func (s *Server) HooksForPluginOrProduct(id string) (plugin.Hooks, error) { +func (ch *Channels) HooksForPluginOrProduct(id string) (plugin.Hooks, error) { var hooks plugin.Hooks - if env := s.pluginService.GetPluginsEnvironment(); env != nil { + if env := ch.GetPluginsEnvironment(); env != nil { // we intentionally ignore the error here, because the id can be a product id // we are going to check if we have the hooks or not hooks, _ = env.HooksForPlugin(id) @@ -296,7 +352,7 @@ func (s *Server) HooksForPluginOrProduct(id string) (plugin.Hooks, error) { } } - hooks = s.hooksManager.HooksForProduct(id) + hooks = ch.srv.hooksManager.HooksForProduct(id) if hooks != nil { return hooks, nil } diff --git a/app/cluster_handlers.go b/app/cluster_handlers.go index 1cebe5596d..3fa90abf1e 100644 --- a/app/cluster_handlers.go +++ b/app/cluster_handlers.go @@ -16,7 +16,7 @@ func (s *Server) clusterInstallPluginHandler(msg *model.ClusterMessage) { if jsonErr := json.Unmarshal(msg.Data, &data); jsonErr != nil { mlog.Warn("Failed to decode from JSON", mlog.Err(jsonErr)) } - s.pluginService.installPluginFromData(data) + s.Channels().installPluginFromData(data) } func (s *Server) clusterRemovePluginHandler(msg *model.ClusterMessage) { @@ -24,7 +24,7 @@ func (s *Server) clusterRemovePluginHandler(msg *model.ClusterMessage) { if jsonErr := json.Unmarshal(msg.Data, &data); jsonErr != nil { mlog.Warn("Failed to decode from JSON", mlog.Err(jsonErr)) } - s.pluginService.removePluginFromData(data) + s.Channels().removePluginFromData(data) } func (s *Server) clusterPluginEventHandler(msg *model.ClusterMessage) { @@ -44,7 +44,12 @@ func (s *Server) clusterPluginEventHandler(msg *model.ClusterMessage) { return } - hooks, err := s.HooksForPluginOrProduct(pluginID) + channels, ok := s.products["channels"].(*Channels) + if !ok { + return + } + + hooks, err := channels.HooksForPluginOrProduct(pluginID) if err != nil { mlog.Warn("Getting hooks for plugin failed", mlog.String("plugin_id", pluginID), mlog.Err(err)) return diff --git a/app/collection.go b/app/collection.go index ff489a18db..9b895e3bc0 100644 --- a/app/collection.go +++ b/app/collection.go @@ -10,26 +10,26 @@ import ( "github.com/mattermost/mattermost-server/v6/shared/mlog" ) -func (s *PluginService) registerCollectionAndTopic(pluginID, collectionType, topicType string) error { +func (a *App) registerCollectionAndTopic(pluginID, collectionType, topicType string) error { // we have a race condition due to multiple plugins calling this method - s.collectionAndTopicTypesMut.Lock() - defer s.collectionAndTopicTypesMut.Unlock() + a.ch.collectionAndTopicTypesMut.Lock() + defer a.ch.collectionAndTopicTypesMut.Unlock() // check if collectionType was already registered by other plugin - existingPluginID, ok := s.collectionTypes[collectionType] + existingPluginID, ok := a.ch.collectionTypes[collectionType] if ok && existingPluginID != pluginID { return model.NewAppError("registerCollectionAndTopic", "app.collection.add_collection.exists.app_error", nil, "", http.StatusBadRequest) } // check if topicType was already registered to other collection - existingCollectionType, ok := s.topicTypes[topicType] + existingCollectionType, ok := a.ch.topicTypes[topicType] if ok && existingCollectionType != collectionType { return model.NewAppError("registerCollectionAndTopic", "app.collection.add_topic.exists.app_error", nil, "", http.StatusBadRequest) } - s.collectionTypes[collectionType] = pluginID - s.topicTypes[topicType] = collectionType + a.ch.collectionTypes[collectionType] = pluginID + a.ch.topicTypes[topicType] = collectionType - s.platform.Log().Info("registered collection and topic type", mlog.String("plugin_id", pluginID), mlog.String("collection_type", collectionType), mlog.String("topic_type", topicType)) + a.ch.srv.Log().Info("registered collection and topic type", mlog.String("plugin_id", pluginID), mlog.String("collection_type", collectionType), mlog.String("topic_type", topicType)) return nil } diff --git a/app/download.go b/app/download.go index 56438507cc..449f787c46 100644 --- a/app/download.go +++ b/app/download.go @@ -22,10 +22,10 @@ const ( ) func (a *App) DownloadFromURL(downloadURL string) ([]byte, error) { - return a.Srv().pluginService.downloadFromURL(downloadURL) + return a.Srv().downloadFromURL(downloadURL) } -func (s *PluginService) downloadFromURL(downloadURL string) ([]byte, error) { +func (s *Server) downloadFromURL(downloadURL string) ([]byte, error) { if !model.IsValidHTTPURL(downloadURL) { return nil, errors.Errorf("invalid url %s", downloadURL) } @@ -38,7 +38,7 @@ func (s *PluginService) downloadFromURL(downloadURL string) ([]byte, error) { return nil, errors.Errorf("insecure url not allowed %s", downloadURL) } - client := s.httpService.MakeClient(true) + client := s.HTTPService().MakeClient(true) client.Timeout = HTTPRequestTimeout var resp *http.Response diff --git a/app/file.go b/app/file.go index e1acafa827..7a66d25010 100644 --- a/app/file.go +++ b/app/file.go @@ -926,7 +926,7 @@ func (a *App) DoUploadFileExpectModification(c request.CTX, now time.Time, rawTe var rejectionError *model.AppError pluginContext := pluginContext(c) - a.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { + a.ch.RunMultiHook(func(hooks plugin.Hooks) bool { var newBytes bytes.Buffer replacementInfo, rejectionReason := hooks.FileWillBeUploaded(pluginContext, info, bytes.NewReader(data), &newBytes) if rejectionReason != "" { diff --git a/app/integration_action.go b/app/integration_action.go index 51bef2b86f..4ae7e97e07 100644 --- a/app/integration_action.go +++ b/app/integration_action.go @@ -375,10 +375,10 @@ func (w *LocalResponseWriter) WriteHeader(statusCode int) { } func (a *App) doPluginRequest(c *request.Context, method, rawURL string, values url.Values, body []byte) (*http.Response, *model.AppError) { - return a.ch.srv.pluginService.doPluginRequest(c, method, rawURL, values, body) + return a.ch.doPluginRequest(c, method, rawURL, values, body) } -func (s *PluginService) doPluginRequest(c *request.Context, method, rawURL string, values url.Values, body []byte) (*http.Response, *model.AppError) { +func (ch *Channels) doPluginRequest(c *request.Context, method, rawURL string, values url.Values, body []byte) (*http.Response, *model.AppError) { rawURL = strings.TrimPrefix(rawURL, "/") inURL, err := url.Parse(rawURL) if err != nil { @@ -427,7 +427,7 @@ func (s *PluginService) doPluginRequest(c *request.Context, method, rawURL strin params["plugin_id"] = pluginID r = mux.SetURLVars(r, params) - s.ServePluginRequest(w, r) + ch.ServePluginRequest(w, r) resp := &http.Response{ StatusCode: w.status, diff --git a/app/login.go b/app/login.go index af322bd13b..e0854bef37 100644 --- a/app/login.go +++ b/app/login.go @@ -159,7 +159,7 @@ func (a *App) GetUserForLogin(id, loginId string) (*model.User, *model.AppError) func (a *App) DoLogin(c *request.Context, w http.ResponseWriter, r *http.Request, user *model.User, deviceID string, isMobile, isOAuthUser, isSaml bool) *model.AppError { var rejectionReason string pluginContext := pluginContext(c) - a.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { + a.ch.RunMultiHook(func(hooks plugin.Hooks) bool { rejectionReason = hooks.UserWillLogIn(pluginContext, user) return rejectionReason == "" }, plugin.UserWillLogInID) @@ -225,7 +225,7 @@ func (a *App) DoLogin(c *request.Context, w http.ResponseWriter, r *http.Request } a.Srv().Go(func() { - a.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { + a.ch.RunMultiHook(func(hooks plugin.Hooks) bool { hooks.UserHasLoggedIn(pluginContext, user) return true }, plugin.UserHasLoggedInID) diff --git a/app/onboarding.go b/app/onboarding.go index fe40e9d997..d76525f017 100644 --- a/app/onboarding.go +++ b/app/onboarding.go @@ -28,6 +28,11 @@ func (a *App) markAdminOnboardingComplete(c *request.Context) *model.AppError { } func (a *App) CompleteOnboarding(c *request.Context, request *model.CompleteOnboardingRequest) *model.AppError { + pluginsEnvironment := a.Channels().GetPluginsEnvironment() + if pluginsEnvironment == nil { + return a.markAdminOnboardingComplete(c) + } + pluginContext := pluginContext(c) for _, pluginID := range request.InstallPlugins { @@ -36,7 +41,7 @@ func (a *App) CompleteOnboarding(c *request.Context, request *model.CompleteOnbo installRequest := &model.InstallMarketplacePluginRequest{ Id: id, } - _, appErr := a.Srv().pluginService.InstallMarketplacePlugin(installRequest) + _, appErr := a.Channels().InstallMarketplacePlugin(installRequest) if appErr != nil { mlog.Error("Failed to install plugin for onboarding", mlog.String("id", id), mlog.Err(appErr)) return @@ -48,7 +53,7 @@ func (a *App) CompleteOnboarding(c *request.Context, request *model.CompleteOnbo return } - hooks, err := a.Srv().HooksForPluginOrProduct(id) + hooks, err := a.ch.HooksForPluginOrProduct(id) if err != nil { mlog.Warn("Getting hooks for plugin failed", mlog.String("plugin_id", id), mlog.Err(err)) return diff --git a/app/opentracing/opentracing_layer.go b/app/opentracing/opentracing_layer.go index 3d6c2b5ad5..367d98ec39 100644 --- a/app/opentracing/opentracing_layer.go +++ b/app/opentracing/opentracing_layer.go @@ -13125,23 +13125,6 @@ func (a *OpenTracingAppLayer) PluginCommandsForTeam(teamID string) []*model.Comm return resultVar0 } -func (a *OpenTracingAppLayer) PluginService() *app.PluginService { - origCtx := a.ctx - span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.PluginService") - - a.ctx = newCtx - a.app.Srv().Store().SetContext(newCtx) - defer func() { - a.app.Srv().Store().SetContext(origCtx) - a.ctx = origCtx - }() - - defer span.Finish() - resultVar0 := a.app.PluginService() - - return resultVar0 -} - func (a *OpenTracingAppLayer) PopulateWebConnConfig(s *model.Session, cfg *platform.WebConnConfig, seqVal string) (*platform.WebConnConfig, error) { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.PopulateWebConnConfig") diff --git a/app/plugin.go b/app/plugin.go index d985455e5b..679576e723 100644 --- a/app/plugin.go +++ b/app/plugin.go @@ -20,37 +20,16 @@ import ( svg "github.com/h2non/go-is-svg" "github.com/pkg/errors" - "github.com/mattermost/mattermost-server/v6/app/platform" "github.com/mattermost/mattermost-server/v6/app/request" - "github.com/mattermost/mattermost-server/v6/config" "github.com/mattermost/mattermost-server/v6/model" "github.com/mattermost/mattermost-server/v6/plugin" "github.com/mattermost/mattermost-server/v6/product" - "github.com/mattermost/mattermost-server/v6/services/httpservice" "github.com/mattermost/mattermost-server/v6/services/marketplace" "github.com/mattermost/mattermost-server/v6/shared/filestore" "github.com/mattermost/mattermost-server/v6/shared/mlog" "github.com/mattermost/mattermost-server/v6/utils/fileutils" ) -type PluginService struct { - platform *platform.PlatformService - channels *Channels - fileStore filestore.FileBackend - httpService httpservice.HTTPService - - pluginCommandsLock sync.RWMutex - pluginCommands []*PluginCommand - pluginsLock sync.RWMutex - pluginsEnvironment *plugin.Environment - pluginConfigListenerID string - // collectionTypes maps from collection types to the registering plugin id - collectionTypes map[string]string - // topicTypes maps from topic types to collection types - topicTypes map[string]string - collectionAndTopicTypesMut sync.Mutex -} - const prepackagedPluginsDir = "prepackaged_plugins" type pluginSignaturePath struct { @@ -84,91 +63,20 @@ func (rs *routerService) getHandler(productID string) (http.Handler, bool) { return handler, ok } -func (a *App) PluginService() *PluginService { - return a.ch.srv.pluginService -} - -func (s *Server) InitializePluginService() error { - product, ok := s.products["channels"] - if !ok { - return errors.New("unable to find channels product") - } - channels, ok := product.(*Channels) - if !ok { - return errors.New("unable to cast product to channels product") - } - - ps := &PluginService{ - platform: s.platform, - channels: channels, - fileStore: s.platform.FileBackend(), - httpService: s.httpService, - collectionTypes: make(map[string]string), - topicTypes: make(map[string]string), - } - s.pluginService = ps - - pluginsRoute := s.Router.PathPrefix("/plugins/{plugin_id:[A-Za-z0-9\\_\\-\\.]+}").Subrouter() - pluginsRoute.HandleFunc("", ps.ServePluginRequest) - pluginsRoute.HandleFunc("/public/{public_file:.*}", ps.ServePluginPublicRequest) - pluginsRoute.HandleFunc("/{anything:.*}", ps.ServePluginRequest) - - ps.initPlugins(request.EmptyContext(s.platform.Log()), *s.platform.Config().PluginSettings.Directory, *s.platform.Config().PluginSettings.ClientDirectory) - - // Start plugins - ctx := request.EmptyContext(s.platform.Log()) - - // Add the config listener to enable/disable plugins - s.platform.AddConfigListener(func(prevCfg, cfg *model.Config) { - // We compute the difference between configs - // to ensure we don't re-init plugins unnecessarily. - diffs, err := config.Diff(prevCfg, cfg) - if err != nil { - s.platform.Log().Warn("Error in comparing configs", mlog.Err(err)) - return - } - - hasDiff := false - // TODO: This could be a method on ConfigDiffs itself - for _, diff := range diffs { - if strings.HasPrefix(diff.Path, "PluginSettings.") { - hasDiff = true - break - } - } - - // Do only if some plugin related settings has changed. - if hasDiff { - if *cfg.PluginSettings.Enable { - s.pluginService.initPlugins(ctx, *cfg.PluginSettings.Directory, *s.Config().PluginSettings.ClientDirectory) - } else { - s.pluginService.ShutDownPlugins() - } - } - - }) - - return nil -} - -func (s *Server) GetPluginsEnvironment() *plugin.Environment { - return s.pluginService.GetPluginsEnvironment() -} - // GetPluginsEnvironment returns the plugin environment for use if plugins are enabled and // initialized. // // To get the plugins environment when the plugins are disabled, manually acquire the plugins // lock instead. -func (s *PluginService) GetPluginsEnvironment() *plugin.Environment { - if !*s.platform.Config().PluginSettings.Enable { +func (ch *Channels) GetPluginsEnvironment() *plugin.Environment { + if !*ch.cfgSvc.Config().PluginSettings.Enable { return nil } - s.pluginsLock.RLock() - defer s.pluginsLock.RUnlock() + ch.pluginsLock.RLock() + defer ch.pluginsLock.RUnlock() - return s.pluginsEnvironment + return ch.pluginsEnvironment } // GetPluginsEnvironment returns the plugin environment for use if plugins are enabled and @@ -177,39 +85,33 @@ func (s *PluginService) GetPluginsEnvironment() *plugin.Environment { // To get the plugins environment when the plugins are disabled, manually acquire the plugins // lock instead. func (a *App) GetPluginsEnvironment() *plugin.Environment { - // TODO: Telemetry service starts before products start, so we need to check if the plugin service is initialized. - // Move the telemetry service to start after products start. - if a.ch.srv.pluginService == nil { - return nil - } - - return a.ch.srv.pluginService.GetPluginsEnvironment() + return a.ch.GetPluginsEnvironment() } -func (s *PluginService) SetPluginsEnvironment(pluginsEnvironment *plugin.Environment) { - s.pluginsLock.Lock() - defer s.pluginsLock.Unlock() +func (ch *Channels) SetPluginsEnvironment(pluginsEnvironment *plugin.Environment) { + ch.pluginsLock.Lock() + defer ch.pluginsLock.Unlock() - s.pluginsEnvironment = pluginsEnvironment - s.platform.SetPluginsEnvironment(s.channels.srv) + ch.pluginsEnvironment = pluginsEnvironment + ch.srv.Platform().SetPluginsEnvironment(ch) } -func (s *PluginService) syncPluginsActiveState() { +func (ch *Channels) syncPluginsActiveState() { // Acquiring lock manually, as plugins might be disabled. See GetPluginsEnvironment. - s.pluginsLock.RLock() - pluginsEnvironment := s.pluginsEnvironment - s.pluginsLock.RUnlock() + ch.pluginsLock.RLock() + pluginsEnvironment := ch.pluginsEnvironment + ch.pluginsLock.RUnlock() if pluginsEnvironment == nil { return } - config := s.platform.Config().PluginSettings + config := ch.cfgSvc.Config().PluginSettings if *config.Enable { availablePlugins, err := pluginsEnvironment.Available() if err != nil { - s.platform.Log().Error("Unable to get available plugins", mlog.Err(err)) + ch.srv.Log().Error("Unable to get available plugins", mlog.Err(err)) return } @@ -223,24 +125,24 @@ func (s *PluginService) syncPluginsActiveState() { pluginEnabled = state.Enable } - if hasOverride, value := s.getPluginStateOverride(pluginID); hasOverride { + if hasOverride, value := ch.getPluginStateOverride(pluginID); hasOverride { pluginEnabled = value } if pluginEnabled { // Disable focalboard in product mode. - if pluginID == model.PluginIdFocalboard && s.platform.Config().FeatureFlags.BoardsProduct { + if pluginID == model.PluginIdFocalboard && ch.cfgSvc.Config().FeatureFlags.BoardsProduct { msg := "Plugin cannot run in product mode. Disabling." mlog.Warn(msg, mlog.String("plugin_id", model.PluginIdFocalboard)) // This is a mini-version of ch.disablePlugin. // We don't call that directly, because that will recursively call // this method. - s.platform.UpdateConfig(func(cfg *model.Config) { + ch.cfgSvc.UpdateConfig(func(cfg *model.Config) { cfg.PluginSettings.PluginStates[pluginID] = &model.PluginState{Enable: false} }) pluginsEnvironment.SetPluginError(pluginID, msg) - s.unregisterPluginCommands(pluginID) + ch.unregisterPluginCommands(pluginID) disabledPlugins = append(disabledPlugins, plugin) continue } @@ -264,7 +166,7 @@ func (s *PluginService) syncPluginsActiveState() { if deactivated && plugin.Manifest.HasClient() { message := model.NewWebSocketEvent(model.WebsocketEventPluginDisabled, "", "", "", nil, "") message.Add("manifest", plugin.Manifest.ClientManifest()) - s.platform.Publish(message) + ch.srv.platform.Publish(message) } }(plugin) } @@ -278,14 +180,14 @@ func (s *PluginService) syncPluginsActiveState() { pluginID := plugin.Manifest.Id updatedManifest, activated, err := pluginsEnvironment.Activate(pluginID) if err != nil { - plugin.WrapLogger(s.platform.Log().(*mlog.Logger)).Error("Unable to activate plugin", mlog.Err(err)) + plugin.WrapLogger(ch.srv.Log()).Error("Unable to activate plugin", mlog.Err(err)) return } if activated { // Notify all cluster clients if ready - if err := s.notifyPluginEnabled(updatedManifest); err != nil { - s.platform.Log().Error("Failed to notify cluster on plugin enable", mlog.Err(err)) + if err := ch.notifyPluginEnabled(updatedManifest); err != nil { + ch.srv.Log().Error("Failed to notify cluster on plugin enable", mlog.Err(err)) } } }(plugin) @@ -295,7 +197,7 @@ func (s *PluginService) syncPluginsActiveState() { pluginsEnvironment.Shutdown() } - if err := s.notifyPluginStatusesChanged(); err != nil { + if err := ch.notifyPluginStatusesChanged(); err != nil { mlog.Warn("failed to notify plugin status changed", mlog.Err(err)) } } @@ -305,29 +207,27 @@ func (a *App) NewPluginAPI(c *request.Context, manifest *model.Manifest) plugin. } func (a *App) InitPlugins(c *request.Context, pluginDir, webappPluginDir string) { - a.ch.srv.pluginService.initPlugins(c, pluginDir, webappPluginDir) + a.ch.initPlugins(c, pluginDir, webappPluginDir) } -func (s *PluginService) initPlugins(c *request.Context, pluginDir, webappPluginDir string) { +func (ch *Channels) initPlugins(c *request.Context, pluginDir, webappPluginDir string) { // Acquiring lock manually, as plugins might be disabled. See GetPluginsEnvironment. defer func() { - // platform service requires plugins environment to be initialized - // so that it can use it in cluster service initialization - s.platform.SetPluginsEnvironment(s.channels.srv) + ch.srv.Platform().SetPluginsEnvironment(ch) }() - s.pluginsLock.RLock() - pluginsEnvironment := s.pluginsEnvironment - s.pluginsLock.RUnlock() - if pluginsEnvironment != nil || !*s.platform.Config().PluginSettings.Enable { - s.syncPluginsActiveState() + ch.pluginsLock.RLock() + pluginsEnvironment := ch.pluginsEnvironment + ch.pluginsLock.RUnlock() + if pluginsEnvironment != nil || !*ch.cfgSvc.Config().PluginSettings.Enable { + ch.syncPluginsActiveState() if pluginsEnvironment != nil { - pluginsEnvironment.TogglePluginHealthCheckJob(*s.platform.Config().PluginSettings.EnableHealthCheck) + pluginsEnvironment.TogglePluginHealthCheckJob(*ch.cfgSvc.Config().PluginSettings.EnableHealthCheck) } return } - s.platform.Log().Info("Starting up plugins") + ch.srv.Log().Info("Starting up plugins") if err := os.Mkdir(pluginDir, 0744); err != nil && !os.IsExist(err) { mlog.Error("Failed to start up plugins", mlog.Err(err)) @@ -340,77 +240,77 @@ func (s *PluginService) initPlugins(c *request.Context, pluginDir, webappPluginD } newAPIFunc := func(manifest *model.Manifest) plugin.API { - return New(ServerConnector(s.channels)).NewPluginAPI(c, manifest) + return New(ServerConnector(ch)).NewPluginAPI(c, manifest) } env, err := plugin.NewEnvironment( newAPIFunc, - NewDriverImpl(s.platform), + NewDriverImpl(ch.srv), pluginDir, webappPluginDir, - *s.platform.Config().ExperimentalSettings.PatchPluginsReactDOM, - s.platform.Logger(), - s.platform.Metrics(), + *ch.cfgSvc.Config().ExperimentalSettings.PatchPluginsReactDOM, + ch.srv.Log(), + ch.srv.GetMetrics(), ) if err != nil { mlog.Error("Failed to start up plugins", mlog.Err(err)) return } - s.pluginsLock.Lock() - s.pluginsEnvironment = env - s.pluginsLock.Unlock() + ch.pluginsLock.Lock() + ch.pluginsEnvironment = env + ch.pluginsLock.Unlock() - s.pluginsEnvironment.TogglePluginHealthCheckJob(*s.platform.Config().PluginSettings.EnableHealthCheck) + ch.pluginsEnvironment.TogglePluginHealthCheckJob(*ch.cfgSvc.Config().PluginSettings.EnableHealthCheck) - if err := s.syncPlugins(); err != nil { + if err := ch.syncPlugins(); err != nil { mlog.Error("Failed to sync plugins from the file store", mlog.Err(err)) } - plugins := s.processPrepackagedPlugins(prepackagedPluginsDir) - pluginsEnvironment = s.GetPluginsEnvironment() + plugins := ch.processPrepackagedPlugins(prepackagedPluginsDir) + pluginsEnvironment = ch.GetPluginsEnvironment() if pluginsEnvironment == nil { mlog.Info("Plugins environment not found, server is likely shutting down") return } pluginsEnvironment.SetPrepackagedPlugins(plugins) - s.installFeatureFlagPlugins() + ch.installFeatureFlagPlugins() // Sync plugin active state when config changes. Also notify plugins. - s.pluginsLock.Lock() - s.platform.RemoveConfigListener(s.pluginConfigListenerID) - s.pluginConfigListenerID = s.platform.AddConfigListener(func(old, new *model.Config) { + ch.pluginsLock.Lock() + ch.RemoveConfigListener(ch.pluginConfigListenerID) + ch.pluginConfigListenerID = ch.AddConfigListener(func(old, new *model.Config) { // If plugin status remains unchanged, only then run this. // Because (*App).InitPlugins is already run as a config change hook. if *old.PluginSettings.Enable == *new.PluginSettings.Enable { - s.installFeatureFlagPlugins() - s.syncPluginsActiveState() + ch.installFeatureFlagPlugins() + ch.syncPluginsActiveState() } - s.pluginsEnvironment.RunMultiPluginHook(func(hooks plugin.Hooks) bool { + ch.RunMultiHook(func(hooks plugin.Hooks) bool { if err := hooks.OnConfigurationChange(); err != nil { - s.platform.Log().Error("Plugin OnConfigurationChange hook failed", mlog.Err(err)) + ch.srv.Log().Error("Plugin OnConfigurationChange hook failed", mlog.Err(err)) } return true }, plugin.OnConfigurationChangeID) }) - s.pluginsLock.Unlock() + ch.pluginsLock.Unlock() - s.syncPluginsActiveState() + ch.syncPluginsActiveState() } // SyncPlugins synchronizes the plugins installed locally // with the plugin bundles available in the file store. func (a *App) SyncPlugins() *model.AppError { - return a.ch.srv.pluginService.syncPlugins() + return a.ch.syncPlugins() } // SyncPlugins synchronizes the plugins installed locally // with the plugin bundles available in the file store. -func (s *PluginService) syncPlugins() *model.AppError { +func (ch *Channels) syncPlugins() *model.AppError { mlog.Info("Syncing plugins from the file store") - pluginsEnvironment := s.GetPluginsEnvironment() + pluginsEnvironment := ch.GetPluginsEnvironment() if pluginsEnvironment == nil { return model.NewAppError("SyncPlugins", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented) } @@ -426,14 +326,14 @@ func (s *PluginService) syncPlugins() *model.AppError { go func(pluginID string) { defer wg.Done() // Only handle managed plugins with .filestore flag file. - _, err := os.Stat(filepath.Join(*s.platform.Config().PluginSettings.Directory, pluginID, managedPluginFileName)) + _, err := os.Stat(filepath.Join(*ch.cfgSvc.Config().PluginSettings.Directory, pluginID, managedPluginFileName)) if os.IsNotExist(err) { mlog.Warn("Skipping sync for unmanaged plugin", mlog.String("plugin_id", pluginID)) } else if err != nil { mlog.Error("Skipping sync for plugin after failure to check if managed", mlog.String("plugin_id", pluginID), mlog.Err(err)) } else { mlog.Debug("Removing local installation of managed plugin before sync", mlog.String("plugin_id", pluginID)) - if err := s.removePluginLocally(pluginID); err != nil { + if err := ch.removePluginLocally(pluginID); err != nil { mlog.Error("Failed to remove local installation of managed plugin before sync", mlog.String("plugin_id", pluginID), mlog.Err(err)) } } @@ -442,7 +342,7 @@ func (s *PluginService) syncPlugins() *model.AppError { wg.Wait() // Install plugins from the file store. - pluginSignaturePathMap, appErr := s.getPluginsFromFolder() + pluginSignaturePathMap, appErr := ch.getPluginsFromFolder() if appErr != nil { return appErr } @@ -451,7 +351,7 @@ func (s *PluginService) syncPlugins() *model.AppError { wg.Add(1) go func(plugin *pluginSignaturePath) { defer wg.Done() - reader, appErr := s.fileStore.Reader(plugin.path) + reader, appErr := ch.srv.fileReader(plugin.path) if appErr != nil { mlog.Error("Failed to open plugin bundle from file store.", mlog.String("bundle", plugin.path), mlog.Err(appErr)) return @@ -459,8 +359,8 @@ func (s *PluginService) syncPlugins() *model.AppError { defer reader.Close() var signature filestore.ReadCloseSeeker - if *s.platform.Config().PluginSettings.RequirePluginSignature { - signature, appErr = s.fileStore.Reader(plugin.signaturePath) + if *ch.cfgSvc.Config().PluginSettings.RequirePluginSignature { + signature, appErr = ch.srv.fileReader(plugin.signaturePath) if appErr != nil { mlog.Error("Failed to open plugin signature from file store.", mlog.Err(appErr)) return @@ -469,7 +369,7 @@ func (s *PluginService) syncPlugins() *model.AppError { } mlog.Info("Syncing plugin from file store", mlog.String("bundle", plugin.path)) - if _, err := s.installPluginLocally(reader, signature, installPluginLocallyAlways); err != nil { + if _, err := ch.installPluginLocally(reader, signature, installPluginLocallyAlways); err != nil { mlog.Error("Failed to sync plugin from file store", mlog.String("bundle", plugin.path), mlog.Err(err)) } }(plugin) @@ -479,11 +379,11 @@ func (s *PluginService) syncPlugins() *model.AppError { return nil } -func (s *PluginService) ShutDownPlugins() { +func (ch *Channels) ShutDownPlugins() { // Acquiring lock manually, as plugins might be disabled. See GetPluginsEnvironment. - s.pluginsLock.RLock() - pluginsEnvironment := s.pluginsEnvironment - s.pluginsLock.RUnlock() + ch.pluginsLock.RLock() + pluginsEnvironment := ch.pluginsEnvironment + ch.pluginsLock.RUnlock() if pluginsEnvironment == nil { return } @@ -492,14 +392,14 @@ func (s *PluginService) ShutDownPlugins() { pluginsEnvironment.Shutdown() - s.platform.RemoveConfigListener(s.pluginConfigListenerID) - s.pluginConfigListenerID = "" + ch.RemoveConfigListener(ch.pluginConfigListenerID) + ch.pluginConfigListenerID = "" // Acquiring lock manually before cleaning up PluginsEnvironment. - s.pluginsLock.Lock() - defer s.pluginsLock.Unlock() - if s.pluginsEnvironment == pluginsEnvironment { - s.pluginsEnvironment = nil + ch.pluginsLock.Lock() + defer ch.pluginsLock.Unlock() + if ch.pluginsEnvironment == pluginsEnvironment { + ch.pluginsEnvironment = nil } else { mlog.Warn("Another PluginsEnvironment detected while shutting down plugins.") } @@ -525,11 +425,11 @@ func (a *App) GetActivePluginManifests() ([]*model.Manifest, *model.AppError) { // activation if inactive anywhere in the cluster. // Notifies cluster peers through config change. func (a *App) EnablePlugin(id string) *model.AppError { - return a.PluginService().enablePlugin(id) + return a.ch.enablePlugin(id) } -func (s *PluginService) enablePlugin(id string) *model.AppError { - pluginsEnvironment := s.GetPluginsEnvironment() +func (ch *Channels) enablePlugin(id string) *model.AppError { + pluginsEnvironment := ch.GetPluginsEnvironment() if pluginsEnvironment == nil { return model.NewAppError("EnablePlugin", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented) } @@ -553,16 +453,16 @@ func (s *PluginService) enablePlugin(id string) *model.AppError { return model.NewAppError("EnablePlugin", "app.plugin.not_installed.app_error", nil, "", http.StatusNotFound) } - if id == model.PluginIdFocalboard && s.platform.Config().FeatureFlags.BoardsProduct { + if id == model.PluginIdFocalboard && ch.cfgSvc.Config().FeatureFlags.BoardsProduct { return model.NewAppError("EnablePlugin", "app.plugin.product_mode.app_error", map[string]any{"Name": model.PluginIdFocalboard}, "", http.StatusBadRequest) } - s.platform.UpdateConfig(func(cfg *model.Config) { + ch.cfgSvc.UpdateConfig(func(cfg *model.Config) { cfg.PluginSettings.PluginStates[id] = &model.PluginState{Enable: true} }) // This call will implicitly invoke SyncPluginsActiveState which will activate enabled plugins. - if _, _, err := s.platform.SaveConfig(s.platform.Config(), true); err != nil { + if _, _, err := ch.cfgSvc.SaveConfig(ch.cfgSvc.Config(), true); err != nil { if err.Id == "ent.cluster.save_config.error" { return model.NewAppError("EnablePlugin", "app.plugin.cluster.save_config.app_error", nil, "", http.StatusInternalServerError) } @@ -575,7 +475,7 @@ func (s *PluginService) enablePlugin(id string) *model.AppError { // DisablePlugin will set the config for an installed plugin to disabled, triggering deactivation if active. // Notifies cluster peers through config change. func (a *App) DisablePlugin(id string) *model.AppError { - appErr := a.ch.srv.pluginService.disablePlugin(id) + appErr := a.ch.disablePlugin(id) if appErr != nil { return appErr } @@ -583,22 +483,22 @@ func (a *App) DisablePlugin(id string) *model.AppError { return nil } -func (s *PluginService) disablePlugin(id string) *model.AppError { +func (ch *Channels) disablePlugin(id string) *model.AppError { // find all collectionTypes registered by plugin - for collectionTypeToRemove, existingPluginId := range s.collectionTypes { + for collectionTypeToRemove, existingPluginId := range ch.collectionTypes { if existingPluginId != id { continue } // find all topicTypes for existing collectionType - for topicTypeToRemove, existingCollectionType := range s.topicTypes { + for topicTypeToRemove, existingCollectionType := range ch.topicTypes { if existingCollectionType == collectionTypeToRemove { - delete(s.topicTypes, topicTypeToRemove) + delete(ch.topicTypes, topicTypeToRemove) } } - delete(s.collectionTypes, collectionTypeToRemove) + delete(ch.collectionTypes, collectionTypeToRemove) } - pluginsEnvironment := s.GetPluginsEnvironment() + pluginsEnvironment := ch.GetPluginsEnvironment() if pluginsEnvironment == nil { return model.NewAppError("DisablePlugin", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented) } @@ -622,13 +522,13 @@ func (s *PluginService) disablePlugin(id string) *model.AppError { return model.NewAppError("DisablePlugin", "app.plugin.not_installed.app_error", nil, "", http.StatusNotFound) } - s.platform.UpdateConfig(func(cfg *model.Config) { + ch.cfgSvc.UpdateConfig(func(cfg *model.Config) { cfg.PluginSettings.PluginStates[id] = &model.PluginState{Enable: false} }) - s.unregisterPluginCommands(id) + ch.unregisterPluginCommands(id) // This call will implicitly invoke SyncPluginsActiveState which will deactivate disabled plugins. - if _, _, err := s.platform.SaveConfig(s.platform.Config(), true); err != nil { + if _, _, err := ch.cfgSvc.SaveConfig(ch.cfgSvc.Config(), true); err != nil { return model.NewAppError("DisablePlugin", "app.plugin.config.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } @@ -715,8 +615,8 @@ func (a *App) GetMarketplacePlugins(filter *model.MarketplacePluginFilter) ([]*m // getPrepackagedPlugin returns a pre-packaged plugin. // // If version is empty, the first matching plugin is returned. -func (s *PluginService) getPrepackagedPlugin(pluginID, version string) (*plugin.PrepackagedPlugin, *model.AppError) { - pluginsEnvironment := s.GetPluginsEnvironment() +func (ch *Channels) getPrepackagedPlugin(pluginID, version string) (*plugin.PrepackagedPlugin, *model.AppError) { + pluginsEnvironment := ch.GetPluginsEnvironment() if pluginsEnvironment == nil { return nil, model.NewAppError("getPrepackagedPlugin", "app.plugin.config.app_error", nil, "plugin environment is nil", http.StatusInternalServerError) } @@ -734,16 +634,16 @@ func (s *PluginService) getPrepackagedPlugin(pluginID, version string) (*plugin. // getRemoteMarketplacePlugin returns plugin from marketplace-server. // // If version is empty, the latest compatible version is used. -func (s *PluginService) getRemoteMarketplacePlugin(pluginID, version string) (*model.BaseMarketplacePlugin, *model.AppError) { +func (ch *Channels) getRemoteMarketplacePlugin(pluginID, version string) (*model.BaseMarketplacePlugin, *model.AppError) { marketplaceClient, err := marketplace.NewClient( - *s.platform.Config().PluginSettings.MarketplaceURL, - s.httpService, + *ch.cfgSvc.Config().PluginSettings.MarketplaceURL, + ch.srv.HTTPService(), ) if err != nil { return nil, model.NewAppError("GetMarketplacePlugin", "app.plugin.marketplace_client.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } - filter := s.getBaseMarketplaceFilter() + filter := ch.getBaseMarketplaceFilter() filter.PluginId = pluginID var plugin *model.BaseMarketplacePlugin @@ -898,15 +798,15 @@ func (a *App) mergeLocalPlugins(remoteMarketplacePlugins map[string]*model.Marke } func (a *App) getBaseMarketplaceFilter() *model.MarketplacePluginFilter { - return a.ch.srv.pluginService.getBaseMarketplaceFilter() + return a.ch.getBaseMarketplaceFilter() } -func (s *PluginService) getBaseMarketplaceFilter() *model.MarketplacePluginFilter { +func (ch *Channels) getBaseMarketplaceFilter() *model.MarketplacePluginFilter { filter := &model.MarketplacePluginFilter{ ServerVersion: model.CurrentVersion, } - license := s.platform.License() + license := ch.srv.License() if license != nil && license.HasEnterpriseMarketplacePlugins() { filter.EnterprisePlugins = true } @@ -953,8 +853,8 @@ func pluginMatchesFilter(manifest *model.Manifest, filter string) bool { // it will notify all connected websocket clients (across all peers) to trigger the (re-)installation. // There is a small chance that this never occurs, because the last server to finish installing dies before it can announce. // There is also a chance that multiple servers notify, but the webapp handles this idempotently. -func (s *PluginService) notifyPluginEnabled(manifest *model.Manifest) error { - pluginsEnvironment := s.GetPluginsEnvironment() +func (ch *Channels) notifyPluginEnabled(manifest *model.Manifest) error { + pluginsEnvironment := ch.GetPluginsEnvironment() if pluginsEnvironment == nil { return errors.New("pluginsEnvironment is nil") } @@ -964,15 +864,15 @@ func (s *PluginService) notifyPluginEnabled(manifest *model.Manifest) error { var statuses model.PluginStatuses - if s.platform.Cluster() != nil { + if ch.srv.platform.Cluster() != nil { var err *model.AppError - statuses, err = s.platform.Cluster().GetPluginStatuses() + statuses, err = ch.srv.platform.Cluster().GetPluginStatuses() if err != nil { return err } } - localStatus, err := s.GetPluginStatus(manifest.Id) + localStatus, err := ch.GetPluginStatus(manifest.Id) if err != nil { return err } @@ -992,26 +892,26 @@ func (s *PluginService) notifyPluginEnabled(manifest *model.Manifest) error { // Notify all cluster peer clients. message := model.NewWebSocketEvent(model.WebsocketEventPluginEnabled, "", "", "", nil, "") message.Add("manifest", manifest.ClientManifest()) - s.platform.Publish(message) + ch.srv.platform.Publish(message) return nil } -func (s *PluginService) getPluginsFromFolder() (map[string]*pluginSignaturePath, *model.AppError) { - fileStorePaths, appErr := s.fileStore.ListDirectory(fileStorePluginFolder) +func (ch *Channels) getPluginsFromFolder() (map[string]*pluginSignaturePath, *model.AppError) { + fileStorePaths, appErr := ch.srv.listDirectory(fileStorePluginFolder, false) if appErr != nil { return nil, model.NewAppError("getPluginsFromDir", "app.plugin.sync.list_filestore.app_error", nil, "", http.StatusInternalServerError).Wrap(appErr) } - return s.getPluginsFromFilePaths(fileStorePaths), nil + return ch.getPluginsFromFilePaths(fileStorePaths), nil } -func (s *PluginService) getPluginsFromFilePaths(fileStorePaths []string) map[string]*pluginSignaturePath { +func (ch *Channels) getPluginsFromFilePaths(fileStorePaths []string) map[string]*pluginSignaturePath { pluginSignaturePathMap := make(map[string]*pluginSignaturePath) fsPrefix := "" - if *s.platform.Config().FileSettings.DriverName == model.ImageDriverS3 { - ptr := s.platform.Config().FileSettings.AmazonS3PathPrefix + if *ch.cfgSvc.Config().FileSettings.DriverName == model.ImageDriverS3 { + ptr := ch.cfgSvc.Config().FileSettings.AmazonS3PathPrefix if ptr != nil && *ptr != "" { fsPrefix = *ptr + "/" } @@ -1044,7 +944,7 @@ func (s *PluginService) getPluginsFromFilePaths(fileStorePaths []string) map[str return pluginSignaturePathMap } -func (s *PluginService) processPrepackagedPlugins(pluginsDir string) []*plugin.PrepackagedPlugin { +func (ch *Channels) processPrepackagedPlugins(pluginsDir string) []*plugin.PrepackagedPlugin { prepackagedPluginsDir, found := fileutils.FindDir(pluginsDir) if !found { return nil @@ -1060,7 +960,7 @@ func (s *PluginService) processPrepackagedPlugins(pluginsDir string) []*plugin.P return nil } - pluginSignaturePathMap := s.getPluginsFromFilePaths(fileStorePaths) + pluginSignaturePathMap := ch.getPluginsFromFilePaths(fileStorePaths) plugins := make([]*plugin.PrepackagedPlugin, 0, len(pluginSignaturePathMap)) prepackagedPlugins := make(chan *plugin.PrepackagedPlugin, len(pluginSignaturePathMap)) @@ -1069,7 +969,7 @@ func (s *PluginService) processPrepackagedPlugins(pluginsDir string) []*plugin.P wg.Add(1) go func(psPath *pluginSignaturePath) { defer wg.Done() - p, err := s.processPrepackagedPlugin(psPath) + p, err := ch.processPrepackagedPlugin(psPath) if err != nil { mlog.Error("Failed to install prepackaged plugin", mlog.String("path", psPath.path), mlog.Err(err)) return @@ -1090,7 +990,7 @@ func (s *PluginService) processPrepackagedPlugins(pluginsDir string) []*plugin.P // processPrepackagedPlugin will return the prepackaged plugin metadata and will also // install the prepackaged plugin if it had been previously enabled and AutomaticPrepackagedPlugins is true. -func (s *PluginService) processPrepackagedPlugin(pluginPath *pluginSignaturePath) (*plugin.PrepackagedPlugin, error) { +func (ch *Channels) processPrepackagedPlugin(pluginPath *pluginSignaturePath) (*plugin.PrepackagedPlugin, error) { mlog.Debug("Processing prepackaged plugin", mlog.String("path", pluginPath.path)) fileReader, err := os.Open(pluginPath.path) @@ -1111,18 +1011,18 @@ func (s *PluginService) processPrepackagedPlugin(pluginPath *pluginSignaturePath } // Skip installing the plugin at all if automatic prepackaged plugins is disabled - if !*s.platform.Config().PluginSettings.AutomaticPrepackagedPlugins { + if !*ch.cfgSvc.Config().PluginSettings.AutomaticPrepackagedPlugins { return plugin, nil } // Skip installing if the plugin is has not been previously enabled. - pluginState := s.platform.Config().PluginSettings.PluginStates[plugin.Manifest.Id] + pluginState := ch.cfgSvc.Config().PluginSettings.PluginStates[plugin.Manifest.Id] if pluginState == nil || !pluginState.Enable { return plugin, nil } mlog.Debug("Installing prepackaged plugin", mlog.String("path", pluginPath.path)) - if _, err := s.installExtractedPlugin(plugin.Manifest, pluginDir, installPluginLocallyOnlyIfNewOrUpgrade); err != nil { + if _, err := ch.installExtractedPlugin(plugin.Manifest, pluginDir, installPluginLocallyOnlyIfNewOrUpgrade); err != nil { return nil, errors.Wrapf(err, "Failed to install extracted prepackaged plugin %s", pluginPath.path) } @@ -1130,24 +1030,24 @@ func (s *PluginService) processPrepackagedPlugin(pluginPath *pluginSignaturePath } // installFeatureFlagPlugins handles the automatic installation/upgrade of plugins from feature flags -func (s *PluginService) installFeatureFlagPlugins() { - ffControledPlugins := s.platform.Config().FeatureFlags.Plugins() +func (ch *Channels) installFeatureFlagPlugins() { + ffControledPlugins := ch.cfgSvc.Config().FeatureFlags.Plugins() // Respect the automatic prepackaged disable setting - if !*s.platform.Config().PluginSettings.AutomaticPrepackagedPlugins { + if !*ch.cfgSvc.Config().PluginSettings.AutomaticPrepackagedPlugins { return } for pluginID, version := range ffControledPlugins { // Skip installing if the plugin has been previously disabled. - pluginState := s.platform.Config().PluginSettings.PluginStates[pluginID] + pluginState := ch.cfgSvc.Config().PluginSettings.PluginStates[pluginID] if pluginState != nil && !pluginState.Enable { - s.platform.Log().Debug("Not auto installing/upgrade because plugin was disabled", mlog.String("plugin_id", pluginID), mlog.String("version", version)) + ch.srv.Log().Debug("Not auto installing/upgrade because plugin was disabled", mlog.String("plugin_id", pluginID), mlog.String("version", version)) continue } // Check if we already installed this version as InstallMarketplacePlugin can't handle re-installs well. - pluginStatus, err := s.GetPluginStatus(pluginID) + pluginStatus, err := ch.GetPluginStatus(pluginID) pluginExists := err == nil if pluginExists && pluginStatus.Version == version { continue @@ -1155,37 +1055,37 @@ func (s *PluginService) installFeatureFlagPlugins() { if version != "" && version != "control" { // If we are on-prem skip installation if this is a downgrade - license := s.platform.License() + license := ch.srv.License() inCloud := license != nil && *license.Features.Cloud if !inCloud && pluginExists { parsedVersion, err := semver.Parse(version) if err != nil { - s.platform.Log().Debug("Bad version from feature flag", mlog.String("plugin_id", pluginID), mlog.Err(err), mlog.String("version", version)) + ch.srv.Log().Debug("Bad version from feature flag", mlog.String("plugin_id", pluginID), mlog.Err(err), mlog.String("version", version)) return } parsedExistingVersion, err := semver.Parse(pluginStatus.Version) if err != nil { - s.platform.Log().Debug("Bad version from plugin manifest", mlog.String("plugin_id", pluginID), mlog.Err(err), mlog.String("version", pluginStatus.Version)) + ch.srv.Log().Debug("Bad version from plugin manifest", mlog.String("plugin_id", pluginID), mlog.Err(err), mlog.String("version", pluginStatus.Version)) return } if parsedVersion.LTE(parsedExistingVersion) { - s.platform.Log().Debug("Skip installation because given version was a downgrade and on-prem installations should not downgrade.", mlog.String("plugin_id", pluginID), mlog.Err(err), mlog.String("version", pluginStatus.Version)) + ch.srv.Log().Debug("Skip installation because given version was a downgrade and on-prem installations should not downgrade.", mlog.String("plugin_id", pluginID), mlog.Err(err), mlog.String("version", pluginStatus.Version)) return } } - _, err := s.InstallMarketplacePlugin(&model.InstallMarketplacePluginRequest{ + _, err := ch.InstallMarketplacePlugin(&model.InstallMarketplacePluginRequest{ Id: pluginID, Version: version, }) if err != nil { - s.platform.Log().Debug("Unable to install plugin from FF manifest", mlog.String("plugin_id", pluginID), mlog.Err(err), mlog.String("version", version)) + ch.srv.Log().Debug("Unable to install plugin from FF manifest", mlog.String("plugin_id", pluginID), mlog.Err(err), mlog.String("version", version)) } else { - if err := s.enablePlugin(pluginID); err != nil { - s.platform.Log().Debug("Unable to enable plugin installed from feature flag.", mlog.String("plugin_id", pluginID), mlog.Err(err), mlog.String("version", version)) + if err := ch.enablePlugin(pluginID); err != nil { + ch.srv.Log().Debug("Unable to enable plugin installed from feature flag.", mlog.String("plugin_id", pluginID), mlog.Err(err), mlog.String("version", version)) } else { - s.platform.Log().Debug("Installed and enabled plugin.", mlog.String("plugin_id", pluginID), mlog.String("version", version)) + ch.srv.Log().Debug("Installed and enabled plugin.", mlog.String("plugin_id", pluginID), mlog.String("version", version)) } } } @@ -1240,15 +1140,15 @@ func getIcon(iconPath string) (string, error) { return fmt.Sprintf("data:image/svg+xml;base64,%s", base64.StdEncoding.EncodeToString(icon)), nil } -func (s *PluginService) getPluginStateOverride(pluginID string) (bool, bool) { +func (ch *Channels) getPluginStateOverride(pluginID string) (bool, bool) { switch pluginID { case model.PluginIdApps: // Tie Apps proxy disabled status to the feature flag. - if !s.platform.Config().FeatureFlags.AppsEnabled { + if !ch.cfgSvc.Config().FeatureFlags.AppsEnabled { return true, false } case model.PluginIdCalls: - if !s.platform.Config().FeatureFlags.CallsEnabled { + if !ch.cfgSvc.Config().FeatureFlags.CallsEnabled { return true, false } } diff --git a/app/plugin_api.go b/app/plugin_api.go index 57ee5b4192..5c8ee9fad1 100644 --- a/app/plugin_api.go +++ b/app/plugin_api.go @@ -886,7 +886,7 @@ func (api *PluginAPI) DisablePlugin(id string) *model.AppError { } func (api *PluginAPI) RemovePlugin(id string) *model.AppError { - return api.app.Srv().pluginService.RemovePlugin(id) + return api.app.Channels().RemovePlugin(id) } func (api *PluginAPI) GetPluginStatus(id string) (*model.PluginStatus, *model.AppError) { @@ -1235,7 +1235,7 @@ func (api *PluginAPI) GetCloudLimits() (*model.ProductLimits, error) { // RegisterCollectionAndTopic informs the server that this plugin handles // the given collection and topic types. func (api *PluginAPI) RegisterCollectionAndTopic(collectionType, topicType string) error { - return api.app.Srv().pluginService.registerCollectionAndTopic(api.id, collectionType, topicType) + return api.app.registerCollectionAndTopic(api.id, collectionType, topicType) } func (api *PluginAPI) CreateUploadSession(us *model.UploadSession) (*model.UploadSession, error) { diff --git a/app/plugin_api_test.go b/app/plugin_api_test.go index cec5736eb3..759613781b 100644 --- a/app/plugin_api_test.go +++ b/app/plugin_api_test.go @@ -92,7 +92,7 @@ func setupMultiPluginAPITest(t *testing.T, pluginCodes []string, pluginManifests return app.NewPluginAPI(c, manifest) } - env, err := plugin.NewEnvironment(newPluginAPI, NewDriverImpl(app.Srv().Platform()), pluginDir, webappPluginDir, false, app.Log(), nil) + env, err := plugin.NewEnvironment(newPluginAPI, NewDriverImpl(app.Srv()), pluginDir, webappPluginDir, false, app.Log(), nil) require.NoError(t, err) require.Equal(t, len(pluginCodes), len(pluginIDs)) @@ -119,7 +119,7 @@ func setupMultiPluginAPITest(t *testing.T, pluginCodes []string, pluginManifests }) } - app.PluginService().SetPluginsEnvironment(env) + app.ch.SetPluginsEnvironment(env) return pluginDir } @@ -849,7 +849,7 @@ func TestPluginAPIGetPlugins(t *testing.T) { defer os.RemoveAll(pluginDir) defer os.RemoveAll(webappPluginDir) - env, err := plugin.NewEnvironment(th.NewPluginAPI, NewDriverImpl(th.Server.Platform()), pluginDir, webappPluginDir, false, th.App.Log(), nil) + env, err := plugin.NewEnvironment(th.NewPluginAPI, NewDriverImpl(th.Server), pluginDir, webappPluginDir, false, th.App.Log(), nil) require.NoError(t, err) pluginIDs := []string{"pluginid1", "pluginid2", "pluginid3"} @@ -866,7 +866,7 @@ func TestPluginAPIGetPlugins(t *testing.T) { require.True(t, activated) pluginManifests = append(pluginManifests, manifest) } - th.App.PluginService().SetPluginsEnvironment(env) + th.App.ch.SetPluginsEnvironment(env) // Deactivate the last one for testing success := env.Deactivate(pluginIDs[len(pluginIDs)-1]) @@ -937,10 +937,10 @@ func TestInstallPlugin(t *testing.T) { return app.NewPluginAPI(c, manifest) } - env, err := plugin.NewEnvironment(newPluginAPI, NewDriverImpl(app.Srv().Platform()), pluginDir, webappPluginDir, false, app.Log(), nil) + env, err := plugin.NewEnvironment(newPluginAPI, NewDriverImpl(app.Srv()), pluginDir, webappPluginDir, false, app.Log(), nil) require.NoError(t, err) - app.PluginService().SetPluginsEnvironment(env) + app.ch.SetPluginsEnvironment(env) backend := filepath.Join(pluginDir, pluginID, "backend.exe") utils.CompileGo(t, pluginCode, backend) @@ -1632,10 +1632,10 @@ func TestAPIMetrics(t *testing.T) { defer os.RemoveAll(pluginDir) defer os.RemoveAll(webappPluginDir) - env, err := plugin.NewEnvironment(th.NewPluginAPI, NewDriverImpl(th.Server.Platform()), pluginDir, webappPluginDir, false, th.App.Log(), metricsMock) + env, err := plugin.NewEnvironment(th.NewPluginAPI, NewDriverImpl(th.Server), pluginDir, webappPluginDir, false, th.App.Log(), metricsMock) require.NoError(t, err) - th.App.PluginService().SetPluginsEnvironment(env) + th.App.ch.SetPluginsEnvironment(env) pluginID := model.NewId() backend := filepath.Join(pluginDir, pluginID, "backend.exe") @@ -2079,10 +2079,10 @@ func TestRegisterCollectionAndTopic(t *testing.T) { return th.App.NewPluginAPI(th.Context, manifest) } - env, err := plugin.NewEnvironment(newPluginAPI, NewDriverImpl(th.Server.Platform()), pluginDir, webappPluginDir, false, th.App.Log(), nil) + env, err := plugin.NewEnvironment(newPluginAPI, NewDriverImpl(th.App.Srv()), pluginDir, webappPluginDir, false, th.App.Log(), nil) require.NoError(t, err) - th.App.PluginService().SetPluginsEnvironment(env) + th.App.ch.SetPluginsEnvironment(env) pluginID := "testplugin" pluginManifest := `{"id": "testplugin", "server": {"executable": "backend.exe"}}` @@ -2179,10 +2179,10 @@ func TestPluginUploadsAPI(t *testing.T) { newPluginAPI := func(manifest *model.Manifest) plugin.API { return th.App.NewPluginAPI(th.Context, manifest) } - env, err := plugin.NewEnvironment(newPluginAPI, NewDriverImpl(th.Server.Platform()), pluginDir, webappPluginDir, false, th.App.Log(), nil) + env, err := plugin.NewEnvironment(newPluginAPI, NewDriverImpl(th.App.Srv()), pluginDir, webappPluginDir, false, th.App.Log(), nil) require.NoError(t, err) - th.App.PluginService().SetPluginsEnvironment(env) + th.App.ch.SetPluginsEnvironment(env) pluginID := "testplugin" pluginManifest := `{"id": "testplugin", "server": {"executable": "backend.exe"}}` diff --git a/app/plugin_commands.go b/app/plugin_commands.go index 8d034931e9..e70c69a38a 100644 --- a/app/plugin_commands.go +++ b/app/plugin_commands.go @@ -22,10 +22,6 @@ type PluginCommand struct { } func (a *App) RegisterPluginCommand(pluginID string, command *model.Command) error { - return a.Srv().pluginService.registerPluginCommand(pluginID, command) -} - -func (s *PluginService) registerPluginCommand(pluginID string, command *model.Command) error { if command.Trigger == "" { return errors.New("invalid command") } @@ -59,10 +55,10 @@ func (s *PluginService) registerPluginCommand(pluginID string, command *model.Co AutocompleteIconData: command.AutocompleteIconData, } - s.pluginCommandsLock.Lock() - defer s.pluginCommandsLock.Unlock() + a.ch.pluginCommandsLock.Lock() + defer a.ch.pluginCommandsLock.Unlock() - for _, pc := range s.pluginCommands { + for _, pc := range a.ch.pluginCommands { if pc.Command.Trigger == command.Trigger && pc.Command.TeamId == command.TeamId { if pc.PluginId == pluginID { pc.Command = command @@ -71,7 +67,7 @@ func (s *PluginService) registerPluginCommand(pluginID string, command *model.Co } } - s.pluginCommands = append(s.pluginCommands, &PluginCommand{ + a.ch.pluginCommands = append(a.ch.pluginCommands, &PluginCommand{ Command: command, PluginId: pluginID, }) @@ -79,47 +75,39 @@ func (s *PluginService) registerPluginCommand(pluginID string, command *model.Co } func (a *App) UnregisterPluginCommand(pluginID, teamID, trigger string) { - a.Srv().pluginService.unregisterPluginCommand(pluginID, teamID, trigger) -} - -func (s *PluginService) unregisterPluginCommand(pluginID, teamID, trigger string) { trigger = strings.ToLower(trigger) - s.pluginCommandsLock.Lock() - defer s.pluginCommandsLock.Unlock() + a.ch.pluginCommandsLock.Lock() + defer a.ch.pluginCommandsLock.Unlock() var remaining []*PluginCommand - for _, pc := range s.pluginCommands { + for _, pc := range a.ch.pluginCommands { if pc.Command.TeamId != teamID || pc.Command.Trigger != trigger { remaining = append(remaining, pc) } } - s.pluginCommands = remaining + a.ch.pluginCommands = remaining } -func (s *PluginService) unregisterPluginCommands(pluginID string) { - s.pluginCommandsLock.Lock() - defer s.pluginCommandsLock.Unlock() +func (ch *Channels) unregisterPluginCommands(pluginID string) { + ch.pluginCommandsLock.Lock() + defer ch.pluginCommandsLock.Unlock() var remaining []*PluginCommand - for _, pc := range s.pluginCommands { + for _, pc := range ch.pluginCommands { if pc.PluginId != pluginID { remaining = append(remaining, pc) } } - s.pluginCommands = remaining + ch.pluginCommands = remaining } func (a *App) PluginCommandsForTeam(teamID string) []*model.Command { - return a.Srv().pluginService.PluginCommandsForTeam(teamID) -} - -func (s *PluginService) PluginCommandsForTeam(teamID string) []*model.Command { - s.pluginCommandsLock.RLock() - defer s.pluginCommandsLock.RUnlock() + a.ch.pluginCommandsLock.RLock() + defer a.ch.pluginCommandsLock.RUnlock() var commands []*model.Command - for _, pc := range s.pluginCommands { + for _, pc := range a.ch.pluginCommands { if pc.Command.TeamId == "" || pc.Command.TeamId == teamID { commands = append(commands, pc.Command) } @@ -127,24 +115,6 @@ func (s *PluginService) PluginCommandsForTeam(teamID string) []*model.Command { return commands } -func (s *PluginService) getPluginCommandFromArgs(args *model.CommandArgs) *PluginCommand { - parts := strings.Split(args.Command, " ") - trigger := parts[0][1:] - trigger = strings.ToLower(trigger) - - var matched *PluginCommand - s.pluginCommandsLock.RLock() - for _, pc := range s.pluginCommands { - if (pc.Command.TeamId == "" || pc.Command.TeamId == args.TeamId) && pc.Command.Trigger == trigger { - matched = pc - break - } - } - s.pluginCommandsLock.RUnlock() - - return matched -} - // tryExecutePluginCommand attempts to run a command provided by a plugin based on the given arguments. If no such // command can be found, returns nil for all arguments. func (a *App) tryExecutePluginCommand(c request.CTX, args *model.CommandArgs) (*model.Command, *model.CommandResponse, *model.AppError) { @@ -152,7 +122,15 @@ func (a *App) tryExecutePluginCommand(c request.CTX, args *model.CommandArgs) (* trigger := parts[0][1:] trigger = strings.ToLower(trigger) - matched := a.Srv().pluginService.getPluginCommandFromArgs(args) + var matched *PluginCommand + a.ch.pluginCommandsLock.RLock() + for _, pc := range a.ch.pluginCommands { + if (pc.Command.TeamId == "" || pc.Command.TeamId == args.TeamId) && pc.Command.Trigger == trigger { + matched = pc + break + } + } + a.ch.pluginCommandsLock.RUnlock() if matched == nil { return nil, nil, nil } diff --git a/app/plugin_commands_test.go b/app/plugin_commands_test.go index 1cf9751d9a..e56cf74718 100644 --- a/app/plugin_commands_test.go +++ b/app/plugin_commands_test.go @@ -106,7 +106,7 @@ func TestPluginCommand(t *testing.T) { require.NotEqual(t, "plugin", commands.Trigger) } - th.App.PluginService().RemovePlugin(pluginIDs[0]) + th.App.ch.RemovePlugin(pluginIDs[0]) }) t.Run("re-entrant command registration on config change", func(t *testing.T) { @@ -207,7 +207,7 @@ func TestPluginCommand(t *testing.T) { killed = true } - th.App.PluginService().RemovePlugin(pluginIDs[0]) + th.App.ch.RemovePlugin(pluginIDs[0]) require.False(t, killed, "execute command appears to have deadlocked") }) @@ -285,7 +285,7 @@ func TestPluginCommand(t *testing.T) { require.Equal(t, model.CommandResponseTypeEphemeral, resp.ResponseType) require.Equal(t, "text", resp.Text) - th.App.PluginService().RemovePlugin(pluginIDs[0]) + th.App.ch.RemovePlugin(pluginIDs[0]) }) t.Run("plugin has crashed before execution of command", func(t *testing.T) { tearDown, pluginIDs, activationErrors := SetAppEnvironmentWithPlugins(t, []string{` @@ -329,7 +329,7 @@ func TestPluginCommand(t *testing.T) { require.Nil(t, resp) require.NotNil(t, err) require.Equal(t, err.Id, "model.plugin_command_error.error.app_error") - th.App.PluginService().RemovePlugin(pluginIDs[0]) + th.App.ch.RemovePlugin(pluginIDs[0]) }) t.Run("plugin has crashed due to the execution of the command", func(t *testing.T) { @@ -374,7 +374,7 @@ func TestPluginCommand(t *testing.T) { require.Nil(t, resp) require.NotNil(t, err) require.Equal(t, err.Id, "model.plugin_command_crash.error.app_error") - th.App.PluginService().RemovePlugin(pluginIDs[0]) + th.App.ch.RemovePlugin(pluginIDs[0]) }) t.Run("plugin returning status code 0", func(t *testing.T) { diff --git a/app/plugin_db_driver.go b/app/plugin_db_driver.go index 0b74577f36..a29fa7467f 100644 --- a/app/plugin_db_driver.go +++ b/app/plugin_db_driver.go @@ -10,7 +10,6 @@ import ( "sync" "time" - "github.com/mattermost/mattermost-server/v6/app/platform" "github.com/mattermost/mattermost-server/v6/model" "github.com/mattermost/mattermost-server/v6/plugin" ) @@ -20,7 +19,7 @@ import ( // a new entry tracked centrally in a map. Further requests operate on the // object ID. type DriverImpl struct { - ps *platform.PlatformService + s *Server connMut sync.RWMutex connMap map[string]*sql.Conn txMut sync.Mutex @@ -31,9 +30,9 @@ type DriverImpl struct { rowsMap map[string]driver.Rows } -func NewDriverImpl(s *platform.PlatformService) *DriverImpl { +func NewDriverImpl(s *Server) *DriverImpl { return &DriverImpl{ - ps: s, + s: s, connMap: make(map[string]*sql.Conn), txMap: make(map[string]driver.Tx), stMap: make(map[string]driver.Stmt), @@ -42,11 +41,11 @@ func NewDriverImpl(s *platform.PlatformService) *DriverImpl { } func (d *DriverImpl) Conn(isMaster bool) (string, error) { - dbFunc := d.ps.Store.GetInternalMasterDB + dbFunc := d.s.Platform().Store.GetInternalMasterDB if !isMaster { - dbFunc = d.ps.Store.GetInternalReplicaDB + dbFunc = d.s.Platform().Store.GetInternalReplicaDB } - timeout := time.Duration(*d.ps.Config().SqlSettings.QueryTimeout) * time.Second + timeout := time.Duration(*d.s.Config().SqlSettings.QueryTimeout) * time.Second ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() conn, err := dbFunc().Conn(ctx) diff --git a/app/plugin_db_driver_test.go b/app/plugin_db_driver_test.go index 797f677d59..a2c428fb6f 100644 --- a/app/plugin_db_driver_test.go +++ b/app/plugin_db_driver_test.go @@ -15,7 +15,7 @@ func TestConnCreateTimeout(t *testing.T) { *th.App.Config().SqlSettings.QueryTimeout = 0 - d := NewDriverImpl(th.Server.platform) + d := NewDriverImpl(th.Server) _, err := d.Conn(true) require.Error(t, err) } diff --git a/app/plugin_event.go b/app/plugin_event.go index 19d9f1dabc..c30e2d1af5 100644 --- a/app/plugin_event.go +++ b/app/plugin_event.go @@ -9,10 +9,10 @@ import ( "github.com/mattermost/mattermost-server/v6/model" ) -func (s *PluginService) notifyClusterPluginEvent(event model.ClusterEvent, data model.PluginEventData) { +func (ch *Channels) notifyClusterPluginEvent(event model.ClusterEvent, data model.PluginEventData) { buf, _ := json.Marshal(data) - if s.platform.Cluster() != nil { - s.platform.Cluster().SendClusterMessage(&model.ClusterMessage{ + if ch.srv.platform.Cluster() != nil { + ch.srv.platform.Cluster().SendClusterMessage(&model.ClusterMessage{ Event: event, SendType: model.ClusterSendReliable, WaitForAllToSend: true, diff --git a/app/plugin_hooks_test.go b/app/plugin_hooks_test.go index 105982a7b2..161994a1ab 100644 --- a/app/plugin_hooks_test.go +++ b/app/plugin_hooks_test.go @@ -33,10 +33,10 @@ func SetAppEnvironmentWithPlugins(t *testing.T, pluginCode []string, app *App, a webappPluginDir, err := os.MkdirTemp("", "") require.NoError(t, err) - env, err := plugin.NewEnvironment(apiFunc, NewDriverImpl(app.Srv().Platform()), pluginDir, webappPluginDir, false, app.Log(), nil) + env, err := plugin.NewEnvironment(apiFunc, NewDriverImpl(app.Srv()), pluginDir, webappPluginDir, false, app.Log(), nil) require.NoError(t, err) - app.PluginService().SetPluginsEnvironment(env) + app.ch.SetPluginsEnvironment(env) pluginIDs := []string{} activationErrors := []error{} for _, code := range pluginCode { @@ -1030,10 +1030,10 @@ func TestHookMetrics(t *testing.T) { defer os.RemoveAll(pluginDir) defer os.RemoveAll(webappPluginDir) - env, err := plugin.NewEnvironment(th.NewPluginAPI, NewDriverImpl(th.Server.Platform()), pluginDir, webappPluginDir, false, th.App.Log(), metricsMock) + env, err := plugin.NewEnvironment(th.NewPluginAPI, NewDriverImpl(th.Server), pluginDir, webappPluginDir, false, th.App.Log(), metricsMock) require.NoError(t, err) - th.App.PluginService().SetPluginsEnvironment(env) + th.App.ch.SetPluginsEnvironment(env) pluginID := model.NewId() backend := filepath.Join(pluginDir, pluginID, "backend.exe") @@ -1234,7 +1234,7 @@ func TestHookRunDataRetention(t *testing.T) { require.True(t, th.App.GetPluginsEnvironment().IsActive(pluginID)) hookCalled := false - th.App.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { + th.App.Channels().RunMultiHook(func(hooks plugin.Hooks) bool { n, _ := hooks.RunDataRetention(0, 0) // Ensure return it correct assert.Equal(t, int64(100), n) @@ -1278,7 +1278,7 @@ func TestHookOnSendDailyTelemetry(t *testing.T) { require.True(t, th.App.GetPluginsEnvironment().IsActive(pluginID)) hookCalled := false - th.App.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { + th.App.Channels().RunMultiHook(func(hooks plugin.Hooks) bool { hooks.OnSendDailyTelemetry() hookCalled = true @@ -1322,7 +1322,7 @@ func TestHookOnCloudLimitsUpdated(t *testing.T) { require.True(t, th.App.GetPluginsEnvironment().IsActive(pluginID)) hookCalled := false - th.App.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { + th.App.Channels().RunMultiHook(func(hooks plugin.Hooks) bool { hooks.OnCloudLimitsUpdated(nil) hookCalled = true diff --git a/app/plugin_install.go b/app/plugin_install.go index f0c54a0da4..431c3abebe 100644 --- a/app/plugin_install.go +++ b/app/plugin_install.go @@ -58,10 +58,10 @@ const managedPluginFileName = ".filestore" // fileStorePluginFolder is the folder name in the file store of the plugin bundles installed. const fileStorePluginFolder = "plugins" -func (s *PluginService) installPluginFromData(data model.PluginEventData) { +func (ch *Channels) installPluginFromData(data model.PluginEventData) { mlog.Debug("Installing plugin as per cluster message", mlog.String("plugin_id", data.Id)) - pluginSignaturePathMap, appErr := s.getPluginsFromFolder() + pluginSignaturePathMap, appErr := ch.getPluginsFromFolder() if appErr != nil { mlog.Error("Failed to get plugin signatures from filestore. Can't install plugin from data.", mlog.Err(appErr)) return @@ -72,53 +72,53 @@ func (s *PluginService) installPluginFromData(data model.PluginEventData) { return } - reader, err := s.fileStore.Reader(plugin.path) - if err != nil { - mlog.Error("Failed to open plugin bundle from file store.", mlog.String("bundle", plugin.path), mlog.Err(err)) + reader, appErr := ch.srv.fileReader(plugin.path) + if appErr != nil { + mlog.Error("Failed to open plugin bundle from file store.", mlog.String("bundle", plugin.path), mlog.Err(appErr)) return } defer reader.Close() var signature filestore.ReadCloseSeeker - if *s.platform.Config().PluginSettings.RequirePluginSignature { - signature, err = s.fileStore.Reader(plugin.signaturePath) - if err != nil { - mlog.Error("Failed to open plugin signature from file store.", mlog.Err(err)) + if *ch.cfgSvc.Config().PluginSettings.RequirePluginSignature { + signature, appErr = ch.srv.fileReader(plugin.signaturePath) + if appErr != nil { + mlog.Error("Failed to open plugin signature from file store.", mlog.Err(appErr)) return } defer signature.Close() } - manifest, appErr := s.installPluginLocally(reader, signature, installPluginLocallyAlways) + manifest, appErr := ch.installPluginLocally(reader, signature, installPluginLocallyAlways) if appErr != nil { mlog.Error("Failed to sync plugin from file store", mlog.String("bundle", plugin.path), mlog.Err(appErr)) return } - if err2 := s.notifyPluginEnabled(manifest); err2 != nil { - mlog.Error("Failed notify plugin enabled", mlog.Err(err2)) + if err := ch.notifyPluginEnabled(manifest); err != nil { + mlog.Error("Failed notify plugin enabled", mlog.Err(err)) } - if err2 := s.notifyPluginStatusesChanged(); err2 != nil { - mlog.Error("Failed to notify plugin status changed", mlog.Err(err2)) + if err := ch.notifyPluginStatusesChanged(); err != nil { + mlog.Error("Failed to notify plugin status changed", mlog.Err(err)) } } -func (s *PluginService) removePluginFromData(data model.PluginEventData) { +func (ch *Channels) removePluginFromData(data model.PluginEventData) { mlog.Debug("Removing plugin as per cluster message", mlog.String("plugin_id", data.Id)) - if err := s.removePluginLocally(data.Id); err != nil { + if err := ch.removePluginLocally(data.Id); err != nil { mlog.Warn("Failed to remove plugin locally", mlog.Err(err), mlog.String("id", data.Id)) } - if err := s.notifyPluginStatusesChanged(); err != nil { + if err := ch.notifyPluginStatusesChanged(); err != nil { mlog.Warn("failed to notify plugin status changed", mlog.Err(err)) } } // InstallPluginWithSignature verifies and installs plugin. -func (s *PluginService) installPluginWithSignature(pluginFile, signature io.ReadSeeker) (*model.Manifest, *model.AppError) { - return s.installPlugin(pluginFile, signature, installPluginLocallyAlways) +func (ch *Channels) installPluginWithSignature(pluginFile, signature io.ReadSeeker) (*model.Manifest, *model.AppError) { + return ch.installPlugin(pluginFile, signature, installPluginLocallyAlways) } // InstallPlugin unpacks and installs a plugin but does not enable or activate it. @@ -132,40 +132,40 @@ func (a *App) InstallPlugin(pluginFile io.ReadSeeker, replace bool) (*model.Mani } func (a *App) installPlugin(pluginFile, signature io.ReadSeeker, installationStrategy pluginInstallationStrategy) (*model.Manifest, *model.AppError) { - return a.ch.srv.pluginService.installPlugin(pluginFile, signature, installationStrategy) + return a.ch.installPlugin(pluginFile, signature, installationStrategy) } -func (s *PluginService) installPlugin(pluginFile, signature io.ReadSeeker, installationStrategy pluginInstallationStrategy) (*model.Manifest, *model.AppError) { - manifest, appErr := s.installPluginLocally(pluginFile, signature, installationStrategy) +func (ch *Channels) installPlugin(pluginFile, signature io.ReadSeeker, installationStrategy pluginInstallationStrategy) (*model.Manifest, *model.AppError) { + manifest, appErr := ch.installPluginLocally(pluginFile, signature, installationStrategy) if appErr != nil { return nil, appErr } if signature != nil { signature.Seek(0, 0) - if _, err := s.fileStore.WriteFile(signature, getSignatureStorePath(manifest.Id)); err != nil { - return nil, model.NewAppError("saveSignature", "app.plugin.store_signature.app_error", nil, "", http.StatusInternalServerError).Wrap(err) + if _, appErr = ch.srv.writeFile(signature, getSignatureStorePath(manifest.Id)); appErr != nil { + return nil, model.NewAppError("saveSignature", "app.plugin.store_signature.app_error", nil, "", http.StatusInternalServerError).Wrap(appErr) } } // Store bundle in the file store to allow access from other servers. pluginFile.Seek(0, 0) - if _, appErr := s.fileStore.WriteFile(pluginFile, getBundleStorePath(manifest.Id)); appErr != nil { + if _, appErr := ch.srv.writeFile(pluginFile, getBundleStorePath(manifest.Id)); appErr != nil { return nil, model.NewAppError("uploadPlugin", "app.plugin.store_bundle.app_error", nil, "", http.StatusInternalServerError).Wrap(appErr) } - s.notifyClusterPluginEvent( + ch.notifyClusterPluginEvent( model.ClusterEventInstallPlugin, model.PluginEventData{ Id: manifest.Id, }, ) - if err := s.notifyPluginEnabled(manifest); err != nil { + if err := ch.notifyPluginEnabled(manifest); err != nil { mlog.Warn("Failed notify plugin enabled", mlog.Err(err)) } - if err := s.notifyPluginStatusesChanged(); err != nil { + if err := ch.notifyPluginStatusesChanged(); err != nil { mlog.Warn("Failed to notify plugin status changed", mlog.Err(err)) } @@ -174,10 +174,10 @@ func (s *PluginService) installPlugin(pluginFile, signature io.ReadSeeker, insta // InstallMarketplacePlugin installs a plugin listed in the marketplace server. It will get the plugin bundle // from the prepackaged folder, if available, or remotely if EnableRemoteMarketplace is true. -func (s *PluginService) InstallMarketplacePlugin(request *model.InstallMarketplacePluginRequest) (*model.Manifest, *model.AppError) { +func (ch *Channels) InstallMarketplacePlugin(request *model.InstallMarketplacePluginRequest) (*model.Manifest, *model.AppError) { var pluginFile, signatureFile io.ReadSeeker - prepackagedPlugin, appErr := s.getPrepackagedPlugin(request.Id, request.Version) + prepackagedPlugin, appErr := ch.getPrepackagedPlugin(request.Id, request.Version) if appErr != nil && appErr.Id != "app.plugin.marketplace_plugins.not_found.app_error" { return nil, appErr } @@ -192,9 +192,9 @@ func (s *PluginService) InstallMarketplacePlugin(request *model.InstallMarketpla signatureFile = bytes.NewReader(prepackagedPlugin.Signature) } - if *s.platform.Config().PluginSettings.EnableRemoteMarketplace { + if *ch.cfgSvc.Config().PluginSettings.EnableRemoteMarketplace { var plugin *model.BaseMarketplacePlugin - plugin, appErr = s.getRemoteMarketplacePlugin(request.Id, request.Version) + plugin, appErr = ch.getRemoteMarketplacePlugin(request.Id, request.Version) if appErr != nil { return nil, appErr } @@ -214,7 +214,7 @@ func (s *PluginService) InstallMarketplacePlugin(request *model.InstallMarketpla } if prepackagedVersion.LT(marketplaceVersion) { // Always true if no prepackaged plugin was found - downloadedPluginBytes, err := s.downloadFromURL(plugin.DownloadURL) + downloadedPluginBytes, err := ch.srv.downloadFromURL(plugin.DownloadURL) if err != nil { return nil, model.NewAppError("InstallMarketplacePlugin", "app.plugin.install_marketplace_plugin.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } @@ -234,7 +234,7 @@ func (s *PluginService) InstallMarketplacePlugin(request *model.InstallMarketpla return nil, model.NewAppError("InstallMarketplacePlugin", "app.plugin.marketplace_plugins.signature_not_found.app_error", nil, "", http.StatusInternalServerError) } - manifest, appErr := s.installPluginWithSignature(pluginFile, signatureFile) + manifest, appErr := ch.installPluginWithSignature(pluginFile, signatureFile) if appErr != nil { return nil, appErr } @@ -253,15 +253,15 @@ const ( installPluginLocallyAlways ) -func (s *PluginService) installPluginLocally(pluginFile, signature io.ReadSeeker, installationStrategy pluginInstallationStrategy) (*model.Manifest, *model.AppError) { - pluginsEnvironment := s.GetPluginsEnvironment() +func (ch *Channels) installPluginLocally(pluginFile, signature io.ReadSeeker, installationStrategy pluginInstallationStrategy) (*model.Manifest, *model.AppError) { + pluginsEnvironment := ch.GetPluginsEnvironment() if pluginsEnvironment == nil { return nil, model.NewAppError("installPluginLocally", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented) } // verify signature if signature != nil { - if err := s.verifyPlugin(pluginFile, signature); err != nil { + if err := ch.verifyPlugin(pluginFile, signature); err != nil { return nil, err } } @@ -277,7 +277,7 @@ func (s *PluginService) installPluginLocally(pluginFile, signature io.ReadSeeker return nil, appErr } - manifest, appErr = s.installExtractedPlugin(manifest, pluginDir, installationStrategy) + manifest, appErr = ch.installExtractedPlugin(manifest, pluginDir, installationStrategy) if appErr != nil { return nil, appErr } @@ -312,8 +312,8 @@ func extractPlugin(pluginFile io.ReadSeeker, extractDir string) (*model.Manifest return manifest, extractDir, nil } -func (s *PluginService) installExtractedPlugin(manifest *model.Manifest, fromPluginDir string, installationStrategy pluginInstallationStrategy) (*model.Manifest, *model.AppError) { - pluginsEnvironment := s.GetPluginsEnvironment() +func (ch *Channels) installExtractedPlugin(manifest *model.Manifest, fromPluginDir string, installationStrategy pluginInstallationStrategy) (*model.Manifest, *model.AppError) { + pluginsEnvironment := ch.GetPluginsEnvironment() if pluginsEnvironment == nil { return nil, model.NewAppError("installExtractedPlugin", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented) } @@ -360,12 +360,12 @@ func (s *PluginService) installExtractedPlugin(manifest *model.Manifest, fromPlu // Otherwise remove the existing installation prior to install below. mlog.Debug("Removing existing installation of plugin before local install", mlog.String("plugin_id", existingManifest.Id), mlog.String("version", existingManifest.Version)) - if err := s.removePluginLocally(existingManifest.Id); err != nil { + if err := ch.removePluginLocally(existingManifest.Id); err != nil { return nil, model.NewAppError("installExtractedPlugin", "app.plugin.install_id_failed_remove.app_error", nil, "", http.StatusBadRequest) } } - pluginPath := filepath.Join(*s.platform.Config().PluginSettings.Directory, manifest.Id) + pluginPath := filepath.Join(*ch.cfgSvc.Config().PluginSettings.Directory, manifest.Id) err = utils.CopyDir(fromPluginDir, pluginPath) if err != nil { return nil, model.NewAppError("installExtractedPlugin", "app.plugin.mvdir.app_error", nil, "", http.StatusInternalServerError).Wrap(err) @@ -387,9 +387,9 @@ func (s *PluginService) installExtractedPlugin(manifest *model.Manifest, fromPlu } // Activate the plugin if enabled. - pluginState := s.platform.Config().PluginSettings.PluginStates[manifest.Id] + pluginState := ch.cfgSvc.Config().PluginSettings.PluginStates[manifest.Id] if pluginState != nil && pluginState.Enable { - if hasOverride, enabled := s.getPluginStateOverride(manifest.Id); hasOverride && !enabled { + if hasOverride, enabled := ch.getPluginStateOverride(manifest.Id); hasOverride && !enabled { return manifest, nil } @@ -405,49 +405,49 @@ func (s *PluginService) installExtractedPlugin(manifest *model.Manifest, fromPlu return manifest, nil } -func (s *PluginService) RemovePlugin(id string) *model.AppError { +func (ch *Channels) RemovePlugin(id string) *model.AppError { // Disable plugin before removal to make sure this // plugin remains disabled on re-install. - if err := s.disablePlugin(id); err != nil { + if err := ch.disablePlugin(id); err != nil { return err } - if err := s.removePluginLocally(id); err != nil { + if err := ch.removePluginLocally(id); err != nil { return err } // Remove bundle from the file store. storePluginFileName := getBundleStorePath(id) - bundleExist, err := s.fileStore.FileExists(storePluginFileName) + bundleExist, err := ch.srv.fileExists(storePluginFileName) if err != nil { return model.NewAppError("removePlugin", "app.plugin.remove_bundle.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } if !bundleExist { return nil } - if err = s.fileStore.RemoveFile(storePluginFileName); err != nil { + if err = ch.srv.removeFile(storePluginFileName); err != nil { return model.NewAppError("removePlugin", "app.plugin.remove_bundle.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } - if err2 := s.removeSignature(id); err2 != nil { - mlog.Warn("Can't remove signature", mlog.Err(err2)) + if err = ch.removeSignature(id); err != nil { + mlog.Warn("Can't remove signature", mlog.Err(err)) } - s.notifyClusterPluginEvent( + ch.notifyClusterPluginEvent( model.ClusterEventRemovePlugin, model.PluginEventData{ Id: id, }, ) - if err := s.notifyPluginStatusesChanged(); err != nil { + if err := ch.notifyPluginStatusesChanged(); err != nil { mlog.Warn("Failed to notify plugin status changed", mlog.Err(err)) } return nil } -func (s *PluginService) removePluginLocally(id string) *model.AppError { - pluginsEnvironment := s.GetPluginsEnvironment() +func (ch *Channels) removePluginLocally(id string) *model.AppError { + pluginsEnvironment := ch.GetPluginsEnvironment() if pluginsEnvironment == nil { return model.NewAppError("removePlugin", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented) } @@ -473,7 +473,7 @@ func (s *PluginService) removePluginLocally(id string) *model.AppError { pluginsEnvironment.Deactivate(id) pluginsEnvironment.RemovePlugin(id) - s.unregisterPluginCommands(id) + ch.unregisterPluginCommands(id) if err := os.RemoveAll(pluginPath); err != nil { return model.NewAppError("removePlugin", "app.plugin.remove.app_error", nil, "", http.StatusInternalServerError).Wrap(err) @@ -482,9 +482,9 @@ func (s *PluginService) removePluginLocally(id string) *model.AppError { return nil } -func (s *PluginService) removeSignature(pluginID string) *model.AppError { +func (ch *Channels) removeSignature(pluginID string) *model.AppError { filePath := getSignatureStorePath(pluginID) - exists, err := s.fileStore.FileExists(filePath) + exists, err := ch.srv.fileExists(filePath) if err != nil { return model.NewAppError("removeSignature", "app.plugin.remove_bundle.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } @@ -492,7 +492,7 @@ func (s *PluginService) removeSignature(pluginID string) *model.AppError { mlog.Debug("no plugin signature to remove", mlog.String("plugin_id", pluginID)) return nil } - if err = s.fileStore.RemoveFile(filePath); err != nil { + if err = ch.srv.removeFile(filePath); err != nil { return model.NewAppError("removeSignature", "app.plugin.remove_bundle.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } return nil diff --git a/app/plugin_install_test.go b/app/plugin_install_test.go index ca15747865..a3eb6cfb2d 100644 --- a/app/plugin_install_test.go +++ b/app/plugin_install_test.go @@ -73,7 +73,7 @@ func TestInstallPluginLocally(t *testing.T) { th := Setup(t) defer th.TearDown() - actualManifest, appErr := th.App.PluginService().installPluginLocally(&nilReadSeeker{}, nil, installPluginLocallyOnlyIfNew) + actualManifest, appErr := th.App.ch.installPluginLocally(&nilReadSeeker{}, nil, installPluginLocallyOnlyIfNew) require.NotNil(t, appErr) assert.Equal(t, "app.plugin.extract.app_error", appErr.Id, appErr.Error()) require.Nil(t, actualManifest) @@ -87,7 +87,7 @@ func TestInstallPluginLocally(t *testing.T) { {"test", "test file"}, }) - actualManifest, appErr := th.App.PluginService().installPluginLocally(reader, nil, installPluginLocallyOnlyIfNew) + actualManifest, appErr := th.App.ch.installPluginLocally(reader, nil, installPluginLocallyOnlyIfNew) require.NotNil(t, appErr) assert.Equal(t, "app.plugin.manifest.app_error", appErr.Id, appErr.Error()) require.Nil(t, actualManifest) @@ -106,7 +106,7 @@ func TestInstallPluginLocally(t *testing.T) { {"plugin.json", string(manifestJSON)}, }) - actualManifest, appError := th.App.PluginService().installPluginLocally(reader, nil, installationStrategy) + actualManifest, appError := th.App.ch.installPluginLocally(reader, nil, installationStrategy) if actualManifest != nil { require.Equal(t, manifest, actualManifest) } @@ -134,7 +134,7 @@ func TestInstallPluginLocally(t *testing.T) { require.NoError(t, err) for _, bundleInfo := range bundleInfos { - err := th.App.PluginService().removePluginLocally(bundleInfo.Manifest.Id) + err := th.App.ch.removePluginLocally(bundleInfo.Manifest.Id) require.Nilf(t, err, "failed to remove existing plugin %s", bundleInfo.Manifest.Id) } } diff --git a/app/plugin_requests.go b/app/plugin_requests.go index 208adb11f4..1ccc966822 100644 --- a/app/plugin_requests.go +++ b/app/plugin_requests.go @@ -20,16 +20,16 @@ import ( "github.com/mattermost/mattermost-server/v6/utils" ) -func (s *PluginService) ServePluginRequest(w http.ResponseWriter, r *http.Request) { +func (ch *Channels) ServePluginRequest(w http.ResponseWriter, r *http.Request) { params := mux.Vars(r) - if handler, ok := s.channels.routerSvc.getHandler(params["plugin_id"]); ok { - s.servePluginRequest(w, r, func(*plugin.Context, http.ResponseWriter, *http.Request) { + if handler, ok := ch.routerSvc.getHandler(params["plugin_id"]); ok { + ch.servePluginRequest(w, r, func(*plugin.Context, http.ResponseWriter, *http.Request) { handler.ServeHTTP(w, r) }) return } - pluginsEnvironment := s.GetPluginsEnvironment() + pluginsEnvironment := ch.GetPluginsEnvironment() if pluginsEnvironment == nil { err := model.NewAppError("ServePluginRequest", "app.plugin.disabled.app_error", nil, "Enable plugins to serve plugin requests", http.StatusNotImplemented) mlog.Error(err.Error()) @@ -49,11 +49,11 @@ func (s *PluginService) ServePluginRequest(w http.ResponseWriter, r *http.Reques return } - s.servePluginRequest(w, r, hooks.ServeHTTP) + ch.servePluginRequest(w, r, hooks.ServeHTTP) } func (a *App) ServeInterPluginRequest(w http.ResponseWriter, r *http.Request, sourcePluginId, destinationPluginId string) { - pluginsEnvironment := a.ch.srv.pluginService.GetPluginsEnvironment() + pluginsEnvironment := a.ch.GetPluginsEnvironment() if pluginsEnvironment == nil { err := model.NewAppError("ServeInterPluginRequest", "app.plugin.disabled.app_error", nil, "Plugin environment not found.", http.StatusNotImplemented) a.Log().Error(err.Error()) @@ -87,7 +87,7 @@ func (a *App) ServeInterPluginRequest(w http.ResponseWriter, r *http.Request, so // ServePluginPublicRequest serves public plugin files // at the URL http(s)://$SITE_URL/plugins/$PLUGIN_ID/public/{anything} -func (s *PluginService) ServePluginPublicRequest(w http.ResponseWriter, r *http.Request) { +func (ch *Channels) ServePluginPublicRequest(w http.ResponseWriter, r *http.Request) { if strings.HasSuffix(r.URL.Path, "/") { http.NotFound(w, r) return @@ -97,7 +97,7 @@ func (s *PluginService) ServePluginPublicRequest(w http.ResponseWriter, r *http. vars := mux.Vars(r) pluginID := vars["plugin_id"] - pluginsEnv := s.GetPluginsEnvironment() + pluginsEnv := ch.GetPluginsEnvironment() // Check if someone has nullified the pluginsEnv in the meantime if pluginsEnv == nil { @@ -121,11 +121,11 @@ func (s *PluginService) ServePluginPublicRequest(w http.ResponseWriter, r *http. http.ServeFile(w, r, publicFile) } -func (s *PluginService) servePluginRequest(w http.ResponseWriter, r *http.Request, handler func(*plugin.Context, http.ResponseWriter, *http.Request)) { +func (ch *Channels) servePluginRequest(w http.ResponseWriter, r *http.Request, handler func(*plugin.Context, http.ResponseWriter, *http.Request)) { token := "" context := &plugin.Context{ RequestId: model.NewId(), - IPAddress: utils.GetIPAddress(r, s.platform.Config().ServiceSettings.TrustedProxyIPHeader), + IPAddress: utils.GetIPAddress(r, ch.cfgSvc.Config().ServiceSettings.TrustedProxyIPHeader), AcceptLanguage: r.Header.Get("Accept-Language"), UserAgent: r.UserAgent(), } @@ -148,8 +148,8 @@ func (s *PluginService) servePluginRequest(w http.ResponseWriter, r *http.Reques r.Header.Del("Mattermost-User-Id") if token != "" { - session, err := New(ServerConnector(s.channels)).GetSession(token) - defer s.platform.ReturnSessionToPool(session) + session, err := New(ServerConnector(ch)).GetSession(token) + defer ch.srv.platform.ReturnSessionToPool(session) csrfCheckPassed := false @@ -190,7 +190,7 @@ func (s *PluginService) servePluginRequest(w http.ResponseWriter, r *http.Reques mlog.String("user_id", userID), } - if *s.platform.Config().ServiceSettings.ExperimentalStrictCSRFEnforcement { + if *ch.cfgSvc.Config().ServiceSettings.ExperimentalStrictCSRFEnforcement { mlog.Warn(csrfErrorMessage, fields...) } else { mlog.Debug(csrfErrorMessage, fields...) @@ -219,7 +219,7 @@ func (s *PluginService) servePluginRequest(w http.ResponseWriter, r *http.Reques params := mux.Vars(r) - subpath, _ := utils.GetSubpathFromConfig(s.platform.Config()) + subpath, _ := utils.GetSubpathFromConfig(ch.cfgSvc.Config()) newQuery := r.URL.Query() newQuery.Del("access_token") diff --git a/app/plugin_requests_test.go b/app/plugin_requests_test.go index e457d8e5f1..c41c70be6d 100644 --- a/app/plugin_requests_test.go +++ b/app/plugin_requests_test.go @@ -24,7 +24,7 @@ func TestServePluginPublicRequest(t *testing.T) { require.NoError(t, err) rr := httptest.NewRecorder() - handler := http.HandlerFunc(th.App.PluginService().ServePluginPublicRequest) + handler := http.HandlerFunc(th.App.ch.ServePluginPublicRequest) handler.ServeHTTP(rr, req) assert.Equal(t, http.StatusNotFound, rr.Code) diff --git a/app/plugin_shutdown_test.go b/app/plugin_shutdown_test.go index 1c77fc3814..293d882f1f 100644 --- a/app/plugin_shutdown_test.go +++ b/app/plugin_shutdown_test.go @@ -63,7 +63,7 @@ func TestPluginShutdownTest(t *testing.T) { done := make(chan bool) go func() { defer close(done) - th.App.PluginService().ShutDownPlugins() + th.App.ch.ShutDownPlugins() }() select { diff --git a/app/plugin_signature.go b/app/plugin_signature.go index 928a9687b8..0903aa08fc 100644 --- a/app/plugin_signature.go +++ b/app/plugin_signature.go @@ -73,16 +73,16 @@ func (a *App) DeletePublicKey(name string) *model.AppError { // VerifyPlugin checks that the given signature corresponds to the given plugin and matches a trusted certificate. func (a *App) VerifyPlugin(plugin, signature io.ReadSeeker) *model.AppError { - return a.ch.srv.pluginService.verifyPlugin(plugin, signature) + return a.ch.verifyPlugin(plugin, signature) } -func (s *PluginService) verifyPlugin(plugin, signature io.ReadSeeker) *model.AppError { +func (ch *Channels) verifyPlugin(plugin, signature io.ReadSeeker) *model.AppError { if err := verifySignature(bytes.NewReader(mattermostPluginPublicKey), plugin, signature); err == nil { return nil } - publicKeys := s.platform.Config().PluginSettings.SignaturePublicKeyFiles + publicKeys := ch.cfgSvc.Config().PluginSettings.SignaturePublicKeyFiles for _, pk := range publicKeys { - pkBytes, appErr := s.platform.GetConfigFile(pk) + pkBytes, appErr := ch.srv.getPublicKey(pk) if appErr != nil { mlog.Warn("Unable to get public key for ", mlog.String("filename", pk)) continue diff --git a/app/plugin_statuses.go b/app/plugin_statuses.go index 2b27d7520c..399d58e5b2 100644 --- a/app/plugin_statuses.go +++ b/app/plugin_statuses.go @@ -10,8 +10,8 @@ import ( ) // GetPluginStatus returns the status for a plugin installed on this server. -func (s *PluginService) GetPluginStatus(id string) (*model.PluginStatus, *model.AppError) { - pluginsEnvironment := s.GetPluginsEnvironment() +func (ch *Channels) GetPluginStatus(id string) (*model.PluginStatus, *model.AppError) { + pluginsEnvironment := ch.GetPluginsEnvironment() if pluginsEnvironment == nil { return nil, model.NewAppError("GetPluginStatus", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented) } @@ -24,8 +24,8 @@ func (s *PluginService) GetPluginStatus(id string) (*model.PluginStatus, *model. for _, status := range pluginStatuses { if status.PluginId == id { // Add our cluster ID - if s.platform.Cluster() != nil { - status.ClusterId = s.platform.Cluster().GetClusterId() + if ch.srv.platform.Cluster() != nil { + status.ClusterId = ch.srv.platform.Cluster().GetClusterId() } return status, nil @@ -37,12 +37,12 @@ func (s *PluginService) GetPluginStatus(id string) (*model.PluginStatus, *model. // GetPluginStatus returns the status for a plugin installed on this server. func (a *App) GetPluginStatus(id string) (*model.PluginStatus, *model.AppError) { - return a.ch.srv.pluginService.GetPluginStatus(id) + return a.ch.GetPluginStatus(id) } // GetPluginStatuses returns the status for plugins installed on this server. -func (s *PluginService) GetPluginStatuses() (model.PluginStatuses, *model.AppError) { - pluginsEnvironment := s.GetPluginsEnvironment() +func (ch *Channels) GetPluginStatuses() (model.PluginStatuses, *model.AppError) { + pluginsEnvironment := ch.GetPluginsEnvironment() if pluginsEnvironment == nil { return nil, model.NewAppError("GetPluginStatuses", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented) } @@ -54,8 +54,8 @@ func (s *PluginService) GetPluginStatuses() (model.PluginStatuses, *model.AppErr // Add our cluster ID for _, status := range pluginStatuses { - if s.platform.Cluster() != nil { - status.ClusterId = s.platform.Cluster().GetClusterId() + if ch.srv.platform.Cluster() != nil { + status.ClusterId = ch.srv.platform.Cluster().GetClusterId() } else { status.ClusterId = "" } @@ -66,22 +66,22 @@ func (s *PluginService) GetPluginStatuses() (model.PluginStatuses, *model.AppErr // GetPluginStatuses returns the status for plugins installed on this server. func (a *App) GetPluginStatuses() (model.PluginStatuses, *model.AppError) { - return a.ch.srv.pluginService.GetPluginStatuses() + return a.ch.GetPluginStatuses() } // GetClusterPluginStatuses returns the status for plugins installed anywhere in the cluster. func (a *App) GetClusterPluginStatuses() (model.PluginStatuses, *model.AppError) { - return a.ch.srv.pluginService.getClusterPluginStatuses() + return a.ch.getClusterPluginStatuses() } -func (s *PluginService) getClusterPluginStatuses() (model.PluginStatuses, *model.AppError) { - pluginStatuses, err := s.GetPluginStatuses() +func (ch *Channels) getClusterPluginStatuses() (model.PluginStatuses, *model.AppError) { + pluginStatuses, err := ch.GetPluginStatuses() if err != nil { return nil, err } - if s.platform.Cluster() != nil && *s.platform.Config().ClusterSettings.Enable { - clusterPluginStatuses, err := s.platform.Cluster().GetPluginStatuses() + if ch.srv.platform.Cluster() != nil && *ch.cfgSvc.Config().ClusterSettings.Enable { + clusterPluginStatuses, err := ch.srv.platform.Cluster().GetPluginStatuses() if err != nil { return nil, model.NewAppError("GetClusterPluginStatuses", "app.plugin.get_cluster_plugin_statuses.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } @@ -92,8 +92,8 @@ func (s *PluginService) getClusterPluginStatuses() (model.PluginStatuses, *model return pluginStatuses, nil } -func (s *PluginService) notifyPluginStatusesChanged() error { - pluginStatuses, err := s.getClusterPluginStatuses() +func (ch *Channels) notifyPluginStatusesChanged() error { + pluginStatuses, err := ch.getClusterPluginStatuses() if err != nil { return err } @@ -102,7 +102,7 @@ func (s *PluginService) notifyPluginStatusesChanged() error { message := model.NewWebSocketEvent(model.WebsocketEventPluginStatusesChanged, "", "", "", nil, "") message.Add("plugin_statuses", pluginStatuses) message.GetBroadcast().ContainsSensitiveData = true - s.platform.Publish(message) + ch.srv.platform.Publish(message) return nil } diff --git a/app/plugin_test.go b/app/plugin_test.go index 0d3ec65431..57802c67ac 100644 --- a/app/plugin_test.go +++ b/app/plugin_test.go @@ -346,7 +346,7 @@ func TestServePluginRequest(t *testing.T) { w := httptest.NewRecorder() r := httptest.NewRequest("GET", "/plugins/foo/bar", nil) - th.App.PluginService().ServePluginRequest(w, r) + th.App.ch.ServePluginRequest(w, r) assert.Equal(t, http.StatusNotImplemented, w.Result().StatusCode) } @@ -390,7 +390,7 @@ func TestPrivateServePluginRequest(t *testing.T) { request = mux.SetURLVars(request, map[string]string{"plugin_id": "id"}) - th.App.PluginService().servePluginRequest(recorder, request, handler) + th.App.ch.servePluginRequest(recorder, request, handler) }) } @@ -413,7 +413,7 @@ func TestHandlePluginRequest(t *testing.T) { var assertions func(*http.Request) router := mux.NewRouter() router.HandleFunc("/plugins/{plugin_id:[A-Za-z0-9\\_\\-\\.]+}/{anything:.*}", func(_ http.ResponseWriter, r *http.Request) { - th.App.PluginService().servePluginRequest(nil, r, func(_ *plugin.Context, _ http.ResponseWriter, r *http.Request) { + th.App.ch.servePluginRequest(nil, r, func(_ *plugin.Context, _ http.ResponseWriter, r *http.Request) { assertions(r) }) }) @@ -625,7 +625,7 @@ func TestPluginSync(t *testing.T) { appErr = th.App.DeletePublicKey("pub_key") checkNoError(t, appErr) - appErr = th.App.PluginService().RemovePlugin("testplugin") + appErr = th.App.ch.RemovePlugin("testplugin") checkNoError(t, appErr) }) }) @@ -642,7 +642,7 @@ func TestChannelsPluginsInit(t *testing.T) { path, _ := fileutils.FindDir("tests") require.NotPanics(t, func() { - th.Server.pluginService.initPlugins(ctx, path, path) + th.Server.Channels().initPlugins(ctx, path, path) }) } @@ -763,7 +763,7 @@ func TestPluginPanicLogs(t *testing.T) { th.TestLogger.Flush() // We shutdown plugins first so that the read on the log buffer is race-free. - th.App.PluginService().ShutDownPlugins() + th.App.ch.ShutDownPlugins() tearDown() testlib.AssertLog(t, th.LogBuffer, mlog.LvlDebug.Name, "panic: some text from panic") @@ -831,7 +831,7 @@ func TestProcessPrepackagedPlugins(t *testing.T) { require.NoError(t, err) require.NotNil(t, pluginBytes) - manifest, appErr := th.App.PluginService().installPluginLocally(bytes.NewReader(pluginBytes), nil, installPluginLocallyAlways) + manifest, appErr := th.App.ch.installPluginLocally(bytes.NewReader(pluginBytes), nil, installPluginLocallyAlways) require.Nil(t, appErr) require.Equal(t, "testplugin", manifest.Id) @@ -848,7 +848,7 @@ func TestProcessPrepackagedPlugins(t *testing.T) { *cfg.PluginSettings.EnableRemoteMarketplace = false }) - plugins := th.App.PluginService().processPrepackagedPlugins(prepackagedPluginsDir) + plugins := th.App.ch.processPrepackagedPlugins(prepackagedPluginsDir) require.Len(t, plugins, 1) require.Equal(t, plugins[0].Manifest.Id, "testplugin") require.Empty(t, plugins[0].Signature, 0) @@ -858,7 +858,7 @@ func TestProcessPrepackagedPlugins(t *testing.T) { require.Len(t, pluginStatus, 1) require.Equal(t, pluginStatus[0].PluginId, "testplugin") - appErr = th.App.PluginService().RemovePlugin("testplugin") + appErr = th.App.ch.RemovePlugin("testplugin") checkNoError(t, appErr) pluginStatus, err = env.Statuses() @@ -875,7 +875,7 @@ func TestProcessPrepackagedPlugins(t *testing.T) { env := th.App.GetPluginsEnvironment() - plugins := th.App.PluginService().processPrepackagedPlugins(prepackagedPluginsDir) + plugins := th.App.ch.processPrepackagedPlugins(prepackagedPluginsDir) require.Len(t, plugins, 1) require.Equal(t, plugins[0].Manifest.Id, "testplugin") require.Empty(t, plugins[0].Signature, 0) @@ -908,7 +908,7 @@ func TestProcessPrepackagedPlugins(t *testing.T) { err = testlib.CopyFile(testPlugin2SignaturePath, filepath.Join(prepackagedPluginsDir, "testplugin2.tar.gz.sig")) require.NoError(t, err) - plugins := th.App.PluginService().processPrepackagedPlugins(prepackagedPluginsDir) + plugins := th.App.ch.processPrepackagedPlugins(prepackagedPluginsDir) require.Len(t, plugins, 2) require.Contains(t, []string{"testplugin", "testplugin2"}, plugins[0].Manifest.Id) require.NotEmpty(t, plugins[0].Signature) @@ -939,7 +939,7 @@ func TestProcessPrepackagedPlugins(t *testing.T) { require.NoError(t, err) require.NotNil(t, pluginBytes) - manifest, appErr := th.App.PluginService().installPluginLocally(bytes.NewReader(pluginBytes), nil, installPluginLocallyAlways) + manifest, appErr := th.App.ch.installPluginLocally(bytes.NewReader(pluginBytes), nil, installPluginLocallyAlways) require.Nil(t, appErr) require.Equal(t, "testplugin", manifest.Id) @@ -957,7 +957,7 @@ func TestProcessPrepackagedPlugins(t *testing.T) { err = testlib.CopyFile(testPlugin2SignaturePath, filepath.Join(prepackagedPluginsDir, "testplugin2.tar.gz.sig")) require.NoError(t, err) - plugins := th.App.PluginService().processPrepackagedPlugins(prepackagedPluginsDir) + plugins := th.App.ch.processPrepackagedPlugins(prepackagedPluginsDir) require.Len(t, plugins, 2) require.Contains(t, []string{"testplugin", "testplugin2"}, plugins[0].Manifest.Id) require.NotEmpty(t, plugins[0].Signature) @@ -969,7 +969,7 @@ func TestProcessPrepackagedPlugins(t *testing.T) { require.Len(t, pluginStatus, 1) require.Equal(t, pluginStatus[0].PluginId, "testplugin") - appErr = th.App.PluginService().RemovePlugin("testplugin") + appErr = th.App.ch.RemovePlugin("testplugin") checkNoError(t, appErr) pluginStatus, err = env.Statuses() @@ -994,7 +994,7 @@ func TestProcessPrepackagedPlugins(t *testing.T) { err = testlib.CopyFile(testPlugin2SignaturePath, filepath.Join(prepackagedPluginsDir, "testplugin2.tar.gz.sig")) require.NoError(t, err) - plugins := th.App.PluginService().processPrepackagedPlugins(prepackagedPluginsDir) + plugins := th.App.ch.processPrepackagedPlugins(prepackagedPluginsDir) require.Len(t, plugins, 2) require.Contains(t, []string{"testplugin", "testplugin2"}, plugins[0].Manifest.Id) require.NotEmpty(t, plugins[0].Signature) @@ -1071,14 +1071,14 @@ func TestGetPluginStateOverride(t *testing.T) { defer th.TearDown() t.Run("no override", func(t *testing.T) { - overrides, value := th.App.PluginService().getPluginStateOverride("focalboard") + overrides, value := th.App.ch.getPluginStateOverride("focalboard") require.False(t, overrides) require.False(t, value) }) t.Run("calls override", func(t *testing.T) { t.Run("on-prem", func(t *testing.T) { - overrides, value := th.App.PluginService().getPluginStateOverride("com.mattermost.calls") + overrides, value := th.App.ch.getPluginStateOverride("com.mattermost.calls") require.False(t, overrides) require.False(t, value) }) @@ -1086,7 +1086,7 @@ func TestGetPluginStateOverride(t *testing.T) { t.Run("Cloud, without enabled flag", func(t *testing.T) { os.Setenv("MM_CLOUD_INSTALLATION_ID", "test") defer os.Unsetenv("MM_CLOUD_INSTALLATION_ID") - overrides, value := th.App.PluginService().getPluginStateOverride("com.mattermost.calls") + overrides, value := th.App.ch.getPluginStateOverride("com.mattermost.calls") require.False(t, overrides) require.False(t, value) }) @@ -1100,7 +1100,7 @@ func TestGetPluginStateOverride(t *testing.T) { th2 := Setup(t) defer th2.TearDown() - overrides, value := th2.App.PluginService().getPluginStateOverride("com.mattermost.calls") + overrides, value := th2.App.ch.getPluginStateOverride("com.mattermost.calls") require.False(t, overrides) require.False(t, value) }) @@ -1114,7 +1114,7 @@ func TestGetPluginStateOverride(t *testing.T) { th2 := Setup(t) defer th2.TearDown() - overrides, value := th2.App.PluginService().getPluginStateOverride("com.mattermost.calls") + overrides, value := th2.App.ch.getPluginStateOverride("com.mattermost.calls") require.True(t, overrides) require.False(t, value) }) @@ -1126,7 +1126,7 @@ func TestGetPluginStateOverride(t *testing.T) { th2 := Setup(t) defer th2.TearDown() - overrides, value := th2.App.PluginService().getPluginStateOverride("com.mattermost.calls") + overrides, value := th2.App.ch.getPluginStateOverride("com.mattermost.calls") require.True(t, overrides) require.False(t, value) }) @@ -1134,7 +1134,7 @@ func TestGetPluginStateOverride(t *testing.T) { t.Run("apps override", func(t *testing.T) { t.Run("without enabled flag", func(t *testing.T) { - overrides, value := th.App.PluginService().getPluginStateOverride("com.mattermost.apps") + overrides, value := th.App.ch.getPluginStateOverride("com.mattermost.apps") require.False(t, overrides) require.False(t, value) }) @@ -1146,7 +1146,7 @@ func TestGetPluginStateOverride(t *testing.T) { th2 := Setup(t) defer th2.TearDown() - overrides, value := th2.App.PluginService().getPluginStateOverride("com.mattermost.apps") + overrides, value := th2.App.ch.getPluginStateOverride("com.mattermost.apps") require.True(t, overrides) require.False(t, value) }) diff --git a/app/post.go b/app/post.go index b98cb13759..095494d080 100644 --- a/app/post.go +++ b/app/post.go @@ -269,7 +269,7 @@ func (a *App) CreatePost(c request.CTX, post *model.Post, channel *model.Channel } var rejectionError *model.AppError pluginContext := pluginContext(c) - a.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { + a.ch.RunMultiHook(func(hooks plugin.Hooks) bool { replacementPost, rejectionReason := hooks.MessageWillBePosted(pluginContext, post.ForPlugin()) if rejectionReason != "" { id := "Post rejected by plugin. " + rejectionReason @@ -328,7 +328,7 @@ func (a *App) CreatePost(c request.CTX, post *model.Post, channel *model.Channel // and to remove the non-GOB-encodable Metadata from it. pluginPost := rpost.ForPlugin() a.Srv().Go(func() { - a.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { + a.ch.RunMultiHook(func(hooks plugin.Hooks) bool { hooks.MessageHasBeenPosted(pluginContext, pluginPost) return true }, plugin.MessageHasBeenPostedID) @@ -655,7 +655,7 @@ func (a *App) UpdatePost(c *request.Context, post *model.Post, safeUpdate bool) var rejectionReason string pluginContext := pluginContext(c) - a.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { + a.ch.RunMultiHook(func(hooks plugin.Hooks) bool { newPost, rejectionReason = hooks.MessageWillBeUpdated(pluginContext, newPost.ForPlugin(), oldPost.ForPlugin()) return post != nil }, plugin.MessageWillBeUpdatedID) @@ -680,7 +680,7 @@ func (a *App) UpdatePost(c *request.Context, post *model.Post, safeUpdate bool) pluginOldPost := oldPost.ForPlugin() pluginNewPost := newPost.ForPlugin() a.Srv().Go(func() { - a.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { + a.ch.RunMultiHook(func(hooks plugin.Hooks) bool { hooks.MessageHasBeenUpdated(pluginContext, pluginNewPost, pluginOldPost) return true }, plugin.MessageHasBeenUpdatedID) diff --git a/app/reaction.go b/app/reaction.go index c79036b443..fc6d54699f 100644 --- a/app/reaction.go +++ b/app/reaction.go @@ -45,7 +45,7 @@ func (a *App) SaveReactionForPost(c *request.Context, reaction *model.Reaction) pluginContext := pluginContext(c) a.Srv().Go(func() { - a.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { + a.ch.RunMultiHook(func(hooks plugin.Hooks) bool { hooks.ReactionHasBeenAdded(pluginContext, reaction) return true }, plugin.ReactionHasBeenAddedID) @@ -142,7 +142,7 @@ func (a *App) DeleteReactionForPost(c *request.Context, reaction *model.Reaction pluginContext := pluginContext(c) a.Srv().Go(func() { - a.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { + a.ch.RunMultiHook(func(hooks plugin.Hooks) bool { hooks.ReactionHasBeenRemoved(pluginContext, reaction) return true }, plugin.ReactionHasBeenRemovedID) diff --git a/app/server.go b/app/server.go index 22f9d24c1c..fbd433a6db 100644 --- a/app/server.go +++ b/app/server.go @@ -119,7 +119,6 @@ type Server struct { telemetryService *telemetry.TelemetryService userService *users.UserService teamService *teams.TeamService - pluginService *PluginService serviceMux sync.RWMutex remoteClusterService remotecluster.RemoteClusterServiceIFace @@ -719,10 +718,6 @@ func (s *Server) Shutdown() { } } - // Stop the plugin service, we need to stop plugin service before stopping the - // product as products are being consumed by this service. - s.pluginService.ShutDownPlugins() - // Stop products. // This needs to happen last because products are dependent // on parent services. @@ -829,18 +824,11 @@ func stripPort(hostport string) string { func (s *Server) Start() error { // Start products. // This needs to happen before because products are dependent on the HTTP server. + // make sure channels starts first if err := s.products["channels"].Start(); err != nil { return errors.Wrap(err, "Unable to start channels") } - - // This should actually be started after products, but we have a product hooks - // dependency for now, once that get sorted out, this should be moved to the appropriate - // order. - if err := s.InitializePluginService(); err != nil { - return errors.Wrap(err, "Unable to start plugin service") - } - for name, product := range s.products { if name == "channels" { continue diff --git a/app/team.go b/app/team.go index 5724f683c2..222d10e442 100644 --- a/app/team.go +++ b/app/team.go @@ -853,7 +853,7 @@ func (a *App) JoinUserToTeam(c request.CTX, team *model.Team, user *model.User, a.Srv().Go(func() { pluginContext := pluginContext(c) - a.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { + a.ch.RunMultiHook(func(hooks plugin.Hooks) bool { hooks.UserHasJoinedTeam(pluginContext, teamMember, actor) return true }, plugin.UserHasJoinedTeamID) @@ -1225,7 +1225,7 @@ func (a *App) postProcessTeamMemberLeave(c request.CTX, teamMember *model.TeamMe a.Srv().Go(func() { pluginContext := pluginContext(c) - a.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { + a.ch.RunMultiHook(func(hooks plugin.Hooks) bool { hooks.UserHasLeftTeam(pluginContext, teamMember, actor) return true }, plugin.UserHasLeftTeamID) diff --git a/app/upload.go b/app/upload.go index b60f9be57b..318e3ede89 100644 --- a/app/upload.go +++ b/app/upload.go @@ -62,7 +62,7 @@ func (a *App) runPluginsHook(c request.CTX, info *model.FileInfo, file io.Reader var rejErr *model.AppError var once sync.Once pluginContext := pluginContext(c) - a.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { + a.ch.RunMultiHook(func(hooks plugin.Hooks) bool { once.Do(func() { hookHasRunCh <- struct{}{} }) diff --git a/app/user.go b/app/user.go index 81faa5b57d..6e12bda34b 100644 --- a/app/user.go +++ b/app/user.go @@ -310,7 +310,7 @@ func (a *App) createUserOrGuest(c request.CTX, user *model.User, guest bool) (*m pluginContext := pluginContext(c) a.Srv().Go(func() { - a.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { + a.ch.RunMultiHook(func(hooks plugin.Hooks) bool { hooks.UserHasBeenCreated(pluginContext, ruser) return true }, plugin.UserHasBeenCreatedID) diff --git a/app/web_conn.go b/app/web_conn.go index 1db5866e0a..cdf59eb31e 100644 --- a/app/web_conn.go +++ b/app/web_conn.go @@ -16,5 +16,5 @@ func (a *App) PopulateWebConnConfig(s *model.Session, cfg *platform.WebConnConfi // NewWebConn returns a new WebConn instance. func (a *App) NewWebConn(cfg *platform.WebConnConfig) *platform.WebConn { - return a.Srv().Platform().NewWebConn(cfg, a, a.Srv()) + return a.Srv().Platform().NewWebConn(cfg, a, a.ch) } diff --git a/cmd/mattermost/commands/init.go b/cmd/mattermost/commands/init.go index a90b4b02b3..e93d9640fe 100644 --- a/cmd/mattermost/commands/init.go +++ b/cmd/mattermost/commands/init.go @@ -7,6 +7,7 @@ import ( "github.com/spf13/cobra" "github.com/mattermost/mattermost-server/v6/app" + "github.com/mattermost/mattermost-server/v6/app/request" "github.com/mattermost/mattermost-server/v6/config" "github.com/mattermost/mattermost-server/v6/model" "github.com/mattermost/mattermost-server/v6/shared/i18n" @@ -20,11 +21,7 @@ func initDBCommandContextCobra(command *cobra.Command, readOnlyConfigStore bool) panic(err) } - err = a.Srv().InitializePluginService() - if err != nil { - return nil, err - } - + a.InitPlugins(request.EmptyContext(a.Log()), *a.Config().PluginSettings.Directory, *a.Config().PluginSettings.ClientDirectory) a.DoAppMigrations() return a, nil diff --git a/web/web_test.go b/web/web_test.go index b57e3888bd..4214649031 100644 --- a/web/web_test.go +++ b/web/web_test.go @@ -282,7 +282,7 @@ func TestPublicFilesRequest(t *testing.T) { defer os.RemoveAll(pluginDir) defer os.RemoveAll(webappPluginDir) - env, err := plugin.NewEnvironment(th.NewPluginAPI, app.NewDriverImpl(th.Server.Platform()), pluginDir, webappPluginDir, false, th.App.Log(), nil) + env, err := plugin.NewEnvironment(th.NewPluginAPI, app.NewDriverImpl(th.Server), pluginDir, webappPluginDir, false, th.App.Log(), nil) require.NoError(t, err) pluginID := "com.mattermost.sample" @@ -329,7 +329,7 @@ func TestPublicFilesRequest(t *testing.T) { require.NotNil(t, manifest) require.True(t, activated) - th.App.PluginService().SetPluginsEnvironment(env) + th.App.Channels().SetPluginsEnvironment(env) req, _ := http.NewRequest("GET", "/plugins/com.mattermost.sample/public/hello.html", nil) res := httptest.NewRecorder() From 37960c7680f57c9fd8f0717b99d0fd8a6664db7c Mon Sep 17 00:00:00 2001 From: Kyriakos Z <3829551+koox00@users.noreply.github.com> Date: Thu, 22 Dec 2022 16:15:11 +0200 Subject: [PATCH 15/37] MM-49077: fixes errors in migration job (#21911) * MM-49077: fixes errors in migration job UrgentMentionCount does not have a default values, so COALESCE has to be used. SELECT * FROM ChannelMembers fails for the above reason. This commit SELECTs each and every column from ChannelMembers instead. * Fixes syntax error Co-authored-by: Mattermod --- store/sqlstore/channel_store.go | 52 +++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/store/sqlstore/channel_store.go b/store/sqlstore/channel_store.go index 85864f3784..ca7575924a 100644 --- a/store/sqlstore/channel_store.go +++ b/store/sqlstore/channel_store.go @@ -3850,7 +3850,31 @@ func (s SqlChannelStore) MigrateChannelMembers(fromChannelId string, fromUserId defer finalizeTransactionX(transaction, &err) channelMembers := []channelMember{} - if err := transaction.Select(&channelMembers, "SELECT * from ChannelMembers WHERE (ChannelId, UserId) > (?, ?) ORDER BY ChannelId, UserId LIMIT 100", fromChannelId, fromUserId); err != nil { + query := ` + SELECT + ChannelId, + UserId, + Roles, + LastViewedAt, + MsgCount, + MentionCount, + MentionCountRoot, + COALESCE(UrgentMentionCount, 0) AS UrgentMentionCount, + MsgCountRoot, + NotifyProps, + LastUpdateAt, + SchemeUser, + SchemeAdmin, + SchemeGuest + FROM + ChannelMembers + WHERE + (ChannelId, UserId) > (?, ?) + ORDER BY ChannelId, UserId + LIMIT 100 + ` + + if err := transaction.Select(&channelMembers, query, fromChannelId, fromUserId); err != nil { return nil, errors.Wrap(err, "failed to find ChannelMembers") } @@ -3954,7 +3978,31 @@ func (s SqlChannelStore) ClearAllCustomRoleAssignments() (err error) { } channelMembers := []*channelMember{} - if err = transaction.Select(&channelMembers, "SELECT * from ChannelMembers WHERE (ChannelId, UserId) > (?, ?) ORDER BY ChannelId, UserId LIMIT 1000", lastChannelId, lastUserId); err != nil { + query := ` + SELECT + ChannelId, + UserId, + Roles, + LastViewedAt, + MsgCount, + MentionCount, + MentionCountRoot, + COALESCE(UrgentMentionCount, 0) AS UrgentMentionCount, + MsgCountRoot, + NotifyProps, + LastUpdateAt, + SchemeUser, + SchemeAdmin, + SchemeGuest + FROM + ChannelMembers + WHERE + (ChannelId, UserId) > (?, ?) + ORDER BY ChannelId, UserId + LIMIT 1000 + ` + + if err = transaction.Select(&channelMembers, query, lastChannelId, lastUserId); err != nil { finalizeTransactionX(transaction, &err) return errors.Wrap(err, "failed to find ChannelMembers") } From f8b6b6b0edd9c0f2e78f677fdfc6e6be2eb02cd5 Mon Sep 17 00:00:00 2001 From: Nathaniel Allred Date: Thu, 22 Dec 2022 11:32:23 -0600 Subject: [PATCH 16/37] always set CWSURL in client config (#21940) --- config/client.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/config/client.go b/config/client.go index b8160993fd..e26c2bea40 100644 --- a/config/client.go +++ b/config/client.go @@ -92,6 +92,8 @@ func GenerateClientConfig(c *model.Config, telemetryID string, license *model.Li props["EnableEmailInvitations"] = strconv.FormatBool(*c.ServiceSettings.EnableEmailInvitations) + props["CWSURL"] = *c.CloudSettings.CWSURL + // Set default values for all options that require a license. props["ExperimentalEnableAuthenticationTransfer"] = "true" props["LdapNicknameAttributeSet"] = "false" @@ -123,7 +125,6 @@ func GenerateClientConfig(c *model.Config, telemetryID string, license *model.Li props["DataRetentionFileRetentionDays"] = "0" props["DataRetentionEnableBoardsDeletion"] = "false" props["DataRetentionBoardsRetentionDays"] = "0" - props["CWSURL"] = "" props["CustomUrlSchemes"] = strings.Join(c.DisplaySettings.CustomURLSchemes, ",") props["IsDefaultMarketplace"] = strconv.FormatBool(*c.PluginSettings.MarketplaceURL == model.PluginSettingsDefaultMarketplaceURL) @@ -195,10 +196,6 @@ func GenerateClientConfig(c *model.Config, telemetryID string, license *model.Li props["DataRetentionBoardsRetentionDays"] = strconv.FormatInt(int64(*c.DataRetentionSettings.BoardsRetentionDays), 10) } - if license.IsCloud() { - props["CWSURL"] = *c.CloudSettings.CWSURL - } - if *license.Features.SharedChannels { props["ExperimentalSharedChannels"] = strconv.FormatBool(*c.ExperimentalSettings.EnableSharedChannels) props["ExperimentalRemoteClusterService"] = strconv.FormatBool(c.FeatureFlags.EnableRemoteClusterService && *c.ExperimentalSettings.EnableRemoteClusterService) From 7a8b09c242cc164fe0428e748b6458228c404e9a Mon Sep 17 00:00:00 2001 From: emmyni <44761757+emmyni@users.noreply.github.com> Date: Thu, 22 Dec 2022 15:33:04 -0500 Subject: [PATCH 17/37] Add comments for the subscriptionHistoryEvent call (#21946) --- app/user.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/user.go b/app/user.go index 6e12bda34b..d259439859 100644 --- a/app/user.go +++ b/app/user.go @@ -316,7 +316,11 @@ func (a *App) createUserOrGuest(c request.CTX, user *model.User, guest bool) (*m }, plugin.UserHasBeenCreatedID) }) - // Create/Update the subscriptionHistoryEvent + // For cloud yearly subscriptions, if the current user count of the workspace exceeds the number of seats initially purchased + // (plus the “threshold” of 10%), then a subscriptionHistoryEvent object would need to be created and added to the subscriptionHistory + // table in CWS. This is then used to calculate how much the customers have to pay in addition for the extra users. If the + // workspace is currently on a monthly plan, then this function will not do anything. + go func() { _, err := a.SendSubscriptionHistoryEvent(ruser.Id) if err != nil { From 06745d9b61941ad5e14465395b48e30b332747c6 Mon Sep 17 00:00:00 2001 From: Ibrahim Serdar Acikgoz Date: Fri, 23 Dec 2022 09:45:42 +0300 Subject: [PATCH 18/37] [MM-48878] cmd/mattermost/export: start export command without metrics (#21857) --- cmd/mattermost/commands/db.go | 3 ++- cmd/mattermost/commands/export.go | 7 ++++--- cmd/mattermost/commands/init.go | 22 ++++++++-------------- cmd/mattermost/commands/jobserver.go | 3 ++- cmd/mattermost/commands/test.go | 5 +++-- 5 files changed, 19 insertions(+), 21 deletions(-) diff --git a/cmd/mattermost/commands/db.go b/cmd/mattermost/commands/db.go index db33032297..c0d186c265 100644 --- a/cmd/mattermost/commands/db.go +++ b/cmd/mattermost/commands/db.go @@ -10,6 +10,7 @@ import ( "github.com/pkg/errors" "github.com/spf13/cobra" + "github.com/mattermost/mattermost-server/v6/app" "github.com/mattermost/mattermost-server/v6/audit" "github.com/mattermost/mattermost-server/v6/config" "github.com/mattermost/mattermost-server/v6/store/sqlstore" @@ -100,7 +101,7 @@ func initDbCmdF(command *cobra.Command, _ []string) error { } func resetCmdF(command *cobra.Command, args []string) error { - a, err := InitDBCommandContextCobra(command) + a, err := InitDBCommandContextCobra(command, app.SkipPostInitialization()) if err != nil { return err } diff --git a/cmd/mattermost/commands/export.go b/cmd/mattermost/commands/export.go index fa0f251e1b..d5573ee975 100644 --- a/cmd/mattermost/commands/export.go +++ b/cmd/mattermost/commands/export.go @@ -10,6 +10,7 @@ import ( "path/filepath" "time" + "github.com/mattermost/mattermost-server/v6/app" "github.com/mattermost/mattermost-server/v6/app/request" "github.com/mattermost/mattermost-server/v6/audit" "github.com/mattermost/mattermost-server/v6/model" @@ -93,7 +94,7 @@ func init() { } func scheduleExportCmdF(command *cobra.Command, args []string) error { - a, err := InitDBCommandContextCobra(command) + a, err := InitDBCommandContextCobra(command, app.SkipPostInitialization()) if err != nil { return err } @@ -153,7 +154,7 @@ func scheduleExportCmdF(command *cobra.Command, args []string) error { func buildExportCmdF(format string) func(command *cobra.Command, args []string) error { return func(command *cobra.Command, args []string) error { - a, err := InitDBCommandContextCobra(command) + a, err := InitDBCommandContextCobra(command, app.SkipPostInitialization()) license := a.Srv().License() if err != nil { return err @@ -201,7 +202,7 @@ func buildExportCmdF(format string) func(command *cobra.Command, args []string) } func bulkExportCmdF(command *cobra.Command, args []string) error { - a, err := InitDBCommandContextCobra(command) + a, err := InitDBCommandContextCobra(command, app.SkipPostInitialization()) if err != nil { return err } diff --git a/cmd/mattermost/commands/init.go b/cmd/mattermost/commands/init.go index e93d9640fe..1c7e8baf07 100644 --- a/cmd/mattermost/commands/init.go +++ b/cmd/mattermost/commands/init.go @@ -14,8 +14,8 @@ import ( "github.com/mattermost/mattermost-server/v6/utils" ) -func initDBCommandContextCobra(command *cobra.Command, readOnlyConfigStore bool) (*app.App, error) { - a, err := initDBCommandContext(getConfigDSN(command, config.GetEnvironment()), readOnlyConfigStore) +func initDBCommandContextCobra(command *cobra.Command, readOnlyConfigStore bool, options ...app.Option) (*app.App, error) { + a, err := initDBCommandContext(getConfigDSN(command, config.GetEnvironment()), readOnlyConfigStore, options...) if err != nil { // Returning an error just prints the usage message, so actually panic panic(err) @@ -27,25 +27,19 @@ func initDBCommandContextCobra(command *cobra.Command, readOnlyConfigStore bool) return a, nil } -func InitDBCommandContextCobra(command *cobra.Command) (*app.App, error) { - return initDBCommandContextCobra(command, true) +func InitDBCommandContextCobra(command *cobra.Command, options ...app.Option) (*app.App, error) { + return initDBCommandContextCobra(command, true, options...) } -func InitDBCommandContextCobraReadWrite(command *cobra.Command) (*app.App, error) { - return initDBCommandContextCobra(command, false) -} - -func initDBCommandContext(configDSN string, readOnlyConfigStore bool) (*app.App, error) { +func initDBCommandContext(configDSN string, readOnlyConfigStore bool, options ...app.Option) (*app.App, error) { if err := utils.TranslationsPreInit(); err != nil { return nil, err } model.AppErrorInit(i18n.T) - s, err := app.NewServer( - // The option order is important as app.Config option reads app.StartMetrics option. - app.StartMetrics, - app.Config(configDSN, readOnlyConfigStore, nil), - ) + // The option order is important as app.Config option reads app.StartMetrics option. + options = append(options, app.Config(configDSN, readOnlyConfigStore, nil)) + s, err := app.NewServer(options...) if err != nil { return nil, err } diff --git a/cmd/mattermost/commands/jobserver.go b/cmd/mattermost/commands/jobserver.go index 488b3f8fe7..be59971aab 100644 --- a/cmd/mattermost/commands/jobserver.go +++ b/cmd/mattermost/commands/jobserver.go @@ -10,6 +10,7 @@ import ( "github.com/spf13/cobra" + "github.com/mattermost/mattermost-server/v6/app" "github.com/mattermost/mattermost-server/v6/audit" "github.com/mattermost/mattermost-server/v6/config" "github.com/mattermost/mattermost-server/v6/shared/mlog" @@ -34,7 +35,7 @@ func jobserverCmdF(command *cobra.Command, args []string) error { noSchedule, _ := command.Flags().GetBool("noschedule") // Initialize - a, err := initDBCommandContext(getConfigDSN(command, config.GetEnvironment()), false) + a, err := initDBCommandContext(getConfigDSN(command, config.GetEnvironment()), false, app.StartMetrics) if err != nil { return err } diff --git a/cmd/mattermost/commands/test.go b/cmd/mattermost/commands/test.go index 75c49a4ee7..1a50d53221 100644 --- a/cmd/mattermost/commands/test.go +++ b/cmd/mattermost/commands/test.go @@ -14,6 +14,7 @@ import ( "github.com/spf13/cobra" "github.com/mattermost/mattermost-server/v6/api4" + "github.com/mattermost/mattermost-server/v6/app" "github.com/mattermost/mattermost-server/v6/model" "github.com/mattermost/mattermost-server/v6/shared/i18n" "github.com/mattermost/mattermost-server/v6/wsapi" @@ -46,7 +47,7 @@ func init() { } func webClientTestsCmdF(command *cobra.Command, args []string) error { - a, err := InitDBCommandContextCobra(command) + a, err := InitDBCommandContextCobra(command, app.StartMetrics) if err != nil { return err } @@ -70,7 +71,7 @@ func webClientTestsCmdF(command *cobra.Command, args []string) error { } func serverForWebClientTestsCmdF(command *cobra.Command, args []string) error { - a, err := InitDBCommandContextCobra(command) + a, err := InitDBCommandContextCobra(command, app.StartMetrics) if err != nil { return err } From 40cd6b16561339ae9172a30c54990975e23d4ba4 Mon Sep 17 00:00:00 2001 From: Ibrahim Serdar Acikgoz Date: Fri, 23 Dec 2022 14:34:48 +0300 Subject: [PATCH 19/37] sqlstore/store: make SetupConnection public to be used by products (#21948) --- store/sqlstore/store.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/store/sqlstore/store.go b/store/sqlstore/store.go index 630e7705d3..28a86886cf 100644 --- a/store/sqlstore/store.go +++ b/store/sqlstore/store.go @@ -226,7 +226,9 @@ func New(settings model.SqlSettings, metrics einterfaces.MetricsInterface) *SqlS return store } -func setupConnection(connType string, dataSource string, settings *model.SqlSettings) *dbsql.DB { +// SetupConnection sets up the connection to the database and pings it to make sure it's alive. +// It also applies any database configuration settings that are required. +func SetupConnection(connType string, dataSource string, settings *model.SqlSettings) *dbsql.DB { db, err := dbsql.Open(*settings.DriverName, dataSource) if err != nil { mlog.Fatal("Failed to open SQL connection to err.", mlog.Err(err)) @@ -292,7 +294,7 @@ func (ss *SqlStore) initConnection() { } } - handle := setupConnection("master", dataSource, ss.settings) + handle := SetupConnection("master", dataSource, ss.settings) ss.masterX = newSqlxDBWrapper(sqlx.NewDb(handle, ss.DriverName()), time.Duration(*ss.settings.QueryTimeout)*time.Second, *ss.settings.Trace) @@ -303,7 +305,7 @@ func (ss *SqlStore) initConnection() { if len(ss.settings.DataSourceReplicas) > 0 { ss.ReplicaXs = make([]*sqlxDBWrapper, len(ss.settings.DataSourceReplicas)) for i, replica := range ss.settings.DataSourceReplicas { - handle := setupConnection(fmt.Sprintf("replica-%v", i), replica, ss.settings) + handle := SetupConnection(fmt.Sprintf("replica-%v", i), replica, ss.settings) ss.ReplicaXs[i] = newSqlxDBWrapper(sqlx.NewDb(handle, ss.DriverName()), time.Duration(*ss.settings.QueryTimeout)*time.Second, *ss.settings.Trace) @@ -316,7 +318,7 @@ func (ss *SqlStore) initConnection() { if len(ss.settings.DataSourceSearchReplicas) > 0 { ss.searchReplicaXs = make([]*sqlxDBWrapper, len(ss.settings.DataSourceSearchReplicas)) for i, replica := range ss.settings.DataSourceSearchReplicas { - handle := setupConnection(fmt.Sprintf("search-replica-%v", i), replica, ss.settings) + handle := SetupConnection(fmt.Sprintf("search-replica-%v", i), replica, ss.settings) ss.searchReplicaXs[i] = newSqlxDBWrapper(sqlx.NewDb(handle, ss.DriverName()), time.Duration(*ss.settings.QueryTimeout)*time.Second, *ss.settings.Trace) @@ -332,7 +334,7 @@ func (ss *SqlStore) initConnection() { if src.DataSource == nil { continue } - ss.replicaLagHandles[i] = setupConnection(fmt.Sprintf(replicaLagPrefix+"-%d", i), *src.DataSource, ss.settings) + ss.replicaLagHandles[i] = SetupConnection(fmt.Sprintf(replicaLagPrefix+"-%d", i), *src.DataSource, ss.settings) } } } @@ -1073,7 +1075,7 @@ func (ss *SqlStore) migrate(direction migrationDirection) error { if err != nil { return err } - db := setupConnection("master", dataSource, ss.settings) + db := SetupConnection("master", dataSource, ss.settings) driver, err = ms.WithInstance(db) defer db.Close() case model.DatabaseDriverPostgres: From 1e02b99bf10f6aa5147f5888e2c01f994b6a9c5a Mon Sep 17 00:00:00 2001 From: Agniva De Sarker Date: Fri, 23 Dec 2022 19:56:49 +0530 Subject: [PATCH 20/37] MM-49354: Make compliance export query cancellable (#21943) We accept a context that can cancel the query. This allows us to exit a job faster. ```release-note Compliance export job can now cancel the SQL query execution during server shutdown which will allow the job to exit faster. ``` Co-authored-by: Mattermod --- store/opentracinglayer/opentracinglayer.go | 4 +-- store/retrylayer/retrylayer.go | 4 +-- store/sqlstore/compliance_store.go | 5 +-- store/sqlstore/sqlx_wrapper.go | 6 +++- store/store.go | 2 +- store/storetest/compliance_store.go | 37 +++++++++++----------- store/storetest/mocks/ComplianceStore.go | 20 ++++++------ store/timerlayer/timerlayer.go | 4 +-- 8 files changed, 45 insertions(+), 37 deletions(-) diff --git a/store/opentracinglayer/opentracinglayer.go b/store/opentracinglayer/opentracinglayer.go index 87b443640a..129ef5254b 100644 --- a/store/opentracinglayer/opentracinglayer.go +++ b/store/opentracinglayer/opentracinglayer.go @@ -3197,7 +3197,7 @@ func (s *OpenTracingLayerComplianceStore) GetAll(offset int, limit int) (model.C return result, err } -func (s *OpenTracingLayerComplianceStore) MessageExport(cursor model.MessageExportCursor, limit int) ([]*model.MessageExport, model.MessageExportCursor, error) { +func (s *OpenTracingLayerComplianceStore) MessageExport(ctx context.Context, cursor model.MessageExportCursor, limit int) ([]*model.MessageExport, model.MessageExportCursor, error) { origCtx := s.Root.Store.Context() span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ComplianceStore.MessageExport") s.Root.Store.SetContext(newCtx) @@ -3206,7 +3206,7 @@ func (s *OpenTracingLayerComplianceStore) MessageExport(cursor model.MessageExpo }() defer span.Finish() - result, resultVar1, err := s.ComplianceStore.MessageExport(cursor, limit) + result, resultVar1, err := s.ComplianceStore.MessageExport(ctx, cursor, limit) if err != nil { span.LogFields(spanlog.Error(err)) ext.Error.Set(span, true) diff --git a/store/retrylayer/retrylayer.go b/store/retrylayer/retrylayer.go index 38a3f52dbf..065a1400a9 100644 --- a/store/retrylayer/retrylayer.go +++ b/store/retrylayer/retrylayer.go @@ -3567,11 +3567,11 @@ func (s *RetryLayerComplianceStore) GetAll(offset int, limit int) (model.Complia } -func (s *RetryLayerComplianceStore) MessageExport(cursor model.MessageExportCursor, limit int) ([]*model.MessageExport, model.MessageExportCursor, error) { +func (s *RetryLayerComplianceStore) MessageExport(ctx context.Context, cursor model.MessageExportCursor, limit int) ([]*model.MessageExport, model.MessageExportCursor, error) { tries := 0 for { - result, resultVar1, err := s.ComplianceStore.MessageExport(cursor, limit) + result, resultVar1, err := s.ComplianceStore.MessageExport(ctx, cursor, limit) if err == nil { return result, resultVar1, nil } diff --git a/store/sqlstore/compliance_store.go b/store/sqlstore/compliance_store.go index 6e2ae38ac1..9c76cea3d6 100644 --- a/store/sqlstore/compliance_store.go +++ b/store/sqlstore/compliance_store.go @@ -4,6 +4,7 @@ package sqlstore import ( + "context" "database/sql" "fmt" "strings" @@ -270,7 +271,7 @@ func (s SqlComplianceStore) ComplianceExport(job *model.Compliance, cursor model return append(channelPosts, directMessagePosts...), cursor, nil } -func (s SqlComplianceStore) MessageExport(cursor model.MessageExportCursor, limit int) ([]*model.MessageExport, model.MessageExportCursor, error) { +func (s SqlComplianceStore) MessageExport(ctx context.Context, cursor model.MessageExportCursor, limit int) ([]*model.MessageExport, model.MessageExportCursor, error) { var args []any args = append(args, model.ChannelTypeDirect, model.ChannelTypeGroup, cursor.LastPostUpdateAt, cursor.LastPostUpdateAt, cursor.LastPostId, limit) query := @@ -317,7 +318,7 @@ func (s SqlComplianceStore) MessageExport(cursor model.MessageExportCursor, limi LIMIT ?` cposts := []*model.MessageExport{} - if err := s.GetReplicaX().Select(&cposts, query, args...); err != nil { + if err := s.GetReplicaX().SelectCtx(ctx, &cposts, query, args...); err != nil { return nil, cursor, errors.Wrap(err, "unable to export messages") } if len(cposts) > 0 { diff --git a/store/sqlstore/sqlx_wrapper.go b/store/sqlstore/sqlx_wrapper.go index 95b274aae9..3d215ff8a5 100644 --- a/store/sqlstore/sqlx_wrapper.go +++ b/store/sqlstore/sqlx_wrapper.go @@ -224,8 +224,12 @@ func (w *sqlxDBWrapper) QueryX(query string, args ...any) (*sqlx.Rows, error) { } func (w *sqlxDBWrapper) Select(dest any, query string, args ...any) error { + return w.SelectCtx(context.Background(), dest, query, args...) +} + +func (w *sqlxDBWrapper) SelectCtx(ctx context.Context, dest any, query string, args ...any) error { query = w.DB.Rebind(query) - ctx, cancel := context.WithTimeout(context.Background(), w.queryTimeout) + ctx, cancel := context.WithTimeout(ctx, w.queryTimeout) defer cancel() if w.trace { diff --git a/store/store.go b/store/store.go index 49490afdbb..8a1c69d91a 100644 --- a/store/store.go +++ b/store/store.go @@ -549,7 +549,7 @@ type ComplianceStore interface { Get(id string) (*model.Compliance, error) GetAll(offset, limit int) (model.Compliances, error) ComplianceExport(compliance *model.Compliance, cursor model.ComplianceExportCursor, limit int) ([]*model.CompliancePost, model.ComplianceExportCursor, error) - MessageExport(cursor model.MessageExportCursor, limit int) ([]*model.MessageExport, model.MessageExportCursor, error) + MessageExport(ctx context.Context, cursor model.MessageExportCursor, limit int) ([]*model.MessageExport, model.MessageExportCursor, error) } type OAuthStore interface { diff --git a/store/storetest/compliance_store.go b/store/storetest/compliance_store.go index f39b1c43ef..7c2d106325 100644 --- a/store/storetest/compliance_store.go +++ b/store/storetest/compliance_store.go @@ -4,6 +4,7 @@ package storetest import ( + "context" "encoding/json" "testing" "time" @@ -399,7 +400,7 @@ func testMessageExportPublicChannel(t *testing.T, ss store.Store) { // get the starting number of message export entries startTime := model.GetMillis() - messages, _, err := ss.Compliance().MessageExport(model.MessageExportCursor{LastPostUpdateAt: startTime - 10}, 10) + messages, _, err := ss.Compliance().MessageExport(context.Background(), model.MessageExportCursor{LastPostUpdateAt: startTime - 10}, 10) require.NoError(t, err) assert.Equal(t, 0, len(messages)) @@ -469,7 +470,7 @@ func testMessageExportPublicChannel(t *testing.T, ss store.Store) { // fetch the message exports for both posts that user1 sent messageExportMap := map[string]model.MessageExport{} - messages, _, err = ss.Compliance().MessageExport(model.MessageExportCursor{LastPostUpdateAt: startTime - 10}, 10) + messages, _, err = ss.Compliance().MessageExport(context.Background(), model.MessageExportCursor{LastPostUpdateAt: startTime - 10}, 10) require.NoError(t, err) assert.Equal(t, 2, len(messages)) @@ -503,7 +504,7 @@ func testMessageExportPrivateChannel(t *testing.T, ss store.Store) { // get the starting number of message export entries startTime := model.GetMillis() - messages, _, err := ss.Compliance().MessageExport(model.MessageExportCursor{LastPostUpdateAt: startTime - 10}, 10) + messages, _, err := ss.Compliance().MessageExport(context.Background(), model.MessageExportCursor{LastPostUpdateAt: startTime - 10}, 10) require.NoError(t, err) assert.Equal(t, 0, len(messages)) @@ -573,7 +574,7 @@ func testMessageExportPrivateChannel(t *testing.T, ss store.Store) { // fetch the message exports for both posts that user1 sent messageExportMap := map[string]model.MessageExport{} - messages, _, err = ss.Compliance().MessageExport(model.MessageExportCursor{LastPostUpdateAt: startTime - 10}, 10) + messages, _, err = ss.Compliance().MessageExport(context.Background(), model.MessageExportCursor{LastPostUpdateAt: startTime - 10}, 10) require.NoError(t, err) assert.Equal(t, 2, len(messages)) @@ -609,7 +610,7 @@ func testMessageExportDirectMessageChannel(t *testing.T, ss store.Store) { // get the starting number of message export entries startTime := model.GetMillis() - messages, _, err := ss.Compliance().MessageExport(model.MessageExportCursor{LastPostUpdateAt: startTime - 10}, 10) + messages, _, err := ss.Compliance().MessageExport(context.Background(), model.MessageExportCursor{LastPostUpdateAt: startTime - 10}, 10) require.NoError(t, err) assert.Equal(t, 0, len(messages)) @@ -664,7 +665,7 @@ func testMessageExportDirectMessageChannel(t *testing.T, ss store.Store) { // fetch the message export for the post that user1 sent messageExportMap := map[string]model.MessageExport{} - messages, _, err = ss.Compliance().MessageExport(model.MessageExportCursor{LastPostUpdateAt: startTime - 10}, 10) + messages, _, err = ss.Compliance().MessageExport(context.Background(), model.MessageExportCursor{LastPostUpdateAt: startTime - 10}, 10) require.NoError(t, err) assert.Equal(t, 1, len(messages)) @@ -690,7 +691,7 @@ func testMessageExportGroupMessageChannel(t *testing.T, ss store.Store) { // get the starting number of message export entries startTime := model.GetMillis() - messages, _, err := ss.Compliance().MessageExport(model.MessageExportCursor{LastPostUpdateAt: startTime - 10}, 10) + messages, _, err := ss.Compliance().MessageExport(context.Background(), model.MessageExportCursor{LastPostUpdateAt: startTime - 10}, 10) require.NoError(t, err) assert.Equal(t, 0, len(messages)) @@ -762,7 +763,7 @@ func testMessageExportGroupMessageChannel(t *testing.T, ss store.Store) { // fetch the message export for the post that user1 sent messageExportMap := map[string]model.MessageExport{} - messages, _, err = ss.Compliance().MessageExport(model.MessageExportCursor{LastPostUpdateAt: startTime - 10}, 10) + messages, _, err = ss.Compliance().MessageExport(context.Background(), model.MessageExportCursor{LastPostUpdateAt: startTime - 10}, 10) require.NoError(t, err) assert.Equal(t, 1, len(messages)) @@ -787,7 +788,7 @@ func testEditExportMessage(t *testing.T, ss store.Store) { defer cleanupStoreState(t, ss) // get the starting number of message export entries startTime := model.GetMillis() - messages, _, err := ss.Compliance().MessageExport(model.MessageExportCursor{LastPostUpdateAt: startTime - 1}, 10) + messages, _, err := ss.Compliance().MessageExport(context.Background(), model.MessageExportCursor{LastPostUpdateAt: startTime - 1}, 10) require.NoError(t, err) assert.Equal(t, 0, len(messages)) @@ -842,7 +843,7 @@ func testEditExportMessage(t *testing.T, ss store.Store) { require.NoError(t, err) // fetch the message exports from the start - messages, _, err = ss.Compliance().MessageExport(model.MessageExportCursor{LastPostUpdateAt: startTime - 1}, 10) + messages, _, err = ss.Compliance().MessageExport(context.Background(), model.MessageExportCursor{LastPostUpdateAt: startTime - 1}, 10) require.NoError(t, err) assert.Equal(t, 2, len(messages)) @@ -879,7 +880,7 @@ func testEditAfterExportMessage(t *testing.T, ss store.Store) { defer cleanupStoreState(t, ss) // get the starting number of message export entries startTime := model.GetMillis() - messages, _, err := ss.Compliance().MessageExport(model.MessageExportCursor{LastPostUpdateAt: startTime - 1}, 10) + messages, _, err := ss.Compliance().MessageExport(context.Background(), model.MessageExportCursor{LastPostUpdateAt: startTime - 1}, 10) require.NoError(t, err) assert.Equal(t, 0, len(messages)) @@ -927,7 +928,7 @@ func testEditAfterExportMessage(t *testing.T, ss store.Store) { require.NoError(t, err) // fetch the message exports from the start - messages, _, err = ss.Compliance().MessageExport(model.MessageExportCursor{LastPostUpdateAt: startTime - 1}, 10) + messages, _, err = ss.Compliance().MessageExport(context.Background(), model.MessageExportCursor{LastPostUpdateAt: startTime - 1}, 10) require.NoError(t, err) assert.Equal(t, 1, len(messages)) @@ -953,7 +954,7 @@ func testEditAfterExportMessage(t *testing.T, ss store.Store) { require.NoError(t, err) // fetch the message exports after edit - messages, _, err = ss.Compliance().MessageExport(model.MessageExportCursor{LastPostUpdateAt: postEditTime - 1}, 10) + messages, _, err = ss.Compliance().MessageExport(context.Background(), model.MessageExportCursor{LastPostUpdateAt: postEditTime - 1}, 10) require.NoError(t, err) assert.Equal(t, 2, len(messages)) @@ -990,7 +991,7 @@ func testDeleteExportMessage(t *testing.T, ss store.Store) { defer cleanupStoreState(t, ss) // get the starting number of message export entries startTime := model.GetMillis() - messages, _, err := ss.Compliance().MessageExport(model.MessageExportCursor{LastPostUpdateAt: startTime - 1}, 10) + messages, _, err := ss.Compliance().MessageExport(context.Background(), model.MessageExportCursor{LastPostUpdateAt: startTime - 1}, 10) require.NoError(t, err) assert.Equal(t, 0, len(messages)) @@ -1043,7 +1044,7 @@ func testDeleteExportMessage(t *testing.T, ss store.Store) { require.NoError(t, err) // fetch the message exports from the start - messages, _, err = ss.Compliance().MessageExport(model.MessageExportCursor{LastPostUpdateAt: startTime - 1}, 10) + messages, _, err = ss.Compliance().MessageExport(context.Background(), model.MessageExportCursor{LastPostUpdateAt: startTime - 1}, 10) require.NoError(t, err) assert.Equal(t, 1, len(messages)) @@ -1075,7 +1076,7 @@ func testDeleteAfterExportMessage(t *testing.T, ss store.Store) { defer cleanupStoreState(t, ss) // get the starting number of message export entries startTime := model.GetMillis() - messages, _, err := ss.Compliance().MessageExport(model.MessageExportCursor{LastPostUpdateAt: startTime - 1}, 10) + messages, _, err := ss.Compliance().MessageExport(context.Background(), model.MessageExportCursor{LastPostUpdateAt: startTime - 1}, 10) require.NoError(t, err) assert.Equal(t, 0, len(messages)) @@ -1123,7 +1124,7 @@ func testDeleteAfterExportMessage(t *testing.T, ss store.Store) { require.NoError(t, err) // fetch the message exports from the start - messages, _, err = ss.Compliance().MessageExport(model.MessageExportCursor{LastPostUpdateAt: startTime - 1}, 10) + messages, _, err = ss.Compliance().MessageExport(context.Background(), model.MessageExportCursor{LastPostUpdateAt: startTime - 1}, 10) require.NoError(t, err) assert.Equal(t, 1, len(messages)) @@ -1146,7 +1147,7 @@ func testDeleteAfterExportMessage(t *testing.T, ss store.Store) { require.NoError(t, err) // fetch the message exports after delete - messages, _, err = ss.Compliance().MessageExport(model.MessageExportCursor{LastPostUpdateAt: postDeleteTime - 1}, 10) + messages, _, err = ss.Compliance().MessageExport(context.Background(), model.MessageExportCursor{LastPostUpdateAt: postDeleteTime - 1}, 10) require.NoError(t, err) assert.Equal(t, 1, len(messages)) diff --git a/store/storetest/mocks/ComplianceStore.go b/store/storetest/mocks/ComplianceStore.go index 657670b023..a1041e3bae 100644 --- a/store/storetest/mocks/ComplianceStore.go +++ b/store/storetest/mocks/ComplianceStore.go @@ -5,6 +5,8 @@ package mocks import ( + context "context" + model "github.com/mattermost/mattermost-server/v6/model" mock "github.com/stretchr/testify/mock" ) @@ -90,13 +92,13 @@ func (_m *ComplianceStore) GetAll(offset int, limit int) (model.Compliances, err return r0, r1 } -// MessageExport provides a mock function with given fields: cursor, limit -func (_m *ComplianceStore) MessageExport(cursor model.MessageExportCursor, limit int) ([]*model.MessageExport, model.MessageExportCursor, error) { - ret := _m.Called(cursor, limit) +// MessageExport provides a mock function with given fields: ctx, cursor, limit +func (_m *ComplianceStore) MessageExport(ctx context.Context, cursor model.MessageExportCursor, limit int) ([]*model.MessageExport, model.MessageExportCursor, error) { + ret := _m.Called(ctx, cursor, limit) var r0 []*model.MessageExport - if rf, ok := ret.Get(0).(func(model.MessageExportCursor, int) []*model.MessageExport); ok { - r0 = rf(cursor, limit) + if rf, ok := ret.Get(0).(func(context.Context, model.MessageExportCursor, int) []*model.MessageExport); ok { + r0 = rf(ctx, cursor, limit) } else { if ret.Get(0) != nil { r0 = ret.Get(0).([]*model.MessageExport) @@ -104,15 +106,15 @@ func (_m *ComplianceStore) MessageExport(cursor model.MessageExportCursor, limit } var r1 model.MessageExportCursor - if rf, ok := ret.Get(1).(func(model.MessageExportCursor, int) model.MessageExportCursor); ok { - r1 = rf(cursor, limit) + if rf, ok := ret.Get(1).(func(context.Context, model.MessageExportCursor, int) model.MessageExportCursor); ok { + r1 = rf(ctx, cursor, limit) } else { r1 = ret.Get(1).(model.MessageExportCursor) } var r2 error - if rf, ok := ret.Get(2).(func(model.MessageExportCursor, int) error); ok { - r2 = rf(cursor, limit) + if rf, ok := ret.Get(2).(func(context.Context, model.MessageExportCursor, int) error); ok { + r2 = rf(ctx, cursor, limit) } else { r2 = ret.Error(2) } diff --git a/store/timerlayer/timerlayer.go b/store/timerlayer/timerlayer.go index 02be7b110d..19ccaf656d 100644 --- a/store/timerlayer/timerlayer.go +++ b/store/timerlayer/timerlayer.go @@ -2932,10 +2932,10 @@ func (s *TimerLayerComplianceStore) GetAll(offset int, limit int) (model.Complia return result, err } -func (s *TimerLayerComplianceStore) MessageExport(cursor model.MessageExportCursor, limit int) ([]*model.MessageExport, model.MessageExportCursor, error) { +func (s *TimerLayerComplianceStore) MessageExport(ctx context.Context, cursor model.MessageExportCursor, limit int) ([]*model.MessageExport, model.MessageExportCursor, error) { start := time.Now() - result, resultVar1, err := s.ComplianceStore.MessageExport(cursor, limit) + result, resultVar1, err := s.ComplianceStore.MessageExport(ctx, cursor, limit) elapsed := float64(time.Since(start)) / float64(time.Second) if s.Root.Metrics != nil { From 7a9354cd0962ce0ddf13f4c4171e039149fffd5a Mon Sep 17 00:00:00 2001 From: Doug Lauder Date: Fri, 23 Dec 2022 10:47:29 -0500 Subject: [PATCH 21/37] Expose boards APIs for MPA (#21917) * add first batch of Boards service APIs for MPA --- go.mod | 6 ++++-- go.sum | 10 ++++++++-- product/api.go | 21 +++++++++++++++++++++ product/service.go | 1 + 4 files changed, 34 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index c157155e51..2e04b4e93c 100644 --- a/go.mod +++ b/go.mod @@ -33,6 +33,7 @@ require ( github.com/jmoiron/sqlx v1.3.5 github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 github.com/lib/pq v1.10.7 + github.com/mattermost/focalboard/server v0.0.0-20221222174020-fd4cf95f8ac9 github.com/mattermost/go-i18n v1.11.1-0.20211013152124-5c415071e404 github.com/mattermost/gziphandler v0.0.1 github.com/mattermost/ldap v0.0.0-20201202150706-ee0e6284187d @@ -85,6 +86,7 @@ require ( github.com/aymerick/douceur v0.2.0 // indirect github.com/bits-and-blooms/bitset v1.3.3 // indirect github.com/bits-and-blooms/bloom/v3 v3.3.1 // indirect + github.com/blang/semver/v4 v4.0.0 // indirect github.com/blevesearch/bleve_index_api v1.0.5 // indirect github.com/blevesearch/geo v0.1.15 // indirect github.com/blevesearch/go-porterstemmer v1.0.3 // indirect @@ -131,10 +133,10 @@ require ( github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/levigross/exp-html v0.0.0-20120902181939-8df60c69a8f5 // indirect + github.com/mattermost/mattermost-plugin-api v0.0.29-0.20220801143717-73008cfda2fb // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.16 // indirect github.com/mattn/go-runewidth v0.0.14 // indirect - github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect github.com/minio/md5-simd v1.1.2 // indirect github.com/minio/sha256-simd v1.0.0 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect @@ -179,7 +181,7 @@ require ( gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect - lukechampine.com/uint128 v1.1.1 // indirect + lukechampine.com/uint128 v1.2.0 // indirect modernc.org/cc/v3 v3.36.0 // indirect modernc.org/ccgo/v3 v3.16.6 // indirect modernc.org/libc v1.16.7 // indirect diff --git a/go.sum b/go.sum index 5bfeee94ff..196d356a53 100644 --- a/go.sum +++ b/go.sum @@ -216,6 +216,8 @@ github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJm github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/blevesearch/bleve/v2 v2.3.6-0.20221111171245-56dc9b25507e h1:r/cWPLUPgAM3SWWniQ6j0Hzb++h+uEycDD9UOUuC1Vk= github.com/blevesearch/bleve/v2 v2.3.6-0.20221111171245-56dc9b25507e/go.mod h1:mfCWvuwg/XnPVZHEejATm5TyFqyeLmm8p9Y3xDvwz4k= github.com/blevesearch/bleve_index_api v1.0.3/go.mod h1:fiwKS0xLEm+gBRgv5mumf0dhgFr2mDgZah1pqv1c1M4= @@ -979,6 +981,8 @@ github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsI github.com/markbates/pkger v0.15.1/go.mod h1:0JoVlrol20BSywW79rN3kdFFsE5xYM+rSCQDXbLhiuI= github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= +github.com/mattermost/focalboard/server v0.0.0-20221222174020-fd4cf95f8ac9 h1:UE3KuILWJwTnaXAy2YHqkCu1f7i+VdX2VNukBGYbPuI= +github.com/mattermost/focalboard/server v0.0.0-20221222174020-fd4cf95f8ac9/go.mod h1:h1HQ8UVoNMyDHzjPD7UtYbTPMWjP6d1qJZuLdT6ElNg= github.com/mattermost/go-i18n v1.11.1-0.20211013152124-5c415071e404 h1:Khvh6waxG1cHc4Cz5ef9n3XVCxRWpAKUtqg9PJl5+y8= github.com/mattermost/go-i18n v1.11.1-0.20211013152124-5c415071e404/go.mod h1:RyS7FDNQlzF1PsjbJWHRI35exqaKGSO9qD4iv8QjE34= github.com/mattermost/gziphandler v0.0.1 h1:uXHcXF5agnQ6bXabvpiwwwZOlCYoa7mKHH0lxns/o8w= @@ -987,6 +991,8 @@ github.com/mattermost/ldap v0.0.0-20201202150706-ee0e6284187d h1:/RJ/UV7M5c7L2TQ github.com/mattermost/ldap v0.0.0-20201202150706-ee0e6284187d/go.mod h1:HLbgMEI5K131jpxGazJ97AxfPDt31osq36YS1oxFQPQ= github.com/mattermost/logr/v2 v2.0.15 h1:+WNbGcsc3dBao65eXlceB6dTILNJRIrvubnsTl3zBew= github.com/mattermost/logr/v2 v2.0.15/go.mod h1:mpPp935r5dIkFDo2y9Q87cQWhFR/4xXpNh0k/y8Hmwg= +github.com/mattermost/mattermost-plugin-api v0.0.29-0.20220801143717-73008cfda2fb h1:q1qXKVv59rA2gcQ7lVLc5OlWBmfsR3i8mdGD5EZesyk= +github.com/mattermost/mattermost-plugin-api v0.0.29-0.20220801143717-73008cfda2fb/go.mod h1:PIeo40t9VTA4Wu1FwjzH7QmcgC3SRyk/ohCwJw4/oSo= github.com/mattermost/morph v1.0.5-0.20221115094356-4c18a75b1f5e h1:VfNz+fvJ3DxOlALM22Eea8ONp5jHrybKBCcCtDPVlss= github.com/mattermost/morph v1.0.5-0.20221115094356-4c18a75b1f5e/go.mod h1:xo0ljDknTpPxEdhhrUdwhLCexIsYyDKS6b41HqG8wGU= github.com/mattermost/rsc v0.0.0-20160330161541-bbaefb05eaa0 h1:G9tL6JXRBMzjuD1kkBtcnd42kUiT6QDwxfFYu7adM6o= @@ -1028,7 +1034,6 @@ github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A github.com/mattn/go-sqlite3 v1.14.10/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.12/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U= -github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88Jz2VyhSmden33/aXg4oVIY= @@ -2282,8 +2287,9 @@ k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -lukechampine.com/uint128 v1.1.1 h1:pnxCASz787iMf+02ssImqk6OLt+Z5QHMoZyUXR4z6JU= lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= +lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI= +lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= modernc.org/b v1.0.0/go.mod h1:uZWcZfRj1BpYzfN9JTerzlNUnnPsV9O2ZA8JsRcubNg= modernc.org/cc/v3 v3.32.4/go.mod h1:0R6jl1aZlIl2avnYfbfHBS1QB6/f+16mihBObaBC878= modernc.org/cc/v3 v3.36.0 h1:0kmRkTmqNidmu3c7BNDSdVHCxXCkWLmWmCIVX4LUboo= diff --git a/product/api.go b/product/api.go index 5067c20ce9..587f9d788c 100644 --- a/product/api.go +++ b/product/api.go @@ -12,6 +12,8 @@ import ( "github.com/mattermost/mattermost-server/v6/model" "github.com/mattermost/mattermost-server/v6/shared/filestore" "github.com/mattermost/mattermost-server/v6/shared/mlog" + + fb_model "github.com/mattermost/focalboard/server/model" ) // RouterService enables registering the product router to the server. After registering the @@ -189,3 +191,22 @@ type PreferencesService interface { UpdatePreferencesForUser(userID string, preferences model.Preferences) *model.AppError DeletePreferencesForUser(userID string, preferences model.Preferences) *model.AppError } + +// BoardsService is the API for accessing Boards service APIs. +// +// The service shall be registered via app.BoardsKey service key. +type BoardsService interface { + GetTemplates(teamID string, userID string) ([]*fb_model.Board, error) + GetBoard(boardID string) (*fb_model.Board, error) + CreateBoard(board *fb_model.Board, userID string, addmember bool) (*fb_model.Board, error) + PatchBoard(boardPatch *fb_model.BoardPatch, boardID string, userID string) (*fb_model.Board, error) + DeleteBoard(boardID string, userID string) error + SearchBoards(searchTerm string, searchField fb_model.BoardSearchField, userID string, includePublicBoards bool) ([]*fb_model.Board, error) + LinkBoardToChannel(boardID string, channelID string, userID string) (*fb_model.Board, error) + GetCards(boardID string) ([]*fb_model.Card, error) + GetCard(cardID string) (*fb_model.Card, error) + CreateCard(card *fb_model.Card, boardID string, userID string) (*fb_model.Card, error) + PatchCard(cardPatch *fb_model.CardPatch, cardID string, userID string) (*fb_model.Card, error) + DeleteCard(cardID string, userID string) error + HasPermissionToBoard(userID, boardID string, permission *model.Permission) bool +} diff --git a/product/service.go b/product/service.go index 221fa1c168..a0a92e6adc 100644 --- a/product/service.go +++ b/product/service.go @@ -25,4 +25,5 @@ const ( StoreKey ServiceKey = "storekey" SystemKey ServiceKey = "systemkey" PreferencesKey ServiceKey = "preferenceskey" + BoardsKey ServiceKey = "boards" ) From 99b232e0808d2942a87b4b7964308f0a462d0031 Mon Sep 17 00:00:00 2001 From: Gabe Jackson Date: Sun, 25 Dec 2022 21:43:30 -0500 Subject: [PATCH 22/37] Improve bulk export logging (#21935) This change includes the following: - Adds the job ID as a log field for bulk exports - Adds checkpoint logging for posts and direct posts so that progress can be tracked. --- app/export.go | 17 +++++++++++++++++ jobs/export_process/worker.go | 3 ++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/app/export.go b/app/export.go index 6afd544534..8e52fa74e7 100644 --- a/app/export.go +++ b/app/export.go @@ -398,8 +398,15 @@ func (a *App) buildUserNotifyProps(notifyProps model.StringMap) *imports.UserNot func (a *App) exportAllPosts(ctx request.CTX, writer io.Writer, withAttachments bool) ([]imports.AttachmentImportData, *model.AppError) { var attachments []imports.AttachmentImportData afterId := strings.Repeat("0", 26) + var postProcessCount uint64 + logCheckpoint := time.Now() for { + if time.Since(logCheckpoint) > 5*time.Minute { + ctx.Logger().Debug(fmt.Sprintf("Bulk Export: processed %d posts", postProcessCount)) + logCheckpoint = time.Now() + } + posts, nErr := a.Srv().Store().Post().GetParentsForExportAfter(1000, afterId) if nErr != nil { return nil, model.NewAppError("exportAllPosts", "app.post.get_posts.app_error", nil, "", http.StatusInternalServerError).Wrap(nErr) @@ -411,6 +418,7 @@ func (a *App) exportAllPosts(ctx request.CTX, writer io.Writer, withAttachments for _, post := range posts { afterId = post.Id + postProcessCount++ // Skip deleted. if post.DeleteAt != 0 { @@ -677,7 +685,15 @@ func (a *App) buildFavoritedByList(channelID string) ([]string, *model.AppError) func (a *App) exportAllDirectPosts(ctx request.CTX, writer io.Writer, withAttachments bool) ([]imports.AttachmentImportData, *model.AppError) { var attachments []imports.AttachmentImportData afterId := strings.Repeat("0", 26) + var postProcessCount uint64 + logCheckpoint := time.Now() + for { + if time.Since(logCheckpoint) > 5*time.Minute { + ctx.Logger().Debug(fmt.Sprintf("Bulk Export: processed %d direct posts", postProcessCount)) + logCheckpoint = time.Now() + } + posts, err := a.Srv().Store().Post().GetDirectPostParentsForExportAfter(1000, afterId) if err != nil { return nil, model.NewAppError("exportAllDirectPosts", "app.post.get_direct_posts.app_error", nil, "", http.StatusInternalServerError).Wrap(err) @@ -689,6 +705,7 @@ func (a *App) exportAllDirectPosts(ctx request.CTX, writer io.Writer, withAttach for _, post := range posts { afterId = post.Id + postProcessCount++ // Skip deleted. if post.DeleteAt != 0 { diff --git a/jobs/export_process/worker.go b/jobs/export_process/worker.go index ddf9462b14..7f80ac1950 100644 --- a/jobs/export_process/worker.go +++ b/jobs/export_process/worker.go @@ -55,7 +55,8 @@ func MakeWorker(jobServer *jobs.JobServer, app AppIface) model.Worker { } }() - appErr := app.BulkExport(request.EmptyContext(app.Log()), wr, outPath, opts) + logger := app.Log().With(mlog.String("job_id", job.Id)) + appErr := app.BulkExport(request.EmptyContext(logger), wr, outPath, opts) wr.Close() // Close never returns an error if appErr != nil { From ab133f20de9a29b478f393109f53d091f15eed20 Mon Sep 17 00:00:00 2001 From: Doug Lauder Date: Mon, 26 Dec 2022 11:53:34 -0500 Subject: [PATCH 23/37] add DuplicateBoard to service API (#21950) Co-authored-by: Mattermod --- product/api.go | 1 + 1 file changed, 1 insertion(+) diff --git a/product/api.go b/product/api.go index 587f9d788c..54d473bb61 100644 --- a/product/api.go +++ b/product/api.go @@ -209,4 +209,5 @@ type BoardsService interface { PatchCard(cardPatch *fb_model.CardPatch, cardID string, userID string) (*fb_model.Card, error) DeleteCard(cardID string, userID string) error HasPermissionToBoard(userID, boardID string, permission *model.Permission) bool + DuplicateBoard(boardID string, userID string, toTeam string, asTemplate bool) (*fb_model.BoardsAndBlocks, []*fb_model.BoardMember, error) } From dc533e59b880a54e9cd9aa89fbf075b66e5185f1 Mon Sep 17 00:00:00 2001 From: Vishal Date: Tue, 27 Dec 2022 14:34:40 +0530 Subject: [PATCH 24/37] Message export: Fix job progress calculation (#21925) * use posts count instead of job duration to calculate progress Co-authored-by: Mattermod --- i18n/en.json | 8 -- model/post.go | 13 +-- store/sqlstore/post_store.go | 14 +++ store/storetest/post_store.go | 163 ++++++++++++++++++++++++++++++---- 4 files changed, 166 insertions(+), 32 deletions(-) diff --git a/i18n/en.json b/i18n/en.json index 25f8a93b61..4cb1c9c634 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -7571,14 +7571,6 @@ "id": "ent.id_loaded.license_disable.app_error", "translation": "Your license does not support ID Loaded Push Notifications." }, - { - "id": "ent.jobs.do_job.batch_size.parse_error", - "translation": "Could not parse message export job BatchSize." - }, - { - "id": "ent.jobs.do_job.batch_start_timestamp.parse_error", - "translation": "Could not parse message export job ExportFromTimestamp." - }, { "id": "ent.jobs.start_synchronize_job.timeout", "translation": "Reached AD/LDAP synchronization job timeout." diff --git a/model/post.go b/model/post.go index 2a7c4538e0..e7c11edc44 100644 --- a/model/post.go +++ b/model/post.go @@ -333,13 +333,16 @@ type GetPostsOptions struct { type PostCountOptions struct { // Only include posts on a specific team. "" for any team. - TeamId string - MustHaveFile bool - MustHaveHashtag bool - ExcludeDeleted bool - UsersPostsOnly bool + TeamId string + MustHaveFile bool + MustHaveHashtag bool + ExcludeDeleted bool + ExcludeSystemPosts bool + UsersPostsOnly bool // AllowFromCache looks up cache only when ExcludeDeleted and UsersPostsOnly are true and rest are falsy. AllowFromCache bool + SincePostID string + SinceUpdateAt int64 } func (o *Post) Etag() string { diff --git a/store/sqlstore/post_store.go b/store/sqlstore/post_store.go index b7f5979cd9..8bb4de38cd 100644 --- a/store/sqlstore/post_store.go +++ b/store/sqlstore/post_store.go @@ -2272,6 +2272,20 @@ func (s *SqlPostStore) AnalyticsPostCount(options *model.PostCountOptions) (int6 query = query.Where(sq.Eq{"p.DeleteAt": 0}) } + if options.ExcludeSystemPosts { + query = query.Where("p.Type NOT LIKE 'system_%'") + } + + if options.SinceUpdateAt > 0 { + query = query.Where(sq.Or{ + sq.Gt{"p.UpdateAt": options.SinceUpdateAt}, + sq.And{ + sq.Eq{"p.UpdateAt": options.SinceUpdateAt}, + sq.Gt{"p.Id": options.SincePostID}, + }, + }) + } + queryString, args, err := query.ToSql() if err != nil { return 0, errors.Wrap(err, "post_tosql") diff --git a/store/storetest/post_store.go b/store/storetest/post_store.go index f3c7d91630..c6e9ecd7ef 100644 --- a/store/storetest/post_store.go +++ b/store/storetest/post_store.go @@ -38,6 +38,7 @@ func TestPostStore(t *testing.T, ss store.Store, s SqlStore) { t.Run("GetPostBeforeAfter", func(t *testing.T) { testPostStoreGetPostBeforeAfter(t, ss) }) t.Run("UserCountsWithPostsByDay", func(t *testing.T) { testUserCountsWithPostsByDay(t, ss) }) t.Run("PostCountsByDuration", func(t *testing.T) { testPostCountsByDay(t, ss) }) + t.Run("PostCounts", func(t *testing.T) { testPostCounts(t, ss) }) t.Run("GetFlaggedPostsForTeam", func(t *testing.T) { testPostStoreGetFlaggedPostsForTeam(t, ss, s) }) t.Run("GetFlaggedPosts", func(t *testing.T) { testPostStoreGetFlaggedPosts(t, ss) }) t.Run("GetFlaggedPostsForChannel", func(t *testing.T) { testPostStoreGetFlaggedPostsForChannel(t, ss) }) @@ -2685,45 +2686,169 @@ func testPostCountsByDay(t *testing.T, ss store.Store) { r1, err = ss.Post().AnalyticsPostCountsByDay(postCountsOptions) require.NoError(t, err) assert.Equal(t, float64(1), r1[0].Value) +} + +func testPostCounts(t *testing.T, ss store.Store) { + now := time.Now() + twentyMinAgo := now.Add(-20 * time.Minute).UnixMilli() + fifteenMinAgo := now.Add(-15 * time.Minute).UnixMilli() + tenMinAgo := now.Add(-10 * time.Minute).UnixMilli() + + t1 := &model.Team{} + t1.DisplayName = "DisplayName" + t1.Name = NewTestId() + t1.Email = MakeEmail() + t1.Type = model.TeamOpen + t1, err := ss.Team().Save(t1) + require.NoError(t, err) + + c1 := &model.Channel{} + c1.TeamId = t1.Id + c1.DisplayName = "Channel2" + c1.Name = NewTestId() + c1.Type = model.ChannelTypeOpen + c1, nErr := ss.Channel().Save(c1, -1) + require.NoError(t, nErr) + + // system post + p1 := &model.Post{} + p1.Type = "system_add_to_channel" + p1.ChannelId = c1.Id + p1.UserId = model.NewId() + p1.Message = NewTestId() + p1.CreateAt = twentyMinAgo + p1.UpdateAt = twentyMinAgo + _, nErr = ss.Post().Save(p1) + require.NoError(t, nErr) + + p2 := &model.Post{} + p2.ChannelId = c1.Id + p2.UserId = model.NewId() + p2.Message = NewTestId() + p2.Hashtags = "hashtag" + p2.CreateAt = twentyMinAgo + p2.UpdateAt = twentyMinAgo + p2, nErr = ss.Post().Save(p2) + require.NoError(t, nErr) + + p3 := &model.Post{} + p3.ChannelId = c1.Id + p3.UserId = model.NewId() + p3.Message = NewTestId() + p3.FileIds = []string{"fileId1"} + p3.CreateAt = twentyMinAgo + p3.UpdateAt = twentyMinAgo + _, nErr = ss.Post().Save(p3) + require.NoError(t, nErr) + + p4 := &model.Post{} + p4.ChannelId = c1.Id + p4.UserId = model.NewId() + p4.Message = NewTestId() + p4.Filenames = []string{"filename1"} + p4.CreateAt = tenMinAgo + p4.UpdateAt = tenMinAgo + p4, nErr = ss.Post().Save(p4) + require.NoError(t, nErr) + + p5 := &model.Post{} + p5.ChannelId = c1.Id + p5.UserId = p4.UserId + p5.Message = NewTestId() + p5.Hashtags = "hashtag" + p5.FileIds = []string{"fileId2"} + p5.CreateAt = tenMinAgo + p5.UpdateAt = tenMinAgo + _, nErr = ss.Post().Save(p5) + require.NoError(t, nErr) + + bot1 := &model.Bot{ + Username: "username", + Description: "a bot", + OwnerId: model.NewId(), + UserId: model.NewId(), + } + _, nErr = ss.Bot().Save(bot1) + require.NoError(t, nErr) + + p6 := &model.Post{} + p6.Message = "bot message one" + p6.ChannelId = c1.Id + p6.UserId = bot1.UserId + p6.CreateAt = twentyMinAgo + p6.UpdateAt = twentyMinAgo + _, nErr = ss.Post().Save(p6) + require.NoError(t, nErr) + + p7 := &model.Post{} + p7.Message = "bot message two" + p7.ChannelId = c1.Id + p7.UserId = bot1.UserId + p7.CreateAt = tenMinAgo + p7.UpdateAt = tenMinAgo + _, nErr = ss.Post().Save(p7) + require.NoError(t, nErr) + + // total across all teams + c, err := ss.Post().AnalyticsPostCount(&model.PostCountOptions{}) + require.NoError(t, err) + assert.GreaterOrEqual(t, c, int64(7)) // total for single team - r2, err := ss.Post().AnalyticsPostCount(&model.PostCountOptions{TeamId: t1.Id}) + c, err = ss.Post().AnalyticsPostCount(&model.PostCountOptions{TeamId: t1.Id}) require.NoError(t, err) - assert.Equal(t, int64(6), r2) + assert.Equal(t, int64(7), c) - // total across teams - r2, err = ss.Post().AnalyticsPostCount(&model.PostCountOptions{}) + // with files + c, err = ss.Post().AnalyticsPostCount(&model.PostCountOptions{TeamId: t1.Id, MustHaveFile: true}) require.NoError(t, err) - assert.GreaterOrEqual(t, r2, int64(6)) + assert.Equal(t, int64(3), c) - // total across teams with files - r2, err = ss.Post().AnalyticsPostCount(&model.PostCountOptions{MustHaveFile: true}) + // with hashtags + c, err = ss.Post().AnalyticsPostCount(&model.PostCountOptions{TeamId: t1.Id, MustHaveHashtag: true}) require.NoError(t, err) - assert.GreaterOrEqual(t, r2, int64(3)) + assert.Equal(t, int64(2), c) - // total across teams with hashtags - r2, err = ss.Post().AnalyticsPostCount(&model.PostCountOptions{MustHaveHashtag: true}) + // with hashtags and files + c, err = ss.Post().AnalyticsPostCount(&model.PostCountOptions{TeamId: t1.Id, MustHaveFile: true, MustHaveHashtag: true}) require.NoError(t, err) - assert.GreaterOrEqual(t, r2, int64(2)) + assert.Equal(t, int64(1), c) - // total across teams with hashtags and files - r2, err = ss.Post().AnalyticsPostCount(&model.PostCountOptions{MustHaveFile: true, MustHaveHashtag: true}) + // excluding system posts + c, err = ss.Post().AnalyticsPostCount(&model.PostCountOptions{TeamId: t1.Id, ExcludeSystemPosts: true}) require.NoError(t, err) - assert.GreaterOrEqual(t, r2, int64(1)) + assert.Equal(t, int64(6), c) + + // before update_at time + c, err = ss.Post().AnalyticsPostCount(&model.PostCountOptions{TeamId: t1.Id, SinceUpdateAt: fifteenMinAgo}) + require.NoError(t, err) + assert.Equal(t, int64(3), c) + + // equal to update_at time + c, err = ss.Post().AnalyticsPostCount(&model.PostCountOptions{TeamId: t1.Id, SinceUpdateAt: tenMinAgo}) + require.NoError(t, err) + assert.Equal(t, int64(3), c) + + // since update_at and since post id + tenMinAgoIDs := []string{p4.Id, p5.Id, p7.Id} + sort.Strings(tenMinAgoIDs) + c, err = ss.Post().AnalyticsPostCount(&model.PostCountOptions{TeamId: t1.Id, SinceUpdateAt: tenMinAgo, SincePostID: tenMinAgoIDs[0]}) + require.NoError(t, err) + assert.Equal(t, int64(2), c) // delete 1 post - err = ss.Post().Delete(o1.Id, 1, o1.UserId) + err = ss.Post().Delete(p2.Id, 1, p2.UserId) require.NoError(t, err) // total for single team with the deleted post excluded - r2, err = ss.Post().AnalyticsPostCount(&model.PostCountOptions{TeamId: t1.Id, ExcludeDeleted: true}) + c, err = ss.Post().AnalyticsPostCount(&model.PostCountOptions{TeamId: t1.Id, ExcludeDeleted: true}) require.NoError(t, err) - assert.Equal(t, int64(5), r2) + assert.Equal(t, int64(6), c) // total users only posts for single team with the deleted post excluded - r2, err = ss.Post().AnalyticsPostCount(&model.PostCountOptions{TeamId: t1.Id, ExcludeDeleted: true, UsersPostsOnly: true}) + c, err = ss.Post().AnalyticsPostCount(&model.PostCountOptions{TeamId: t1.Id, ExcludeDeleted: true, UsersPostsOnly: true}) require.NoError(t, err) - assert.Equal(t, int64(3), r2) + assert.Equal(t, int64(3), c) } func testPostStoreGetFlaggedPostsForTeam(t *testing.T, ss store.Store, s SqlStore) { From b9501bc5352b7964d5784bd105def526352c8f6f Mon Sep 17 00:00:00 2001 From: Said Atrahouch Date: Tue, 27 Dec 2022 12:08:11 +0100 Subject: [PATCH 25/37] Mm 47648 check license expandable (#21770) * [MM-47648] Handler for check if a subscription is expandable * Mock cloud regenerate * PR Webapp test purpose only * [MM-47648] Refactor and tests for expand stats for a subscription * Fix typos * style fix * has to be revert * CWS URL to Spinwick * new spinwick url * target portal test * Generate token instead of send licenseId * Rename * Rename methods * Revert public key and cws url Co-authored-by: Mattermod --- api4/cloud.go | 29 ++++++++++++++++ api4/cloud_test.go | 52 +++++++++++++++++++++++++++++ einterfaces/cloud.go | 1 + einterfaces/mocks/CloudInterface.go | 23 +++++++++++++ model/client4.go | 13 ++++++++ model/cloud.go | 4 +++ 6 files changed, 122 insertions(+) diff --git a/api4/cloud.go b/api4/cloud.go index 2499164fac..d517291d48 100644 --- a/api4/cloud.go +++ b/api4/cloud.go @@ -40,6 +40,7 @@ func (api *API) InitCloud() { api.BaseRoutes.Cloud.Handle("/subscription", api.APISessionRequired(getSubscription)).Methods("GET") api.BaseRoutes.Cloud.Handle("/subscription/invoices", api.APISessionRequired(getInvoicesForSubscription)).Methods("GET") api.BaseRoutes.Cloud.Handle("/subscription/invoices/{invoice_id:[A-Za-z0-9]+}/pdf", api.APISessionRequired(getSubscriptionInvoicePDF)).Methods("GET") + api.BaseRoutes.Cloud.Handle("/subscription/expand", api.APISessionRequired(GetLicenseExpandStatus)).Methods("GET") api.BaseRoutes.Cloud.Handle("/subscription", api.APISessionRequired(changeSubscription)).Methods("PUT") // GET /api/v4/cloud/request-trial @@ -413,6 +414,34 @@ func getCloudCustomer(c *Context, w http.ResponseWriter, r *http.Request) { w.Write(json) } +func GetLicenseExpandStatus(c *Context, w http.ResponseWriter, r *http.Request) { + if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionManageLicenseInformation) { + c.SetPermissionError(model.PermissionManageLicenseInformation) + return + } + + _, token, err := c.App.Srv().GenerateLicenseRenewalLink() + + if err != nil { + c.Err = err + return + } + + res, cloudErr := c.App.Cloud().GetLicenseExpandStatus(c.AppContext.Session().UserId, token) + if cloudErr != nil { + c.Err = model.NewAppError("Api4.GetLicenseExpandStatusForSubscription", "api.cloud.request_error", nil, "", http.StatusInternalServerError).Wrap(cloudErr) + return + } + + json, jsonErr := json.Marshal(res) + if jsonErr != nil { + c.Err = model.NewAppError("Api4.GetLicenseExpandStatusForSubscription", "api.cloud.app_error", nil, "", http.StatusInternalServerError).Wrap(jsonErr) + return + } + + w.Write(json) +} + func updateCloudCustomer(c *Context, w http.ResponseWriter, r *http.Request) { if !c.App.Channels().License().IsCloud() { c.Err = model.NewAppError("Api4.updateCloudCustomer", "api.cloud.license_error", nil, "", http.StatusForbidden) diff --git a/api4/cloud_test.go b/api4/cloud_test.go index 75bf89f631..399ee67f45 100644 --- a/api4/cloud_test.go +++ b/api4/cloud_test.go @@ -649,6 +649,58 @@ func TestGetCloudProducts(t *testing.T) { }) } +func Test_GetExpandStatsForSubscription(t *testing.T) { + isExpandable := &model.SubscriptionExpandStatus{ + IsExpandable: true, + } + + licenseId := "licenseID" + + t.Run("NON Admin users are UNABLE to request expand stats for the subscription", func(t *testing.T) { + th := Setup(t).InitBasic() + defer th.TearDown() + + th.Client.Login(th.BasicUser.Email, th.BasicUser.Password) + + cloud := mocks.CloudInterface{} + + cloud.Mock.On("GetLicenseExpandStatus", mock.Anything).Return(isExpandable, nil) + + cloudImpl := th.App.Srv().Cloud + defer func() { + th.App.Srv().Cloud = cloudImpl + }() + th.App.Srv().Cloud = &cloud + + subscriptionExpandable, r, err := th.Client.GetExpandStats(licenseId) + require.Error(t, err) + require.Nil(t, subscriptionExpandable) + require.Equal(t, http.StatusForbidden, r.StatusCode, "403 Forbidden") + }) + + t.Run("Admin users are UNABLE to request licenses is expendable due missing the id", func(t *testing.T) { + th := Setup(t).InitBasic() + defer th.TearDown() + + th.Client.Login(th.SystemAdminUser.Email, th.SystemAdminUser.Password) + + cloud := mocks.CloudInterface{} + + cloud.Mock.On("GetLicenseExpandStatus", mock.Anything).Return(isExpandable, nil) + + cloudImpl := th.App.Srv().Cloud + defer func() { + th.App.Srv().Cloud = cloudImpl + }() + th.App.Srv().Cloud = &cloud + + subscriptionExpandable, r, err := th.Client.GetExpandStats("") + require.Error(t, err) + require.Nil(t, subscriptionExpandable) + require.Equal(t, http.StatusBadRequest, r.StatusCode, "400 Bad Request") + }) +} + func TestGetSelfHostedProducts(t *testing.T) { products := []*model.Product{ { diff --git a/einterfaces/cloud.go b/einterfaces/cloud.go index d58e8c92dd..0c70c02cf3 100644 --- a/einterfaces/cloud.go +++ b/einterfaces/cloud.go @@ -17,6 +17,7 @@ type CloudInterface interface { ConfirmCustomerPayment(userID string, confirmRequest *model.ConfirmPaymentMethodRequest) error GetCloudCustomer(userID string) (*model.CloudCustomer, error) + GetLicenseExpandStatus(userID string, token string) (*model.SubscriptionExpandStatus, error) UpdateCloudCustomer(userID string, customerInfo *model.CloudCustomerInfo) (*model.CloudCustomer, error) UpdateCloudCustomerAddress(userID string, address *model.Address) (*model.CloudCustomer, error) diff --git a/einterfaces/mocks/CloudInterface.go b/einterfaces/mocks/CloudInterface.go index 4b89135dc0..a447399cd8 100644 --- a/einterfaces/mocks/CloudInterface.go +++ b/einterfaces/mocks/CloudInterface.go @@ -325,6 +325,29 @@ func (_m *CloudInterface) GetInvoicesForSubscription(userID string) ([]*model.In return r0, r1 } +// GetLicenseExpandStatus provides a mock function with given fields: userID, token +func (_m *CloudInterface) GetLicenseExpandStatus(userID string, token string) (*model.SubscriptionExpandStatus, error) { + ret := _m.Called(userID, token) + + var r0 *model.SubscriptionExpandStatus + if rf, ok := ret.Get(0).(func(string, string) *model.SubscriptionExpandStatus); ok { + r0 = rf(userID, token) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*model.SubscriptionExpandStatus) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(string, string) error); ok { + r1 = rf(userID, token) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // GetLicenseRenewalStatus provides a mock function with given fields: userID, token func (_m *CloudInterface) GetLicenseRenewalStatus(userID string, token string) error { ret := _m.Called(userID, token) diff --git a/model/client4.go b/model/client4.go index 48d63e959c..298334d71a 100644 --- a/model/client4.go +++ b/model/client4.go @@ -8213,6 +8213,19 @@ func (c *Client4) GetCloudCustomer() (*CloudCustomer, *Response, error) { return cloudCustomer, BuildResponse(r), nil } +func (c *Client4) GetExpandStats(licenseId string) (*SubscriptionExpandStatus, *Response, error) { + r, err := c.DoAPIGet(fmt.Sprintf("%s%s?licenseID=%s", c.cloudRoute(), "/subscription/expand", licenseId), "") + if err != nil { + return nil, BuildResponse(r), err + } + defer closeBody(r) + + var subscriptionExpandable *SubscriptionExpandStatus + json.NewDecoder(r.Body).Decode(&subscriptionExpandable) + + return subscriptionExpandable, BuildResponse(r), nil +} + func (c *Client4) GetSubscription() (*Subscription, *Response, error) { r, err := c.DoAPIGet(c.cloudRoute()+"/subscription", "") if err != nil { diff --git a/model/cloud.go b/model/cloud.go index 7feae13e3d..36c639d66c 100644 --- a/model/cloud.go +++ b/model/cloud.go @@ -124,6 +124,10 @@ type ValidateBusinessEmailResponse struct { IsValid bool `json:"is_valid"` } +type SubscriptionExpandStatus struct { + IsExpandable bool `json:"is_expandable"` +} + // CloudCustomerInfo represents editable info of a customer. type CloudCustomerInfo struct { Name string `json:"name"` From fc018cbe380de4e575aac69eb5645bd92e8dc8a0 Mon Sep 17 00:00:00 2001 From: Tom De Moor Date: Tue, 27 Dec 2022 12:08:30 +0100 Subject: [PATCH 26/37] Translated using Weblate (Dutch) Currently translated at 100.0% (2430 of 2430 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/nl/ Translated using Weblate (Dutch) Currently translated at 98.1% (2384 of 2430 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/nl/ --- i18n/nl.json | 182 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 177 insertions(+), 5 deletions(-) diff --git a/i18n/nl.json b/i18n/nl.json index 683aa0f875..f24f00debb 100644 --- a/i18n/nl.json +++ b/i18n/nl.json @@ -4769,7 +4769,7 @@ }, { "id": "web.error.unsupported_browser.min_os_version.mac", - "translation": "macOS 10.14+" + "translation": "macOS 11+" }, { "id": "web.error.unsupported_browser.min_browser_version.safari", @@ -9264,7 +9264,7 @@ }, { "id": "api.templates.delinquency_14.subject", - "translation": "Betaling is overtijd voor jouw Mattermost {{.Plan}}." + "translation": "Betaling is laattijdig voor jouw Mattermost {{.Plan}}" }, { "id": "api.templates.delinquency_14.button", @@ -9300,7 +9300,7 @@ }, { "id": "api.templates.delinquency_7.subtitle1", - "translation": "We konden jouw laatste betaling niet verwerken" + "translation": "We konden jouw laatste betaling niet verwerken." }, { "id": "api.templates.delinquency_7.button", @@ -9348,7 +9348,7 @@ }, { "id": "api.templates.delinquency_45.subtitle1", - "translation": "We hebben geen betaling kunnen innen voor openstaande facturen van {{.DelinquencyDate}}. Jouw werkruimte loopt het risico om gedowngraded te worden." + "translation": "We hebben geen betaling kunnen innen voor openstaande facturen vanaf {{.DelinquencyDate}}. Jouw werkruimte loopt het risico om gedowngraded te worden." }, { "id": "api.templates.delinquency_45.subject", @@ -9364,7 +9364,7 @@ }, { "id": "api.templates.delinquency_30.subtitle2", - "translation": "als geen actie wordt ondernomen, zal jouw werkruimte worden gedowngraded en kunnen de volgende gegevens worden gearchiveerd:" + "translation": "Als er geen actie wordt ondernomen, zal jouw werkruimte worden gedowngraded en kunnen de volgende gegevens worden gearchiveerd:" }, { "id": "api.templates.delinquency_30.subtitle1", @@ -9561,5 +9561,177 @@ { "id": "api.admin.syncables_error", "translation": "kon gebruiker niet toevoegen aan groep-teams en groep-kanalen" + }, + { + "id": "api.acknowledgement.save.archived_channel.app_error", + "translation": "Je kan niet bevestigen in een gearchiveerd kanaal." + }, + { + "id": "api.acknowledgement.delete.deadline.app_error", + "translation": "Je kan een bevestiging niet wissen nadat 5min verstreken zijn." + }, + { + "id": "api.acknowledgement.delete.archived_channel.app_error", + "translation": "Je kan een bevestiging in een gearchiveerd kanaal niet verwijderen." + }, + { + "id": "worktemplate.product_teams.feature_release.description.playbook", + "translation": "Creëer transparante workflows tussen ontwikkelingsteams om ervoor te zorgen dat jouw ontwikkelingsproces naadloos verloopt." + }, + { + "id": "worktemplate.product_teams.feature_release.description.integration", + "translation": "Verhoog de productiviteit in je kanaal door een Jira bot en Github bot te integreren. Deze worden voor jou gedownload." + }, + { + "id": "worktemplate.product_teams.feature_release.description.channel", + "translation": "Chat met je team in een Feature Release-kanaal dat gemakkelijk verbinding maakt met je boards, playbooks en app bots." + }, + { + "id": "worktemplate.product_teams.feature_release.description.board", + "translation": "Gebruik ons vergaderagenda-bord voor terugkerende vergaderingen zoals stand-up en ons Projecttakenbord om de voortgang van taken onderweg te beheren." + }, + { + "id": "worktemplate.category.product_teams", + "translation": "Productteams" + }, + { + "id": "model.draft.is_valid.user_id.app_error", + "translation": "Ongeldig gebruikers-id." + }, + { + "id": "model.draft.is_valid.update_at.app_error", + "translation": "Het veld 'update at' moet een geldige tijd zijn." + }, + { + "id": "model.draft.is_valid.root_id.app_error", + "translation": "Ongeldig root id." + }, + { + "id": "model.draft.is_valid.props.app_error", + "translation": "Ongeldige eigenschappen." + }, + { + "id": "model.draft.is_valid.priority.app_error", + "translation": "Ongeldige prioriteit" + }, + { + "id": "model.draft.is_valid.msg.app_error", + "translation": "Ongeldig bericht." + }, + { + "id": "model.draft.is_valid.file_ids.app_error", + "translation": "Ongeldige bestandids." + }, + { + "id": "model.draft.is_valid.create_at.app_error", + "translation": "Create at moet een geldige tijd zijn." + }, + { + "id": "model.acknowledgement.is_valid.user_id.app_error", + "translation": "Ongeldig gebruikersid." + }, + { + "id": "model.draft.is_valid.channel_id.app_error", + "translation": "Ongeldig kanaalid." + }, + { + "id": "model.acknowledgement.is_valid.post_id.app_error", + "translation": "Ongeldig bericht-id." + }, + { + "id": "app.worktemplates.get_templates.app_error", + "translation": "Kon geen werksjablonen ophalen" + }, + { + "id": "app.worktemplates.get_categories.app_error", + "translation": "Kan geen werksjablooncategorieën ophalen" + }, + { + "id": "app.post_prority.get_for_post.app_error", + "translation": "Kon geen berichtprioriteit ophalen voor bericht" + }, + { + "id": "app.draft.update.app_error", + "translation": "Kan het concept niet bijwerken." + }, + { + "id": "app.draft.save.app_error", + "translation": "Kan het concept niet opslaan." + }, + { + "id": "app.draft.get_for_draft.app_error", + "translation": "Kan geen bestanden ophalen voor concept." + }, + { + "id": "app.draft.get_drafts.app_error", + "translation": "Kon de Concepten van de gebruiker niet ophalen." + }, + { + "id": "app.draft.get.app_error", + "translation": "Kon het concept niet ophalen." + }, + { + "id": "app.draft.feature_disabled", + "translation": "De Conceptfunctie is uitgeschakeld." + }, + { + "id": "app.draft.delete.app_error", + "translation": "Kan het concept niet verwijderen." + }, + { + "id": "app.channel.get_priority_for_posts.app_error", + "translation": "Fout bij het ophalen van de prioriteiten voor berichten" + }, + { + "id": "app.channel.count_urgent_posts_since.app_error", + "translation": "Fout bij het tellen van de dringende berichten sinds de opgegeven datum." + }, + { + "id": "app.acknowledgement.save.save.app_error", + "translation": "Fout hij het bewaren van de bevestiging voor het bericht." + }, + { + "id": "app.acknowledgement.getforpost.get.app_error", + "translation": "Fout hij het ophalen van de bevestiging voor bericht." + }, + { + "id": "app.acknowledgement.get.app_error", + "translation": "Fout hij het ophalen van de bevestiging." + }, + { + "id": "app.acknowledgement.delete.app_error", + "translation": "Kan bevestiging niet verwijderen." + }, + { + "id": "api.user.get_users.validation.app_error", + "translation": "Fout bij het ophalen van rollen tijdens de validatie." + }, + { + "id": "api.upload.create.upload_too_large.app_error", + "translation": "Kan bestand niet uploaden. Bestand is te groot." + }, + { + "id": "api.templates.cloud_welcome_email.yearly_plan_button", + "translation": "Bekijk jouw factuur" + }, + { + "id": "api.templates.cloud_upgrade_confirmation_yearly.subtitle", + "translation": "Jouw {{.WorkspaceName}} werkruimte is nu opgewaardeerd." + }, + { + "id": "api.templates.cloud_upgrade_confirmation_monthly.subtitle", + "translation": "Jouw {{.WorkspaceName}} werkruimte is nu geüpgraded. Dit zal gefactureerd worden vanaf {{.Date}}" + }, + { + "id": "api.server.hosted_signup_unavailable.error", + "translation": "Portaal niet beschikbaar voor self-hosted signup." + }, + { + "id": "api.drafts.disabled.app_error", + "translation": "De Conceptfunctie is uitgeschakeld." + }, + { + "id": "api.draft.create_draft.can_not_draft_to_deleted.error", + "translation": "Kan concept niet bewaren in een verwijderd kanaal" } ] From 0a0ff0b2d88526df8477ce3f30c39c3da63aa3ac Mon Sep 17 00:00:00 2001 From: MArtin Johnson Date: Tue, 27 Dec 2022 12:08:30 +0100 Subject: [PATCH 27/37] Translated using Weblate (Swedish) Currently translated at 100.0% (2430 of 2430 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/sv/ --- i18n/sv.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/i18n/sv.json b/i18n/sv.json index 3d3d19a447..1e3e7f0b36 100644 --- a/i18n/sv.json +++ b/i18n/sv.json @@ -9729,5 +9729,9 @@ { "id": "model.draft.is_valid.props.app_error", "translation": "Ogiltiga attribut." + }, + { + "id": "api.server.hosted_signup_unavailable.error", + "translation": "Portalen är inte tillgänglig för egen-hostad registrering." } ] From badbb4908981a0e992e82f60b56e336ca0ca65b8 Mon Sep 17 00:00:00 2001 From: Ji-Hyeon Gim Date: Tue, 27 Dec 2022 12:08:30 +0100 Subject: [PATCH 28/37] Translated using Weblate (Korean) Currently translated at 82.3% (2001 of 2430 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/ko/ --- i18n/ko.json | 86 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 85 insertions(+), 1 deletion(-) diff --git a/i18n/ko.json b/i18n/ko.json index 82c9a20ffd..69d0686554 100644 --- a/i18n/ko.json +++ b/i18n/ko.json @@ -7929,7 +7929,7 @@ }, { "id": "api.cloud.delinquency_email.missing_email_to_trigger", - "translation": "연체 이메일을 보내기 위한 필수 항목이 누락되었습니다." + "translation": "연체 알림 전자우편을 보내기 위한 필수 항목이 누락되었습니다." }, { "id": "api.error_set_first_admin_complete_setup", @@ -7958,5 +7958,89 @@ { "id": "sharedchannel.cannot_deliver_post", "translation": "{{.Remote}} 원격 사이트가 오프라인이기 때문에 하나 또는 그 이상의 포스트가 전송되지 않았습니다. 포스트는 해당 사이트가 온라인일 때 전송될 것입니다." + }, + { + "id": "app.notification.body.thread.title", + "translation": "{{.SenderName}}님이 글타래에 답장을 남겼습니다" + }, + { + "id": "app.notification.body.thread_channel.subTitle", + "translation": "자리를 비운 동안, {{.SenderName}}님이 지켜보는 중인 글타래에 답장을 남겼습니다." + }, + { + "id": "app.notification.body.thread_channel_full.subTitle", + "translation": "자리를 비운 동안, {{.SenderName}}님이 {{.ChannelName}} 채널에 있는 지켜보는 중인 글타래에 답장을 남겼습니다." + }, + { + "id": "app.notification.body.thread_gm.subTitle", + "translation": "자리를 비운 동안, {{.SenderName}}님이 그룹 글타래에 답장을 남겼습니다." + }, + { + "id": "app.notification.body.thread_dm.subTitle", + "translation": "자리를 비운 동안, {{.SenderName}}님이 당신이 직접 보낸 메시지에 답장을 남겼습니다." + }, + { + "id": "app.channel.autofollow.app_error", + "translation": "언급된 사용자의 글타래 권한을 갱신하지 못했습니다" + }, + { + "id": "api.getThreadsForUser.bad_params", + "translation": "getThreadsForUser의 Before와 After 매개변수는 상호 배타적입니다" + }, + { + "id": "api.getThreadsForUser.bad_only_params", + "translation": "getThreadsForUser의 OnlyThreads와 OnlyTotals 매개변수는 상호 배타적입니다" + }, + { + "id": "app.post.get_top_threads_for_team_since.app_error", + "translation": "팀의 상위 글타래를 가져올 수 없습니다." + }, + { + "id": "app.post.get_top_threads_for_user_since.app_error", + "translation": "사용자의 상위 글타래를 가져올 수 없습니다." + }, + { + "id": "app.user.get_thread_count_for_user.app_error", + "translation": "사용자의 글타래 개수를 가져올 수 없습니다." + }, + { + "id": "app.user.get_thread_membership_for_user.app_error", + "translation": "사용자 글타래 권한을 가져올 수 없습니다" + }, + { + "id": "app.user.get_thread_membership_for_user.not_found", + "translation": "사용자 글타래 권한이 없습니다" + }, + { + "id": "app.user.get_threads_for_user.app_error", + "translation": "사용자 글타래들을 가져올 수 없습니다" + }, + { + "id": "app.user.get_threads_for_user.not_found", + "translation": "사용자 글타래가 존재하지 않거나 지켜보고 있지 않습니다" + }, + { + "id": "app.user.update_thread_follow_for_user.app_error", + "translation": "글타래의 지켜보기 상태를 갱신할 수 없습니다" + }, + { + "id": "app.user.update_thread_read_for_user.app_error", + "translation": "글타래의 읽음 상태를 갱신할 수 없습니다" + }, + { + "id": "app.user.update_thread_read_for_user_by_post.app_error", + "translation": "유효하지 않은 post_id" + }, + { + "id": "model.config.is_valid.collapsed_threads.app_error", + "translation": "CollapsedThreads 설정은 disabled, default_on 혹은 default_off 중 하나여야만 합니다" + }, + { + "id": "model.config.is_valid.collapsed_threads.autofollow.app_error", + "translation": "CollapsedThreads 기능을 활성화하려면 ThreadAutoFollow 기능이 활성화되어야 합니다" + }, + { + "id": "app.user.update_threads_read_for_user.app_error", + "translation": "모든 사용자 글타래들을 읽음 상태로 변경할 수 없습니다" } ] From ef39d50cd5c91481c3509a38e9ea05fdf4c9b3bc Mon Sep 17 00:00:00 2001 From: Matthew Williams Date: Tue, 27 Dec 2022 12:08:30 +0100 Subject: [PATCH 29/37] Translated using Weblate (English (Australia)) Currently translated at 99.6% (2421 of 2430 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/en_AU/ --- i18n/en_AU.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/i18n/en_AU.json b/i18n/en_AU.json index 3283b75397..484634dbdd 100644 --- a/i18n/en_AU.json +++ b/i18n/en_AU.json @@ -9693,5 +9693,9 @@ { "id": "api.user.get_users.validation.app_error", "translation": "Error fetching roles during validation." + }, + { + "id": "api.server.hosted_signup_unavailable.error", + "translation": "Portal unavailable for self-hosted signup." } ] From 5e4d787766ddbc51fbed26f82ee1b3488e7ed6f1 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Tue, 27 Dec 2022 12:08:31 +0100 Subject: [PATCH 30/37] Update translation files Updated by "Cleanup translation files" hook in Weblate. Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/ Update translation files Updated by "Cleanup translation files" hook in Weblate. Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/ --- i18n/bg.json | 8 -------- i18n/de.json | 8 -------- i18n/en_AU.json | 8 -------- i18n/es.json | 8 -------- i18n/fa.json | 8 -------- i18n/fr.json | 8 -------- i18n/hu.json | 8 -------- i18n/it.json | 8 -------- i18n/ja.json | 8 -------- i18n/ko.json | 8 -------- i18n/nl.json | 8 -------- i18n/pl.json | 8 -------- i18n/pt-BR.json | 8 -------- i18n/ro.json | 8 -------- i18n/ru.json | 8 -------- i18n/sv.json | 8 -------- i18n/tr.json | 8 -------- i18n/uk.json | 8 -------- i18n/zh-CN.json | 8 -------- i18n/zh-TW.json | 8 -------- 20 files changed, 160 deletions(-) diff --git a/i18n/bg.json b/i18n/bg.json index 1e2ac3b534..f9fa12c015 100644 --- a/i18n/bg.json +++ b/i18n/bg.json @@ -1887,14 +1887,6 @@ "id": "ent.jobs.start_synchronize_job.timeout", "translation": "Изтече времето на AD/LDAP задачата за синхронизация." }, - { - "id": "ent.jobs.do_job.batch_start_timestamp.parse_error", - "translation": "Не може да се анализира заданието за износ на съобщения ExportFromTimestamp." - }, - { - "id": "ent.jobs.do_job.batch_size.parse_error", - "translation": "Не може да се анализира заданието за износ на съобщения BatchSize." - }, { "id": "ent.id_loaded.license_disable.app_error", "translation": "Лицензът ви не поддържа ID заредени изскачащи известия." diff --git a/i18n/de.json b/i18n/de.json index 7daafc82da..82a8cccb82 100644 --- a/i18n/de.json +++ b/i18n/de.json @@ -6355,14 +6355,6 @@ "id": "ent.jobs.start_synchronize_job.timeout", "translation": "AD/LDAP-Synchronisierungs-Job-Timeout erreicht." }, - { - "id": "ent.jobs.do_job.batch_start_timestamp.parse_error", - "translation": "Konnte ExportFromTimestamp des Nachrichten-Export-Jobs nicht verarbeiten." - }, - { - "id": "ent.jobs.do_job.batch_size.parse_error", - "translation": "Konnte BatchSize des Nachrichten-Export-Jobs nicht verarbeiten." - }, { "id": "ent.cluster.404.app_error", "translation": "Cluster-API-Endpunkt nicht gefunden." diff --git a/i18n/en_AU.json b/i18n/en_AU.json index 484634dbdd..e03b05824f 100644 --- a/i18n/en_AU.json +++ b/i18n/en_AU.json @@ -3251,14 +3251,6 @@ "id": "ent.jobs.start_synchronize_job.timeout", "translation": "Reached AD/LDAP synchronisation job timeout." }, - { - "id": "ent.jobs.do_job.batch_start_timestamp.parse_error", - "translation": "Could not parse message export job ExportFromTimestamp." - }, - { - "id": "ent.jobs.do_job.batch_size.parse_error", - "translation": "Could not parse message export job BatchSize." - }, { "id": "ent.id_loaded.license_disable.app_error", "translation": "Your licence does not support ID Loaded Push Notifications." diff --git a/i18n/es.json b/i18n/es.json index 68dd0230be..4de9de9d37 100644 --- a/i18n/es.json +++ b/i18n/es.json @@ -6571,14 +6571,6 @@ "id": "ent.jobs.start_synchronize_job.timeout", "translation": "El tiempo de espera del trabajo de sincronización AD/LDAP fue alcanzado." }, - { - "id": "ent.jobs.do_job.batch_start_timestamp.parse_error", - "translation": "No se pudo interpretar la fecha y hora de inicio de la exportación de mensajes." - }, - { - "id": "ent.jobs.do_job.batch_size.parse_error", - "translation": "No se pudo interpretar el tamaño de bloque de datos de la exportación de mensajes." - }, { "id": "ent.cluster.404.app_error", "translation": "No se encontró el endpoint del API para el agrupamiento de servidores." diff --git a/i18n/fa.json b/i18n/fa.json index cdee577b6c..f348528ffb 100644 --- a/i18n/fa.json +++ b/i18n/fa.json @@ -1919,14 +1919,6 @@ "id": "ent.jobs.start_synchronize_job.timeout", "translation": "مهلت زمانی هماهنگ سازی AD/LDAP رسیده است." }, - { - "id": "ent.jobs.do_job.batch_start_timestamp.parse_error", - "translation": "تجزیه و تحلیل کار صادرات پیام ExportFromTimestamp امکان پذیر نیست." - }, - { - "id": "ent.jobs.do_job.batch_size.parse_error", - "translation": "تجزیه و تحلیل کار صادرات پیام صادرات از مهر زمان انجام نمی شود.." - }, { "id": "ent.id_loaded.license_disable.app_error", "translation": "مجوز شما از ID Loaded Push Notifications پشتیبانی نمی کند." diff --git a/i18n/fr.json b/i18n/fr.json index 107121d484..356a12a5e3 100644 --- a/i18n/fr.json +++ b/i18n/fr.json @@ -6343,14 +6343,6 @@ "id": "ent.jobs.start_synchronize_job.timeout", "translation": "Temps d'attente pour la tâche de synchronisation AD/LDAP atteint." }, - { - "id": "ent.jobs.do_job.batch_start_timestamp.parse_error", - "translation": "Impossible d'interpréter le paramètre ExportFromTimestamp de la tâche d'exportation de messages." - }, - { - "id": "ent.jobs.do_job.batch_size.parse_error", - "translation": "Impossible d'interpréter le paramètre BatchSize de la tâche d'exportation de messages." - }, { "id": "ent.cluster.404.app_error", "translation": "Le nœud d'API cluster est introuvable." diff --git a/i18n/hu.json b/i18n/hu.json index 3ddcd9f7b7..c010bb1704 100644 --- a/i18n/hu.json +++ b/i18n/hu.json @@ -3291,14 +3291,6 @@ "id": "model.upload_session.is_valid.id.app_error", "translation": "Érvénytelen érték a Id -nak" }, - { - "id": "ent.jobs.do_job.batch_start_timestamp.parse_error", - "translation": "Nem sikerült feldolgozni az üzenet export munka ExportFromTimestamp értékét." - }, - { - "id": "ent.jobs.do_job.batch_size.parse_error", - "translation": "Nem sikerült feldolgozni az üzenet export BatchSize értékét." - }, { "id": "ent.id_loaded.license_disable.app_error", "translation": "Az Ön licensze nem támogatja az azonosítóval betöltött push értesítéseket." diff --git a/i18n/it.json b/i18n/it.json index 762c7d9bcc..14f201c891 100644 --- a/i18n/it.json +++ b/i18n/it.json @@ -6587,14 +6587,6 @@ "id": "ent.jobs.start_synchronize_job.timeout", "translation": "Raggiunto timeout nel lavoro di sincronizzazione AD/LDAP." }, - { - "id": "ent.jobs.do_job.batch_start_timestamp.parse_error", - "translation": "Impossibile analizzare il messaggio del lavoro di esportazione ExportFromTimestamp." - }, - { - "id": "ent.jobs.do_job.batch_size.parse_error", - "translation": "Impossibile analizzare il messaggio del lavoro di esportazione BatchSize." - }, { "id": "ent.cluster.404.app_error", "translation": "Cluster API non trovate." diff --git a/i18n/ja.json b/i18n/ja.json index 8e854aa2e9..7625ed5305 100644 --- a/i18n/ja.json +++ b/i18n/ja.json @@ -6563,14 +6563,6 @@ "id": "ent.jobs.start_synchronize_job.timeout", "translation": "AD/LDAP同期処理がタイムアウトしました。" }, - { - "id": "ent.jobs.do_job.batch_start_timestamp.parse_error", - "translation": "メッセージエクスポートジョブのExportFromTimestampを解析できませんでした。" - }, - { - "id": "ent.jobs.do_job.batch_size.parse_error", - "translation": "メッセージエクスポートジョブのBatchSizeを解析できませんでした。" - }, { "id": "ent.cluster.404.app_error", "translation": "クラスターAPIエンドポイントが見つかりませんでした。" diff --git a/i18n/ko.json b/i18n/ko.json index 69d0686554..ad4733a1aa 100644 --- a/i18n/ko.json +++ b/i18n/ko.json @@ -6519,14 +6519,6 @@ "id": "ent.jobs.start_synchronize_job.timeout", "translation": "Reached AD/LDAP synchronization job timeout." }, - { - "id": "ent.jobs.do_job.batch_start_timestamp.parse_error", - "translation": "Could not parse message export job ExportFromTimestamp." - }, - { - "id": "ent.jobs.do_job.batch_size.parse_error", - "translation": "Could not parse message export job BatchSize." - }, { "id": "ent.cluster.404.app_error", "translation": "Cluster API endpoint not found." diff --git a/i18n/nl.json b/i18n/nl.json index f24f00debb..45c0f6813e 100644 --- a/i18n/nl.json +++ b/i18n/nl.json @@ -6603,14 +6603,6 @@ "id": "ent.jobs.start_synchronize_job.timeout", "translation": "Time-out voor synchronisatie van AD/LDAP werd bereikt." }, - { - "id": "ent.jobs.do_job.batch_start_timestamp.parse_error", - "translation": "Fout bij het verwerken van bericht export taak ExportFromTimeStamp." - }, - { - "id": "ent.jobs.do_job.batch_size.parse_error", - "translation": "Fout bij ontleden van bericht exporttaak BatchSize." - }, { "id": "ent.cluster.404.app_error", "translation": "Cluster API endpoint niet gevonden." diff --git a/i18n/pl.json b/i18n/pl.json index cc23eeb202..ae492d4db1 100644 --- a/i18n/pl.json +++ b/i18n/pl.json @@ -6351,14 +6351,6 @@ "id": "ent.jobs.start_synchronize_job.timeout", "translation": "Osiągnięto limit czasu zadania synchronizacji AD/LDAP." }, - { - "id": "ent.jobs.do_job.batch_start_timestamp.parse_error", - "translation": "Nie można przeanalizować zadania eksportowania komunikatu ExportFromTimestamp." - }, - { - "id": "ent.jobs.do_job.batch_size.parse_error", - "translation": "Nie można przeanalizować zadania eksportu komunikatu BatchSize." - }, { "id": "ent.cluster.404.app_error", "translation": "Nie znaleziono punktu końcowego interfejsu API klastra." diff --git a/i18n/pt-BR.json b/i18n/pt-BR.json index 134b851682..81fed091ea 100644 --- a/i18n/pt-BR.json +++ b/i18n/pt-BR.json @@ -6571,14 +6571,6 @@ "id": "ent.jobs.start_synchronize_job.timeout", "translation": "Tarefa de sincronização do AD/LDAP alcançou o limite de tempo." }, - { - "id": "ent.jobs.do_job.batch_start_timestamp.parse_error", - "translation": "Não foi possível analisar a tarefa de exportação de mensagens ExportFromTimestamp." - }, - { - "id": "ent.jobs.do_job.batch_size.parse_error", - "translation": "Não foi possível analisar a tarefa de exportação de mensagens BatchSize." - }, { "id": "ent.cluster.404.app_error", "translation": "Endpoint Cluster API não encontrado." diff --git a/i18n/ro.json b/i18n/ro.json index f2686f070c..a7ec824541 100644 --- a/i18n/ro.json +++ b/i18n/ro.json @@ -6619,14 +6619,6 @@ "id": "ent.jobs.start_synchronize_job.timeout", "translation": "Am atins intervalul de timp pentru lucrarea de sincronizare AD/LDAP." }, - { - "id": "ent.jobs.do_job.batch_start_timestamp.parse_error", - "translation": "Ar putea analiza mesaj export job ExportFromTimestamp." - }, - { - "id": "ent.jobs.do_job.batch_size.parse_error", - "translation": "Ar putea analiza mesaj export job BatchSize." - }, { "id": "ent.cluster.404.app_error", "translation": "Clustering API final nu a fost găsit." diff --git a/i18n/ru.json b/i18n/ru.json index b31289ad9c..62022682fd 100644 --- a/i18n/ru.json +++ b/i18n/ru.json @@ -6615,14 +6615,6 @@ "id": "ent.jobs.start_synchronize_job.timeout", "translation": "Достигнут тайм-аут задания синхронизации AD/LDAP." }, - { - "id": "ent.jobs.do_job.batch_start_timestamp.parse_error", - "translation": "Не удалось разобрать задание экспорта сообщения ExportFromTimestamp." - }, - { - "id": "ent.jobs.do_job.batch_size.parse_error", - "translation": "Не удалось проанализировать задание экспорта сообщения BatchSize." - }, { "id": "ent.cluster.404.app_error", "translation": "Не найдена конечная точка API кластера." diff --git a/i18n/sv.json b/i18n/sv.json index 1e3e7f0b36..f5b180b542 100644 --- a/i18n/sv.json +++ b/i18n/sv.json @@ -3039,14 +3039,6 @@ "id": "ent.jobs.start_synchronize_job.timeout", "translation": "Nådde timeout för AD/LDAP-synkronisering." }, - { - "id": "ent.jobs.do_job.batch_start_timestamp.parse_error", - "translation": "Kunde inte tolka värdet ExportFromTimestamp i export job." - }, - { - "id": "ent.jobs.do_job.batch_size.parse_error", - "translation": "Kunde inte tolka värdet BatchSize i export job." - }, { "id": "ent.id_loaded.license_disable.app_error", "translation": "Din licens tillåter inte pushnotifiering via meddelande-ID." diff --git a/i18n/tr.json b/i18n/tr.json index 39a1074bc0..2eadae012a 100644 --- a/i18n/tr.json +++ b/i18n/tr.json @@ -6603,14 +6603,6 @@ "id": "ent.jobs.start_synchronize_job.timeout", "translation": "AD/LDAP eşitleme görevi zaman aşımına uğradı." }, - { - "id": "ent.jobs.do_job.batch_start_timestamp.parse_error", - "translation": "İleti dışa aktarma görevinde ExportFromTimestamp değeri çözümlenemedi." - }, - { - "id": "ent.jobs.do_job.batch_size.parse_error", - "translation": "İleti dışa aktarma görevinde BatchSize değeri çözümlenemedi." - }, { "id": "ent.cluster.404.app_error", "translation": "Küme API uç noktası bulunamadı." diff --git a/i18n/uk.json b/i18n/uk.json index cbba647d9d..7ffd8d2e6d 100644 --- a/i18n/uk.json +++ b/i18n/uk.json @@ -6335,14 +6335,6 @@ "id": "ent.jobs.start_synchronize_job.timeout", "translation": "Досягнута тайм-аут завдання синхронізації AD / LDAP." }, - { - "id": "ent.jobs.do_job.batch_start_timestamp.parse_error", - "translation": "Не вдалося проаналізувати завдання експортування повідомлень ExportFromTimestamp." - }, - { - "id": "ent.jobs.do_job.batch_size.parse_error", - "translation": "Не вдалося проаналізувати BatchSize завдання експортування повідомлення." - }, { "id": "ent.cluster.404.app_error", "translation": "Не знайдена кінцева точка API кластера." diff --git a/i18n/zh-CN.json b/i18n/zh-CN.json index 2d91449bce..4fb33345ac 100644 --- a/i18n/zh-CN.json +++ b/i18n/zh-CN.json @@ -6479,14 +6479,6 @@ "id": "ent.jobs.start_synchronize_job.timeout", "translation": "AD/LDAP 同步任务超时。" }, - { - "id": "ent.jobs.do_job.batch_start_timestamp.parse_error", - "translation": "无法解析 ExportFromTimestamp 导出任务消息。" - }, - { - "id": "ent.jobs.do_job.batch_size.parse_error", - "translation": "无法解析 BatchSize 导出任务消息。" - }, { "id": "ent.cluster.404.app_error", "translation": "未找到机群 API 接口。" diff --git a/i18n/zh-TW.json b/i18n/zh-TW.json index e1a171691e..caedf2787b 100644 --- a/i18n/zh-TW.json +++ b/i18n/zh-TW.json @@ -6539,14 +6539,6 @@ "id": "ent.jobs.start_synchronize_job.timeout", "translation": "AD/LDAP 同步工作逾時。" }, - { - "id": "ent.jobs.do_job.batch_start_timestamp.parse_error", - "translation": "無法解析訊息匯出工作 ExportFromTimestamp。" - }, - { - "id": "ent.jobs.do_job.batch_size.parse_error", - "translation": "無法解析訊息匯出工作 BatchSize。" - }, { "id": "ent.cluster.404.app_error", "translation": "找不到叢集 API 端點。" From 93f17980a113f3f476a0614be367a52b17f49f89 Mon Sep 17 00:00:00 2001 From: Agniva De Sarker Date: Thu, 29 Dec 2022 12:21:25 +0530 Subject: [PATCH 31/37] Remove unnecessary debug log (#21959) This log is continuously spamming our community server and I see it all the time in my dev environment. Let's remove this to keep the logs clean. ```release-note NONE ``` --- api4/drafts.go | 1 - 1 file changed, 1 deletion(-) diff --git a/api4/drafts.go b/api4/drafts.go index 102c164b8f..c3640f7a84 100644 --- a/api4/drafts.go +++ b/api4/drafts.go @@ -124,7 +124,6 @@ func deleteDraft(c *Context, w http.ResponseWriter, r *http.Request) { switch { case err.StatusCode == http.StatusNotFound: // If the draft doesn't exist in the server, we don't need to delete. - mlog.Debug("Unable to find the draft", mlog.Err(err)) ReturnStatusOK(w) default: c.Err = err From babe01ee2ed4440499a699519b6407ad5de515d1 Mon Sep 17 00:00:00 2001 From: Vishal Date: Fri, 30 Dec 2022 14:41:31 +0530 Subject: [PATCH 32/37] remove crt feature flag (#21788) Co-authored-by: Mattermod --- api4/channel_test.go | 3 --- api4/post_test.go | 3 --- api4/user_test.go | 20 ++++---------------- app/channel_test.go | 9 ++------- app/notification.go | 2 +- app/notification_test.go | 4 +--- app/post_test.go | 16 ---------------- app/user_test.go | 3 --- model/feature_flags.go | 4 ---- 9 files changed, 8 insertions(+), 56 deletions(-) diff --git a/api4/channel_test.go b/api4/channel_test.go index 16e432bf0d..6f8f5aeabe 100644 --- a/api4/channel_test.go +++ b/api4/channel_test.go @@ -8,7 +8,6 @@ import ( "encoding/json" "fmt" "net/http" - "os" "sort" "strings" "sync" @@ -4584,8 +4583,6 @@ func TestViewChannelWithoutCollapsedThreads(t *testing.T) { th := Setup(t).InitBasic() defer th.TearDown() - os.Setenv("MM_FEATUREFLAGS_COLLAPSEDTHREADS", "true") - defer os.Unsetenv("MM_FEATUREFLAGS_COLLAPSEDTHREADS") th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ThreadAutoFollow = true *cfg.ServiceSettings.CollapsedThreads = model.CollapsedThreadsDefaultOn diff --git a/api4/post_test.go b/api4/post_test.go index 8594fae136..7f3ec6c442 100644 --- a/api4/post_test.go +++ b/api4/post_test.go @@ -12,7 +12,6 @@ import ( "net/http" "net/http/httptest" "net/url" - "os" "reflect" "sort" "strings" @@ -2954,8 +2953,6 @@ func TestSetChannelUnread(t *testing.T) { } func TestSetPostUnreadWithoutCollapsedThreads(t *testing.T) { - os.Setenv("MM_FEATUREFLAGS_COLLAPSEDTHREADS", "true") - defer os.Unsetenv("MM_FEATUREFLAGS_COLLAPSEDTHREADS") th := Setup(t).InitBasic() defer th.TearDown() th.App.UpdateConfig(func(cfg *model.Config) { diff --git a/api4/user_test.go b/api4/user_test.go index affd768fd7..a20ae682a1 100644 --- a/api4/user_test.go +++ b/api4/user_test.go @@ -5794,8 +5794,6 @@ func TestUpdatePassword(t *testing.T) { func TestGetThreadsForUser(t *testing.T) { os.Setenv("MM_FEATUREFLAGS_POSTPRIORITY", "true") defer os.Unsetenv("MM_FEATUREFLAGS_POSTPRIORITY") - os.Setenv("MM_FEATUREFLAGS_COLLAPSEDTHREADS", "true") - defer os.Unsetenv("MM_FEATUREFLAGS_COLLAPSEDTHREADS") th := Setup(t).InitBasic() defer th.TearDown() @@ -6151,8 +6149,6 @@ func TestGetThreadsForUser(t *testing.T) { func TestThreadSocketEvents(t *testing.T) { th := Setup(t).InitBasic() defer th.TearDown() - os.Setenv("MM_FEATUREFLAGS_COLLAPSEDTHREADS", "true") - defer os.Unsetenv("MM_FEATUREFLAGS_COLLAPSEDTHREADS") th.ConfigStore.SetReadOnlyFF(false) defer th.ConfigStore.SetReadOnlyFF(true) @@ -6533,8 +6529,6 @@ func TestMaintainUnreadRepliesInThread(t *testing.T) { defer th.UnlinkUserFromTeam(th.SystemAdminUser, th.BasicTeam) th.AddUserToChannel(th.SystemAdminUser, th.BasicChannel) defer th.RemoveUserFromChannel(th.SystemAdminUser, th.BasicChannel) - os.Setenv("MM_FEATUREFLAGS_COLLAPSEDTHREADS", "true") - defer os.Unsetenv("MM_FEATUREFLAGS_COLLAPSEDTHREADS") th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ThreadAutoFollow = true *cfg.ServiceSettings.CollapsedThreads = model.CollapsedThreadsDefaultOn @@ -6590,8 +6584,7 @@ func TestMaintainUnreadRepliesInThread(t *testing.T) { func TestThreadCounts(t *testing.T) { th := Setup(t).InitBasic() defer th.TearDown() - os.Setenv("MM_FEATUREFLAGS_COLLAPSEDTHREADS", "true") - defer os.Unsetenv("MM_FEATUREFLAGS_COLLAPSEDTHREADS") + th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ThreadAutoFollow = true *cfg.ServiceSettings.CollapsedThreads = model.CollapsedThreadsDefaultOn @@ -6633,8 +6626,6 @@ func TestThreadCounts(t *testing.T) { func TestSingleThreadGet(t *testing.T) { os.Setenv("MM_FEATUREFLAGS_POSTPRIORITY", "true") defer os.Unsetenv("MM_FEATUREFLAGS_POSTPRIORITY") - os.Setenv("MM_FEATUREFLAGS_COLLAPSEDTHREADS", "true") - defer os.Unsetenv("MM_FEATUREFLAGS_COLLAPSEDTHREADS") th := Setup(t).InitBasic() defer th.TearDown() @@ -6704,8 +6695,7 @@ func TestMaintainUnreadMentionsInThread(t *testing.T) { th.AddUserToChannel(th.SystemAdminUser, th.BasicChannel) defer th.RemoveUserFromChannel(th.SystemAdminUser, th.BasicChannel) client := th.Client - os.Setenv("MM_FEATUREFLAGS_COLLAPSEDTHREADS", "true") - defer os.Unsetenv("MM_FEATUREFLAGS_COLLAPSEDTHREADS") + th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ThreadAutoFollow = true *cfg.ServiceSettings.CollapsedThreads = model.CollapsedThreadsDefaultOn @@ -6768,8 +6758,7 @@ func TestMaintainUnreadMentionsInThread(t *testing.T) { func TestReadThreads(t *testing.T) { th := Setup(t).InitBasic() defer th.TearDown() - os.Setenv("MM_FEATUREFLAGS_COLLAPSEDTHREADS", "true") - defer os.Unsetenv("MM_FEATUREFLAGS_COLLAPSEDTHREADS") + th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ThreadAutoFollow = true *cfg.ServiceSettings.CollapsedThreads = model.CollapsedThreadsDefaultOn @@ -6872,8 +6861,7 @@ func TestReadThreads(t *testing.T) { func TestMarkThreadUnreadMentionCount(t *testing.T) { th := Setup(t).InitBasic() defer th.TearDown() - os.Setenv("MM_FEATUREFLAGS_COLLAPSEDTHREADS", "true") - defer os.Unsetenv("MM_FEATUREFLAGS_COLLAPSEDTHREADS") + th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ThreadAutoFollow = true *cfg.ServiceSettings.CollapsedThreads = model.CollapsedThreadsDefaultOn diff --git a/app/channel_test.go b/app/channel_test.go index bf21274763..af162e45b9 100644 --- a/app/channel_test.go +++ b/app/channel_test.go @@ -8,7 +8,6 @@ import ( "errors" "fmt" "net/http" - "os" "sort" "strings" "sync" @@ -2127,8 +2126,7 @@ func TestViewChannelCollapsedThreadsTurnedOff(t *testing.T) { th.AddUserToChannel(u2, c1) // Enable CRT - os.Setenv("MM_FEATUREFLAGS_COLLAPSEDTHREADS", "true") - defer os.Unsetenv("MM_FEATUREFLAGS_COLLAPSEDTHREADS") + th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ThreadAutoFollow = true *cfg.ServiceSettings.CollapsedThreads = model.CollapsedThreadsDefaultOn @@ -2198,8 +2196,6 @@ func TestViewChannelCollapsedThreadsTurnedOff(t *testing.T) { func TestMarkChannelAsUnreadFromPostCollapsedThreadsTurnedOff(t *testing.T) { // Enable CRT - os.Setenv("MM_FEATUREFLAGS_COLLAPSEDTHREADS", "true") - defer os.Unsetenv("MM_FEATUREFLAGS_COLLAPSEDTHREADS") th := Setup(t).InitBasic() defer th.TearDown() @@ -2286,8 +2282,7 @@ func TestMarkChannelAsUnreadFromPostCollapsedThreadsTurnedOff(t *testing.T) { } func TestMarkUnreadCRTOffUpdatesThreads(t *testing.T) { - os.Setenv("MM_FEATUREFLAGS_COLLAPSEDTHREADS", "true") - defer os.Unsetenv("MM_FEATUREFLAGS_COLLAPSEDTHREADS") + th := Setup(t).InitBasic() defer th.TearDown() th.App.UpdateConfig(func(cfg *model.Config) { diff --git a/app/notification.go b/app/notification.go index cd275134d4..215945405c 100644 --- a/app/notification.go +++ b/app/notification.go @@ -43,7 +43,7 @@ func (a *App) SendNotifications(c request.CTX, post *model.Post, team *model.Tea return []string{}, nil } - isCRTAllowed := a.Config().FeatureFlags.CollapsedThreads && *a.Config().ServiceSettings.CollapsedThreads != model.CollapsedThreadsDisabled + isCRTAllowed := *a.Config().ServiceSettings.CollapsedThreads != model.CollapsedThreadsDisabled pchan := make(chan store.StoreResult, 1) go func() { diff --git a/app/notification_test.go b/app/notification_test.go index 1563be191d..f00c038dac 100644 --- a/app/notification_test.go +++ b/app/notification_test.go @@ -5,7 +5,6 @@ package app import ( "fmt" - "os" "testing" "github.com/stretchr/testify/assert" @@ -2734,8 +2733,7 @@ func TestReplyPostNotificationsWithCRT(t *testing.T) { }() // Enable CRT - os.Setenv("MM_FEATUREFLAGS_COLLAPSEDTHREADS", "true") - defer os.Unsetenv("MM_FEATUREFLAGS_COLLAPSEDTHREADS") + th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ThreadAutoFollow = true *cfg.ServiceSettings.CollapsedThreads = model.CollapsedThreadsDefaultOn diff --git a/app/post_test.go b/app/post_test.go index 0b9c37a3f8..d3073fe302 100644 --- a/app/post_test.go +++ b/app/post_test.go @@ -2357,8 +2357,6 @@ func TestThreadMembership(t *testing.T) { func TestFollowThreadSkipsParticipants(t *testing.T) { th := Setup(t).InitBasic() defer th.TearDown() - os.Setenv("MM_FEATUREFLAGS_COLLAPSEDTHREADS", "true") - defer os.Unsetenv("MM_FEATUREFLAGS_COLLAPSEDTHREADS") th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ThreadAutoFollow = true @@ -2415,8 +2413,6 @@ func TestFollowThreadSkipsParticipants(t *testing.T) { func TestAutofollowBasedOnRootPost(t *testing.T) { th := Setup(t).InitBasic() defer th.TearDown() - os.Setenv("MM_FEATUREFLAGS_COLLAPSEDTHREADS", "true") - defer os.Unsetenv("MM_FEATUREFLAGS_COLLAPSEDTHREADS") th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ThreadAutoFollow = true @@ -2445,8 +2441,6 @@ func TestAutofollowBasedOnRootPost(t *testing.T) { func TestViewChannelShouldNotUpdateThreads(t *testing.T) { th := Setup(t).InitBasic() defer th.TearDown() - os.Setenv("MM_FEATUREFLAGS_COLLAPSEDTHREADS", "true") - defer os.Unsetenv("MM_FEATUREFLAGS_COLLAPSEDTHREADS") th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ThreadAutoFollow = true @@ -2528,12 +2522,6 @@ func TestCollapsedThreadFetch(t *testing.T) { }) t.Run("Should not panic on unexpected db error", func(t *testing.T) { - os.Setenv("MM_FEATUREFLAGS_COLLAPSEDTHREADS", "true") - defer os.Unsetenv("MM_FEATUREFLAGS_COLLAPSEDTHREADS") - th.App.UpdateConfig(func(cfg *model.Config) { - cfg.FeatureFlags.CollapsedThreads = true - }) - channel := th.CreateChannel(th.Context, th.BasicTeam) th.AddUserToChannel(user2, channel) defer th.App.DeleteChannel(th.Context, channel, user1.Id) @@ -2765,8 +2753,6 @@ func TestSharedChannelSyncForPostActions(t *testing.T) { func TestAutofollowOnPostingAfterUnfollow(t *testing.T) { th := Setup(t).InitBasic() defer th.TearDown() - os.Setenv("MM_FEATUREFLAGS_COLLAPSEDTHREADS", "true") - defer os.Unsetenv("MM_FEATUREFLAGS_COLLAPSEDTHREADS") th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ThreadAutoFollow = true @@ -2834,8 +2820,6 @@ func TestGetPostIfAuthorized(t *testing.T) { func TestShouldNotRefollowOnOthersReply(t *testing.T) { th := Setup(t).InitBasic() defer th.TearDown() - os.Setenv("MM_FEATUREFLAGS_COLLAPSEDTHREADS", "true") - defer os.Unsetenv("MM_FEATUREFLAGS_COLLAPSEDTHREADS") th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ThreadAutoFollow = true diff --git a/app/user_test.go b/app/user_test.go index 788ae11275..8a71680a20 100644 --- a/app/user_test.go +++ b/app/user_test.go @@ -8,7 +8,6 @@ import ( "context" "encoding/json" "errors" - "os" "path/filepath" "strings" "testing" @@ -1673,8 +1672,6 @@ func TestPatchUser(t *testing.T) { } func TestUpdateThreadReadForUser(t *testing.T) { - os.Setenv("MM_FEATUREFLAGS_COLLAPSEDTHREADS", "true") - defer os.Unsetenv("MM_FEATUREFLAGS_COLLAPSEDTHREADS") t.Run("Ensure thread membership is created and followed", func(t *testing.T) { th := Setup(t).InitBasic() diff --git a/model/feature_flags.go b/model/feature_flags.go index 27bab4830e..47dd10378d 100644 --- a/model/feature_flags.go +++ b/model/feature_flags.go @@ -16,9 +16,6 @@ type FeatureFlags struct { // all other values as false. TestBoolFeature bool - // Toggle on and off support for Collapsed Threads - CollapsedThreads bool - // Enable the remote cluster service for shared channels. EnableRemoteClusterService bool @@ -81,7 +78,6 @@ type FeatureFlags struct { func (f *FeatureFlags) SetDefaults() { f.TestFeature = "off" f.TestBoolFeature = false - f.CollapsedThreads = true f.EnableRemoteClusterService = false f.AppsEnabled = true f.PluginApps = "" From f1e96cb83076ba5f6ad6d3f9a73264e1baaa33f7 Mon Sep 17 00:00:00 2001 From: Luis Suarez Date: Mon, 2 Jan 2023 18:07:38 -0500 Subject: [PATCH 33/37] MM-48988 Log plugins version on install and on startup (#21907) --- app/plugin_install.go | 2 ++ plugin/environment.go | 2 ++ 2 files changed, 4 insertions(+) diff --git a/app/plugin_install.go b/app/plugin_install.go index 431c3abebe..b35982b82c 100644 --- a/app/plugin_install.go +++ b/app/plugin_install.go @@ -402,6 +402,8 @@ func (ch *Channels) installExtractedPlugin(manifest *model.Manifest, fromPluginD manifest = updatedManifest } + mlog.Debug("Installing plugin", mlog.String("plugin_id", manifest.Id), mlog.String("version", manifest.Version)) + return manifest, nil } diff --git a/plugin/environment.go b/plugin/environment.go index 4ccbbdf023..cf7553579d 100644 --- a/plugin/environment.go +++ b/plugin/environment.go @@ -330,6 +330,8 @@ func (env *Environment) Activate(id string) (manifest *model.Manifest, activated return nil, false, fmt.Errorf("unable to start plugin: must at least have a web app or server component") } + mlog.Debug("Plugin activated", mlog.String("plugin_id", pluginInfo.Manifest.Id), mlog.String("version", pluginInfo.Manifest.Version)) + return pluginInfo.Manifest, true, nil } From 1353ebc3abf34c8767bf776e618361b870145bca Mon Sep 17 00:00:00 2001 From: Nathaniel Allred Date: Tue, 3 Jan 2023 08:36:23 -0600 Subject: [PATCH 34/37] Mm 49258 - Inform admins trying to self-hosted purchase who the email listed on the JWT claim is (#21964) * add email of JWT claim to bootstrap signup response --- model/hosted_customer.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/model/hosted_customer.go b/model/hosted_customer.go index 0e1373a2bc..4f1917bdaf 100644 --- a/model/hosted_customer.go +++ b/model/hosted_customer.go @@ -10,6 +10,8 @@ type BootstrapSelfHostedSignupRequest struct { type BootstrapSelfHostedSignupResponse struct { Progress string `json:"progress"` + // email listed on the JWT claim + Email string `json:"email"` } type BootstrapSelfHostedSignupResponseInternal struct { From 6ad18615352fd9b7f36fb00072a25968e1abc00a Mon Sep 17 00:00:00 2001 From: Agniva De Sarker Date: Tue, 3 Jan 2023 20:14:16 +0530 Subject: [PATCH 35/37] MM-48345: Fix Critical level logging (#21952) Critical wasn't added in the StdLevels slice and due to that mlog.Critical wasn't printing anything. This was a complete blindspot that was missed. We have removed all references to mlog.Critical in the codebase and pointed Critical to be Fatal in the library. In v8, we will remove Critical altogether. ```release-note Servers with an encrypted key will throw an error during startup now. ``` --- app/migrations.go | 74 ++++++++++++++--------------- app/platform/metrics.go | 2 +- app/server.go | 6 +-- services/upgrader/upgrader_linux.go | 6 +-- shared/mlog/global.go | 9 ++-- shared/mlog/global_test.go | 14 ++---- 6 files changed, 51 insertions(+), 60 deletions(-) diff --git a/app/migrations.go b/app/migrations.go index 21295aa840..70c11b75fc 100644 --- a/app/migrations.go +++ b/app/migrations.go @@ -46,7 +46,7 @@ func (s *Server) doAdvancedPermissionsMigration() { // If this failed for reasons other than the role already existing, don't mark the migration as done. fetchedRole, err := s.Store().Role().GetByName(context.Background(), role.Name) if err != nil { - mlog.Critical("Failed to migrate role to database.", mlog.Err(err)) + mlog.Fatal("Failed to migrate role to database.", mlog.Err(err)) allSucceeded = false continue } @@ -59,7 +59,7 @@ func (s *Server) doAdvancedPermissionsMigration() { role.Id = fetchedRole.Id if _, err = s.Store().Role().Save(role); err != nil { // Role is not the same, but failed to update. - mlog.Critical("Failed to migrate role to database.", mlog.Err(err)) + mlog.Fatal("Failed to migrate role to database.", mlog.Err(err)) allSucceeded = false } } @@ -81,7 +81,7 @@ func (s *Server) doAdvancedPermissionsMigration() { } if err := s.Store().System().Save(&system); err != nil { - mlog.Critical("Failed to mark advanced permissions migration as completed.", mlog.Err(err)) + mlog.Fatal("Failed to mark advanced permissions migration as completed.", mlog.Err(err)) } } @@ -114,21 +114,21 @@ func (s *Server) doEmojisPermissionsMigration() { // Emoji creation is set to all by default role, err = s.GetRoleByName(context.Background(), model.SystemUserRoleId) if err != nil { - mlog.Critical("Failed to migrate emojis creation permissions from mattermost config.", mlog.Err(err)) + mlog.Fatal("Failed to migrate emojis creation permissions from mattermost config.", mlog.Err(err)) return } if role != nil { role.Permissions = append(role.Permissions, model.PermissionCreateEmojis.Id, model.PermissionDeleteEmojis.Id) if _, nErr := s.Store().Role().Save(role); nErr != nil { - mlog.Critical("Failed to migrate emojis creation permissions from mattermost config.", mlog.Err(nErr)) + mlog.Fatal("Failed to migrate emojis creation permissions from mattermost config.", mlog.Err(nErr)) return } } systemAdminRole, err = s.GetRoleByName(context.Background(), model.SystemAdminRoleId) if err != nil { - mlog.Critical("Failed to migrate emojis creation permissions from mattermost config.", mlog.Err(err)) + mlog.Fatal("Failed to migrate emojis creation permissions from mattermost config.", mlog.Err(err)) return } @@ -138,7 +138,7 @@ func (s *Server) doEmojisPermissionsMigration() { model.PermissionDeleteOthersEmojis.Id, ) if _, err := s.Store().Role().Save(systemAdminRole); err != nil { - mlog.Critical("Failed to migrate emojis creation permissions from mattermost config.", mlog.Err(err)) + mlog.Fatal("Failed to migrate emojis creation permissions from mattermost config.", mlog.Err(err)) return } @@ -148,7 +148,7 @@ func (s *Server) doEmojisPermissionsMigration() { } if err := s.Store().System().Save(&system); err != nil { - mlog.Critical("Failed to mark emojis permissions migration as completed.", mlog.Err(err)) + mlog.Fatal("Failed to mark emojis permissions migration as completed.", mlog.Err(err)) } } @@ -167,26 +167,26 @@ func (s *Server) doGuestRolesCreationMigration() { allSucceeded := true if _, err := s.Store().Role().GetByName(context.Background(), model.ChannelGuestRoleId); err != nil { if _, err := s.Store().Role().Save(roles[model.ChannelGuestRoleId]); err != nil { - mlog.Critical("Failed to create new guest role to database.", mlog.Err(err)) + mlog.Fatal("Failed to create new guest role to database.", mlog.Err(err)) allSucceeded = false } } if _, err := s.Store().Role().GetByName(context.Background(), model.TeamGuestRoleId); err != nil { if _, err := s.Store().Role().Save(roles[model.TeamGuestRoleId]); err != nil { - mlog.Critical("Failed to create new guest role to database.", mlog.Err(err)) + mlog.Fatal("Failed to create new guest role to database.", mlog.Err(err)) allSucceeded = false } } if _, err := s.Store().Role().GetByName(context.Background(), model.SystemGuestRoleId); err != nil { if _, err := s.Store().Role().Save(roles[model.SystemGuestRoleId]); err != nil { - mlog.Critical("Failed to create new guest role to database.", mlog.Err(err)) + mlog.Fatal("Failed to create new guest role to database.", mlog.Err(err)) allSucceeded = false } } schemes, err := s.Store().Scheme().GetAllPage("", 0, 1000000) if err != nil { - mlog.Critical("Failed to get all schemes.", mlog.Err(err)) + mlog.Fatal("Failed to get all schemes.", mlog.Err(err)) allSucceeded = false } for _, scheme := range schemes { @@ -201,7 +201,7 @@ func (s *Server) doGuestRolesCreationMigration() { } if savedRole, err := s.Store().Role().Save(teamGuestRole); err != nil { - mlog.Critical("Failed to create new guest role for custom scheme.", mlog.Err(err)) + mlog.Fatal("Failed to create new guest role for custom scheme.", mlog.Err(err)) allSucceeded = false } else { scheme.DefaultTeamGuestRole = savedRole.Name @@ -217,7 +217,7 @@ func (s *Server) doGuestRolesCreationMigration() { } if savedRole, err := s.Store().Role().Save(channelGuestRole); err != nil { - mlog.Critical("Failed to create new guest role for custom scheme.", mlog.Err(err)) + mlog.Fatal("Failed to create new guest role for custom scheme.", mlog.Err(err)) allSucceeded = false } else { scheme.DefaultChannelGuestRole = savedRole.Name @@ -225,7 +225,7 @@ func (s *Server) doGuestRolesCreationMigration() { _, err := s.Store().Scheme().Save(scheme) if err != nil { - mlog.Critical("Failed to update custom scheme.", mlog.Err(err)) + mlog.Fatal("Failed to update custom scheme.", mlog.Err(err)) allSucceeded = false } } @@ -241,7 +241,7 @@ func (s *Server) doGuestRolesCreationMigration() { } if err := s.Store().System().Save(&system); err != nil { - mlog.Critical("Failed to mark guest roles creation migration as completed.", mlog.Err(err)) + mlog.Fatal("Failed to mark guest roles creation migration as completed.", mlog.Err(err)) } } @@ -260,19 +260,19 @@ func (s *Server) doSystemConsoleRolesCreationMigration() { allSucceeded := true if _, err := s.Store().Role().GetByName(context.Background(), model.SystemManagerRoleId); err != nil { if _, err := s.Store().Role().Save(roles[model.SystemManagerRoleId]); err != nil { - mlog.Critical("Failed to create new role.", mlog.Err(err), mlog.String("role", model.SystemManagerRoleId)) + mlog.Fatal("Failed to create new role.", mlog.Err(err), mlog.String("role", model.SystemManagerRoleId)) allSucceeded = false } } if _, err := s.Store().Role().GetByName(context.Background(), model.SystemReadOnlyAdminRoleId); err != nil { if _, err := s.Store().Role().Save(roles[model.SystemReadOnlyAdminRoleId]); err != nil { - mlog.Critical("Failed to create new role.", mlog.Err(err), mlog.String("role", model.SystemReadOnlyAdminRoleId)) + mlog.Fatal("Failed to create new role.", mlog.Err(err), mlog.String("role", model.SystemReadOnlyAdminRoleId)) allSucceeded = false } } if _, err := s.Store().Role().GetByName(context.Background(), model.SystemUserManagerRoleId); err != nil { if _, err := s.Store().Role().Save(roles[model.SystemUserManagerRoleId]); err != nil { - mlog.Critical("Failed to create new role.", mlog.Err(err), mlog.String("role", model.SystemUserManagerRoleId)) + mlog.Fatal("Failed to create new role.", mlog.Err(err), mlog.String("role", model.SystemUserManagerRoleId)) allSucceeded = false } } @@ -287,7 +287,7 @@ func (s *Server) doSystemConsoleRolesCreationMigration() { } if err := s.Store().System().Save(&system); err != nil { - mlog.Critical("Failed to mark system console roles creation migration as completed.", mlog.Err(err)) + mlog.Fatal("Failed to mark system console roles creation migration as completed.", mlog.Err(err)) } } @@ -302,7 +302,7 @@ func (s *Server) doCustomGroupAdminRoleCreationMigration() { allSucceeded := true if _, err := s.Store().Role().GetByName(context.Background(), model.SystemCustomGroupAdminRoleId); err != nil { if _, err := s.Store().Role().Save(roles[model.SystemCustomGroupAdminRoleId]); err != nil { - mlog.Critical("Failed to create new role.", mlog.Err(err), mlog.String("role", model.SystemCustomGroupAdminRoleId)) + mlog.Fatal("Failed to create new role.", mlog.Err(err), mlog.String("role", model.SystemCustomGroupAdminRoleId)) allSucceeded = false } } @@ -317,7 +317,7 @@ func (s *Server) doCustomGroupAdminRoleCreationMigration() { } if err := s.Store().System().Save(&system); err != nil { - mlog.Critical("Failed to mark custom group admin role creation migration as completed.", mlog.Err(err)) + mlog.Fatal("Failed to mark custom group admin role creation migration as completed.", mlog.Err(err)) } } @@ -337,7 +337,7 @@ func (s *Server) doContentExtractionConfigDefaultTrueMigration() { } if err := s.Store().System().Save(&system); err != nil { - mlog.Critical("Failed to mark content extraction config migration as completed.", mlog.Err(err)) + mlog.Fatal("Failed to mark content extraction config migration as completed.", mlog.Err(err)) } } @@ -352,31 +352,31 @@ func (s *Server) doPlaybooksRolesCreationMigration() { allSucceeded := true if _, err := s.Store().Role().GetByName(context.Background(), model.PlaybookAdminRoleId); err != nil { if _, err := s.Store().Role().Save(roles[model.PlaybookAdminRoleId]); err != nil { - mlog.Critical("Failed to create new playbook admin role to database.", mlog.Err(err)) + mlog.Fatal("Failed to create new playbook admin role to database.", mlog.Err(err)) allSucceeded = false } } if _, err := s.Store().Role().GetByName(context.Background(), model.PlaybookMemberRoleId); err != nil { if _, err := s.Store().Role().Save(roles[model.PlaybookMemberRoleId]); err != nil { - mlog.Critical("Failed to create new playbook member role to database.", mlog.Err(err)) + mlog.Fatal("Failed to create new playbook member role to database.", mlog.Err(err)) allSucceeded = false } } if _, err := s.Store().Role().GetByName(context.Background(), model.RunAdminRoleId); err != nil { if _, err := s.Store().Role().Save(roles[model.RunAdminRoleId]); err != nil { - mlog.Critical("Failed to create new run admin role to database.", mlog.Err(err)) + mlog.Fatal("Failed to create new run admin role to database.", mlog.Err(err)) allSucceeded = false } } if _, err := s.Store().Role().GetByName(context.Background(), model.RunMemberRoleId); err != nil { if _, err := s.Store().Role().Save(roles[model.RunMemberRoleId]); err != nil { - mlog.Critical("Failed to create new run member role to database.", mlog.Err(err)) + mlog.Fatal("Failed to create new run member role to database.", mlog.Err(err)) allSucceeded = false } } schemes, err := s.Store().Scheme().GetAllPage(model.SchemeScopeTeam, 0, 1000000) if err != nil { - mlog.Critical("Failed to get all schemes.", mlog.Err(err)) + mlog.Fatal("Failed to get all schemes.", mlog.Err(err)) allSucceeded = false } @@ -391,7 +391,7 @@ func (s *Server) doPlaybooksRolesCreationMigration() { } if savedRole, err := s.Store().Role().Save(playbookAdminRole); err != nil { - mlog.Critical("Failed to create new playbook admin role for existing custom scheme.", mlog.Err(err)) + mlog.Fatal("Failed to create new playbook admin role for existing custom scheme.", mlog.Err(err)) allSucceeded = false } else { scheme.DefaultPlaybookAdminRole = savedRole.Name @@ -406,7 +406,7 @@ func (s *Server) doPlaybooksRolesCreationMigration() { } if savedRole, err := s.Store().Role().Save(playbookMember); err != nil { - mlog.Critical("Failed to create new playbook member role for existing custom scheme.", mlog.Err(err)) + mlog.Fatal("Failed to create new playbook member role for existing custom scheme.", mlog.Err(err)) allSucceeded = false } else { scheme.DefaultPlaybookMemberRole = savedRole.Name @@ -422,7 +422,7 @@ func (s *Server) doPlaybooksRolesCreationMigration() { } if savedRole, err := s.Store().Role().Save(runAdminRole); err != nil { - mlog.Critical("Failed to create new run admin role for existing custom scheme.", mlog.Err(err)) + mlog.Fatal("Failed to create new run admin role for existing custom scheme.", mlog.Err(err)) allSucceeded = false } else { scheme.DefaultRunAdminRole = savedRole.Name @@ -438,7 +438,7 @@ func (s *Server) doPlaybooksRolesCreationMigration() { } if savedRole, err := s.Store().Role().Save(runMemberRole); err != nil { - mlog.Critical("Failed to create new run member role for existing custom scheme.", mlog.Err(err)) + mlog.Fatal("Failed to create new run member role for existing custom scheme.", mlog.Err(err)) allSucceeded = false } else { scheme.DefaultRunMemberRole = savedRole.Name @@ -446,7 +446,7 @@ func (s *Server) doPlaybooksRolesCreationMigration() { } _, err := s.Store().Scheme().Save(scheme) if err != nil { - mlog.Critical("Failed to update custom scheme.", mlog.Err(err)) + mlog.Fatal("Failed to update custom scheme.", mlog.Err(err)) allSucceeded = false } } @@ -462,7 +462,7 @@ func (s *Server) doPlaybooksRolesCreationMigration() { } if err := s.Store().System().Save(&system); err != nil { - mlog.Critical("Failed to mark playbook roles creation migration as completed.", mlog.Err(err)) + mlog.Fatal("Failed to mark playbook roles creation migration as completed.", mlog.Err(err)) } } @@ -507,7 +507,7 @@ func (s *Server) doFirstAdminSetupCompleteMigration() { } if err := s.Store().System().Save(&system); err != nil { - mlog.Critical("Failed to mark first admin setup migration as completed.", mlog.Err(err)) + mlog.Fatal("Failed to mark first admin setup migration as completed.", mlog.Err(err)) } } @@ -534,7 +534,7 @@ func (s *Server) doRemainingSchemaMigrations() { } if err := s.Store().System().Save(&system); err != nil { - mlog.Critical("Failed to mark the remaining schema migrations as completed.", mlog.Err(err)) + mlog.Fatal("Failed to mark the remaining schema migrations as completed.", mlog.Err(err)) } } @@ -552,7 +552,7 @@ func (s *Server) doAppMigrations() { // migrations. For example, it needs the guest roles migration. err := s.doPermissionsMigrations() if err != nil { - mlog.Critical("(app.App).DoPermissionsMigrations failed", mlog.Err(err)) + mlog.Fatal("(app.App).DoPermissionsMigrations failed", mlog.Err(err)) } s.doContentExtractionConfigDefaultTrueMigration() s.doPlaybooksRolesCreationMigration() diff --git a/app/platform/metrics.go b/app/platform/metrics.go index 2253594011..f30edb5093 100644 --- a/app/platform/metrics.go +++ b/app/platform/metrics.go @@ -111,7 +111,7 @@ func (pm *platformMetrics) startMetricsServer() error { go func() { close(notify) if err := pm.server.Serve(l); err != nil && err != http.ErrServerClosed { - pm.logger.Critical(err.Error()) + pm.logger.Fatal(err.Error()) } }() diff --git a/app/server.go b/app/server.go index fbd433a6db..dc66353ee4 100644 --- a/app/server.go +++ b/app/server.go @@ -1059,7 +1059,7 @@ func (s *Server) Start() error { } if err != nil && err != http.ErrServerClosed { - mlog.Critical("Error starting server", mlog.Err(err)) + mlog.Fatal("Error starting server", mlog.Err(err)) time.Sleep(time.Second) } @@ -1068,7 +1068,7 @@ func (s *Server) Start() error { if *s.platform.Config().ServiceSettings.EnableLocalMode { if err := s.startLocalModeServer(); err != nil { - mlog.Critical(err.Error()) + mlog.Fatal(err.Error()) } } @@ -1100,7 +1100,7 @@ func (s *Server) startLocalModeServer() error { go func() { err = s.localModeServer.Serve(unixListener) if err != nil && err != http.ErrServerClosed { - mlog.Critical("Error starting unix socket server", mlog.Err(err)) + mlog.Fatal("Error starting unix socket server", mlog.Err(err)) } }() return nil diff --git a/services/upgrader/upgrader_linux.go b/services/upgrader/upgrader_linux.go index 1f895480ea..04ad1bcf82 100644 --- a/services/upgrader/upgrader_linux.go +++ b/services/upgrader/upgrader_linux.go @@ -333,7 +333,7 @@ func extractBinary(executablePath string, filename string) error { if err != nil { err2 := os.Rename(tmpFileName, executablePath) if err2 != nil { - mlog.Critical("Unable to restore the backup of the executable file. Restore the executable file manually.") + mlog.Fatal("Unable to restore the backup of the executable file. Restore the executable file manually.") return errors.Wrap(err2, "critical error: unable to upgrade the binary or restore the old binary version. Please restore it manually") } return err @@ -342,13 +342,13 @@ func extractBinary(executablePath string, filename string) error { if _, err = io.Copy(outFile, tarReader); err != nil { err2 := os.Remove(executablePath) if err2 != nil { - mlog.Critical("Unable to restore the backup of the executable file. Restore the executable file manually.") + mlog.Fatal("Unable to restore the backup of the executable file. Restore the executable file manually.") return errors.Wrap(err2, "critical error: unable to upgrade the binary or restore the old binary version. Please restore it manually") } err2 = os.Rename(tmpFileName, executablePath) if err2 != nil { - mlog.Critical("Unable to restore the backup of the executable file. Restore the executable file manually.") + mlog.Fatal("Unable to restore the backup of the executable file. Restore the executable file manually.") return errors.Wrap(err2, "critical error: unable to upgrade the binary or restore the old binary version. Please restore it manually") } return err diff --git a/shared/mlog/global.go b/shared/mlog/global.go index de346123aa..71d7430a42 100644 --- a/shared/mlog/global.go +++ b/shared/mlog/global.go @@ -113,13 +113,10 @@ func Error(msg string, fields ...Field) { // Convenience method equivalent to calling `Log` with the `Critical` level. // DEPRECATED: Either use Error or Fatal. +// Critical level isn't added in mlog/levels.go:StdAll so calling this doesn't +// really work. For now we just call Fatal to atleast print something. func Critical(msg string, fields ...Field) { - logger := getGlobalLogger() - if logger == nil { - defaultLog(LvlCritical, msg, fields...) - return - } - logger.Critical(msg, fields...) + Fatal(msg, fields...) } func Fatal(msg string, fields ...Field) { diff --git a/shared/mlog/global_test.go b/shared/mlog/global_test.go index d97b3b15a3..486387e5f6 100644 --- a/shared/mlog/global_test.go +++ b/shared/mlog/global_test.go @@ -24,7 +24,6 @@ func TestLoggingBeforeInitialized(t *testing.T) { mlog.Debug("debug log") mlog.Warn("warning log") mlog.Error("error log") - mlog.Critical("critical log") }) } @@ -40,14 +39,13 @@ func TestLoggingAfterInitialized(t *testing.T) { Type: "file", Format: "json", FormatOptions: json.RawMessage(`{"enable_caller":true}`), - Levels: []mlog.Level{mlog.LvlCritical, mlog.LvlError, mlog.LvlWarn, mlog.LvlInfo, mlog.LvlDebug}, + Levels: []mlog.Level{mlog.LvlError, mlog.LvlWarn, mlog.LvlInfo, mlog.LvlDebug}, }, []string{ `{"timestamp":0,"level":"debug","msg":"real debug log","caller":"mlog/global_test.go:0"}`, `{"timestamp":0,"level":"info","msg":"real info log","caller":"mlog/global_test.go:0"}`, `{"timestamp":0,"level":"warn","msg":"real warning log","caller":"mlog/global_test.go:0"}`, `{"timestamp":0,"level":"error","msg":"real error log","caller":"mlog/global_test.go:0"}`, - `{"timestamp":0,"level":"critical","msg":"real critical log","caller":"mlog/global_test.go:0"}`, }, }, { @@ -56,11 +54,10 @@ func TestLoggingAfterInitialized(t *testing.T) { Type: "file", Format: "json", FormatOptions: json.RawMessage(`{"enable_caller":true}`), - Levels: []mlog.Level{mlog.LvlCritical, mlog.LvlError}, + Levels: []mlog.Level{mlog.LvlError}, }, []string{ `{"timestamp":0,"level":"error","msg":"real error log","caller":"mlog/global_test.go:0"}`, - `{"timestamp":0,"level":"critical","msg":"real critical log","caller":"mlog/global_test.go:0"}`, }, }, { @@ -69,14 +66,13 @@ func TestLoggingAfterInitialized(t *testing.T) { Type: "file", Format: "plain", FormatOptions: json.RawMessage(`{"delim":" | ", "enable_caller":true}`), - Levels: []mlog.Level{mlog.LvlCritical, mlog.LvlError, mlog.LvlWarn, mlog.LvlInfo, mlog.LvlDebug}, + Levels: []mlog.Level{mlog.LvlError, mlog.LvlWarn, mlog.LvlInfo, mlog.LvlDebug}, }, []string{ `debug | TIME | real debug log | caller="mlog/global_test.go:0"`, `info | TIME | real info log | caller="mlog/global_test.go:0"`, `warn | TIME | real warning log | caller="mlog/global_test.go:0"`, `error | TIME | real error log | caller="mlog/global_test.go:0"`, - `critical | TIME | real critical log | caller="mlog/global_test.go:0"`, }, }, { @@ -85,11 +81,10 @@ func TestLoggingAfterInitialized(t *testing.T) { Type: "file", Format: "plain", FormatOptions: json.RawMessage(`{"delim":" | ", "enable_caller":true}`), - Levels: []mlog.Level{mlog.LvlCritical, mlog.LvlError}, + Levels: []mlog.Level{mlog.LvlError}, }, []string{ `error | TIME | real error log | caller="mlog/global_test.go:0"`, - `critical | TIME | real critical log | caller="mlog/global_test.go:0"`, }, }, } @@ -116,7 +111,6 @@ func TestLoggingAfterInitialized(t *testing.T) { mlog.Info("real info log") mlog.Warn("real warning log") mlog.Error("real error log") - mlog.Critical("real critical log") logger.Shutdown() From 356188de2884ae51f8c0747ecfe85793f171ae65 Mon Sep 17 00:00:00 2001 From: Agniva De Sarker Date: Tue, 3 Jan 2023 20:14:45 +0530 Subject: [PATCH 36/37] Clean untranslated logs (#21956) Remove the word "" from appearing in the logs. ```release-note NONE ``` --- model/utils.go | 8 ++++++-- model/utils_test.go | 5 +++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/model/utils.go b/model/utils.go index a8d82e3669..4ed85e2b32 100644 --- a/model/utils.go +++ b/model/utils.go @@ -256,11 +256,15 @@ func (er *AppError) Error() string { // render the error information sb.WriteString(er.Where) sb.WriteString(": ") - sb.WriteString(er.Message) + if er.Message != NoTranslation { + sb.WriteString(er.Message) + } // only render the detailed error when it's present if er.DetailedError != "" { - sb.WriteString(", ") + if er.Message != NoTranslation { + sb.WriteString(", ") + } sb.WriteString(er.DetailedError) } diff --git a/model/utils_test.go b/model/utils_test.go index 1b96f44516..606477d750 100644 --- a/model/utils_test.go +++ b/model/utils_test.go @@ -81,6 +81,11 @@ func TestAppError(t *testing.T) { t.Log(appErr.Error()) } +func TestAppErrorNoTranslation(t *testing.T) { + appErr := NewAppError("TestAppError", NoTranslation, nil, "test error", http.StatusBadRequest) + require.Equal(t, "TestAppError: test error", appErr.Error()) +} + func TestAppErrorJunk(t *testing.T) { rerr := AppErrorFromJSON(strings.NewReader("This is a broken test")) require.Equal(t, "body: This is a broken test", rerr.DetailedError) From e4b050693db226304939fe2b171d33f19487766e Mon Sep 17 00:00:00 2001 From: Agniva De Sarker Date: Tue, 3 Jan 2023 21:30:51 +0530 Subject: [PATCH 37/37] Return 404 when thread membership not found (#21960) We were incorrectly returning by checking for `err != nil` when a 404 error was included in that condition. ```release-note NONE ``` --- app/post_test.go | 5 +++++ app/user.go | 31 +++++++++++++++++++------------ app/user_test.go | 5 +++++ 3 files changed, 29 insertions(+), 12 deletions(-) diff --git a/app/post_test.go b/app/post_test.go index d3073fe302..4630eb75af 100644 --- a/app/post_test.go +++ b/app/post_test.go @@ -2408,6 +2408,11 @@ func TestFollowThreadSkipsParticipants(t *testing.T) { for _, p := range thread.Participants { require.True(t, p.Id == sysadmin.Id || p.Id == user.Id) } + + threadMembership.PostId = "notfound" + _, err = th.App.GetThreadForUser(threadMembership, false) + require.NotNil(t, err) + assert.Equal(t, http.StatusNotFound, err.StatusCode) } func TestAutofollowBasedOnRootPost(t *testing.T) { diff --git a/app/user.go b/app/user.go index d259439859..991457f7ba 100644 --- a/app/user.go +++ b/app/user.go @@ -2484,24 +2484,31 @@ func (a *App) GetThreadsForUser(userID, teamID string, options model.GetUserThre } func (a *App) GetThreadMembershipForUser(userId, threadId string) (*model.ThreadMembership, *model.AppError) { - threadMembership, err := a.Srv().Store().Thread().GetMembershipForUser(userId, threadId) - if err != nil { - return nil, model.NewAppError("GetThreadMembershipForUser", "app.user.get_thread_membership_for_user.app_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - if threadMembership == nil { - return nil, model.NewAppError("GetThreadMembershipForUser", "app.user.get_thread_membership_for_user.not_found", nil, "thread membership not found/followed", http.StatusNotFound) + threadMembership, nErr := a.Srv().Store().Thread().GetMembershipForUser(userId, threadId) + if nErr != nil { + var nfErr *store.ErrNotFound + switch { + case errors.As(nErr, &nfErr): + return nil, model.NewAppError("GetThreadMembershipForUser", "app.user.get_thread_membership_for_user.not_found", nil, "", http.StatusNotFound).Wrap(nErr) + default: + return nil, model.NewAppError("GetThreadMembershipForUser", "app.user.get_thread_membership_for_user.app_error", nil, "", http.StatusInternalServerError).Wrap(nErr) + } } return threadMembership, nil } func (a *App) GetThreadForUser(threadMembership *model.ThreadMembership, extended bool) (*model.ThreadResponse, *model.AppError) { - thread, err := a.Srv().Store().Thread().GetThreadForUser(threadMembership, extended, a.isPostPriorityEnabled()) - if err != nil { - return nil, model.NewAppError("GetThreadForUser", "app.user.get_threads_for_user.app_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - if thread == nil { - return nil, model.NewAppError("GetThreadForUser", "app.user.get_threads_for_user.not_found", nil, "thread not found/followed", http.StatusNotFound) + thread, nErr := a.Srv().Store().Thread().GetThreadForUser(threadMembership, extended, a.isPostPriorityEnabled()) + if nErr != nil { + var nfErr *store.ErrNotFound + switch { + case errors.As(nErr, &nfErr): + return nil, model.NewAppError("GetThreadForUser", "app.user.get_threads_for_user.not_found", nil, "thread not found/followed", http.StatusNotFound) + default: + return nil, model.NewAppError("GetThreadForUser", "app.user.get_threads_for_user.app_error", nil, "", http.StatusInternalServerError).Wrap(nErr) + } } + a.sanitizeProfiles(thread.Participants, false) thread.Post.SanitizeProps() return thread, nil diff --git a/app/user_test.go b/app/user_test.go index 8a71680a20..086e71e25f 100644 --- a/app/user_test.go +++ b/app/user_test.go @@ -8,6 +8,7 @@ import ( "context" "encoding/json" "errors" + "net/http" "path/filepath" "strings" "testing" @@ -1700,6 +1701,10 @@ func TestUpdateThreadReadForUser(t *testing.T) { require.Nil(t, appErr) require.NotNil(t, threadMembership) assert.True(t, threadMembership.Following) + + _, appErr = th.App.GetThreadMembershipForUser(th.BasicUser.Id, "notfound") + require.NotNil(t, appErr) + assert.Equal(t, http.StatusNotFound, appErr.StatusCode) }) t.Run("Ensure no panic on error", func(t *testing.T) {