Adds logical deletes to shared channel remotes and remote clusters (#28159)
* Adds logical deletes to shared channel remotes and remote clusters Instead of physically deleting the shared channel remote and remote clusters records when a channel is unshared, a remote uninvited or a remote cluster is deleted, now those have a logical `DeleteAt` field that is set. This allows us to safely restore shared channels between two remote clusters (as of now resetting the cursor without backfilling their contents) and to know which connections were established in the past and now are severed. * Delete the index in remoteclusters before adding the new column * Fix bad error check
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
19733eef1e
Коммит
f8202309ce
@@ -30,10 +30,20 @@ func (a *App) RegisterPluginForSharedChannels(rctx request.CTX, opts model.Regis
|
||||
|
||||
// if plugin is already registered then treat this as an update.
|
||||
if rc != nil {
|
||||
rctx.Logger().Debug("Plugin already registered for Shared Channels",
|
||||
mlog.String("plugin_id", opts.PluginID),
|
||||
mlog.String("remote_id", rc.RemoteId),
|
||||
)
|
||||
// plugin was deleted at some point
|
||||
if rc.DeleteAt != 0 {
|
||||
rctx.Logger().Debug("Restoring plugin registration for Shared Channels",
|
||||
mlog.String("plugin_id", opts.PluginID),
|
||||
mlog.String("remote_id", rc.RemoteId),
|
||||
)
|
||||
|
||||
rc.DeleteAt = 0
|
||||
} else {
|
||||
rctx.Logger().Debug("Plugin already registered for Shared Channels",
|
||||
mlog.String("plugin_id", opts.PluginID),
|
||||
mlog.String("remote_id", rc.RemoteId),
|
||||
)
|
||||
}
|
||||
|
||||
rc.DisplayName = opts.Displayname
|
||||
rc.Options = opts.GetOptionFlags()
|
||||
@@ -82,6 +92,11 @@ func (a *App) UnregisterPluginForSharedChannels(pluginID string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if rc.DeleteAt != 0 {
|
||||
// plugin already unregistered, nothing to do
|
||||
return nil
|
||||
}
|
||||
|
||||
_, appErr := a.DeleteRemoteCluster(rc.RemoteId)
|
||||
if appErr != nil {
|
||||
return appErr
|
||||
|
||||
@@ -6,11 +6,9 @@ package app
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
"github.com/mattermost/mattermost/server/public/shared/i18n"
|
||||
)
|
||||
|
||||
func setupRemoteCluster(tb testing.TB) *TestHelper {
|
||||
@@ -38,8 +36,7 @@ func TestAddRemoteCluster(t *testing.T) {
|
||||
|
||||
remoteCluster.RemoteId = model.NewId()
|
||||
_, err = th.App.AddRemoteCluster(remoteCluster)
|
||||
require.NotNil(t, err, "Adding a duplicate remote cluster should error")
|
||||
assert.Contains(t, err.Error(), i18n.T("api.remote_cluster.save_not_unique.app_error"))
|
||||
require.Nil(t, err, "Adding a duplicate remote cluster should work fine")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -74,8 +71,7 @@ func TestUpdateRemoteCluster(t *testing.T) {
|
||||
|
||||
savedRemoteClustered.SiteURL = remoteCluster.SiteURL
|
||||
_, err = th.App.UpdateRemoteCluster(savedRemoteClustered)
|
||||
require.NotNil(t, err, "Updating remote cluster with duplicate site url should error")
|
||||
assert.Contains(t, err.Error(), i18n.T("api.remote_cluster.update_not_unique.app_error"))
|
||||
require.Nil(t, err, "Updating remote cluster with duplicate site url should work fine")
|
||||
})
|
||||
|
||||
t.Run("update remote cluster with an already existing site url, is not allowed", func(t *testing.T) {
|
||||
@@ -106,7 +102,6 @@ func TestUpdateRemoteCluster(t *testing.T) {
|
||||
// Same site url
|
||||
anotherExistingRemoteClustered.SiteURL = existingRemoteCluster.SiteURL
|
||||
_, err = th.App.UpdateRemoteCluster(anotherExistingRemoteClustered)
|
||||
require.NotNil(t, err, "Updating remote cluster should error")
|
||||
assert.Contains(t, err.Error(), i18n.T("api.remote_cluster.update_not_unique.app_error"))
|
||||
require.Nil(t, err, "Updating remote cluster should work fine")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ func (a *App) GetCloudSession(token string) (*model.Session, *model.AppError) {
|
||||
|
||||
func (a *App) GetRemoteClusterSession(token string, remoteId string) (*model.Session, *model.AppError) {
|
||||
rc, appErr := a.GetRemoteCluster(remoteId)
|
||||
if appErr == nil && subtle.ConstantTimeCompare([]byte(rc.Token), []byte(token)) == 1 {
|
||||
if appErr == nil && rc.DeleteAt == 0 && subtle.ConstantTimeCompare([]byte(rc.Token), []byte(token)) == 1 {
|
||||
// Need a bare-bones session object for later checks
|
||||
session := &model.Session{
|
||||
Token: token,
|
||||
|
||||
@@ -219,7 +219,7 @@ func (rp *RemoteProvider) doRemove(a *app.App, args *model.CommandArgs, margs ma
|
||||
|
||||
// doStatus displays connection status for all remote clusters.
|
||||
func (rp *RemoteProvider) doStatus(a *app.App, args *model.CommandArgs, _ map[string]string) *model.CommandResponse {
|
||||
list, err := a.GetAllRemoteClusters(0, 999999, model.RemoteClusterQueryFilter{})
|
||||
list, err := a.GetAllRemoteClusters(0, 999999, model.RemoteClusterQueryFilter{IncludeDeleted: true})
|
||||
if err != nil {
|
||||
responsef(args.T("api.command_remote.fetch_status.error", map[string]any{"Error": err.Error()}))
|
||||
}
|
||||
@@ -230,15 +230,16 @@ func (rp *RemoteProvider) doStatus(a *app.App, args *model.CommandArgs, _ map[st
|
||||
|
||||
var sb strings.Builder
|
||||
fmt.Fprintf(&sb, args.T("api.command_remote.remote_table_header")+" \n")
|
||||
// | Secure Connection | Display name | ConnectionID | Site URL | Invite accepted | Online | Last ping |
|
||||
fmt.Fprintf(&sb, "| :---- | :---- | :---- | :---- | :---- | :---- | :---- | \n")
|
||||
// | Secure Connection | Display name | ConnectionID | Site URL | Default Team | Invite accepted | Online | Last ping | Deleted |
|
||||
fmt.Fprintf(&sb, "| :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | | :---- |\n")
|
||||
|
||||
for _, rc := range list {
|
||||
accepted := formatBool(args.T, rc.IsConfirmed())
|
||||
online := formatBool(args.T, isOnline(rc.LastPingAt))
|
||||
lastPing := formatTimestamp(rc.LastPingAt)
|
||||
deleted := formatBool(args.T, rc.DeleteAt != 0)
|
||||
|
||||
fmt.Fprintf(&sb, "| %s | %s | %s | %s | %s | %s | %s |\n", rc.Name, rc.DisplayName, rc.RemoteId, rc.GetSiteURL(), accepted, online, lastPing)
|
||||
fmt.Fprintf(&sb, "| %s | %s | %s | %s | %s | %s | %s | %s | %s |\n", rc.Name, rc.DisplayName, rc.RemoteId, rc.GetSiteURL(), rc.DefaultTeamId, accepted, online, lastPing, deleted)
|
||||
}
|
||||
return responsef(sb.String())
|
||||
}
|
||||
|
||||
@@ -252,6 +252,9 @@ func (sp *ShareProvider) doInviteRemote(a *app.App, c request.CTX, args *model.C
|
||||
if appErr != nil {
|
||||
return responsef(args.T("api.command_share.remote_id_invalid.error", map[string]any{"Error": appErr.Error()}))
|
||||
}
|
||||
if rc.DeleteAt != 0 {
|
||||
return responsef(args.T("api.command_share.remote_id_invalid.error", map[string]any{"Error": "entity is deleted"}))
|
||||
}
|
||||
|
||||
if err = a.InviteRemoteToChannel(args.ChannelId, remoteID, args.UserId, true); err != nil {
|
||||
return responsef(args.T("api.command_share.invite_remote_to_channel.error", map[string]any{"Error": err.Error()}))
|
||||
|
||||
Ссылка в новой задаче
Block a user