Compare commits
17 Commits
master
...
feature/co
Author | SHA1 | Date | |
---|---|---|---|
|
794a16ee13 | ||
|
8431357269 | ||
|
528805584d | ||
|
369cb0d3b6 | ||
|
4f1089495d | ||
|
d4dacd6f8e | ||
|
31e317be74 | ||
|
c2fd1d82a3 | ||
|
eece0137ef | ||
|
7467933680 | ||
|
fd50118712 | ||
|
08b351605d | ||
|
49b375f625 | ||
|
01111b5258 | ||
|
64942c9793 | ||
|
54cfc6bcbd | ||
|
3ec7da2b15 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -3,3 +3,4 @@
|
||||
coverage
|
||||
webhook
|
||||
/test/hookecho
|
||||
go_build_webhook*
|
||||
|
@ -129,6 +129,7 @@ Check out [Hook examples page](docs/Hook-Examples.md) for more complex examples
|
||||
- [Adventures in webhooks](https://medium.com/@draketech/adventures-in-webhooks-2d6584501c62) by [Drake](https://medium.com/@draketech)
|
||||
- [GitHub pro tips](http://notes.spencerlyon.com/2016/01/04/github-pro-tips/) by [Spencer Lyon](http://notes.spencerlyon.com/)
|
||||
- [XiaoMi Vacuum + Amazon Button = Dash Cleaning](https://www.instructables.com/id/XiaoMi-Vacuum-Amazon-Button-Dash-Cleaning/) by [c0mmensal](https://www.instructables.com/member/c0mmensal/)
|
||||
- [Set up Automated Deployments From Github With Webhook](https://maximorlov.com/automated-deployments-from-github-with-webhook/) by [Maxim Orlov](https://twitter.com/_maximization)
|
||||
- VIDEO: [Gitlab CI/CD configuration using Docker and adnanh/webhook to deploy on VPS - Tutorial #1](https://www.youtube.com/watch?v=Qhn-lXjyrZA&feature=youtu.be) by [Yes! Let's Learn Software Engineering](https://www.youtube.com/channel/UCH4XJf2BZ_52fbf8fOBMF3w)
|
||||
- ...
|
||||
- Want to add your own? Open an Issue or create a PR :-)
|
||||
|
@ -22,6 +22,18 @@ Hooks are defined as objects in the JSON or YAML hooks configuration file. Pleas
|
||||
* `pass-file-to-command` - specifies a list of entries that will be serialized as a file. Incoming [data](Referencing-Request-Values.md) will be serialized in a request-temporary-file (otherwise parallel calls of the hook would lead to concurrent overwritings of the file). The filename to be addressed within the subsequent script is provided via an environment variable. Use `envname` to specify the name of the environment variable. If `envname` is not provided `HOOK_` and the name used to reference the request value are used. Defining `command-working-directory` will store the file relative to this location, if not provided, the systems temporary file directory will be used. If `base64decode` is true, the incoming binary data will be base 64 decoded prior to storing it into the file. By default the corresponding file will be removed after the webhook exited.
|
||||
* `trigger-rule` - specifies the rule that will be evaluated in order to determine should the hook be triggered. Check [Hook rules page](Hook-Rules.md) to see the list of valid rules and their usage
|
||||
* `trigger-rule-mismatch-http-response-code` - specifies the HTTP status code to be returned when the trigger rule is not satisfied
|
||||
* `pre-hook-command` - specifies the command that will be run before the hook gets invoked. Check [Pre-hook command page](PreHook-Command.md) for more details and examples.
|
||||
* to the STDIN of this command, webhook will pass a JSON string representation of an object with the following properties:
|
||||
* `hookID` - ID of the hook that got matched
|
||||
* `method` - HTTP(s) method used by the client (i.e. GET, POST, etc...)
|
||||
* `URI` - URI which client requested
|
||||
* `host` - value of the `Host` header sent by the client
|
||||
* `remoteAddr` - client's IP address and port in the `IP:PORT` format
|
||||
* `query` - object with query parameters and their respective values
|
||||
* `headers` - object with headers and their respective values
|
||||
* `base64EncodedBody` - base64 encoded request body
|
||||
* Output of this command __MUST__ be valid JSON string which will be parsed by the webhook and accessible using the `pre-hook` as source when referencing values.
|
||||
* __Important! Any errors encountered while trying to execute the pre-hook command will prevent the hook from triggering!__
|
||||
|
||||
## Examples
|
||||
Check out [Hook examples page](Hook-Examples.md) for more complex examples of hooks.
|
||||
|
56
docs/PreHook-Command.md
Normal file
56
docs/PreHook-Command.md
Normal file
@ -0,0 +1,56 @@
|
||||
# Pre-hook command
|
||||
To the STDIN of the pre-hook command, webhook will pass a JSON string representation of an object with the following properties:
|
||||
* `hookID` - ID of the hook that got matched
|
||||
* `method` - HTTP(s) method used by the client (i.e. GET, POST, etc...)
|
||||
* `URI` - URI which client requested
|
||||
* `host` - value of the `Host` header sent by the client
|
||||
* `remoteAddr` - client's IP address and port in the `IP:PORT` format
|
||||
* `query` - object with query parameters and their respective values
|
||||
* `headers` - object with headers and their respective values
|
||||
* `base64EncodedBody` - base64 encoded request body
|
||||
|
||||
_Please note!_ Output of this command __MUST__ be valid JSON string which will be parsed by the webhook and accessible using the `pre-hook` as source when referencing values.
|
||||
|
||||
__Important! Any errors encountered while trying to execute the pre-hook command will prevent the hook from triggering!__
|
||||
|
||||
# Examples
|
||||
|
||||
_Please note:_ Following examples use shell scripts as pre-hook commands, but it is possible to use ruby, python, or anything else you like, as long as it outputs a valid JSON string as the result.
|
||||
|
||||
_Make sure you have the `jq` command available, as we're using it to parse the JSON in the pre-hook script._
|
||||
|
||||
## Getting the IP address of the requester
|
||||
<details>
|
||||
<summary>script.sh</summary>
|
||||
|
||||
#!/bin/bash
|
||||
|
||||
ip=$1
|
||||
|
||||
echo $ip >> ips.txt
|
||||
</details>
|
||||
<details>
|
||||
<summary>prehook.sh</summary>
|
||||
|
||||
#!/bin/bash
|
||||
|
||||
context=$(cat)
|
||||
ip=`echo $context | jq -r '.remoteAddr' | cut -d ':' -f 1`
|
||||
|
||||
echo "{\"ip\": \"$ip\"}"
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>hooks.json</summary>
|
||||
|
||||
[
|
||||
{
|
||||
"id": "log-ip",
|
||||
"pre-hook-command": "/home/example/prehook.sh",
|
||||
"execute-command": "/home/example/script.sh",
|
||||
"pass-arguments-to-command": [
|
||||
{ "source": "pre-hook", "name": "ip" }
|
||||
]
|
||||
}
|
||||
]
|
||||
</details>
|
@ -1,7 +1,17 @@
|
||||
# Referencing request values
|
||||
There are three types of request values:
|
||||
There are four types of request values:
|
||||
|
||||
1. HTTP Request Header values
|
||||
1. Pre-hook values
|
||||
These are the values provided by the `pre-hook-command` output. More details on the pre-hook command can be found [here](PreHook-Command.md).
|
||||
|
||||
```json
|
||||
{
|
||||
"source": "pre-hook",
|
||||
"name": "parameter-name"
|
||||
}
|
||||
```
|
||||
|
||||
2. HTTP Request Header values
|
||||
|
||||
```json
|
||||
{
|
||||
@ -10,7 +20,7 @@ There are three types of request values:
|
||||
}
|
||||
```
|
||||
|
||||
2. HTTP Query parameters
|
||||
3. HTTP Query parameters
|
||||
|
||||
```json
|
||||
{
|
||||
@ -19,7 +29,7 @@ There are three types of request values:
|
||||
}
|
||||
```
|
||||
|
||||
3. Payload (JSON or form-value encoded)
|
||||
4. Payload (JSON or form-value encoded)
|
||||
```json
|
||||
{
|
||||
"source": "payload",
|
||||
|
13
hooks.json
Normal file
13
hooks.json
Normal file
@ -0,0 +1,13 @@
|
||||
[
|
||||
{
|
||||
"id": "test",
|
||||
"pre-hook-command": "/home/adnan/test.sh",
|
||||
"execute-command": "/bin/echo",
|
||||
"pass-arguments-to-command": [
|
||||
{
|
||||
"source": "payload",
|
||||
"name": "root"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
@ -17,7 +17,9 @@ import (
|
||||
"log"
|
||||
"math"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/textproto"
|
||||
"net/url"
|
||||
"os"
|
||||
"reflect"
|
||||
"regexp"
|
||||
@ -35,6 +37,7 @@ const (
|
||||
SourceQuery string = "url"
|
||||
SourceQueryAlias string = "query"
|
||||
SourcePayload string = "payload"
|
||||
SourcePreHook string = "pre-hook"
|
||||
SourceString string = "string"
|
||||
SourceEntirePayload string = "entire-payload"
|
||||
SourceEntireQuery string = "entire-query"
|
||||
@ -442,6 +445,8 @@ func (ha *Argument) Get(r *Request) (string, error) {
|
||||
source = &r.Query
|
||||
case SourcePayload:
|
||||
source = &r.Payload
|
||||
case SourcePreHook:
|
||||
source = &r.PreHook
|
||||
case SourceString:
|
||||
return ha.Name, nil
|
||||
case SourceEntirePayload:
|
||||
@ -527,10 +532,23 @@ func (h *HooksFiles) Set(value string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// PreHookContext is a structure consisted of request context data that will be passed to the pre-hook command
|
||||
type PreHookContext struct {
|
||||
HookID string `json:"hookID"`
|
||||
Method string `json:"method"`
|
||||
Base64EncodedBody string `json:"base64EncodedBody"`
|
||||
RemoteAddr string `json:"remoteAddr"`
|
||||
URI string `json:"URI"`
|
||||
Host string `json:"host"`
|
||||
Headers http.Header `json:"headers"`
|
||||
Query url.Values `json:"query"`
|
||||
}
|
||||
|
||||
// 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"`
|
||||
PreHookCommand string `json:"pre-hook-command,omitempty"`
|
||||
CommandWorkingDirectory string `json:"command-working-directory,omitempty"`
|
||||
ResponseMessage string `json:"response-message,omitempty"`
|
||||
ResponseHeaders ResponseHeaders `json:"response-headers,omitempty"`
|
||||
@ -575,6 +593,8 @@ func (h *Hook) ParseJSONParameters(r *Request) []error {
|
||||
source = &r.Headers
|
||||
case SourcePayload:
|
||||
source = &r.Payload
|
||||
case SourcePreHook:
|
||||
source = &r.PreHook
|
||||
case SourceQuery, SourceQueryAlias:
|
||||
source = &r.Query
|
||||
}
|
||||
@ -875,6 +895,7 @@ func (r MatchRule) Evaluate(req *Request) (bool, error) {
|
||||
if r.Type == IPWhitelist {
|
||||
return CheckIPWhitelist(req.RawRequest.RemoteAddr, r.IPRange)
|
||||
}
|
||||
|
||||
if r.Type == ScalrSignature {
|
||||
return CheckScalrSignature(req, r.Secret, true)
|
||||
}
|
||||
|
@ -253,20 +253,22 @@ func TestExtractParameter(t *testing.T) {
|
||||
}
|
||||
|
||||
var argumentGetTests = []struct {
|
||||
source, name string
|
||||
headers, query, payload map[string]interface{}
|
||||
value string
|
||||
ok bool
|
||||
source, name string
|
||||
headers, query, payload, prehook map[string]interface{}
|
||||
value string
|
||||
ok bool
|
||||
}{
|
||||
{"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},
|
||||
{"header", "a", map[string]interface{}{"A": "z"}, nil, nil, nil, "z", true},
|
||||
{"url", "a", nil, map[string]interface{}{"a": "z"}, nil, nil, "z", true},
|
||||
{"payload", "a", nil, nil, map[string]interface{}{"a": "z"}, nil, "z", true},
|
||||
{"prehook", "a", nil, nil, nil, map[string]interface{}{"a": "z"}, "z", true},
|
||||
{"string", "a", nil, nil, nil, nil, "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
|
||||
{"header", "a", nil, map[string]interface{}{"a": "z"}, map[string]interface{}{"a": "z"}, nil, "", false}, // nil headers
|
||||
{"url", "a", map[string]interface{}{"A": "z"}, nil, map[string]interface{}{"a": "z"}, nil, "", false}, // nil query
|
||||
{"payload", "a", map[string]interface{}{"A": "z"}, map[string]interface{}{"a": "z"}, nil, nil, "", false}, // nil payload
|
||||
{"prehook", "a", nil, nil, nil, nil, "", false}, // nil prehook
|
||||
{"foo", "a", map[string]interface{}{"A": "z"}, nil, nil, nil, "", false}, // invalid source
|
||||
}
|
||||
|
||||
func TestArgumentGet(t *testing.T) {
|
||||
@ -276,6 +278,7 @@ func TestArgumentGet(t *testing.T) {
|
||||
Headers: tt.headers,
|
||||
Query: tt.query,
|
||||
Payload: tt.payload,
|
||||
PreHook: tt.prehook,
|
||||
}
|
||||
value, err := a.Get(r)
|
||||
if (err == nil) != tt.ok || value != tt.value {
|
||||
@ -285,19 +288,20 @@ func TestArgumentGet(t *testing.T) {
|
||||
}
|
||||
|
||||
var hookParseJSONParametersTests = []struct {
|
||||
params []Argument
|
||||
headers, query, payload map[string]interface{}
|
||||
rheaders, rquery, rpayload map[string]interface{}
|
||||
ok bool
|
||||
params []Argument
|
||||
headers, query, payload, prehook map[string]interface{}
|
||||
rheaders, rquery, rpayload, rprehook map[string]interface{}
|
||||
ok bool
|
||||
}{
|
||||
{[]Argument{Argument{"header", "a", "", false}}, map[string]interface{}{"A": `{"b": "y"}`}, nil, nil, map[string]interface{}{"A": map[string]interface{}{"b": "y"}}, nil, nil, true},
|
||||
{[]Argument{Argument{"url", "a", "", false}}, nil, map[string]interface{}{"a": `{"b": "y"}`}, nil, nil, map[string]interface{}{"a": map[string]interface{}{"b": "y"}}, nil, true},
|
||||
{[]Argument{Argument{"payload", "a", "", false}}, nil, nil, map[string]interface{}{"a": `{"b": "y"}`}, nil, nil, map[string]interface{}{"a": map[string]interface{}{"b": "y"}}, true},
|
||||
{[]Argument{Argument{"header", "z", "", false}}, map[string]interface{}{"Z": `{}`}, nil, nil, map[string]interface{}{"Z": map[string]interface{}{}}, nil, nil, true},
|
||||
{[]Argument{Argument{"header", "a", "", false}}, map[string]interface{}{"A": `{"b": "y"}`}, nil, nil, nil, map[string]interface{}{"A": map[string]interface{}{"b": "y"}}, nil, nil, nil, true},
|
||||
{[]Argument{Argument{"url", "a", "", false}}, nil, map[string]interface{}{"a": `{"b": "y"}`}, nil, nil, nil, map[string]interface{}{"a": map[string]interface{}{"b": "y"}}, nil, nil, true},
|
||||
{[]Argument{Argument{"payload", "a", "", false}}, nil, nil, map[string]interface{}{"a": `{"b": "y"}`}, nil, nil, nil, map[string]interface{}{"a": map[string]interface{}{"b": "y"}}, nil, true},
|
||||
{[]Argument{Argument{"prehook", "a", "", false}}, nil, nil, nil, map[string]interface{}{"a": `{"b": "y"}`}, nil, nil, nil, map[string]interface{}{"a": map[string]interface{}{"b": "y"}}, true},
|
||||
{[]Argument{Argument{"header", "z", "", false}}, map[string]interface{}{"Z": `{}`}, nil, nil, nil, map[string]interface{}{"Z": map[string]interface{}{}}, nil, nil, nil, true},
|
||||
// failures
|
||||
{[]Argument{Argument{"header", "z", "", false}}, map[string]interface{}{"Z": ``}, nil, nil, map[string]interface{}{"Z": ``}, nil, nil, false}, // empty string
|
||||
{[]Argument{Argument{"header", "y", "", false}}, map[string]interface{}{"X": `{}`}, nil, nil, map[string]interface{}{"X": `{}`}, nil, nil, false}, // missing parameter
|
||||
{[]Argument{Argument{"string", "z", "", false}}, map[string]interface{}{"Z": ``}, nil, nil, map[string]interface{}{"Z": ``}, nil, nil, false}, // invalid argument source
|
||||
{[]Argument{Argument{"header", "z", "", false}}, map[string]interface{}{"Z": ``}, nil, nil, nil, map[string]interface{}{"Z": ``}, nil, nil, nil, false}, // empty string
|
||||
{[]Argument{Argument{"header", "y", "", false}}, map[string]interface{}{"X": `{}`}, nil, nil, nil, map[string]interface{}{"X": `{}`}, nil, nil, nil, false}, // missing parameter
|
||||
{[]Argument{Argument{"string", "z", "", false}}, map[string]interface{}{"Z": ``}, nil, nil, nil, map[string]interface{}{"Z": ``}, nil, nil, nil, false}, // invalid argument source
|
||||
}
|
||||
|
||||
func TestHookParseJSONParameters(t *testing.T) {
|
||||
@ -307,6 +311,7 @@ func TestHookParseJSONParameters(t *testing.T) {
|
||||
Headers: tt.headers,
|
||||
Query: tt.query,
|
||||
Payload: tt.payload,
|
||||
PreHook: tt.prehook,
|
||||
}
|
||||
err := h.ParseJSONParameters(r)
|
||||
if (err == nil) != tt.ok || !reflect.DeepEqual(tt.headers, tt.rheaders) {
|
||||
@ -316,15 +321,15 @@ func TestHookParseJSONParameters(t *testing.T) {
|
||||
}
|
||||
|
||||
var hookExtractCommandArgumentsTests = []struct {
|
||||
exec string
|
||||
args []Argument
|
||||
headers, query, payload map[string]interface{}
|
||||
value []string
|
||||
ok bool
|
||||
exec string
|
||||
args []Argument
|
||||
headers, query, payload, prehook map[string]interface{}
|
||||
value []string
|
||||
ok bool
|
||||
}{
|
||||
{"test", []Argument{Argument{"header", "a", "", false}}, map[string]interface{}{"A": "z"}, nil, nil, []string{"test", "z"}, true},
|
||||
{"test", []Argument{Argument{"header", "a", "", false}}, map[string]interface{}{"A": "z"}, nil, nil, nil, []string{"test", "z"}, true},
|
||||
// failures
|
||||
{"fail", []Argument{Argument{"payload", "a", "", false}}, map[string]interface{}{"A": "z"}, nil, nil, []string{"fail", ""}, false},
|
||||
{"fail", []Argument{Argument{"payload", "a", "", false}}, map[string]interface{}{"A": "z"}, nil, nil, nil, []string{"fail", ""}, false},
|
||||
}
|
||||
|
||||
func TestHookExtractCommandArguments(t *testing.T) {
|
||||
@ -334,6 +339,7 @@ func TestHookExtractCommandArguments(t *testing.T) {
|
||||
Headers: tt.headers,
|
||||
Query: tt.query,
|
||||
Payload: tt.payload,
|
||||
PreHook: tt.prehook,
|
||||
}
|
||||
value, err := h.ExtractCommandArguments(r)
|
||||
if (err == nil) != tt.ok || !reflect.DeepEqual(value, tt.value) {
|
||||
@ -362,24 +368,24 @@ func TestHookExtractCommandArguments(t *testing.T) {
|
||||
// }
|
||||
// ]
|
||||
var hookExtractCommandArgumentsForEnvTests = []struct {
|
||||
exec string
|
||||
args []Argument
|
||||
headers, query, payload map[string]interface{}
|
||||
value []string
|
||||
ok bool
|
||||
exec string
|
||||
args []Argument
|
||||
headers, query, payload, prehook map[string]interface{}
|
||||
value []string
|
||||
ok bool
|
||||
}{
|
||||
// successes
|
||||
{
|
||||
"test",
|
||||
[]Argument{Argument{"header", "a", "", false}},
|
||||
map[string]interface{}{"A": "z"}, nil, nil,
|
||||
map[string]interface{}{"A": "z"}, nil, nil, nil,
|
||||
[]string{"HOOK_a=z"},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"test",
|
||||
[]Argument{Argument{"header", "a", "MYKEY", false}},
|
||||
map[string]interface{}{"A": "z"}, nil, nil,
|
||||
map[string]interface{}{"A": "z"}, nil, nil, nil,
|
||||
[]string{"MYKEY=z"},
|
||||
true,
|
||||
},
|
||||
@ -387,7 +393,7 @@ var hookExtractCommandArgumentsForEnvTests = []struct {
|
||||
{
|
||||
"fail",
|
||||
[]Argument{Argument{"payload", "a", "", false}},
|
||||
map[string]interface{}{"A": "z"}, nil, nil,
|
||||
map[string]interface{}{"A": "z"}, nil, nil, nil,
|
||||
[]string{},
|
||||
false,
|
||||
},
|
||||
@ -400,6 +406,7 @@ func TestHookExtractCommandArgumentsForEnv(t *testing.T) {
|
||||
Headers: tt.headers,
|
||||
Query: tt.query,
|
||||
Payload: tt.payload,
|
||||
PreHook: tt.prehook,
|
||||
}
|
||||
value, err := h.ExtractCommandArgumentsForEnv(r)
|
||||
if (err == nil) != tt.ok || !reflect.DeepEqual(value, tt.value) {
|
||||
@ -479,43 +486,44 @@ func TestHooksMatch(t *testing.T) {
|
||||
var matchRuleTests = []struct {
|
||||
typ, regex, secret, value, ipRange string
|
||||
param Argument
|
||||
headers, query, payload map[string]interface{}
|
||||
headers, query, payload, prehook map[string]interface{}
|
||||
body []byte
|
||||
remoteAddr string
|
||||
ok bool
|
||||
err bool
|
||||
}{
|
||||
{"value", "", "", "z", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", true, false},
|
||||
{"regex", "^z", "", "z", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", true, false},
|
||||
{"payload-hmac-sha1", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "b17e04cbb22afa8ffbff8796fc1894ed27badd9e"}, nil, nil, []byte(`{"a": "z"}`), "", true, false},
|
||||
{"payload-hash-sha1", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "b17e04cbb22afa8ffbff8796fc1894ed27badd9e"}, nil, nil, []byte(`{"a": "z"}`), "", true, false},
|
||||
{"payload-hmac-sha256", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89"}, nil, nil, []byte(`{"a": "z"}`), "", true, false},
|
||||
{"payload-hash-sha256", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89"}, nil, nil, []byte(`{"a": "z"}`), "", true, false},
|
||||
{"value", "", "", "z", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "z"}, nil, nil, nil, []byte{}, "", true, false},
|
||||
{"regex", "^z", "", "z", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "z"}, nil, nil, nil, []byte{}, "", true, false},
|
||||
{"payload-hmac-sha1", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "b17e04cbb22afa8ffbff8796fc1894ed27badd9e"}, nil, nil, nil, []byte(`{"a": "z"}`), "", true, false},
|
||||
{"payload-hash-sha1", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "b17e04cbb22afa8ffbff8796fc1894ed27badd9e"}, nil, nil, nil, []byte(`{"a": "z"}`), "", true, false},
|
||||
{"payload-hmac-sha256", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89"}, nil, nil, nil, []byte(`{"a": "z"}`), "", true, false},
|
||||
{"payload-hash-sha256", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89"}, nil, nil, nil, []byte(`{"a": "z"}`), "", true, false},
|
||||
// failures
|
||||
{"value", "", "", "X", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", false, false},
|
||||
{"regex", "^X", "", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", false, false},
|
||||
{"value", "", "2", "X", "", Argument{"header", "a", "", false}, map[string]interface{}{"Y": "z"}, nil, nil, []byte{}, "", false, true}, // reference invalid header
|
||||
{"value", "", "", "X", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "z"}, nil, nil, nil, []byte{}, "", false, false},
|
||||
{"regex", "^X", "", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "z"}, nil, nil, nil, []byte{}, "", false, false},
|
||||
{"value", "", "2", "X", "", Argument{"header", "a", "", false}, map[string]interface{}{"Y": "z"}, nil, nil, nil, []byte{}, "", false, true}, // reference invalid header
|
||||
// errors
|
||||
{"regex", "*", "", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", false, true}, // invalid regex
|
||||
{"payload-hmac-sha1", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": ""}, nil, nil, []byte{}, "", false, true}, // invalid hmac
|
||||
{"payload-hash-sha1", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": ""}, nil, nil, []byte{}, "", false, true}, // invalid hmac
|
||||
{"payload-hmac-sha256", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": ""}, nil, nil, []byte{}, "", false, true}, // invalid hmac
|
||||
{"payload-hash-sha256", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": ""}, nil, nil, []byte{}, "", false, true}, // invalid hmac
|
||||
{"payload-hmac-sha512", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": ""}, nil, nil, []byte{}, "", false, true}, // invalid hmac
|
||||
{"payload-hash-sha512", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": ""}, nil, nil, []byte{}, "", false, true}, // invalid hmac
|
||||
{"regex", "*", "", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "z"}, nil, nil, nil, []byte{}, "", false, true}, // invalid regex
|
||||
{"payload-hmac-sha1", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": ""}, nil, nil, nil, []byte{}, "", false, true}, // invalid hmac
|
||||
{"payload-hash-sha1", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": ""}, nil, nil, nil, []byte{}, "", false, true}, // invalid hmac
|
||||
{"payload-hmac-sha256", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": ""}, nil, nil, nil, []byte{}, "", false, true}, // invalid hmac
|
||||
{"payload-hash-sha256", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": ""}, nil, nil, nil, []byte{}, "", false, true}, // invalid hmac
|
||||
{"payload-hmac-sha512", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": ""}, nil, nil, nil, []byte{}, "", false, true}, // invalid hmac
|
||||
{"payload-hash-sha512", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": ""}, nil, nil, nil, []byte{}, "", false, true}, // invalid hmac
|
||||
|
||||
// IP whitelisting, valid cases
|
||||
{"ip-whitelist", "", "", "", "192.168.0.1/24", Argument{}, nil, nil, nil, []byte{}, "192.168.0.2:9000", true, false}, // valid IPv4, with range
|
||||
{"ip-whitelist", "", "", "", "192.168.0.1/24", Argument{}, nil, nil, nil, []byte{}, "192.168.0.2:9000", true, false}, // valid IPv4, with range
|
||||
{"ip-whitelist", "", "", "", "192.168.0.1", Argument{}, nil, nil, nil, []byte{}, "192.168.0.1:9000", true, false}, // valid IPv4, no range
|
||||
{"ip-whitelist", "", "", "", "::1/24", Argument{}, nil, nil, nil, []byte{}, "[::1]:9000", true, false}, // valid IPv6, with range
|
||||
{"ip-whitelist", "", "", "", "::1", Argument{}, nil, nil, nil, []byte{}, "[::1]:9000", true, false}, // valid IPv6, no range
|
||||
{"ip-whitelist", "", "", "", "192.168.0.1/24", Argument{}, nil, nil, nil, nil, []byte{}, "192.168.0.2:9000", true, false}, // valid IPv4, with range
|
||||
{"ip-whitelist", "", "", "", "192.168.0.1/24", Argument{}, nil, nil, nil, nil, []byte{}, "192.168.0.2:9000", true, false}, // valid IPv4, with range
|
||||
{"ip-whitelist", "", "", "", "192.168.0.1", Argument{}, nil, nil, nil, nil, []byte{}, "192.168.0.1:9000", true, false}, // valid IPv4, no range
|
||||
{"ip-whitelist", "", "", "", "::1/24", Argument{}, nil, nil, nil, nil, []byte{}, "[::1]:9000", true, false}, // valid IPv6, with range
|
||||
{"ip-whitelist", "", "", "", "::1", Argument{}, nil, nil, nil, nil, []byte{}, "[::1]:9000", true, false}, // valid IPv6, no range
|
||||
// IP whitelisting, invalid cases
|
||||
{"ip-whitelist", "", "", "", "192.168.0.1/a", Argument{}, nil, nil, nil, []byte{}, "192.168.0.2:9000", false, true}, // invalid IPv4, with range
|
||||
{"ip-whitelist", "", "", "", "192.168.0.a", Argument{}, nil, nil, nil, []byte{}, "192.168.0.2:9000", false, true}, // invalid IPv4, no range
|
||||
{"ip-whitelist", "", "", "", "192.168.0.1/24", Argument{}, nil, nil, nil, []byte{}, "192.168.0.a:9000", false, true}, // invalid IPv4 address
|
||||
{"ip-whitelist", "", "", "", "::1/a", Argument{}, nil, nil, nil, []byte{}, "[::1]:9000", false, true}, // invalid IPv6, with range
|
||||
{"ip-whitelist", "", "", "", "::z", Argument{}, nil, nil, nil, []byte{}, "[::1]:9000", false, true}, // invalid IPv6, no range
|
||||
{"ip-whitelist", "", "", "", "::1/24", Argument{}, nil, nil, nil, []byte{}, "[::z]:9000", false, true}, // invalid IPv6 address
|
||||
{"ip-whitelist", "", "", "", "192.168.0.1/a", Argument{}, nil, nil, nil, nil, []byte{}, "192.168.0.2:9000", false, true}, // invalid IPv4, with range
|
||||
{"ip-whitelist", "", "", "", "192.168.0.a", Argument{}, nil, nil, nil, nil, []byte{}, "192.168.0.2:9000", false, true}, // invalid IPv4, no range
|
||||
{"ip-whitelist", "", "", "", "192.168.0.1/24", Argument{}, nil, nil, nil, nil, []byte{}, "192.168.0.a:9000", false, true}, // invalid IPv4 address
|
||||
{"ip-whitelist", "", "", "", "::1/a", Argument{}, nil, nil, nil, nil, []byte{}, "[::1]:9000", false, true}, // invalid IPv6, with range
|
||||
{"ip-whitelist", "", "", "", "::z", Argument{}, nil, nil, nil, nil, []byte{}, "[::1]:9000", false, true}, // invalid IPv6, no range
|
||||
{"ip-whitelist", "", "", "", "::1/24", Argument{}, nil, nil, nil, nil, []byte{}, "[::z]:9000", false, true}, // invalid IPv6 address
|
||||
}
|
||||
|
||||
func TestMatchRule(t *testing.T) {
|
||||
@ -525,6 +533,7 @@ func TestMatchRule(t *testing.T) {
|
||||
Headers: tt.headers,
|
||||
Query: tt.query,
|
||||
Payload: tt.payload,
|
||||
PreHook: tt.prehook,
|
||||
Body: tt.body,
|
||||
RawRequest: &http.Request{
|
||||
RemoteAddr: tt.remoteAddr,
|
||||
@ -538,12 +547,12 @@ func TestMatchRule(t *testing.T) {
|
||||
}
|
||||
|
||||
var andRuleTests = []struct {
|
||||
desc string // description of the test case
|
||||
rule AndRule
|
||||
headers, query, payload map[string]interface{}
|
||||
body []byte
|
||||
ok bool
|
||||
err bool
|
||||
desc string // description of the test case
|
||||
rule AndRule
|
||||
headers, query, payload, prehook map[string]interface{}
|
||||
body []byte
|
||||
ok bool
|
||||
err bool
|
||||
}{
|
||||
{
|
||||
"(a=z, b=y): a=z && b=y",
|
||||
@ -551,8 +560,7 @@ var andRuleTests = []struct {
|
||||
{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}},
|
||||
{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", "", false}, ""}},
|
||||
},
|
||||
map[string]interface{}{"A": "z", "B": "y"}, nil, nil,
|
||||
[]byte{},
|
||||
map[string]interface{}{"A": "z", "B": "y"}, nil, nil, nil, []byte{},
|
||||
true, false,
|
||||
},
|
||||
{
|
||||
@ -561,8 +569,7 @@ var andRuleTests = []struct {
|
||||
{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}},
|
||||
{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", "", false}, ""}},
|
||||
},
|
||||
map[string]interface{}{"A": "z", "B": "Y"}, nil, nil,
|
||||
[]byte{},
|
||||
map[string]interface{}{"A": "z", "B": "Y"}, nil, nil, nil, []byte{},
|
||||
false, false,
|
||||
},
|
||||
// Complex test to cover Rules.Evaluate
|
||||
@ -588,16 +595,15 @@ 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, nil, []byte{},
|
||||
true, false,
|
||||
},
|
||||
{"empty rule", AndRule{{}}, nil, nil, nil, nil, false, false},
|
||||
{"empty rule", AndRule{{}}, nil, nil, nil, nil, nil, false, false},
|
||||
// failures
|
||||
{
|
||||
"invalid rule",
|
||||
AndRule{{Match: &MatchRule{"value", "", "", "X", Argument{"header", "a", "", false}, ""}}},
|
||||
map[string]interface{}{"Y": "z"}, nil, nil, nil,
|
||||
map[string]interface{}{"Y": "z"}, nil, nil, nil, nil,
|
||||
false, true,
|
||||
},
|
||||
}
|
||||
@ -608,6 +614,7 @@ func TestAndRule(t *testing.T) {
|
||||
Headers: tt.headers,
|
||||
Query: tt.query,
|
||||
Payload: tt.payload,
|
||||
PreHook: tt.prehook,
|
||||
Body: tt.body,
|
||||
}
|
||||
ok, err := tt.rule.Evaluate(r)
|
||||
@ -618,12 +625,12 @@ func TestAndRule(t *testing.T) {
|
||||
}
|
||||
|
||||
var orRuleTests = []struct {
|
||||
desc string // description of the test case
|
||||
rule OrRule
|
||||
headers, query, payload map[string]interface{}
|
||||
body []byte
|
||||
ok bool
|
||||
err bool
|
||||
desc string // description of the test case
|
||||
rule OrRule
|
||||
headers, query, payload, prehook map[string]interface{}
|
||||
body []byte
|
||||
ok bool
|
||||
err bool
|
||||
}{
|
||||
{
|
||||
"(a=z, b=X): a=z || b=y",
|
||||
@ -631,8 +638,7 @@ var orRuleTests = []struct {
|
||||
{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}},
|
||||
{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", "", false}, ""}},
|
||||
},
|
||||
map[string]interface{}{"A": "z", "B": "X"}, nil, nil,
|
||||
[]byte{},
|
||||
map[string]interface{}{"A": "z", "B": "X"}, nil, nil, nil, []byte{},
|
||||
true, false,
|
||||
},
|
||||
{
|
||||
@ -641,8 +647,7 @@ var orRuleTests = []struct {
|
||||
{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}},
|
||||
{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", "", false}, ""}},
|
||||
},
|
||||
map[string]interface{}{"A": "X", "B": "y"}, nil, nil,
|
||||
[]byte{},
|
||||
map[string]interface{}{"A": "X", "B": "y"}, nil, nil, nil, []byte{},
|
||||
true, false,
|
||||
},
|
||||
{
|
||||
@ -651,8 +656,7 @@ var orRuleTests = []struct {
|
||||
{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}},
|
||||
{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", "", false}, ""}},
|
||||
},
|
||||
map[string]interface{}{"A": "Z", "B": "Y"}, nil, nil,
|
||||
[]byte{},
|
||||
map[string]interface{}{"A": "Z", "B": "Y"}, nil, nil, nil, []byte{},
|
||||
false, false,
|
||||
},
|
||||
// failures
|
||||
@ -661,8 +665,7 @@ var orRuleTests = []struct {
|
||||
OrRule{
|
||||
{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}},
|
||||
},
|
||||
map[string]interface{}{"Y": "Z"}, nil, nil,
|
||||
[]byte{},
|
||||
map[string]interface{}{"Y": "Z"}, nil, nil, nil, []byte{},
|
||||
false, false,
|
||||
},
|
||||
}
|
||||
@ -673,6 +676,7 @@ func TestOrRule(t *testing.T) {
|
||||
Headers: tt.headers,
|
||||
Query: tt.query,
|
||||
Payload: tt.payload,
|
||||
PreHook: tt.prehook,
|
||||
Body: tt.body,
|
||||
}
|
||||
ok, err := tt.rule.Evaluate(r)
|
||||
@ -683,15 +687,15 @@ func TestOrRule(t *testing.T) {
|
||||
}
|
||||
|
||||
var notRuleTests = []struct {
|
||||
desc string // description of the test case
|
||||
rule NotRule
|
||||
headers, query, payload map[string]interface{}
|
||||
body []byte
|
||||
ok bool
|
||||
err bool
|
||||
desc string // description of the test case
|
||||
rule NotRule
|
||||
headers, query, payload, prehook map[string]interface{}
|
||||
body []byte
|
||||
ok bool
|
||||
err bool
|
||||
}{
|
||||
{"(a=z): !a=X", NotRule{Match: &MatchRule{"value", "", "", "X", Argument{"header", "a", "", false}, ""}}, map[string]interface{}{"A": "z"}, nil, nil, []byte{}, true, false},
|
||||
{"(a=z): !a=z", NotRule{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}}, map[string]interface{}{"A": "z"}, nil, nil, []byte{}, false, false},
|
||||
{"(a=z): !a=X", NotRule{Match: &MatchRule{"value", "", "", "X", Argument{"header", "a", "", false}, ""}}, map[string]interface{}{"A": "z"}, nil, nil, nil, []byte{}, true, false},
|
||||
{"(a=z): !a=z", NotRule{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}}, map[string]interface{}{"A": "z"}, nil, nil, nil, []byte{}, false, false},
|
||||
}
|
||||
|
||||
func TestNotRule(t *testing.T) {
|
||||
@ -700,6 +704,7 @@ func TestNotRule(t *testing.T) {
|
||||
Headers: tt.headers,
|
||||
Query: tt.query,
|
||||
Payload: tt.payload,
|
||||
PreHook: tt.prehook,
|
||||
Body: tt.body,
|
||||
}
|
||||
ok, err := tt.rule.Evaluate(r)
|
||||
|
@ -31,6 +31,9 @@ type Request struct {
|
||||
// Payload is a map of the parsed payload.
|
||||
Payload map[string]interface{}
|
||||
|
||||
// PreHook is a map of the parsed pre-hook command result
|
||||
PreHook map[string]interface{}
|
||||
|
||||
// The underlying HTTP request.
|
||||
RawRequest *http.Request
|
||||
}
|
||||
|
102
webhook.go
102
webhook.go
@ -2,9 +2,11 @@ package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
@ -469,6 +471,106 @@ func hookHandler(w http.ResponseWriter, r *http.Request) {
|
||||
log.Printf("[%s] error parsing body payload due to unsupported content type header: %s\n", req.ID, req.ContentType)
|
||||
}
|
||||
|
||||
if matchedHook.PreHookCommand != "" {
|
||||
// check the command exists
|
||||
var searchPath string
|
||||
if filepath.IsAbs(matchedHook.PreHookCommand) || matchedHook.CommandWorkingDirectory == "" {
|
||||
searchPath = matchedHook.PreHookCommand
|
||||
} else {
|
||||
searchPath = filepath.Join(matchedHook.CommandWorkingDirectory, matchedHook.PreHookCommand)
|
||||
}
|
||||
|
||||
preHookCommandPath, err := exec.LookPath(searchPath)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("[%s] unable to locate pre-hook command: '%s', %+v\n", req.ID, matchedHook.PreHookCommand, err)
|
||||
// check if parameters specified in pre-hook command by mistake
|
||||
if strings.IndexByte(matchedHook.PreHookCommand, ' ') != -1 {
|
||||
s := strings.Fields(matchedHook.PreHookCommand)[0]
|
||||
log.Printf("[%s] please use a wrapper script to provide arguments to pre-hook command for '%s'\n", req.ID, s)
|
||||
}
|
||||
|
||||
writeHttpResponseCode(w, req.ID, matchedHook.ID, http.StatusInternalServerError)
|
||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
fmt.Fprint(w, "Error occurred while executing the hook's pre-hook command. Please check your logs for more details.")
|
||||
return
|
||||
}
|
||||
|
||||
preHookCommandStdin := hook.PreHookContext{
|
||||
HookID: matchedHook.ID,
|
||||
Method: r.Method,
|
||||
Base64EncodedBody: base64.StdEncoding.EncodeToString(req.Body),
|
||||
RemoteAddr: r.RemoteAddr,
|
||||
URI: r.RequestURI,
|
||||
Host: r.Host,
|
||||
Headers: r.Header,
|
||||
Query: r.URL.Query(),
|
||||
}
|
||||
|
||||
preHookCommandStdinJSONString, err := json.Marshal(preHookCommandStdin)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("[%s] unable to encode pre-hook context as JSON string for the pre-hook command: %+v\n", req.ID, err)
|
||||
|
||||
writeHttpResponseCode(w, req.ID, matchedHook.ID, http.StatusInternalServerError)
|
||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
fmt.Fprint(w, "Error occurred while executing the hook's pre-hook command. Please check your logs for more details.")
|
||||
return
|
||||
}
|
||||
preHookCommand := exec.Command(preHookCommandPath)
|
||||
preHookCommand.Dir = matchedHook.CommandWorkingDirectory
|
||||
preHookCommand.Env = append(os.Environ())
|
||||
|
||||
preHookCommandStdinPipe, err := preHookCommand.StdinPipe()
|
||||
|
||||
if err != nil {
|
||||
log.Printf("[%s] unable to acquire stdin pipe for the pre-hook command: %+v\n", req.ID, err)
|
||||
|
||||
writeHttpResponseCode(w, req.ID, matchedHook.ID, http.StatusInternalServerError)
|
||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
fmt.Fprint(w, "Error occurred while executing the hook's pre-hook command. Please check your logs for more details.")
|
||||
return
|
||||
}
|
||||
|
||||
_, err = io.WriteString(preHookCommandStdinPipe, string(preHookCommandStdinJSONString))
|
||||
|
||||
preHookCommandStdinPipe.Close()
|
||||
|
||||
if err != nil {
|
||||
log.Printf("[%s] unable to write to pre-hook command stdin: %+v\n", req.ID, err)
|
||||
|
||||
writeHttpResponseCode(w, req.ID, matchedHook.ID, http.StatusInternalServerError)
|
||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
fmt.Fprint(w, "Error occurred while executing the hook's pre-hook command. Please check your logs for more details.")
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("[%s] executing pre-hook command %s (%s) using %s as cwd\n", req.ID, matchedHook.PreHookCommand, preHookCommand.Path, preHookCommand.Dir)
|
||||
|
||||
preHookCommandOutput, err := preHookCommand.CombinedOutput()
|
||||
|
||||
if err != nil {
|
||||
log.Printf("[%s] unable to execute pre-hook command: %+v\n", req.ID, err)
|
||||
|
||||
writeHttpResponseCode(w, req.ID, matchedHook.ID, http.StatusInternalServerError)
|
||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
fmt.Fprint(w, "Error occurred while executing the hook's pre-hook command. Please check your logs for more details.")
|
||||
return
|
||||
}
|
||||
|
||||
JSONDecoder := json.NewDecoder(strings.NewReader(string(preHookCommandOutput)))
|
||||
JSONDecoder.UseNumber()
|
||||
|
||||
if err := JSONDecoder.Decode(&req.PreHook); err != nil {
|
||||
log.Printf("[%s] unable to parse pre-hook command output: %+v\npre-hook command output was: %+v\n", req.ID, err, string(preHookCommandOutput))
|
||||
|
||||
writeHttpResponseCode(w, req.ID, matchedHook.ID, http.StatusInternalServerError)
|
||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
fmt.Fprint(w, "Error occurred while executing the hook's pre-hook command. Please check your logs for more details.")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// handle hook
|
||||
errors := matchedHook.ParseJSONParameters(req)
|
||||
for _, err := range errors {
|
||||
|
Loading…
Reference in New Issue
Block a user