Compare commits

...

22 Commits

Author SHA1 Message Date
Adnan Hajdarević 005e723b23 Merge pull request #34 from adnanh/development
Development
2015-06-06 14:28:54 +02:00
Adnan Hajdarevic 9977fa8c61 refactor 2015-06-06 14:28:00 +02:00
Adnan Hajdarevic cbe2440cda add entire query and headers as well 2015-06-06 14:25:32 +02:00
Adnan Hajdarevic 9c545a745f return command output, pass whole payload as json to the command 2015-06-06 14:19:52 +02:00
Adnan Hajdarević 263c75b1b5 Update README.md 2015-06-05 11:21:52 +02:00
Adnan Hajdarević 83cbffd37c Merge pull request #33 from gitter-badger/gitter-badge
Add a Gitter chat badge to README.md
2015-06-05 11:21:06 +02:00
The Gitter Badger b310b79fb8 Added Gitter badge 2015-06-05 09:15:27 +00:00
Adnan Hajdarevic f1ebc440a4 match all hooks with the same id 2015-05-27 09:16:26 +02:00
Adnan Hajdarević 10732bd57b Merge pull request #30 from adnanh/development
separated windows and other platforms to different files, removed sig…
2015-05-16 13:36:20 +02:00
Adnan Hajdarevic 4350685330 separated windows and other platforms to different files, removed signal watcher from windows build file so webhook can actually compile on windows, added string as a source, so you can pass static strings to your scripts without having to wrap them around with other scripts 2015-05-16 13:32:21 +02:00
Adnan Hajdarević 6053f48b23 Merge pull request #28 from kevinlebrun/fix-for-osx-signals
Fix for OS X USR1 signal
2015-04-06 16:07:39 +02:00
Kevin Le Brun 6cd8258651 Fix for OS X USR1 signal
It seems that signals code for Linux and OS X (FreeBSD) are different. I
rely on `syscall.SIGUSR1` which should be cross-compatible.

Tested with `kill -usr1 <pid>` on OS X 10.10
2015-04-05 23:21:40 +02:00
Adnan Hajdarević fb71ea0fae Update README.md 2015-03-31 22:24:27 +02:00
Adnan Hajdarević aeacb6dac7 Merge pull request #24 from adnanh/development
added hook reload on USR1 signal
2015-03-31 22:14:47 +02:00
Adnan Hajdarevic 1039151a16 added hook reload on USR1 signal 2015-03-31 22:10:35 +02:00
Adnan Hajdarević db928228c8 bumping up the minor version 2015-03-30 15:33:15 +02:00
Adnan Hajdarević 6896a34aab Merge pull request #21 from kevinlebrun/master
Allow charset to be defined in Content-Type header
2015-03-30 00:22:29 +02:00
Kevin Le Brun 5f853d8aba Allow charset to be defined in Content-Type header
The payload couldn't be parsed when charset was present in the
`Content-Type` header. The content type should begin with the MIME type
so we now check if the content type starts with `application/json` or
`application/x-www-form-urlencoded`.

This closes #20
2015-03-29 20:01:56 +02:00
Adnan Hajdarević 12c48f87cb Merge pull request #19 from moorereason/test-json
Add tests for ParseJSONParameters
2015-03-21 10:28:20 +01:00
Adnan Hajdarević acf38c3210 Merge pull request #18 from moorereason/extract-tests
Add new tests for ExtractParameter
2015-03-21 10:26:36 +01:00
Cameron Moore d3f368cb8f Add tests for ParseJSONParameters
This commit adds 100% coverage to Hook.ParaseJSONParameters.
2015-03-20 23:22:30 -05:00
Cameron Moore 943bc258f7 Add new tests for ExtractParameter
This commit regains 100% coverage for ExtractParameter after changes
from commit 688483d.
2015-03-20 22:43:39 -05:00
6 changed files with 478 additions and 47 deletions
+4
View File
@@ -1,3 +1,5 @@
[![Join the chat at https://gitter.im/adnanh/webhook](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/adnanh/webhook?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
# What is webhook?
[webhook](https://github.com/adnanh/webhook/) is a lightweight configurable tool written in Go, that allows you to easily create HTTP endpoints (hooks) on your server, which you can use to execute configured commands. You can also pass data from the HTTP request (such as headers, payload or query variables) to your commands. [webhook](https://github.com/adnanh/webhook/) also allows you to specify rules which have to be satisfied in order for the hook to be triggered.
@@ -62,6 +64,8 @@ Check out [Hook examples page](https://github.com/adnanh/webhook/wiki/Hook-Examp
# Contributing
Any form of contribution is welcome and highly appreciated.
Big thanks to [all the current contributors](https://github.com/adnanh/webhook/graphs/contributors) for their contributions!
# License
The MIT License (MIT)
+63 -4
View File
@@ -16,9 +16,13 @@ import (
// Constants used to specify the parameter source
const (
SourceHeader string = "header"
SourceQuery string = "url"
SourcePayload string = "payload"
SourceHeader string = "header"
SourceQuery string = "url"
SourcePayload string = "payload"
SourceString string = "string"
SourceEntirePayload string = "entire-payload"
SourceEntireQuery string = "entire-query"
SourceEntireHeaders string = "entire-headers"
)
// CheckPayloadSignature calculates and verifies SHA1 signature of the given payload
@@ -148,6 +152,32 @@ func (ha *Argument) Get(headers, query, payload *map[string]interface{}) (string
source = query
case SourcePayload:
source = payload
case SourceString:
return ha.Name, true
case SourceEntirePayload:
r, err := json.Marshal(payload)
if err != nil {
return "", false
}
return string(r), true
case SourceEntireHeaders:
r, err := json.Marshal(headers)
if err != nil {
return "", false
}
return string(r), true
case SourceEntireQuery:
r, err := json.Marshal(query)
if err != nil {
return "", false
}
return string(r), true
}
if source != nil {
@@ -163,6 +193,7 @@ type Hook struct {
ExecuteCommand string `json:"execute-command"`
CommandWorkingDirectory string `json:"command-working-directory"`
ResponseMessage string `json:"response-message"`
CaptureCommandOutput bool `json:"include-command-output-in-response"`
PassArgumentsToCommand []Argument `json:"pass-arguments-to-command"`
JSONStringParameters []Argument `json:"parse-parameters-as-json"`
TriggerRule *Rules `json:"trigger-rule"`
@@ -194,7 +225,11 @@ func (h *Hook) ParseJSONParameters(headers, query, payload *map[string]interface
source = query
}
ReplaceParameter(h.JSONStringParameters[i].Name, source, newArg)
if source != nil {
ReplaceParameter(h.JSONStringParameters[i].Name, source, newArg)
} else {
log.Printf("invalid source for argument %+v\n", h.JSONStringParameters[i])
}
}
} else {
log.Printf("couldn't retrieve argument for %+v\n", h.JSONStringParameters[i])
@@ -253,6 +288,23 @@ func (h *Hooks) Match(id string) *Hook {
return nil
}
// MatchAll iterates through Hooks and returns all of the hooks that match the
// given ID, if no hook matches the given ID, nil is returned
func (h *Hooks) MatchAll(id string) []*Hook {
matchedHooks := make([]*Hook, 0)
for i := range *h {
if (*h)[i].ID == id {
matchedHooks = append(matchedHooks, &(*h)[i])
}
}
if len(matchedHooks) > 0 {
return matchedHooks
}
return nil
}
// Rules is a structure that contains one of the valid rule types
type Rules struct {
And *AndRule `json:"and"`
@@ -361,3 +413,10 @@ func (r MatchRule) Evaluate(headers, query, payload *map[string]interface{}, bod
}
return false
}
// CommandStatusResponse type encapsulates the executed command exit code, message, stdout and stderr
type CommandStatusResponse struct {
ResponseMessage string `json:"message"`
Output string `json:"output"`
Error string `json:"error"`
}
+28
View File
@@ -48,6 +48,8 @@ var extractParameterTests = []struct {
{"a.500.b", map[string]interface{}{"a": map[string]interface{}{"b": "z"}}, "", false}, // non-existent slice
{"a.501.b", map[string]interface{}{"a": []interface{}{map[string]interface{}{"b": "y"}, map[string]interface{}{"b": "z"}}}, "", false}, // non-existent slice index
{"a.502.b", map[string]interface{}{"a": []interface{}{}}, "", false}, // non-existent slice index
{"a.b.503", map[string]interface{}{"a": map[string]interface{}{"b": []interface{}{"x", "y", "z"}}}, "", false}, // trailing, non-existent slice index
{"a.b", interface{}("a"), "", false}, // non-map, non-slice input
}
func TestExtractParameter(t *testing.T) {
@@ -68,6 +70,7 @@ var argumentGetTests = []struct {
{"header", "a", &map[string]interface{}{"a": "z"}, nil, nil, "z", true},
{"url", "a", nil, &map[string]interface{}{"a": "z"}, nil, "z", true},
{"payload", "a", nil, nil, &map[string]interface{}{"a": "z"}, "z", true},
{"string", "a", nil, nil, &map[string]interface{}{"a": "z"}, "a", true},
// failures
{"header", "a", nil, &map[string]interface{}{"a": "z"}, &map[string]interface{}{"a": "z"}, "", false}, // nil headers
{"url", "a", &map[string]interface{}{"a": "z"}, nil, &map[string]interface{}{"a": "z"}, "", false}, // nil query
@@ -85,6 +88,31 @@ func TestArgumentGet(t *testing.T) {
}
}
var hookParseJSONParametersTests = []struct {
params []Argument
headers, query, payload *map[string]interface{}
rheaders, rquery, rpayload *map[string]interface{}
}{
{[]Argument{Argument{"header", "a"}}, &map[string]interface{}{"a": `{"b": "y"}`}, nil, nil, &map[string]interface{}{"a": map[string]interface{}{"b": "y"}}, nil, nil},
{[]Argument{Argument{"url", "a"}}, nil, &map[string]interface{}{"a": `{"b": "y"}`}, nil, nil, &map[string]interface{}{"a": map[string]interface{}{"b": "y"}}, nil},
{[]Argument{Argument{"payload", "a"}}, nil, nil, &map[string]interface{}{"a": `{"b": "y"}`}, nil, nil, &map[string]interface{}{"a": map[string]interface{}{"b": "y"}}},
{[]Argument{Argument{"header", "z"}}, &map[string]interface{}{"z": `{}`}, nil, nil, &map[string]interface{}{"z": map[string]interface{}{}}, nil, nil},
// failures
{[]Argument{Argument{"header", "z"}}, &map[string]interface{}{"z": ``}, nil, nil, &map[string]interface{}{"z": ``}, nil, nil}, // empty string
{[]Argument{Argument{"header", "y"}}, &map[string]interface{}{"X": `{}`}, nil, nil, &map[string]interface{}{"X": `{}`}, nil, nil}, // missing parameter
{[]Argument{Argument{"string", "z"}}, &map[string]interface{}{"z": ``}, nil, nil, &map[string]interface{}{"z": ``}, nil, nil}, // invalid argument source
}
func TestHookParseJSONParameters(t *testing.T) {
for _, tt := range hookParseJSONParametersTests {
h := &Hook{JSONStringParameters: tt.params}
h.ParseJSONParameters(tt.headers, tt.query, tt.payload)
if !reflect.DeepEqual(tt.headers, tt.rheaders) {
t.Errorf("failed to parse %v:\nexpected %#v,\ngot %#v", tt.params, *tt.rheaders, *tt.headers)
}
}
}
var hookExtractCommandArgumentsTests = []struct {
exec string
args []Argument
+1
View File
@@ -3,6 +3,7 @@
"id": "webhook",
"execute-command": "/home/adnan/redeploy-go-webhook.sh",
"command-working-directory": "/home/adnan/go",
"response-message": "I got the payload!",
"pass-arguments-to-command":
[
{
+99 -43
View File
@@ -1,3 +1,5 @@
//+build !windows
package main
import (
@@ -10,7 +12,9 @@ import (
"net/url"
"os"
"os/exec"
"os/signal"
"strings"
"syscall"
"github.com/adnanh/webhook/hook"
@@ -21,7 +25,7 @@ import (
)
const (
version = "2.3.0"
version = "2.3.4"
)
var (
@@ -36,6 +40,7 @@ var (
key = flag.String("key", "key.pem", "path to the HTTPS certificate private key pem file")
watcher *fsnotify.Watcher
signals chan os.Signal
hooks hook.Hooks
)
@@ -54,6 +59,14 @@ func init() {
log.Println("version " + version + " starting")
// set os signal watcher
log.Printf("setting up os signal watcher\n")
signals = make(chan os.Signal, 1)
signal.Notify(signals, syscall.SIGUSR1)
go watchForSignals()
// load and parse hooks
log.Printf("attempting to load hooks from %s\n", *hooksFilePath)
@@ -131,10 +144,10 @@ func main() {
func hookHandler(w http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["id"]
hook := hooks.Match(id)
matchedHooks := hooks.MatchAll(id)
if hook != nil {
log.Printf("%s got matched\n", id)
if matchedHooks != nil {
log.Printf("%s got matched (%d time(s))\n", id, len(matchedHooks))
body, err := ioutil.ReadAll(r.Body)
if err != nil {
@@ -152,7 +165,7 @@ func hookHandler(w http.ResponseWriter, r *http.Request) {
contentType := r.Header.Get("Content-Type")
if contentType == "application/json" {
if strings.Contains(contentType, "json") {
decoder := json.NewDecoder(strings.NewReader(string(body)))
decoder.UseNumber()
@@ -161,7 +174,7 @@ func hookHandler(w http.ResponseWriter, r *http.Request) {
if err != nil {
log.Printf("error parsing JSON payload %+v\n", err)
}
} else if contentType == "application/x-www-form-urlencoded" {
} else if strings.Contains(contentType, "form") {
fd, err := url.ParseQuery(string(body))
if err != nil {
log.Printf("error parsing form payload %+v\n", err)
@@ -170,39 +183,84 @@ func hookHandler(w http.ResponseWriter, r *http.Request) {
}
}
hook.ParseJSONParameters(&headers, &query, &payload)
// handle hook
go handleHook(hook, &headers, &query, &payload, &body)
for _, h := range matchedHooks {
h.ParseJSONParameters(&headers, &query, &payload)
if h.TriggerRule == nil || h.TriggerRule != nil && h.TriggerRule.Evaluate(&headers, &query, &payload, &body) {
log.Printf("%s hook triggered successfully\n", h.ID)
// send the hook defined response message
fmt.Fprintf(w, hook.ResponseMessage)
if h.CaptureCommandOutput {
response := handleHook(h, &headers, &query, &payload, &body)
fmt.Fprintf(w, response)
} else {
go handleHook(h, &headers, &query, &payload, &body)
fmt.Fprintf(w, h.ResponseMessage)
}
return
}
}
// if none of the hooks got triggered
log.Printf("%s got matched (%d time(s)), but didn't get triggered because the trigger rules were not satisfied\n", matchedHooks[0].ID, len(matchedHooks))
w.WriteHeader(http.StatusBadRequest)
fmt.Fprintf(w, "Hook rules were not satisfied.")
} else {
w.WriteHeader(http.StatusNotFound)
fmt.Fprintf(w, "Hook not found.")
}
}
func handleHook(hook *hook.Hook, headers, query, payload *map[string]interface{}, body *[]byte) {
if hook.TriggerRule == nil || hook.TriggerRule != nil && hook.TriggerRule.Evaluate(headers, query, payload, body) {
log.Printf("%s hook triggered successfully\n", hook.ID)
func handleHook(h *hook.Hook, headers, query, payload *map[string]interface{}, body *[]byte) string {
cmd := exec.Command(h.ExecuteCommand)
cmd.Args = h.ExtractCommandArguments(headers, query, payload)
cmd.Dir = h.CommandWorkingDirectory
cmd := exec.Command(hook.ExecuteCommand)
cmd.Args = hook.ExtractCommandArguments(headers, query, payload)
cmd.Dir = hook.CommandWorkingDirectory
log.Printf("executing %s (%s) with arguments %s using %s as cwd\n", h.ExecuteCommand, cmd.Path, cmd.Args, cmd.Dir)
log.Printf("executing %s (%s) with arguments %s using %s as cwd\n", hook.ExecuteCommand, cmd.Path, cmd.Args, cmd.Dir)
out, err := cmd.CombinedOutput()
out, err := cmd.Output()
log.Printf("command output: %s\n", out)
log.Printf("stdout: %s\n", out)
var errorResponse string
if err != nil {
log.Printf("stderr: %+v\n", err)
}
log.Printf("finished handling %s\n", hook.ID)
if err != nil {
log.Printf("error occurred: %+v\n", err)
errorResponse = fmt.Sprintf("%+v", err)
}
log.Printf("finished handling %s\n", h.ID)
var response []byte
response, err = json.Marshal(&hook.CommandStatusResponse{ResponseMessage: h.ResponseMessage, Output: string(out), Error: errorResponse})
if err != nil {
log.Printf("error marshalling response: %+v", err)
return h.ResponseMessage
}
return string(response)
}
func reloadHooks() {
newHooks := hook.Hooks{}
// parse and swap
log.Printf("attempting to reload hooks from %s\n", *hooksFilePath)
err := newHooks.LoadFromFile(*hooksFilePath)
if err != nil {
log.Printf("couldn't load hooks from file! %+v\n", err)
} else {
log.Printf("%s hook did not get triggered\n", hook.ID)
log.Printf("loaded %d hook(s) from file\n", len(hooks))
for _, hook := range hooks {
log.Printf("\t> %s\n", hook.ID)
}
hooks = newHooks
}
}
@@ -213,24 +271,7 @@ func watchForFileChange() {
if event.Op&fsnotify.Write == fsnotify.Write {
log.Println("hooks file modified")
newHooks := hook.Hooks{}
// parse and swap
log.Printf("attempting to reload hooks from %s\n", *hooksFilePath)
err := newHooks.LoadFromFile(*hooksFilePath)
if err != nil {
log.Printf("couldn't load hooks from file! %+v\n", err)
} else {
log.Printf("loaded %d hook(s) from file\n", len(hooks))
for _, hook := range hooks {
log.Printf("\t> %s\n", hook.ID)
}
hooks = newHooks
}
reloadHooks()
}
case err := <-(*watcher).Errors:
log.Println("watcher error:", err)
@@ -238,6 +279,21 @@ func watchForFileChange() {
}
}
func watchForSignals() {
log.Println("os signal watcher ready")
for {
sig := <-signals
if sig == syscall.SIGUSR1 {
log.Println("caught USR1 signal")
reloadHooks()
} else {
log.Printf("caught unhandled signal %+v\n", sig)
}
}
}
// valuesToMap converts map[string][]string to a map[string]string object
func valuesToMap(values map[string][]string) map[string]interface{} {
ret := make(map[string]interface{})
+283
View File
@@ -0,0 +1,283 @@
//+build !windows
package main
import (
"encoding/json"
"flag"
"fmt"
"io/ioutil"
"log"
"net/http"
"net/url"
"os"
"os/exec"
"strings"
"github.com/adnanh/webhook/hook"
"github.com/codegangsta/negroni"
"github.com/gorilla/mux"
fsnotify "gopkg.in/fsnotify.v1"
)
const (
version = "2.3.4"
)
var (
ip = flag.String("ip", "", "ip the webhook should serve hooks on")
port = flag.Int("port", 9000, "port the webhook should serve hooks on")
verbose = flag.Bool("verbose", false, "show verbose output")
hotReload = flag.Bool("hotreload", false, "watch hooks file for changes and reload them automatically")
hooksFilePath = flag.String("hooks", "hooks.json", "path to the json file containing defined hooks the webhook should serve")
hooksURLPrefix = flag.String("urlprefix", "hooks", "url prefix to use for served hooks (protocol://yourserver:port/PREFIX/:hook-id)")
secure = flag.Bool("secure", false, "use HTTPS instead of HTTP")
cert = flag.String("cert", "cert.pem", "path to the HTTPS certificate pem file")
key = flag.String("key", "key.pem", "path to the HTTPS certificate private key pem file")
watcher *fsnotify.Watcher
signals chan os.Signal
hooks hook.Hooks
)
func init() {
hooks = hook.Hooks{}
flag.Parse()
log.SetPrefix("[webhook] ")
log.SetFlags(log.Ldate | log.Ltime)
if !*verbose {
log.SetOutput(ioutil.Discard)
}
log.Println("version " + version + " starting")
// load and parse hooks
log.Printf("attempting to load hooks from %s\n", *hooksFilePath)
err := hooks.LoadFromFile(*hooksFilePath)
if err != nil {
log.Printf("couldn't load hooks from file! %+v\n", err)
} else {
log.Printf("loaded %d hook(s) from file\n", len(hooks))
for _, hook := range hooks {
log.Printf("\t> %s\n", hook.ID)
}
}
}
func main() {
if *hotReload {
// set up file watcher
log.Printf("setting up file watcher for %s\n", *hooksFilePath)
var err error
watcher, err = fsnotify.NewWatcher()
if err != nil {
log.Fatal("error creating file watcher instance", err)
}
defer watcher.Close()
go watchForFileChange()
err = watcher.Add(*hooksFilePath)
if err != nil {
log.Fatal("error adding hooks file to the watcher", err)
}
}
l := negroni.NewLogger()
l.Logger = log.New(os.Stdout, "[webhook] ", log.Ldate|log.Ltime)
negroniRecovery := &negroni.Recovery{
Logger: l.Logger,
PrintStack: true,
StackAll: false,
StackSize: 1024 * 8,
}
n := negroni.New(negroniRecovery, l)
router := mux.NewRouter()
var hooksURL string
if *hooksURLPrefix == "" {
hooksURL = "/{id}"
} else {
hooksURL = "/" + *hooksURLPrefix + "/{id}"
}
router.HandleFunc(hooksURL, hookHandler)
n.UseHandler(router)
if *secure {
log.Printf("starting secure (https) webhook on %s:%d", *ip, *port)
log.Fatal(http.ListenAndServeTLS(fmt.Sprintf("%s:%d", *ip, *port), *cert, *key, n))
} else {
log.Printf("starting insecure (http) webhook on %s:%d", *ip, *port)
log.Fatal(http.ListenAndServe(fmt.Sprintf("%s:%d", *ip, *port), n))
}
}
func hookHandler(w http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["id"]
matchedHooks := hooks.MatchAll(id)
if matchedHooks != nil {
log.Printf("%s got matched (%d time(s))\n", id, len(matchedHooks))
body, err := ioutil.ReadAll(r.Body)
if err != nil {
log.Printf("error reading the request body. %+v\n", err)
}
// parse headers
headers := valuesToMap(r.Header)
// parse query variables
query := valuesToMap(r.URL.Query())
// parse body
var payload map[string]interface{}
contentType := r.Header.Get("Content-Type")
if strings.Contains(contentType, "json") {
decoder := json.NewDecoder(strings.NewReader(string(body)))
decoder.UseNumber()
err := decoder.Decode(&payload)
if err != nil {
log.Printf("error parsing JSON payload %+v\n", err)
}
} else if strings.Contains(contentType, "form") {
fd, err := url.ParseQuery(string(body))
if err != nil {
log.Printf("error parsing form payload %+v\n", err)
} else {
payload = valuesToMap(fd)
}
}
// handle hook
for _, h := range matchedHooks {
h.ParseJSONParameters(&headers, &query, &payload)
if h.TriggerRule == nil || h.TriggerRule != nil && h.TriggerRule.Evaluate(&headers, &query, &payload, &body) {
log.Printf("%s hook triggered successfully\n", h.ID)
if h.CaptureCommandOutput {
response := handleHook(h, &headers, &query, &payload, &body)
fmt.Fprintf(w, response)
} else {
go handleHook(h, &headers, &query, &payload, &body)
fmt.Fprintf(w, h.ResponseMessage)
}
return
}
}
// if none of the hooks got triggered
log.Printf("%s got matched (%d time(s)), but didn't get triggered because the trigger rules were not satisfied\n", matchedHooks[0].ID, len(matchedHooks))
w.WriteHeader(http.StatusBadRequest)
fmt.Fprintf(w, "Hook rules were not satisfied.")
} else {
w.WriteHeader(http.StatusNotFound)
fmt.Fprintf(w, "Hook not found.")
}
}
func handleHook(h *hook.Hook, headers, query, payload *map[string]interface{}, body *[]byte) string {
cmd := exec.Command(h.ExecuteCommand)
cmd.Args = h.ExtractCommandArguments(headers, query, payload)
cmd.Dir = h.CommandWorkingDirectory
log.Printf("executing %s (%s) with arguments %s using %s as cwd\n", h.ExecuteCommand, cmd.Path, cmd.Args, cmd.Dir)
out, err := cmd.CombinedOutput()
log.Printf("command output: %s\n", out)
var errorResponse string
if err != nil {
log.Printf("error occurred: %+v\n", err)
errorResponse = fmt.Sprintf("%+v", err)
}
log.Printf("finished handling %s\n", h.ID)
var response []byte
response, err = json.Marshal(&hook.CommandStatusResponse{ResponseMessage: h.ResponseMessage, Output: string(out), Error: errorResponse})
if err != nil {
log.Printf("error marshalling response: %+v", err)
return h.ResponseMessage
}
return string(response)
}
func reloadHooks() {
newHooks := hook.Hooks{}
// parse and swap
log.Printf("attempting to reload hooks from %s\n", *hooksFilePath)
err := newHooks.LoadFromFile(*hooksFilePath)
if err != nil {
log.Printf("couldn't load hooks from file! %+v\n", err)
} else {
log.Printf("loaded %d hook(s) from file\n", len(hooks))
for _, hook := range hooks {
log.Printf("\t> %s\n", hook.ID)
}
hooks = newHooks
}
}
func watchForFileChange() {
for {
select {
case event := <-(*watcher).Events:
if event.Op&fsnotify.Write == fsnotify.Write {
log.Println("hooks file modified")
reloadHooks()
}
case err := <-(*watcher).Errors:
log.Println("watcher error:", err)
}
}
}
// valuesToMap converts map[string][]string to a map[string]string object
func valuesToMap(values map[string][]string) map[string]interface{} {
ret := make(map[string]interface{})
for key, value := range values {
if len(value) > 0 {
ret[key] = value[0]
}
}
return ret
}