mirror of
https://github.com/adnanh/webhook.git
synced 2025-05-15 01:54:45 +00:00
initial commit
This commit is contained in:
commit
ffabc5541e
5 changed files with 557 additions and 0 deletions
0
README.md
Normal file
0
README.md
Normal file
40
hooks.json
Normal file
40
hooks.json
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "draos",
|
||||||
|
"command": "dir",
|
||||||
|
"secret": "",
|
||||||
|
"trigger-rule": {
|
||||||
|
"and": [
|
||||||
|
{
|
||||||
|
"match": {
|
||||||
|
"parameter": "commits.100.id",
|
||||||
|
"value": "20c16a7055dae65c69b9ca3f2e8cd1ac8fe9bbf2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"match": {
|
||||||
|
"parameter": "ref",
|
||||||
|
"value": "refs/heads/master"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"match": {
|
||||||
|
"parameter": "repository.id",
|
||||||
|
"value": "17341098"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "something",
|
||||||
|
"command": "",
|
||||||
|
"cwd": ".",
|
||||||
|
"trigger-rule": {
|
||||||
|
"match": {
|
||||||
|
"parameter": "second",
|
||||||
|
"value": "2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
146
hooks/hooks.go
Normal file
146
hooks/hooks.go
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
package hooks
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io/ioutil"
|
||||||
|
|
||||||
|
"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"`
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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["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}
|
||||||
|
|
||||||
|
// 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.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 = "."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
299
rules/rules.go
Normal file
299
rules/rules.go
Normal file
|
@ -0,0 +1,299 @@
|
||||||
|
package rules
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
func extract(s string, params interface{}) (string, bool) {
|
||||||
|
var p []string
|
||||||
|
|
||||||
|
if paramsValue := reflect.ValueOf(params); paramsValue.Kind() == reflect.Slice {
|
||||||
|
if paramsValueSliceLength := paramsValue.Len(); paramsValueSliceLength > 0 {
|
||||||
|
|
||||||
|
if p = strings.SplitN(s, ".", 3); len(p) > 3 {
|
||||||
|
index, err := strconv.ParseInt(p[1], 10, 64)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", false
|
||||||
|
} else if paramsValueSliceLength <= int(index) {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
return extract(p[2], params.([]map[string]interface{})[index])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
if p = strings.SplitN(s, ".", 2); len(p) > 1 {
|
||||||
|
if pValue, ok := params.(map[string]interface{})[p[0]]; ok {
|
||||||
|
return extract(p[1], pValue)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if pValue, ok := params.(map[string]interface{})[p[0]]; ok {
|
||||||
|
return fmt.Sprintf("%v", pValue), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 := extract(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
|
||||||
|
}
|
72
webhook.go
Normal file
72
webhook.go
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"os/exec"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/adnanh/webhook/hooks"
|
||||||
|
|
||||||
|
"github.com/go-martini/martini"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
version string = "1.0.0"
|
||||||
|
ip string = ""
|
||||||
|
port int = 9000
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
webhooks *hooks.Hooks
|
||||||
|
appStart time.Time
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
appStart = time.Now()
|
||||||
|
var e error
|
||||||
|
|
||||||
|
webhooks, e = hooks.New("hooks.json")
|
||||||
|
|
||||||
|
if e != nil {
|
||||||
|
fmt.Printf("Error while loading hooks from hooks.json:\n\t>>> %s\n", e)
|
||||||
|
}
|
||||||
|
|
||||||
|
web := martini.Classic()
|
||||||
|
|
||||||
|
web.Get("/", rootHandler)
|
||||||
|
web.Get("/hook/:id", hookHandler)
|
||||||
|
web.Post("/hook/:id", hookHandler)
|
||||||
|
|
||||||
|
fmt.Printf("Starting go-webhook with %d hook(s)\n\n", webhooks.Count())
|
||||||
|
|
||||||
|
web.RunOnAddr(fmt.Sprintf("%s:%d", ip, port))
|
||||||
|
}
|
||||||
|
|
||||||
|
func rootHandler() string {
|
||||||
|
return fmt.Sprintf("go-webhook %s running for %s serving %d hook(s)\n%+v", version, time.Since(appStart).String(), webhooks.Count(), webhooks)
|
||||||
|
}
|
||||||
|
|
||||||
|
func hookHandler(req *http.Request, params martini.Params) string {
|
||||||
|
|
||||||
|
decoder := json.NewDecoder(req.Body)
|
||||||
|
decoder.UseNumber()
|
||||||
|
|
||||||
|
p := make(map[string]interface{})
|
||||||
|
|
||||||
|
err := decoder.Decode(&p)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "Error occurred while parsing the payload :-("
|
||||||
|
}
|
||||||
|
|
||||||
|
go func(id string, params interface{}) {
|
||||||
|
if hook := webhooks.Match(id, params); hook != nil {
|
||||||
|
cmd := exec.Command(hook.Command, "", "", hook.Cwd)
|
||||||
|
out, _ := cmd.Output()
|
||||||
|
fmt.Printf("Command output for %v >>> %s\n", hook, out)
|
||||||
|
}
|
||||||
|
}(params["id"], p)
|
||||||
|
return "Got it, thanks. :-)"
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue