mirror of
https://github.com/mattermost/mattermost.git
synced 2025-02-25 18:55:24 -06:00
Adding Upgrade to Enterprise version feature (#14539)
* Adding Upgrade to Enterprise version feature * Addressing PR review comments, and adding some minor improvements * Add tests file * Addressing PR comments * fix linter checks * Storing and exposing the upgraded from TE info * Fix showing errors on mac * A more appropiate status code for not-supported upgrade * Fixing tests * Handling permissions errors * More server logging around upgrade failures * Apply text changes suggested from code review Co-authored-by: Eric Sadur <57730300+esadur@users.noreply.github.com> * Address PR review comments * Only allow to restart the system after an upgrade * Verify file signature before upgrade * Adding limit to the downloaded file * Simplifying the upgrade binary process with backup in memory * Fixing backup/restore mechanism for the binary file * Improve file permissions handling * Askin the permissions for the right place (the parent directory) * Fixing tests * Addressing PR review comments * Fix license headers * Fixing retry layer * Making it work on windows builds * Adding license header * Fixing 2 tests * Fixing tests that need UpgradeFromTE System key mock * Extracting i18n translation * Apply suggestions from code review Co-authored-by: Eric Sadur <57730300+esadur@users.noreply.github.com> * Improving how the errors are written * Fixing another error text * Removing unneeded translation * Fixing upgrade status strings * Update i18n/en.json Co-authored-by: Eric Sadur <57730300+esadur@users.noreply.github.com> * Fixing tests Co-authored-by: Eric Sadur <57730300+esadur@users.noreply.github.com> Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
This commit is contained in:
109
api4/system.go
109
api4/system.go
@@ -13,11 +13,14 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v5/audit"
|
||||
"github.com/mattermost/mattermost-server/v5/mlog"
|
||||
"github.com/mattermost/mattermost-server/v5/model"
|
||||
"github.com/mattermost/mattermost-server/v5/services/cache"
|
||||
"github.com/mattermost/mattermost-server/v5/services/filesstore"
|
||||
"github.com/mattermost/mattermost-server/v5/services/upgrader"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -54,7 +57,9 @@ func (api *API) InitSystem() {
|
||||
api.BaseRoutes.ApiRoot.Handle("/server_busy", api.ApiSessionRequired(setServerBusy)).Methods("POST")
|
||||
api.BaseRoutes.ApiRoot.Handle("/server_busy", api.ApiSessionRequired(getServerBusyExpires)).Methods("GET")
|
||||
api.BaseRoutes.ApiRoot.Handle("/server_busy", api.ApiSessionRequired(clearServerBusy)).Methods("DELETE")
|
||||
|
||||
api.BaseRoutes.ApiRoot.Handle("/upgrade_to_enterprise", api.ApiSessionRequired(upgradeToEnterprise)).Methods("POST")
|
||||
api.BaseRoutes.ApiRoot.Handle("/upgrade_to_enterprise/status", api.ApiSessionRequired(upgradeToEnterpriseStatus)).Methods("GET")
|
||||
api.BaseRoutes.ApiRoot.Handle("/restart", api.ApiSessionRequired(restart)).Methods("POST")
|
||||
api.BaseRoutes.ApiRoot.Handle("/warn_metrics/status", api.ApiSessionRequired(getWarnMetricsStatus)).Methods("GET")
|
||||
api.BaseRoutes.ApiRoot.Handle("/warn_metrics/ack/{warn_metric_id:[A-Za-z0-9-_]+}", api.ApiHandler(sendWarnMetricAckEmail)).Methods("POST")
|
||||
}
|
||||
@@ -548,6 +553,108 @@ func getServerBusyExpires(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(c.App.Srv().Busy.ToJson()))
|
||||
}
|
||||
|
||||
func upgradeToEnterprise(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
auditRec := c.MakeAuditRecord("upgradeToEnterprise", audit.Fail)
|
||||
defer c.LogAuditRec(auditRec)
|
||||
|
||||
if !c.App.SessionHasPermissionTo(*c.App.Session(), model.PERMISSION_MANAGE_SYSTEM) {
|
||||
c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM)
|
||||
return
|
||||
}
|
||||
|
||||
if model.BuildEnterpriseReady == "true" {
|
||||
c.Err = model.NewAppError("upgradeToEnterprise", "api.upgrade_to_enterprise.already-enterprise.app_error", nil, "", http.StatusTooManyRequests)
|
||||
return
|
||||
}
|
||||
|
||||
percentage, _ := c.App.Srv().UpgradeToE0Status()
|
||||
|
||||
if percentage > 0 {
|
||||
c.Err = model.NewAppError("upgradeToEnterprise", "api.upgrade_to_enterprise.app_error", nil, "", http.StatusTooManyRequests)
|
||||
return
|
||||
}
|
||||
if percentage == 100 {
|
||||
c.Err = model.NewAppError("upgradeToEnterprise", "api.upgrade_to_enterprise.already-done.app_error", nil, "", http.StatusTooManyRequests)
|
||||
return
|
||||
}
|
||||
|
||||
if err := c.App.Srv().CanIUpgradeToE0(); err != nil {
|
||||
var ipErr *upgrader.InvalidPermissions
|
||||
var iaErr *upgrader.InvalidArch
|
||||
switch {
|
||||
case errors.As(err, &ipErr):
|
||||
params := map[string]interface{}{
|
||||
"MattermostUsername": ipErr.MattermostUsername,
|
||||
"FileUsername": ipErr.FileUsername,
|
||||
"Path": ipErr.Path,
|
||||
}
|
||||
if ipErr.ErrType == "invalid-user-and-permission" {
|
||||
c.Err = model.NewAppError("upgradeToEnterprise", "api.upgrade_to_enterprise.invalid-user-and-permission.app_error", params, err.Error(), http.StatusForbidden)
|
||||
} else if ipErr.ErrType == "invalid-user" {
|
||||
c.Err = model.NewAppError("upgradeToEnterprise", "api.upgrade_to_enterprise.invalid-user.app_error", params, err.Error(), http.StatusForbidden)
|
||||
} else if ipErr.ErrType == "invalid-permission" {
|
||||
c.Err = model.NewAppError("upgradeToEnterprise", "api.upgrade_to_enterprise.invalid-permission.app_error", params, err.Error(), http.StatusForbidden)
|
||||
}
|
||||
case errors.As(err, &iaErr):
|
||||
c.Err = model.NewAppError("upgradeToEnterprise", "api.upgrade_to_enterprise.system_not_supported.app_error", nil, err.Error(), http.StatusForbidden)
|
||||
default:
|
||||
c.Err = model.NewAppError("upgradeToEnterprise", "api.upgrade_to_enterprise.generic_error.app_error", nil, err.Error(), http.StatusForbidden)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
c.App.Srv().Go(func() {
|
||||
c.App.Srv().UpgradeToE0()
|
||||
})
|
||||
|
||||
auditRec.Success()
|
||||
w.WriteHeader(http.StatusAccepted)
|
||||
ReturnStatusOK(w)
|
||||
}
|
||||
|
||||
func upgradeToEnterpriseStatus(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
if !c.App.SessionHasPermissionTo(*c.App.Session(), model.PERMISSION_MANAGE_SYSTEM) {
|
||||
c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM)
|
||||
return
|
||||
}
|
||||
|
||||
percentage, err := c.App.Srv().UpgradeToE0Status()
|
||||
var s map[string]interface{}
|
||||
if err != nil {
|
||||
var isErr *upgrader.InvalidSignature
|
||||
switch {
|
||||
case errors.As(err, &isErr):
|
||||
appErr := model.NewAppError("upgradeToEnterpriseStatus", "api.upgrade_to_enterprise_status.app_error", nil, err.Error(), http.StatusBadRequest)
|
||||
s = map[string]interface{}{"percentage": 0, "error": appErr.Message}
|
||||
default:
|
||||
appErr := model.NewAppError("upgradeToEnterpriseStatus", "api.upgrade_to_enterprise_status.signature.app_error", nil, err.Error(), http.StatusBadRequest)
|
||||
s = map[string]interface{}{"percentage": 0, "error": appErr.Message}
|
||||
}
|
||||
} else {
|
||||
s = map[string]interface{}{"percentage": percentage, "error": nil}
|
||||
}
|
||||
|
||||
w.Write([]byte(model.StringInterfaceToJson(s)))
|
||||
}
|
||||
|
||||
func restart(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
auditRec := c.MakeAuditRecord("restartServer", audit.Fail)
|
||||
defer c.LogAuditRec(auditRec)
|
||||
|
||||
if !c.App.SessionHasPermissionTo(*c.App.Session(), model.PERMISSION_MANAGE_SYSTEM) {
|
||||
c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM)
|
||||
return
|
||||
}
|
||||
|
||||
auditRec.Success()
|
||||
ReturnStatusOK(w)
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
go func() {
|
||||
c.App.Srv().Restart()
|
||||
}()
|
||||
}
|
||||
|
||||
func getWarnMetricsStatus(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
if !c.App.SessionHasPermissionTo(*c.App.Session(), model.PERMISSION_MANAGE_SYSTEM) {
|
||||
c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM)
|
||||
|
||||
@@ -42,6 +42,7 @@ func TestUnitUpdateConfig(t *testing.T) {
|
||||
mockPostStore := mocks.PostStore{}
|
||||
mockPostStore.On("GetMaxPostSize").Return(65535, nil)
|
||||
mockSystemStore := mocks.SystemStore{}
|
||||
mockSystemStore.On("GetByName", "UpgradedFromTE").Return(&model.System{Name: "UpgradedFromTE", Value: "false"}, nil)
|
||||
mockSystemStore.On("GetByName", "InstallationDate").Return(&model.System{Name: "InstallationDate", Value: "10"}, nil)
|
||||
mockSystemStore.On("GetByName", "FirstServerRunTimestamp").Return(&model.System{Name: "FirstServerRunTimestamp", Value: "10"}, nil)
|
||||
mockSystemStore.On("Get").Return(make(model.StringMap), nil)
|
||||
|
||||
@@ -346,6 +346,7 @@ func (s *Server) ClientConfigWithComputed() map[string]string {
|
||||
// by the client.
|
||||
respCfg["NoAccounts"] = strconv.FormatBool(s.IsFirstUserAccount())
|
||||
respCfg["MaxPostSize"] = strconv.Itoa(s.MaxPostSize())
|
||||
respCfg["UpgradedFromTE"] = strconv.FormatBool(s.isUpgradedFromTE())
|
||||
respCfg["InstallationDate"] = ""
|
||||
if installationDate, err := s.getSystemInstallDate(); err == nil {
|
||||
respCfg["InstallationDate"] = strconv.FormatInt(installationDate, 10)
|
||||
|
||||
@@ -74,6 +74,7 @@ func TestClientConfigWithComputed(t *testing.T) {
|
||||
mockPostStore := mocks.PostStore{}
|
||||
mockPostStore.On("GetMaxPostSize").Return(65535, nil)
|
||||
mockSystemStore := mocks.SystemStore{}
|
||||
mockSystemStore.On("GetByName", "UpgradedFromTE").Return(&model.System{Name: "UpgradedFromTE", Value: "false"}, nil)
|
||||
mockSystemStore.On("GetByName", "InstallationDate").Return(&model.System{Name: "InstallationDate", Value: "10"}, nil)
|
||||
mockStore.On("User").Return(&mockUserStore)
|
||||
mockStore.On("Post").Return(&mockPostStore)
|
||||
|
||||
@@ -104,6 +104,7 @@ func TestSAMLSettings(t *testing.T) {
|
||||
mockPostStore := storemocks.PostStore{}
|
||||
mockPostStore.On("GetMaxPostSize").Return(65535, nil)
|
||||
mockSystemStore := storemocks.SystemStore{}
|
||||
mockSystemStore.On("GetByName", "UpgradedFromTE").Return(&model.System{Name: "UpgradedFromTE", Value: "false"}, nil)
|
||||
mockSystemStore.On("GetByName", "InstallationDate").Return(&model.System{Name: "InstallationDate", Value: "10"}, nil)
|
||||
mockSystemStore.On("GetByName", "FirstServerRunTimestamp").Return(&model.System{Name: "FirstServerRunTimestamp", Value: "10"}, nil)
|
||||
|
||||
|
||||
@@ -554,6 +554,7 @@ func TestGetPushNotificationMessage(t *testing.T) {
|
||||
mockPostStore := mocks.PostStore{}
|
||||
mockPostStore.On("GetMaxPostSize").Return(65535, nil)
|
||||
mockSystemStore := mocks.SystemStore{}
|
||||
mockSystemStore.On("GetByName", "UpgradedFromTE").Return(&model.System{Name: "UpgradedFromTE", Value: "false"}, nil)
|
||||
mockSystemStore.On("GetByName", "InstallationDate").Return(&model.System{Name: "InstallationDate", Value: "10"}, nil)
|
||||
mockSystemStore.On("GetByName", "FirstServerRunTimestamp").Return(&model.System{Name: "FirstServerRunTimestamp", Value: "10"}, nil)
|
||||
|
||||
@@ -1126,6 +1127,7 @@ func TestClearPushNotificationSync(t *testing.T) {
|
||||
mockPostStore := mocks.PostStore{}
|
||||
mockPostStore.On("GetMaxPostSize").Return(65535, nil)
|
||||
mockSystemStore := mocks.SystemStore{}
|
||||
mockSystemStore.On("GetByName", "UpgradedFromTE").Return(&model.System{Name: "UpgradedFromTE", Value: "false"}, nil)
|
||||
mockSystemStore.On("GetByName", "InstallationDate").Return(&model.System{Name: "InstallationDate", Value: "10"}, nil)
|
||||
mockSystemStore.On("GetByName", "FirstServerRunTimestamp").Return(&model.System{Name: "FirstServerRunTimestamp", Value: "10"}, nil)
|
||||
|
||||
@@ -1180,6 +1182,7 @@ func TestUpdateMobileAppBadgeSync(t *testing.T) {
|
||||
mockPostStore := mocks.PostStore{}
|
||||
mockPostStore.On("GetMaxPostSize").Return(65535, nil)
|
||||
mockSystemStore := mocks.SystemStore{}
|
||||
mockSystemStore.On("GetByName", "UpgradedFromTE").Return(&model.System{Name: "UpgradedFromTE", Value: "false"}, nil)
|
||||
mockSystemStore.On("GetByName", "InstallationDate").Return(&model.System{Name: "InstallationDate", Value: "10"}, nil)
|
||||
mockSystemStore.On("GetByName", "FirstServerRunTimestamp").Return(&model.System{Name: "FirstServerRunTimestamp", Value: "10"}, nil)
|
||||
|
||||
@@ -1222,6 +1225,7 @@ func TestSendAckToPushProxy(t *testing.T) {
|
||||
mockPostStore := mocks.PostStore{}
|
||||
mockPostStore.On("GetMaxPostSize").Return(65535, nil)
|
||||
mockSystemStore := mocks.SystemStore{}
|
||||
mockSystemStore.On("GetByName", "UpgradedFromTE").Return(&model.System{Name: "UpgradedFromTE", Value: "false"}, nil)
|
||||
mockSystemStore.On("GetByName", "InstallationDate").Return(&model.System{Name: "InstallationDate", Value: "10"}, nil)
|
||||
mockSystemStore.On("GetByName", "FirstServerRunTimestamp").Return(&model.System{Name: "FirstServerRunTimestamp", Value: "10"}, nil)
|
||||
|
||||
@@ -1379,6 +1383,7 @@ func BenchmarkPushNotificationThroughput(b *testing.B) {
|
||||
mockPostStore := mocks.PostStore{}
|
||||
mockPostStore.On("GetMaxPostSize").Return(65535, nil)
|
||||
mockSystemStore := mocks.SystemStore{}
|
||||
mockSystemStore.On("GetByName", "UpgradedFromTE").Return(&model.System{Name: "UpgradedFromTE", Value: "false"}, nil)
|
||||
mockSystemStore.On("GetByName", "InstallationDate").Return(&model.System{Name: "InstallationDate", Value: "10"}, nil)
|
||||
mockSystemStore.On("GetByName", "FirstServerRunTimestamp").Return(&model.System{Name: "FirstServerRunTimestamp", Value: "10"}, nil)
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ func TestPluginPublicKeys(t *testing.T) {
|
||||
mockPostStore := mocks.PostStore{}
|
||||
mockPostStore.On("GetMaxPostSize").Return(65535, nil)
|
||||
mockSystemStore := mocks.SystemStore{}
|
||||
mockSystemStore.On("GetByName", "UpgradedFromTE").Return(&model.System{Name: "UpgradedFromTE", Value: "false"}, nil)
|
||||
mockSystemStore.On("GetByName", "InstallationDate").Return(&model.System{Name: "InstallationDate", Value: "10"}, nil)
|
||||
mockSystemStore.On("GetByName", "FirstServerRunTimestamp").Return(&model.System{Name: "FirstServerRunTimestamp", Value: "10"}, nil)
|
||||
|
||||
|
||||
@@ -459,6 +459,7 @@ func TestImageProxy(t *testing.T) {
|
||||
mockPostStore := storemocks.PostStore{}
|
||||
mockPostStore.On("GetMaxPostSize").Return(65535, nil)
|
||||
mockSystemStore := storemocks.SystemStore{}
|
||||
mockSystemStore.On("GetByName", "UpgradedFromTE").Return(&model.System{Name: "UpgradedFromTE", Value: "false"}, nil)
|
||||
mockSystemStore.On("GetByName", "InstallationDate").Return(&model.System{Name: "InstallationDate", Value: "10"}, nil)
|
||||
mockSystemStore.On("GetByName", "FirstServerRunTimestamp").Return(&model.System{Name: "FirstServerRunTimestamp", Value: "10"}, nil)
|
||||
|
||||
|
||||
@@ -13,10 +13,12 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/getsentry/sentry-go"
|
||||
@@ -44,6 +46,7 @@ import (
|
||||
"github.com/mattermost/mattermost-server/v5/services/searchengine/bleveengine"
|
||||
"github.com/mattermost/mattermost-server/v5/services/timezones"
|
||||
"github.com/mattermost/mattermost-server/v5/services/tracing"
|
||||
"github.com/mattermost/mattermost-server/v5/services/upgrader"
|
||||
"github.com/mattermost/mattermost-server/v5/store"
|
||||
"github.com/mattermost/mattermost-server/v5/store/localcachelayer"
|
||||
"github.com/mattermost/mattermost-server/v5/store/retrylayer"
|
||||
@@ -723,6 +726,51 @@ func (s *Server) Shutdown() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) Restart() error {
|
||||
percentage, err := s.UpgradeToE0Status()
|
||||
if err != nil || percentage != 100 {
|
||||
return errors.Wrap(err, "unable to restart because the system has not been upgraded")
|
||||
}
|
||||
s.Shutdown()
|
||||
|
||||
argv0, err := exec.LookPath(os.Args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = os.Stat(argv0); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mlog.Info("Restarting server")
|
||||
return syscall.Exec(argv0, os.Args, os.Environ())
|
||||
}
|
||||
|
||||
func (s *Server) isUpgradedFromTE() bool {
|
||||
val, err := s.Store.System().GetByName(model.SYSTEM_UPGRADED_FROM_TE_ID)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return val.Value == "true"
|
||||
}
|
||||
|
||||
func (s *Server) CanIUpgradeToE0() error {
|
||||
return upgrader.CanIUpgradeToE0()
|
||||
}
|
||||
|
||||
func (s *Server) UpgradeToE0() error {
|
||||
if err := upgrader.UpgradeToE0(); err != nil {
|
||||
return err
|
||||
}
|
||||
upgradedFromTE := &model.System{Name: model.SYSTEM_UPGRADED_FROM_TE_ID, Value: "true"}
|
||||
s.Store.System().Save(upgradedFromTE)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) UpgradeToE0Status() (int64, error) {
|
||||
return upgrader.UpgradeToE0Status()
|
||||
}
|
||||
|
||||
// Go creates a goroutine, but maintains a record of it to ensure that execution completes before
|
||||
// the server is shutdown.
|
||||
func (s *Server) Go(f func()) {
|
||||
|
||||
@@ -131,6 +131,7 @@ func TestHubSessionRevokeRace(t *testing.T) {
|
||||
mockPostStore := mocks.PostStore{}
|
||||
mockPostStore.On("GetMaxPostSize").Return(65535, nil)
|
||||
mockSystemStore := mocks.SystemStore{}
|
||||
mockSystemStore.On("GetByName", "UpgradedFromTE").Return(&model.System{Name: "UpgradedFromTE", Value: "false"}, nil)
|
||||
mockSystemStore.On("GetByName", "InstallationDate").Return(&model.System{Name: "InstallationDate", Value: "10"}, nil)
|
||||
mockSystemStore.On("GetByName", "FirstServerRunTimestamp").Return(&model.System{Name: "FirstServerRunTimestamp", Value: "10"}, nil)
|
||||
|
||||
|
||||
40
i18n/en.json
40
i18n/en.json
@@ -2622,6 +2622,46 @@
|
||||
"id": "api.templates.welcome_subject",
|
||||
"translation": "[{{ .SiteName }}] You joined {{ .ServerURL }}"
|
||||
},
|
||||
{
|
||||
"id": "api.upgrade_to_enterprise.already-done.app_error",
|
||||
"translation": "You have already upgraded to Mattermost Enterprise Edition. Please restart the server to finish the upgrade."
|
||||
},
|
||||
{
|
||||
"id": "api.upgrade_to_enterprise.already-enterprise.app_error",
|
||||
"translation": "You cannot upgrade because you are already running Mattermost Enterprise Edition."
|
||||
},
|
||||
{
|
||||
"id": "api.upgrade_to_enterprise.app_error",
|
||||
"translation": "An upgrade to Mattermost Enterprise Edition is already running."
|
||||
},
|
||||
{
|
||||
"id": "api.upgrade_to_enterprise.generic_error.app_error",
|
||||
"translation": "Mattermost was unable to upgrade to Enterprise Edition."
|
||||
},
|
||||
{
|
||||
"id": "api.upgrade_to_enterprise.invalid-permission.app_error",
|
||||
"translation": "Mattermost was unable to upgrade to Enterprise Edition. The Mattermost system user {{.MattermostUsername}} does not have write access to the necessary binary file. A system administrator can update the file permissions by executing the following command on the server where Mattermost is installed:\n\n```\nchmod +w \"{{.Path}}\"\n```\n\nAfter changing the file permissions, try to upgrade Mattermost again. When you have upgraded and restarted, remember to restore the original binary file permissions:\n\n```\nchmod -w \"{{.Path}}\"\n```"
|
||||
},
|
||||
{
|
||||
"id": "api.upgrade_to_enterprise.invalid-user-and-permission.app_error",
|
||||
"translation": "Mattermost was unable to upgrade to Enterprise Edition. The Mattermost system user {{.MattermostUsername}} does not have write access to the necessary binary file. A system administrator can update the file permissions by executing the following command on the server where Mattermost is installed:\n\n```\nchown {{.MattermostUsername}} \"{{.Path}}\"\nchmod +w \"{{.Path}}\"\n```\n\nAfter changing the file permissions, try to upgrade Mattermost again. When you have upgraded and restarted, remember to restore the original binary file permissions:\n\n```\nchown {{.FileUsername}} \"{{.Path}}\"\nchmod -w \"{{.Path}}\"\n```"
|
||||
},
|
||||
{
|
||||
"id": "api.upgrade_to_enterprise.invalid-user.app_error",
|
||||
"translation": "Mattermost was unable to upgrade to Enterprise Edition. The Mattermost system user {{.MattermostUsername}} does not have write access to the necessary binary file. A system administrator can update the file permissions by executing the following command on the server where Mattermost is installed:\n\n```\nchown {{.MattermostUsername}} \"{{.Path}}\"\n```\n\nAfter changing the file permissions, try to upgrade Mattermost again. When you have upgraded and restarted, remember to restore the original binary file permissions:\n\n```\nchown {{.FileUsername}} \"{{.Path}}\"\n```"
|
||||
},
|
||||
{
|
||||
"id": "api.upgrade_to_enterprise.system_not_supported.app_error",
|
||||
"translation": "Mattermost was unable to upgrade to Enterprise Edition. This feature will only work on Linux systems with x86-64 architecture."
|
||||
},
|
||||
{
|
||||
"id": "api.upgrade_to_enterprise_status.app_error",
|
||||
"translation": "Mattermost was unable to upgrade to Enterprise Edition."
|
||||
},
|
||||
{
|
||||
"id": "api.upgrade_to_enterprise_status.signature.app_error",
|
||||
"translation": "Mattermost was unable to upgrade to Enterprise Edition. The digital signature of the downloaded binary file could not be verified."
|
||||
},
|
||||
{
|
||||
"id": "api.user.activate_mfa.email_and_ldap_only.app_error",
|
||||
"translation": "MFA is not available for this account type."
|
||||
|
||||
@@ -20,6 +20,7 @@ const (
|
||||
SYSTEM_INSTALLATION_DATE_KEY = "InstallationDate"
|
||||
SYSTEM_FIRST_SERVER_RUN_TIMESTAMP_KEY = "FirstServerRunTimestamp"
|
||||
SYSTEM_CLUSTER_ENCRYPTION_KEY = "ClusterEncryptionKey"
|
||||
SYSTEM_UPGRADED_FROM_TE_ID = "UpgradedFromTE"
|
||||
SYSTEM_WARN_METRIC_NUMBER_OF_ACTIVE_USERS_200 = "warn_metric_number_of_active_users_200"
|
||||
SYSTEM_WARN_METRIC_NUMBER_OF_ACTIVE_USERS_400 = "warn_metric_number_of_active_users_400"
|
||||
SYSTEM_WARN_METRIC_NUMBER_OF_ACTIVE_USERS_500 = "warn_metric_number_of_active_users_500"
|
||||
|
||||
49
services/upgrader/errors.go
Normal file
49
services/upgrader/errors.go
Normal file
@@ -0,0 +1,49 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package upgrader
|
||||
|
||||
import "fmt"
|
||||
|
||||
// InvalidArch indicates that the current operating system or cpu architecture doesn't support upgrades
|
||||
type InvalidArch struct{}
|
||||
|
||||
func NewInvalidArch() *InvalidArch {
|
||||
return &InvalidArch{}
|
||||
}
|
||||
|
||||
func (e *InvalidArch) Error() string {
|
||||
return "invalid operating system or processor architecture"
|
||||
}
|
||||
|
||||
// InvalidSignature indicates that the downloaded file doesn't have a valid signature.
|
||||
type InvalidSignature struct{}
|
||||
|
||||
func NewInvalidSignature() *InvalidSignature {
|
||||
return &InvalidSignature{}
|
||||
}
|
||||
|
||||
func (e *InvalidSignature) Error() string {
|
||||
return "invalid file signature"
|
||||
}
|
||||
|
||||
// InvalidPermissions indicates that the file permissions doesn't allow to upgrade
|
||||
type InvalidPermissions struct {
|
||||
ErrType string
|
||||
Path string
|
||||
FileUsername string
|
||||
MattermostUsername string
|
||||
}
|
||||
|
||||
func NewInvalidPermissions(errType string, path string, mattermostUsername string, fileUsername string) *InvalidPermissions {
|
||||
return &InvalidPermissions{
|
||||
ErrType: errType,
|
||||
Path: path,
|
||||
FileUsername: fileUsername,
|
||||
MattermostUsername: mattermostUsername,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *InvalidPermissions) Error() string {
|
||||
return fmt.Sprintf("the user %s is unable to update the %s file", e.MattermostUsername, e.Path)
|
||||
}
|
||||
17
services/upgrader/upgrader.go
Normal file
17
services/upgrader/upgrader.go
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
// +build !linux
|
||||
|
||||
package upgrader
|
||||
|
||||
func CanIUpgradeToE0() error {
|
||||
return &InvalidArch{}
|
||||
}
|
||||
|
||||
func UpgradeToE0() error {
|
||||
return &InvalidArch{}
|
||||
}
|
||||
|
||||
func UpgradeToE0Status() (int64, error) {
|
||||
return 0, &InvalidArch{}
|
||||
}
|
||||
332
services/upgrader/upgrader_linux.go
Normal file
332
services/upgrader/upgrader_linux.go
Normal file
@@ -0,0 +1,332 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package upgrader
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/user"
|
||||
"path"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/crypto/openpgp"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v5/mlog"
|
||||
"github.com/mattermost/mattermost-server/v5/model"
|
||||
)
|
||||
|
||||
const mattermostBuildPublicKey = `-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
mQENBFjZQxwBCAC6kNn3zDlq/aY83M9V7MHVPoK2jnZ3BfH7sA+ibQXsijCkPSR4
|
||||
5bCUJ9qVA4XKGK+cpO9vkolSNs10igCaaemaUZNB6ksu3gT737/SZcCAfRO+cLX7
|
||||
Q2la+jwTvu1YeT/M5xDZ1KHTFxsGskeIenz2rZHeuZwBl9qep34QszWtRX40eRts
|
||||
fl6WltLrepiExTp6NMZ50k+Em4JGM6CWBMo22ucy0jYjZXO5hEGb3o6NGiG+Dx2z
|
||||
b2J78LksCKGsSrn0F1rLJeA933bFL4g9ozv9asBlzmpgG77ESg6YE1N/Rh7WDzVA
|
||||
prIR0MuB5JjElASw5LDVxDV6RZsxEVQr7ETLABEBAAG0KU1hdHRlcm1vc3QgQnVp
|
||||
bGQgPGRldi1vcHNAbWF0dGVybW9zdC5jb20+iQFUBBMBCAA+AhsDBQsJCAcCBhUI
|
||||
CQoLAgQWAgMBAh4BAheAFiEEobMdRvDzoQsCzy1E+PLDF0R3SygFAl6HYr0FCQlw
|
||||
hqEACgkQ+PLDF0R3SyheNQgAnkiT2vFMCtU5FmC16HVYXzDpYMtdCQPh/gmeEkiI
|
||||
80rFRg/cn6f0BNnaTfDu6r6cepmhLNpDAowjQ7uBnv8fL2dzCydIGFv2r7FfmcOJ
|
||||
zhEQ3zXPwP6mYlxPCCgxAozsLv9Yv41KGCHIlzYwkAazc0BhpAW/h8L3VGkE+b+g
|
||||
x6lKVoufm4rKnT49Dgly6fVOxuR/BqZo87B5jksV3izLTHt5hiY8Pc5GW8WwO/tr
|
||||
pNAw+6HRXq1Dr/JRz5PIOr5KP5tVLBed4IteZ1xaTRd4++07ZbiZjhXY8WKpVp3y
|
||||
iN7Om24jQpxbJI9+KKJ3+yhcwhr8/PJ8ZVuhJo3BNv1PcQ==
|
||||
=9Qk8
|
||||
-----END PGP PUBLIC KEY BLOCK-----`
|
||||
|
||||
var upgradePercentage int64
|
||||
var upgradeError error
|
||||
var upgrading int32
|
||||
|
||||
type writeCounter struct {
|
||||
total int64
|
||||
readed int64
|
||||
}
|
||||
|
||||
func (wc *writeCounter) Write(p []byte) (int, error) {
|
||||
n := len(p)
|
||||
wc.readed += int64(n)
|
||||
percentage := (wc.readed * 100) / wc.total
|
||||
if percentage == 0 {
|
||||
upgradePercentage = 1
|
||||
} else if percentage == 100 {
|
||||
upgradePercentage = 99
|
||||
} else {
|
||||
upgradePercentage = percentage
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func getCurrentVersionTgzUrl() string {
|
||||
return "https://releases.mattermost.com/" + model.CurrentVersion + "/mattermost-" + model.CurrentVersion + "-linux-amd64.tar.gz"
|
||||
}
|
||||
|
||||
func verifySignature(filename string, sigfilename string, publicKey string) error {
|
||||
keyring, err := openpgp.ReadArmoredKeyRing(bytes.NewReader([]byte(publicKey)))
|
||||
if err != nil {
|
||||
mlog.Error("Unable to load the public key to verify the file signature", mlog.Err(err))
|
||||
return NewInvalidSignature()
|
||||
}
|
||||
|
||||
mattermost_tar, err := os.Open(filename)
|
||||
if err != nil {
|
||||
mlog.Error("Unable to open the Mattermost .tar file to verify the file signature", mlog.Err(err))
|
||||
return NewInvalidSignature()
|
||||
}
|
||||
|
||||
signature, err := os.Open(sigfilename)
|
||||
if err != nil {
|
||||
mlog.Error("Unable to open the Mattermost .sig file verify the file signature", mlog.Err(err))
|
||||
return NewInvalidSignature()
|
||||
}
|
||||
|
||||
_, err = openpgp.CheckDetachedSignature(keyring, mattermost_tar, signature)
|
||||
if err != nil {
|
||||
mlog.Error("Unable to verify the Mattermost file signature", mlog.Err(err))
|
||||
return NewInvalidSignature()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func canIWriteTheExecutable() error {
|
||||
executablePath, err := os.Executable()
|
||||
if err != nil {
|
||||
return errors.New("error getting the path of the executable")
|
||||
}
|
||||
executableInfo, err := os.Stat(path.Dir(executablePath))
|
||||
if err != nil {
|
||||
return errors.New("error getting the executable info")
|
||||
}
|
||||
stat, ok := executableInfo.Sys().(*syscall.Stat_t)
|
||||
if !ok {
|
||||
return errors.New("error getting the executable info")
|
||||
}
|
||||
fileUID := int(stat.Uid)
|
||||
fileUser, err := user.LookupId(strconv.Itoa(fileUID))
|
||||
if err != nil {
|
||||
return errors.New("error getting the executable info")
|
||||
}
|
||||
|
||||
mattermostUID := os.Getuid()
|
||||
mattermostUser, err := user.LookupId(strconv.Itoa(mattermostUID))
|
||||
if err != nil {
|
||||
return errors.New("error getting the executable info")
|
||||
}
|
||||
|
||||
mode := executableInfo.Mode()
|
||||
if fileUID != mattermostUID && mode&(1<<1) == 0 && mode&(1<<7) == 0 {
|
||||
return NewInvalidPermissions("invalid-user-and-permission", path.Dir(executablePath), mattermostUser.Username, fileUser.Username)
|
||||
}
|
||||
|
||||
if fileUID != mattermostUID && mode&(1<<1) == 0 && mode&(1<<7) != 0 {
|
||||
return NewInvalidPermissions("invalid-user", path.Dir(executablePath), mattermostUser.Username, fileUser.Username)
|
||||
}
|
||||
|
||||
if fileUID == mattermostUID && mode&(1<<7) == 0 {
|
||||
return NewInvalidPermissions("invalid-permission", path.Dir(executablePath), mattermostUser.Username, fileUser.Username)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func canIUpgrade() error {
|
||||
if runtime.GOARCH != "amd64" {
|
||||
return NewInvalidArch()
|
||||
}
|
||||
if runtime.GOOS != "linux" {
|
||||
return NewInvalidArch()
|
||||
}
|
||||
return canIWriteTheExecutable()
|
||||
}
|
||||
|
||||
func CanIUpgradeToE0() error {
|
||||
if err := canIUpgrade(); err != nil {
|
||||
mlog.Error("Unable to upgrade from TE to E0", mlog.Err(err))
|
||||
return err
|
||||
}
|
||||
if model.BuildEnterpriseReady == "true" {
|
||||
mlog.Warn("Unable to upgrade from TE to E0. The server is already running E0.")
|
||||
return errors.New("you cannot upgrade your server from TE to E0 because you are already running Mattermost Enterprise Edition")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func UpgradeToE0() error {
|
||||
if !atomic.CompareAndSwapInt32(&upgrading, 0, 1) {
|
||||
mlog.Warn("Trying to upgrade while another upgrade is running")
|
||||
return errors.New("another upgrade is already running")
|
||||
}
|
||||
defer atomic.CompareAndSwapInt32(&upgrading, 1, 0)
|
||||
|
||||
upgradePercentage = 1
|
||||
upgradeError = nil
|
||||
|
||||
executablePath, err := os.Executable()
|
||||
if err != nil {
|
||||
upgradePercentage = 0
|
||||
upgradeError = errors.New("error getting the executable path")
|
||||
mlog.Error("Unable to get the path of the Mattermost executable", mlog.Err(err))
|
||||
return err
|
||||
}
|
||||
|
||||
filename, err := download(getCurrentVersionTgzUrl(), 1024*1024*300)
|
||||
if err != nil {
|
||||
if filename != "" {
|
||||
os.Remove(filename)
|
||||
}
|
||||
upgradeError = fmt.Errorf("error downloading the new Mattermost server binary file (percentage: %d)", upgradePercentage)
|
||||
mlog.Error("Unable to download the Mattermost server binary file", mlog.Int64("percentage", upgradePercentage), mlog.String("url", getCurrentVersionTgzUrl()), mlog.Err(err))
|
||||
upgradePercentage = 0
|
||||
return err
|
||||
}
|
||||
defer os.Remove(filename)
|
||||
sigfilename, err := download(getCurrentVersionTgzUrl()+".sig", 1024)
|
||||
if err != nil {
|
||||
if sigfilename != "" {
|
||||
os.Remove(sigfilename)
|
||||
}
|
||||
upgradeError = errors.New("error downloading the signature file of the new server")
|
||||
mlog.Error("Unable to download the signature file of the new Mattermost server", mlog.String("url", getCurrentVersionTgzUrl()+".sig"), mlog.Err(err))
|
||||
upgradePercentage = 0
|
||||
return err
|
||||
}
|
||||
defer os.Remove(sigfilename)
|
||||
|
||||
err = verifySignature(filename, sigfilename, mattermostBuildPublicKey)
|
||||
if err != nil {
|
||||
upgradePercentage = 0
|
||||
upgradeError = errors.New("unable to verify the signature of the downloaded file")
|
||||
mlog.Error("Unable to verify the signature of the downloaded file", mlog.Err(err))
|
||||
return err
|
||||
}
|
||||
|
||||
err = extractBinary(executablePath, filename)
|
||||
if err != nil {
|
||||
upgradePercentage = 0
|
||||
upgradeError = err
|
||||
mlog.Error("Unable to extract the binary from the downloaded file", mlog.Err(err))
|
||||
return err
|
||||
}
|
||||
upgradePercentage = 100
|
||||
return nil
|
||||
}
|
||||
|
||||
func UpgradeToE0Status() (int64, error) {
|
||||
return upgradePercentage, upgradeError
|
||||
}
|
||||
|
||||
func download(url string, limit int64) (string, error) {
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
out, err := ioutil.TempFile("", "*_mattermost.tar.gz")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
counter := &writeCounter{total: resp.ContentLength}
|
||||
_, err = io.Copy(out, io.TeeReader(&io.LimitedReader{R: resp.Body, N: limit}, counter))
|
||||
return out.Name(), err
|
||||
}
|
||||
|
||||
func getFilePermissionsOrDefault(filename string, def os.FileMode) os.FileMode {
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
mlog.Error("Unable to get the file permissions", mlog.String("filename", filename), mlog.Err(err))
|
||||
return def
|
||||
}
|
||||
fileStats, err := file.Stat()
|
||||
if err != nil {
|
||||
mlog.Error("Unable to get the file permissions", mlog.String("filename", filename), mlog.Err(err))
|
||||
return def
|
||||
}
|
||||
return fileStats.Mode()
|
||||
}
|
||||
|
||||
func extractBinary(executablePath string, filename string) error {
|
||||
gzipStream, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
uncompressedStream, err := gzip.NewReader(gzipStream)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tarReader := tar.NewReader(uncompressedStream)
|
||||
|
||||
for {
|
||||
header, err := tarReader.Next()
|
||||
|
||||
if err == io.EOF {
|
||||
return errors.New("unable to find the Mattermost binary in the downloaded version")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if header.Typeflag == tar.TypeReg && header.Name == "mattermost/bin/mattermost" {
|
||||
permissions := getFilePermissionsOrDefault(executablePath, 0755)
|
||||
tmpFile, err := ioutil.TempFile(path.Dir(executablePath), "*")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tmpFileName := tmpFile.Name()
|
||||
os.Remove(tmpFileName)
|
||||
err = os.Rename(executablePath, tmpFileName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
outFile, err := os.Create(executablePath)
|
||||
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.")
|
||||
return errors.Wrap(err2, "critical error: unable to upgrade the binary or restore the old binary version. Please restore it manually")
|
||||
}
|
||||
return err
|
||||
}
|
||||
defer outFile.Close()
|
||||
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.")
|
||||
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.")
|
||||
return errors.Wrap(err2, "critical error: unable to upgrade the binary or restore the old binary version. Please restore it manually")
|
||||
}
|
||||
return err
|
||||
}
|
||||
err = os.Remove(tmpFileName)
|
||||
if err != nil {
|
||||
mlog.Warn("Unable to clean up the binary backup file.", mlog.Err(err))
|
||||
}
|
||||
err = os.Chmod(executablePath, permissions)
|
||||
if err != nil {
|
||||
mlog.Warn("Unable to set the correct permissions for the file.", mlog.Err(err))
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
134
services/upgrader/upgrader_test.go
Normal file
134
services/upgrader/upgrader_test.go
Normal file
@@ -0,0 +1,134 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package upgrader
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"compress/gzip"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v5/model"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCanIUpgradeToE0(t *testing.T) {
|
||||
t.Run("when you are already in an enterprise build", func(t *testing.T) {
|
||||
buildEnterprise := model.BuildEnterpriseReady
|
||||
model.BuildEnterpriseReady = "true"
|
||||
defer func() {
|
||||
model.BuildEnterpriseReady = buildEnterprise
|
||||
}()
|
||||
require.Error(t, CanIUpgradeToE0())
|
||||
})
|
||||
|
||||
t.Run("when you are not in an enterprise build", func(t *testing.T) {
|
||||
buildEnterprise := model.BuildEnterpriseReady
|
||||
model.BuildEnterpriseReady = "false"
|
||||
defer func() {
|
||||
model.BuildEnterpriseReady = buildEnterprise
|
||||
}()
|
||||
require.NoError(t, CanIUpgradeToE0())
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetCurrentVersionTgzUrl(t *testing.T) {
|
||||
currentVersion := model.CurrentVersion
|
||||
model.CurrentVersion = "5.22.0"
|
||||
defer func() {
|
||||
model.CurrentVersion = currentVersion
|
||||
}()
|
||||
require.Equal(t, "https://releases.mattermost.com/5.22.0/mattermost-5.22.0-linux-amd64.tar.gz", getCurrentVersionTgzUrl())
|
||||
}
|
||||
|
||||
func TestExtractBinary(t *testing.T) {
|
||||
t.Run("extract from empty file", func(t *testing.T) {
|
||||
tmpMockTarGz, err := ioutil.TempFile("", "mock_tgz")
|
||||
require.Nil(t, err)
|
||||
defer os.Remove(tmpMockTarGz.Name())
|
||||
tmpMockTarGz.Close()
|
||||
|
||||
tmpMockExecutable, err := ioutil.TempFile("", "mock_exe")
|
||||
require.Nil(t, err)
|
||||
defer os.Remove(tmpMockExecutable.Name())
|
||||
tmpMockExecutable.Close()
|
||||
|
||||
extractBinary(tmpMockExecutable.Name(), tmpMockTarGz.Name())
|
||||
})
|
||||
|
||||
t.Run("extract from empty tar.gz file", func(t *testing.T) {
|
||||
tmpMockTarGz, err := ioutil.TempFile("", "mock_tgz")
|
||||
require.Nil(t, err)
|
||||
defer os.Remove(tmpMockTarGz.Name())
|
||||
gz := gzip.NewWriter(tmpMockTarGz)
|
||||
tw := tar.NewWriter(gz)
|
||||
tw.Close()
|
||||
gz.Close()
|
||||
tmpMockTarGz.Close()
|
||||
|
||||
tmpMockExecutable, err := ioutil.TempFile("", "mock_exe")
|
||||
require.Nil(t, err)
|
||||
defer os.Remove(tmpMockExecutable.Name())
|
||||
tmpMockExecutable.Close()
|
||||
|
||||
require.Error(t, extractBinary(tmpMockExecutable.Name(), tmpMockTarGz.Name()))
|
||||
})
|
||||
|
||||
t.Run("extract from tar.gz without mattermost/bin/mattermost file", func(t *testing.T) {
|
||||
tmpMockTarGz, err := ioutil.TempFile("", "mock_tgz")
|
||||
require.Nil(t, err)
|
||||
defer os.Remove(tmpMockTarGz.Name())
|
||||
gz := gzip.NewWriter(tmpMockTarGz)
|
||||
tw := tar.NewWriter(gz)
|
||||
|
||||
tw.WriteHeader(&tar.Header{
|
||||
Typeflag: tar.TypeReg,
|
||||
Name: "test-filename",
|
||||
Size: 4,
|
||||
})
|
||||
tw.Write([]byte("test"))
|
||||
|
||||
gz.Close()
|
||||
tmpMockTarGz.Close()
|
||||
|
||||
tmpMockExecutable, err := ioutil.TempFile("", "mock_exe")
|
||||
require.Nil(t, err)
|
||||
defer os.Remove(tmpMockExecutable.Name())
|
||||
tmpMockExecutable.Close()
|
||||
|
||||
require.Error(t, extractBinary(tmpMockExecutable.Name(), tmpMockTarGz.Name()))
|
||||
})
|
||||
|
||||
t.Run("extract from tar.gz with mattermost/bin/mattermost file", func(t *testing.T) {
|
||||
tmpMockTarGz, err := ioutil.TempFile("", "mock_tgz")
|
||||
require.Nil(t, err)
|
||||
defer os.Remove(tmpMockTarGz.Name())
|
||||
gz := gzip.NewWriter(tmpMockTarGz)
|
||||
tw := tar.NewWriter(gz)
|
||||
|
||||
tw.WriteHeader(&tar.Header{
|
||||
Typeflag: tar.TypeReg,
|
||||
Name: "mattermost/bin/mattermost",
|
||||
Size: 4,
|
||||
})
|
||||
tw.Write([]byte("test"))
|
||||
|
||||
gz.Close()
|
||||
tmpMockTarGz.Close()
|
||||
|
||||
tmpMockExecutable, err := ioutil.TempFile("", "mock_exe")
|
||||
require.Nil(t, err)
|
||||
defer os.Remove(tmpMockExecutable.Name())
|
||||
tmpMockExecutable.Close()
|
||||
|
||||
require.NoError(t, extractBinary(tmpMockExecutable.Name(), tmpMockTarGz.Name()))
|
||||
tmpMockExecutableAfter, err := os.Open(tmpMockExecutable.Name())
|
||||
require.NoError(t, err)
|
||||
defer tmpMockExecutableAfter.Close()
|
||||
bytes, err := ioutil.ReadAll(tmpMockExecutableAfter)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []byte("test"), bytes)
|
||||
})
|
||||
}
|
||||
@@ -24,6 +24,7 @@ func (s *TestStore) Close() {
|
||||
func GetMockStoreForSetupFunctions() *mocks.Store {
|
||||
mockStore := mocks.Store{}
|
||||
systemStore := mocks.SystemStore{}
|
||||
systemStore.On("GetByName", "UpgradedFromTE").Return(nil, model.NewAppError("FakeError", "app.system.get_by_name.app_error", nil, "", http.StatusInternalServerError))
|
||||
systemStore.On("GetByName", "AsymmetricSigningKey").Return(nil, model.NewAppError("FakeError", "app.system.get_by_name.app_error", nil, "", http.StatusInternalServerError))
|
||||
systemStore.On("GetByName", "PostActionCookieSecret").Return(nil, model.NewAppError("FakeError", "app.system.get_by_name.app_error", nil, "", http.StatusInternalServerError))
|
||||
systemStore.On("GetByName", "InstallationDate").Return(&model.System{Name: "InstallationDate", Value: strconv.FormatInt(model.GetMillis(), 10)}, nil)
|
||||
|
||||
@@ -43,6 +43,7 @@ func TestMfaRequired(t *testing.T) {
|
||||
mockPostStore := mocks.PostStore{}
|
||||
mockPostStore.On("GetMaxPostSize").Return(65535, nil)
|
||||
mockSystemStore := mocks.SystemStore{}
|
||||
mockSystemStore.On("GetByName", "UpgradedFromTE").Return(&model.System{Name: "UpgradedFromTE", Value: "false"}, nil)
|
||||
mockSystemStore.On("GetByName", "InstallationDate").Return(&model.System{Name: "InstallationDate", Value: "10"}, nil)
|
||||
|
||||
mockStore.On("User").Return(&mockUserStore)
|
||||
|
||||
@@ -70,6 +70,7 @@ func TestHandlerServeHTTPSecureTransport(t *testing.T) {
|
||||
mockPostStore := mocks.PostStore{}
|
||||
mockPostStore.On("GetMaxPostSize").Return(65535, nil)
|
||||
mockSystemStore := mocks.SystemStore{}
|
||||
mockSystemStore.On("GetByName", "UpgradedFromTE").Return(&model.System{Name: "UpgradedFromTE", Value: "false"}, nil)
|
||||
mockSystemStore.On("GetByName", "InstallationDate").Return(&model.System{Name: "InstallationDate", Value: "10"}, nil)
|
||||
mockSystemStore.On("GetByName", "FirstServerRunTimestamp").Return(&model.System{Name: "FirstServerRunTimestamp", Value: "10"}, nil)
|
||||
|
||||
@@ -311,6 +312,7 @@ func TestHandlerServeCSPHeader(t *testing.T) {
|
||||
mockPostStore := mocks.PostStore{}
|
||||
mockPostStore.On("GetMaxPostSize").Return(65535, nil)
|
||||
mockSystemStore := mocks.SystemStore{}
|
||||
mockSystemStore.On("GetByName", "UpgradedFromTE").Return(&model.System{Name: "UpgradedFromTE", Value: "false"}, nil)
|
||||
mockSystemStore.On("GetByName", "InstallationDate").Return(&model.System{Name: "InstallationDate", Value: "10"}, nil)
|
||||
mockSystemStore.On("GetByName", "FirstServerRunTimestamp").Return(&model.System{Name: "FirstServerRunTimestamp", Value: "10"}, nil)
|
||||
|
||||
@@ -477,6 +479,7 @@ func TestCheckCSRFToken(t *testing.T) {
|
||||
mockPostStore := mocks.PostStore{}
|
||||
mockPostStore.On("GetMaxPostSize").Return(65535, nil)
|
||||
mockSystemStore := mocks.SystemStore{}
|
||||
mockSystemStore.On("GetByName", "UpgradedFromTE").Return(&model.System{Name: "UpgradedFromTE", Value: "false"}, nil)
|
||||
mockSystemStore.On("GetByName", "InstallationDate").Return(&model.System{Name: "InstallationDate", Value: "10"}, nil)
|
||||
mockSystemStore.On("GetByName", "FirstServerRunTimestamp").Return(&model.System{Name: "FirstServerRunTimestamp", Value: "10"}, nil)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user