Mattermost plugin first commit

This commit is contained in:
Zef Hemel
2022-07-22 13:44:45 +02:00
parent 65234e17dd
commit 88121da341
21 changed files with 2796 additions and 2 deletions
+83
View File
@@ -0,0 +1,83 @@
package main
import (
"reflect"
"github.com/pkg/errors"
)
// configuration captures the plugin's external configuration as exposed in the Mattermost server
// configuration, as well as values computed from the configuration. Any public fields will be
// deserialized from the Mattermost server configuration in OnConfigurationChange.
//
// As plugins are inherently concurrent (hooks being called asynchronously), and the plugin
// configuration can change at any time, access to the configuration must be synchronized. The
// strategy used in this plugin is to guard a pointer to the configuration, and clone the entire
// struct whenever it changes. You may replace this with whatever strategy you choose.
//
// If you add non-reference types to your configuration struct, be sure to rewrite Clone as a deep
// copy appropriate for your types.
type configuration struct {
}
// Clone shallow copies the configuration. Your implementation may require a deep copy if
// your configuration has reference types.
func (c *configuration) Clone() *configuration {
var clone = *c
return &clone
}
// getConfiguration retrieves the active configuration under lock, making it safe to use
// concurrently. The active configuration may change underneath the client of this method, but
// the struct returned by this API call is considered immutable.
func (p *Plugin) getConfiguration() *configuration {
p.configurationLock.RLock()
defer p.configurationLock.RUnlock()
if p.configuration == nil {
return &configuration{}
}
return p.configuration
}
// setConfiguration replaces the active configuration under lock.
//
// Do not call setConfiguration while holding the configurationLock, as sync.Mutex is not
// reentrant. In particular, avoid using the plugin API entirely, as this may in turn trigger a
// hook back into the plugin. If that hook attempts to acquire this lock, a deadlock may occur.
//
// This method panics if setConfiguration is called with the existing configuration. This almost
// certainly means that the configuration was modified without being cloned and may result in
// an unsafe access.
func (p *Plugin) setConfiguration(configuration *configuration) {
p.configurationLock.Lock()
defer p.configurationLock.Unlock()
if configuration != nil && p.configuration == configuration {
// Ignore assignment if the configuration struct is empty. Go will optimize the
// allocation for same to point at the same memory address, breaking the check
// above.
if reflect.ValueOf(*configuration).NumField() == 0 {
return
}
panic("setConfiguration called with the existing configuration")
}
p.configuration = configuration
}
// OnConfigurationChange is invoked when configuration changes may have been made.
func (p *Plugin) OnConfigurationChange() error {
var configuration = new(configuration)
// Load the public configuration fields from the Mattermost server configuration.
if err := p.API.LoadPluginConfiguration(configuration); err != nil {
return errors.Wrap(err, "failed to load plugin configuration")
}
p.setConfiguration(configuration)
return nil
}
+9
View File
@@ -0,0 +1,9 @@
package main
import (
"github.com/mattermost/mattermost-server/v6/plugin"
)
func main() {
plugin.ClientMain(&Plugin{})
}
+74
View File
@@ -0,0 +1,74 @@
package main
import (
"fmt"
"io"
"net/http"
"strings"
"sync"
"github.com/mattermost/mattermost-server/v6/plugin"
"github.com/mattermost/mattermost-server/v6/shared/mlog"
)
// Plugin implements the interface expected by the Mattermost server to communicate between the server and plugin processes.
type Plugin struct {
plugin.MattermostPlugin
// configurationLock synchronizes access to the configuration.
configurationLock sync.RWMutex
// configuration is the active plugin configuration. Consult getConfiguration and
// setConfiguration for usage.
configuration *configuration
}
// ServeHTTP demonstrates a plugin that handles HTTP requests by greeting the world.
func (p *Plugin) ServeHTTP(c *plugin.Context, w http.ResponseWriter, r *http.Request) {
if strings.HasPrefix(r.URL.Path, "/fs") || strings.HasPrefix(r.URL.Path, "/plug/") {
p.httpProxy(w, r)
return
}
if r.URL.Path == "/global.plug.json" {
p.httpProxy(w, r)
return
}
r2, err := http.Get("http://localhost:8065")
if err != nil {
fmt.Println(err)
return
}
io.Copy(w, r2.Body)
// fmt.Fprint(w, "Hello, world!")
}
func (p *Plugin) httpProxy(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
mlog.Info(fmt.Sprintf("Got HTTP request: %s: %s Headers: %+v", r.Method, r.URL, r.Header))
req, err := http.NewRequest(r.Method, fmt.Sprintf("http://localhost:%d%s", 3000, r.URL), r.Body)
if err != nil {
http.Error(w, fmt.Sprintf("Proxy error: %s", err), http.StatusInternalServerError)
return
}
req.Header = r.Header
res, err := http.DefaultClient.Do(req)
if err != nil {
http.Error(w, fmt.Sprintf("Proxy error: %s", err), http.StatusInternalServerError)
return
}
for k, vs := range res.Header {
for _, v := range vs {
w.Header().Add(k, v)
}
}
w.WriteHeader(res.StatusCode)
_, err = io.Copy(w, res.Body)
if err != nil {
mlog.Error("Error proxying", mlog.Err(err))
}
res.Body.Close()
}
// See https://developers.mattermost.com/extend/plugins/server/reference/
+28
View File
@@ -0,0 +1,28 @@
package main
import (
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
)
func TestServeHTTP(t *testing.T) {
assert := assert.New(t)
plugin := Plugin{}
w := httptest.NewRecorder()
r := httptest.NewRequest(http.MethodGet, "/", nil)
plugin.ServeHTTP(nil, w, r)
result := w.Result()
assert.NotNil(result)
defer result.Body.Close()
bodyBytes, err := ioutil.ReadAll(result.Body)
assert.Nil(err)
bodyString := string(bodyBytes)
assert.Equal("Hello, world!", bodyString)
}