wip
This commit is contained in:
parent
d4dacd6f8e
commit
4f1089495d
@ -32,7 +32,7 @@ Hooks are defined as objects in the JSON or YAML hooks configuration file. Pleas
|
||||
* `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 `context` as source when referencing values.
|
||||
* 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.
|
||||
|
||||
## Examples
|
||||
Check out [Hook examples page](Hook-Examples.md) for more complex examples of hooks.
|
||||
|
@ -1,12 +1,12 @@
|
||||
# Referencing request values
|
||||
There are four types of request values:
|
||||
|
||||
1. Context values
|
||||
1. Pre-hook values
|
||||
These are the values provided by the `pre-hook-command` output.
|
||||
|
||||
```json
|
||||
{
|
||||
"source": "context",
|
||||
"source": "pre-hook",
|
||||
"name": "parameter-name"
|
||||
}
|
||||
```
|
||||
|
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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
@ -35,7 +35,7 @@ const (
|
||||
SourceQuery string = "url"
|
||||
SourceQueryAlias string = "query"
|
||||
SourcePayload string = "payload"
|
||||
SourceContext string = "context"
|
||||
SourcePreHook string = "pre-hook"
|
||||
SourceString string = "string"
|
||||
SourceEntirePayload string = "entire-payload"
|
||||
SourceEntireQuery string = "entire-query"
|
||||
@ -443,8 +443,8 @@ func (ha *Argument) Get(r *Request) (string, error) {
|
||||
source = &r.Query
|
||||
case SourcePayload:
|
||||
source = &r.Payload
|
||||
case SourceContext:
|
||||
source = &r.Context
|
||||
case SourcePreHook:
|
||||
source = &r.PreHook
|
||||
case SourceString:
|
||||
return ha.Name, nil
|
||||
case SourceEntirePayload:
|
||||
@ -591,8 +591,8 @@ func (h *Hook) ParseJSONParameters(r *Request) []error {
|
||||
source = &r.Headers
|
||||
case SourcePayload:
|
||||
source = &r.Payload
|
||||
case SourceContext:
|
||||
source = &r.Context
|
||||
case SourcePreHook:
|
||||
source = &r.PreHook
|
||||
case SourceQuery, SourceQueryAlias:
|
||||
source = &r.Query
|
||||
}
|
||||
|
@ -254,20 +254,20 @@ func TestExtractParameter(t *testing.T) {
|
||||
|
||||
var argumentGetTests = []struct {
|
||||
source, name string
|
||||
headers, query, payload, context map[string]interface{}
|
||||
headers, query, payload, prehook map[string]interface{}
|
||||
value string
|
||||
ok bool
|
||||
}{
|
||||
{"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},
|
||||
{"context", "a", nil, nil, nil, map[string]interface{}{"a": "z"}, "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"}, 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
|
||||
{"context", "a", nil, nil, nil, nil, "", false}, // nil context
|
||||
{"prehook", "a", nil, nil, nil, nil, "", false}, // nil prehook
|
||||
{"foo", "a", map[string]interface{}{"A": "z"}, nil, nil, nil, "", false}, // invalid source
|
||||
}
|
||||
|
||||
@ -278,7 +278,7 @@ func TestArgumentGet(t *testing.T) {
|
||||
Headers: tt.headers,
|
||||
Query: tt.query,
|
||||
Payload: tt.payload,
|
||||
Context: tt.context,
|
||||
PreHook: tt.prehook,
|
||||
}
|
||||
value, err := a.Get(r)
|
||||
if (err == nil) != tt.ok || value != tt.value {
|
||||
@ -289,14 +289,14 @@ func TestArgumentGet(t *testing.T) {
|
||||
|
||||
var hookParseJSONParametersTests = []struct {
|
||||
params []Argument
|
||||
headers, query, payload, context map[string]interface{}
|
||||
rheaders, rquery, rpayload, rcontext map[string]interface{}
|
||||
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, 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{"context", "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{"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, nil, map[string]interface{}{"Z": ``}, nil, nil, nil, false}, // empty string
|
||||
@ -311,7 +311,7 @@ func TestHookParseJSONParameters(t *testing.T) {
|
||||
Headers: tt.headers,
|
||||
Query: tt.query,
|
||||
Payload: tt.payload,
|
||||
Context: tt.context,
|
||||
PreHook: tt.prehook,
|
||||
}
|
||||
err := h.ParseJSONParameters(r)
|
||||
if (err == nil) != tt.ok || !reflect.DeepEqual(tt.headers, tt.rheaders) {
|
||||
@ -323,7 +323,7 @@ func TestHookParseJSONParameters(t *testing.T) {
|
||||
var hookExtractCommandArgumentsTests = []struct {
|
||||
exec string
|
||||
args []Argument
|
||||
headers, query, payload, context map[string]interface{}
|
||||
headers, query, payload, prehook map[string]interface{}
|
||||
value []string
|
||||
ok bool
|
||||
}{
|
||||
@ -339,7 +339,7 @@ func TestHookExtractCommandArguments(t *testing.T) {
|
||||
Headers: tt.headers,
|
||||
Query: tt.query,
|
||||
Payload: tt.payload,
|
||||
Context: tt.context,
|
||||
PreHook: tt.prehook,
|
||||
}
|
||||
value, err := h.ExtractCommandArguments(r)
|
||||
if (err == nil) != tt.ok || !reflect.DeepEqual(value, tt.value) {
|
||||
@ -370,7 +370,7 @@ func TestHookExtractCommandArguments(t *testing.T) {
|
||||
var hookExtractCommandArgumentsForEnvTests = []struct {
|
||||
exec string
|
||||
args []Argument
|
||||
headers, query, payload, context map[string]interface{}
|
||||
headers, query, payload, prehook map[string]interface{}
|
||||
value []string
|
||||
ok bool
|
||||
}{
|
||||
@ -406,7 +406,7 @@ func TestHookExtractCommandArgumentsForEnv(t *testing.T) {
|
||||
Headers: tt.headers,
|
||||
Query: tt.query,
|
||||
Payload: tt.payload,
|
||||
Context: tt.context,
|
||||
PreHook: tt.prehook,
|
||||
}
|
||||
value, err := h.ExtractCommandArgumentsForEnv(r)
|
||||
if (err == nil) != tt.ok || !reflect.DeepEqual(value, tt.value) {
|
||||
@ -486,7 +486,7 @@ func TestHooksMatch(t *testing.T) {
|
||||
var matchRuleTests = []struct {
|
||||
typ, regex, secret, value, ipRange string
|
||||
param Argument
|
||||
headers, query, payload, context map[string]interface{}
|
||||
headers, query, payload, prehook map[string]interface{}
|
||||
body []byte
|
||||
remoteAddr string
|
||||
ok bool
|
||||
@ -533,7 +533,7 @@ func TestMatchRule(t *testing.T) {
|
||||
Headers: tt.headers,
|
||||
Query: tt.query,
|
||||
Payload: tt.payload,
|
||||
Context: tt.context,
|
||||
PreHook: tt.prehook,
|
||||
Body: tt.body,
|
||||
RawRequest: &http.Request{
|
||||
RemoteAddr: tt.remoteAddr,
|
||||
@ -549,7 +549,7 @@ func TestMatchRule(t *testing.T) {
|
||||
var andRuleTests = []struct {
|
||||
desc string // description of the test case
|
||||
rule AndRule
|
||||
headers, query, payload, context map[string]interface{}
|
||||
headers, query, payload, prehook map[string]interface{}
|
||||
body []byte
|
||||
ok bool
|
||||
err bool
|
||||
@ -614,7 +614,7 @@ func TestAndRule(t *testing.T) {
|
||||
Headers: tt.headers,
|
||||
Query: tt.query,
|
||||
Payload: tt.payload,
|
||||
Context: tt.context,
|
||||
PreHook: tt.prehook,
|
||||
Body: tt.body,
|
||||
}
|
||||
ok, err := tt.rule.Evaluate(r)
|
||||
@ -627,7 +627,7 @@ func TestAndRule(t *testing.T) {
|
||||
var orRuleTests = []struct {
|
||||
desc string // description of the test case
|
||||
rule OrRule
|
||||
headers, query, payload, context map[string]interface{}
|
||||
headers, query, payload, prehook map[string]interface{}
|
||||
body []byte
|
||||
ok bool
|
||||
err bool
|
||||
@ -676,7 +676,7 @@ func TestOrRule(t *testing.T) {
|
||||
Headers: tt.headers,
|
||||
Query: tt.query,
|
||||
Payload: tt.payload,
|
||||
Context: tt.context,
|
||||
PreHook: tt.prehook,
|
||||
Body: tt.body,
|
||||
}
|
||||
ok, err := tt.rule.Evaluate(r)
|
||||
@ -689,7 +689,7 @@ func TestOrRule(t *testing.T) {
|
||||
var notRuleTests = []struct {
|
||||
desc string // description of the test case
|
||||
rule NotRule
|
||||
headers, query, payload, context map[string]interface{}
|
||||
headers, query, payload, prehook map[string]interface{}
|
||||
body []byte
|
||||
ok bool
|
||||
err bool
|
||||
@ -704,7 +704,7 @@ func TestNotRule(t *testing.T) {
|
||||
Headers: tt.headers,
|
||||
Query: tt.query,
|
||||
Payload: tt.payload,
|
||||
Context: tt.context,
|
||||
PreHook: tt.prehook,
|
||||
Body: tt.body,
|
||||
}
|
||||
ok, err := tt.rule.Evaluate(r)
|
||||
|
@ -31,8 +31,8 @@ type Request struct {
|
||||
// Payload is a map of the parsed payload.
|
||||
Payload map[string]interface{}
|
||||
|
||||
// Context is a map of the parsed pre-hook command result
|
||||
Context 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
|
||||
|
75
webhook.go
75
webhook.go
@ -473,14 +473,14 @@ func hookHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
if matchedHook.PreHookCommand != "" {
|
||||
// check the command exists
|
||||
var lookpath string
|
||||
var searchPath string
|
||||
if filepath.IsAbs(matchedHook.PreHookCommand) || matchedHook.CommandWorkingDirectory == "" {
|
||||
lookpath = matchedHook.PreHookCommand
|
||||
searchPath = matchedHook.PreHookCommand
|
||||
} else {
|
||||
lookpath = filepath.Join(matchedHook.CommandWorkingDirectory, matchedHook.PreHookCommand)
|
||||
searchPath = filepath.Join(matchedHook.CommandWorkingDirectory, matchedHook.PreHookCommand)
|
||||
}
|
||||
|
||||
preHookCommandPath, err := exec.LookPath(lookpath)
|
||||
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)
|
||||
@ -489,7 +489,13 @@ func hookHandler(w http.ResponseWriter, r *http.Request) {
|
||||
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)
|
||||
}
|
||||
} else {
|
||||
|
||||
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,
|
||||
@ -501,36 +507,67 @@ func hookHandler(w http.ResponseWriter, r *http.Request) {
|
||||
Query: r.URL.Query(),
|
||||
}
|
||||
|
||||
if preHookCommandStdinJSONString, err := json.Marshal(preHookCommandStdin); err != nil {
|
||||
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)
|
||||
} else {
|
||||
|
||||
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())
|
||||
|
||||
if preHookCommandStdinPipe, err := preHookCommand.StdinPipe(); err != nil {
|
||||
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)
|
||||
} else {
|
||||
_, err := io.WriteString(preHookCommandStdinPipe, string(preHookCommandStdinJSONString))
|
||||
|
||||
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)
|
||||
} else {
|
||||
|
||||
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)
|
||||
|
||||
if preHookCommandOutput, err := preHookCommand.CombinedOutput(); err != nil {
|
||||
preHookCommandOutput, err := preHookCommand.CombinedOutput()
|
||||
|
||||
if err != nil {
|
||||
log.Printf("[%s] unable to execute pre-hook command: %+v\n", req.ID, err)
|
||||
} else {
|
||||
|
||||
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.Context); err != nil {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user