mirror of
https://github.com/adnanh/webhook.git
synced 2025-05-23 05:42:30 +00:00
webhook 2.0.0
This commit is contained in:
parent
489750a710
commit
90528b2ed9
7 changed files with 377 additions and 630 deletions
|
@ -5,7 +5,6 @@ import (
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -20,11 +19,11 @@ func CheckPayloadSignature(payload []byte, secret string, signature string) (str
|
||||||
return expectedMAC, hmac.Equal([]byte(signature), []byte(expectedMAC))
|
return expectedMAC, hmac.Equal([]byte(signature), []byte(expectedMAC))
|
||||||
}
|
}
|
||||||
|
|
||||||
// FormValuesToMap converts url.Values to a map[string]interface{} object
|
// ValuesToMap converts map[string][]string to a map[string]string object
|
||||||
func FormValuesToMap(formValues url.Values) map[string]interface{} {
|
func ValuesToMap(values map[string][]string) map[string]interface{} {
|
||||||
ret := make(map[string]interface{})
|
ret := make(map[string]interface{})
|
||||||
|
|
||||||
for key, value := range formValues {
|
for key, value := range values {
|
||||||
if len(value) > 0 {
|
if len(value) > 0 {
|
||||||
ret[key] = value[0]
|
ret[key] = value[0]
|
||||||
}
|
}
|
||||||
|
@ -33,8 +32,12 @@ func FormValuesToMap(formValues url.Values) map[string]interface{} {
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExtractJSONParameter extracts value from payload based on the passed string
|
// ExtractParameter extracts value from interface{} based on the passed string
|
||||||
func ExtractJSONParameter(s string, params interface{}) (string, bool) {
|
func ExtractParameter(s string, params interface{}) (string, bool) {
|
||||||
|
if params == nil {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
var p []string
|
var p []string
|
||||||
|
|
||||||
if paramsValue := reflect.ValueOf(params); paramsValue.Kind() == reflect.Slice {
|
if paramsValue := reflect.ValueOf(params); paramsValue.Kind() == reflect.Slice {
|
||||||
|
@ -49,7 +52,7 @@ func ExtractJSONParameter(s string, params interface{}) (string, bool) {
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
|
|
||||||
return ExtractJSONParameter(p[2], params.([]map[string]interface{})[index])
|
return ExtractParameter(p[2], params.([]map[string]interface{})[index])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,7 +61,7 @@ func ExtractJSONParameter(s string, params interface{}) (string, bool) {
|
||||||
|
|
||||||
if p = strings.SplitN(s, ".", 2); len(p) > 1 {
|
if p = strings.SplitN(s, ".", 2); len(p) > 1 {
|
||||||
if pValue, ok := params.(map[string]interface{})[p[0]]; ok {
|
if pValue, ok := params.(map[string]interface{})[p[0]]; ok {
|
||||||
return ExtractJSONParameter(p[1], pValue)
|
return ExtractParameter(p[1], pValue)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if pValue, ok := params.(map[string]interface{})[p[0]]; ok {
|
if pValue, ok := params.(map[string]interface{})[p[0]]; ok {
|
||||||
|
|
214
hook/hook.go
Normal file
214
hook/hook.go
Normal file
|
@ -0,0 +1,214 @@
|
||||||
|
package hook
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"regexp"
|
||||||
|
|
||||||
|
"github.com/adnanh/webhook/helpers"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Constants used to specify the parameter source
|
||||||
|
const (
|
||||||
|
SourceHeader string = "header"
|
||||||
|
SourceQuery string = "url"
|
||||||
|
SourcePayload string = "payload"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Argument type specifies the parameter key name and the source it should
|
||||||
|
// be extracted from
|
||||||
|
type Argument struct {
|
||||||
|
Source string `json:"source"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get Argument method returns the value for the Argument's key name
|
||||||
|
// based on the Argument's source
|
||||||
|
func (ha *Argument) Get(headers, query, payload *map[string]interface{}) (string, bool) {
|
||||||
|
var source *map[string]interface{}
|
||||||
|
|
||||||
|
switch ha.Source {
|
||||||
|
case SourceHeader:
|
||||||
|
source = headers
|
||||||
|
case SourceQuery:
|
||||||
|
source = query
|
||||||
|
case SourcePayload:
|
||||||
|
source = payload
|
||||||
|
}
|
||||||
|
|
||||||
|
if source != nil {
|
||||||
|
return helpers.ExtractParameter(ha.Name, *source)
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hook type is a structure containing details for a single hook
|
||||||
|
type Hook struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
ExecuteCommand string `json:"execute-command"`
|
||||||
|
CommandWorkingDirectory string `json:"command-working-directory"`
|
||||||
|
PassArgumentsToCommand []Argument `json:"pass-arguments-to-command"`
|
||||||
|
TriggerRule *Rules `json:"trigger-rule"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractCommandArguments creates a list of arguments, based on the
|
||||||
|
// PassArgumentsToCommand property that is ready to be used with exec.Command()
|
||||||
|
func (h *Hook) ExtractCommandArguments(headers, query, payload *map[string]interface{}) []string {
|
||||||
|
var args = make([]string, 0)
|
||||||
|
|
||||||
|
args = append(args, h.ExecuteCommand)
|
||||||
|
|
||||||
|
for i := range h.PassArgumentsToCommand {
|
||||||
|
if arg, ok := h.PassArgumentsToCommand[i].Get(headers, query, payload); ok {
|
||||||
|
args = append(args, arg)
|
||||||
|
} else {
|
||||||
|
args = append(args, "")
|
||||||
|
log.Printf("couldn't retrieve argument for %+v\n", h.PassArgumentsToCommand[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return args
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hooks is an array of Hook objects
|
||||||
|
type Hooks []Hook
|
||||||
|
|
||||||
|
// LoadFromFile attempts to load hooks from specified JSON file
|
||||||
|
func (h *Hooks) LoadFromFile(path string) error {
|
||||||
|
if path == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse hook file for hooks
|
||||||
|
file, e := ioutil.ReadFile(path)
|
||||||
|
|
||||||
|
if e != nil {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
e = json.Unmarshal(file, h)
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match iterates through Hooks and returns first one that matches the given ID,
|
||||||
|
// if no hook matches the given ID, nil is returned
|
||||||
|
func (h *Hooks) Match(id string) *Hook {
|
||||||
|
for i := range *h {
|
||||||
|
if (*h)[i].ID == id {
|
||||||
|
return &(*h)[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rules is a structure that contains one of the valid rule types
|
||||||
|
type Rules struct {
|
||||||
|
And *AndRule `json:"and"`
|
||||||
|
Or *OrRule `json:"or"`
|
||||||
|
Not *NotRule `json:"not"`
|
||||||
|
Match *MatchRule `json:"match"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Evaluate finds the first rule property that is not nil and returns the value
|
||||||
|
// it evaluates to
|
||||||
|
func (r Rules) Evaluate(headers, query, payload *map[string]interface{}, body *[]byte) bool {
|
||||||
|
switch {
|
||||||
|
case r.And != nil:
|
||||||
|
return r.And.Evaluate(headers, query, payload, body)
|
||||||
|
case r.Or != nil:
|
||||||
|
return r.Or.Evaluate(headers, query, payload, body)
|
||||||
|
case r.Not != nil:
|
||||||
|
return r.Not.Evaluate(headers, query, payload, body)
|
||||||
|
case r.Match != nil:
|
||||||
|
return r.Match.Evaluate(headers, query, payload, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// AndRule will evaluate to true if and only if all of the ChildRules evaluate to true
|
||||||
|
type AndRule []Rules
|
||||||
|
|
||||||
|
// Evaluate AndRule will return true if and only if all of ChildRules evaluate to true
|
||||||
|
func (r AndRule) Evaluate(headers, query, payload *map[string]interface{}, body *[]byte) bool {
|
||||||
|
res := true
|
||||||
|
|
||||||
|
for _, v := range r {
|
||||||
|
res = res && v.Evaluate(headers, query, payload, body)
|
||||||
|
if res == false {
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// OrRule will evaluate to true if any of the ChildRules evaluate to true
|
||||||
|
type OrRule []Rules
|
||||||
|
|
||||||
|
// Evaluate OrRule will return true if any of ChildRules evaluate to true
|
||||||
|
func (r OrRule) Evaluate(headers, query, payload *map[string]interface{}, body *[]byte) bool {
|
||||||
|
res := false
|
||||||
|
|
||||||
|
for _, v := range r {
|
||||||
|
res = res || v.Evaluate(headers, query, payload, body)
|
||||||
|
if res == true {
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotRule will evaluate to true if any and only if the ChildRule evaluates to false
|
||||||
|
type NotRule Rules
|
||||||
|
|
||||||
|
// Evaluate NotRule will return true if and only if ChildRule evaluates to false
|
||||||
|
func (r NotRule) Evaluate(headers, query, payload *map[string]interface{}, body *[]byte) bool {
|
||||||
|
return !r.Evaluate(headers, query, payload, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatchRule will evaluate to true based on the type
|
||||||
|
type MatchRule struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Regex string `json:"regex"`
|
||||||
|
Secret string `json:"secret"`
|
||||||
|
Value string `json:"value"`
|
||||||
|
Parameter Argument `json:"parameter"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constants for the MatchRule type
|
||||||
|
const (
|
||||||
|
MatchValue string = "value"
|
||||||
|
MatchRegex string = "regex"
|
||||||
|
MatchHashSHA1 string = "payload-hash-sha1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Evaluate MatchRule will return based on the type
|
||||||
|
func (r MatchRule) Evaluate(headers, query, payload *map[string]interface{}, body *[]byte) bool {
|
||||||
|
if arg, ok := r.Parameter.Get(headers, query, payload); ok {
|
||||||
|
switch r.Type {
|
||||||
|
case MatchValue:
|
||||||
|
return arg == r.Value
|
||||||
|
case MatchRegex:
|
||||||
|
ok, err := regexp.MatchString(r.Regex, arg)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("error while trying to evaluate regex: %+v", err)
|
||||||
|
}
|
||||||
|
return ok
|
||||||
|
case MatchHashSHA1:
|
||||||
|
expected, ok := helpers.CheckPayloadSignature(*body, r.Secret, arg)
|
||||||
|
if !ok {
|
||||||
|
log.Printf("payload signature mismatch, expected %s got %s", expected, arg)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Printf("couldn't retrieve argument for %+v\n", r.Parameter)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
|
@ -1,18 +1,52 @@
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"id": "webhook",
|
"id": "webhook",
|
||||||
"command": "/home/adnan/redeploy-go-webhook.sh",
|
"execute-command": "/home/adnan/redeploy-go-webhook.sh",
|
||||||
"args": [
|
"command-working-directory": "/home/adnan/go",
|
||||||
"head"
|
"pass-arguments-to-command":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"source": "payload",
|
||||||
|
"name": "head"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "payload",
|
||||||
|
"name": "pusher.name"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "payload",
|
||||||
|
"name": "pusher.email"
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"cwd": "/home/adnan/go",
|
|
||||||
"trigger-rule":
|
"trigger-rule":
|
||||||
{
|
{
|
||||||
"match":
|
"and":
|
||||||
{
|
[
|
||||||
"parameter": "ref",
|
{
|
||||||
"value": "refs/heads/master"
|
"match":
|
||||||
}
|
{
|
||||||
|
"type": "payload-hash-sha1",
|
||||||
|
"secret": "mysecret",
|
||||||
|
"parameter":
|
||||||
|
{
|
||||||
|
"source": "header",
|
||||||
|
"name": "X-Hub-Signature"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"match":
|
||||||
|
{
|
||||||
|
"type": "value",
|
||||||
|
"value": "refs/heads/master",
|
||||||
|
"parameter":
|
||||||
|
{
|
||||||
|
"source": "payload",
|
||||||
|
"name": "ref"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
203
hooks/hooks.go
203
hooks/hooks.go
|
@ -1,203 +0,0 @@
|
||||||
package hooks
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/url"
|
|
||||||
|
|
||||||
"github.com/adnanh/webhook/helpers"
|
|
||||||
"github.com/adnanh/webhook/rules"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Hook is a structure that contains command to be executed
|
|
||||||
// and the current working directory name where that command should be executed
|
|
||||||
type Hook struct {
|
|
||||||
ID string `json:"id"`
|
|
||||||
Command string `json:"command"`
|
|
||||||
Cwd string `json:"cwd"`
|
|
||||||
Secret string `json:"secret"`
|
|
||||||
Args []string `json:"args"`
|
|
||||||
Rule rules.Rule `json:"trigger-rule"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hooks represents structure that contains list of Hook objects
|
|
||||||
// and the name of file which is correspondingly mapped to it
|
|
||||||
type Hooks struct {
|
|
||||||
fileName string
|
|
||||||
list []Hook
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseFormArgs gets arguments from the Form payload that should be passed to the command
|
|
||||||
func (h *Hook) ParseFormArgs(form url.Values) []string {
|
|
||||||
var args = make([]string, 0)
|
|
||||||
|
|
||||||
args = append(args, h.Command)
|
|
||||||
|
|
||||||
for i := range h.Args {
|
|
||||||
if arg := form[h.Args[i]]; len(arg) > 0 {
|
|
||||||
args = append(args, arg[0])
|
|
||||||
} else {
|
|
||||||
args = append(args, "")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return args
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseJSONArgs gets arguments from the JSON payload that should be passed to the command
|
|
||||||
func (h *Hook) ParseJSONArgs(payload interface{}) []string {
|
|
||||||
var args = make([]string, 0)
|
|
||||||
|
|
||||||
args = append(args, h.Command)
|
|
||||||
|
|
||||||
for i := range h.Args {
|
|
||||||
if arg, ok := helpers.ExtractJSONParameter(h.Args[i], payload); ok {
|
|
||||||
args = append(args, arg)
|
|
||||||
} else {
|
|
||||||
args = append(args, "")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return args
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON implementation for a single hook
|
|
||||||
func (h *Hook) UnmarshalJSON(j []byte) error {
|
|
||||||
m := make(map[string]interface{})
|
|
||||||
err := json.Unmarshal(j, &m)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if v, ok := m["id"]; ok {
|
|
||||||
h.ID = v.(string)
|
|
||||||
}
|
|
||||||
|
|
||||||
if v, ok := m["command"]; ok {
|
|
||||||
h.Command = v.(string)
|
|
||||||
}
|
|
||||||
|
|
||||||
if v, ok := m["cwd"]; ok {
|
|
||||||
h.Cwd = v.(string)
|
|
||||||
}
|
|
||||||
|
|
||||||
if v, ok := m["secret"]; ok {
|
|
||||||
h.Secret = v.(string)
|
|
||||||
}
|
|
||||||
|
|
||||||
if v, ok := m["args"]; ok {
|
|
||||||
h.Args = make([]string, 0)
|
|
||||||
|
|
||||||
for i := range v.([]interface{}) {
|
|
||||||
h.Args = append(h.Args, v.([]interface{})[i].(string))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if v, ok := m["trigger-rule"]; ok {
|
|
||||||
rule := v.(map[string]interface{})
|
|
||||||
|
|
||||||
if ruleValue, ok := rule["match"]; ok {
|
|
||||||
ruleString, _ := json.Marshal(ruleValue)
|
|
||||||
rulePtr := new(rules.MatchRule)
|
|
||||||
|
|
||||||
err = json.Unmarshal(ruleString, rulePtr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
h.Rule = *rulePtr
|
|
||||||
|
|
||||||
} else if ruleValue, ok := rule["not"]; ok {
|
|
||||||
ruleString, _ := json.Marshal(ruleValue)
|
|
||||||
rulePtr := new(rules.NotRule)
|
|
||||||
|
|
||||||
err = json.Unmarshal(ruleString, rulePtr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
h.Rule = *rulePtr
|
|
||||||
|
|
||||||
} else if ruleValue, ok := rule["and"]; ok {
|
|
||||||
ruleString, _ := json.Marshal(ruleValue)
|
|
||||||
rulePtr := new(rules.AndRule)
|
|
||||||
|
|
||||||
err = json.Unmarshal(ruleString, rulePtr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
h.Rule = *rulePtr
|
|
||||||
|
|
||||||
} else if ruleValue, ok := rule["or"]; ok {
|
|
||||||
ruleString, _ := json.Marshal(ruleValue)
|
|
||||||
rulePtr := new(rules.OrRule)
|
|
||||||
|
|
||||||
err = json.Unmarshal(ruleString, rulePtr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
h.Rule = *rulePtr
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// New creates an instance of Hooks, tries to unmarshal contents of hookFile
|
|
||||||
// and returns a pointer to the newly created instance
|
|
||||||
func New(hookFile string) (*Hooks, error) {
|
|
||||||
h := &Hooks{fileName: hookFile}
|
|
||||||
|
|
||||||
if hookFile == "" {
|
|
||||||
return h, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse hook file for hooks
|
|
||||||
file, e := ioutil.ReadFile(hookFile)
|
|
||||||
|
|
||||||
if e != nil {
|
|
||||||
return h, e
|
|
||||||
}
|
|
||||||
|
|
||||||
e = json.Unmarshal(file, &(h.list))
|
|
||||||
|
|
||||||
h.SetDefaults()
|
|
||||||
|
|
||||||
return h, e
|
|
||||||
}
|
|
||||||
|
|
||||||
// Match looks for the hook with the given id in the list of hooks
|
|
||||||
// and returns the pointer to the hook if it exists, or nil if it doesn't exist
|
|
||||||
func (h *Hooks) Match(id string, params interface{}) *Hook {
|
|
||||||
for i := range h.list {
|
|
||||||
if h.list[i].ID == id {
|
|
||||||
if h.list[i].Rule == nil || (h.list[i].Rule != nil && h.list[i].Rule.Evaluate(params)) {
|
|
||||||
return &h.list[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Count returns number of hooks in the list
|
|
||||||
func (h *Hooks) Count() int {
|
|
||||||
return len(h.list)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetDefaults sets default values that were ommited for hooks in JSON file
|
|
||||||
func (h *Hooks) SetDefaults() {
|
|
||||||
for i := range h.list {
|
|
||||||
if h.list[i].Cwd == "" {
|
|
||||||
h.list[i].Cwd = "."
|
|
||||||
}
|
|
||||||
|
|
||||||
if h.list[i].Args == nil {
|
|
||||||
h.list[i].Args = make([]string, 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
262
rules/rules.go
262
rules/rules.go
|
@ -1,262 +0,0 @@
|
||||||
package rules
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
|
|
||||||
"github.com/adnanh/webhook/helpers"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Rule interface
|
|
||||||
type Rule interface {
|
|
||||||
Evaluate(params interface{}) bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// AndRule type is a structure that contains list of rules (SubRules) that will be evaluated,
|
|
||||||
// and the AndRule's Evaluate method will evaluate to true if and only if all
|
|
||||||
// of the SubRules evaluate to true
|
|
||||||
type AndRule struct {
|
|
||||||
SubRules []Rule `json:"and"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// OrRule type is a structure that contains list of rules (SubRules) that will be evaluated,
|
|
||||||
// and the OrRule's Evaluate method will evaluate to true if any of the SubRules
|
|
||||||
// evaluate to true
|
|
||||||
type OrRule struct {
|
|
||||||
SubRules []Rule `json:"or"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NotRule type is a structure that contains a single rule (SubRule) that will be evaluated,
|
|
||||||
// and the OrRule's Evaluate method will evaluate to true if any and only if
|
|
||||||
// the SubRule evaluates to false
|
|
||||||
type NotRule struct {
|
|
||||||
SubRule Rule `json:"not"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// MatchRule type is a structure that contains MatchParameter structure
|
|
||||||
type MatchRule struct {
|
|
||||||
MatchParameter MatchParameter `json:"match"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// MatchParameter type is a structure that contains Parameter and Value which are used in
|
|
||||||
// Match
|
|
||||||
type MatchParameter struct {
|
|
||||||
Parameter string `json:"parameter"`
|
|
||||||
Value string `json:"value"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Evaluate AndRule will return true if and only if all of SubRules evaluate to true
|
|
||||||
func (r AndRule) Evaluate(params interface{}) bool {
|
|
||||||
res := true
|
|
||||||
|
|
||||||
for _, v := range r.SubRules {
|
|
||||||
res = res && v.Evaluate(params)
|
|
||||||
if res == false {
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
// Evaluate OrRule will return true if any of SubRules evaluate to true
|
|
||||||
func (r OrRule) Evaluate(params interface{}) bool {
|
|
||||||
res := false
|
|
||||||
|
|
||||||
for _, v := range r.SubRules {
|
|
||||||
res = res || v.Evaluate(params)
|
|
||||||
if res == true {
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
// Evaluate NotRule will return true if and only if SubRule evaluates to false
|
|
||||||
func (r NotRule) Evaluate(params interface{}) bool {
|
|
||||||
return !r.SubRule.Evaluate(params)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Evaluate MatchRule will return true if and only if the MatchParameter.Parameter
|
|
||||||
// named property value in supplied params matches the MatchParameter.Value
|
|
||||||
func (r MatchRule) Evaluate(params interface{}) bool {
|
|
||||||
if v, ok := helpers.ExtractJSONParameter(r.MatchParameter.Parameter, params); ok {
|
|
||||||
return v == r.MatchParameter.Value
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON implementation for the MatchRule type
|
|
||||||
func (r *MatchRule) UnmarshalJSON(j []byte) error {
|
|
||||||
err := json.Unmarshal(j, &r.MatchParameter)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON implementation for the NotRule type
|
|
||||||
func (r *NotRule) UnmarshalJSON(j []byte) error {
|
|
||||||
m := make(map[string]interface{})
|
|
||||||
err := json.Unmarshal(j, &m)
|
|
||||||
|
|
||||||
if ruleValue, ok := m["match"]; ok {
|
|
||||||
ruleString, _ := json.Marshal(ruleValue)
|
|
||||||
rulePtr := new(MatchRule)
|
|
||||||
|
|
||||||
err = json.Unmarshal(ruleString, &rulePtr.MatchParameter)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
r.SubRule = *rulePtr
|
|
||||||
|
|
||||||
} else if ruleValue, ok := m["not"]; ok {
|
|
||||||
ruleString, _ := json.Marshal(ruleValue)
|
|
||||||
rulePtr := new(NotRule)
|
|
||||||
|
|
||||||
err = json.Unmarshal(ruleString, rulePtr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
r.SubRule = *rulePtr
|
|
||||||
|
|
||||||
} else if ruleValue, ok := m["and"]; ok {
|
|
||||||
ruleString, _ := json.Marshal(ruleValue)
|
|
||||||
rulePtr := new(AndRule)
|
|
||||||
|
|
||||||
err = json.Unmarshal(ruleString, rulePtr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
r.SubRule = *rulePtr
|
|
||||||
|
|
||||||
} else if ruleValue, ok := m["or"]; ok {
|
|
||||||
ruleString, _ := json.Marshal(ruleValue)
|
|
||||||
rulePtr := new(OrRule)
|
|
||||||
|
|
||||||
err = json.Unmarshal(ruleString, rulePtr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
r.SubRule = *rulePtr
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON implementation for the AndRule type
|
|
||||||
func (r *AndRule) UnmarshalJSON(j []byte) error {
|
|
||||||
rules := new([]interface{})
|
|
||||||
err := json.Unmarshal(j, &rules)
|
|
||||||
|
|
||||||
for _, rulesValue := range *rules {
|
|
||||||
m := rulesValue.(map[string]interface{})
|
|
||||||
|
|
||||||
if ruleValue, ok := m["match"]; ok {
|
|
||||||
ruleString, _ := json.Marshal(ruleValue)
|
|
||||||
rulePtr := new(MatchRule)
|
|
||||||
|
|
||||||
err = json.Unmarshal(ruleString, &rulePtr.MatchParameter)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
r.SubRules = append(r.SubRules, *rulePtr)
|
|
||||||
|
|
||||||
} else if ruleValue, ok := m["not"]; ok {
|
|
||||||
ruleString, _ := json.Marshal(ruleValue)
|
|
||||||
rulePtr := new(NotRule)
|
|
||||||
|
|
||||||
err = json.Unmarshal(ruleString, rulePtr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
r.SubRules = append(r.SubRules, *rulePtr)
|
|
||||||
|
|
||||||
} else if ruleValue, ok := m["and"]; ok {
|
|
||||||
ruleString, _ := json.Marshal(ruleValue)
|
|
||||||
rulePtr := new(AndRule)
|
|
||||||
|
|
||||||
err = json.Unmarshal(ruleString, rulePtr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
r.SubRules = append(r.SubRules, *rulePtr)
|
|
||||||
|
|
||||||
} else if ruleValue, ok := m["or"]; ok {
|
|
||||||
ruleString, _ := json.Marshal(ruleValue)
|
|
||||||
rulePtr := new(OrRule)
|
|
||||||
|
|
||||||
err = json.Unmarshal(ruleString, rulePtr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
r.SubRules = append(r.SubRules, *rulePtr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON implementation for the OrRule type
|
|
||||||
func (r *OrRule) UnmarshalJSON(j []byte) error {
|
|
||||||
rules := new([]interface{})
|
|
||||||
err := json.Unmarshal(j, &rules)
|
|
||||||
|
|
||||||
for _, rulesValue := range *rules {
|
|
||||||
m := rulesValue.(map[string]interface{})
|
|
||||||
|
|
||||||
if ruleValue, ok := m["match"]; ok {
|
|
||||||
ruleString, _ := json.Marshal(ruleValue)
|
|
||||||
rulePtr := new(MatchRule)
|
|
||||||
|
|
||||||
err = json.Unmarshal(ruleString, &rulePtr.MatchParameter)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
r.SubRules = append(r.SubRules, *rulePtr)
|
|
||||||
|
|
||||||
} else if ruleValue, ok := m["not"]; ok {
|
|
||||||
ruleString, _ := json.Marshal(ruleValue)
|
|
||||||
rulePtr := new(NotRule)
|
|
||||||
|
|
||||||
err = json.Unmarshal(ruleString, rulePtr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
r.SubRules = append(r.SubRules, *rulePtr)
|
|
||||||
|
|
||||||
} else if ruleValue, ok := m["and"]; ok {
|
|
||||||
ruleString, _ := json.Marshal(ruleValue)
|
|
||||||
rulePtr := new(AndRule)
|
|
||||||
|
|
||||||
err = json.Unmarshal(ruleString, rulePtr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
r.SubRules = append(r.SubRules, *rulePtr)
|
|
||||||
|
|
||||||
} else if ruleValue, ok := m["or"]; ok {
|
|
||||||
ruleString, _ := json.Marshal(ruleValue)
|
|
||||||
rulePtr := new(OrRule)
|
|
||||||
|
|
||||||
err = json.Unmarshal(ruleString, rulePtr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
r.SubRules = append(r.SubRules, *rulePtr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
192
webhook.go
192
webhook.go
|
@ -5,134 +5,160 @@ import (
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/adnanh/webhook/helpers"
|
"github.com/adnanh/webhook/helpers"
|
||||||
"github.com/adnanh/webhook/hooks"
|
"github.com/adnanh/webhook/hook"
|
||||||
|
|
||||||
"github.com/go-martini/martini"
|
"github.com/codegangsta/negroni"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
l4g "code.google.com/p/log4go"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
version string = "1.0.4"
|
version = "2.0.0"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
webhooks *hooks.Hooks
|
ip = flag.String("ip", "", "ip the webhook should serve hooks on")
|
||||||
appStart time.Time
|
port = flag.Int("port", 9000, "port the webhook should serve hooks on")
|
||||||
ip = flag.String("ip", "", "ip the webhook server should listen on")
|
verbose = flag.Bool("verbose", false, "show verbose output")
|
||||||
port = flag.Int("port", 9000, "port the webhook server should listen on")
|
hooksFilePath = flag.String("hooks", "hooks.json", "path to the json file containing defined hooks the webhook should serve")
|
||||||
hooksFilename = flag.String("hooks", "hooks.json", "path to the json file containing defined hooks the webhook should serve")
|
|
||||||
logFilename = flag.String("log", "webhook.log", "path to the log file")
|
hooks hook.Hooks
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
hooks = hook.Hooks{}
|
||||||
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
fileLogWriter := l4g.NewFileLogWriter(*logFilename, false)
|
log.SetPrefix("[webhook] ")
|
||||||
fileLogWriter.SetRotateDaily(false)
|
log.SetFlags(log.Ldate | log.Ltime)
|
||||||
|
|
||||||
martini.Env = "production"
|
if !*verbose {
|
||||||
|
log.SetOutput(ioutil.Discard)
|
||||||
|
}
|
||||||
|
|
||||||
l4g.AddFilter("file", l4g.FINE, fileLogWriter)
|
log.Println("version " + version + " starting")
|
||||||
|
|
||||||
|
// load and parse hooks
|
||||||
|
log.Printf("attempting to load hooks from %s\n", *hooksFilePath)
|
||||||
|
|
||||||
|
err := hooks.LoadFromFile(*hooksFilePath)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("couldn't load hooks from file! %+v\n", err)
|
||||||
|
} else {
|
||||||
|
log.Printf("loaded %d hook(s) from file\n", len(hooks))
|
||||||
|
|
||||||
|
for _, hook := range hooks {
|
||||||
|
log.Printf("\t> %s\n", hook.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// set up file watcher
|
||||||
|
//log.Printf("setting up file watcher for %s\n", *hooksFilePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
appStart = time.Now()
|
l := log.New(os.Stdout, "[webhook] ", log.Ldate|log.Ltime)
|
||||||
var e error
|
|
||||||
|
|
||||||
webhooks, e = hooks.New(*hooksFilename)
|
negroniLogger := &negroni.Logger{l}
|
||||||
|
|
||||||
if e != nil {
|
negroniRecovery := &negroni.Recovery{
|
||||||
l4g.Warn("Error occurred while loading hooks from %s: %s", *hooksFilename, e)
|
Logger: l,
|
||||||
|
PrintStack: true,
|
||||||
|
StackAll: false,
|
||||||
|
StackSize: 1024 * 8,
|
||||||
}
|
}
|
||||||
|
|
||||||
web := martini.Classic()
|
n := negroni.New(negroniRecovery, negroniLogger)
|
||||||
|
|
||||||
web.Get("/", rootHandler)
|
router := mux.NewRouter()
|
||||||
web.Get("/hook/:id", hookHandler)
|
router.HandleFunc("/hooks/{id}", hookHandler)
|
||||||
web.Post("/hook/:id", hookHandler)
|
|
||||||
|
|
||||||
l4g.Info("Starting webhook %s with %d hook(s) on %s:%d", version, webhooks.Count(), *ip, *port)
|
n.UseHandler(router)
|
||||||
|
|
||||||
web.RunOnAddr(fmt.Sprintf("%s:%d", *ip, *port))
|
log.Fatal(http.ListenAndServe(fmt.Sprintf("%s:%d", *ip, *port), n))
|
||||||
|
|
||||||
|
log.Printf("listening on %s:%d", *ip, *port)
|
||||||
}
|
}
|
||||||
|
|
||||||
func rootHandler() string {
|
func hookHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
return fmt.Sprintf("webhook %s running for %s serving %d hook(s)\n", version, time.Since(appStart).String(), webhooks.Count())
|
id := mux.Vars(r)["id"]
|
||||||
}
|
|
||||||
|
|
||||||
func jsonHandler(id string, body []byte, signature string, payload interface{}) {
|
hook := hooks.Match(id)
|
||||||
if hook := webhooks.Match(id, payload); hook != nil {
|
|
||||||
if hook.Secret != "" {
|
|
||||||
if signature == "" {
|
|
||||||
l4g.Error("Hook %s got matched and contains the secret, but the request didn't contain any signature.", hook.ID)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if expectedMAC, ok := helpers.CheckPayloadSignature(body, hook.Secret, signature); !ok {
|
if hook != nil {
|
||||||
l4g.Error("Hook %s got matched and contains the secret, but the request contained invalid signature. Expected %s, got %s.", hook.ID, expectedMAC, signature)
|
log.Printf("%s got matched\n", id)
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := exec.Command(hook.Command)
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
cmd.Args = hook.ParseJSONArgs(payload)
|
|
||||||
cmd.Dir = hook.Cwd
|
|
||||||
out, err := cmd.Output()
|
|
||||||
l4g.Info("Hook %s triggered successfully! Command output:\n%s\n%+v", hook.ID, out, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func formHandler(id string, formValues url.Values) {
|
|
||||||
if hook := webhooks.Match(id, helpers.FormValuesToMap(formValues)); hook != nil {
|
|
||||||
cmd := exec.Command(hook.Command)
|
|
||||||
cmd.Args = hook.ParseFormArgs(formValues)
|
|
||||||
cmd.Dir = hook.Cwd
|
|
||||||
out, err := cmd.Output()
|
|
||||||
l4g.Info("Hook %s triggered successfully! Command output:\n%s\n%+v", hook.ID, out, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func hookHandler(req *http.Request, params martini.Params) string {
|
|
||||||
if req.Header.Get("Content-Type") == "application/json" {
|
|
||||||
defer req.Body.Close()
|
|
||||||
|
|
||||||
body, err := ioutil.ReadAll(req.Body)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l4g.Warn("Error occurred while trying to read the request body: %s", err)
|
log.Printf("error reading the request body. %+v\n", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
payloadJSON := make(map[string]interface{})
|
// parse headers
|
||||||
|
headers := helpers.ValuesToMap(r.Header)
|
||||||
|
|
||||||
decoder := json.NewDecoder(strings.NewReader(string(body)))
|
// parse query variables
|
||||||
decoder.UseNumber()
|
query := helpers.ValuesToMap(r.URL.Query())
|
||||||
|
|
||||||
err = decoder.Decode(&payloadJSON)
|
// parse body
|
||||||
|
var payload map[string]interface{}
|
||||||
|
|
||||||
if err != nil {
|
contentType := r.Header.Get("Content-Type")
|
||||||
l4g.Warn("Error occurred while trying to parse the payload as JSON: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
payloadSignature := ""
|
if contentType == "application/json" {
|
||||||
|
decoder := json.NewDecoder(strings.NewReader(string(body)))
|
||||||
|
decoder.UseNumber()
|
||||||
|
|
||||||
if strings.Contains(req.Header.Get("User-Agent"), "GitHub-Hookshot") {
|
err := decoder.Decode(&payload)
|
||||||
if len(req.Header.Get("X-Hub-Signature")) > 5 {
|
|
||||||
payloadSignature = req.Header.Get("X-Hub-Signature")[5:]
|
if err != nil {
|
||||||
|
log.Printf("error parsing JSON payload %+v\n", err)
|
||||||
|
}
|
||||||
|
} else if contentType == "application/x-www-form-urlencoded" {
|
||||||
|
fd, err := url.ParseQuery(string(body))
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("error parsing form payload %+v\n", err)
|
||||||
|
} else {
|
||||||
|
payload = helpers.ValuesToMap(fd)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
go jsonHandler(params["id"], body, payloadSignature, payloadJSON)
|
// handle hook
|
||||||
|
go handleHook(hook, &headers, &query, &payload, &body)
|
||||||
|
|
||||||
|
// say thanks
|
||||||
|
fmt.Fprintf(w, "Thanks.")
|
||||||
} else {
|
} else {
|
||||||
req.ParseForm()
|
fmt.Fprintf(w, "Hook not found.")
|
||||||
go formHandler(params["id"], req.Form)
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleHook(hook *hook.Hook, headers, query, payload *map[string]interface{}, body *[]byte) {
|
||||||
|
if hook.TriggerRule == nil || hook.TriggerRule != nil && hook.TriggerRule.Evaluate(headers, query, payload, body) {
|
||||||
|
log.Printf("%s hook triggered successfully\n", hook.ID)
|
||||||
|
|
||||||
|
cmd := exec.Command(hook.ExecuteCommand)
|
||||||
|
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.Output()
|
||||||
|
|
||||||
|
log.Printf("stdout: %s\n", out)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("stderr: %+v\n", err)
|
||||||
|
}
|
||||||
|
log.Printf("finished handling %s\n", hook.ID)
|
||||||
|
} else {
|
||||||
|
log.Printf("%s hook did not get triggered\n", hook.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
return "Got it, thanks. :-)"
|
|
||||||
}
|
}
|
||||||
|
|
65
webhook2.go
65
webhook2.go
|
@ -1,65 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/codegangsta/negroni"
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ip = flag.String("ip", "", "ip the webhook should serve hooks on")
|
|
||||||
port = flag.Int("port", 9000, "port the webhook should serve hooks on")
|
|
||||||
verbose = flag.Bool("verbose", false, "show verbose output")
|
|
||||||
hooksFilePath = flag.String("hooks", "hooks.json", "path to the json file containing defined hooks the webhook should serve")
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
log.SetPrefix("[webhook] ")
|
|
||||||
log.SetFlags(log.Ldate | log.Ltime)
|
|
||||||
|
|
||||||
if !*verbose {
|
|
||||||
log.SetOutput(ioutil.Discard)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Println("starting")
|
|
||||||
|
|
||||||
// load and parse hooks
|
|
||||||
log.Printf("attempting to load hooks from %s\n", *hooksFilePath)
|
|
||||||
|
|
||||||
// set up file watcher
|
|
||||||
log.Printf("setting up file watcher for %s\n", *hooksFilePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
router := mux.NewRouter()
|
|
||||||
router.HandleFunc("/hooks/{id}", hookHandler)
|
|
||||||
|
|
||||||
n := negroni.Classic()
|
|
||||||
n.UseHandler(router)
|
|
||||||
|
|
||||||
n.Run(fmt.Sprintf("%s:%d", *ip, *port))
|
|
||||||
}
|
|
||||||
|
|
||||||
func hookHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
id := vars["id"]
|
|
||||||
|
|
||||||
// parse headers
|
|
||||||
|
|
||||||
// parse body
|
|
||||||
|
|
||||||
// find hook
|
|
||||||
|
|
||||||
// trigger hook
|
|
||||||
|
|
||||||
// say thanks
|
|
||||||
|
|
||||||
fmt.Fprintf(w, "Thanks. %s %+v %+v %+v", id, vars, r.Header, r.Body)
|
|
||||||
}
|
|
Loading…
Add table
Add a link
Reference in a new issue