Move config store into Platform Service (#20718)

* update config methods
Этот коммит содержится в:
Ibrahim Serdar Acikgoz
2022-08-08 16:52:31 +03:00
коммит произвёл GitHub
родитель b826421716
Коммит f0e4794cda
43 изменённых файлов: 764 добавлений и 519 удалений

Просмотреть файл

@@ -29,7 +29,7 @@ func (s *Server) GetLogs(page, perPage int) ([]string, *model.AppError) {
var lines []string var lines []string
license := s.License() license := s.License()
if license != nil && *license.Features.Cluster && s.Cluster != nil && *s.Config().ClusterSettings.Enable { if license != nil && *license.Features.Cluster && s.Cluster != nil && *s.platform.Config().ClusterSettings.Enable {
if info := s.Cluster.GetMyClusterInfo(); info != nil { if info := s.Cluster.GetMyClusterInfo(); info != nil {
lines = append(lines, "-----------------------------------------------------------------------------------------------------------") lines = append(lines, "-----------------------------------------------------------------------------------------------------------")
lines = append(lines, "-----------------------------------------------------------------------------------------------------------") lines = append(lines, "-----------------------------------------------------------------------------------------------------------")
@@ -48,7 +48,7 @@ func (s *Server) GetLogs(page, perPage int) ([]string, *model.AppError) {
lines = append(lines, melines...) lines = append(lines, melines...)
if s.Cluster != nil && *s.Config().ClusterSettings.Enable { if s.Cluster != nil && *s.platform.Config().ClusterSettings.Enable {
clines, err := s.Cluster.GetLogs(page, perPage) clines, err := s.Cluster.GetLogs(page, perPage)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -67,9 +67,9 @@ func (a *App) GetLogs(page, perPage int) ([]string, *model.AppError) {
func (s *Server) GetLogsSkipSend(page, perPage int) ([]string, *model.AppError) { func (s *Server) GetLogsSkipSend(page, perPage int) ([]string, *model.AppError) {
var lines []string var lines []string
if *s.Config().LogSettings.EnableFile { if *s.platform.Config().LogSettings.EnableFile {
s.Log.Flush() s.Log.Flush()
logFile := config.GetLogFileLocation(*s.Config().LogSettings.FileLocation) logFile := config.GetLogFileLocation(*s.platform.Config().LogSettings.FileLocation)
file, err := os.Open(logFile) file, err := os.Open(logFile)
if err != nil { if err != nil {
return nil, model.NewAppError("getLogs", "api.admin.file_read_error", nil, err.Error(), http.StatusInternalServerError) return nil, model.NewAppError("getLogs", "api.admin.file_read_error", nil, err.Error(), http.StatusInternalServerError)

Просмотреть файл

@@ -283,6 +283,8 @@ type AppIface interface {
// PromoteGuestToUser Convert user's roles and all his membership's roles from // PromoteGuestToUser Convert user's roles and all his membership's roles from
// guest roles to regular user roles. // guest roles to regular user roles.
PromoteGuestToUser(c *request.Context, user *model.User, requestorId string) *model.AppError PromoteGuestToUser(c *request.Context, user *model.User, requestorId string) *model.AppError
// Removes a listener function by the unique ID returned when AddConfigListener was called
RemoveConfigListener(id string)
// RenameChannel is used to rename the channel Name and the DisplayName fields // RenameChannel is used to rename the channel Name and the DisplayName fields
RenameChannel(c request.CTX, channel *model.Channel, newChannelName string, newDisplayName string) (*model.Channel, *model.AppError) RenameChannel(c request.CTX, channel *model.Channel, newChannelName string, newDisplayName string) (*model.Channel, *model.AppError)
// RenameTeam is used to rename the team Name and the DisplayName fields // RenameTeam is used to rename the team Name and the DisplayName fields
@@ -941,7 +943,6 @@ type AppIface interface {
ReloadConfig() error ReloadConfig() error
RemoveAllDeactivatedMembersFromChannel(c request.CTX, channel *model.Channel) *model.AppError RemoveAllDeactivatedMembersFromChannel(c request.CTX, channel *model.Channel) *model.AppError
RemoveChannelsFromRetentionPolicy(policyID string, channelIDs []string) *model.AppError RemoveChannelsFromRetentionPolicy(policyID string, channelIDs []string) *model.AppError
RemoveConfigListener(id string)
RemoveCustomStatus(c request.CTX, userID string) *model.AppError RemoveCustomStatus(c request.CTX, userID string) *model.AppError
RemoveDirectory(path string) *model.AppError RemoveDirectory(path string) *model.AppError
RemoveFile(path string) *model.AppError RemoveFile(path string) *model.AppError

Просмотреть файл

@@ -109,10 +109,10 @@ func (s *Server) configureAudit(adt *audit.Audit, bAllowAdvancedLogging bool) er
adt.OnError = s.onAuditError adt.OnError = s.onAuditError
var logConfigSrc config.LogConfigSrc var logConfigSrc config.LogConfigSrc
dsn := *s.Config().ExperimentalAuditSettings.AdvancedLoggingConfig dsn := *s.platform.Config().ExperimentalAuditSettings.AdvancedLoggingConfig
if bAllowAdvancedLogging && dsn != "" { if bAllowAdvancedLogging && dsn != "" {
var err error var err error
logConfigSrc, err = config.NewLogConfigSrc(dsn, s.configStore.Store) logConfigSrc, err = config.NewLogConfigSrc(dsn, s.platform.GetConfigStore())
if err != nil { if err != nil {
return fmt.Errorf("invalid config source for audit, %w", err) return fmt.Errorf("invalid config source for audit, %w", err)
} }
@@ -120,7 +120,7 @@ func (s *Server) configureAudit(adt *audit.Audit, bAllowAdvancedLogging bool) er
} }
// ExperimentalAuditSettings provides basic file audit (E0, E10); logConfigSrc provides advanced config (E20). // ExperimentalAuditSettings provides basic file audit (E0, E10); logConfigSrc provides advanced config (E20).
cfg, err := config.MloggerConfigFromAuditConfig(s.Config().ExperimentalAuditSettings, logConfigSrc) cfg, err := config.MloggerConfigFromAuditConfig(s.platform.Config().ExperimentalAuditSettings, logConfigSrc)
if err != nil { if err != nil {
return fmt.Errorf("invalid config for audit, %w", err) return fmt.Errorf("invalid config for audit, %w", err)
} }

Просмотреть файл

@@ -2056,7 +2056,7 @@ func TestMarkChannelsAsViewedPanic(t *testing.T) {
UserStore: &mockUserStore, UserStore: &mockUserStore,
SessionStore: &mockSessionStore, SessionStore: &mockSessionStore,
OAuthStore: &mockOAuthStore, OAuthStore: &mockOAuthStore,
ConfigFn: th.App.ch.srv.Config, ConfigFn: th.App.ch.srv.platform.Config,
LicenseFn: th.App.ch.srv.License, LicenseFn: th.App.ch.srv.License,
}) })
require.NoError(t, err) require.NoError(t, err)

Просмотреть файл

@@ -31,12 +31,6 @@ type licenseSvc interface {
RequestTrialLicense(requesterID string, users int, termsAccepted bool, receiveEmailsAccepted bool) *model.AppError RequestTrialLicense(requesterID string, users int, termsAccepted bool, receiveEmailsAccepted bool) *model.AppError
} }
// namer is an interface which enforces that
// all services can return their names.
type namer interface {
Name() ServiceKey
}
// Channels contains all channels related state. // Channels contains all channels related state.
type Channels struct { type Channels struct {
srv *Server srv *Server
@@ -107,7 +101,7 @@ func init() {
func NewChannels(s *Server, services map[ServiceKey]any) (*Channels, error) { func NewChannels(s *Server, services map[ServiceKey]any) (*Channels, error) {
ch := &Channels{ ch := &Channels{
srv: s, srv: s,
imageProxy: imageproxy.MakeImageProxy(s, s.httpService, s.Log), imageProxy: imageproxy.MakeImageProxy(s.platform, s.httpService, s.Log),
uploadLockMap: map[string]bool{}, uploadLockMap: map[string]bool{},
} }
@@ -133,10 +127,6 @@ func NewChannels(s *Server, services map[ServiceKey]any) (*Channels, error) {
if !ok { if !ok {
return nil, errors.New("Config service did not satisfy ConfigSvc interface") return nil, errors.New("Config service did not satisfy ConfigSvc interface")
} }
_, ok = svc.(namer)
if !ok {
return nil, errors.New("Config service does not contain Name method")
}
ch.cfgSvc = cfgSvc ch.cfgSvc = cfgSvc
case FilestoreKey: case FilestoreKey:
filestore, ok := svc.(filestore.FileBackend) filestore, ok := svc.(filestore.FileBackend)
@@ -149,10 +139,6 @@ func NewChannels(s *Server, services map[ServiceKey]any) (*Channels, error) {
if !ok { if !ok {
return nil, errors.New("License service did not satisfy licenseSvc interface") return nil, errors.New("License service did not satisfy licenseSvc interface")
} }
_, ok = svc.(namer)
if !ok {
return nil, errors.New("License service does not contain Name method")
}
ch.licenseSvc = svc ch.licenseSvc = svc
} }
} }

Просмотреть файл

@@ -83,7 +83,7 @@ func (cds *ClusterDiscoveryService) Stop() {
} }
func (s *Server) IsLeader() bool { func (s *Server) IsLeader() bool {
if s.License() != nil && *s.Config().ClusterSettings.Enable && s.Cluster != nil { if s.License() != nil && *s.platform.Config().ClusterSettings.Enable && s.Cluster != nil {
return s.Cluster.IsLeader() return s.Cluster.IsLeader()
} }
return true return true

Просмотреть файл

@@ -12,7 +12,6 @@ import (
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http"
"net/url" "net/url"
"reflect" "reflect"
"strconv" "strconv"
@@ -22,7 +21,6 @@ import (
"github.com/mattermost/mattermost-server/v6/config" "github.com/mattermost/mattermost-server/v6/config"
"github.com/mattermost/mattermost-server/v6/model" "github.com/mattermost/mattermost-server/v6/model"
"github.com/mattermost/mattermost-server/v6/product"
"github.com/mattermost/mattermost-server/v6/shared/mail" "github.com/mattermost/mattermost-server/v6/shared/mail"
"github.com/mattermost/mattermost-server/v6/shared/mlog" "github.com/mattermost/mattermost-server/v6/shared/mlog"
"github.com/mattermost/mattermost-server/v6/utils" "github.com/mattermost/mattermost-server/v6/utils"
@@ -32,113 +30,24 @@ const (
ErrorTermsOfServiceNoRowsFound = "app.terms_of_service.get.no_rows.app_error" ErrorTermsOfServiceNoRowsFound = "app.terms_of_service.get.no_rows.app_error"
) )
// ensure the config wrapper implements `product.ConfigService`
var _ product.ConfigService = (*configWrapper)(nil)
// configWrapper is an adapter struct that only exposes the
// config related functionality to be passed down to other products.
type configWrapper struct {
srv *Server
*config.Store
}
func (w *configWrapper) Name() ServiceKey {
return ConfigKey
}
func (w *configWrapper) Config() *model.Config {
return w.Store.Get()
}
func (w *configWrapper) AddConfigListener(listener func(*model.Config, *model.Config)) string {
return w.Store.AddListener(listener)
}
func (w *configWrapper) RemoveConfigListener(id string) {
w.Store.RemoveListener(id)
}
func (w *configWrapper) UpdateConfig(f func(*model.Config)) {
if w.Store.IsReadOnly() {
return
}
old := w.Config()
updated := old.Clone()
f(updated)
if _, _, err := w.Store.Set(updated); err != nil {
mlog.Error("Failed to update config", mlog.Err(err))
}
}
func (w *configWrapper) SaveConfig(newCfg *model.Config, sendConfigChangeClusterMessage bool) (*model.Config, *model.Config, *model.AppError) {
oldCfg, newCfg, err := w.Store.Set(newCfg)
if errors.Cause(err) == config.ErrReadOnlyConfiguration {
return nil, nil, model.NewAppError("saveConfig", "ent.cluster.save_config.error", nil, err.Error(), http.StatusForbidden)
} else if err != nil {
return nil, nil, model.NewAppError("saveConfig", "app.save_config.app_error", nil, err.Error(), http.StatusInternalServerError)
}
if w.srv.startMetrics && *w.Config().MetricsSettings.Enable {
if w.srv.GetMetrics() != nil {
w.srv.GetMetrics().Register()
}
w.srv.platform.RestartMetrics() // TODO: remove when this moved to the platform service
} else {
w.srv.platform.ShutdownMetrics() // TODO: remove when this moved to the platform service
}
if w.srv.Cluster != nil {
err := w.srv.Cluster.ConfigChanged(w.Store.RemoveEnvironmentOverrides(oldCfg),
w.Store.RemoveEnvironmentOverrides(newCfg), sendConfigChangeClusterMessage)
if err != nil {
return nil, nil, err
}
}
return oldCfg, newCfg, nil
}
func (w *configWrapper) ReloadConfig() error {
if err := w.Store.Load(); err != nil {
return err
}
return nil
}
func (s *Server) Config() *model.Config { func (s *Server) Config() *model.Config {
return s.configStore.Config() return s.platform.Config()
}
func (s *Server) ConfigStore() *configWrapper {
return s.configStore
} }
func (a *App) Config() *model.Config { func (a *App) Config() *model.Config {
return a.ch.cfgSvc.Config() return a.ch.cfgSvc.Config()
} }
func (s *Server) EnvironmentConfig(filter func(reflect.StructField) bool) map[string]any {
return s.configStore.GetEnvironmentOverridesWithFilter(filter)
}
func (a *App) EnvironmentConfig(filter func(reflect.StructField) bool) map[string]any { func (a *App) EnvironmentConfig(filter func(reflect.StructField) bool) map[string]any {
return a.Srv().EnvironmentConfig(filter) return a.Srv().platform.GetEnvironmentOverridesWithFilter(filter)
}
func (s *Server) UpdateConfig(f func(*model.Config)) {
s.configStore.UpdateConfig(f)
} }
func (a *App) UpdateConfig(f func(*model.Config)) { func (a *App) UpdateConfig(f func(*model.Config)) {
a.Srv().UpdateConfig(f) a.Srv().platform.UpdateConfig(f)
}
func (s *Server) ReloadConfig() error {
return s.configStore.ReloadConfig()
} }
func (a *App) ReloadConfig() error { func (a *App) ReloadConfig() error {
return a.Srv().ReloadConfig() return a.Srv().platform.ReloadConfig()
} }
func (a *App) ClientConfig() map[string]string { func (a *App) ClientConfig() map[string]string {
@@ -153,24 +62,13 @@ func (a *App) LimitedClientConfig() map[string]string {
return a.ch.limitedClientConfig.Load().(map[string]string) return a.ch.limitedClientConfig.Load().(map[string]string)
} }
// Registers a function with a given listener to be called when the config is reloaded and may have changed. The function
// will be called with two arguments: the old config and the new config. AddConfigListener returns a unique ID
// for the listener that can later be used to remove it.
func (s *Server) AddConfigListener(listener func(*model.Config, *model.Config)) string {
return s.configStore.AddConfigListener(listener)
}
func (a *App) AddConfigListener(listener func(*model.Config, *model.Config)) string { func (a *App) AddConfigListener(listener func(*model.Config, *model.Config)) string {
return a.Srv().AddConfigListener(listener) return a.Srv().platform.AddConfigListener(listener)
} }
// Removes a listener function by the unique ID returned when AddConfigListener was called // Removes a listener function by the unique ID returned when AddConfigListener was called
func (s *Server) RemoveConfigListener(id string) {
s.configStore.RemoveConfigListener(id)
}
func (a *App) RemoveConfigListener(id string) { func (a *App) RemoveConfigListener(id string) {
a.Srv().RemoveConfigListener(id) a.Srv().platform.RemoveConfigListener(id)
} }
// ensurePostActionCookieSecret ensures that the key for encrypting PostActionCookie exists // ensurePostActionCookieSecret ensures that the key for encrypting PostActionCookie exists
@@ -449,7 +347,7 @@ func (a *App) LimitedClientConfigWithComputed() map[string]string {
// GetConfigFile proxies access to the given configuration file to the underlying config store. // GetConfigFile proxies access to the given configuration file to the underlying config store.
func (a *App) GetConfigFile(name string) ([]byte, error) { func (a *App) GetConfigFile(name string) ([]byte, error) {
data, err := a.Srv().configStore.GetFile(name) data, err := a.Srv().platform.GetConfigFile(name)
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "failed to get config file %s", name) return nil, errors.Wrapf(err, "failed to get config file %s", name)
} }
@@ -471,15 +369,9 @@ func (a *App) GetEnvironmentConfig(filter func(reflect.StructField) bool) map[st
return a.EnvironmentConfig(filter) return a.EnvironmentConfig(filter)
} }
// SaveConfig replaces the active configuration, optionally notifying cluster peers.
// It returns both the previous and current configs.
func (s *Server) SaveConfig(newCfg *model.Config, sendConfigChangeClusterMessage bool) (*model.Config, *model.Config, *model.AppError) {
return s.configStore.SaveConfig(newCfg, sendConfigChangeClusterMessage)
}
// SaveConfig replaces the active configuration, optionally notifying cluster peers. // SaveConfig replaces the active configuration, optionally notifying cluster peers.
func (a *App) SaveConfig(newCfg *model.Config, sendConfigChangeClusterMessage bool) (*model.Config, *model.Config, *model.AppError) { func (a *App) SaveConfig(newCfg *model.Config, sendConfigChangeClusterMessage bool) (*model.Config, *model.Config, *model.AppError) {
return a.Srv().SaveConfig(newCfg, sendConfigChangeClusterMessage) return a.Srv().platform.SaveConfig(newCfg, sendConfigChangeClusterMessage)
} }
func (a *App) HandleMessageExportConfig(cfg *model.Config, appCfg *model.Config) { func (a *App) HandleMessageExportConfig(cfg *model.Config, appCfg *model.Config) {
@@ -499,8 +391,8 @@ func (a *App) HandleMessageExportConfig(cfg *model.Config, appCfg *model.Config)
} }
func (s *Server) MailServiceConfig() *mail.SMTPConfig { func (s *Server) MailServiceConfig() *mail.SMTPConfig {
emailSettings := s.Config().EmailSettings emailSettings := s.platform.Config().EmailSettings
hostname := utils.GetHostnameFromSiteURL(*s.Config().ServiceSettings.SiteURL) hostname := utils.GetHostnameFromSiteURL(*s.platform.Config().ServiceSettings.SiteURL)
cfg := mail.SMTPConfig{ cfg := mail.SMTPConfig{
Hostname: hostname, Hostname: hostname,
ConnectionSecurity: *emailSettings.ConnectionSecurity, ConnectionSecurity: *emailSettings.ConnectionSecurity,

Просмотреть файл

@@ -16,41 +16,6 @@ import (
"github.com/mattermost/mattermost-server/v6/utils" "github.com/mattermost/mattermost-server/v6/utils"
) )
func TestConfigListener(t *testing.T) {
th := Setup(t)
defer th.TearDown()
originalSiteName := th.App.Config().TeamSettings.SiteName
listenerCalled := false
listener := func(oldConfig *model.Config, newConfig *model.Config) {
assert.False(t, listenerCalled, "listener called twice")
assert.Equal(t, *originalSiteName, *oldConfig.TeamSettings.SiteName, "old config contains incorrect site name")
assert.Equal(t, "test123", *newConfig.TeamSettings.SiteName, "new config contains incorrect site name")
listenerCalled = true
}
listenerId := th.App.AddConfigListener(listener)
defer th.App.RemoveConfigListener(listenerId)
listener2Called := false
listener2 := func(oldConfig *model.Config, newConfig *model.Config) {
assert.False(t, listener2Called, "listener2 called twice")
listener2Called = true
}
listener2Id := th.App.AddConfigListener(listener2)
defer th.App.RemoveConfigListener(listener2Id)
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.TeamSettings.SiteName = "test123"
})
assert.True(t, listenerCalled, "listener should've been called")
assert.True(t, listener2Called, "listener 2 should've been called")
}
func TestAsymmetricSigningKey(t *testing.T) { func TestAsymmetricSigningKey(t *testing.T) {
th := SetupWithStoreMock(t) th := SetupWithStoreMock(t)
defer th.TearDown() defer th.TearDown()

Просмотреть файл

@@ -35,7 +35,7 @@ func (s *Server) downloadFromURL(downloadURL string) ([]byte, error) {
if err != nil { if err != nil {
return nil, errors.Errorf("failed to parse url %s", downloadURL) return nil, errors.Errorf("failed to parse url %s", downloadURL)
} }
if !*s.Config().PluginSettings.AllowInsecureDownloadURL && u.Scheme != "https" { if !*s.platform.Config().PluginSettings.AllowInsecureDownloadURL && u.Scheme != "https" {
return nil, errors.Errorf("insecure url not allowed %s", downloadURL) return nil, errors.Errorf("insecure url not allowed %s", downloadURL)
} }

Просмотреть файл

@@ -87,9 +87,9 @@ func RegisterCloudInterface(f func(*Server) einterfaces.CloudInterface) {
cloudInterface = f cloudInterface = f
} }
var metricsInterface func(*Server) einterfaces.MetricsInterface var metricsInterface func(*Server, string, string) einterfaces.MetricsInterface
func RegisterMetricsInterface(f func(*Server) einterfaces.MetricsInterface) { func RegisterMetricsInterface(f func(*Server, string, string) einterfaces.MetricsInterface) {
metricsInterface = f metricsInterface = f
} }

Просмотреть файл

@@ -1,118 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package app
import (
"encoding/json"
"os"
"time"
"github.com/mattermost/mattermost-server/v6/app/featureflag"
"github.com/mattermost/mattermost-server/v6/shared/mlog"
)
// setupFeatureFlags called on startup and when the cluster leader changes.
// Starts or stops the synchronization of feature flags from upstream management.
func (s *Server) setupFeatureFlags() {
s.featureFlagSynchronizerMutex.Lock()
defer s.featureFlagSynchronizerMutex.Unlock()
splitKey := *s.Config().ServiceSettings.SplitKey
splitConfigured := splitKey != ""
syncFeatureFlags := splitConfigured && s.IsLeader()
s.configStore.SetReadOnlyFF(!splitConfigured)
if syncFeatureFlags {
if err := s.startFeatureFlagUpdateJob(); err != nil {
s.Log.Warn("Unable to setup synchronization with feature flag management. Will fallback to cache.", mlog.Err(err))
}
} else {
s.stopFeatureFlagUpdateJob()
}
if err := s.configStore.Load(); err != nil {
s.Log.Warn("Unable to load config store after feature flag setup.", mlog.Err(err))
}
}
func (s *Server) updateFeatureFlagValuesFromManagement() {
newCfg := s.configStore.GetNoEnv().Clone()
oldFlags := *newCfg.FeatureFlags
newFlags := s.featureFlagSynchronizer.UpdateFeatureFlagValues(oldFlags)
oldFlagsBytes, _ := json.Marshal(oldFlags)
newFlagsBytes, _ := json.Marshal(newFlags)
s.Log.Debug("Checking feature flags from management service", mlog.String("old_flags", string(oldFlagsBytes)), mlog.String("new_flags", string(newFlagsBytes)))
if oldFlags != newFlags {
s.Log.Debug("Feature flag change detected, updating config")
*newCfg.FeatureFlags = newFlags
s.SaveConfig(newCfg, true)
}
}
func (s *Server) startFeatureFlagUpdateJob() error {
// Can be run multiple times
if s.featureFlagSynchronizer != nil {
return nil
}
var log *mlog.Logger
if *s.Config().ServiceSettings.DebugSplit {
log = s.Log
}
attributes := map[string]any{}
// if we are part of a cloud installation, add its installation and group id
if installationId := os.Getenv("MM_CLOUD_INSTALLATION_ID"); installationId != "" {
attributes["installation_id"] = installationId
}
if groupId := os.Getenv("MM_CLOUD_GROUP_ID"); groupId != "" {
attributes["group_id"] = groupId
}
synchronizer, err := featureflag.NewSynchronizer(featureflag.SyncParams{
ServerID: s.TelemetryId(),
SplitKey: *s.Config().ServiceSettings.SplitKey,
Log: log,
Attributes: attributes,
})
if err != nil {
return err
}
s.featureFlagStop = make(chan struct{})
s.featureFlagStopped = make(chan struct{})
s.featureFlagSynchronizer = synchronizer
syncInterval := *s.Config().ServiceSettings.FeatureFlagSyncIntervalSeconds
go func() {
ticker := time.NewTicker(time.Duration(syncInterval) * time.Second)
defer ticker.Stop()
defer close(s.featureFlagStopped)
if err := synchronizer.EnsureReady(); err != nil {
s.Log.Warn("Problem connecting to feature flag management. Will fallback to cloud cache.", mlog.Err(err))
return
}
s.updateFeatureFlagValuesFromManagement()
for {
select {
case <-s.featureFlagStop:
return
case <-ticker.C:
s.updateFeatureFlagValuesFromManagement()
}
}
}()
return nil
}
func (s *Server) stopFeatureFlagUpdateJob() {
if s.featureFlagSynchronizer != nil {
close(s.featureFlagStop)
<-s.featureFlagStopped
s.featureFlagSynchronizer.Close()
s.featureFlagSynchronizer = nil
}
}

Просмотреть файл

@@ -191,7 +191,7 @@ func (a *App) writeLdapFile(filename string, fileData *multipart.FileHeader) *mo
return model.NewAppError("AddLdapCertificate", "api.admin.add_certificate.saving.app_error", nil, err.Error(), http.StatusInternalServerError) return model.NewAppError("AddLdapCertificate", "api.admin.add_certificate.saving.app_error", nil, err.Error(), http.StatusInternalServerError)
} }
err = a.Srv().configStore.SetFile(filename, data) err = a.Srv().platform.SetConfigFile(filename, data)
if err != nil { if err != nil {
return model.NewAppError("AddLdapCertificate", "api.admin.add_certificate.saving.app_error", nil, err.Error(), http.StatusInternalServerError) return model.NewAppError("AddLdapCertificate", "api.admin.add_certificate.saving.app_error", nil, err.Error(), http.StatusInternalServerError)
} }
@@ -234,7 +234,7 @@ func (a *App) AddLdapPrivateCertificate(fileData *multipart.FileHeader) *model.A
} }
func (a *App) removeLdapFile(filename string) *model.AppError { func (a *App) removeLdapFile(filename string) *model.AppError {
if err := a.Srv().configStore.RemoveFile(filename); err != nil { if err := a.Srv().platform.RemoveConfigFile(filename); err != nil {
return model.NewAppError("RemoveLdapFile", "api.admin.remove_certificate.delete.app_error", map[string]any{"Filename": filename}, err.Error(), http.StatusInternalServerError) return model.NewAppError("RemoveLdapFile", "api.admin.remove_certificate.delete.app_error", map[string]any{"Filename": filename}, err.Error(), http.StatusInternalServerError)
} }
return nil return nil

Просмотреть файл

@@ -48,7 +48,7 @@ func (w *licenseWrapper) GetLicense() *model.License {
} }
func (w *licenseWrapper) RequestTrialLicense(requesterID string, users int, termsAccepted bool, receiveEmailsAccepted bool) *model.AppError { func (w *licenseWrapper) RequestTrialLicense(requesterID string, users int, termsAccepted bool, receiveEmailsAccepted bool) *model.AppError {
if *w.srv.Config().ExperimentalSettings.RestrictSystemAdmin { if *w.srv.platform.Config().ExperimentalSettings.RestrictSystemAdmin {
return model.NewAppError("RequestTrialLicense", "api.restricted_system_admin", nil, "", http.StatusForbidden) return model.NewAppError("RequestTrialLicense", "api.restricted_system_admin", nil, "", http.StatusForbidden)
} }
@@ -75,8 +75,8 @@ func (w *licenseWrapper) RequestTrialLicense(requesterID string, users int, term
ServerID: w.srv.TelemetryId(), ServerID: w.srv.TelemetryId(),
Name: requester.GetDisplayName(model.ShowFullName), Name: requester.GetDisplayName(model.ShowFullName),
Email: requester.Email, Email: requester.Email,
SiteName: *w.srv.Config().TeamSettings.SiteName, SiteName: *w.srv.platform.Config().TeamSettings.SiteName,
SiteURL: *w.srv.Config().ServiceSettings.SiteURL, SiteURL: *w.srv.platform.Config().ServiceSettings.SiteURL,
Users: users, Users: users,
TermsAccepted: termsAccepted, TermsAccepted: termsAccepted,
ReceiveEmailsAccepted: receiveEmailsAccepted, ReceiveEmailsAccepted: receiveEmailsAccepted,
@@ -93,6 +93,11 @@ type JWTClaims struct {
jwt.StandardClaims jwt.StandardClaims
} }
func (s *Server) License() *model.License {
license, _ := s.licenseValue.Load().(*model.License)
return license
}
func (s *Server) LoadLicense() { func (s *Server) LoadLicense() {
// ENV var overrides all other sources of license. // ENV var overrides all other sources of license.
licenseStr := os.Getenv(LicenseEnv) licenseStr := os.Getenv(LicenseEnv)
@@ -131,7 +136,7 @@ func (s *Server) LoadLicense() {
if !model.IsValidId(licenseId) { if !model.IsValidId(licenseId) {
// Lets attempt to load the file from disk since it was missing from the DB // Lets attempt to load the file from disk since it was missing from the DB
license, licenseBytes := utils.GetAndValidateLicenseFileFromDisk(*s.Config().ServiceSettings.LicenseFileLocation) license, licenseBytes := utils.GetAndValidateLicenseFileFromDisk(*s.platform.Config().ServiceSettings.LicenseFileLocation)
if license != nil { if license != nil {
if _, err := s.SaveLicense(licenseBytes); err != nil { if _, err := s.SaveLicense(licenseBytes); err != nil {
@@ -177,13 +182,13 @@ func (s *Server) SaveLicense(licenseBytes []byte) (*model.License, *model.AppErr
return nil, model.NewAppError("addLicense", model.ExpiredLicenseError, nil, "", http.StatusBadRequest) return nil, model.NewAppError("addLicense", model.ExpiredLicenseError, nil, "", http.StatusBadRequest)
} }
if *s.Config().JobSettings.RunJobs && s.Jobs != nil { if *s.platform.Config().JobSettings.RunJobs && s.Jobs != nil {
if err := s.Jobs.StopWorkers(); err != nil && !errors.Is(err, jobs.ErrWorkersNotRunning) { if err := s.Jobs.StopWorkers(); err != nil && !errors.Is(err, jobs.ErrWorkersNotRunning) {
mlog.Warn("Stopping job server workers failed", mlog.Err(err)) mlog.Warn("Stopping job server workers failed", mlog.Err(err))
} }
} }
if *s.Config().JobSettings.RunScheduler && s.Jobs != nil { if *s.platform.Config().JobSettings.RunScheduler && s.Jobs != nil {
if err := s.Jobs.StopSchedulers(); err != nil && !errors.Is(err, jobs.ErrSchedulersNotRunning) { if err := s.Jobs.StopSchedulers(); err != nil && !errors.Is(err, jobs.ErrSchedulersNotRunning) {
mlog.Error("Stopping job server schedulers failed", mlog.Err(err)) mlog.Error("Stopping job server schedulers failed", mlog.Err(err))
} }
@@ -193,12 +198,12 @@ func (s *Server) SaveLicense(licenseBytes []byte) (*model.License, *model.AppErr
// restart job server workers - this handles the edge case where a license file is uploaded, but the job server // restart job server workers - this handles the edge case where a license file is uploaded, but the job server
// doesn't start until the server is restarted, which prevents the 'run job now' buttons in system console from // doesn't start until the server is restarted, which prevents the 'run job now' buttons in system console from
// functioning as expected // functioning as expected
if *s.Config().JobSettings.RunJobs && s.Jobs != nil { if *s.platform.Config().JobSettings.RunJobs && s.Jobs != nil {
if err := s.Jobs.StartWorkers(); err != nil { if err := s.Jobs.StartWorkers(); err != nil {
mlog.Error("Starting job server workers failed", mlog.Err(err)) mlog.Error("Starting job server workers failed", mlog.Err(err))
} }
} }
if *s.Config().JobSettings.RunScheduler && s.Jobs != nil { if *s.platform.Config().JobSettings.RunScheduler && s.Jobs != nil {
if err := s.Jobs.StartSchedulers(); err != nil && !errors.Is(err, jobs.ErrSchedulersRunning) { if err := s.Jobs.StartSchedulers(); err != nil && !errors.Is(err, jobs.ErrSchedulersRunning) {
mlog.Error("Starting job server schedulers failed", mlog.Err(err)) mlog.Error("Starting job server schedulers failed", mlog.Err(err))
} }
@@ -233,7 +238,7 @@ func (s *Server) SaveLicense(licenseBytes []byte) (*model.License, *model.AppErr
return nil, model.NewAppError("addLicense", "api.license.add_license.save_active.app_error", nil, "", http.StatusInternalServerError) return nil, model.NewAppError("addLicense", "api.license.add_license.save_active.app_error", nil, "", http.StatusInternalServerError)
} }
s.ReloadConfig() s.platform.ReloadConfig()
s.InvalidateAllCaches() s.InvalidateAllCaches()
return &license, nil return &license, nil
@@ -256,12 +261,20 @@ func (s *Server) SetLicense(license *model.License) bool {
license.Features.SetDefaults() license.Features.SetDefaults()
s.licenseValue.Store(license) s.licenseValue.Store(license)
if s.platform != nil {
s.platform.SetLicense(license)
}
s.clientLicenseValue.Store(utils.GetClientLicense(license)) s.clientLicenseValue.Store(utils.GetClientLicense(license))
return true return true
} }
s.licenseValue.Store((*model.License)(nil)) s.licenseValue.Store((*model.License)(nil))
s.clientLicenseValue.Store(map[string]string(nil)) s.clientLicenseValue.Store(map[string]string(nil))
if s.platform != nil {
s.platform.SetLicense((*model.License)(nil))
}
return false return false
} }
@@ -307,7 +320,7 @@ func (s *Server) RemoveLicense() *model.AppError {
} }
s.SetLicense(nil) s.SetLicense(nil)
s.ReloadConfig() s.platform.ReloadConfig()
s.InvalidateAllCaches() s.InvalidateAllCaches()
return nil return nil
@@ -360,7 +373,7 @@ func (s *Server) RequestTrialLicense(trialRequest *model.TrialLicenseRequest) *m
return err return err
} }
s.ReloadConfig() s.platform.ReloadConfig()
s.InvalidateAllCaches() s.InvalidateAllCaches()
return nil return nil

Просмотреть файл

@@ -69,9 +69,9 @@ func (s *Server) doAdvancedPermissionsMigration() {
return return
} }
config := s.Config() config := s.platform.Config()
*config.ServiceSettings.PostEditTimeLimit = -1 *config.ServiceSettings.PostEditTimeLimit = -1
if _, _, err := s.SaveConfig(config, true); err != nil { if _, _, err := s.platform.SaveConfig(config, true); err != nil {
mlog.Error("Failed to update config in Advanced Permissions Phase 1 Migration.", mlog.Err(err)) mlog.Error("Failed to update config in Advanced Permissions Phase 1 Migration.", mlog.Err(err))
} }
@@ -327,7 +327,7 @@ func (s *Server) doContentExtractionConfigDefaultTrueMigration() {
return return
} }
s.UpdateConfig(func(config *model.Config) { s.platform.UpdateConfig(func(config *model.Config) {
config.FileSettings.ExtractContent = model.NewBool(true) config.FileSettings.ExtractContent = model.NewBool(true)
}) })
@@ -474,7 +474,7 @@ const existingInstallationPostsThreshold = 10
func (s *Server) doFirstAdminSetupCompleteMigration() { func (s *Server) doFirstAdminSetupCompleteMigration() {
// Don't run the migration until the flag is turned on. // Don't run the migration until the flag is turned on.
if !s.Config().FeatureFlags.UseCaseOnboarding { if !s.platform.Config().FeatureFlags.UseCaseOnboarding {
return return
} }

Просмотреть файл

@@ -290,7 +290,7 @@ func (a *App) UpdateMobileAppBadge(userID string) {
} }
func (s *Server) createPushNotificationsHub(c request.CTX) { func (s *Server) createPushNotificationsHub(c request.CTX) {
buffer := *s.Config().EmailSettings.PushNotificationBuffer buffer := *s.platform.Config().EmailSettings.PushNotificationBuffer
hub := PushNotificationsHub{ hub := PushNotificationsHub{
notificationsChan: make(chan PushNotification, buffer), notificationsChan: make(chan PushNotification, buffer),
app: New(ServerConnector(s.Channels())), app: New(ServerConnector(s.Channels())),

Просмотреть файл

@@ -17,6 +17,7 @@ import (
"github.com/stretchr/testify/mock" "github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/mattermost/mattermost-server/v6/app/platform"
"github.com/mattermost/mattermost-server/v6/config" "github.com/mattermost/mattermost-server/v6/config"
"github.com/mattermost/mattermost-server/v6/model" "github.com/mattermost/mattermost-server/v6/model"
fmocks "github.com/mattermost/mattermost-server/v6/shared/filestore/mocks" fmocks "github.com/mattermost/mattermost-server/v6/shared/filestore/mocks"
@@ -1443,9 +1444,13 @@ func TestPushNotificationRace(t *testing.T) {
Router: mux.NewRouter(), Router: mux.NewRouter(),
filestore: &fmocks.FileBackend{}, filestore: &fmocks.FileBackend{},
} }
s.configStore = &configWrapper{srv: s, Store: memoryStore} var err error
s.platform, err = platform.New(platform.ServiceConfig{
ConfigStore: memoryStore,
})
require.NoError(t, err)
serviceMap := map[ServiceKey]any{ serviceMap := map[ServiceKey]any{
ConfigKey: s.configStore, ConfigKey: s.platform,
LicenseKey: &licenseWrapper{s}, LicenseKey: &licenseWrapper{s},
FilestoreKey: s.filestore, FilestoreKey: s.filestore,
} }

Просмотреть файл

@@ -6,6 +6,7 @@ package app
import ( import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/mattermost/mattermost-server/v6/app/platform"
"github.com/mattermost/mattermost-server/v6/config" "github.com/mattermost/mattermost-server/v6/config"
"github.com/mattermost/mattermost-server/v6/einterfaces" "github.com/mattermost/mattermost-server/v6/einterfaces"
"github.com/mattermost/mattermost-server/v6/model" "github.com/mattermost/mattermost-server/v6/model"
@@ -52,7 +53,22 @@ func Config(dsn string, readOnly bool, configDefaults *model.Config) Option {
return errors.Wrap(err, "failed to apply Config option") return errors.Wrap(err, "failed to apply Config option")
} }
s.configStore = &configWrapper{srv: s, Store: configStore} platformCfg := platform.ServiceConfig{
ConfigStore: configStore,
Logger: s.Log,
StartMetrics: s.startMetrics,
Cluster: s.Cluster,
}
if metricsInterface != nil {
platformCfg.Metrics = metricsInterface(s, *configStore.Get().SqlSettings.DriverName, *configStore.Get().SqlSettings.DataSource)
}
ps, sErr := platform.New(platformCfg)
if sErr != nil {
return errors.Wrap(sErr, "failed to initialize platform")
}
s.platform = ps
return nil return nil
} }
} }
@@ -60,7 +76,21 @@ func Config(dsn string, readOnly bool, configDefaults *model.Config) Option {
// ConfigStore applies the given config store, typically to replace the traditional sources with a memory store for testing. // ConfigStore applies the given config store, typically to replace the traditional sources with a memory store for testing.
func ConfigStore(configStore *config.Store) Option { func ConfigStore(configStore *config.Store) Option {
return func(s *Server) error { return func(s *Server) error {
s.configStore = &configWrapper{srv: s, Store: configStore} platformCfg := platform.ServiceConfig{
ConfigStore: configStore,
Logger: s.Log,
StartMetrics: s.startMetrics,
Cluster: s.Cluster,
}
if metricsInterface != nil {
platformCfg.Metrics = metricsInterface(s, *configStore.Get().SqlSettings.DriverName, *configStore.Get().SqlSettings.DataSource)
}
ps, sErr := platform.New(platformCfg)
if sErr != nil {
return errors.Wrap(sErr, "failed to initialize platform")
}
s.platform = ps
return nil return nil
} }

12
app/platform/cluster.go Обычный файл
Просмотреть файл

@@ -0,0 +1,12 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package platform
func (ps *PlatformService) IsLeader() bool {
if ps.License() != nil && *ps.Config().ClusterSettings.Enable && ps.cluster != nil {
return ps.cluster.IsLeader()
}
return true
}

Просмотреть файл

@@ -5,9 +5,14 @@ package platform
import ( import (
"errors" "errors"
"fmt"
"net/http"
"reflect"
"github.com/mattermost/mattermost-server/v6/config" "github.com/mattermost/mattermost-server/v6/config"
"github.com/mattermost/mattermost-server/v6/einterfaces" "github.com/mattermost/mattermost-server/v6/einterfaces"
"github.com/mattermost/mattermost-server/v6/model"
"github.com/mattermost/mattermost-server/v6/product"
"github.com/mattermost/mattermost-server/v6/shared/mlog" "github.com/mattermost/mattermost-server/v6/shared/mlog"
) )
@@ -30,7 +35,145 @@ func (c *ServiceConfig) validate() error {
} }
if c.Logger == nil { if c.Logger == nil {
return errors.New("Logger is required") var err error
// If Logger is not set, use a default logger temporarily.
// this should be removed once the logger is properly configured with the service config.
// MM-45841
c.Logger, err = mlog.NewLogger()
if err != nil {
return err
}
} }
return nil return nil
} }
// ensure the config wrapper implements `product.ConfigService`
var _ product.ConfigService = (*PlatformService)(nil)
func (ps *PlatformService) Config() *model.Config {
return ps.configStore.Get()
}
// Registers a function with a given listener to be called when the config is reloaded and may have changed. The function
// will be called with two arguments: the old config and the new config. AddConfigListener returns a unique ID
// for the listener that can later be used to remove it.
func (ps *PlatformService) AddConfigListener(listener func(*model.Config, *model.Config)) string {
return ps.configStore.AddListener(listener)
}
func (ps *PlatformService) RemoveConfigListener(id string) {
ps.configStore.RemoveListener(id)
}
func (ps *PlatformService) UpdateConfig(f func(*model.Config)) {
if ps.configStore.IsReadOnly() {
return
}
old := ps.Config()
updated := old.Clone()
f(updated)
if _, _, err := ps.configStore.Set(updated); err != nil {
ps.logger.Error("Failed to update config", mlog.Err(err))
}
}
// SaveConfig replaces the active configuration, optionally notifying cluster peers.
// It returns both the previous and current configs.
func (ps *PlatformService) SaveConfig(newCfg *model.Config, sendConfigChangeClusterMessage bool) (*model.Config, *model.Config, *model.AppError) {
oldCfg, newCfg, err := ps.configStore.Set(newCfg)
if errors.Is(err, config.ErrReadOnlyConfiguration) {
return nil, nil, model.NewAppError("saveConfig", "ent.cluster.save_config.error", nil, err.Error(), http.StatusForbidden)
} else if err != nil {
return nil, nil, model.NewAppError("saveConfig", "app.save_config.app_error", nil, err.Error(), http.StatusInternalServerError)
}
if ps.serviceConfig.StartMetrics && *ps.Config().MetricsSettings.Enable {
ps.RestartMetrics()
} else {
ps.ShutdownMetrics()
}
if ps.cluster != nil {
err := ps.cluster.ConfigChanged(ps.configStore.RemoveEnvironmentOverrides(oldCfg),
ps.configStore.RemoveEnvironmentOverrides(newCfg), sendConfigChangeClusterMessage)
if err != nil {
return nil, nil, err
}
}
return oldCfg, newCfg, nil
}
func (ps *PlatformService) ReloadConfig() error {
if err := ps.configStore.Load(); err != nil {
return err
}
return nil
}
func (ps *PlatformService) GetEnvironmentOverridesWithFilter(filter func(reflect.StructField) bool) map[string]interface{} {
return ps.configStore.GetEnvironmentOverridesWithFilter(filter)
}
func (ps *PlatformService) GetEnvironmentOverrides() map[string]interface{} {
return ps.configStore.GetEnvironmentOverrides()
}
func (ps *PlatformService) DescribeConfig() string {
return ps.configStore.String()
}
func (ps *PlatformService) CleanUpConfig() error {
return ps.configStore.CleanUp()
}
// ConfigureLogger applies the specified configuration to a logger.
func (ps *PlatformService) ConfigureLogger(name string, logger *mlog.Logger, logSettings *model.LogSettings, getPath func(string) string) error {
// Advanced logging is E20 only, however logging must be initialized before the license
// file is loaded. If no valid E20 license exists then advanced logging will be
// shutdown once license is loaded/checked.
var err error
dsn := *logSettings.AdvancedLoggingConfig
var logConfigSrc config.LogConfigSrc
if dsn != "" {
logConfigSrc, err = config.NewLogConfigSrc(dsn, ps.configStore)
if err != nil {
return fmt.Errorf("invalid config source for %s, %w", name, err)
}
ps.logger.Info("Loaded configuration for "+name, mlog.String("source", dsn))
}
cfg, err := config.MloggerConfigFromLoggerConfig(logSettings, logConfigSrc, getPath)
if err != nil {
return fmt.Errorf("invalid config source for %s, %w", name, err)
}
if err := logger.ConfigureTargets(cfg, nil); err != nil {
return fmt.Errorf("invalid config for %s, %w", name, err)
}
return nil
}
func (ps *PlatformService) GetConfigStore() *config.Store {
return ps.configStore
}
func (ps *PlatformService) GetConfigFile(name string) ([]byte, error) {
return ps.configStore.GetFile(name)
}
func (ps *PlatformService) SetConfigFile(name string, data []byte) error {
return ps.configStore.SetFile(name, data)
}
func (ps *PlatformService) RemoveConfigFile(name string) error {
return ps.configStore.RemoveFile(name)
}
func (ps *PlatformService) HasConfigFile(name string) (bool, error) {
return ps.configStore.HasFile(name)
}
func (ps *PlatformService) SetConfigReadOnlyFF(readOnly bool) {
ps.configStore.SetReadOnlyFF(readOnly)
}

47
app/platform/config_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,47 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package platform
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/mattermost/mattermost-server/v6/model"
)
func TestConfigListener(t *testing.T) {
th := Setup(t)
defer th.TearDown()
originalSiteName := th.Service.Config().TeamSettings.SiteName
listenerCalled := false
listener := func(oldConfig *model.Config, newConfig *model.Config) {
assert.False(t, listenerCalled, "listener called twice")
assert.Equal(t, *originalSiteName, *oldConfig.TeamSettings.SiteName, "old config contains incorrect site name")
assert.Equal(t, "test123", *newConfig.TeamSettings.SiteName, "new config contains incorrect site name")
listenerCalled = true
}
listenerId := th.Service.AddConfigListener(listener)
defer th.Service.RemoveConfigListener(listenerId)
listener2Called := false
listener2 := func(oldConfig *model.Config, newConfig *model.Config) {
assert.False(t, listener2Called, "listener2 called twice")
listener2Called = true
}
listener2Id := th.Service.AddConfigListener(listener2)
defer th.Service.RemoveConfigListener(listener2Id)
th.Service.UpdateConfig(func(cfg *model.Config) {
*cfg.TeamSettings.SiteName = "test123"
})
assert.True(t, listenerCalled, "listener should've been called")
assert.True(t, listener2Called, "listener 2 should've been called")
}

118
app/platform/feature_flags.go Обычный файл
Просмотреть файл

@@ -0,0 +1,118 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package platform
import (
"encoding/json"
"os"
"time"
"github.com/mattermost/mattermost-server/v6/app/featureflag"
"github.com/mattermost/mattermost-server/v6/shared/mlog"
)
// SetupFeatureFlags called on startup and when the cluster leader changes.
// Starts or stops the synchronization of feature flags from upstream management.
func (ps *PlatformService) SetupFeatureFlags() {
ps.featureFlagSynchronizerMutex.Lock()
defer ps.featureFlagSynchronizerMutex.Unlock()
splitKey := *ps.Config().ServiceSettings.SplitKey
splitConfigured := splitKey != ""
syncFeatureFlags := splitConfigured && ps.IsLeader()
ps.configStore.SetReadOnlyFF(!splitConfigured)
if syncFeatureFlags {
if err := ps.startFeatureFlagUpdateJob(); err != nil {
ps.logger.Warn("Unable to setup synchronization with feature flag management. Will fallback to cache.", mlog.Err(err))
}
} else {
ps.StopFeatureFlagUpdateJob()
}
if err := ps.configStore.Load(); err != nil {
ps.logger.Warn("Unable to load config store after feature flag setup.", mlog.Err(err))
}
}
func (ps *PlatformService) updateFeatureFlagValuesFromManagement() {
newCfg := ps.configStore.GetNoEnv().Clone()
oldFlags := *newCfg.FeatureFlags
newFlags := ps.featureFlagSynchronizer.UpdateFeatureFlagValues(oldFlags)
oldFlagsBytes, _ := json.Marshal(oldFlags)
newFlagsBytes, _ := json.Marshal(newFlags)
ps.logger.Debug("Checking feature flags from management service", mlog.String("old_flags", string(oldFlagsBytes)), mlog.String("new_flags", string(newFlagsBytes)))
if oldFlags != newFlags {
ps.logger.Debug("Feature flag change detected, updating config")
*newCfg.FeatureFlags = newFlags
ps.SaveConfig(newCfg, true)
}
}
func (ps *PlatformService) startFeatureFlagUpdateJob() error {
// Can be run multiple times
if ps.featureFlagSynchronizer != nil {
return nil
}
var log *mlog.Logger
if *ps.Config().ServiceSettings.DebugSplit {
log = ps.logger
}
attributes := map[string]any{}
// if we are part of a cloud installation, add its installation and group id
if installationId := os.Getenv("MM_CLOUD_INSTALLATION_ID"); installationId != "" {
attributes["installation_id"] = installationId
}
if groupId := os.Getenv("MM_CLOUD_GROUP_ID"); groupId != "" {
attributes["group_id"] = groupId
}
synchronizer, err := featureflag.NewSynchronizer(featureflag.SyncParams{
ServerID: ps.telemetryId,
SplitKey: *ps.Config().ServiceSettings.SplitKey,
Log: log,
Attributes: attributes,
})
if err != nil {
return err
}
ps.featureFlagStop = make(chan struct{})
ps.featureFlagStopped = make(chan struct{})
ps.featureFlagSynchronizer = synchronizer
syncInterval := *ps.Config().ServiceSettings.FeatureFlagSyncIntervalSeconds
go func() {
ticker := time.NewTicker(time.Duration(syncInterval) * time.Second)
defer ticker.Stop()
defer close(ps.featureFlagStopped)
if err := synchronizer.EnsureReady(); err != nil {
ps.logger.Warn("Problem connecting to feature flag management. Will fallback to cloud cache.", mlog.Err(err))
return
}
ps.updateFeatureFlagValuesFromManagement()
for {
select {
case <-ps.featureFlagStop:
return
case <-ticker.C:
ps.updateFeatureFlagValuesFromManagement()
}
}
}()
return nil
}
func (ps *PlatformService) StopFeatureFlagUpdateJob() {
if ps.featureFlagSynchronizer != nil {
close(ps.featureFlagStop)
<-ps.featureFlagStopped
ps.featureFlagSynchronizer.Close()
ps.featureFlagSynchronizer = nil
}
}

87
app/platform/helper_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,87 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package platform
import (
"io/ioutil"
"path/filepath"
"testing"
"github.com/mattermost/mattermost-server/v6/config"
"github.com/mattermost/mattermost-server/v6/model"
"github.com/mattermost/mattermost-server/v6/store"
)
type TestHelper struct {
Service *PlatformService
}
func Setup(tb testing.TB) *TestHelper {
if testing.Short() {
tb.SkipNow()
}
dbStore := mainHelper.GetStore()
dbStore.DropAllTables()
dbStore.MarkSystemRanUnitTests()
mainHelper.PreloadMigrations()
return setupTestHelper(dbStore, false, true, tb)
}
func setupTestHelper(dbStore store.Store, enterprise bool, includeCacheLayer bool, tb testing.TB) *TestHelper {
tempWorkspace, err := ioutil.TempDir("", "apptest")
if err != nil {
panic(err)
}
configStore := config.NewTestMemoryStore()
memoryConfig := configStore.Get()
*memoryConfig.PluginSettings.Directory = filepath.Join(tempWorkspace, "plugins")
*memoryConfig.PluginSettings.ClientDirectory = filepath.Join(tempWorkspace, "webapp")
*memoryConfig.PluginSettings.AutomaticPrepackagedPlugins = false
*memoryConfig.LogSettings.EnableSentry = false // disable error reporting during tests
*memoryConfig.AnnouncementSettings.AdminNoticesEnabled = false
*memoryConfig.AnnouncementSettings.UserNoticesEnabled = false
configStore.Set(memoryConfig)
ps, err := New(ServiceConfig{
ConfigStore: configStore,
})
if err != nil {
panic(err)
}
th := &TestHelper{
Service: ps,
}
// Share same configuration with app.TestHelper
th.Service.UpdateConfig(func(cfg *model.Config) {
*cfg.TeamSettings.MaxUsersPerTeam = 50
*cfg.RateLimitSettings.Enable = false
*cfg.TeamSettings.EnableOpenServer = true
})
// Disable strict password requirements for test
th.Service.UpdateConfig(func(cfg *model.Config) {
*cfg.PasswordSettings.MinimumLength = 5
*cfg.PasswordSettings.Lowercase = false
*cfg.PasswordSettings.Uppercase = false
*cfg.PasswordSettings.Symbol = false
*cfg.PasswordSettings.Number = false
})
if enterprise {
th.Service.SetLicense(model.NewTestLicense())
} else {
th.Service.SetLicense(nil)
}
return th
}
func (th *TestHelper) TearDown() {
// Add cleaning code here
}

32
app/platform/main_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,32 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package platform
import (
"flag"
"testing"
"github.com/mattermost/mattermost-server/v6/testlib"
)
var mainHelper *testlib.MainHelper
var replicaFlag bool
func TestMain(m *testing.M) {
if f := flag.Lookup("mysql-replica"); f == nil {
flag.BoolVar(&replicaFlag, "mysql-replica", false, "")
flag.Parse()
}
var options = testlib.HelperOptions{
EnableStore: true,
EnableResources: true,
WithReadReplica: replicaFlag,
}
mainHelper = testlib.NewMainHelperWithOptions(&options)
defer mainHelper.Close()
mainHelper.Main(m)
}

19
app/platform/server_license.go Обычный файл
Просмотреть файл

@@ -0,0 +1,19 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package platform
import (
"github.com/mattermost/mattermost-server/v6/model"
)
// License returns the license stored in the server struct.
// This should be removed with MM-45839
func (ps *PlatformService) License() *model.License {
license, _ := ps.licenseValue.Load().(*model.License)
return license
}
func (ps *PlatformService) SetLicense(license *model.License) {
ps.licenseValue.Store(license)
}

Просмотреть файл

@@ -4,6 +4,11 @@
package platform package platform
import ( import (
"fmt"
"sync"
"sync/atomic"
"github.com/mattermost/mattermost-server/v6/app/featureflag"
"github.com/mattermost/mattermost-server/v6/config" "github.com/mattermost/mattermost-server/v6/config"
"github.com/mattermost/mattermost-server/v6/einterfaces" "github.com/mattermost/mattermost-server/v6/einterfaces"
"github.com/mattermost/mattermost-server/v6/shared/mlog" "github.com/mattermost/mattermost-server/v6/shared/mlog"
@@ -19,6 +24,14 @@ type PlatformService struct {
metrics *platformMetrics metrics *platformMetrics
featureFlagSynchronizerMutex sync.Mutex
featureFlagSynchronizer *featureflag.Synchronizer
featureFlagStop chan struct{}
featureFlagStopped chan struct{}
licenseValue atomic.Value
telemetryId string
cluster einterfaces.ClusterInterface cluster einterfaces.ClusterInterface
} }
@@ -49,3 +62,18 @@ func (ps *PlatformService) ShutdownMetrics() error {
return nil return nil
} }
func (ps *PlatformService) ShutdownConfig() error {
if ps.configStore != nil {
err := ps.configStore.Close()
if err != nil {
return fmt.Errorf("failed to close config store: %w", err)
}
}
return nil
}
func (ps *PlatformService) SetTelemetryId(id string) {
ps.telemetryId = id
}

Просмотреть файл

@@ -25,7 +25,7 @@ func (a *App) GetPublicKey(name string) ([]byte, *model.AppError) {
} }
func (s *Server) getPublicKey(name string) ([]byte, *model.AppError) { func (s *Server) getPublicKey(name string) ([]byte, *model.AppError) {
data, err := s.configStore.GetFile(name) data, err := s.platform.GetConfigFile(name)
if err != nil { if err != nil {
return nil, model.NewAppError("GetPublicKey", "app.plugin.get_public_key.get_file.app_error", nil, err.Error(), http.StatusInternalServerError) return nil, model.NewAppError("GetPublicKey", "app.plugin.get_public_key.get_file.app_error", nil, err.Error(), http.StatusInternalServerError)
} }
@@ -41,7 +41,7 @@ func (a *App) AddPublicKey(name string, key io.Reader) *model.AppError {
if err != nil { if err != nil {
return model.NewAppError("AddPublicKey", "app.plugin.write_file.read.app_error", nil, err.Error(), http.StatusInternalServerError) return model.NewAppError("AddPublicKey", "app.plugin.write_file.read.app_error", nil, err.Error(), http.StatusInternalServerError)
} }
err = a.Srv().configStore.SetFile(name, data) err = a.Srv().platform.SetConfigFile(name, data)
if err != nil { if err != nil {
return model.NewAppError("AddPublicKey", "app.plugin.write_file.saving.app_error", nil, err.Error(), http.StatusInternalServerError) return model.NewAppError("AddPublicKey", "app.plugin.write_file.saving.app_error", nil, err.Error(), http.StatusInternalServerError)
} }
@@ -61,7 +61,7 @@ func (a *App) DeletePublicKey(name string) *model.AppError {
return model.NewAppError("AddPublicKey", "app.plugin.modify_saml.app_error", nil, "", http.StatusInternalServerError) return model.NewAppError("AddPublicKey", "app.plugin.modify_saml.app_error", nil, "", http.StatusInternalServerError)
} }
filename := filepath.Base(name) filename := filepath.Base(name)
if err := a.Srv().configStore.RemoveFile(filename); err != nil { if err := a.Srv().platform.RemoveConfigFile(filename); err != nil {
return model.NewAppError("DeletePublicKey", "app.plugin.delete_public_key.delete.app_error", nil, err.Error(), http.StatusInternalServerError) return model.NewAppError("DeletePublicKey", "app.plugin.delete_public_key.delete.app_error", nil, err.Error(), http.StatusInternalServerError)
} }

Просмотреть файл

@@ -41,7 +41,7 @@ var linkCache = cache.NewLRU(cache.LRUOptions{
func (s *Server) initPostMetadata() { func (s *Server) initPostMetadata() {
// Dump any cached links if the proxy settings have changed so image URLs can be updated // Dump any cached links if the proxy settings have changed so image URLs can be updated
s.AddConfigListener(func(before, after *model.Config) { s.platform.AddConfigListener(func(before, after *model.Config) {
if (before.ImageProxySettings.Enable != after.ImageProxySettings.Enable) || if (before.ImageProxySettings.Enable != after.ImageProxySettings.Enable) ||
(before.ImageProxySettings.ImageProxyType != after.ImageProxySettings.ImageProxyType) || (before.ImageProxySettings.ImageProxyType != after.ImageProxySettings.ImageProxyType) ||
(before.ImageProxySettings.RemoteImageProxyURL != after.ImageProxySettings.RemoteImageProxyURL) || (before.ImageProxySettings.RemoteImageProxyURL != after.ImageProxySettings.RemoteImageProxyURL) ||

Просмотреть файл

@@ -796,7 +796,7 @@ func TestPreparePostForClientWithImageProxy(t *testing.T) {
*cfg.ImageProxySettings.RemoteImageProxyOptions = "foo" *cfg.ImageProxySettings.RemoteImageProxyOptions = "foo"
}) })
th.App.ch.imageProxy = imageproxy.MakeImageProxy(th.Server, th.Server.HTTPService(), th.Server.Log) th.App.ch.imageProxy = imageproxy.MakeImageProxy(th.Server.platform, th.Server.HTTPService(), th.Server.Log)
return th return th
} }

Просмотреть файл

@@ -472,7 +472,7 @@ func TestImageProxy(t *testing.T) {
*cfg.ServiceSettings.SiteURL = "http://mymattermost.com" *cfg.ServiceSettings.SiteURL = "http://mymattermost.com"
}) })
th.App.ch.imageProxy = imageproxy.MakeImageProxy(th.Server, th.Server.HTTPService(), th.Server.Log) th.App.ch.imageProxy = imageproxy.MakeImageProxy(th.Server.platform, th.Server.HTTPService(), th.Server.Log)
for name, tc := range map[string]struct { for name, tc := range map[string]struct {
ProxyType string ProxyType string
@@ -686,7 +686,7 @@ func TestCreatePost(t *testing.T) {
*cfg.ImageProxySettings.RemoteImageProxyOptions = "foo" *cfg.ImageProxySettings.RemoteImageProxyOptions = "foo"
}) })
th.App.ch.imageProxy = imageproxy.MakeImageProxy(th.Server, th.Server.HTTPService(), th.Server.Log) th.App.ch.imageProxy = imageproxy.MakeImageProxy(th.Server.platform, th.Server.HTTPService(), th.Server.Log)
imageURL := "http://mydomain.com/myimage" imageURL := "http://mydomain.com/myimage"
proxiedImageURL := "http://mymattermost.com/api/v4/image?url=http%3A%2F%2Fmydomain.com%2Fmyimage" proxiedImageURL := "http://mymattermost.com/api/v4/image?url=http%3A%2F%2Fmydomain.com%2Fmyimage"
@@ -956,7 +956,7 @@ func TestPatchPost(t *testing.T) {
*cfg.ImageProxySettings.RemoteImageProxyOptions = "foo" *cfg.ImageProxySettings.RemoteImageProxyOptions = "foo"
}) })
th.App.ch.imageProxy = imageproxy.MakeImageProxy(th.Server, th.Server.HTTPService(), th.Server.Log) th.App.ch.imageProxy = imageproxy.MakeImageProxy(th.Server.platform, th.Server.HTTPService(), th.Server.Log)
imageURL := "http://mydomain.com/myimage" imageURL := "http://mydomain.com/myimage"
proxiedImageURL := "http://mymattermost.com/api/v4/image?url=http%3A%2F%2Fmydomain.com%2Fmyimage" proxiedImageURL := "http://mymattermost.com/api/v4/image?url=http%3A%2F%2Fmydomain.com%2Fmyimage"
@@ -1252,7 +1252,7 @@ func TestUpdatePost(t *testing.T) {
*cfg.ImageProxySettings.RemoteImageProxyOptions = "foo" *cfg.ImageProxySettings.RemoteImageProxyOptions = "foo"
}) })
th.App.ch.imageProxy = imageproxy.MakeImageProxy(th.Server, th.Server.HTTPService(), th.Server.Log) th.App.ch.imageProxy = imageproxy.MakeImageProxy(th.Server.platform, th.Server.HTTPService(), th.Server.Log)
imageURL := "http://mydomain.com/myimage" imageURL := "http://mydomain.com/myimage"
proxiedImageURL := "http://mymattermost.com/api/v4/image?url=http%3A%2F%2Fmydomain.com%2Fmyimage" proxiedImageURL := "http://mymattermost.com/api/v4/image?url=http%3A%2F%2Fmydomain.com%2Fmyimage"

Просмотреть файл

@@ -90,8 +90,8 @@ func TestGetTopReactionsForTeamSince(t *testing.T) {
th := Setup(t).InitBasic() th := Setup(t).InitBasic()
defer th.TearDown() defer th.TearDown()
th.Server.configStore.SetReadOnlyFF(false) th.Server.platform.SetConfigReadOnlyFF(false)
defer th.Server.configStore.SetReadOnlyFF(true) defer th.Server.platform.SetConfigReadOnlyFF(true)
userId := th.BasicUser.Id userId := th.BasicUser.Id
user2Id := th.BasicUser2.Id user2Id := th.BasicUser2.Id
@@ -261,8 +261,8 @@ func TestGetTopReactionsForUserSince(t *testing.T) {
th := Setup(t).InitBasic() th := Setup(t).InitBasic()
defer th.TearDown() defer th.TearDown()
th.Server.configStore.SetReadOnlyFF(false) th.Server.platform.SetConfigReadOnlyFF(false)
defer th.Server.configStore.SetReadOnlyFF(true) defer th.Server.platform.SetConfigReadOnlyFF(true)
userId := th.BasicUser.Id userId := th.BasicUser.Id

Просмотреть файл

@@ -47,7 +47,7 @@ func (a *App) writeSamlFile(filename string, fileData *multipart.FileHeader) *mo
return model.NewAppError("AddSamlCertificate", "api.admin.add_certificate.saving.app_error", nil, err.Error(), http.StatusInternalServerError) return model.NewAppError("AddSamlCertificate", "api.admin.add_certificate.saving.app_error", nil, err.Error(), http.StatusInternalServerError)
} }
err = a.Srv().configStore.SetFile(filename, data) err = a.Srv().platform.SetConfigFile(filename, data)
if err != nil { if err != nil {
return model.NewAppError("AddSamlCertificate", "api.admin.add_certificate.saving.app_error", nil, err.Error(), http.StatusInternalServerError) return model.NewAppError("AddSamlCertificate", "api.admin.add_certificate.saving.app_error", nil, err.Error(), http.StatusInternalServerError)
} }
@@ -107,7 +107,7 @@ func (a *App) AddSamlIdpCertificate(fileData *multipart.FileHeader) *model.AppEr
} }
func (a *App) removeSamlFile(filename string) *model.AppError { func (a *App) removeSamlFile(filename string) *model.AppError {
if err := a.Srv().configStore.RemoveFile(filename); err != nil { if err := a.Srv().platform.RemoveConfigFile(filename); err != nil {
return model.NewAppError("RemoveSamlFile", "api.admin.remove_certificate.delete.app_error", map[string]any{"Filename": filename}, err.Error(), http.StatusInternalServerError) return model.NewAppError("RemoveSamlFile", "api.admin.remove_certificate.delete.app_error", map[string]any{"Filename": filename}, err.Error(), http.StatusInternalServerError)
} }
@@ -171,9 +171,9 @@ func (a *App) RemoveSamlIdpCertificate() *model.AppError {
func (a *App) GetSamlCertificateStatus() *model.SamlCertificateStatus { func (a *App) GetSamlCertificateStatus() *model.SamlCertificateStatus {
status := &model.SamlCertificateStatus{} status := &model.SamlCertificateStatus{}
status.IdpCertificateFile, _ = a.Srv().configStore.HasFile(*a.Config().SamlSettings.IdpCertificateFile) status.IdpCertificateFile, _ = a.Srv().platform.HasConfigFile(*a.Config().SamlSettings.IdpCertificateFile)
status.PrivateKeyFile, _ = a.Srv().configStore.HasFile(*a.Config().SamlSettings.PrivateKeyFile) status.PrivateKeyFile, _ = a.Srv().platform.HasConfigFile(*a.Config().SamlSettings.PrivateKeyFile)
status.PublicCertificateFile, _ = a.Srv().configStore.HasFile(*a.Config().SamlSettings.PublicCertificateFile) status.PublicCertificateFile, _ = a.Srv().platform.HasConfigFile(*a.Config().SamlSettings.PublicCertificateFile)
return status return status
} }
@@ -267,7 +267,7 @@ func (a *App) SetSamlIdpCertificateFromMetadata(data []byte) *model.AppError {
Bytes: block.Bytes, Bytes: block.Bytes,
}) })
if err := a.Srv().configStore.SetFile(SamlIdpCertificateName, data); err != nil { if err := a.Srv().platform.SetConfigFile(SamlIdpCertificateName, data); err != nil {
return model.NewAppError("SetSamlIdpCertificateFromMetadata", "api.admin.saml.failure_save_idp_certificate_file.app_error", nil, err.Error(), http.StatusInternalServerError) return model.NewAppError("SetSamlIdpCertificateFromMetadata", "api.admin.saml.failure_save_idp_certificate_file.app_error", nil, err.Error(), http.StatusInternalServerError)
} }

Просмотреть файл

@@ -33,7 +33,7 @@ const (
) )
func (s *Server) DoSecurityUpdateCheck() { func (s *Server) DoSecurityUpdateCheck() {
if !*s.Config().ServiceSettings.EnableSecurityFixAlert { if !*s.platform.Config().ServiceSettings.EnableSecurityFixAlert {
return return
} }
@@ -53,7 +53,7 @@ func (s *Server) DoSecurityUpdateCheck() {
v.Set(PropSecurityID, s.TelemetryId()) v.Set(PropSecurityID, s.TelemetryId())
v.Set(PropSecurityBuild, model.CurrentVersion+"."+model.BuildNumber) v.Set(PropSecurityBuild, model.CurrentVersion+"."+model.BuildNumber)
v.Set(PropSecurityEnterpriseReady, model.BuildEnterpriseReady) v.Set(PropSecurityEnterpriseReady, model.BuildEnterpriseReady)
v.Set(PropSecurityDatabase, *s.Config().SqlSettings.DriverName) v.Set(PropSecurityDatabase, *s.platform.Config().SqlSettings.DriverName)
v.Set(PropSecurityOS, runtime.GOOS) v.Set(PropSecurityOS, runtime.GOOS)
if props[model.SystemRanUnitTests] != "" { if props[model.SystemRanUnitTests] != "" {

Просмотреть файл

@@ -31,7 +31,6 @@ import (
"golang.org/x/crypto/acme/autocert" "golang.org/x/crypto/acme/autocert"
"github.com/mattermost/mattermost-server/v6/app/email" "github.com/mattermost/mattermost-server/v6/app/email"
"github.com/mattermost/mattermost-server/v6/app/featureflag"
"github.com/mattermost/mattermost-server/v6/app/platform" "github.com/mattermost/mattermost-server/v6/app/platform"
"github.com/mattermost/mattermost-server/v6/app/request" "github.com/mattermost/mattermost-server/v6/app/request"
"github.com/mattermost/mattermost-server/v6/app/teams" "github.com/mattermost/mattermost-server/v6/app/teams"
@@ -168,7 +167,6 @@ type Server struct {
searchConfigListenerId string searchConfigListenerId string
searchLicenseListenerId string searchLicenseListenerId string
loggerLicenseListenerId string loggerLicenseListenerId string
configStore *configWrapper
filestore filestore.FileBackend filestore filestore.FileBackend
platform *platform.PlatformService platform *platform.PlatformService
@@ -201,11 +199,6 @@ type Server struct {
tracer *tracing.Tracer tracer *tracing.Tracer
featureFlagSynchronizer *featureflag.Synchronizer
featureFlagStop chan struct{}
featureFlagStopped chan struct{}
featureFlagSynchronizerMutex sync.Mutex
products map[string]Product products map[string]Product
} }
@@ -237,7 +230,7 @@ func NewServer(options ...Option) (*Server, error) {
// and has dependency requirements with the previous step. // and has dependency requirements with the previous step.
// //
// Step 1: Config. // Step 1: Config.
if s.configStore == nil { if s.platform == nil {
innerStore, err := config.NewFileStore("config.json", true) innerStore, err := config.NewFileStore("config.json", true)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "failed to load config") return nil, errors.Wrap(err, "failed to load config")
@@ -247,7 +240,25 @@ func NewServer(options ...Option) (*Server, error) {
return nil, errors.Wrap(err, "failed to load config") return nil, errors.Wrap(err, "failed to load config")
} }
s.configStore = &configWrapper{srv: s, Store: configStore} platformCfg := platform.ServiceConfig{
ConfigStore: configStore,
Logger: s.Log,
StartMetrics: s.startMetrics,
Cluster: s.Cluster,
}
if metricsInterface != nil {
platformCfg.Metrics = metricsInterface(s, *configStore.Get().SqlSettings.DriverName, *configStore.Get().SqlSettings.DataSource)
}
ps, sErr := platform.New(platformCfg)
if sErr != nil {
return nil, errors.Wrap(sErr, "failed to initialize platform")
}
s.platform = ps
if s.licenseValue.Load() != nil {
ps.SetLicense(s.licenseValue.Load().(*model.License)) // in case license is set in server options
}
} }
// Step 2: Logging // Step 2: Logging
@@ -255,7 +266,7 @@ func NewServer(options ...Option) (*Server, error) {
mlog.Error("Could not initiate logging", mlog.Err(err)) mlog.Error("Could not initiate logging", mlog.Err(err))
} }
subpath, err := utils.GetSubpathFromConfig(s.Config()) subpath, err := utils.GetSubpathFromConfig(s.platform.Config())
if err != nil { if err != nil {
return nil, errors.Wrap(err, "failed to parse SiteURL subpath") return nil, errors.Wrap(err, "failed to parse SiteURL subpath")
} }
@@ -264,12 +275,12 @@ func NewServer(options ...Option) (*Server, error) {
// This is called after initLogging() to avoid a race condition. // This is called after initLogging() to avoid a race condition.
mlog.Info("Server is initializing...", mlog.String("go_version", runtime.Version())) mlog.Info("Server is initializing...", mlog.String("go_version", runtime.Version()))
s.httpService = httpservice.MakeHTTPService(s) s.httpService = httpservice.MakeHTTPService(s.platform)
// Step 3: Search Engine // Step 3: Search Engine
// Depends on Step 1 (config). // Depends on Step 1 (config).
searchEngine := searchengine.NewBroker(s.Config()) searchEngine := searchengine.NewBroker(s.platform.Config())
bleveEngine := bleveengine.NewBleveEngine(s.Config()) bleveEngine := bleveengine.NewBleveEngine(s.platform.Config())
if err := bleveEngine.Start(); err != nil { if err := bleveEngine.Start(); err != nil {
return nil, err return nil, err
} }
@@ -280,22 +291,6 @@ func NewServer(options ...Option) (*Server, error) {
// Depends on step 3 (s.SearchEngine must be non-nil) // Depends on step 3 (s.SearchEngine must be non-nil)
s.initEnterprise() s.initEnterprise()
platformCfg := platform.ServiceConfig{
ConfigStore: s.configStore.Store,
Logger: s.Log,
StartMetrics: s.startMetrics,
Cluster: s.Cluster,
}
if metricsInterface != nil {
platformCfg.Metrics = metricsInterface(s)
}
ps, sErr := platform.New(platformCfg)
if sErr != nil {
return nil, errors.Wrap(sErr, "failed to initialize platform")
}
s.platform = ps
// Step 5: Cache provider. // Step 5: Cache provider.
// At the moment we only have this implementation // At the moment we only have this implementation
// in the future the cache provider will be built based on the loaded config // in the future the cache provider will be built based on the loaded config
@@ -308,7 +303,7 @@ func NewServer(options ...Option) (*Server, error) {
// Depends on Step 1 (config), 4 (metrics, cluster) and 5 (cacheProvider). // Depends on Step 1 (config), 4 (metrics, cluster) and 5 (cacheProvider).
if s.newStore == nil { if s.newStore == nil {
s.newStore = func() (store.Store, error) { s.newStore = func() (store.Store, error) {
s.sqlStore = sqlstore.New(s.Config().SqlSettings, s.GetMetrics()) s.sqlStore = sqlstore.New(s.platform.Config().SqlSettings, s.GetMetrics())
lcl, err2 := localcachelayer.NewLocalCacheLayer( lcl, err2 := localcachelayer.NewLocalCacheLayer(
retrylayer.New(s.sqlStore), retrylayer.New(s.sqlStore),
@@ -323,10 +318,10 @@ func NewServer(options ...Option) (*Server, error) {
searchStore := searchlayer.NewSearchLayer( searchStore := searchlayer.NewSearchLayer(
lcl, lcl,
s.SearchEngine, s.SearchEngine,
s.Config(), s.platform.Config(),
) )
s.AddConfigListener(func(prevCfg, cfg *model.Config) { s.platform.AddConfigListener(func(prevCfg, cfg *model.Config) {
searchStore.UpdateConfig(cfg) searchStore.UpdateConfig(cfg)
}) })
@@ -352,7 +347,7 @@ func NewServer(options ...Option) (*Server, error) {
UserStore: s.Store.User(), UserStore: s.Store.User(),
SessionStore: s.Store.Session(), SessionStore: s.Store.Session(),
OAuthStore: s.Store.OAuth(), OAuthStore: s.Store.OAuth(),
ConfigFn: s.Config, ConfigFn: s.platform.Config,
Metrics: s.GetMetrics(), Metrics: s.GetMetrics(),
Cluster: s.Cluster, Cluster: s.Cluster,
LicenseFn: s.License, LicenseFn: s.License,
@@ -376,9 +371,9 @@ func NewServer(options ...Option) (*Server, error) {
} }
license := s.License() license := s.License()
insecure := s.Config().ServiceSettings.EnableInsecureOutgoingConnections insecure := s.platform.Config().ServiceSettings.EnableInsecureOutgoingConnections
// Step 7: Initialize filestore // Step 7: Initialize filestore
backend, err := filestore.NewFileBackend(s.Config().FileSettings.ToFileBackendSettings(license != nil && *license.Features.Compliance, insecure != nil && *insecure)) backend, err := filestore.NewFileBackend(s.platform.Config().FileSettings.ToFileBackendSettings(license != nil && *license.Features.Compliance, insecure != nil && *insecure))
if err != nil { if err != nil {
return nil, errors.Wrap(err, "failed to initialize filebackend") return nil, errors.Wrap(err, "failed to initialize filebackend")
} }
@@ -398,7 +393,7 @@ func NewServer(options ...Option) (*Server, error) {
GroupStore: s.Store.Group(), GroupStore: s.Store.Group(),
Users: s.userService, Users: s.userService,
WebHub: s, WebHub: s,
ConfigFn: s.Config, ConfigFn: s.platform.Config,
LicenseFn: s.License, LicenseFn: s.License,
}) })
if err != nil { if err != nil {
@@ -410,7 +405,7 @@ func NewServer(options ...Option) (*Server, error) {
serviceMap := map[ServiceKey]any{ serviceMap := map[ServiceKey]any{
ChannelKey: &channelsWrapper{srv: s}, ChannelKey: &channelsWrapper{srv: s},
ConfigKey: s.configStore, ConfigKey: s.platform,
LicenseKey: s.licenseWrapper, LicenseKey: s.licenseWrapper,
FilestoreKey: s.filestore, FilestoreKey: s.filestore,
FileInfoStoreKey: &fileInfoWrapper{srv: s}, FileInfoStoreKey: &fileInfoWrapper{srv: s},
@@ -441,7 +436,7 @@ func NewServer(options ...Option) (*Server, error) {
// below this. Otherwise, please add it to Channels struct in app/channels.go. // below this. Otherwise, please add it to Channels struct in app/channels.go.
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
if *s.Config().LogSettings.EnableDiagnostics && *s.Config().LogSettings.EnableSentry { if *s.platform.Config().LogSettings.EnableDiagnostics && *s.platform.Config().LogSettings.EnableSentry {
if strings.Contains(SentryDSN, "placeholder") { if strings.Contains(SentryDSN, "placeholder") {
mlog.Warn("Sentry reporting is enabled, but SENTRY_DSN is not set. Disabling reporting.") mlog.Warn("Sentry reporting is enabled, but SENTRY_DSN is not set. Disabling reporting.")
} else { } else {
@@ -468,7 +463,7 @@ func NewServer(options ...Option) (*Server, error) {
} }
} }
if *s.Config().ServiceSettings.EnableOpenTracing { if *s.platform.Config().ServiceSettings.EnableOpenTracing {
tracer, err2 := tracing.New() tracer, err2 := tracing.New()
if err2 != nil { if err2 != nil {
return nil, err2 return nil, err2
@@ -496,7 +491,7 @@ func NewServer(options ...Option) (*Server, error) {
s.createPushNotificationsHub(request.EmptyContext(s.GetLogger())) s.createPushNotificationsHub(request.EmptyContext(s.GetLogger()))
if err2 := i18n.InitTranslations(*s.Config().LocalizationSettings.DefaultServerLocale, *s.Config().LocalizationSettings.DefaultClientLocale); err2 != nil { if err2 := i18n.InitTranslations(*s.platform.Config().LocalizationSettings.DefaultServerLocale, *s.platform.Config().LocalizationSettings.DefaultClientLocale); err2 != nil {
return nil, errors.Wrapf(err2, "unable to load Mattermost translation files") return nil, errors.Wrapf(err2, "unable to load Mattermost translation files")
} }
@@ -515,7 +510,7 @@ func NewServer(options ...Option) (*Server, error) {
}) })
s.htmlTemplateWatcher = htmlTemplateWatcher s.htmlTemplateWatcher = htmlTemplateWatcher
s.configListenerId = s.AddConfigListener(func(_, _ *model.Config) { s.configListenerId = s.platform.AddConfigListener(func(_, _ *model.Config) {
ch := s.Channels() ch := s.Channels()
ch.regenerateClientConfig() ch.regenerateClientConfig()
@@ -544,9 +539,10 @@ func NewServer(options ...Option) (*Server, error) {
}) })
s.telemetryService = telemetry.New(New(ServerConnector(s.Channels())), s.Store, s.SearchEngine, s.Log) s.telemetryService = telemetry.New(New(ServerConnector(s.Channels())), s.Store, s.SearchEngine, s.Log)
s.platform.SetTelemetryId(s.TelemetryId()) // TODO: move this into platform once telemetry service moved to platform.
emailService, err := email.NewService(email.ServiceConfig{ emailService, err := email.NewService(email.ServiceConfig{
ConfigFn: s.Config, ConfigFn: s.platform.Config,
LicenseFn: s.License, LicenseFn: s.License,
GoFn: s.Go, GoFn: s.Go,
TemplatesContainer: s.TemplatesContainer(), TemplatesContainer: s.TemplatesContainer(),
@@ -558,7 +554,7 @@ func NewServer(options ...Option) (*Server, error) {
} }
s.EmailService = emailService s.EmailService = emailService
s.setupFeatureFlags() s.platform.SetupFeatureFlags()
s.initJobs() s.initJobs()
@@ -567,7 +563,7 @@ func NewServer(options ...Option) (*Server, error) {
if s.Jobs != nil { if s.Jobs != nil {
s.Jobs.HandleClusterLeaderChange(s.IsLeader()) s.Jobs.HandleClusterLeaderChange(s.IsLeader())
} }
s.setupFeatureFlags() s.platform.SetupFeatureFlags()
}) })
// If configured with a subpath, redirect 404s at the root back into the subpath. // If configured with a subpath, redirect 404s at the root back into the subpath.
@@ -578,12 +574,12 @@ func NewServer(options ...Option) (*Server, error) {
}) })
} }
if _, err = url.ParseRequestURI(*s.Config().ServiceSettings.SiteURL); err != nil { if _, err = url.ParseRequestURI(*s.platform.Config().ServiceSettings.SiteURL); err != nil {
mlog.Error("SiteURL must be set. Some features will operate incorrectly if the SiteURL is not set. See documentation for details: https://docs.mattermost.com/configure/configuration-settings.html#site-url") mlog.Error("SiteURL must be set. Some features will operate incorrectly if the SiteURL is not set. See documentation for details: https://docs.mattermost.com/configure/configuration-settings.html#site-url")
} }
// Start email batching because it's not like the other jobs // Start email batching because it's not like the other jobs
s.AddConfigListener(func(_, _ *model.Config) { s.platform.AddConfigListener(func(_, _ *model.Config) {
s.EmailService.InitEmailBatching() s.EmailService.InitEmailBatching()
}) })
@@ -604,7 +600,7 @@ func NewServer(options ...Option) (*Server, error) {
pwd, _ := os.Getwd() pwd, _ := os.Getwd()
mlog.Info("Printing current working", mlog.String("directory", pwd)) mlog.Info("Printing current working", mlog.String("directory", pwd))
mlog.Info("Loaded config", mlog.String("source", s.configStore.String())) mlog.Info("Loaded config", mlog.String("source", s.platform.DescribeConfig()))
allowAdvancedLogging := license != nil && *license.Features.AdvancedLogging allowAdvancedLogging := license != nil && *license.Features.AdvancedLogging
@@ -626,7 +622,7 @@ func NewServer(options ...Option) (*Server, error) {
// Enable developer settings if this is a "dev" build // Enable developer settings if this is a "dev" build
if model.BuildNumber == "dev" { if model.BuildNumber == "dev" {
s.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableDeveloper = true }) s.platform.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableDeveloper = true })
} }
if s.startMetrics { if s.startMetrics {
@@ -649,13 +645,13 @@ func NewServer(options ...Option) (*Server, error) {
} }
}) })
s.SearchEngine.UpdateConfig(s.Config()) s.SearchEngine.UpdateConfig(s.platform.Config())
searchConfigListenerId, searchLicenseListenerId := s.StartSearchEngine() searchConfigListenerId, searchLicenseListenerId := s.StartSearchEngine()
s.searchConfigListenerId = searchConfigListenerId s.searchConfigListenerId = searchConfigListenerId
s.searchLicenseListenerId = searchLicenseListenerId s.searchLicenseListenerId = searchLicenseListenerId
// if enabled - perform initial product notices fetch // if enabled - perform initial product notices fetch
if *s.Config().AnnouncementSettings.AdminNoticesEnabled || *s.Config().AnnouncementSettings.UserNoticesEnabled { if *s.platform.Config().AnnouncementSettings.AdminNoticesEnabled || *s.platform.Config().AnnouncementSettings.UserNoticesEnabled {
go func() { go func() {
appInstance := New(ServerConnector(s.Channels())) appInstance := New(ServerConnector(s.Channels()))
if err := appInstance.UpdateProductNotices(); err != nil { if err := appInstance.UpdateProductNotices(); err != nil {
@@ -668,7 +664,7 @@ func NewServer(options ...Option) (*Server, error) {
return s, nil return s, nil
} }
s.AddConfigListener(func(old, new *model.Config) { s.platform.AddConfigListener(func(old, new *model.Config) {
appInstance := New(ServerConnector(s.Channels())) appInstance := New(ServerConnector(s.Channels()))
if *old.GuestAccountsSettings.Enable && !*new.GuestAccountsSettings.Enable { if *old.GuestAccountsSettings.Enable && !*new.GuestAccountsSettings.Enable {
c := request.EmptyContext(s.GetLogger()) c := request.EmptyContext(s.GetLogger())
@@ -679,7 +675,7 @@ func NewServer(options ...Option) (*Server, error) {
}) })
// Disable active guest accounts on first run if guest accounts are disabled // Disable active guest accounts on first run if guest accounts are disabled
if !*s.Config().GuestAccountsSettings.Enable { if !*s.platform.Config().GuestAccountsSettings.Enable {
appInstance := New(ServerConnector(s.Channels())) appInstance := New(ServerConnector(s.Channels()))
c := request.EmptyContext(s.GetLogger()) c := request.EmptyContext(s.GetLogger())
if appErr := appInstance.DeactivateGuests(c); appErr != nil { if appErr := appInstance.DeactivateGuests(c); appErr != nil {
@@ -703,7 +699,7 @@ func NewServer(options ...Option) (*Server, error) {
s.initPostMetadata() s.initPostMetadata()
// Dump the image cache if the proxy settings have changed. (need switch URLs to the correct proxy) // Dump the image cache if the proxy settings have changed. (need switch URLs to the correct proxy)
s.AddConfigListener(func(oldCfg, newCfg *model.Config) { s.platform.AddConfigListener(func(oldCfg, newCfg *model.Config) {
if (oldCfg.ImageProxySettings.Enable != newCfg.ImageProxySettings.Enable) || if (oldCfg.ImageProxySettings.Enable != newCfg.ImageProxySettings.Enable) ||
(oldCfg.ImageProxySettings.ImageProxyType != newCfg.ImageProxySettings.ImageProxyType) || (oldCfg.ImageProxySettings.ImageProxyType != newCfg.ImageProxySettings.ImageProxyType) ||
(oldCfg.ImageProxySettings.RemoteImageProxyURL != newCfg.ImageProxySettings.RemoteImageProxyURL) || (oldCfg.ImageProxySettings.RemoteImageProxyURL != newCfg.ImageProxySettings.RemoteImageProxyURL) ||
@@ -755,18 +751,18 @@ func (s *Server) runJobs() {
complianceI.StartComplianceDailyJob() complianceI.StartComplianceDailyJob()
} }
if *s.Config().JobSettings.RunJobs && s.Jobs != nil { if *s.platform.Config().JobSettings.RunJobs && s.Jobs != nil {
if err := s.Jobs.StartWorkers(); err != nil { if err := s.Jobs.StartWorkers(); err != nil {
mlog.Error("Failed to start job server workers", mlog.Err(err)) mlog.Error("Failed to start job server workers", mlog.Err(err))
} }
} }
if *s.Config().JobSettings.RunScheduler && s.Jobs != nil { if *s.platform.Config().JobSettings.RunScheduler && s.Jobs != nil {
if err := s.Jobs.StartSchedulers(); err != nil { if err := s.Jobs.StartSchedulers(); err != nil {
mlog.Error("Failed to start job server schedulers", mlog.Err(err)) mlog.Error("Failed to start job server schedulers", mlog.Err(err))
} }
} }
if *s.Config().ServiceSettings.EnableAWSMetering { if *s.platform.Config().ServiceSettings.EnableAWSMetering {
runReportToAWSMeterJob(s) runReportToAWSMeterJob(s)
} }
} }
@@ -786,7 +782,7 @@ func (s *Server) Channels() *Channels {
// Return Database type (postgres or mysql) and current version of the schema // Return Database type (postgres or mysql) and current version of the schema
func (s *Server) DatabaseTypeAndSchemaVersion() (string, string) { func (s *Server) DatabaseTypeAndSchemaVersion() (string, string) {
schemaVersion, _ := s.Store.GetDBSchemaVersion() schemaVersion, _ := s.Store.GetDBSchemaVersion()
return *s.Config().SqlSettings.DriverName, strconv.Itoa(schemaVersion) return *s.platform.Config().SqlSettings.DriverName, strconv.Itoa(schemaVersion)
} }
// initLogging initializes and configures the logger(s). This may be called more than once. // initLogging initializes and configures the logger(s). This may be called more than once.
@@ -809,7 +805,7 @@ func (s *Server) initLogging() error {
s.NotificationsLog = l.With(mlog.String("logSource", "notifications")) s.NotificationsLog = l.With(mlog.String("logSource", "notifications"))
} }
if err := s.configureLogger("logging", s.Log, &s.Config().LogSettings, s.configStore.Store, config.GetLogFileLocation); err != nil { if err := s.platform.ConfigureLogger("logging", s.Log, &s.platform.Config().LogSettings, config.GetLogFileLocation); err != nil {
// if the config is locked then a unit test has already configured and locked the logger; not an error. // if the config is locked then a unit test has already configured and locked the logger; not an error.
if !errors.Is(err, mlog.ErrConfigurationLock) { if !errors.Is(err, mlog.ErrConfigurationLock) {
// revert to default logger if the config is invalid // revert to default logger if the config is invalid
@@ -824,8 +820,8 @@ func (s *Server) initLogging() error {
// Use the app logger as the global logger (eventually remove all instances of global logging). // Use the app logger as the global logger (eventually remove all instances of global logging).
mlog.InitGlobalLogger(s.Log) mlog.InitGlobalLogger(s.Log)
notificationLogSettings := config.GetLogSettingsFromNotificationsLogSettings(&s.Config().NotificationLogSettings) notificationLogSettings := config.GetLogSettingsFromNotificationsLogSettings(&s.platform.Config().NotificationLogSettings)
if err := s.configureLogger("notification logging", s.NotificationsLog, notificationLogSettings, s.configStore.Store, config.GetNotificationsLogFileLocation); err != nil { if err := s.platform.ConfigureLogger("notification logging", s.NotificationsLog, notificationLogSettings, config.GetNotificationsLogFileLocation); err != nil {
if !errors.Is(err, mlog.ErrConfigurationLock) { if !errors.Is(err, mlog.ErrConfigurationLock) {
mlog.Error("Error configuring notification logger", mlog.Err(err)) mlog.Error("Error configuring notification logger", mlog.Err(err))
return err return err
@@ -834,33 +830,6 @@ func (s *Server) initLogging() error {
return nil return nil
} }
// configureLogger applies the specified configuration to a logger.
func (s *Server) configureLogger(name string, logger *mlog.Logger, logSettings *model.LogSettings, configStore *config.Store, getPath func(string) string) error {
// Advanced logging is E20 only, however logging must be initialized before the license
// file is loaded. If no valid E20 license exists then advanced logging will be
// shutdown once license is loaded/checked.
var err error
dsn := *logSettings.AdvancedLoggingConfig
var logConfigSrc config.LogConfigSrc
if dsn != "" {
logConfigSrc, err = config.NewLogConfigSrc(dsn, configStore)
if err != nil {
return fmt.Errorf("invalid config source for %s, %w", name, err)
}
mlog.Info("Loaded configuration for "+name, mlog.String("source", dsn))
}
cfg, err := config.MloggerConfigFromLoggerConfig(logSettings, logConfigSrc, getPath)
if err != nil {
return fmt.Errorf("invalid config source for %s, %w", name, err)
}
if err := logger.ConfigureTargets(cfg, nil); err != nil {
return fmt.Errorf("invalid config for %s, %w", name, err)
}
return nil
}
// removeUnlicensedLogTargets removes any unlicensed log target types. // removeUnlicensedLogTargets removes any unlicensed log target types.
func (s *Server) removeUnlicensedLogTargets(license *model.License) { func (s *Server) removeUnlicensedLogTargets(license *model.License) {
if license != nil && *license.Features.AdvancedLogging { if license != nil && *license.Features.AdvancedLogging {
@@ -895,7 +864,7 @@ func (s *Server) startInterClusterServices(license *model.License) error {
} }
// Config check // Config check
if !*s.Config().ExperimentalSettings.EnableRemoteClusterService { if !*s.platform.Config().ExperimentalSettings.EnableRemoteClusterService {
mlog.Debug("Remote Cluster Service disabled via config") mlog.Debug("Remote Cluster Service disabled via config")
return nil return nil
} }
@@ -924,7 +893,7 @@ func (s *Server) startInterClusterServices(license *model.License) error {
} }
// Config check // Config check
if !*s.Config().ExperimentalSettings.EnableSharedChannels { if !*s.platform.Config().ExperimentalSettings.EnableSharedChannels {
mlog.Debug("Shared Channels Service disabled via config") mlog.Debug("Shared Channels Service disabled via config")
return nil return nil
} }
@@ -1029,14 +998,16 @@ func (s *Server) Shutdown() {
s.WaitForGoroutines() s.WaitForGoroutines()
s.RemoveConfigListener(s.configListenerId) s.platform.RemoveConfigListener(s.configListenerId)
s.stopSearchEngine() s.stopSearchEngine()
s.Audit.Shutdown() s.Audit.Shutdown()
s.stopFeatureFlagUpdateJob() s.platform.StopFeatureFlagUpdateJob()
s.configStore.Close() if err = s.platform.ShutdownConfig(); err != nil {
s.Log.Warn("Failed to shut down config store", mlog.Err(err))
}
if s.Cluster != nil { if s.Cluster != nil {
s.Cluster.StopInterNodeCommunication() s.Cluster.StopInterNodeCommunication()
@@ -1239,23 +1210,23 @@ func (s *Server) Start() error {
s.checkPushNotificationServerURL() s.checkPushNotificationServerURL()
s.ReloadConfig() s.platform.ReloadConfig()
mlog.Info("Starting Server...") mlog.Info("Starting Server...")
var handler http.Handler = s.RootRouter var handler http.Handler = s.RootRouter
if *s.Config().LogSettings.EnableDiagnostics && *s.Config().LogSettings.EnableSentry && !strings.Contains(SentryDSN, "placeholder") { if *s.platform.Config().LogSettings.EnableDiagnostics && *s.platform.Config().LogSettings.EnableSentry && !strings.Contains(SentryDSN, "placeholder") {
sentryHandler := sentryhttp.New(sentryhttp.Options{ sentryHandler := sentryhttp.New(sentryhttp.Options{
Repanic: true, Repanic: true,
}) })
handler = sentryHandler.Handle(handler) handler = sentryHandler.Handle(handler)
} }
if allowedOrigins := *s.Config().ServiceSettings.AllowCorsFrom; allowedOrigins != "" { if allowedOrigins := *s.platform.Config().ServiceSettings.AllowCorsFrom; allowedOrigins != "" {
exposedCorsHeaders := *s.Config().ServiceSettings.CorsExposedHeaders exposedCorsHeaders := *s.platform.Config().ServiceSettings.CorsExposedHeaders
allowCredentials := *s.Config().ServiceSettings.CorsAllowCredentials allowCredentials := *s.platform.Config().ServiceSettings.CorsAllowCredentials
debug := *s.Config().ServiceSettings.CorsDebug debug := *s.platform.Config().ServiceSettings.CorsDebug
corsWrapper := cors.New(cors.Options{ corsWrapper := cors.New(cors.Options{
AllowedOrigins: strings.Fields(allowedOrigins), AllowedOrigins: strings.Fields(allowedOrigins),
AllowedMethods: corsAllowedMethods, AllowedMethods: corsAllowedMethods,
@@ -1274,10 +1245,10 @@ func (s *Server) Start() error {
handler = corsWrapper.Handler(handler) handler = corsWrapper.Handler(handler)
} }
if *s.Config().RateLimitSettings.Enable { if *s.platform.Config().RateLimitSettings.Enable {
mlog.Info("RateLimiter is enabled") mlog.Info("RateLimiter is enabled")
rateLimiter, err2 := NewRateLimiter(&s.Config().RateLimitSettings, s.Config().ServiceSettings.TrustedProxyIPHeader) rateLimiter, err2 := NewRateLimiter(&s.platform.Config().RateLimitSettings, s.platform.Config().ServiceSettings.TrustedProxyIPHeader)
if err2 != nil { if err2 != nil {
return err2 return err2
} }
@@ -1292,15 +1263,15 @@ func (s *Server) Start() error {
s.Server = &http.Server{ s.Server = &http.Server{
Handler: handler, Handler: handler,
ReadTimeout: time.Duration(*s.Config().ServiceSettings.ReadTimeout) * time.Second, ReadTimeout: time.Duration(*s.platform.Config().ServiceSettings.ReadTimeout) * time.Second,
WriteTimeout: time.Duration(*s.Config().ServiceSettings.WriteTimeout) * time.Second, WriteTimeout: time.Duration(*s.platform.Config().ServiceSettings.WriteTimeout) * time.Second,
IdleTimeout: time.Duration(*s.Config().ServiceSettings.IdleTimeout) * time.Second, IdleTimeout: time.Duration(*s.platform.Config().ServiceSettings.IdleTimeout) * time.Second,
ErrorLog: errStdLog, ErrorLog: errStdLog,
} }
addr := *s.Config().ServiceSettings.ListenAddress addr := *s.platform.Config().ServiceSettings.ListenAddress
if addr == "" { if addr == "" {
if *s.Config().ServiceSettings.ConnectionSecurity == model.ConnSecurityTLS { if *s.platform.Config().ServiceSettings.ConnectionSecurity == model.ConnSecurityTLS {
addr = ":https" addr = ":https"
} else { } else {
addr = ":http" addr = ":http"
@@ -1317,11 +1288,11 @@ func (s *Server) Start() error {
mlog.Info(logListeningPort, mlog.String("address", listener.Addr().String())) mlog.Info(logListeningPort, mlog.String("address", listener.Addr().String()))
m := &autocert.Manager{ m := &autocert.Manager{
Cache: autocert.DirCache(*s.Config().ServiceSettings.LetsEncryptCertificateCacheFile), Cache: autocert.DirCache(*s.platform.Config().ServiceSettings.LetsEncryptCertificateCacheFile),
Prompt: autocert.AcceptTOS, Prompt: autocert.AcceptTOS,
} }
if *s.Config().ServiceSettings.Forward80To443 { if *s.platform.Config().ServiceSettings.Forward80To443 {
if host, port, err := net.SplitHostPort(addr); err != nil { if host, port, err := net.SplitHostPort(addr); err != nil {
mlog.Error("Unable to setup forwarding", mlog.Err(err)) mlog.Error("Unable to setup forwarding", mlog.Err(err))
} else if port != "443" { } else if port != "443" {
@@ -1329,7 +1300,7 @@ func (s *Server) Start() error {
} else { } else {
httpListenAddress := net.JoinHostPort(host, "http") httpListenAddress := net.JoinHostPort(host, "http")
if *s.Config().ServiceSettings.UseLetsEncrypt { if *s.platform.Config().ServiceSettings.UseLetsEncrypt {
server := &http.Server{ server := &http.Server{
Addr: httpListenAddress, Addr: httpListenAddress,
Handler: m.HTTPHandler(nil), Handler: m.HTTPHandler(nil),
@@ -1353,21 +1324,21 @@ func (s *Server) Start() error {
}() }()
} }
} }
} else if *s.Config().ServiceSettings.UseLetsEncrypt { } else if *s.platform.Config().ServiceSettings.UseLetsEncrypt {
return errors.New(i18n.T("api.server.start_server.forward80to443.disabled_while_using_lets_encrypt")) return errors.New(i18n.T("api.server.start_server.forward80to443.disabled_while_using_lets_encrypt"))
} }
s.didFinishListen = make(chan struct{}) s.didFinishListen = make(chan struct{})
go func() { go func() {
var err error var err error
if *s.Config().ServiceSettings.ConnectionSecurity == model.ConnSecurityTLS { if *s.platform.Config().ServiceSettings.ConnectionSecurity == model.ConnSecurityTLS {
tlsConfig := &tls.Config{ tlsConfig := &tls.Config{
PreferServerCipherSuites: true, PreferServerCipherSuites: true,
CurvePreferences: []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256}, CurvePreferences: []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256},
} }
switch *s.Config().ServiceSettings.TLSMinVer { switch *s.platform.Config().ServiceSettings.TLSMinVer {
case "1.0": case "1.0":
tlsConfig.MinVersion = tls.VersionTLS10 tlsConfig.MinVersion = tls.VersionTLS10
case "1.1": case "1.1":
@@ -1385,11 +1356,11 @@ func (s *Server) Start() error {
tls.TLS_RSA_WITH_AES_256_GCM_SHA384, tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
} }
if len(s.Config().ServiceSettings.TLSOverwriteCiphers) == 0 { if len(s.platform.Config().ServiceSettings.TLSOverwriteCiphers) == 0 {
tlsConfig.CipherSuites = defaultCiphers tlsConfig.CipherSuites = defaultCiphers
} else { } else {
var cipherSuites []uint16 var cipherSuites []uint16
for _, cipher := range s.Config().ServiceSettings.TLSOverwriteCiphers { for _, cipher := range s.platform.Config().ServiceSettings.TLSOverwriteCiphers {
value, ok := model.ServerTLSSupportedCiphers[cipher] value, ok := model.ServerTLSSupportedCiphers[cipher]
if !ok { if !ok {
@@ -1411,12 +1382,12 @@ func (s *Server) Start() error {
certFile := "" certFile := ""
keyFile := "" keyFile := ""
if *s.Config().ServiceSettings.UseLetsEncrypt { if *s.platform.Config().ServiceSettings.UseLetsEncrypt {
tlsConfig.GetCertificate = m.GetCertificate tlsConfig.GetCertificate = m.GetCertificate
tlsConfig.NextProtos = append(tlsConfig.NextProtos, "h2") tlsConfig.NextProtos = append(tlsConfig.NextProtos, "h2")
} else { } else {
certFile = *s.Config().ServiceSettings.TLSCertFile certFile = *s.platform.Config().ServiceSettings.TLSCertFile
keyFile = *s.Config().ServiceSettings.TLSKeyFile keyFile = *s.platform.Config().ServiceSettings.TLSKeyFile
} }
s.Server.TLSConfig = tlsConfig s.Server.TLSConfig = tlsConfig
@@ -1433,7 +1404,7 @@ func (s *Server) Start() error {
close(s.didFinishListen) close(s.didFinishListen)
}() }()
if *s.Config().ServiceSettings.EnableLocalMode { if *s.platform.Config().ServiceSettings.EnableLocalMode {
if err := s.startLocalModeServer(); err != nil { if err := s.startLocalModeServer(); err != nil {
mlog.Critical(err.Error()) mlog.Critical(err.Error())
} }
@@ -1451,7 +1422,7 @@ func (s *Server) startLocalModeServer() error {
Handler: s.LocalRouter, Handler: s.LocalRouter,
} }
socket := *s.configStore.Get().ServiceSettings.LocalModeSocketLocation socket := *s.platform.Config().ServiceSettings.LocalModeSocketLocation
if err := os.RemoveAll(socket); err != nil { if err := os.RemoveAll(socket); err != nil {
return errors.Wrapf(err, i18n.T("api.server.start_server.starting.critical"), err) return errors.Wrapf(err, i18n.T("api.server.start_server.starting.critical"), err)
} }
@@ -1495,7 +1466,7 @@ func (a *App) OriginChecker() func(*http.Request) bool {
} }
func (s *Server) checkPushNotificationServerURL() { func (s *Server) checkPushNotificationServerURL() {
notificationServer := *s.Config().EmailSettings.PushNotificationServer notificationServer := *s.platform.Config().EmailSettings.PushNotificationServer
if strings.HasPrefix(notificationServer, "http://") { if strings.HasPrefix(notificationServer, "http://") {
mlog.Warn("Your push notification server is configured with HTTP. For improved security, update to HTTPS in your configuration.") mlog.Warn("Your push notification server is configured with HTTP. For improved security, update to HTTPS in your configuration.")
} }
@@ -1563,7 +1534,7 @@ func runReportToAWSMeterJob(s *Server) {
} }
func doReportUsageToAWSMeteringService(s *Server) { func doReportUsageToAWSMeteringService(s *Server) {
awsMeter := awsmeter.New(s.Store, s.Config()) awsMeter := awsmeter.New(s.Store, s.platform.Config())
if awsMeter == nil { if awsMeter == nil {
mlog.Error("Cannot obtain instance of AWS Metering Service.") mlog.Error("Cannot obtain instance of AWS Metering Service.")
return return
@@ -1604,12 +1575,12 @@ func doSessionCleanup(s *Server) {
} }
func doJobsCleanup(s *Server) { func doJobsCleanup(s *Server) {
if *s.Config().JobSettings.CleanupJobsThresholdDays < 0 { if *s.platform.Config().JobSettings.CleanupJobsThresholdDays < 0 {
return return
} }
mlog.Debug("Cleaning up jobs store.") mlog.Debug("Cleaning up jobs store.")
dur := time.Duration(*s.Config().JobSettings.CleanupJobsThresholdDays) * time.Hour * 24 dur := time.Duration(*s.platform.Config().JobSettings.CleanupJobsThresholdDays) * time.Hour * 24
expiry := model.GetMillisForTime(time.Now().Add(-dur)) expiry := model.GetMillisForTime(time.Now().Add(-dur))
err := s.Store.Job().Cleanup(expiry, jobsCleanupBatchSize) err := s.Store.Job().Cleanup(expiry, jobsCleanupBatchSize)
if err != nil { if err != nil {
@@ -1618,12 +1589,12 @@ func doJobsCleanup(s *Server) {
} }
func doConfigCleanup(s *Server) { func doConfigCleanup(s *Server) {
if *s.Config().JobSettings.CleanupConfigThresholdDays < 0 || !config.IsDatabaseDSN(s.ConfigStore().Store.String()) { if *s.platform.Config().JobSettings.CleanupConfigThresholdDays < 0 || !config.IsDatabaseDSN(s.platform.DescribeConfig()) {
return return
} }
mlog.Info("Cleaning up configuration store.") mlog.Info("Cleaning up configuration store.")
if err := s.ConfigStore().Store.CleanUp(); err != nil { if err := s.platform.CleanUpConfig(); err != nil {
mlog.Warn("Error while cleaning up configurations", mlog.Err(err)) mlog.Warn("Error while cleaning up configurations", mlog.Err(err))
} }
} }
@@ -1654,7 +1625,7 @@ func (s *Server) sendLicenseUpForRenewalEmail(users map[string]*model.User, lice
if name == "" { if name == "" {
name = user.Username name = user.Username
} }
if err := s.EmailService.SendLicenseUpForRenewalEmail(user.Email, name, user.Locale, *s.Config().ServiceSettings.SiteURL, renewalLink, daysToExpiration); err != nil { if err := s.EmailService.SendLicenseUpForRenewalEmail(user.Email, name, user.Locale, *s.platform.Config().ServiceSettings.SiteURL, renewalLink, daysToExpiration); err != nil {
mlog.Error("Error sending license up for renewal email to", mlog.String("user_email", user.Email), mlog.Err(err)) mlog.Error("Error sending license up for renewal email to", mlog.String("user_email", user.Email), mlog.Err(err))
countNotOks++ countNotOks++
} }
@@ -1735,7 +1706,7 @@ func (s *Server) doLicenseExpirationCheck() {
mlog.Debug("Sending license expired email.", mlog.String("user_email", user.Email)) mlog.Debug("Sending license expired email.", mlog.String("user_email", user.Email))
s.Go(func() { s.Go(func() {
if err := s.SendRemoveExpiredLicenseEmail(user.Email, renewalLink, user.Locale, *s.Config().ServiceSettings.SiteURL); err != nil { if err := s.SendRemoveExpiredLicenseEmail(user.Email, renewalLink, user.Locale, *s.platform.Config().ServiceSettings.SiteURL); err != nil {
mlog.Error("Error while sending the license expired email.", mlog.String("user_email", user.Email), mlog.Err(err)) mlog.Error("Error while sending the license expired email.", mlog.String("user_email", user.Email), mlog.Err(err))
} }
}) })
@@ -1765,7 +1736,7 @@ func (s *Server) StartSearchEngine() (string, string) {
}) })
} }
configListenerId := s.AddConfigListener(func(oldConfig *model.Config, newConfig *model.Config) { configListenerId := s.platform.AddConfigListener(func(oldConfig *model.Config, newConfig *model.Config) {
if s.SearchEngine == nil { if s.SearchEngine == nil {
return return
} }
@@ -1824,7 +1795,7 @@ func (s *Server) StartSearchEngine() (string, string) {
} }
func (s *Server) stopSearchEngine() { func (s *Server) stopSearchEngine() {
s.RemoveConfigListener(s.searchConfigListenerId) s.platform.RemoveConfigListener(s.searchConfigListenerId)
s.RemoveLicenseListener(s.searchLicenseListenerId) s.RemoveLicenseListener(s.searchLicenseListenerId)
if s.SearchEngine != nil && s.SearchEngine.ElasticsearchEngine != nil && s.SearchEngine.ElasticsearchEngine.IsActive() { if s.SearchEngine != nil && s.SearchEngine.ElasticsearchEngine != nil && s.SearchEngine.ElasticsearchEngine.IsActive() {
s.SearchEngine.ElasticsearchEngine.Stop() s.SearchEngine.ElasticsearchEngine.Stop()
@@ -1858,7 +1829,7 @@ func (ch *Channels) ClientConfigHash() string {
} }
func (s *Server) initJobs() { func (s *Server) initJobs() {
s.Jobs = jobs.NewJobServer(s, s.Store, s.GetMetrics()) s.Jobs = jobs.NewJobServer(s.platform, s.Store, s.GetMetrics())
if jobsDataRetentionJobInterface != nil { if jobsDataRetentionJobInterface != nil {
builder := jobsDataRetentionJobInterface(s) builder := jobsDataRetentionJobInterface(s)
@@ -2031,7 +2002,7 @@ func (s *Server) SetSharedChannelSyncService(sharedChannelService SharedChannelS
} }
func (s *Server) GetProfileImage(user *model.User) ([]byte, bool, *model.AppError) { func (s *Server) GetProfileImage(user *model.User) ([]byte, bool, *model.AppError) {
if *s.Config().FileSettings.DriverName == "" { if *s.platform.Config().FileSettings.DriverName == "" {
img, appErr := s.GetDefaultProfileImage(user) img, appErr := s.GetDefaultProfileImage(user)
if appErr != nil { if appErr != nil {
return nil, false, appErr return nil, false, appErr
@@ -2141,3 +2112,8 @@ func (a *App) GetAppliedSchemaMigrations() ([]model.AppliedMigration, *model.App
} }
return table, nil return table, nil
} }
// Expose platform service from server, this should be replaced with server itself in time.
func (s *Server) Platform() *platform.PlatformService {
return s.platform
}

Просмотреть файл

@@ -17,17 +17,17 @@ const inactivityEmailSent = "INACTIVITY"
func (s *Server) doInactivityCheck() { func (s *Server) doInactivityCheck() {
if *s.Config().ServiceSettings.EnableDeveloper { if *s.platform.Config().ServiceSettings.EnableDeveloper {
mlog.Info("No activity check because developer mode is enabled") mlog.Info("No activity check because developer mode is enabled")
return return
} }
if !*s.Config().EmailSettings.EnableInactivityEmail { if !*s.platform.Config().EmailSettings.EnableInactivityEmail {
mlog.Info("No activity check because EnableInactivityEmail is false") mlog.Info("No activity check because EnableInactivityEmail is false")
return return
} }
if !s.Config().FeatureFlags.EnableInactivityCheckJob { if !s.platform.Config().FeatureFlags.EnableInactivityCheckJob {
mlog.Info("No activity check because EnableInactivityCheckJob feature flag is disabled") mlog.Info("No activity check because EnableInactivityCheckJob feature flag is disabled")
return return
} }
@@ -70,7 +70,7 @@ func (s *Server) doInactivityCheck() {
} }
func (s *Server) takeInactivityAction() { func (s *Server) takeInactivityAction() {
siteURL := *s.Config().ServiceSettings.SiteURL siteURL := *s.platform.Config().ServiceSettings.SiteURL
if siteURL == "" { if siteURL == "" {
mlog.Warn("No SiteURL configured") mlog.Warn("No SiteURL configured")
} }

Просмотреть файл

@@ -1,13 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package app
import (
"github.com/mattermost/mattermost-server/v6/model"
)
func (s *Server) License() *model.License {
license, _ := s.licenseValue.Load().(*model.License)
return license
}

Просмотреть файл

@@ -23,6 +23,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/mattermost/mattermost-server/v6/app/platform"
"github.com/mattermost/mattermost-server/v6/config" "github.com/mattermost/mattermost-server/v6/config"
"github.com/mattermost/mattermost-server/v6/model" "github.com/mattermost/mattermost-server/v6/model"
"github.com/mattermost/mattermost-server/v6/shared/filestore" "github.com/mattermost/mattermost-server/v6/shared/filestore"
@@ -83,54 +84,70 @@ func TestReadReplicaDisabledBasedOnLicense(t *testing.T) {
s, err := NewServer(func(server *Server) error { s, err := NewServer(func(server *Server) error {
configStore := config.NewTestMemoryStore() configStore := config.NewTestMemoryStore()
configStore.Set(&cfg) configStore.Set(&cfg)
server.configStore = &configWrapper{srv: server, Store: configStore} var err error
server.platform, err = platform.New(platform.ServiceConfig{
ConfigStore: configStore,
})
require.NoError(t, err)
return nil return nil
}) })
require.NoError(t, err) require.NoError(t, err)
defer s.Shutdown() defer s.Shutdown()
require.Same(t, s.sqlStore.GetMasterX(), s.sqlStore.GetReplicaX()) require.Same(t, s.sqlStore.GetMasterX(), s.sqlStore.GetReplicaX())
require.Len(t, s.Config().SqlSettings.DataSourceReplicas, 1) require.Len(t, s.platform.Config().SqlSettings.DataSourceReplicas, 1)
}) })
t.Run("Read Replicas With License", func(t *testing.T) { t.Run("Read Replicas With License", func(t *testing.T) {
s, err := NewServer(func(server *Server) error { s, err := NewServer(func(server *Server) error {
configStore := config.NewTestMemoryStore() configStore := config.NewTestMemoryStore()
configStore.Set(&cfg) configStore.Set(&cfg)
server.configStore = &configWrapper{srv: server, Store: configStore} var err error
server.platform, err = platform.New(platform.ServiceConfig{
ConfigStore: configStore,
})
require.NoError(t, err)
server.licenseValue.Store(model.NewTestLicense()) server.licenseValue.Store(model.NewTestLicense())
return nil return nil
}) })
require.NoError(t, err) require.NoError(t, err)
defer s.Shutdown() defer s.Shutdown()
require.NotSame(t, s.sqlStore.GetMasterX(), s.sqlStore.GetReplicaX()) require.NotSame(t, s.sqlStore.GetMasterX(), s.sqlStore.GetReplicaX())
require.Len(t, s.Config().SqlSettings.DataSourceReplicas, 1) require.Len(t, s.platform.Config().SqlSettings.DataSourceReplicas, 1)
}) })
t.Run("Search Replicas with no License", func(t *testing.T) { t.Run("Search Replicas with no License", func(t *testing.T) {
s, err := NewServer(func(server *Server) error { s, err := NewServer(func(server *Server) error {
configStore := config.NewTestMemoryStore() configStore := config.NewTestMemoryStore()
configStore.Set(&cfg) configStore.Set(&cfg)
server.configStore = &configWrapper{srv: server, Store: configStore} var err error
server.platform, err = platform.New(platform.ServiceConfig{
ConfigStore: configStore,
})
require.NoError(t, err)
return nil return nil
}) })
require.NoError(t, err) require.NoError(t, err)
defer s.Shutdown() defer s.Shutdown()
require.Same(t, s.sqlStore.GetMasterX(), s.sqlStore.GetSearchReplicaX()) require.Same(t, s.sqlStore.GetMasterX(), s.sqlStore.GetSearchReplicaX())
require.Len(t, s.Config().SqlSettings.DataSourceSearchReplicas, 1) require.Len(t, s.platform.Config().SqlSettings.DataSourceSearchReplicas, 1)
}) })
t.Run("Search Replicas With License", func(t *testing.T) { t.Run("Search Replicas With License", func(t *testing.T) {
s, err := NewServer(func(server *Server) error { s, err := NewServer(func(server *Server) error {
configStore := config.NewTestMemoryStore() configStore := config.NewTestMemoryStore()
configStore.Set(&cfg) configStore.Set(&cfg)
server.configStore = &configWrapper{srv: server, Store: configStore} var err error
server.platform, err = platform.New(platform.ServiceConfig{
ConfigStore: configStore,
})
require.NoError(t, err)
server.licenseValue.Store(model.NewTestLicense()) server.licenseValue.Store(model.NewTestLicense())
return nil return nil
}) })
require.NoError(t, err) require.NoError(t, err)
defer s.Shutdown() defer s.Shutdown()
require.NotSame(t, s.sqlStore.GetMasterX(), s.sqlStore.GetSearchReplicaX()) require.NotSame(t, s.sqlStore.GetMasterX(), s.sqlStore.GetSearchReplicaX())
require.Len(t, s.Config().SqlSettings.DataSourceSearchReplicas, 1) require.Len(t, s.platform.Config().SqlSettings.DataSourceSearchReplicas, 1)
}) })
} }
@@ -143,7 +160,7 @@ func TestStartServerPortUnavailable(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
// Attempt to listen on the port used above. // Attempt to listen on the port used above.
s.UpdateConfig(func(cfg *model.Config) { s.platform.UpdateConfig(func(cfg *model.Config) {
*cfg.ServiceSettings.ListenAddress = listener.Addr().String() *cfg.ServiceSettings.ListenAddress = listener.Addr().String()
}) })
@@ -168,8 +185,12 @@ func TestStartServerNoS3Bucket(t *testing.T) {
s, err := NewServer(func(server *Server) error { s, err := NewServer(func(server *Server) error {
configStore, _ := config.NewFileStore("config.json", true) configStore, _ := config.NewFileStore("config.json", true)
store, _ := config.NewStoreFromBacking(configStore, nil, false) store, _ := config.NewStoreFromBacking(configStore, nil, false)
server.configStore = &configWrapper{srv: server, Store: store} var err error
server.UpdateConfig(func(cfg *model.Config) { server.platform, err = platform.New(platform.ServiceConfig{
ConfigStore: store,
})
require.NoError(t, err)
server.platform.UpdateConfig(func(cfg *model.Config) {
cfg.FileSettings = model.FileSettings{ cfg.FileSettings = model.FileSettings{
DriverName: model.NewString(model.ImageDriverS3), DriverName: model.NewString(model.ImageDriverS3),
AmazonS3AccessKeyId: model.NewString(model.MinioAccessKey), AmazonS3AccessKeyId: model.NewString(model.MinioAccessKey),
@@ -393,7 +414,7 @@ func TestPanicLog(t *testing.T) {
}) })
testDir, _ := fileutils.FindDir("tests") testDir, _ := fileutils.FindDir("tests")
s.UpdateConfig(func(cfg *model.Config) { s.platform.UpdateConfig(func(cfg *model.Config) {
*cfg.ServiceSettings.ListenAddress = ":0" *cfg.ServiceSettings.ListenAddress = ":0"
*cfg.ServiceSettings.ConnectionSecurity = "TLS" *cfg.ServiceSettings.ConnectionSecurity = "TLS"
*cfg.ServiceSettings.TLSKeyFile = path.Join(testDir, "tls_test_key.pem") *cfg.ServiceSettings.TLSKeyFile = path.Join(testDir, "tls_test_key.pem")

Просмотреть файл

@@ -104,7 +104,7 @@ func TestCustomStatusErrors(t *testing.T) {
UserStore: &mockUserStore, UserStore: &mockUserStore,
SessionStore: &mockSessionStore, SessionStore: &mockSessionStore,
OAuthStore: &mockOAuthStore, OAuthStore: &mockOAuthStore,
ConfigFn: th.App.ch.srv.Config, ConfigFn: th.App.ch.srv.platform.Config,
LicenseFn: th.App.ch.srv.License, LicenseFn: th.App.ch.srv.License,
}) })
require.NoError(t, err) require.NoError(t, err)

Просмотреть файл

@@ -1044,7 +1044,7 @@ func TestLeaveTeamPanic(t *testing.T) {
UserStore: &mockUserStore, UserStore: &mockUserStore,
SessionStore: &mocks.SessionStore{}, SessionStore: &mocks.SessionStore{},
OAuthStore: &mocks.OAuthStore{}, OAuthStore: &mocks.OAuthStore{},
ConfigFn: th.App.ch.srv.Config, ConfigFn: th.App.ch.srv.platform.Config,
LicenseFn: th.App.ch.srv.License, LicenseFn: th.App.ch.srv.License,
}) })
require.NoError(t, err) require.NoError(t, err)
@@ -1088,7 +1088,7 @@ func TestLeaveTeamPanic(t *testing.T) {
GroupStore: &mocks.GroupStore{}, GroupStore: &mocks.GroupStore{},
Users: th.App.ch.srv.userService, Users: th.App.ch.srv.userService,
WebHub: th.App.ch.srv, WebHub: th.App.ch.srv,
ConfigFn: th.App.ch.srv.Config, ConfigFn: th.App.ch.srv.platform.Config,
LicenseFn: th.App.ch.srv.License, LicenseFn: th.App.ch.srv.License,
}) })
require.NoError(t, err) require.NoError(t, err)

Просмотреть файл

@@ -1712,7 +1712,7 @@ func TestUpdateThreadReadForUser(t *testing.T) {
UserStore: &mockUserStore, UserStore: &mockUserStore,
SessionStore: &storemocks.SessionStore{}, SessionStore: &storemocks.SessionStore{},
OAuthStore: &storemocks.OAuthStore{}, OAuthStore: &storemocks.OAuthStore{},
ConfigFn: th.App.ch.srv.Config, ConfigFn: th.App.ch.srv.platform.Config,
LicenseFn: th.App.ch.srv.License, LicenseFn: th.App.ch.srv.License,
}) })
require.NoError(t, err) require.NoError(t, err)
@@ -1749,8 +1749,8 @@ func TestCreateUserWithInitialPreferences(t *testing.T) {
}) })
t.Run("successfully create a user with insights feature flag disabled", func(t *testing.T) { t.Run("successfully create a user with insights feature flag disabled", func(t *testing.T) {
th.Server.configStore.SetReadOnlyFF(false) th.Server.platform.SetConfigReadOnlyFF(false)
defer th.Server.configStore.SetReadOnlyFF(true) defer th.Server.platform.SetConfigReadOnlyFF(true)
th.App.UpdateConfig(func(cfg *model.Config) { cfg.FeatureFlags.InsightsEnabled = false }) th.App.UpdateConfig(func(cfg *model.Config) { cfg.FeatureFlags.InsightsEnabled = false })
defer th.App.UpdateConfig(func(cfg *model.Config) { cfg.FeatureFlags.InsightsEnabled = true }) defer th.App.UpdateConfig(func(cfg *model.Config) { cfg.FeatureFlags.InsightsEnabled = true })
testUser := th.CreateUser() testUser := th.CreateUser()
@@ -1769,8 +1769,8 @@ func TestCreateUserWithInitialPreferences(t *testing.T) {
}) })
t.Run("successfully create a guest user with initial tutorial, insights and recommended steps preferences", func(t *testing.T) { t.Run("successfully create a guest user with initial tutorial, insights and recommended steps preferences", func(t *testing.T) {
th.Server.configStore.SetReadOnlyFF(false) th.Server.platform.SetConfigReadOnlyFF(false)
defer th.Server.configStore.SetReadOnlyFF(true) defer th.Server.platform.SetConfigReadOnlyFF(true)
th.App.UpdateConfig(func(cfg *model.Config) { cfg.FeatureFlags.InsightsEnabled = true }) th.App.UpdateConfig(func(cfg *model.Config) { cfg.FeatureFlags.InsightsEnabled = true })
testUser := th.CreateGuest() testUser := th.CreateGuest()
defer th.App.PermanentDeleteUser(th.Context, testUser) defer th.App.PermanentDeleteUser(th.Context, testUser)

Просмотреть файл

@@ -167,7 +167,7 @@ func TestHubSessionRevokeRace(t *testing.T) {
UserStore: &mockUserStore, UserStore: &mockUserStore,
SessionStore: &mockSessionStore, SessionStore: &mockSessionStore,
OAuthStore: &mockOAuthStore, OAuthStore: &mockOAuthStore,
ConfigFn: th.App.ch.srv.Config, ConfigFn: th.App.ch.srv.platform.Config,
Metrics: th.App.Metrics(), Metrics: th.App.Metrics(),
Cluster: th.App.Cluster(), Cluster: th.App.Cluster(),
LicenseFn: th.App.ch.srv.License, LicenseFn: th.App.ch.srv.License,

Просмотреть файл

@@ -42,9 +42,10 @@ func initDBCommandContext(configDSN string, readOnlyConfigStore bool) (*app.App,
model.AppErrorInit(i18n.T) model.AppErrorInit(i18n.T)
s, err := app.NewServer( s, err := app.NewServer(
// The option order is important as app.Config option reads app.StartMetrics option.
app.StartMetrics,
app.Config(configDSN, readOnlyConfigStore, nil), app.Config(configDSN, readOnlyConfigStore, nil),
app.StartSearchEngine, app.StartSearchEngine,
app.StartMetrics,
) )
if err != nil { if err != nil {
return nil, err return nil, err

Просмотреть файл

@@ -65,11 +65,12 @@ func runServer(configStore *config.Store, interruptChan chan os.Signal) error {
debug.SetTraceback("crash") debug.SetTraceback("crash")
options := []app.Option{ options := []app.Option{
// The option order is important as app.Config option reads app.StartMetrics option.
app.StartMetrics,
app.ConfigStore(configStore), app.ConfigStore(configStore),
app.RunEssentialJobs, app.RunEssentialJobs,
app.JoinCluster, app.JoinCluster,
app.StartSearchEngine, app.StartSearchEngine,
app.StartMetrics,
} }
server, err := app.NewServer(options...) server, err := app.NewServer(options...)
if err != nil { if err != nil {

Просмотреть файл

@@ -102,16 +102,17 @@ func setupTestHelper(tb testing.TB, includeCacheLayer bool) *TestHelper {
} }
} }
a := app.New(app.ServerConnector(s.Channels()))
prevListenAddress := *s.Config().ServiceSettings.ListenAddress prevListenAddress := *s.Config().ServiceSettings.ListenAddress
s.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ListenAddress = ":0" }) a.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ListenAddress = ":0" })
serverErr := s.Start() serverErr := s.Start()
if serverErr != nil { if serverErr != nil {
panic(serverErr) panic(serverErr)
} }
s.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ListenAddress = prevListenAddress }) a.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ListenAddress = prevListenAddress })
// Disable strict password requirements for test // Disable strict password requirements for test
s.UpdateConfig(func(cfg *model.Config) { a.UpdateConfig(func(cfg *model.Config) {
*cfg.PasswordSettings.MinimumLength = 5 *cfg.PasswordSettings.MinimumLength = 5
*cfg.PasswordSettings.Lowercase = false *cfg.PasswordSettings.Lowercase = false
*cfg.PasswordSettings.Uppercase = false *cfg.PasswordSettings.Uppercase = false
@@ -119,15 +120,13 @@ func setupTestHelper(tb testing.TB, includeCacheLayer bool) *TestHelper {
*cfg.PasswordSettings.Number = false *cfg.PasswordSettings.Number = false
}) })
a := app.New(app.ServerConnector(s.Channels()))
web := New(s) web := New(s)
URL = fmt.Sprintf("http://localhost:%v", s.ListenAddr.Port) URL = fmt.Sprintf("http://localhost:%v", s.ListenAddr.Port)
apiClient = model.NewAPIv4Client(URL) apiClient = model.NewAPIv4Client(URL)
s.Store.MarkSystemRanUnitTests() s.Store.MarkSystemRanUnitTests()
s.UpdateConfig(func(cfg *model.Config) { a.UpdateConfig(func(cfg *model.Config) {
*cfg.TeamSettings.EnableOpenServer = true *cfg.TeamSettings.EnableOpenServer = true
}) })