diff --git a/api4/config.go b/api4/config.go index c1c644122f..f3de4256ae 100644 --- a/api4/config.go +++ b/api4/config.go @@ -20,6 +20,7 @@ func (api *API) InitConfig() { api.BaseRoutes.ApiRoot.Handle("/config/reload", api.ApiSessionRequired(configReload)).Methods("POST") api.BaseRoutes.ApiRoot.Handle("/config/client", api.ApiHandler(getClientConfig)).Methods("GET") api.BaseRoutes.ApiRoot.Handle("/config/environment", api.ApiSessionRequired(getEnvironmentConfig)).Methods("GET") + api.BaseRoutes.ApiRoot.Handle("/config/migrate", api.ApiSessionRequired(migrateConfig)).Methods("POST") } func getConfig(c *Context, w http.ResponseWriter, r *http.Request) { @@ -227,3 +228,36 @@ func patchConfig(c *Context, w http.ResponseWriter, r *http.Request) { w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate") w.Write([]byte(c.App.GetSanitizedConfig().ToJson())) } + +func migrateConfig(c *Context, w http.ResponseWriter, r *http.Request) { + props := model.StringInterfaceFromJson(r.Body) + from, ok := props["from"].(string) + if !ok { + c.SetInvalidParam("from") + return + } + to, ok := props["to"].(string) + if !ok { + c.SetInvalidParam("to") + return + } + + auditRec := c.MakeAuditRecord("migrateConfig", audit.Fail) + auditRec.AddMeta("from", from) + auditRec.AddMeta("to", to) + defer c.LogAuditRec(auditRec) + + if !c.App.SessionHasPermissionTo(*c.App.Session(), model.PERMISSION_MANAGE_SYSTEM) { + c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM) + return + } + + err := config.Migrate(from, to) + if err != nil { + c.Err = model.NewAppError("migrateConfig", "api.config.migrate_config.app_error", nil, err.Error(), http.StatusInternalServerError) + return + } + + auditRec.Success() + ReturnStatusOK(w) +} diff --git a/api4/config_local.go b/api4/config_local.go index b01dee97df..275b693672 100644 --- a/api4/config_local.go +++ b/api4/config_local.go @@ -17,6 +17,7 @@ func (api *API) InitConfigLocal() { api.BaseRoutes.ApiRoot.Handle("/config", api.ApiLocal(getConfig)).Methods("GET") api.BaseRoutes.ApiRoot.Handle("/config", api.ApiLocal(localUpdateConfig)).Methods("PUT") api.BaseRoutes.ApiRoot.Handle("/config/patch", api.ApiLocal(localPatchConfig)).Methods("PUT") + api.BaseRoutes.ApiRoot.Handle("/config/migrate", api.ApiLocal(migrateConfig)).Methods("POST") } func localUpdateConfig(c *Context, w http.ResponseWriter, r *http.Request) { diff --git a/api4/config_test.go b/api4/config_test.go index 337aaa8c9f..4589817f01 100644 --- a/api4/config_test.go +++ b/api4/config_test.go @@ -9,6 +9,7 @@ import ( "strings" "testing" + "github.com/mattermost/mattermost-server/v5/config" "github.com/mattermost/mattermost-server/v5/model" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -551,3 +552,26 @@ func TestPatchConfig(t *testing.T) { require.Equal(t, nonEmptyURL, *cfg.ServiceSettings.SiteURL) }) } + +func TestMigrateConfig(t *testing.T) { + th := Setup(t).InitBasic() + defer th.TearDown() + + t.Run("user is not system admin", func(t *testing.T) { + _, response := th.Client.MigrateConfig("from", "to") + CheckForbiddenStatus(t, response) + }) + + th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) { + f, err := config.NewStore("from.json", false) + require.NoError(t, err) + defer f.RemoveFile("from.json") + + _, err = config.NewStore("to.json", false) + require.NoError(t, err) + defer f.RemoveFile("to.json") + + _, response := client.MigrateConfig("from.json", "to.json") + CheckNoError(t, response) + }) +} diff --git a/i18n/en.json b/i18n/en.json index 38b6332f5e..60014df211 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -1110,6 +1110,10 @@ "id": "api.config.client.old_format.app_error", "translation": "New format for the client configuration is not supported yet. Please specify format=old in the query string." }, + { + "id": "api.config.migrate_config.app_error", + "translation": "Failed to migrate config store." + }, { "id": "api.config.update_config.clear_siteurl.app_error", "translation": "Site URL cannot be cleared." diff --git a/model/client4.go b/model/client4.go index 715c862358..294478091d 100644 --- a/model/client4.go +++ b/model/client4.go @@ -3364,6 +3364,19 @@ func (c *Client4) UpdateConfig(config *Config) (*Config, *Response) { return ConfigFromJson(r.Body), BuildResponse(r) } +// MigrateConfig will migrate existing config to the new one. +func (c *Client4) MigrateConfig(from, to string) (bool, *Response) { + m := make(map[string]string, 2) + m["from"] = from + m["to"] = to + r, err := c.DoApiPost(c.GetConfigRoute()+"/migrate", MapToJson(m)) + if err != nil { + return false, BuildErrorResponse(r, err) + } + defer closeBody(r) + return true, BuildResponse(r) +} + // UploadLicenseFile will add a license file to the system. func (c *Client4) UploadLicenseFile(data []byte) (bool, *Response) { body := &bytes.Buffer{}