From a40fba5e293001636fbd7751fa805d9f9c933ee4 Mon Sep 17 00:00:00 2001 From: Greg Dubicki Date: Sun, 27 Aug 2017 10:23:59 +0200 Subject: [PATCH 1/3] Add CaptureCommandOutputOnError to include stdout & stderror in failed executions, with docs. --- docs/Hook-Definition.md | 1 + hook/hook.go | 1 + webhook.go | 8 ++++++-- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/Hook-Definition.md b/docs/Hook-Definition.md index a15baa2..929e922 100644 --- a/docs/Hook-Definition.md +++ b/docs/Hook-Definition.md @@ -9,6 +9,7 @@ Hooks are defined as JSON objects. Please note that in order to be considered va * `response-message` - specifies the string that will be returned to the hook initiator * `response-headers` - specifies the list of headers in format `{"name": "X-Example-Header", "value": "it works"}` that will be returned in HTTP response for the hook * `include-command-output-in-response` - boolean whether webhook should wait for the command to finish and return the raw output as a response to the hook initiator. If the command fails to execute or encounters any errors while executing the response will result in 500 Internal Server Error HTTP status code, otherwise the 200 OK status code will be returned. + * `include-command-output-in-response-on-error` - boolean whether webhook should include command stdout & stderror as a response in failed executions. It only works if `include-command-output-in-response` is set to `true`. * `parse-parameters-as-json` - specifies the list of arguments that contain JSON strings. These parameters will be decoded by webhook and you can access them like regular objects in rules and `pass-arguments-to-command`. * `pass-arguments-to-command` - specifies the list of arguments that will be passed to the command. Check [Referencing request values page](Referencing-Request-Values) to see how to reference the values from the request. If you want to pass a static string value to your command you can specify it as `{ "source": "string", "name": "argumentvalue" }` diff --git a/hook/hook.go b/hook/hook.go index b4eae95..7fd8c1f 100644 --- a/hook/hook.go +++ b/hook/hook.go @@ -384,6 +384,7 @@ type Hook struct { ResponseMessage string `json:"response-message,omitempty"` ResponseHeaders ResponseHeaders `json:"response-headers,omitempty"` CaptureCommandOutput bool `json:"include-command-output-in-response,omitempty"` + CaptureCommandOutputOnError bool `json:"include-command-output-in-response-on-error,omitempty"` PassEnvironmentToCommand []Argument `json:"pass-environment-to-command,omitempty"` PassArgumentsToCommand []Argument `json:"pass-arguments-to-command,omitempty"` PassFileToCommand []Argument `json:"pass-file-to-command,omitempty"` diff --git a/webhook.go b/webhook.go index 4df45f8..8a318ee 100644 --- a/webhook.go +++ b/webhook.go @@ -282,9 +282,13 @@ func hookHandler(w http.ResponseWriter, r *http.Request) { response, err := handleHook(matchedHook, rid, &headers, &query, &payload, &body) 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.") + if matchedHook.CaptureCommandOutputOnError { + fmt.Fprintf(w, response) + } else { + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + fmt.Fprintf(w, "Error occurred while executing the hook's command. Please check your logs for more details.") + } } else { fmt.Fprintf(w, response) } From 0d3d29055bd1d623001ae16d6f37bd86b68f1657 Mon Sep 17 00:00:00 2001 From: Greg Dubicki Date: Sat, 11 Nov 2017 21:05:36 +0100 Subject: [PATCH 2/3] Allow hookecho to exit with codes other than 0 --- .gitignore | 1 + test/hookecho.go | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/.gitignore b/.gitignore index ee20f29..ad87e5e 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ .cover coverage webhook +/test/hookecho diff --git a/test/hookecho.go b/test/hookecho.go index 4ac4b4d..8e308ff 100644 --- a/test/hookecho.go +++ b/test/hookecho.go @@ -6,6 +6,7 @@ import ( "fmt" "os" "strings" + "strconv" ) func main() { @@ -23,4 +24,14 @@ func main() { if len(env) > 0 { fmt.Printf("env: %s\n", strings.Join(env, " ")) } + + if (len(os.Args) > 1) && (strings.HasPrefix(os.Args[1], "exit=")) { + exit_code_str := os.Args[1][5:] + exit_code, err := strconv.Atoi(exit_code_str) + if err != nil { + fmt.Printf("Exit code %s not an int!", exit_code_str) + os.Exit(-1) + } + os.Exit(exit_code) + } } From e2f6e4eb37e2ff7870d8c9e51ac5460840b5e8c6 Mon Sep 17 00:00:00 2001 From: Greg Dubicki Date: Sat, 11 Nov 2017 21:35:55 +0100 Subject: [PATCH 3/3] Add tests for capturing command output and fix running tests on macOS, where there is no /bin/true... --- test/hooks.json.tmpl | 44 ++++++++++++++++++++++++++++++++++++++++++++ test/hooks.yaml.tmpl | 24 ++++++++++++++++++++++++ webhook_test.go | 10 +++++++++- 3 files changed, 77 insertions(+), 1 deletion(-) diff --git a/test/hooks.json.tmpl b/test/hooks.json.tmpl index 0a8aead..d65d1d3 100644 --- a/test/hooks.json.tmpl +++ b/test/hooks.json.tmpl @@ -136,5 +136,49 @@ } } } + }, + { + "id": "capture-command-output-on-success-not-by-default", + "pass-arguments-to-command": [ + { + "source": "string", + "name": "exit=0" + } + ], + "execute-command": "{{ .Hookecho }}" + }, + { + "id": "capture-command-output-on-success-yes-with-flag", + "pass-arguments-to-command": [ + { + "source": "string", + "name": "exit=0" + } + ], + "execute-command": "{{ .Hookecho }}", + "include-command-output-in-response": true + }, + { + "id": "capture-command-output-on-error-not-by-default", + "pass-arguments-to-command": [ + { + "source": "string", + "name": "exit=1" + } + ], + "execute-command": "{{ .Hookecho }}", + "include-command-output-in-response": true + }, + { + "id": "capture-command-output-on-error-yes-with-extra-flag", + "pass-arguments-to-command": [ + { + "source": "string", + "name": "exit=1" + } + ], + "execute-command": "{{ .Hookecho }}", + "include-command-output-in-response": true, + "include-command-output-in-response-on-error": true } ] diff --git a/test/hooks.yaml.tmpl b/test/hooks.yaml.tmpl index 176a76d..c0377e5 100644 --- a/test/hooks.yaml.tmpl +++ b/test/hooks.yaml.tmpl @@ -73,3 +73,27 @@ include-command-output-in-response: true id: gitlab command-working-directory: / +- id: capture-command-output-on-success-not-by-default + pass-arguments-to-command: + - source: string + name: exit=0 + execute-command: '{{ .Hookecho }}' +- id: capture-command-output-on-success-yes-with-flag + pass-arguments-to-command: + - source: string + name: exit=0 + execute-command: '{{ .Hookecho }}' + include-command-output-in-response: true +- id: capture-command-output-on-error-not-by-default + pass-arguments-to-command: + - source: string + name: exit=1 + execute-command: '{{ .Hookecho }}' + include-command-output-in-response: true +- id: capture-command-output-on-error-yes-with-extra-flag + pass-arguments-to-command: + - source: string + name: exit=1 + execute-command: '{{ .Hookecho }}' + include-command-output-in-response: true + include-command-output-in-response-on-error: true diff --git a/webhook_test.go b/webhook_test.go index 93230d4..4373b16 100644 --- a/webhook_test.go +++ b/webhook_test.go @@ -51,7 +51,7 @@ func TestStaticParams(t *testing.T) { } // case 2: binary with spaces in its name - err = os.Symlink("/bin/true", "/tmp/with space") + err = os.Symlink("/bin/echo", "/tmp/with space") if err != nil { t.Fatalf("%v", err) } @@ -613,4 +613,12 @@ env: HOOK_head_commit.timestamp=2013-03-12T08:14:29-07:00 {"empty payload", "bitbucket", nil, `{}`, false, http.StatusOK, `Hook rules were not satisfied.`}, // test with no configured http return code, should default to 200 OK {"empty payload", "gitlab", nil, `{}`, false, http.StatusOK, `Hook rules were not satisfied.`}, + + // test capturing command output + {"don't capture output on success by default", "capture-command-output-on-success-not-by-default", nil, `{}`, false, http.StatusOK, ``}, + {"capture output on success with flag set", "capture-command-output-on-success-yes-with-flag", nil, `{}`, false, http.StatusOK, `arg: exit=0 +`}, + {"don't capture output on error by default", "capture-command-output-on-error-not-by-default", nil, `{}`, false, http.StatusInternalServerError, `Error occurred while executing the hook's command. Please check your logs for more details.`}, + {"capture output on error with extra flag set", "capture-command-output-on-error-yes-with-extra-flag", nil, `{}`, false, http.StatusInternalServerError, `arg: exit=1 +`}, }