Compare commits

...

8 Commits

Author SHA1 Message Date
Adnan Hajdarević 005e723b23 Merge pull request #34 from adnanh/development
Development
2015-06-06 14:28:54 +02:00
Adnan Hajdarevic 9977fa8c61 refactor 2015-06-06 14:28:00 +02:00
Adnan Hajdarevic cbe2440cda add entire query and headers as well 2015-06-06 14:25:32 +02:00
Adnan Hajdarevic 9c545a745f return command output, pass whole payload as json to the command 2015-06-06 14:19:52 +02:00
Adnan Hajdarević 263c75b1b5 Update README.md 2015-06-05 11:21:52 +02:00
Adnan Hajdarević 83cbffd37c Merge pull request #33 from gitter-badger/gitter-badge
Add a Gitter chat badge to README.md
2015-06-05 11:21:06 +02:00
The Gitter Badger b310b79fb8 Added Gitter badge 2015-06-05 09:15:27 +00:00
Adnan Hajdarevic f1ebc440a4 match all hooks with the same id 2015-05-27 09:16:26 +02:00
5 changed files with 161 additions and 57 deletions
+2
View File
@@ -1,3 +1,5 @@
[![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)
# What is 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. [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.
+52
View File
@@ -20,6 +20,9 @@ const (
SourceQuery string = "url" SourceQuery string = "url"
SourcePayload string = "payload" SourcePayload string = "payload"
SourceString string = "string" SourceString string = "string"
SourceEntirePayload string = "entire-payload"
SourceEntireQuery string = "entire-query"
SourceEntireHeaders string = "entire-headers"
) )
// CheckPayloadSignature calculates and verifies SHA1 signature of the given payload // CheckPayloadSignature calculates and verifies SHA1 signature of the given payload
@@ -151,6 +154,30 @@ func (ha *Argument) Get(headers, query, payload *map[string]interface{}) (string
source = payload source = payload
case SourceString: case SourceString:
return ha.Name, true return ha.Name, true
case SourceEntirePayload:
r, err := json.Marshal(payload)
if err != nil {
return "", false
}
return string(r), true
case SourceEntireHeaders:
r, err := json.Marshal(headers)
if err != nil {
return "", false
}
return string(r), true
case SourceEntireQuery:
r, err := json.Marshal(query)
if err != nil {
return "", false
}
return string(r), true
} }
if source != nil { if source != nil {
@@ -166,6 +193,7 @@ type Hook struct {
ExecuteCommand string `json:"execute-command"` ExecuteCommand string `json:"execute-command"`
CommandWorkingDirectory string `json:"command-working-directory"` CommandWorkingDirectory string `json:"command-working-directory"`
ResponseMessage string `json:"response-message"` ResponseMessage string `json:"response-message"`
CaptureCommandOutput bool `json:"include-command-output-in-response"`
PassArgumentsToCommand []Argument `json:"pass-arguments-to-command"` PassArgumentsToCommand []Argument `json:"pass-arguments-to-command"`
JSONStringParameters []Argument `json:"parse-parameters-as-json"` JSONStringParameters []Argument `json:"parse-parameters-as-json"`
TriggerRule *Rules `json:"trigger-rule"` TriggerRule *Rules `json:"trigger-rule"`
@@ -260,6 +288,23 @@ func (h *Hooks) Match(id string) *Hook {
return nil return nil
} }
// MatchAll iterates through Hooks and returns all of the hooks that match the
// given ID, if no hook matches the given ID, nil is returned
func (h *Hooks) MatchAll(id string) []*Hook {
matchedHooks := make([]*Hook, 0)
for i := range *h {
if (*h)[i].ID == id {
matchedHooks = append(matchedHooks, &(*h)[i])
}
}
if len(matchedHooks) > 0 {
return matchedHooks
}
return nil
}
// Rules is a structure that contains one of the valid rule types // Rules is a structure that contains one of the valid rule types
type Rules struct { type Rules struct {
And *AndRule `json:"and"` And *AndRule `json:"and"`
@@ -368,3 +413,10 @@ func (r MatchRule) Evaluate(headers, query, payload *map[string]interface{}, bod
} }
return false 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"`
}
+1
View File
@@ -3,6 +3,7 @@
"id": "webhook", "id": "webhook",
"execute-command": "/home/adnan/redeploy-go-webhook.sh", "execute-command": "/home/adnan/redeploy-go-webhook.sh",
"command-working-directory": "/home/adnan/go", "command-working-directory": "/home/adnan/go",
"response-message": "I got the payload!",
"pass-arguments-to-command": "pass-arguments-to-command":
[ [
{ {
+48 -24
View File
@@ -25,7 +25,7 @@ import (
) )
const ( const (
version = "2.3.3" version = "2.3.4"
) )
var ( var (
@@ -144,10 +144,10 @@ func main() {
func hookHandler(w http.ResponseWriter, r *http.Request) { func hookHandler(w http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["id"] id := mux.Vars(r)["id"]
hook := hooks.Match(id) matchedHooks := hooks.MatchAll(id)
if hook != nil { if matchedHooks != nil {
log.Printf("%s got matched\n", id) log.Printf("%s got matched (%d time(s))\n", id, len(matchedHooks))
body, err := ioutil.ReadAll(r.Body) body, err := ioutil.ReadAll(r.Body)
if err != nil { if err != nil {
@@ -165,7 +165,7 @@ func hookHandler(w http.ResponseWriter, r *http.Request) {
contentType := r.Header.Get("Content-Type") 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 := json.NewDecoder(strings.NewReader(string(body)))
decoder.UseNumber() decoder.UseNumber()
@@ -174,7 +174,7 @@ func hookHandler(w http.ResponseWriter, r *http.Request) {
if err != nil { if err != nil {
log.Printf("error parsing JSON payload %+v\n", err) 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)) fd, err := url.ParseQuery(string(body))
if err != nil { if err != nil {
log.Printf("error parsing form payload %+v\n", err) log.Printf("error parsing form payload %+v\n", err)
@@ -183,40 +183,64 @@ func hookHandler(w http.ResponseWriter, r *http.Request) {
} }
} }
hook.ParseJSONParameters(&headers, &query, &payload)
// handle hook // handle hook
go handleHook(hook, &headers, &query, &payload, &body) 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)
// send the hook defined response message if h.CaptureCommandOutput {
fmt.Fprintf(w, hook.ResponseMessage) response := handleHook(h, &headers, &query, &payload, &body)
fmt.Fprintf(w, response)
} else {
go handleHook(h, &headers, &query, &payload, &body)
fmt.Fprintf(w, h.ResponseMessage)
}
return
}
}
// if none of the hooks got triggered
log.Printf("%s got matched (%d time(s)), but didn't get triggered because the trigger rules were not satisfied\n", matchedHooks[0].ID, len(matchedHooks))
w.WriteHeader(http.StatusBadRequest)
fmt.Fprintf(w, "Hook rules were not satisfied.")
} else { } else {
w.WriteHeader(http.StatusNotFound) w.WriteHeader(http.StatusNotFound)
fmt.Fprintf(w, "Hook not found.") fmt.Fprintf(w, "Hook not found.")
} }
} }
func handleHook(hook *hook.Hook, headers, query, payload *map[string]interface{}, body *[]byte) { func handleHook(h *hook.Hook, headers, query, payload *map[string]interface{}, body *[]byte) string {
if hook.TriggerRule == nil || hook.TriggerRule != nil && hook.TriggerRule.Evaluate(headers, query, payload, body) { cmd := exec.Command(h.ExecuteCommand)
log.Printf("%s hook triggered successfully\n", hook.ID) cmd.Args = h.ExtractCommandArguments(headers, query, payload)
cmd.Dir = h.CommandWorkingDirectory
cmd := exec.Command(hook.ExecuteCommand) log.Printf("executing %s (%s) with arguments %s using %s as cwd\n", h.ExecuteCommand, cmd.Path, cmd.Args, cmd.Dir)
cmd.Args = hook.ExtractCommandArguments(headers, query, payload)
cmd.Dir = hook.CommandWorkingDirectory
log.Printf("executing %s (%s) with arguments %s using %s as cwd\n", hook.ExecuteCommand, cmd.Path, cmd.Args, cmd.Dir) out, err := cmd.CombinedOutput()
out, err := cmd.Output() log.Printf("command output: %s\n", out)
log.Printf("stdout: %s\n", out) var errorResponse string
if err != nil { 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)
} else { log.Printf("finished handling %s\n", h.ID)
log.Printf("%s hook did not get triggered\n", hook.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() { func reloadHooks() {
+50 -25
View File
@@ -1,4 +1,4 @@
//+build windows //+build !windows
package main package main
@@ -23,7 +23,7 @@ import (
) )
const ( const (
version = "2.3.3" version = "2.3.4"
) )
var ( var (
@@ -38,6 +38,7 @@ var (
key = flag.String("key", "key.pem", "path to the HTTPS certificate private key pem file") key = flag.String("key", "key.pem", "path to the HTTPS certificate private key pem file")
watcher *fsnotify.Watcher watcher *fsnotify.Watcher
signals chan os.Signal
hooks hook.Hooks hooks hook.Hooks
) )
@@ -133,10 +134,10 @@ func main() {
func hookHandler(w http.ResponseWriter, r *http.Request) { func hookHandler(w http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["id"] id := mux.Vars(r)["id"]
hook := hooks.Match(id) matchedHooks := hooks.MatchAll(id)
if hook != nil { if matchedHooks != nil {
log.Printf("%s got matched\n", id) log.Printf("%s got matched (%d time(s))\n", id, len(matchedHooks))
body, err := ioutil.ReadAll(r.Body) body, err := ioutil.ReadAll(r.Body)
if err != nil { if err != nil {
@@ -154,7 +155,7 @@ func hookHandler(w http.ResponseWriter, r *http.Request) {
contentType := r.Header.Get("Content-Type") 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 := json.NewDecoder(strings.NewReader(string(body)))
decoder.UseNumber() decoder.UseNumber()
@@ -163,7 +164,7 @@ func hookHandler(w http.ResponseWriter, r *http.Request) {
if err != nil { if err != nil {
log.Printf("error parsing JSON payload %+v\n", err) 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)) fd, err := url.ParseQuery(string(body))
if err != nil { if err != nil {
log.Printf("error parsing form payload %+v\n", err) log.Printf("error parsing form payload %+v\n", err)
@@ -172,40 +173,64 @@ func hookHandler(w http.ResponseWriter, r *http.Request) {
} }
} }
hook.ParseJSONParameters(&headers, &query, &payload)
// handle hook // handle hook
go handleHook(hook, &headers, &query, &payload, &body) 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)
// send the hook defined response message if h.CaptureCommandOutput {
fmt.Fprintf(w, hook.ResponseMessage) response := handleHook(h, &headers, &query, &payload, &body)
fmt.Fprintf(w, response)
} else {
go handleHook(h, &headers, &query, &payload, &body)
fmt.Fprintf(w, h.ResponseMessage)
}
return
}
}
// if none of the hooks got triggered
log.Printf("%s got matched (%d time(s)), but didn't get triggered because the trigger rules were not satisfied\n", matchedHooks[0].ID, len(matchedHooks))
w.WriteHeader(http.StatusBadRequest)
fmt.Fprintf(w, "Hook rules were not satisfied.")
} else { } else {
w.WriteHeader(http.StatusNotFound) w.WriteHeader(http.StatusNotFound)
fmt.Fprintf(w, "Hook not found.") fmt.Fprintf(w, "Hook not found.")
} }
} }
func handleHook(hook *hook.Hook, headers, query, payload *map[string]interface{}, body *[]byte) { func handleHook(h *hook.Hook, headers, query, payload *map[string]interface{}, body *[]byte) string {
if hook.TriggerRule == nil || hook.TriggerRule != nil && hook.TriggerRule.Evaluate(headers, query, payload, body) { cmd := exec.Command(h.ExecuteCommand)
log.Printf("%s hook triggered successfully\n", hook.ID) cmd.Args = h.ExtractCommandArguments(headers, query, payload)
cmd.Dir = h.CommandWorkingDirectory
cmd := exec.Command(hook.ExecuteCommand) log.Printf("executing %s (%s) with arguments %s using %s as cwd\n", h.ExecuteCommand, cmd.Path, cmd.Args, cmd.Dir)
cmd.Args = hook.ExtractCommandArguments(headers, query, payload)
cmd.Dir = hook.CommandWorkingDirectory
log.Printf("executing %s (%s) with arguments %s using %s as cwd\n", hook.ExecuteCommand, cmd.Path, cmd.Args, cmd.Dir) out, err := cmd.CombinedOutput()
out, err := cmd.Output() log.Printf("command output: %s\n", out)
log.Printf("stdout: %s\n", out) var errorResponse string
if err != nil { 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)
} else { log.Printf("finished handling %s\n", h.ID)
log.Printf("%s hook did not get triggered\n", hook.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() { func reloadHooks() {