From 9c545a745f4d8b778a7ec3f055cf55645f8cd52c Mon Sep 17 00:00:00 2001 From: Adnan Hajdarevic Date: Sat, 6 Jun 2015 14:19:52 +0200 Subject: [PATCH] return command output, pass whole payload as json to the command --- hook/hook.go | 25 ++++++++++++++++---- hooks.json.example | 1 + webhook.go | 54 +++++++++++++++++++++++++++--------------- webhook_windows.go | 58 +++++++++++++++++++++++++++++----------------- 4 files changed, 94 insertions(+), 44 deletions(-) diff --git a/hook/hook.go b/hook/hook.go index f3cb78b..3cb5b58 100644 --- a/hook/hook.go +++ b/hook/hook.go @@ -16,10 +16,11 @@ import ( // Constants used to specify the parameter source const ( - SourceHeader string = "header" - SourceQuery string = "url" - SourcePayload string = "payload" - SourceString string = "string" + SourceHeader string = "header" + SourceQuery string = "url" + SourcePayload string = "payload" + SourceString string = "string" + SourceEntirePayload string = "entire-payload" ) // CheckPayloadSignature calculates and verifies SHA1 signature of the given payload @@ -151,6 +152,14 @@ func (ha *Argument) Get(headers, query, payload *map[string]interface{}) (string source = payload case SourceString: return ha.Name, true + case SourceEntirePayload: + r, err := json.Marshal(payload) + + if err != nil { + return "", false + } + + return string(r), true } if source != nil { @@ -166,6 +175,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"` @@ -385,3 +395,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"` +} diff --git a/hooks.json.example b/hooks.json.example index 7c6c040..36ad7a3 100644 --- a/hooks.json.example +++ b/hooks.json.example @@ -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": [ { diff --git a/webhook.go b/webhook.go index 7279e34..207a25a 100644 --- a/webhook.go +++ b/webhook.go @@ -165,7 +165,7 @@ func hookHandler(w http.ResponseWriter, r *http.Request) { 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.UseNumber() @@ -174,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 strings.HasPrefix(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) @@ -184,15 +184,18 @@ func hookHandler(w http.ResponseWriter, r *http.Request) { } // handle hook - for _, hook := range matchedHooks { - hook.ParseJSONParameters(&headers, &query, &payload) - if hook.TriggerRule == nil || hook.TriggerRule != nil && hook.TriggerRule.Evaluate(&headers, &query, &payload, &body) { - log.Printf("%s hook triggered successfully\n", hook.ID) + 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) - go handleHook(hook, &headers, &query, &payload, &body) - - // send the hook defined response message - fmt.Fprintf(w, hook.ResponseMessage) + if !h.CaptureCommandOutput { + go handleHook(h, &headers, &query, &payload, &body) + fmt.Fprintf(w, h.ResponseMessage) + } else { + response := handleHook(h, &headers, &query, &payload, &body) + fmt.Fprintf(w, response) + } return } @@ -209,22 +212,35 @@ func hookHandler(w http.ResponseWriter, r *http.Request) { } } -func handleHook(hook *hook.Hook, headers, query, payload *map[string]interface{}, body *[]byte) { - cmd := exec.Command(hook.ExecuteCommand) - cmd.Args = hook.ExtractCommandArguments(headers, query, payload) - cmd.Dir = hook.CommandWorkingDirectory +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", hook.ExecuteCommand, cmd.Path, cmd.Args, cmd.Dir) + log.Printf("executing %s (%s) with arguments %s using %s as cwd\n", h.ExecuteCommand, cmd.Path, cmd.Args, cmd.Dir) - out, err := cmd.Output() + out, err := cmd.CombinedOutput() - log.Printf("stdout: %s\n", out) + log.Printf("command output: %s\n", out) + + var errorResponse string 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) + 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() { diff --git a/webhook_windows.go b/webhook_windows.go index 1c26828..7dc684a 100644 --- a/webhook_windows.go +++ b/webhook_windows.go @@ -1,4 +1,4 @@ -//+build windows +//+build !windows package main @@ -38,6 +38,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 ) @@ -154,7 +155,7 @@ func hookHandler(w http.ResponseWriter, r *http.Request) { 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.UseNumber() @@ -163,7 +164,7 @@ func hookHandler(w http.ResponseWriter, r *http.Request) { if err != nil { 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)) if err != nil { log.Printf("error parsing form payload %+v\n", err) @@ -173,16 +174,18 @@ func hookHandler(w http.ResponseWriter, r *http.Request) { } // handle hook - for _, hook := range matchedHooks { - hook.ParseJSONParameters(&headers, &query, &payload) + 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 hook.TriggerRule == nil || hook.TriggerRule != nil && hook.TriggerRule.Evaluate(&headers, &query, &payload, &body) { - log.Printf("%s hook triggered successfully\n", hook.ID) - - go handleHook(hook, &headers, &query, &payload, &body) - - // send the hook defined response message - fmt.Fprintf(w, hook.ResponseMessage) + if !h.CaptureCommandOutput { + go handleHook(h, &headers, &query, &payload, &body) + fmt.Fprintf(w, h.ResponseMessage) + } else { + response := handleHook(h, &headers, &query, &payload, &body) + fmt.Fprintf(w, response) + } return } @@ -199,22 +202,35 @@ func hookHandler(w http.ResponseWriter, r *http.Request) { } } -func handleHook(hook *hook.Hook, headers, query, payload *map[string]interface{}, body *[]byte) { - cmd := exec.Command(hook.ExecuteCommand) - cmd.Args = hook.ExtractCommandArguments(headers, query, payload) - cmd.Dir = hook.CommandWorkingDirectory +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", hook.ExecuteCommand, cmd.Path, cmd.Args, cmd.Dir) + log.Printf("executing %s (%s) with arguments %s using %s as cwd\n", h.ExecuteCommand, cmd.Path, cmd.Args, cmd.Dir) - out, err := cmd.Output() + out, err := cmd.CombinedOutput() - log.Printf("stdout: %s\n", out) + log.Printf("command output: %s\n", out) + + var errorResponse string 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) + 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() {