Merge pull request #34 from adnanh/development

Development
This commit is contained in:
Adnan Hajdarević 2015-06-06 14:28:54 +02:00
commit 005e723b23
4 changed files with 159 additions and 57 deletions

View file

@ -16,10 +16,13 @@ import (
// Constants used to specify the parameter source // Constants used to specify the parameter source
const ( const (
SourceHeader string = "header" SourceHeader string = "header"
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"`
}

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":
[ [
{ {

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("%s hook did not get triggered\n", hook.ID)
} }
log.Printf("finished handling %s\n", h.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() {

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("%s hook did not get triggered\n", hook.ID)
} }
log.Printf("finished handling %s\n", h.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() {