diff --git a/restapi/README.md b/restapi/README.md new file mode 100644 index 0000000..2928084 --- /dev/null +++ b/restapi/README.md @@ -0,0 +1,65 @@ +# restapi + +Plugin **restapi** provides a REST API to communicate with the *collectd* +daemon. + +## Description + +The *restapi plugin* starts a webserver and waits for incoming REST API +requests. + +## Building + +To build this plugin, the collectd header files are required. + +On Debian and Ubuntu, the collectd headers are available from the +`collectd-dev` package. Once installed, add the import paths to the +`CGI_CPPFLAGS`: + +```bash +export CGO_CPPFLAGS="-I/usr/include/collectd/core/daemon \ +-I/usr/include/collectd/core -I/usr/include/collectd" +``` + +Alternatively, you can grab the collectd sources, run the `configure` script, +and reference the header files from there: + +```bash +TOP_SRCDIR="${HOME}/collectd" +export CGO_CPPFLAGS="-I${TOP_SRCDIR}/src -I${TOP_SRCDIR}/src/daemon" +``` + +Then build the plugin with the "c-shared" buildmode: + +```bash +go build -buildmode=c-shared -o restapi.so +``` + +## Configuration + +### Synopsis + +``` +LoadPlugin restapi + + Addr "::" + Port "8443" + CertFile "/path/to/cert_file.pem" + KeyFile "/path/to/key_file.pem" + +``` + +### Options + +* **Addr** *Network address* + + Addredd to listen to. Defaults to `""` (any address). +* **Port** *Port* + + Post to listen to. Defaults to `8080` (`8443` if **CertFile** is specified). +* **CertFile** *Path*
+ **KeyFile** *Path* + + TLS certificate and key files. Refer to + [`"net/http".ListenAndServeTLS`](https://golang.org/pkg/net/http/#ListenAndServeTLS) + for more information on the TLS setup. diff --git a/restapi/restapi.go b/restapi/restapi.go new file mode 100644 index 0000000..63762e2 --- /dev/null +++ b/restapi/restapi.go @@ -0,0 +1,118 @@ +package main + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "net" + "net/http" + "time" + + "collectd.org/api" + "collectd.org/config" + "collectd.org/plugin" + "go.uber.org/multierr" +) + +const pluginName = "restapi" + +type restapi struct { + srv *http.Server +} + +func init() { + ra := &restapi{} + + plugin.RegisterConfig(pluginName, ra) + plugin.RegisterShutdown(pluginName, ra) +} + +func valueListHandler(w http.ResponseWriter, req *http.Request) { + if req.Method != http.MethodPost { + w.WriteHeader(http.StatusNotImplemented) + fmt.Fprintln(w, "Only POST is currently supported.") + return + } + + var vls []api.ValueList + if err := json.NewDecoder(req.Body).Decode(&vls); err != nil { + w.WriteHeader(http.StatusBadRequest) + fmt.Fprintln(w, "parsing JSON failed:", err) + return + } + + var errs error + for _, vl := range vls { + errs = multierr.Append(errs, + plugin.Write(req.Context(), &vl)) + } + + if errs != nil { + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintln(w, "plugin.Write():", errs) + return + } +} + +type srvConfig struct { + Args string // unused + Addr string + Port string + CertFile string + KeyFile string +} + +func (ra *restapi) Configure(_ context.Context, rawConfig config.Block) error { + cfg := srvConfig{} + if err := rawConfig.Unmarshal(&cfg); err != nil { + return err + } + + if (cfg.CertFile == "") != (cfg.KeyFile == "") { + return fmt.Errorf("CertFile=%q, KeyFile=%q; need both for TLS setup", + cfg.CertFile, cfg.KeyFile) + } + + if cfg.Port == "" { + cfg.Port = "8080" + if cfg.CertFile != "" { + cfg.Port = "8443" + } + } + + mux := http.NewServeMux() + mux.HandleFunc("/valueList", valueListHandler) + + ra.srv = &http.Server{ + Addr: net.JoinHostPort(cfg.Addr, cfg.Port), + Handler: mux, + } + + go func() { + var err error + if cfg.CertFile != "" { + err = ra.srv.ListenAndServeTLS(cfg.CertFile, cfg.KeyFile) + } else { + err = ra.srv.ListenAndServe() + } + if !errors.Is(err, http.ErrServerClosed) { + plugin.Errorf("%s plugin: ListenAndServe(): %v", pluginName, err) + } + }() + + return nil +} + +func (ra *restapi) Shutdown(ctx context.Context) error { + if ra == nil || ra.srv == nil { + return nil + } + + ctx, cancel := context.WithTimeout(ctx, 30*time.Second) + defer cancel() + + return ra.srv.Shutdown(ctx) +} + +func main() {} // ignored