mirror of
https://github.com/adnanh/webhook.git
synced 2026-07-27 01:29:16 +08:00
Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b66216675a | |||
| ecbcf11153 | |||
| 7d525cf317 | |||
| e83d7029ff | |||
| 36c2c692d6 | |||
| 86cef3e421 | |||
| 75cf8952be | |||
| c53596df59 | |||
| cc0d9b2cba | |||
| c6530b17e7 | |||
| 1943c5311f | |||
| 3b59539a33 | |||
| 923b0c6daa | |||
| 8530255ae6 | |||
| 032c74451f | |||
| 1c50853d8d | |||
| b5ed4cbea7 | |||
| b6d176705e | |||
| 421fc2cbcd | |||
| 10d65dd2fd | |||
| 54a9dbe1d6 | |||
| 30baec91df |
@@ -0,0 +1,29 @@
|
||||
OS = darwin freebsd linux openbsd windows
|
||||
ARCHS = 386 arm amd64 arm64
|
||||
|
||||
all: build release
|
||||
|
||||
build: deps
|
||||
go build
|
||||
|
||||
release: clean deps
|
||||
@for arch in $(ARCHS);\
|
||||
do \
|
||||
for os in $(OS);\
|
||||
do \
|
||||
echo "Building $$os-$$arch"; \
|
||||
mkdir -p build/webhook-$$os-$$arch/; \
|
||||
GOOS=$$os GOARCH=$$arch go build -o build/webhook-$$os-$$arch/webhook; \
|
||||
tar cz -C build -f build/webhook-$$os-$$arch.tar.gz webhook-$$os-$$arch; \
|
||||
done \
|
||||
done
|
||||
|
||||
test: deps
|
||||
go test ./...
|
||||
|
||||
deps:
|
||||
go get -d -v -t ./...
|
||||
|
||||
clean:
|
||||
rm -rf build
|
||||
rm -f webhook
|
||||
@@ -19,12 +19,19 @@ Everything else is the responsibility of the command's author.
|
||||
---
|
||||
|
||||
# Getting started
|
||||
## Installation
|
||||
### Building from source
|
||||
To get started, first make sure you've properly set up your [Golang](http://golang.org/doc/install) environment and then run the
|
||||
```bash
|
||||
$ go get github.com/adnanh/webhook
|
||||
```
|
||||
to get the latest version of the [webhook](https://github.com/adnanh/webhook/).
|
||||
|
||||
### Using package manager
|
||||
#### Debian "sid"
|
||||
If you are using unstable version of Debian linux ("sid"), you can install webhook using `apt-get install webhook` which will install community packaged version (thanks [@freeekanayaka](https://github.com/freeekanayaka)) from https://packages.debian.org/sid/webhook
|
||||
|
||||
## Configuration
|
||||
Next step is to define some hooks you want [webhook](https://github.com/adnanh/webhook/) to serve. Begin by creating an empty file named `hooks.json`. This file will contain an array of hooks the [webhook](https://github.com/adnanh/webhook/) will serve. Check [Hook definition page](https://github.com/adnanh/webhook/wiki/Hook-Definition) to see the detailed description of what properties a hook can contain, and how to use them.
|
||||
|
||||
Let's define a simple hook named `redeploy-webhook` that will run a redeploy script located in `/var/scripts/redeploy.sh`.
|
||||
|
||||
+41
-39
@@ -8,6 +8,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/textproto"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
@@ -18,6 +19,7 @@ import (
|
||||
const (
|
||||
SourceHeader string = "header"
|
||||
SourceQuery string = "url"
|
||||
SourceQueryAlias string = "query"
|
||||
SourcePayload string = "payload"
|
||||
SourceString string = "string"
|
||||
SourceEntirePayload string = "entire-payload"
|
||||
@@ -93,7 +95,7 @@ func CheckPayloadSignature(payload []byte, secret string, signature string) (str
|
||||
expectedMAC := hex.EncodeToString(mac.Sum(nil))
|
||||
|
||||
if !hmac.Equal([]byte(signature), []byte(expectedMAC)) {
|
||||
return expectedMAC, &SignatureError{expectedMAC}
|
||||
return expectedMAC, &SignatureError{signature}
|
||||
}
|
||||
return expectedMAC, err
|
||||
}
|
||||
@@ -205,11 +207,13 @@ type Argument struct {
|
||||
// based on the Argument's source
|
||||
func (ha *Argument) Get(headers, query, payload *map[string]interface{}) (string, bool) {
|
||||
var source *map[string]interface{}
|
||||
key := ha.Name
|
||||
|
||||
switch ha.Source {
|
||||
case SourceHeader:
|
||||
source = headers
|
||||
case SourceQuery:
|
||||
key = textproto.CanonicalMIMEHeaderKey(ha.Name)
|
||||
case SourceQuery, SourceQueryAlias:
|
||||
source = query
|
||||
case SourcePayload:
|
||||
source = payload
|
||||
@@ -242,7 +246,7 @@ func (ha *Argument) Get(headers, query, payload *map[string]interface{}) (string
|
||||
}
|
||||
|
||||
if source != nil {
|
||||
return ExtractParameterAsString(ha.Name, *source)
|
||||
return ExtractParameterAsString(key, *source)
|
||||
}
|
||||
|
||||
return "", false
|
||||
@@ -300,7 +304,9 @@ type Hook struct {
|
||||
|
||||
// ParseJSONParameters decodes specified arguments to JSON objects and replaces the
|
||||
// string with the newly created object
|
||||
func (h *Hook) ParseJSONParameters(headers, query, payload *map[string]interface{}) error {
|
||||
func (h *Hook) ParseJSONParameters(headers, query, payload *map[string]interface{}) []error {
|
||||
var errors = make([]error, 0)
|
||||
|
||||
for i := range h.JSONStringParameters {
|
||||
if arg, ok := h.JSONStringParameters[i].Get(headers, query, payload); ok {
|
||||
var newArg map[string]interface{}
|
||||
@@ -311,7 +317,8 @@ func (h *Hook) ParseJSONParameters(headers, query, payload *map[string]interface
|
||||
err := decoder.Decode(&newArg)
|
||||
|
||||
if err != nil {
|
||||
return &ParseError{err}
|
||||
errors = append(errors, &ParseError{err})
|
||||
continue
|
||||
}
|
||||
|
||||
var source *map[string]interface{}
|
||||
@@ -321,27 +328,38 @@ func (h *Hook) ParseJSONParameters(headers, query, payload *map[string]interface
|
||||
source = headers
|
||||
case SourcePayload:
|
||||
source = payload
|
||||
case SourceQuery:
|
||||
case SourceQuery, SourceQueryAlias:
|
||||
source = query
|
||||
}
|
||||
|
||||
if source != nil {
|
||||
ReplaceParameter(h.JSONStringParameters[i].Name, source, newArg)
|
||||
key := h.JSONStringParameters[i].Name
|
||||
|
||||
if h.JSONStringParameters[i].Source == SourceHeader {
|
||||
key = textproto.CanonicalMIMEHeaderKey(h.JSONStringParameters[i].Name)
|
||||
}
|
||||
|
||||
ReplaceParameter(key, source, newArg)
|
||||
} else {
|
||||
return &SourceError{h.JSONStringParameters[i]}
|
||||
errors = append(errors, &SourceError{h.JSONStringParameters[i]})
|
||||
}
|
||||
} else {
|
||||
return &ArgumentError{h.JSONStringParameters[i]}
|
||||
errors = append(errors, &ArgumentError{h.JSONStringParameters[i]})
|
||||
}
|
||||
}
|
||||
|
||||
if len(errors) > 0 {
|
||||
return errors
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExtractCommandArguments creates a list of arguments, based on the
|
||||
// PassArgumentsToCommand property that is ready to be used with exec.Command()
|
||||
func (h *Hook) ExtractCommandArguments(headers, query, payload *map[string]interface{}) ([]string, error) {
|
||||
func (h *Hook) ExtractCommandArguments(headers, query, payload *map[string]interface{}) ([]string, []error) {
|
||||
var args = make([]string, 0)
|
||||
var errors = make([]error, 0)
|
||||
|
||||
args = append(args, h.ExecuteCommand)
|
||||
|
||||
@@ -350,33 +368,41 @@ func (h *Hook) ExtractCommandArguments(headers, query, payload *map[string]inter
|
||||
args = append(args, arg)
|
||||
} else {
|
||||
args = append(args, "")
|
||||
return args, &ArgumentError{h.PassArgumentsToCommand[i]}
|
||||
errors = append(errors, &ArgumentError{h.PassArgumentsToCommand[i]})
|
||||
}
|
||||
}
|
||||
|
||||
if len(errors) > 0 {
|
||||
return args, errors
|
||||
}
|
||||
|
||||
return args, nil
|
||||
}
|
||||
|
||||
// ExtractCommandArgumentsForEnv creates a list of arguments in key=value
|
||||
// format, based on the PassEnvironmentToCommand property that is ready to be used
|
||||
// with exec.Command().
|
||||
func (h *Hook) ExtractCommandArgumentsForEnv(headers, query, payload *map[string]interface{}) ([]string, error) {
|
||||
func (h *Hook) ExtractCommandArgumentsForEnv(headers, query, payload *map[string]interface{}) ([]string, []error) {
|
||||
var args = make([]string, 0)
|
||||
|
||||
var errors = make([]error, 0)
|
||||
for i := range h.PassEnvironmentToCommand {
|
||||
if arg, ok := h.PassEnvironmentToCommand[i].Get(headers, query, payload); ok {
|
||||
if h.PassEnvironmentToCommand[i].EnvName != "" {
|
||||
// first try to use the EnvName if specified
|
||||
args = append(args, EnvNamespace+h.PassEnvironmentToCommand[i].EnvName+"="+arg)
|
||||
args = append(args, h.PassEnvironmentToCommand[i].EnvName+"="+arg)
|
||||
} else {
|
||||
// then fallback on the name
|
||||
args = append(args, EnvNamespace+h.PassEnvironmentToCommand[i].Name+"="+arg)
|
||||
}
|
||||
} else {
|
||||
return args, &ArgumentError{h.PassEnvironmentToCommand[i]}
|
||||
errors = append(errors, &ArgumentError{h.PassEnvironmentToCommand[i]})
|
||||
}
|
||||
}
|
||||
|
||||
if len(errors) > 0 {
|
||||
return args, errors
|
||||
}
|
||||
|
||||
return args, nil
|
||||
}
|
||||
|
||||
@@ -412,23 +438,6 @@ 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 {
|
||||
var matchedHooks []*Hook
|
||||
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,omitempty"`
|
||||
@@ -538,10 +547,3 @@ func (r MatchRule) Evaluate(headers, query, payload *map[string]interface{}, bod
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// CommandStatusResponse type encapsulates the executed command exit code, message, stdout and stderr
|
||||
type CommandStatusResponse struct {
|
||||
ResponseMessage string `json:"message,omitempty"`
|
||||
Output string `json:"output,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
+6
-1
@@ -2,6 +2,7 @@ package hook
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -25,6 +26,10 @@ func TestCheckPayloadSignature(t *testing.T) {
|
||||
if (err == nil) != tt.ok || mac != tt.mac {
|
||||
t.Errorf("failed to check payload signature {%q, %q, %q}:\nexpected {mac:%#v, ok:%#v},\ngot {mac:%#v, ok:%#v}", tt.payload, tt.secret, tt.signature, tt.mac, tt.ok, mac, (err == nil))
|
||||
}
|
||||
|
||||
if err != nil && strings.Contains(err.Error(), tt.mac) {
|
||||
t.Errorf("error message should not disclose expected mac: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,7 +179,7 @@ var hookExtractCommandArgumentsForEnvTests = []struct {
|
||||
"test",
|
||||
[]Argument{Argument{"header", "a", "MYKEY"}},
|
||||
&map[string]interface{}{"a": "z"}, nil, nil,
|
||||
[]string{"HOOK_MYKEY=z"},
|
||||
[]string{"MYKEY=z"},
|
||||
true,
|
||||
},
|
||||
// failures
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
"id": "bitbucket",
|
||||
"execute-command": "{{ .Hookecho }}",
|
||||
"command-working-directory": "/",
|
||||
"include-command-output-in-response": true,
|
||||
"include-command-output-in-response": false,
|
||||
"response-message": "success",
|
||||
"parse-parameters-as-json": [
|
||||
{
|
||||
@@ -136,4 +136,3 @@
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
+72
-59
@@ -21,11 +21,11 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
version = "2.3.9"
|
||||
version = "2.6.0"
|
||||
)
|
||||
|
||||
var (
|
||||
ip = flag.String("ip", "", "ip the webhook should serve hooks on")
|
||||
ip = flag.String("ip", "0.0.0.0", "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")
|
||||
noPanic = flag.Bool("nopanic", false, "do not panic if hooks cannot be loaded when webhook is not running in verbose mode")
|
||||
@@ -35,6 +35,7 @@ var (
|
||||
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")
|
||||
justDisplayVersion = flag.Bool("version", false, "display webhook version and quit")
|
||||
|
||||
responseHeaders hook.ResponseHeaders
|
||||
|
||||
@@ -51,6 +52,11 @@ func main() {
|
||||
|
||||
flag.Parse()
|
||||
|
||||
if *justDisplayVersion {
|
||||
fmt.Println("webhook version " + version)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
log.SetPrefix("[webhook] ")
|
||||
log.SetFlags(log.Ldate | log.Ltime)
|
||||
|
||||
@@ -76,10 +82,16 @@ func main() {
|
||||
|
||||
log.Printf("couldn't load hooks from file! %+v\n", err)
|
||||
} else {
|
||||
log.Printf("loaded %d hook(s) from file\n", len(hooks))
|
||||
seenHooksIds := make(map[string]bool)
|
||||
|
||||
log.Printf("found %d hook(s) in file\n", len(hooks))
|
||||
|
||||
for _, hook := range hooks {
|
||||
log.Printf("\t> %s\n", hook.ID)
|
||||
if seenHooksIds[hook.ID] == true {
|
||||
log.Fatalf("error: hook with the id %s has already been loaded!\nplease check your hooks file for duplicate hooks ids!\n", hook.ID)
|
||||
}
|
||||
seenHooksIds[hook.ID] = true
|
||||
log.Printf("\tloaded: %s\n", hook.ID)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,10 +117,10 @@ func main() {
|
||||
}
|
||||
|
||||
l := negroni.NewLogger()
|
||||
l.Logger = log.New(os.Stderr, "[webhook] ", log.Ldate|log.Ltime)
|
||||
l.ALogger = log.New(os.Stderr, "[webhook] ", log.Ldate|log.Ltime)
|
||||
|
||||
negroniRecovery := &negroni.Recovery{
|
||||
Logger: l.Logger,
|
||||
Logger: l.ALogger,
|
||||
PrintStack: true,
|
||||
StackAll: false,
|
||||
StackSize: 1024 * 8,
|
||||
@@ -131,10 +143,10 @@ func main() {
|
||||
n.UseHandler(router)
|
||||
|
||||
if *secure {
|
||||
log.Printf("starting secure (https) webhook on %s:%d", *ip, *port)
|
||||
log.Printf("serving hooks on https://%s:%d%s", *ip, *port, hooksURL)
|
||||
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.Printf("serving hooks on http://%s:%d%s", *ip, *port, hooksURL)
|
||||
log.Fatal(http.ListenAndServe(fmt.Sprintf("%s:%d", *ip, *port), n))
|
||||
}
|
||||
|
||||
@@ -147,10 +159,8 @@ 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))
|
||||
if matchedHook := hooks.Match(id); matchedHook != nil {
|
||||
log.Printf("%s got matched\n", id)
|
||||
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
@@ -187,53 +197,53 @@ func hookHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// handle hook
|
||||
for _, h := range matchedHooks {
|
||||
|
||||
err := h.ParseJSONParameters(&headers, &query, &payload)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("error parsing JSON: %s", err)
|
||||
log.Printf(msg)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
fmt.Fprintf(w, msg)
|
||||
return
|
||||
if errors := matchedHook.ParseJSONParameters(&headers, &query, &payload); errors != nil {
|
||||
for _, err := range errors {
|
||||
log.Printf("error parsing JSON parameters: %s\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
var ok bool
|
||||
|
||||
if h.TriggerRule == nil {
|
||||
if matchedHook.TriggerRule == nil {
|
||||
ok = true
|
||||
} else {
|
||||
ok, err = h.TriggerRule.Evaluate(&headers, &query, &payload, &body)
|
||||
ok, err = matchedHook.TriggerRule.Evaluate(&headers, &query, &payload, &body)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("error evaluating hook: %s", err)
|
||||
log.Printf(msg)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
fmt.Fprintf(w, msg)
|
||||
fmt.Fprintf(w, "Error occurred while evaluating hook rules.")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if ok {
|
||||
log.Printf("%s hook triggered successfully\n", h.ID)
|
||||
log.Printf("%s hook triggered successfully\n", matchedHook.ID)
|
||||
|
||||
for _, responseHeader := range h.ResponseHeaders {
|
||||
for _, responseHeader := range matchedHook.ResponseHeaders {
|
||||
w.Header().Set(responseHeader.Name, responseHeader.Value)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
if matchedHook.CaptureCommandOutput {
|
||||
response, err := handleHook(matchedHook, &headers, &query, &payload, &body)
|
||||
|
||||
return
|
||||
if err != nil {
|
||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
fmt.Fprintf(w, "Error occurred while executing the hook's command. Please check your logs for more details.")
|
||||
} else {
|
||||
fmt.Fprintf(w, response)
|
||||
}
|
||||
} else {
|
||||
go handleHook(matchedHook, &headers, &query, &payload, &body)
|
||||
fmt.Fprintf(w, matchedHook.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))
|
||||
log.Printf("%s got matched, but didn't get triggered because the trigger rules were not satisfied\n", matchedHook.ID)
|
||||
|
||||
fmt.Fprintf(w, "Hook rules were not satisfied.")
|
||||
} else {
|
||||
@@ -242,48 +252,43 @@ func hookHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
func handleHook(h *hook.Hook, headers, query, payload *map[string]interface{}, body *[]byte) string {
|
||||
var err error
|
||||
func handleHook(h *hook.Hook, headers, query, payload *map[string]interface{}, body *[]byte) (string, error) {
|
||||
var errors []error
|
||||
|
||||
cmd := exec.Command(h.ExecuteCommand)
|
||||
cmd.Dir = h.CommandWorkingDirectory
|
||||
|
||||
cmd.Args, err = h.ExtractCommandArguments(headers, query, payload)
|
||||
if err != nil {
|
||||
log.Printf("error extracting command arguments: %s", err)
|
||||
cmd.Args, errors = h.ExtractCommandArguments(headers, query, payload)
|
||||
if errors != nil {
|
||||
for _, err := range errors {
|
||||
log.Printf("error extracting command arguments: %s\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
var envs []string
|
||||
envs, err = h.ExtractCommandArgumentsForEnv(headers, query, payload)
|
||||
if err != nil {
|
||||
log.Printf("error extracting command arguments for environment: %s", err)
|
||||
envs, errors = h.ExtractCommandArgumentsForEnv(headers, query, payload)
|
||||
|
||||
if errors != nil {
|
||||
for _, err := range errors {
|
||||
log.Printf("error extracting command arguments for environment: %s\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
cmd.Env = append(os.Environ(), envs...)
|
||||
|
||||
log.Printf("executing %s (%s) with arguments %q and environment %s using %s as cwd\n", h.ExecuteCommand, cmd.Path, cmd.Args, envs, cmd.Dir)
|
||||
|
||||
out, err := cmd.CombinedOutput()
|
||||
out, err := cmd.Output()
|
||||
|
||||
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)
|
||||
return string(out), err
|
||||
}
|
||||
|
||||
func reloadHooks() {
|
||||
@@ -297,10 +302,18 @@ func reloadHooks() {
|
||||
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))
|
||||
seenHooksIds := make(map[string]bool)
|
||||
|
||||
for _, hook := range hooks {
|
||||
log.Printf("\t> %s\n", hook.ID)
|
||||
log.Printf("found %d hook(s) in file\n", len(newHooks))
|
||||
|
||||
for _, hook := range newHooks {
|
||||
if seenHooksIds[hook.ID] == true {
|
||||
log.Printf("error: hook with the id %s has already been loaded!\nplease check your hooks file for duplicate hooks ids!", hook.ID)
|
||||
log.Println("reverting hooks back to the previous configuration")
|
||||
return
|
||||
}
|
||||
seenHooksIds[hook.ID] = true
|
||||
log.Printf("\tloaded: %s\n", hook.ID)
|
||||
}
|
||||
|
||||
hooks = newHooks
|
||||
|
||||
+11
-5
@@ -372,7 +372,9 @@ var hookHandlerTests = []struct {
|
||||
}`,
|
||||
false,
|
||||
http.StatusOK,
|
||||
`{"output":"arg: 1481a2de7b2a7d02428ad93446ab166be7793fbb lolwut@noway.biz\nenv: HOOK_head_commit.timestamp=2013-03-12T08:14:29-07:00\n"}`,
|
||||
`arg: 1481a2de7b2a7d02428ad93446ab166be7793fbb lolwut@noway.biz
|
||||
env: HOOK_head_commit.timestamp=2013-03-12T08:14:29-07:00
|
||||
`,
|
||||
},
|
||||
{
|
||||
"bitbucket", // bitbucket sends their payload using uriencoded params.
|
||||
@@ -381,7 +383,7 @@ var hookHandlerTests = []struct {
|
||||
`payload={"canon_url": "https://bitbucket.org","commits": [{"author": "marcus","branch": "master","files": [{"file": "somefile.py","type": "modified"}],"message": "Added some more things to somefile.py\n","node": "620ade18607a","parents": ["702c70160afc"],"raw_author": "Marcus Bertrand <marcus@somedomain.com>","raw_node": "620ade18607ac42d872b568bb92acaa9a28620e9","revision": null,"size": -1,"timestamp": "2012-05-30 05:58:56","utctimestamp": "2014-11-07 15:19:02+00:00"}],"repository": {"absolute_url": "/webhook/testing/","fork": false,"is_private": true,"name": "Project X","owner": "marcus","scm": "git","slug": "project-x","website": "https://atlassian.com/"},"user": "marcus"}`,
|
||||
true,
|
||||
http.StatusOK,
|
||||
`{"message":"success"}`,
|
||||
`success`,
|
||||
},
|
||||
{
|
||||
"gitlab",
|
||||
@@ -431,7 +433,8 @@ var hookHandlerTests = []struct {
|
||||
}`,
|
||||
false,
|
||||
http.StatusOK,
|
||||
`{"message":"success","output":"arg: b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327 John Smith john@example.com\n"}`,
|
||||
`arg: b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327 John Smith john@example.com
|
||||
`,
|
||||
},
|
||||
|
||||
{
|
||||
@@ -469,7 +472,9 @@ var hookHandlerTests = []struct {
|
||||
}`,
|
||||
false,
|
||||
http.StatusOK,
|
||||
`{"output":"arg: 1481a2de7b2a7d02428ad93446ab166be7793fbb lolwut@noway.biz\nenv: HOOK_head_commit.timestamp=2013-03-12T08:14:29-07:00\n"}`,
|
||||
`arg: 1481a2de7b2a7d02428ad93446ab166be7793fbb lolwut@noway.biz
|
||||
env: HOOK_head_commit.timestamp=2013-03-12T08:14:29-07:00
|
||||
`,
|
||||
},
|
||||
|
||||
{
|
||||
@@ -506,7 +511,8 @@ var hookHandlerTests = []struct {
|
||||
}`,
|
||||
false,
|
||||
http.StatusOK,
|
||||
`{"output":"arg: 1481a2de7b2a7d02428ad93446ab166be7793fbb lolwut@noway.biz\n"}`,
|
||||
`arg: 1481a2de7b2a7d02428ad93446ab166be7793fbb lolwut@noway.biz
|
||||
`,
|
||||
},
|
||||
|
||||
{"empty payload", "github", nil, `{}`, false, http.StatusOK, `Hook rules were not satisfied.`},
|
||||
|
||||
Reference in New Issue
Block a user