MM-28861 Marketplace feature flags (#16451)
* Installing plugins specified by feature flags using the marketplace. * Switch back to using getplugins client. * Respect disabling automatic installation of pluings. * pluginid -> plugin_id * Debug logs for enable plugin error Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
987eddce1f
Коммит
b40a93d68b
@@ -87,6 +87,7 @@ func setupTestHelper(dbStore store.Store, searchEngine *searchengine.Broker, ent
|
|||||||
*memoryConfig.ServiceSettings.LocalModeSocketLocation = filepath.Join(tempWorkspace, "mattermost_local.sock")
|
*memoryConfig.ServiceSettings.LocalModeSocketLocation = filepath.Join(tempWorkspace, "mattermost_local.sock")
|
||||||
*memoryConfig.AnnouncementSettings.AdminNoticesEnabled = false
|
*memoryConfig.AnnouncementSettings.AdminNoticesEnabled = false
|
||||||
*memoryConfig.AnnouncementSettings.UserNoticesEnabled = false
|
*memoryConfig.AnnouncementSettings.UserNoticesEnabled = false
|
||||||
|
*memoryConfig.PluginSettings.AutomaticPrepackagedPlugins = false
|
||||||
if updateConfig != nil {
|
if updateConfig != nil {
|
||||||
updateConfig(memoryConfig)
|
updateConfig(memoryConfig)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ func setupTestHelper(dbStore store.Store, enterprise bool, includeCacheLayer boo
|
|||||||
}
|
}
|
||||||
*config.PluginSettings.Directory = filepath.Join(tempWorkspace, "plugins")
|
*config.PluginSettings.Directory = filepath.Join(tempWorkspace, "plugins")
|
||||||
*config.PluginSettings.ClientDirectory = filepath.Join(tempWorkspace, "webapp")
|
*config.PluginSettings.ClientDirectory = filepath.Join(tempWorkspace, "webapp")
|
||||||
|
*config.PluginSettings.AutomaticPrepackagedPlugins = false
|
||||||
*config.LogSettings.EnableSentry = false // disable error reporting during tests
|
*config.LogSettings.EnableSentry = false // disable error reporting during tests
|
||||||
*config.AnnouncementSettings.AdminNoticesEnabled = false
|
*config.AnnouncementSettings.AdminNoticesEnabled = false
|
||||||
*config.AnnouncementSettings.UserNoticesEnabled = false
|
*config.AnnouncementSettings.UserNoticesEnabled = false
|
||||||
|
|||||||
@@ -198,10 +198,13 @@ func (a *App) InitPlugins(pluginDir, webappPluginDir string) {
|
|||||||
}
|
}
|
||||||
pluginsEnvironment.SetPrepackagedPlugins(plugins)
|
pluginsEnvironment.SetPrepackagedPlugins(plugins)
|
||||||
|
|
||||||
|
a.installFeatureFlagPlugins()
|
||||||
|
|
||||||
// Sync plugin active state when config changes. Also notify plugins.
|
// Sync plugin active state when config changes. Also notify plugins.
|
||||||
a.Srv().PluginsLock.Lock()
|
a.Srv().PluginsLock.Lock()
|
||||||
a.RemoveConfigListener(a.Srv().PluginConfigListenerId)
|
a.RemoveConfigListener(a.Srv().PluginConfigListenerId)
|
||||||
a.Srv().PluginConfigListenerId = a.AddConfigListener(func(*model.Config, *model.Config) {
|
a.Srv().PluginConfigListenerId = a.AddConfigListener(func(*model.Config, *model.Config) {
|
||||||
|
a.installFeatureFlagPlugins()
|
||||||
a.SyncPluginsActiveState()
|
a.SyncPluginsActiveState()
|
||||||
if pluginsEnvironment := a.GetPluginsEnvironment(); pluginsEnvironment != nil {
|
if pluginsEnvironment := a.GetPluginsEnvironment(); pluginsEnvironment != nil {
|
||||||
pluginsEnvironment.RunMultiPluginHook(func(hooks plugin.Hooks) bool {
|
pluginsEnvironment.RunMultiPluginHook(func(hooks plugin.Hooks) bool {
|
||||||
@@ -510,7 +513,8 @@ func (a *App) getRemoteMarketplacePlugin(pluginId, version string) (*model.BaseM
|
|||||||
}
|
}
|
||||||
|
|
||||||
filter := a.getBaseMarketplaceFilter()
|
filter := a.getBaseMarketplaceFilter()
|
||||||
filter.Filter = pluginId
|
filter.PluginId = pluginId
|
||||||
|
filter.ReturnAllVersions = true
|
||||||
|
|
||||||
plugin, err := marketplaceClient.GetPlugin(filter, version)
|
plugin, err := marketplaceClient.GetPlugin(filter, version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -886,6 +890,47 @@ func (a *App) processPrepackagedPlugin(pluginPath *pluginSignaturePath) (*plugin
|
|||||||
return plugin, nil
|
return plugin, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// installFeatureFlagPlugins handles the automatic installation/upgrade of plugins from feature flags
|
||||||
|
func (a *App) installFeatureFlagPlugins() {
|
||||||
|
ffControledPlugins := a.Config().FeatureFlags.Plugins()
|
||||||
|
|
||||||
|
// Respect the automatic prepackaged disable setting
|
||||||
|
if !*a.Config().PluginSettings.AutomaticPrepackagedPlugins {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for pluginId, version := range ffControledPlugins {
|
||||||
|
// Skip installing if the plugin has been previously disabled.
|
||||||
|
pluginState := a.Config().PluginSettings.PluginStates[pluginId]
|
||||||
|
if pluginState != nil && !pluginState.Enable {
|
||||||
|
a.Log().Debug("Not auto installing/upgrade because plugin was disabled", mlog.String("plugin_id", pluginId), mlog.String("version", version))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we already installed this version as InstallMarketplacePlugin can't handle re-installs well.
|
||||||
|
pluginStatus, err := a.Srv().GetPluginStatus(pluginId)
|
||||||
|
if err == nil && pluginStatus.Version == version {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if version != "" && version != "control" {
|
||||||
|
_, err := a.InstallMarketplacePlugin(&model.InstallMarketplacePluginRequest{
|
||||||
|
Id: pluginId,
|
||||||
|
Version: version,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
a.Log().Debug("Unable to install plugin from FF manifest", mlog.String("plugin_id", pluginId), mlog.Err(err), mlog.String("version", version))
|
||||||
|
} else {
|
||||||
|
if err := a.EnablePlugin(pluginId); err != nil {
|
||||||
|
a.Log().Debug("Unable to enable plugin installed from feature flag.", mlog.String("plugin_id", pluginId), mlog.Err(err), mlog.String("version", version))
|
||||||
|
} else {
|
||||||
|
a.Log().Debug("Installed and enabled plugin.", mlog.String("plugin_id", pluginId), mlog.String("version", version))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// getPrepackagedPlugin builds a PrepackagedPlugin from the plugin at the given path, additionally returning the directory in which it was extracted.
|
// getPrepackagedPlugin builds a PrepackagedPlugin from the plugin at the given path, additionally returning the directory in which it was extracted.
|
||||||
func getPrepackagedPlugin(pluginPath *pluginSignaturePath, pluginFile io.ReadSeeker, tmpDir string) (*plugin.PrepackagedPlugin, string, error) {
|
func getPrepackagedPlugin(pluginPath *pluginSignaturePath, pluginFile io.ReadSeeker, tmpDir string) (*plugin.PrepackagedPlugin, string, error) {
|
||||||
manifest, pluginDir, appErr := extractPlugin(pluginFile, tmpDir)
|
manifest, pluginDir, appErr := extractPlugin(pluginFile, tmpDir)
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
|
|
||||||
package model
|
package model
|
||||||
|
|
||||||
|
import "reflect"
|
||||||
|
|
||||||
type FeatureFlags struct {
|
type FeatureFlags struct {
|
||||||
// Exists only for unit and manual testing.
|
// Exists only for unit and manual testing.
|
||||||
// When set to a value, will be returned by the ping endpoint.
|
// When set to a value, will be returned by the ping endpoint.
|
||||||
@@ -13,10 +15,35 @@ type FeatureFlags struct {
|
|||||||
|
|
||||||
// Toggle on and off scheduled jobs for cloud user limit emails see MM-29999
|
// Toggle on and off scheduled jobs for cloud user limit emails see MM-29999
|
||||||
CloudDelinquentEmailJobsEnabled bool
|
CloudDelinquentEmailJobsEnabled bool
|
||||||
|
|
||||||
|
// Feature flags to control plugin versions
|
||||||
|
PluginIncidentManagement string `plugin_id:"com.mattermost.plugin-incident-management"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FeatureFlags) SetDefaults() {
|
func (f *FeatureFlags) SetDefaults() {
|
||||||
f.TestFeature = "off"
|
f.TestFeature = "off"
|
||||||
f.TestBoolFeature = false
|
f.TestBoolFeature = false
|
||||||
f.CloudDelinquentEmailJobsEnabled = false
|
f.CloudDelinquentEmailJobsEnabled = false
|
||||||
|
|
||||||
|
f.PluginIncidentManagement = "1.1.1"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FeatureFlags) Plugins() map[string]string {
|
||||||
|
rFFVal := reflect.ValueOf(f).Elem()
|
||||||
|
rFFType := reflect.TypeOf(f).Elem()
|
||||||
|
|
||||||
|
pluginVersions := make(map[string]string)
|
||||||
|
for i := 0; i < rFFVal.NumField(); i++ {
|
||||||
|
rFieldVal := rFFVal.Field(i)
|
||||||
|
rFieldType := rFFType.Field(i)
|
||||||
|
|
||||||
|
pluginId, hasPluginId := rFieldType.Tag.Lookup("plugin_id")
|
||||||
|
if !hasPluginId {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
pluginVersions[pluginId] = rFieldVal.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
return pluginVersions
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,6 +87,8 @@ type MarketplacePluginFilter struct {
|
|||||||
Cloud bool
|
Cloud bool
|
||||||
LocalOnly bool
|
LocalOnly bool
|
||||||
Platform string
|
Platform string
|
||||||
|
PluginId string
|
||||||
|
ReturnAllVersions bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// ApplyToURL modifies the given url to include query string parameters for the request.
|
// ApplyToURL modifies the given url to include query string parameters for the request.
|
||||||
@@ -103,6 +105,8 @@ func (filter *MarketplacePluginFilter) ApplyToURL(u *url.URL) {
|
|||||||
q.Add("cloud", strconv.FormatBool(filter.Cloud))
|
q.Add("cloud", strconv.FormatBool(filter.Cloud))
|
||||||
q.Add("local_only", strconv.FormatBool(filter.LocalOnly))
|
q.Add("local_only", strconv.FormatBool(filter.LocalOnly))
|
||||||
q.Add("platform", filter.Platform)
|
q.Add("platform", filter.Platform)
|
||||||
|
q.Add("plugin_id", filter.PluginId)
|
||||||
|
q.Add("return_all_versions", strconv.FormatBool(filter.ReturnAllVersions))
|
||||||
u.RawQuery = q.Encode()
|
u.RawQuery = q.Encode()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ func setupTestHelper(t testing.TB, store store.Store, includeCacheLayer bool) *T
|
|||||||
newConfig := memoryStore.Get().Clone()
|
newConfig := memoryStore.Get().Clone()
|
||||||
*newConfig.AnnouncementSettings.AdminNoticesEnabled = false
|
*newConfig.AnnouncementSettings.AdminNoticesEnabled = false
|
||||||
*newConfig.AnnouncementSettings.UserNoticesEnabled = false
|
*newConfig.AnnouncementSettings.UserNoticesEnabled = false
|
||||||
|
*newConfig.PluginSettings.AutomaticPrepackagedPlugins = false
|
||||||
memoryStore.Set(newConfig)
|
memoryStore.Set(newConfig)
|
||||||
var options []app.Option
|
var options []app.Option
|
||||||
options = append(options, app.ConfigStore(memoryStore))
|
options = append(options, app.ConfigStore(memoryStore))
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user