This commit is contained in:
xlr-8 2016-06-19 08:02:23 +00:00 committed by GitHub
commit a49b608c14
7 changed files with 345 additions and 18 deletions

View File

@ -12,6 +12,7 @@ import (
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
"os"
) )
// Constants used to specify the parameter source // Constants used to specify the parameter source
@ -400,6 +401,48 @@ func (h *Hooks) LoadFromFile(path string) error {
return e return e
} }
// LoadFromDir attempts to load hooks from specified Dir
func (h *Hooks) LoadFromDir(path string) ([]string, error) {
warnings := []string{}
loaded := fmt.Errorf("no single hooks loaded from directory %s", path)
if _, e := os.Stat(path); os.IsNotExist(e) {
if path == "" {return []string{"path '" + path + "' is unspecified"}, nil}
return []string{"path '" + path + "' is invalid"}, loaded
}
files, e := ioutil.ReadDir(path)
if e != nil {
return []string{path + " issue while readdir"}, e
} else if len(files) == 0 {
return []string{path + " is empty"}, loaded
}
// add '/' to path if missing
if path[len(path)-1] != '/' {path += "/"}
for _, file := range files {
tmp_hooks := Hooks{}
if file.IsDir() == true {
msg, e := tmp_hooks.LoadFromDir(path + file.Name())
if len(msg) != 0 {
warnings = append(warnings, msg...)
}
if e == nil {
*h = append(*h, tmp_hooks...)
}
} else {
e := tmp_hooks.LoadFromFile(path + file.Name())
if e != nil {
warnings = append(warnings, fmt.Sprintf("%s%s (%+v)", path, file.Name(), e))
} else {
*h = append(*h, tmp_hooks...)
}
}
}
if len(*h) == 0 {return warnings, loaded}
return warnings, nil
}
// Match iterates through Hooks and returns first one that matches the given ID, // Match iterates through Hooks and returns first one that matches the given ID,
// if no hook matches the given ID, nil is returned // if no hook matches the given ID, nil is returned
func (h *Hooks) Match(id string) *Hook { func (h *Hooks) Match(id string) *Hook {

View File

@ -3,6 +3,7 @@ package hook
import ( import (
"reflect" "reflect"
"testing" "testing"
"strings"
) )
var checkPayloadSignatureTests = []struct { var checkPayloadSignatureTests = []struct {
@ -217,6 +218,39 @@ func TestHooksLoadFromFile(t *testing.T) {
} }
} }
var hooksLoadFromDirTests = []struct {
path string
warn string
ok bool
}{
// ok - because at least one hook is loaded - exception for "" path
{"", "path '' is unspecified", true},
{"../test/hooks_dir.test", "../test/hooks_dir.test/depth1/depth2/depth3/depth4/empty_file.example (unexpected end of JSON input),../test/hooks_dir.test/depth1/depth2/depth3b is empty,../test/hooks_dir.test/depth1b/hooks-invalid.json.example (invalid character ':' after array element)", true},
{"../test/hooks_dir.test/depth1", "../test/hooks_dir.test/depth1/depth2/depth3/depth4/empty_file.example (unexpected end of JSON input),../test/hooks_dir.test/depth1/depth2/depth3b is empty", true},
{"../test/hooks_dir.test//depth1/depth2/", "../test/hooks_dir.test//depth1/depth2/depth3/depth4/empty_file.example (unexpected end of JSON input),../test/hooks_dir.test//depth1/depth2/depth3b is empty", true},
// failures - because no hook has been loaded
{"../test/hooks_dir.test///depth1b//", "../test/hooks_dir.test///depth1b//hooks-invalid.json.example (invalid character ':' after array element)", false},
{"../test/hooks_dir.test//depth1/depth2///depth3", "../test/hooks_dir.test//depth1/depth2///depth3/depth4/empty_file.example (unexpected end of JSON input)", false},
{"../test/hooks_dir.test/depth1/depth2/depth3b////", "../test/hooks_dir.test/depth1/depth2/depth3b//// is empty", false},
{"../test/hooks_dir.test/depth1/depth2/depth3/depth4/", "../test/hooks_dir.test/depth1/depth2/depth3/depth4/empty_file.example (unexpected end of JSON input)", false},
{"../test/hooks_dir.test/non-existing-dir", "path '../test/hooks_dir.test/non-existing-dir' is invalid", false},
}
func TestHooksLoadFromDir(t *testing.T) {
for _, tt := range hooksLoadFromDirTests {
h := &Hooks{}
warnings, err := h.LoadFromDir(tt.path)
// to simplify the comparison from the slice we received
warnings_string := strings.Join(warnings, ",")
if (err == nil) != tt.ok {
t.Errorf(err.Error())
}
if warnings_string != tt.warn {
t.Errorf("recevied: [%s]\nexpected [%s]", warnings_string, tt.warn)
}
}
}
var hooksMatchTests = []struct { var hooksMatchTests = []struct {
id string id string
hooks Hooks hooks Hooks

View File

@ -0,0 +1,79 @@
[
{
"id": "webhook-d2",
"execute-command": "/home/adnan/redeploy-go-webhook.sh",
"command-working-directory": "/home/adnan/go",
"response-message": "I got the payload!",
"response-headers":
[
{
"name": "Access-Control-Allow-Origin",
"value": "*"
}
],
"pass-arguments-to-command":
[
{
"source": "payload",
"name": "head_commit.id"
},
{
"source": "payload",
"name": "pusher.name"
},
{
"source": "payload",
"name": "pusher.email"
}
],
"trigger-rule":
{
"and":
[
{
"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"
}
}
}
]
}
},
{
"id": "simple-webhook-d2",
"execute-command": "/tmp/script1.sh",
"pass-arguments-to-command":
[
{
"source": "payload",
"name": "head_commit.id"
},
{
"source": "payload",
"name": "head_commit.author.name"
},
{
"source": "payload",
"name": "head_commit.author.email"
}
]
}
]

View File

@ -0,0 +1,79 @@
[
{
"id": "webhook",
"execute-command": "/home/adnan/redeploy-go-webhook.sh",
"command-working-directory": "/home/adnan/go",
"response-message": "I got the payload!",
"response-headers":
[
{
"name": "Access-Control-Allow-Origin",
"value": "*"
}
],
"pass-arguments-to-command":
[
{
"source": "payload",
"name": "head_commit.id"
},
{
"source": "payload",
"name": "pusher.name"
},
{
"source": "payload",
"name": "pusher.email"
}
],
"trigger-rule":
{
"and":
[
{
"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"
}
}
}
]
}
},
{
"id": "simple-webhook",
"execute-command": "/tmp/script1.sh",
"pass-arguments-to-command":
[
{
"source": "payload",
"name": "head_commit.id"
},
{
"source": "payload",
"name": "head_commit.author.name"
},
{
"source": "payload",
"name": "head_commit.author.email"
}
]
}
]

View File

@ -0,0 +1,74 @@
[
{
"id": "webhook",
"execute-command": "/home/adnan/redeploy-go-webhook.sh",
"command-working-directory": "/home/adnan/go",
"response-message": "I got the payload!",
"response-headers":
[
"value": "*"
}
],
"pass-arguments-to-command":
[
{
"source": "payload",
"name": "head_commit.id"
},
{
"source": "payload",
"name": "pusher.name"
},
{
"source": "payload",
"name": "pusher.email"
},
],
"trigger-rule":
{
"and":
[
{
"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"
{
"id": "simple-webhook",
"execute-command": "/tmp/script1.sh",
"pass-arguments-to-command":
[
{
"source": "payload",
"name": "head_commit.id"
},
{
"source": "payload",
"name": "head_commit.author.name"
},
{
"source": "payload",
"name": "head_commit.author.email"
}
]
}
]]]];

View File

@ -31,6 +31,7 @@ var (
noPanic = flag.Bool("nopanic", false, "do not panic if hooks cannot be loaded when webhook is not running in verbose mode") noPanic = flag.Bool("nopanic", false, "do not panic if hooks cannot be loaded when webhook is not running in verbose mode")
hotReload = flag.Bool("hotreload", false, "watch hooks file for changes and reload them automatically") hotReload = flag.Bool("hotreload", false, "watch hooks file for changes and reload them automatically")
hooksFilePath = flag.String("hooks", "hooks.json", "path to the json file containing defined hooks the webhook should serve") hooksFilePath = flag.String("hooks", "hooks.json", "path to the json file containing defined hooks the webhook should serve")
hooksDirPath = flag.String("hooksdir", "", "path to the json directory containing defined hooks the webhook should serve")
hooksURLPrefix = flag.String("urlprefix", "hooks", "url prefix to use for served hooks (protocol://yourserver:port/PREFIX/:hook-id)") hooksURLPrefix = flag.String("urlprefix", "hooks", "url prefix to use for served hooks (protocol://yourserver:port/PREFIX/:hook-id)")
secure = flag.Bool("secure", false, "use HTTPS instead of HTTP") secure = flag.Bool("secure", false, "use HTTPS instead of HTTP")
cert = flag.String("cert", "cert.pem", "path to the HTTPS certificate pem file") cert = flag.String("cert", "cert.pem", "path to the HTTPS certificate pem file")
@ -64,24 +65,7 @@ func main() {
setupSignals() setupSignals()
// load and parse hooks // load and parse hooks
log.Printf("attempting to load hooks from %s\n", *hooksFilePath) hooks = loadHooks()
err := hooks.LoadFromFile(*hooksFilePath)
if err != nil {
if !*verbose && !*noPanic {
log.SetOutput(os.Stdout)
log.Fatalf("couldn't load any hooks from file! %+v\naborting webhook execution since the -verbose flag is set to false.\nIf, for some reason, you want webhook to start without the hooks, either use -verbose flag, or -nopanic", err)
}
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)
}
}
if *hotReload { if *hotReload {
// set up file watcher // set up file watcher
@ -137,7 +121,41 @@ func main() {
log.Printf("starting insecure (http) webhook on %s:%d", *ip, *port) log.Printf("starting insecure (http) webhook on %s:%d", *ip, *port)
log.Fatal(http.ListenAndServe(fmt.Sprintf("%s:%d", *ip, *port), n)) log.Fatal(http.ListenAndServe(fmt.Sprintf("%s:%d", *ip, *port), n))
} }
}
func loadHooks() hook.Hooks {
log.Printf("attempting to load hooks from file %s\n", *hooksFilePath)
file_hooks := hook.Hooks{}
err_loadfile := file_hooks.LoadFromFile(*hooksFilePath)
log.Printf("attempting to load hooks from dir %s\n", *hooksDirPath)
dir_hooks := hook.Hooks{}
warnings, err_loaddir := dir_hooks.LoadFromDir(*hooksDirPath)
if *hooksDirPath != "" && len(warnings) != 0 {
log.Printf("faced issues while loading from %s:\n", *hooksDirPath)
for _, warning := range warnings {
log.Printf("\t> %s\n", warning)
}
}
if err_loadfile != nil && err_loaddir != nil {
if !*verbose && !*noPanic {
log.SetOutput(os.Stdout)
log.Printf("couldn't load any hooks from file and/or dir!\n")
log.Printf("if, for some reason, you want webhook to start without the hooks, either use -verbose flag, or -nopanic")
log.Fatal("aborting webhook execution since the -verbose flag is set to false.\n")
}
} else {
log.Printf("loaded %d hook(s) from file\n", len(file_hooks))
for _, hook := range file_hooks {
log.Printf("\t> %s\n", hook.ID)
}
log.Printf("loaded %d hook(s) from directory\n", len(dir_hooks))
for _, hook := range dir_hooks {
log.Printf("\t> %s\n", hook.ID)
}
}
return append(dir_hooks, file_hooks...)
} }
func hookHandler(w http.ResponseWriter, r *http.Request) { func hookHandler(w http.ResponseWriter, r *http.Request) {