mirror of
https://github.com/mattermost/mattermost.git
synced 2025-02-25 18:55:24 -06:00
Combines the following repositories into one: https://github.com/mattermost/mattermost-server https://github.com/mattermost/mattermost-webapp https://github.com/mattermost/focalboard https://github.com/mattermost/mattermost-plugin-playbooks
660 lines
19 KiB
Go
660 lines
19 KiB
Go
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
package plugin
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"hash/fnv"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/mattermost/mattermost-server/v6/model"
|
|
"github.com/mattermost/mattermost-server/v6/server/channels/einterfaces"
|
|
"github.com/mattermost/mattermost-server/v6/server/channels/utils"
|
|
"github.com/mattermost/mattermost-server/v6/server/platform/shared/mlog"
|
|
)
|
|
|
|
var ErrNotFound = errors.New("Item not found")
|
|
|
|
type apiImplCreatorFunc func(*model.Manifest) API
|
|
|
|
// registeredPlugin stores the state for a given plugin that has been activated
|
|
// or attempted to be activated this server run.
|
|
//
|
|
// If an installed plugin is missing from the env.registeredPlugins map, then the
|
|
// plugin is configured as disabled and has not been activated during this server run.
|
|
type registeredPlugin struct {
|
|
BundleInfo *model.BundleInfo
|
|
State int
|
|
Error string
|
|
|
|
supervisor *supervisor
|
|
}
|
|
|
|
// PrepackagedPlugin is a plugin prepackaged with the server and found on startup.
|
|
type PrepackagedPlugin struct {
|
|
Path string
|
|
IconData string
|
|
Manifest *model.Manifest
|
|
Signature []byte
|
|
}
|
|
|
|
// Environment represents the execution environment of active plugins.
|
|
//
|
|
// It is meant for use by the Mattermost server to manipulate, interact with and report on the set
|
|
// of active plugins.
|
|
type Environment struct {
|
|
registeredPlugins sync.Map
|
|
pluginHealthCheckJob *PluginHealthCheckJob
|
|
logger *mlog.Logger
|
|
metrics einterfaces.MetricsInterface
|
|
newAPIImpl apiImplCreatorFunc
|
|
dbDriver Driver
|
|
pluginDir string
|
|
webappPluginDir string
|
|
patchReactDOM bool
|
|
prepackagedPlugins []*PrepackagedPlugin
|
|
prepackagedPluginsLock sync.RWMutex
|
|
}
|
|
|
|
func NewEnvironment(
|
|
newAPIImpl apiImplCreatorFunc,
|
|
dbDriver Driver,
|
|
pluginDir string,
|
|
webappPluginDir string,
|
|
patchReactDOM bool,
|
|
logger *mlog.Logger,
|
|
metrics einterfaces.MetricsInterface,
|
|
) (*Environment, error) {
|
|
return &Environment{
|
|
logger: logger,
|
|
metrics: metrics,
|
|
newAPIImpl: newAPIImpl,
|
|
dbDriver: dbDriver,
|
|
pluginDir: pluginDir,
|
|
webappPluginDir: webappPluginDir,
|
|
patchReactDOM: patchReactDOM,
|
|
}, nil
|
|
}
|
|
|
|
// Performs a full scan of the given path.
|
|
//
|
|
// This function will return info for all subdirectories that appear to be plugins (i.e. all
|
|
// subdirectories containing plugin manifest files, regardless of whether they could actually be
|
|
// parsed).
|
|
//
|
|
// Plugins are found non-recursively and paths beginning with a dot are always ignored.
|
|
func scanSearchPath(path string) ([]*model.BundleInfo, error) {
|
|
files, err := os.ReadDir(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var ret []*model.BundleInfo
|
|
for _, file := range files {
|
|
if !file.IsDir() || file.Name()[0] == '.' {
|
|
continue
|
|
}
|
|
info := model.BundleInfoForPath(filepath.Join(path, file.Name()))
|
|
if info.Manifest != nil {
|
|
ret = append(ret, info)
|
|
}
|
|
}
|
|
return ret, nil
|
|
}
|
|
|
|
var pluginIDBlocklist = map[string]bool{
|
|
"playbooks": true,
|
|
"com.mattermost.plugin-incident-response": true,
|
|
"com.mattermost.plugin-incident-management": true,
|
|
"focalboard": true,
|
|
}
|
|
|
|
func PluginIDIsBlocked(id string) bool {
|
|
_, ok := pluginIDBlocklist[id]
|
|
return ok
|
|
}
|
|
|
|
// Returns a list of all plugins within the environment.
|
|
func (env *Environment) Available() ([]*model.BundleInfo, error) {
|
|
rawList, err := scanSearchPath(env.pluginDir)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Filter any plugins that match the blocklist
|
|
filteredList := make([]*model.BundleInfo, 0, len(rawList))
|
|
for _, bundleInfo := range rawList {
|
|
if PluginIDIsBlocked(bundleInfo.Manifest.Id) {
|
|
env.logger.Debug("Plugin ignored by blocklist", mlog.String("plugin_id", bundleInfo.Manifest.Id))
|
|
} else {
|
|
filteredList = append(filteredList, bundleInfo)
|
|
}
|
|
}
|
|
|
|
return filteredList, nil
|
|
}
|
|
|
|
// Returns a list of prepackaged plugins available in the local prepackaged_plugins folder.
|
|
// The list content is immutable and should not be modified.
|
|
func (env *Environment) PrepackagedPlugins() []*PrepackagedPlugin {
|
|
env.prepackagedPluginsLock.RLock()
|
|
defer env.prepackagedPluginsLock.RUnlock()
|
|
|
|
return env.prepackagedPlugins
|
|
}
|
|
|
|
// Returns a list of all currently active plugins within the environment.
|
|
// The returned list should not be modified.
|
|
func (env *Environment) Active() []*model.BundleInfo {
|
|
activePlugins := []*model.BundleInfo{}
|
|
env.registeredPlugins.Range(func(key, value any) bool {
|
|
plugin := value.(registeredPlugin)
|
|
if env.IsActive(plugin.BundleInfo.Manifest.Id) {
|
|
activePlugins = append(activePlugins, plugin.BundleInfo)
|
|
}
|
|
|
|
return true
|
|
})
|
|
|
|
return activePlugins
|
|
}
|
|
|
|
// IsActive returns true if the plugin with the given id is active.
|
|
func (env *Environment) IsActive(id string) bool {
|
|
return env.GetPluginState(id) == model.PluginStateRunning
|
|
}
|
|
|
|
func (env *Environment) SetPluginError(id string, err string) {
|
|
if rp, ok := env.registeredPlugins.Load(id); ok {
|
|
p := rp.(registeredPlugin)
|
|
p.Error = err
|
|
env.registeredPlugins.Store(id, p)
|
|
}
|
|
}
|
|
|
|
func (env *Environment) getPluginError(id string) string {
|
|
if rp, ok := env.registeredPlugins.Load(id); ok {
|
|
return rp.(registeredPlugin).Error
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
// GetPluginState returns the current state of a plugin (disabled, running, or error)
|
|
func (env *Environment) GetPluginState(id string) int {
|
|
rp, ok := env.registeredPlugins.Load(id)
|
|
if !ok {
|
|
return model.PluginStateNotRunning
|
|
}
|
|
|
|
return rp.(registeredPlugin).State
|
|
}
|
|
|
|
// setPluginState sets the current state of a plugin (disabled, running, or error)
|
|
func (env *Environment) setPluginState(id string, state int) {
|
|
if rp, ok := env.registeredPlugins.Load(id); ok {
|
|
p := rp.(registeredPlugin)
|
|
p.State = state
|
|
env.registeredPlugins.Store(id, p)
|
|
}
|
|
}
|
|
|
|
// PublicFilesPath returns a path and true if the plugin with the given id is active.
|
|
// It returns an empty string and false if the path is not set or invalid
|
|
func (env *Environment) PublicFilesPath(id string) (string, error) {
|
|
if !env.IsActive(id) {
|
|
return "", fmt.Errorf("plugin not found: %v", id)
|
|
}
|
|
return filepath.Join(env.pluginDir, id, "public"), nil
|
|
}
|
|
|
|
// Statuses returns a list of plugin statuses representing the state of every plugin
|
|
func (env *Environment) Statuses() (model.PluginStatuses, error) {
|
|
plugins, err := env.Available()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "unable to get plugin statuses")
|
|
}
|
|
|
|
pluginStatuses := make(model.PluginStatuses, 0, len(plugins))
|
|
for _, plugin := range plugins {
|
|
// For now we don't handle bad manifests, we should
|
|
if plugin.Manifest == nil {
|
|
continue
|
|
}
|
|
|
|
pluginState := env.GetPluginState(plugin.Manifest.Id)
|
|
|
|
status := &model.PluginStatus{
|
|
PluginId: plugin.Manifest.Id,
|
|
PluginPath: filepath.Dir(plugin.ManifestPath),
|
|
State: pluginState,
|
|
Error: env.getPluginError(plugin.Manifest.Id),
|
|
Name: plugin.Manifest.Name,
|
|
Description: plugin.Manifest.Description,
|
|
Version: plugin.Manifest.Version,
|
|
}
|
|
|
|
pluginStatuses = append(pluginStatuses, status)
|
|
}
|
|
|
|
return pluginStatuses, nil
|
|
}
|
|
|
|
// GetManifest returns a manifest for a given pluginId.
|
|
// Returns ErrNotFound if plugin is not found.
|
|
func (env *Environment) GetManifest(pluginId string) (*model.Manifest, error) {
|
|
plugins, err := env.Available()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "unable to get plugin statuses")
|
|
}
|
|
|
|
for _, plugin := range plugins {
|
|
if plugin.Manifest != nil && plugin.Manifest.Id == pluginId {
|
|
return plugin.Manifest, nil
|
|
}
|
|
}
|
|
|
|
return nil, ErrNotFound
|
|
}
|
|
|
|
func (env *Environment) Activate(id string) (manifest *model.Manifest, activated bool, reterr error) {
|
|
defer func() {
|
|
if reterr != nil {
|
|
env.SetPluginError(id, reterr.Error())
|
|
} else {
|
|
env.SetPluginError(id, "")
|
|
}
|
|
}()
|
|
|
|
// Check if we are already active
|
|
if env.IsActive(id) {
|
|
return nil, false, nil
|
|
}
|
|
|
|
plugins, err := env.Available()
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
var pluginInfo *model.BundleInfo
|
|
for _, p := range plugins {
|
|
if p.Manifest != nil && p.Manifest.Id == id {
|
|
if pluginInfo != nil {
|
|
return nil, false, fmt.Errorf("multiple plugins found: %v", id)
|
|
}
|
|
pluginInfo = p
|
|
}
|
|
}
|
|
if pluginInfo == nil {
|
|
return nil, false, fmt.Errorf("plugin not found: %v", id)
|
|
}
|
|
|
|
rp := newRegisteredPlugin(pluginInfo)
|
|
env.registeredPlugins.Store(id, rp)
|
|
|
|
defer func() {
|
|
if reterr == nil {
|
|
env.setPluginState(id, model.PluginStateRunning)
|
|
} else {
|
|
env.setPluginState(id, model.PluginStateFailedToStart)
|
|
}
|
|
}()
|
|
|
|
if pluginInfo.Manifest.MinServerVersion != "" {
|
|
fulfilled, err := pluginInfo.Manifest.MeetMinServerVersion(model.CurrentVersion)
|
|
if err != nil {
|
|
return nil, false, fmt.Errorf("%v: %v", err.Error(), id)
|
|
}
|
|
if !fulfilled {
|
|
return nil, false, fmt.Errorf("plugin requires Mattermost %v: %v", pluginInfo.Manifest.MinServerVersion, id)
|
|
}
|
|
}
|
|
|
|
componentActivated := false
|
|
|
|
if pluginInfo.Manifest.HasWebapp() {
|
|
updatedManifest, err := env.UnpackWebappBundle(id)
|
|
if err != nil {
|
|
return nil, false, errors.Wrapf(err, "unable to generate webapp bundle: %v", id)
|
|
}
|
|
pluginInfo.Manifest.Webapp.BundleHash = updatedManifest.Webapp.BundleHash
|
|
|
|
componentActivated = true
|
|
}
|
|
|
|
if pluginInfo.Manifest.HasServer() {
|
|
sup, err := newSupervisor(pluginInfo, env.newAPIImpl(pluginInfo.Manifest), env.dbDriver, env.logger, env.metrics)
|
|
if err != nil {
|
|
return nil, false, errors.Wrapf(err, "unable to start plugin: %v", id)
|
|
}
|
|
|
|
// We pre-emptively set the state to running to prevent re-entrancy issues.
|
|
// The plugin's OnActivate hook can in-turn call UpdateConfiguration
|
|
// which again calls this method. This method is guarded against multiple calls,
|
|
// but fails if it is called recursively.
|
|
//
|
|
// Therefore, setting the state to running prevents this from happening,
|
|
// and in case there is an error, the defer clause will set the proper state anyways.
|
|
env.setPluginState(id, model.PluginStateRunning)
|
|
|
|
if err := sup.Hooks().OnActivate(); err != nil {
|
|
sup.Shutdown()
|
|
return nil, false, err
|
|
}
|
|
rp.supervisor = sup
|
|
env.registeredPlugins.Store(id, rp)
|
|
|
|
componentActivated = true
|
|
}
|
|
|
|
if !componentActivated {
|
|
return nil, false, fmt.Errorf("unable to start plugin: must at least have a web app or server component")
|
|
}
|
|
|
|
mlog.Debug("Plugin activated", mlog.String("plugin_id", pluginInfo.Manifest.Id), mlog.String("version", pluginInfo.Manifest.Version))
|
|
|
|
return pluginInfo.Manifest, true, nil
|
|
}
|
|
|
|
func (env *Environment) RemovePlugin(id string) {
|
|
if _, ok := env.registeredPlugins.Load(id); ok {
|
|
env.registeredPlugins.Delete(id)
|
|
}
|
|
}
|
|
|
|
// Deactivates the plugin with the given id.
|
|
func (env *Environment) Deactivate(id string) bool {
|
|
p, ok := env.registeredPlugins.Load(id)
|
|
if !ok {
|
|
return false
|
|
}
|
|
|
|
isActive := env.IsActive(id)
|
|
|
|
env.setPluginState(id, model.PluginStateNotRunning)
|
|
|
|
if !isActive {
|
|
return false
|
|
}
|
|
|
|
rp := p.(registeredPlugin)
|
|
if rp.supervisor != nil {
|
|
if err := rp.supervisor.Hooks().OnDeactivate(); err != nil {
|
|
env.logger.Error("Plugin OnDeactivate() error", mlog.String("plugin_id", rp.BundleInfo.Manifest.Id), mlog.Err(err))
|
|
}
|
|
rp.supervisor.Shutdown()
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// RestartPlugin deactivates, then activates the plugin with the given id.
|
|
func (env *Environment) RestartPlugin(id string) error {
|
|
env.Deactivate(id)
|
|
_, _, err := env.Activate(id)
|
|
return err
|
|
}
|
|
|
|
// Shutdown deactivates all plugins and gracefully shuts down the environment.
|
|
func (env *Environment) Shutdown() {
|
|
env.TogglePluginHealthCheckJob(false)
|
|
|
|
var wg sync.WaitGroup
|
|
env.registeredPlugins.Range(func(key, value any) bool {
|
|
rp := value.(registeredPlugin)
|
|
|
|
if rp.supervisor == nil || !env.IsActive(rp.BundleInfo.Manifest.Id) {
|
|
return true
|
|
}
|
|
|
|
wg.Add(1)
|
|
|
|
done := make(chan bool)
|
|
go func() {
|
|
defer close(done)
|
|
if err := rp.supervisor.Hooks().OnDeactivate(); err != nil {
|
|
env.logger.Error("Plugin OnDeactivate() error", mlog.String("plugin_id", rp.BundleInfo.Manifest.Id), mlog.Err(err))
|
|
}
|
|
}()
|
|
|
|
go func() {
|
|
defer wg.Done()
|
|
|
|
select {
|
|
case <-time.After(10 * time.Second):
|
|
env.logger.Warn("Plugin OnDeactivate() failed to complete in 10 seconds", mlog.String("plugin_id", rp.BundleInfo.Manifest.Id))
|
|
case <-done:
|
|
}
|
|
|
|
rp.supervisor.Shutdown()
|
|
}()
|
|
|
|
return true
|
|
})
|
|
|
|
wg.Wait()
|
|
|
|
env.registeredPlugins.Range(func(key, value any) bool {
|
|
env.registeredPlugins.Delete(key)
|
|
|
|
return true
|
|
})
|
|
}
|
|
|
|
// UnpackWebappBundle unpacks webapp bundle for a given plugin id on disk.
|
|
func (env *Environment) UnpackWebappBundle(id string) (*model.Manifest, error) {
|
|
plugins, err := env.Available()
|
|
if err != nil {
|
|
return nil, errors.New("Unable to get available plugins")
|
|
}
|
|
var manifest *model.Manifest
|
|
for _, p := range plugins {
|
|
if p.Manifest != nil && p.Manifest.Id == id {
|
|
if manifest != nil {
|
|
return nil, fmt.Errorf("multiple plugins found: %v", id)
|
|
}
|
|
manifest = p.Manifest
|
|
}
|
|
}
|
|
if manifest == nil {
|
|
return nil, fmt.Errorf("plugin not found: %v", id)
|
|
}
|
|
|
|
bundlePath := filepath.Clean(manifest.Webapp.BundlePath)
|
|
if bundlePath == "" || bundlePath[0] == '.' {
|
|
return nil, fmt.Errorf("invalid webapp bundle path")
|
|
}
|
|
bundlePath = filepath.Join(env.pluginDir, id, bundlePath)
|
|
destinationPath := filepath.Join(env.webappPluginDir, id)
|
|
|
|
if err = os.RemoveAll(destinationPath); err != nil {
|
|
return nil, errors.Wrapf(err, "unable to remove old webapp bundle directory: %v", destinationPath)
|
|
}
|
|
|
|
if err = utils.CopyDir(filepath.Dir(bundlePath), destinationPath); err != nil {
|
|
return nil, errors.Wrapf(err, "unable to copy webapp bundle directory: %v", id)
|
|
}
|
|
|
|
sourceBundleFilepath := filepath.Join(destinationPath, filepath.Base(bundlePath))
|
|
|
|
sourceBundleFileContents, err := os.ReadFile(sourceBundleFilepath)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "unable to read webapp bundle: %v", id)
|
|
}
|
|
|
|
if env.patchReactDOM {
|
|
newContents, changed := patchReactDOM(sourceBundleFileContents)
|
|
if changed {
|
|
sourceBundleFileContents = newContents
|
|
err = os.WriteFile(sourceBundleFilepath, sourceBundleFileContents, 0644)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "unable to overwrite webapp bundle: %v", id)
|
|
}
|
|
}
|
|
}
|
|
|
|
hash := fnv.New64a()
|
|
if _, err = hash.Write(sourceBundleFileContents); err != nil {
|
|
return nil, errors.Wrapf(err, "unable to generate hash for webapp bundle: %v", id)
|
|
}
|
|
manifest.Webapp.BundleHash = hash.Sum([]byte{})
|
|
|
|
if err = os.Rename(
|
|
sourceBundleFilepath,
|
|
filepath.Join(destinationPath, fmt.Sprintf("%s_%x_bundle.js", id, manifest.Webapp.BundleHash)),
|
|
); err != nil {
|
|
return nil, errors.Wrapf(err, "unable to rename webapp bundle: %v", id)
|
|
}
|
|
|
|
return manifest, nil
|
|
}
|
|
|
|
func patchReactDOM(initialBytes []byte) ([]byte, bool) {
|
|
if !bytes.Contains(initialBytes, []byte("react-dom.production.min.js")) {
|
|
return initialBytes, false
|
|
}
|
|
|
|
initial := string(initialBytes)
|
|
nameIndex := strings.Index(initial, "react-dom.production.min.js")
|
|
|
|
beginning := strings.LastIndex(initial[:nameIndex], "{")
|
|
var end int
|
|
|
|
argDefBeginning := strings.LastIndex(initial[:beginning], "function") + 9
|
|
argDefEnd := strings.LastIndex(initial[:beginning], ")") - 1
|
|
argsNames := strings.Split(initial[argDefBeginning:argDefEnd], ",")
|
|
if len(argsNames) != 3 {
|
|
return initialBytes, false
|
|
}
|
|
|
|
exportsArgName := strings.TrimSpace(argsNames[1])
|
|
|
|
numOpenBraces := 0
|
|
for i, c := range initial[beginning:] {
|
|
if end != 0 {
|
|
break
|
|
}
|
|
switch c {
|
|
case '}':
|
|
numOpenBraces--
|
|
|
|
if numOpenBraces == 0 {
|
|
end = beginning + i
|
|
}
|
|
case '{':
|
|
numOpenBraces++
|
|
}
|
|
}
|
|
|
|
beforePatch := initial[:end]
|
|
afterPatch := initial[end:]
|
|
|
|
patch := fmt.Sprintf("; Object.assign(%s, window.ReactDOM)", exportsArgName)
|
|
|
|
result := fmt.Sprintf("%s%s%s", beforePatch, patch, afterPatch)
|
|
return []byte(result), true
|
|
}
|
|
|
|
// HooksForPlugin returns the hooks API for the plugin with the given id.
|
|
//
|
|
// Consider using RunMultiPluginHook instead.
|
|
func (env *Environment) HooksForPlugin(id string) (Hooks, error) {
|
|
if p, ok := env.registeredPlugins.Load(id); ok {
|
|
rp := p.(registeredPlugin)
|
|
if rp.supervisor != nil && env.IsActive(id) {
|
|
return rp.supervisor.Hooks(), nil
|
|
}
|
|
}
|
|
|
|
return nil, fmt.Errorf("plugin not found: %v", id)
|
|
}
|
|
|
|
// RunMultiPluginHook invokes hookRunnerFunc for each active plugin that implements the given hookId.
|
|
//
|
|
// If hookRunnerFunc returns false, iteration will not continue. The iteration order among active
|
|
// plugins is not specified.
|
|
func (env *Environment) RunMultiPluginHook(hookRunnerFunc func(hooks Hooks) bool, hookId int) {
|
|
startTime := time.Now()
|
|
|
|
env.registeredPlugins.Range(func(key, value any) bool {
|
|
rp := value.(registeredPlugin)
|
|
|
|
if rp.supervisor == nil || !rp.supervisor.Implements(hookId) || !env.IsActive(rp.BundleInfo.Manifest.Id) {
|
|
return true
|
|
}
|
|
|
|
hookStartTime := time.Now()
|
|
result := hookRunnerFunc(rp.supervisor.Hooks())
|
|
|
|
if env.metrics != nil {
|
|
elapsedTime := float64(time.Since(hookStartTime)) / float64(time.Second)
|
|
env.metrics.ObservePluginMultiHookIterationDuration(rp.BundleInfo.Manifest.Id, elapsedTime)
|
|
}
|
|
|
|
return result
|
|
})
|
|
|
|
if env.metrics != nil {
|
|
elapsedTime := float64(time.Since(startTime)) / float64(time.Second)
|
|
env.metrics.ObservePluginMultiHookDuration(elapsedTime)
|
|
}
|
|
}
|
|
|
|
// PerformHealthCheck uses the active plugin's supervisor to verify if the plugin has crashed.
|
|
func (env *Environment) PerformHealthCheck(id string) error {
|
|
p, ok := env.registeredPlugins.Load(id)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
rp := p.(registeredPlugin)
|
|
|
|
sup := rp.supervisor
|
|
if sup == nil {
|
|
return nil
|
|
}
|
|
return sup.PerformHealthCheck()
|
|
}
|
|
|
|
// SetPrepackagedPlugins saves prepackaged plugins in the environment.
|
|
func (env *Environment) SetPrepackagedPlugins(plugins []*PrepackagedPlugin) {
|
|
env.prepackagedPluginsLock.Lock()
|
|
env.prepackagedPlugins = plugins
|
|
env.prepackagedPluginsLock.Unlock()
|
|
}
|
|
|
|
func newRegisteredPlugin(bundle *model.BundleInfo) registeredPlugin {
|
|
state := model.PluginStateNotRunning
|
|
return registeredPlugin{State: state, BundleInfo: bundle}
|
|
}
|
|
|
|
// TogglePluginHealthCheckJob starts a new job if one is not running and is set to enabled, or kills an existing one if set to disabled.
|
|
func (env *Environment) TogglePluginHealthCheckJob(enable bool) {
|
|
// Config is set to enable. No job exists, start a new job.
|
|
if enable && env.pluginHealthCheckJob == nil {
|
|
mlog.Debug("Enabling plugin health check job", mlog.Duration("interval_s", HealthCheckInterval))
|
|
|
|
job := newPluginHealthCheckJob(env)
|
|
env.pluginHealthCheckJob = job
|
|
go job.run()
|
|
}
|
|
|
|
// Config is set to disable. Job exists, kill existing job.
|
|
if !enable && env.pluginHealthCheckJob != nil {
|
|
mlog.Debug("Disabling plugin health check job")
|
|
|
|
env.pluginHealthCheckJob.Cancel()
|
|
env.pluginHealthCheckJob = nil
|
|
}
|
|
}
|
|
|
|
// GetPluginHealthCheckJob returns the configured PluginHealthCheckJob, if any.
|
|
func (env *Environment) GetPluginHealthCheckJob() *PluginHealthCheckJob {
|
|
return env.pluginHealthCheckJob
|
|
}
|