From 6ea2d68b44cb9c84b160bdf01ed655c90b624ab5 Mon Sep 17 00:00:00 2001 From: Cameron Moore Date: Thu, 22 Dec 2016 10:23:55 -0600 Subject: [PATCH 1/5] Fix failing header tests Now that we use textproto.CanonicalMIMEHeaderKey, all header field test data needs to be title-cased. --- hook/hook_test.go | 62 +++++++++++++++++++++++------------------------ 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/hook/hook_test.go b/hook/hook_test.go index 1fc9a88..39622e9 100644 --- a/hook/hook_test.go +++ b/hook/hook_test.go @@ -72,15 +72,15 @@ var argumentGetTests = []struct { value string ok bool }{ - {"header", "a", &map[string]interface{}{"a": "z"}, nil, nil, "z", true}, + {"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 - {"payload", "a", &map[string]interface{}{"a": "z"}, &map[string]interface{}{"a": "z"}, nil, "", false}, // nil payload - {"foo", "a", &map[string]interface{}{"a": "z"}, nil, nil, "", false}, // invalid source + {"url", "a", &map[string]interface{}{"A": "z"}, nil, &map[string]interface{}{"a": "z"}, "", false}, // nil query + {"payload", "a", &map[string]interface{}{"A": "z"}, &map[string]interface{}{"a": "z"}, nil, "", false}, // nil payload + {"foo", "a", &map[string]interface{}{"A": "z"}, nil, nil, "", false}, // invalid source } func TestArgumentGet(t *testing.T) { @@ -99,14 +99,14 @@ var hookParseJSONParametersTests = []struct { rheaders, rquery, rpayload *map[string]interface{} ok bool }{ - {[]Argument{Argument{"header", "a", ""}}, &map[string]interface{}{"a": `{"b": "y"}`}, nil, nil, &map[string]interface{}{"a": map[string]interface{}{"b": "y"}}, nil, nil, true}, + {[]Argument{Argument{"header", "a", ""}}, &map[string]interface{}{"A": `{"b": "y"}`}, nil, nil, &map[string]interface{}{"A": map[string]interface{}{"b": "y"}}, nil, nil, true}, {[]Argument{Argument{"url", "a", ""}}, nil, &map[string]interface{}{"a": `{"b": "y"}`}, nil, nil, &map[string]interface{}{"a": map[string]interface{}{"b": "y"}}, nil, true}, {[]Argument{Argument{"payload", "a", ""}}, nil, nil, &map[string]interface{}{"a": `{"b": "y"}`}, nil, nil, &map[string]interface{}{"a": map[string]interface{}{"b": "y"}}, true}, - {[]Argument{Argument{"header", "z", ""}}, &map[string]interface{}{"z": `{}`}, nil, nil, &map[string]interface{}{"z": map[string]interface{}{}}, nil, nil, true}, + {[]Argument{Argument{"header", "z", ""}}, &map[string]interface{}{"Z": `{}`}, nil, nil, &map[string]interface{}{"Z": map[string]interface{}{}}, nil, nil, true}, // failures - {[]Argument{Argument{"header", "z", ""}}, &map[string]interface{}{"z": ``}, nil, nil, &map[string]interface{}{"z": ``}, nil, nil, false}, // empty string + {[]Argument{Argument{"header", "z", ""}}, &map[string]interface{}{"Z": ``}, nil, nil, &map[string]interface{}{"Z": ``}, nil, nil, false}, // empty string {[]Argument{Argument{"header", "y", ""}}, &map[string]interface{}{"X": `{}`}, nil, nil, &map[string]interface{}{"X": `{}`}, nil, nil, false}, // missing parameter - {[]Argument{Argument{"string", "z", ""}}, &map[string]interface{}{"z": ``}, nil, nil, &map[string]interface{}{"z": ``}, nil, nil, false}, // invalid argument source + {[]Argument{Argument{"string", "z", ""}}, &map[string]interface{}{"Z": ``}, nil, nil, &map[string]interface{}{"Z": ``}, nil, nil, false}, // invalid argument source } func TestHookParseJSONParameters(t *testing.T) { @@ -126,9 +126,9 @@ var hookExtractCommandArgumentsTests = []struct { value []string ok bool }{ - {"test", []Argument{Argument{"header", "a", ""}}, &map[string]interface{}{"a": "z"}, nil, nil, []string{"test", "z"}, true}, + {"test", []Argument{Argument{"header", "a", ""}}, &map[string]interface{}{"A": "z"}, nil, nil, []string{"test", "z"}, true}, // failures - {"fail", []Argument{Argument{"payload", "a", ""}}, &map[string]interface{}{"a": "z"}, nil, nil, []string{"fail", ""}, false}, + {"fail", []Argument{Argument{"payload", "a", ""}}, &map[string]interface{}{"A": "z"}, nil, nil, []string{"fail", ""}, false}, } func TestHookExtractCommandArguments(t *testing.T) { @@ -171,14 +171,14 @@ var hookExtractCommandArgumentsForEnvTests = []struct { { "test", []Argument{Argument{"header", "a", ""}}, - &map[string]interface{}{"a": "z"}, nil, nil, + &map[string]interface{}{"A": "z"}, nil, nil, []string{"HOOK_a=z"}, true, }, { "test", []Argument{Argument{"header", "a", "MYKEY"}}, - &map[string]interface{}{"a": "z"}, nil, nil, + &map[string]interface{}{"A": "z"}, nil, nil, []string{"MYKEY=z"}, true, }, @@ -186,7 +186,7 @@ var hookExtractCommandArgumentsForEnvTests = []struct { { "fail", []Argument{Argument{"payload", "a", ""}}, - &map[string]interface{}{"a": "z"}, nil, nil, + &map[string]interface{}{"A": "z"}, nil, nil, []string{}, false, }, @@ -248,16 +248,16 @@ var matchRuleTests = []struct { ok bool err bool }{ - {"value", "", "", "z", Argument{"header", "a", ""}, &map[string]interface{}{"a": "z"}, nil, nil, []byte{}, true, false}, - {"regex", "^z", "", "z", Argument{"header", "a", ""}, &map[string]interface{}{"a": "z"}, nil, nil, []byte{}, true, false}, - {"payload-hash-sha1", "", "secret", "", Argument{"header", "a", ""}, &map[string]interface{}{"a": "b17e04cbb22afa8ffbff8796fc1894ed27badd9e"}, nil, nil, []byte(`{"a": "z"}`), true, false}, + {"value", "", "", "z", Argument{"header", "a", ""}, &map[string]interface{}{"A": "z"}, nil, nil, []byte{}, true, false}, + {"regex", "^z", "", "z", Argument{"header", "a", ""}, &map[string]interface{}{"A": "z"}, nil, nil, []byte{}, true, false}, + {"payload-hash-sha1", "", "secret", "", Argument{"header", "a", ""}, &map[string]interface{}{"A": "b17e04cbb22afa8ffbff8796fc1894ed27badd9e"}, nil, nil, []byte(`{"a": "z"}`), true, false}, // failures - {"value", "", "", "X", Argument{"header", "a", ""}, &map[string]interface{}{"a": "z"}, nil, nil, []byte{}, false, false}, - {"regex", "^X", "", "", Argument{"header", "a", ""}, &map[string]interface{}{"a": "z"}, nil, nil, []byte{}, false, false}, - {"value", "", "2", "X", Argument{"header", "a", ""}, &map[string]interface{}{"y": "z"}, nil, nil, []byte{}, false, false}, // reference invalid header + {"value", "", "", "X", Argument{"header", "a", ""}, &map[string]interface{}{"A": "z"}, nil, nil, []byte{}, false, false}, + {"regex", "^X", "", "", Argument{"header", "a", ""}, &map[string]interface{}{"A": "z"}, nil, nil, []byte{}, false, false}, + {"value", "", "2", "X", Argument{"header", "a", ""}, &map[string]interface{}{"Y": "z"}, nil, nil, []byte{}, false, false}, // reference invalid header // errors - {"regex", "*", "", "", Argument{"header", "a", ""}, &map[string]interface{}{"a": "z"}, nil, nil, []byte{}, false, true}, // invalid regex - {"payload-hash-sha1", "", "secret", "", Argument{"header", "a", ""}, &map[string]interface{}{"a": ""}, nil, nil, []byte{}, false, true}, // invalid hmac + {"regex", "*", "", "", Argument{"header", "a", ""}, &map[string]interface{}{"A": "z"}, nil, nil, []byte{}, false, true}, // invalid regex + {"payload-hash-sha1", "", "secret", "", Argument{"header", "a", ""}, &map[string]interface{}{"A": ""}, nil, nil, []byte{}, false, true}, // invalid hmac } func TestMatchRule(t *testing.T) { @@ -284,7 +284,7 @@ var andRuleTests = []struct { {Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", ""}}}, {Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", ""}}}, }, - &map[string]interface{}{"a": "z", "b": "y"}, nil, nil, []byte{}, + &map[string]interface{}{"A": "z", "B": "y"}, nil, nil, []byte{}, true, false, }, { @@ -293,7 +293,7 @@ var andRuleTests = []struct { {Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", ""}}}, {Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", ""}}}, }, - &map[string]interface{}{"a": "z", "b": "Y"}, nil, nil, []byte{}, + &map[string]interface{}{"A": "z", "B": "Y"}, nil, nil, []byte{}, false, false, }, // Complex test to cover Rules.Evaluate @@ -319,7 +319,7 @@ var andRuleTests = []struct { }, }, }, - &map[string]interface{}{"a": "z", "b": "y", "c": "x", "d": "w", "e": "X", "f": "X"}, nil, nil, []byte{}, + &map[string]interface{}{"A": "z", "B": "y", "C": "x", "D": "w", "E": "X", "F": "X"}, nil, nil, []byte{}, true, false, }, {"empty rule", AndRule{{}}, nil, nil, nil, nil, false, false}, @@ -327,7 +327,7 @@ var andRuleTests = []struct { { "invalid rule", AndRule{{Match: &MatchRule{"value", "", "", "X", Argument{"header", "a", ""}}}}, - &map[string]interface{}{"y": "z"}, nil, nil, nil, + &map[string]interface{}{"Y": "z"}, nil, nil, nil, false, false, }, } @@ -355,7 +355,7 @@ var orRuleTests = []struct { {Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", ""}}}, {Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", ""}}}, }, - &map[string]interface{}{"a": "z", "b": "X"}, nil, nil, []byte{}, + &map[string]interface{}{"A": "z", "B": "X"}, nil, nil, []byte{}, true, false, }, { @@ -364,7 +364,7 @@ var orRuleTests = []struct { {Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", ""}}}, {Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", ""}}}, }, - &map[string]interface{}{"a": "X", "b": "y"}, nil, nil, []byte{}, + &map[string]interface{}{"A": "X", "B": "y"}, nil, nil, []byte{}, true, false, }, { @@ -373,7 +373,7 @@ var orRuleTests = []struct { {Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", ""}}}, {Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", ""}}}, }, - &map[string]interface{}{"a": "Z", "b": "Y"}, nil, nil, []byte{}, + &map[string]interface{}{"A": "Z", "B": "Y"}, nil, nil, []byte{}, false, false, }, // failures @@ -382,7 +382,7 @@ var orRuleTests = []struct { OrRule{ {Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", ""}}}, }, - &map[string]interface{}{"y": "Z"}, nil, nil, []byte{}, + &map[string]interface{}{"Y": "Z"}, nil, nil, []byte{}, false, false, }, } @@ -404,8 +404,8 @@ var notRuleTests = []struct { ok bool err bool }{ - {"(a=z): !a=X", NotRule{Match: &MatchRule{"value", "", "", "X", Argument{"header", "a", ""}}}, &map[string]interface{}{"a": "z"}, nil, nil, []byte{}, true, false}, - {"(a=z): !a=z", NotRule{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", ""}}}, &map[string]interface{}{"a": "z"}, nil, nil, []byte{}, false, false}, + {"(a=z): !a=X", NotRule{Match: &MatchRule{"value", "", "", "X", Argument{"header", "a", ""}}}, &map[string]interface{}{"A": "z"}, nil, nil, []byte{}, true, false}, + {"(a=z): !a=z", NotRule{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", ""}}}, &map[string]interface{}{"A": "z"}, nil, nil, []byte{}, false, false}, } func TestNotRule(t *testing.T) { From 8226d5e50f09cd41118e5688e413350ba622fd46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adnan=20Hajdarevi=C4=87?= Date: Thu, 9 Feb 2017 05:22:59 +0100 Subject: [PATCH 2/5] Update README.md --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 379f910..0be08c8 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ -[![ghit.me](https://ghit.me/badge.svg?repo=adnanh/webhook)](https://ghit.me/repo/adnanh/webhook) [![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) [![Flattr this](https://button.flattr.com/flattr-badge-large.png)](https://flattr.com/submit/auto?user_id=adnanh&url=https%3A%2F%2Fwww.github.com%2Fadnanh%2Fwebhook) [Donate via PayPal](https://paypal.me/hookdoo) - +[![ghit.me](https://ghit.me/badge.svg?repo=adnanh/webhook)](https://ghit.me/repo/adnanh/webhook) [![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) [![Flattr this](https://button.flattr.com/flattr-badge-large.png)](https://flattr.com/submit/auto?user_id=adnanh&url=https%3A%2F%2Fwww.github.com%2Fadnanh%2Fwebhook) [Donate via PayPal](https://paypal.me/hookdoo) | [Patreon page](https://www.patreon.com/webhook) # 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. From ddb1f2441aa0ee0b22fca920c15404c43f86fbad Mon Sep 17 00:00:00 2001 From: Mathias Merscher Date: Fri, 10 Feb 2017 12:32:11 +0100 Subject: [PATCH 3/5] make http return code for mismatched rules configurable Signed-off-by: Mathias Merscher --- hook/hook.go | 1 + test/hooks.json.tmpl | 2 ++ webhook.go | 11 +++++++++++ webhook_test.go | 7 ++++++- 4 files changed, 20 insertions(+), 1 deletion(-) diff --git a/hook/hook.go b/hook/hook.go index 0cc2740..926a651 100644 --- a/hook/hook.go +++ b/hook/hook.go @@ -300,6 +300,7 @@ type Hook struct { PassArgumentsToCommand []Argument `json:"pass-arguments-to-command,omitempty"` JSONStringParameters []Argument `json:"parse-parameters-as-json,omitempty"` TriggerRule *Rules `json:"trigger-rule,omitempty"` + TriggerRuleMismatchCode int `json:"trigger-rule-mismatch-code,omitempty"` } // ParseJSONParameters decodes specified arguments to JSON objects and replaces the diff --git a/test/hooks.json.tmpl b/test/hooks.json.tmpl index 4d94449..c731c9a 100644 --- a/test/hooks.json.tmpl +++ b/test/hooks.json.tmpl @@ -4,6 +4,7 @@ "execute-command": "{{ .Hookecho }}", "command-working-directory": "/", "include-command-output-in-response": true, + "trigger-rule-mismatch-code": 400, "pass-environment-to-command": [ { @@ -59,6 +60,7 @@ "command-working-directory": "/", "include-command-output-in-response": false, "response-message": "success", + "trigger-rule-mismatch-code": 999, "parse-parameters-as-json": [ { "source": "payload", diff --git a/webhook.go b/webhook.go index d4908de..92f49b6 100644 --- a/webhook.go +++ b/webhook.go @@ -242,6 +242,17 @@ func hookHandler(w http.ResponseWriter, r *http.Request) { return } + // Check if a return code is configured for the hook + if matchedHook.TriggerRuleMismatchCode != 0 { + // Check if the configured return code is supported by the http package + // by testing if there is a StatusText for this code. + if len(http.StatusText(matchedHook.TriggerRuleMismatchCode)) > 0 { + w.WriteHeader(matchedHook.TriggerRuleMismatchCode) + } else { + log.Printf("%s got matched, but the configured return code %d is unknown - defaulting to 200\n", matchedHook.ID, matchedHook.TriggerRuleMismatchCode) + } + } + // if none of the hooks got triggered log.Printf("%s got matched, but didn't get triggered because the trigger rules were not satisfied\n", matchedHook.ID) diff --git a/webhook_test.go b/webhook_test.go index 0b25de9..eef1e0a 100644 --- a/webhook_test.go +++ b/webhook_test.go @@ -515,5 +515,10 @@ env: HOOK_head_commit.timestamp=2013-03-12T08:14:29-07:00 `, }, - {"empty payload", "github", nil, `{}`, false, http.StatusOK, `Hook rules were not satisfied.`}, + // test with custom return code + {"empty payload", "github", nil, `{}`, false, http.StatusBadRequest, `Hook rules were not satisfied.`}, + // test with custom invalid http code, should default to 200 OK + {"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.`}, } From 058f820cbd1c8f2ae134508e598e5ba3d753b242 Mon Sep 17 00:00:00 2001 From: Mathias Merscher Date: Fri, 10 Feb 2017 18:42:02 +0100 Subject: [PATCH 4/5] rename trigger rule http response code config option Signed-off-by: Mathias Merscher --- hook/hook.go | 22 +++++++++++----------- test/hooks.json.tmpl | 4 ++-- webhook.go | 8 ++++---- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/hook/hook.go b/hook/hook.go index 926a651..d5f43fa 100644 --- a/hook/hook.go +++ b/hook/hook.go @@ -290,17 +290,17 @@ func (h *ResponseHeaders) Set(value string) error { // Hook type is a structure containing details for a single hook type Hook struct { - ID string `json:"id,omitempty"` - ExecuteCommand string `json:"execute-command,omitempty"` - CommandWorkingDirectory string `json:"command-working-directory,omitempty"` - ResponseMessage string `json:"response-message,omitempty"` - ResponseHeaders ResponseHeaders `json:"response-headers,omitempty"` - CaptureCommandOutput bool `json:"include-command-output-in-response,omitempty"` - PassEnvironmentToCommand []Argument `json:"pass-environment-to-command,omitempty"` - PassArgumentsToCommand []Argument `json:"pass-arguments-to-command,omitempty"` - JSONStringParameters []Argument `json:"parse-parameters-as-json,omitempty"` - TriggerRule *Rules `json:"trigger-rule,omitempty"` - TriggerRuleMismatchCode int `json:"trigger-rule-mismatch-code,omitempty"` + ID string `json:"id,omitempty"` + ExecuteCommand string `json:"execute-command,omitempty"` + CommandWorkingDirectory string `json:"command-working-directory,omitempty"` + ResponseMessage string `json:"response-message,omitempty"` + ResponseHeaders ResponseHeaders `json:"response-headers,omitempty"` + CaptureCommandOutput bool `json:"include-command-output-in-response,omitempty"` + PassEnvironmentToCommand []Argument `json:"pass-environment-to-command,omitempty"` + PassArgumentsToCommand []Argument `json:"pass-arguments-to-command,omitempty"` + JSONStringParameters []Argument `json:"parse-parameters-as-json,omitempty"` + TriggerRule *Rules `json:"trigger-rule,omitempty"` + TriggerRuleMismatchHttpResponseCode int `json:"trigger-rule-mismatch-http-response-code,omitempty"` } // ParseJSONParameters decodes specified arguments to JSON objects and replaces the diff --git a/test/hooks.json.tmpl b/test/hooks.json.tmpl index c731c9a..0a8aead 100644 --- a/test/hooks.json.tmpl +++ b/test/hooks.json.tmpl @@ -4,7 +4,7 @@ "execute-command": "{{ .Hookecho }}", "command-working-directory": "/", "include-command-output-in-response": true, - "trigger-rule-mismatch-code": 400, + "trigger-rule-mismatch-http-response-code": 400, "pass-environment-to-command": [ { @@ -60,7 +60,7 @@ "command-working-directory": "/", "include-command-output-in-response": false, "response-message": "success", - "trigger-rule-mismatch-code": 999, + "trigger-rule-mismatch-http-response-code": 999, "parse-parameters-as-json": [ { "source": "payload", diff --git a/webhook.go b/webhook.go index 92f49b6..c3b0f4f 100644 --- a/webhook.go +++ b/webhook.go @@ -243,13 +243,13 @@ func hookHandler(w http.ResponseWriter, r *http.Request) { } // Check if a return code is configured for the hook - if matchedHook.TriggerRuleMismatchCode != 0 { + if matchedHook.TriggerRuleMismatchHttpResponseCode != 0 { // Check if the configured return code is supported by the http package // by testing if there is a StatusText for this code. - if len(http.StatusText(matchedHook.TriggerRuleMismatchCode)) > 0 { - w.WriteHeader(matchedHook.TriggerRuleMismatchCode) + if len(http.StatusText(matchedHook.TriggerRuleMismatchHttpResponseCode)) > 0 { + w.WriteHeader(matchedHook.TriggerRuleMismatchHttpResponseCode) } else { - log.Printf("%s got matched, but the configured return code %d is unknown - defaulting to 200\n", matchedHook.ID, matchedHook.TriggerRuleMismatchCode) + log.Printf("%s got matched, but the configured return code %d is unknown - defaulting to 200\n", matchedHook.ID, matchedHook.TriggerRuleMismatchHttpResponseCode) } } From c51971fd372f929e1b4748d8bb9a81987e305960 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adnan=20Hajdarevi=C4=87?= Date: Fri, 10 Feb 2017 19:19:53 +0100 Subject: [PATCH 5/5] Bump version --- webhook.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webhook.go b/webhook.go index c3b0f4f..7c5f62e 100644 --- a/webhook.go +++ b/webhook.go @@ -21,7 +21,7 @@ import ( ) const ( - version = "2.6.0" + version = "2.6.1" ) var (