mirror of
https://github.com/adnanh/webhook.git
synced 2025-06-06 04:32:29 +00:00
commit
005e723b23
4 changed files with 159 additions and 57 deletions
60
hook/hook.go
60
hook/hook.go
|
@ -16,10 +16,13 @@ import (
|
||||||
|
|
||||||
// Constants used to specify the parameter source
|
// Constants used to specify the parameter source
|
||||||
const (
|
const (
|
||||||
SourceHeader string = "header"
|
SourceHeader string = "header"
|
||||||
SourceQuery string = "url"
|
SourceQuery string = "url"
|
||||||
SourcePayload string = "payload"
|
SourcePayload string = "payload"
|
||||||
SourceString string = "string"
|
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
|
// CheckPayloadSignature calculates and verifies SHA1 signature of the given payload
|
||||||
|
@ -151,6 +154,30 @@ func (ha *Argument) Get(headers, query, payload *map[string]interface{}) (string
|
||||||
source = payload
|
source = payload
|
||||||
case SourceString:
|
case SourceString:
|
||||||
return ha.Name, true
|
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 {
|
if source != nil {
|
||||||
|
@ -166,6 +193,7 @@ type Hook struct {
|
||||||
ExecuteCommand string `json:"execute-command"`
|
ExecuteCommand string `json:"execute-command"`
|
||||||
CommandWorkingDirectory string `json:"command-working-directory"`
|
CommandWorkingDirectory string `json:"command-working-directory"`
|
||||||
ResponseMessage string `json:"response-message"`
|
ResponseMessage string `json:"response-message"`
|
||||||
|
CaptureCommandOutput bool `json:"include-command-output-in-response"`
|
||||||
PassArgumentsToCommand []Argument `json:"pass-arguments-to-command"`
|
PassArgumentsToCommand []Argument `json:"pass-arguments-to-command"`
|
||||||
JSONStringParameters []Argument `json:"parse-parameters-as-json"`
|
JSONStringParameters []Argument `json:"parse-parameters-as-json"`
|
||||||
TriggerRule *Rules `json:"trigger-rule"`
|
TriggerRule *Rules `json:"trigger-rule"`
|
||||||
|
@ -260,6 +288,23 @@ func (h *Hooks) Match(id string) *Hook {
|
||||||
return nil
|
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
|
// Rules is a structure that contains one of the valid rule types
|
||||||
type Rules struct {
|
type Rules struct {
|
||||||
And *AndRule `json:"and"`
|
And *AndRule `json:"and"`
|
||||||
|
@ -368,3 +413,10 @@ func (r MatchRule) Evaluate(headers, query, payload *map[string]interface{}, bod
|
||||||
}
|
}
|
||||||
return false
|
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"`
|
||||||
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
"id": "webhook",
|
"id": "webhook",
|
||||||
"execute-command": "/home/adnan/redeploy-go-webhook.sh",
|
"execute-command": "/home/adnan/redeploy-go-webhook.sh",
|
||||||
"command-working-directory": "/home/adnan/go",
|
"command-working-directory": "/home/adnan/go",
|
||||||
|
"response-message": "I got the payload!",
|
||||||
"pass-arguments-to-command":
|
"pass-arguments-to-command":
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
|
|
76
webhook.go
76
webhook.go
|
@ -25,7 +25,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
version = "2.3.3"
|
version = "2.3.4"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -144,10 +144,10 @@ func main() {
|
||||||
func hookHandler(w http.ResponseWriter, r *http.Request) {
|
func hookHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
id := mux.Vars(r)["id"]
|
id := mux.Vars(r)["id"]
|
||||||
|
|
||||||
hook := hooks.Match(id)
|
matchedHooks := hooks.MatchAll(id)
|
||||||
|
|
||||||
if hook != nil {
|
if matchedHooks != nil {
|
||||||
log.Printf("%s got matched\n", id)
|
log.Printf("%s got matched (%d time(s))\n", id, len(matchedHooks))
|
||||||
|
|
||||||
body, err := ioutil.ReadAll(r.Body)
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -165,7 +165,7 @@ func hookHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
contentType := r.Header.Get("Content-Type")
|
contentType := r.Header.Get("Content-Type")
|
||||||
|
|
||||||
if strings.HasPrefix(contentType, "application/json") {
|
if strings.Contains(contentType, "json") {
|
||||||
decoder := json.NewDecoder(strings.NewReader(string(body)))
|
decoder := json.NewDecoder(strings.NewReader(string(body)))
|
||||||
decoder.UseNumber()
|
decoder.UseNumber()
|
||||||
|
|
||||||
|
@ -174,7 +174,7 @@ func hookHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("error parsing JSON payload %+v\n", err)
|
log.Printf("error parsing JSON payload %+v\n", err)
|
||||||
}
|
}
|
||||||
} else if strings.HasPrefix(contentType, "application/x-www-form-urlencoded") {
|
} else if strings.Contains(contentType, "form") {
|
||||||
fd, err := url.ParseQuery(string(body))
|
fd, err := url.ParseQuery(string(body))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("error parsing form payload %+v\n", err)
|
log.Printf("error parsing form payload %+v\n", err)
|
||||||
|
@ -183,40 +183,64 @@ func hookHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
hook.ParseJSONParameters(&headers, &query, &payload)
|
|
||||||
|
|
||||||
// handle hook
|
// 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
|
if h.CaptureCommandOutput {
|
||||||
fmt.Fprintf(w, hook.ResponseMessage)
|
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 {
|
} else {
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
fmt.Fprintf(w, "Hook not found.")
|
fmt.Fprintf(w, "Hook not found.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleHook(hook *hook.Hook, headers, query, payload *map[string]interface{}, body *[]byte) {
|
func handleHook(h *hook.Hook, headers, query, payload *map[string]interface{}, body *[]byte) string {
|
||||||
if hook.TriggerRule == nil || hook.TriggerRule != nil && hook.TriggerRule.Evaluate(headers, query, payload, body) {
|
cmd := exec.Command(h.ExecuteCommand)
|
||||||
log.Printf("%s hook triggered successfully\n", hook.ID)
|
cmd.Args = h.ExtractCommandArguments(headers, query, payload)
|
||||||
|
cmd.Dir = h.CommandWorkingDirectory
|
||||||
|
|
||||||
cmd := exec.Command(hook.ExecuteCommand)
|
log.Printf("executing %s (%s) with arguments %s using %s as cwd\n", h.ExecuteCommand, cmd.Path, cmd.Args, cmd.Dir)
|
||||||
cmd.Args = hook.ExtractCommandArguments(headers, query, payload)
|
|
||||||
cmd.Dir = hook.CommandWorkingDirectory
|
|
||||||
|
|
||||||
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 {
|
if err != nil {
|
||||||
log.Printf("stderr: %+v\n", err)
|
log.Printf("error occurred: %+v\n", err)
|
||||||
}
|
errorResponse = fmt.Sprintf("%+v", err)
|
||||||
log.Printf("finished handling %s\n", hook.ID)
|
|
||||||
} else {
|
|
||||||
log.Printf("%s hook did not get triggered\n", hook.ID)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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() {
|
func reloadHooks() {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
//+build windows
|
//+build !windows
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
version = "2.3.3"
|
version = "2.3.4"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -38,6 +38,7 @@ var (
|
||||||
key = flag.String("key", "key.pem", "path to the HTTPS certificate private key pem file")
|
key = flag.String("key", "key.pem", "path to the HTTPS certificate private key pem file")
|
||||||
|
|
||||||
watcher *fsnotify.Watcher
|
watcher *fsnotify.Watcher
|
||||||
|
signals chan os.Signal
|
||||||
|
|
||||||
hooks hook.Hooks
|
hooks hook.Hooks
|
||||||
)
|
)
|
||||||
|
@ -133,10 +134,10 @@ func main() {
|
||||||
func hookHandler(w http.ResponseWriter, r *http.Request) {
|
func hookHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
id := mux.Vars(r)["id"]
|
id := mux.Vars(r)["id"]
|
||||||
|
|
||||||
hook := hooks.Match(id)
|
matchedHooks := hooks.MatchAll(id)
|
||||||
|
|
||||||
if hook != nil {
|
if matchedHooks != nil {
|
||||||
log.Printf("%s got matched\n", id)
|
log.Printf("%s got matched (%d time(s))\n", id, len(matchedHooks))
|
||||||
|
|
||||||
body, err := ioutil.ReadAll(r.Body)
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -154,7 +155,7 @@ func hookHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
contentType := r.Header.Get("Content-Type")
|
contentType := r.Header.Get("Content-Type")
|
||||||
|
|
||||||
if strings.HasPrefix(contentType, "application/json") {
|
if strings.Contains(contentType, "json") {
|
||||||
decoder := json.NewDecoder(strings.NewReader(string(body)))
|
decoder := json.NewDecoder(strings.NewReader(string(body)))
|
||||||
decoder.UseNumber()
|
decoder.UseNumber()
|
||||||
|
|
||||||
|
@ -163,7 +164,7 @@ func hookHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("error parsing JSON payload %+v\n", err)
|
log.Printf("error parsing JSON payload %+v\n", err)
|
||||||
}
|
}
|
||||||
} else if strings.HasPrefix(contentType, "application/x-www-form-urlencoded") {
|
} else if strings.Contains(contentType, "form") {
|
||||||
fd, err := url.ParseQuery(string(body))
|
fd, err := url.ParseQuery(string(body))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("error parsing form payload %+v\n", err)
|
log.Printf("error parsing form payload %+v\n", err)
|
||||||
|
@ -172,40 +173,64 @@ func hookHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
hook.ParseJSONParameters(&headers, &query, &payload)
|
|
||||||
|
|
||||||
// handle hook
|
// 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
|
if h.CaptureCommandOutput {
|
||||||
fmt.Fprintf(w, hook.ResponseMessage)
|
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 {
|
} else {
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
fmt.Fprintf(w, "Hook not found.")
|
fmt.Fprintf(w, "Hook not found.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleHook(hook *hook.Hook, headers, query, payload *map[string]interface{}, body *[]byte) {
|
func handleHook(h *hook.Hook, headers, query, payload *map[string]interface{}, body *[]byte) string {
|
||||||
if hook.TriggerRule == nil || hook.TriggerRule != nil && hook.TriggerRule.Evaluate(headers, query, payload, body) {
|
cmd := exec.Command(h.ExecuteCommand)
|
||||||
log.Printf("%s hook triggered successfully\n", hook.ID)
|
cmd.Args = h.ExtractCommandArguments(headers, query, payload)
|
||||||
|
cmd.Dir = h.CommandWorkingDirectory
|
||||||
|
|
||||||
cmd := exec.Command(hook.ExecuteCommand)
|
log.Printf("executing %s (%s) with arguments %s using %s as cwd\n", h.ExecuteCommand, cmd.Path, cmd.Args, cmd.Dir)
|
||||||
cmd.Args = hook.ExtractCommandArguments(headers, query, payload)
|
|
||||||
cmd.Dir = hook.CommandWorkingDirectory
|
|
||||||
|
|
||||||
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 {
|
if err != nil {
|
||||||
log.Printf("stderr: %+v\n", err)
|
log.Printf("error occurred: %+v\n", err)
|
||||||
}
|
errorResponse = fmt.Sprintf("%+v", err)
|
||||||
log.Printf("finished handling %s\n", hook.ID)
|
|
||||||
} else {
|
|
||||||
log.Printf("%s hook did not get triggered\n", hook.ID)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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() {
|
func reloadHooks() {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue