* config file store Introduce an interface and concrete implementation for accessing the config. This mostly maps 1:1 with the exiting usage in `App`, except for internalizing the watcher. A future change will likely eliminate `App.PersistConfig()` and make this implicit on `Set` or `Patch` * experimental file test changes * emoji: move file driver checks from api4 to app It is no longer possible to app.UpdateConfig and provide an invalid configuration, making it hard to test this case. This check doesn't really belong in the api anyway, since it's a configuration validity check and not a permissions check. Either way, the check now occurs at the App level. * api4: generate valid public link salts for test * TestStartServerRateLimiterCriticalError: use mock store to test invalid config * remove config_test.go * remove needsSave, and have Load() save to the backing store as necessary * restore README.md * move ldap UserFilter check to model isValid checks * remove databaseStore until ready * remove unimplemented Patch * simplify unlockOnce implementation * revert forgetting to set s.Ldap * config/file.go: rename ReadOnlyConfigurationError to ErrReadOnlyConfiguration * config: export FileStore * add TestFileStoreSave * improved config/utils test coverage * restore config/README.md copy * tweaks * file store: acquire a write lock on Save/Close to safely close watcher * fix unmarshal_test.go
234 строки
6.8 KiB
Go
234 строки
6.8 KiB
Go
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
|
// See License.txt for license information.
|
|
|
|
package app
|
|
|
|
import (
|
|
"io"
|
|
"os"
|
|
"time"
|
|
|
|
"runtime/debug"
|
|
|
|
"net/http"
|
|
|
|
"github.com/mattermost/mattermost-server/config"
|
|
"github.com/mattermost/mattermost-server/mlog"
|
|
"github.com/mattermost/mattermost-server/model"
|
|
"github.com/mattermost/mattermost-server/services/mailservice"
|
|
"github.com/mattermost/mattermost-server/utils"
|
|
)
|
|
|
|
func (a *App) GetLogs(page, perPage int) ([]string, *model.AppError) {
|
|
var lines []string
|
|
if a.Cluster != nil && *a.Config().ClusterSettings.Enable {
|
|
lines = append(lines, "-----------------------------------------------------------------------------------------------------------")
|
|
lines = append(lines, "-----------------------------------------------------------------------------------------------------------")
|
|
lines = append(lines, a.Cluster.GetMyClusterInfo().Hostname)
|
|
lines = append(lines, "-----------------------------------------------------------------------------------------------------------")
|
|
lines = append(lines, "-----------------------------------------------------------------------------------------------------------")
|
|
}
|
|
|
|
melines, err := a.GetLogsSkipSend(page, perPage)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
lines = append(lines, melines...)
|
|
|
|
if a.Cluster != nil && *a.Config().ClusterSettings.Enable {
|
|
clines, err := a.Cluster.GetLogs(page, perPage)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
lines = append(lines, clines...)
|
|
}
|
|
|
|
return lines, nil
|
|
}
|
|
|
|
func (a *App) GetLogsSkipSend(page, perPage int) ([]string, *model.AppError) {
|
|
var lines []string
|
|
|
|
if *a.Config().LogSettings.EnableFile {
|
|
file, err := os.Open(utils.GetLogFileLocation(*a.Config().LogSettings.FileLocation))
|
|
if err != nil {
|
|
return nil, model.NewAppError("getLogs", "api.admin.file_read_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
defer file.Close()
|
|
|
|
var newLine = []byte{'\n'}
|
|
var lineCount int
|
|
const searchPos = -1
|
|
lineEndPos, err := file.Seek(0, io.SeekEnd)
|
|
if err != nil {
|
|
return nil, model.NewAppError("getLogs", "api.admin.file_read_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
for {
|
|
pos, err := file.Seek(searchPos, io.SeekCurrent)
|
|
if err != nil {
|
|
return nil, model.NewAppError("getLogs", "api.admin.file_read_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
b := make([]byte, 1)
|
|
_, err = file.ReadAt(b, pos)
|
|
if err != nil {
|
|
return nil, model.NewAppError("getLogs", "api.admin.file_read_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
if b[0] == newLine[0] || pos == 0 {
|
|
lineCount++
|
|
if lineCount > page*perPage {
|
|
line := make([]byte, lineEndPos-pos)
|
|
_, err := file.ReadAt(line, pos)
|
|
if err != nil {
|
|
return nil, model.NewAppError("getLogs", "api.admin.file_read_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
lines = append(lines, string(line))
|
|
}
|
|
if pos == 0 {
|
|
break
|
|
}
|
|
lineEndPos = pos
|
|
}
|
|
|
|
if len(lines) == perPage {
|
|
break
|
|
}
|
|
}
|
|
|
|
for i, j := 0, len(lines)-1; i < j; i, j = i+1, j-1 {
|
|
lines[i], lines[j] = lines[j], lines[i]
|
|
}
|
|
} else {
|
|
lines = append(lines, "")
|
|
}
|
|
|
|
return lines, nil
|
|
}
|
|
|
|
func (a *App) GetClusterStatus() []*model.ClusterInfo {
|
|
infos := make([]*model.ClusterInfo, 0)
|
|
|
|
if a.Cluster != nil {
|
|
infos = a.Cluster.GetClusterInfos()
|
|
}
|
|
|
|
return infos
|
|
}
|
|
|
|
func (a *App) InvalidateAllCaches() *model.AppError {
|
|
debug.FreeOSMemory()
|
|
a.InvalidateAllCachesSkipSend()
|
|
|
|
if a.Cluster != nil {
|
|
|
|
msg := &model.ClusterMessage{
|
|
Event: model.CLUSTER_EVENT_INVALIDATE_ALL_CACHES,
|
|
SendType: model.CLUSTER_SEND_RELIABLE,
|
|
WaitForAllToSend: true,
|
|
}
|
|
|
|
a.Cluster.SendClusterMessage(msg)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (a *App) InvalidateAllCachesSkipSend() {
|
|
mlog.Info("Purging all caches")
|
|
a.Srv.sessionCache.Purge()
|
|
ClearStatusCache()
|
|
a.Srv.Store.Channel().ClearCaches()
|
|
a.Srv.Store.User().ClearCaches()
|
|
a.Srv.Store.Post().ClearCaches()
|
|
a.Srv.Store.FileInfo().ClearCaches()
|
|
a.Srv.Store.Webhook().ClearCaches()
|
|
a.LoadLicense()
|
|
}
|
|
|
|
func (a *App) GetSanitizedConfig() *model.Config {
|
|
cfg := a.Config().Clone()
|
|
cfg.Sanitize()
|
|
|
|
return cfg
|
|
}
|
|
|
|
func (a *App) GetEnvironmentConfig() map[string]interface{} {
|
|
return a.EnvironmentConfig()
|
|
}
|
|
|
|
func (a *App) SaveConfig(newCfg *model.Config, sendConfigChangeClusterMessage bool) *model.AppError {
|
|
oldCfg, err := a.Srv.configStore.Set(newCfg)
|
|
if err == config.ErrReadOnlyConfiguration {
|
|
return model.NewAppError("saveConfig", "ent.cluster.save_config.error", nil, err.Error(), http.StatusForbidden)
|
|
} else if err != nil {
|
|
return model.NewAppError("saveConfig", "app.save_config.app_error", nil, err.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
a.PersistConfig()
|
|
if a.Metrics != nil {
|
|
if *a.Config().MetricsSettings.Enable {
|
|
a.Metrics.StartServer()
|
|
} else {
|
|
a.Metrics.StopServer()
|
|
}
|
|
}
|
|
|
|
if a.Cluster != nil {
|
|
err := a.Cluster.ConfigChanged(newCfg, oldCfg, sendConfigChangeClusterMessage)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (a *App) RecycleDatabaseConnection() {
|
|
oldStore := a.Srv.Store
|
|
|
|
mlog.Warn("Attempting to recycle the database connection.")
|
|
a.Srv.Store = a.Srv.newStore()
|
|
a.Srv.Jobs.Store = a.Srv.Store
|
|
|
|
if a.Srv.Store != oldStore {
|
|
time.Sleep(20 * time.Second)
|
|
oldStore.Close()
|
|
}
|
|
|
|
mlog.Warn("Finished recycling the database connection.")
|
|
}
|
|
|
|
func (a *App) TestEmail(userId string, cfg *model.Config) *model.AppError {
|
|
if len(*cfg.EmailSettings.SMTPServer) == 0 {
|
|
return model.NewAppError("testEmail", "api.admin.test_email.missing_server", nil, utils.T("api.context.invalid_param.app_error", map[string]interface{}{"Name": "SMTPServer"}), http.StatusBadRequest)
|
|
}
|
|
|
|
// if the user hasn't changed their email settings, fill in the actual SMTP password so that
|
|
// the user can verify an existing SMTP connection
|
|
if *cfg.EmailSettings.SMTPPassword == model.FAKE_SETTING {
|
|
if *cfg.EmailSettings.SMTPServer == *a.Config().EmailSettings.SMTPServer &&
|
|
*cfg.EmailSettings.SMTPPort == *a.Config().EmailSettings.SMTPPort &&
|
|
*cfg.EmailSettings.SMTPUsername == *a.Config().EmailSettings.SMTPUsername {
|
|
*cfg.EmailSettings.SMTPPassword = *a.Config().EmailSettings.SMTPPassword
|
|
} else {
|
|
return model.NewAppError("testEmail", "api.admin.test_email.reenter_password", nil, "", http.StatusBadRequest)
|
|
}
|
|
}
|
|
user, err := a.GetUser(userId)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
T := utils.GetUserTranslations(user.Locale)
|
|
license := a.License()
|
|
if err := mailservice.SendMailUsingConfig(user.Email, T("api.admin.test_email.subject"), T("api.admin.test_email.body"), cfg, license != nil && *license.Features.Compliance); err != nil {
|
|
return model.NewAppError("testEmail", "app.admin.test_email.failure", map[string]interface{}{"Error": err.Error()}, "", http.StatusInternalServerError)
|
|
}
|
|
|
|
return nil
|
|
}
|