1// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
2// See LICENSE.txt for license information.
3
4package plugin
5
6import (
7	"fmt"
8	"os/exec"
9	"path/filepath"
10	"runtime"
11	"strings"
12	"sync"
13	"time"
14
15	plugin "github.com/hashicorp/go-plugin"
16
17	"github.com/mattermost/mattermost-server/v6/einterfaces"
18	"github.com/mattermost/mattermost-server/v6/model"
19	"github.com/mattermost/mattermost-server/v6/shared/mlog"
20)
21
22type supervisor struct {
23	lock        sync.RWMutex
24	client      *plugin.Client
25	hooks       Hooks
26	implemented [TotalHooksID]bool
27	pid         int
28	hooksClient *hooksRPCClient
29}
30
31func newSupervisor(pluginInfo *model.BundleInfo, apiImpl API, driver Driver, parentLogger *mlog.Logger, metrics einterfaces.MetricsInterface) (retSupervisor *supervisor, retErr error) {
32	sup := supervisor{}
33	defer func() {
34		if retErr != nil {
35			sup.Shutdown()
36		}
37	}()
38
39	wrappedLogger := pluginInfo.WrapLogger(parentLogger)
40
41	hclogAdaptedLogger := &hclogAdapter{
42		wrappedLogger: wrappedLogger,
43		extrasKey:     "wrapped_extras",
44	}
45
46	pluginMap := map[string]plugin.Plugin{
47		"hooks": &hooksPlugin{
48			log:        wrappedLogger,
49			driverImpl: driver,
50			apiImpl:    &apiTimerLayer{pluginInfo.Manifest.Id, apiImpl, metrics},
51		},
52	}
53
54	executable := filepath.Clean(filepath.Join(
55		".",
56		pluginInfo.Manifest.GetExecutableForRuntime(runtime.GOOS, runtime.GOARCH),
57	))
58	if strings.HasPrefix(executable, "..") {
59		return nil, fmt.Errorf("invalid backend executable")
60	}
61	executable = filepath.Join(pluginInfo.Path, executable)
62
63	cmd := exec.Command(executable)
64
65	sup.client = plugin.NewClient(&plugin.ClientConfig{
66		HandshakeConfig: handshake,
67		Plugins:         pluginMap,
68		Cmd:             cmd,
69		SyncStdout:      wrappedLogger.With(mlog.String("source", "plugin_stdout")).StdLogWriter(),
70		SyncStderr:      wrappedLogger.With(mlog.String("source", "plugin_stderr")).StdLogWriter(),
71		Logger:          hclogAdaptedLogger,
72		StartTimeout:    time.Second * 3,
73	})
74
75	rpcClient, err := sup.client.Client()
76	if err != nil {
77		return nil, err
78	}
79
80	sup.pid = cmd.Process.Pid
81
82	raw, err := rpcClient.Dispense("hooks")
83	if err != nil {
84		return nil, err
85	}
86
87	c, ok := raw.(*hooksRPCClient)
88	if ok {
89		sup.hooksClient = c
90	}
91
92	sup.hooks = &hooksTimerLayer{pluginInfo.Manifest.Id, raw.(Hooks), metrics}
93
94	impl, err := sup.hooks.Implemented()
95	if err != nil {
96		return nil, err
97	}
98	for _, hookName := range impl {
99		if hookId, ok := hookNameToId[hookName]; ok {
100			sup.implemented[hookId] = true
101		}
102	}
103
104	return &sup, nil
105}
106
107func (sup *supervisor) Shutdown() {
108	sup.lock.RLock()
109	defer sup.lock.RUnlock()
110	if sup.client != nil {
111		sup.client.Kill()
112	}
113
114	// Wait for API RPC server and DB RPC server to exit.
115	if sup.hooksClient != nil {
116		sup.hooksClient.doneWg.Wait()
117	}
118}
119
120func (sup *supervisor) Hooks() Hooks {
121	sup.lock.RLock()
122	defer sup.lock.RUnlock()
123	return sup.hooks
124}
125
126// PerformHealthCheck checks the plugin through an an RPC ping.
127func (sup *supervisor) PerformHealthCheck() error {
128	// No need for a lock here because Ping is read-locked.
129	if pingErr := sup.Ping(); pingErr != nil {
130		for pingFails := 1; pingFails < HealthCheckPingFailLimit; pingFails++ {
131			pingErr = sup.Ping()
132			if pingErr == nil {
133				break
134			}
135		}
136		if pingErr != nil {
137			return fmt.Errorf("plugin RPC connection is not responding")
138		}
139	}
140
141	return nil
142}
143
144// Ping checks that the RPC connection with the plugin is alive and healthy.
145func (sup *supervisor) Ping() error {
146	sup.lock.RLock()
147	defer sup.lock.RUnlock()
148	client, err := sup.client.Client()
149	if err != nil {
150		return err
151	}
152
153	return client.Ping()
154}
155
156func (sup *supervisor) Implements(hookId int) bool {
157	sup.lock.RLock()
158	defer sup.lock.RUnlock()
159	return sup.implemented[hookId]
160}
161