v8.0 module release (#22975)
https://mattermost.atlassian.net/browse/MM-52079 ```release-note We upgrade the module version to 8.0. The new module path is github.com/mattermost-server/server/v8. ``` Co-authored-by: Doug Lauder <wiggin77@warpmail.net>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
831ea38f7e
Коммит
b200a07881
659
server/plugin/environment.go
Обычный файл
659
server/plugin/environment.go
Обычный файл
@@ -0,0 +1,659 @@
|
||||
// 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/server/v8/channels/einterfaces"
|
||||
"github.com/mattermost/mattermost-server/server/v8/channels/utils"
|
||||
"github.com/mattermost/mattermost-server/server/v8/model"
|
||||
"github.com/mattermost/mattermost-server/server/v8/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
|
||||
}
|
||||
Ссылка в новой задаче
Block a user