Merge 1db3be532b
into 3bcf6d5e2b
This commit is contained in:
commit
a49b608c14
43
hook/hook.go
43
hook/hook.go
@ -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 {
|
||||||
|
@ -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
|
||||||
|
79
test/hooks_dir.test/depth1/depth2/hooks.json.example
Normal file
79
test/hooks_dir.test/depth1/depth2/hooks.json.example
Normal 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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
79
test/hooks_dir.test/depth1/hooks.json.example
Normal file
79
test/hooks_dir.test/depth1/hooks.json.example
Normal 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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
74
test/hooks_dir.test/depth1b/hooks-invalid.json.example
Normal file
74
test/hooks_dir.test/depth1b/hooks-invalid.json.example
Normal 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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]]]];
|
54
webhook.go
54
webhook.go
@ -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) {
|
||||||
|
Loading…
Reference in New Issue
Block a user