mirror of
https://github.com/adnanh/webhook.git
synced 2025-05-14 17:44:42 +00:00
Started work on multi file hooks loading
This commit is contained in:
parent
07f166616c
commit
8207c6cf12
3 changed files with 116 additions and 45 deletions
32
hook/hook.go
32
hook/hook.go
|
@ -273,7 +273,7 @@ func (h *ResponseHeaders) String() string {
|
||||||
result[idx] = fmt.Sprintf("%s=%s", responseHeader.Name, responseHeader.Value)
|
result[idx] = fmt.Sprintf("%s=%s", responseHeader.Name, responseHeader.Value)
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Sprint(strings.Join(result, ", "))
|
return strings.Join(result, ", ")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set method appends new Header object from header=value notation
|
// Set method appends new Header object from header=value notation
|
||||||
|
@ -288,6 +288,23 @@ func (h *ResponseHeaders) Set(value string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HooksFiles is a slice of String
|
||||||
|
type HooksFiles []string
|
||||||
|
|
||||||
|
func (h *HooksFiles) String() string {
|
||||||
|
if len(*h) == 0 {
|
||||||
|
return "hooks.json"
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(*h, ", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set method appends new string
|
||||||
|
func (h *HooksFiles) Set(value string) error {
|
||||||
|
*h = append(*h, value)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Hook type is a structure containing details for a single hook
|
// Hook type is a structure containing details for a single hook
|
||||||
type Hook struct {
|
type Hook struct {
|
||||||
ID string `json:"id,omitempty"`
|
ID string `json:"id,omitempty"`
|
||||||
|
@ -426,6 +443,19 @@ func (h *Hooks) LoadFromFile(path string) error {
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Append appends hooks unless the new hooks contain a hook with an ID that already exists
|
||||||
|
func (h *Hooks) Append(other *Hooks) error {
|
||||||
|
for _, hook := range *other {
|
||||||
|
if h.Match(hook.ID) != nil {
|
||||||
|
return fmt.Errorf("hook with ID %s is already defined", hook.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
*h = append(*h, hook)
|
||||||
|
}
|
||||||
|
|
||||||
|
return 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 {
|
||||||
|
|
|
@ -26,7 +26,7 @@ func watchForSignals() {
|
||||||
if sig == syscall.SIGUSR1 {
|
if sig == syscall.SIGUSR1 {
|
||||||
log.Println("caught USR1 signal")
|
log.Println("caught USR1 signal")
|
||||||
|
|
||||||
reloadHooks()
|
reloadAllHooks()
|
||||||
} else {
|
} else {
|
||||||
log.Printf("caught unhandled signal %+v\n", sig)
|
log.Printf("caught unhandled signal %+v\n", sig)
|
||||||
}
|
}
|
||||||
|
|
127
webhook.go
127
webhook.go
|
@ -21,7 +21,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
version = "2.6.0"
|
version = "2.6.1"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -30,7 +30,6 @@ var (
|
||||||
verbose = flag.Bool("verbose", false, "show verbose output")
|
verbose = flag.Bool("verbose", false, "show verbose output")
|
||||||
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")
|
|
||||||
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")
|
||||||
|
@ -38,20 +37,43 @@ var (
|
||||||
justDisplayVersion = flag.Bool("version", false, "display webhook version and quit")
|
justDisplayVersion = flag.Bool("version", false, "display webhook version and quit")
|
||||||
|
|
||||||
responseHeaders hook.ResponseHeaders
|
responseHeaders hook.ResponseHeaders
|
||||||
|
hooksFiles hook.HooksFiles
|
||||||
|
|
||||||
|
loadedHooksFromFiles = make(map[string]hook.Hooks)
|
||||||
|
|
||||||
watcher *fsnotify.Watcher
|
watcher *fsnotify.Watcher
|
||||||
signals chan os.Signal
|
signals chan os.Signal
|
||||||
|
|
||||||
hooks hook.Hooks
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func matchLoadedHook(id string) *hook.Hook {
|
||||||
hooks = hook.Hooks{}
|
for _, hooks := range loadedHooksFromFiles {
|
||||||
|
if hook := hooks.Match(id); hook != nil {
|
||||||
|
return hook
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func lenLoadedHooks() int {
|
||||||
|
sum := 0
|
||||||
|
for _, hooks := range loadedHooksFromFiles {
|
||||||
|
sum += len(hooks)
|
||||||
|
}
|
||||||
|
|
||||||
|
return sum
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.Var(&hooksFiles, "hooks", "path to the json file containing defined hooks the webhook should serve, use multiple times to load from different files")
|
||||||
flag.Var(&responseHeaders, "header", "response header to return, specified in format name=value, use multiple times to set multiple headers")
|
flag.Var(&responseHeaders, "header", "response header to return, specified in format name=value, use multiple times to set multiple headers")
|
||||||
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
|
if len(hooksFiles) == 0 {
|
||||||
|
log.Fatalln("you must specify at least one file to load hooks from")
|
||||||
|
}
|
||||||
|
|
||||||
if *justDisplayVersion {
|
if *justDisplayVersion {
|
||||||
fmt.Println("webhook version " + version)
|
fmt.Println("webhook version " + version)
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
|
@ -70,50 +92,54 @@ func main() {
|
||||||
setupSignals()
|
setupSignals()
|
||||||
|
|
||||||
// load and parse hooks
|
// load and parse hooks
|
||||||
log.Printf("attempting to load hooks from %s\n", *hooksFilePath)
|
for _, hooksFilePath := range hooksFiles {
|
||||||
|
log.Printf("attempting to load hooks from %s\n", hooksFilePath)
|
||||||
|
|
||||||
err := hooks.LoadFromFile(*hooksFilePath)
|
newHooks := hook.Hooks{}
|
||||||
|
|
||||||
if err != nil {
|
err := newHooks.LoadFromFile(hooksFilePath)
|
||||||
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)
|
if err != nil {
|
||||||
} else {
|
log.Printf("couldn't load hooks from file! %+v\n", err)
|
||||||
seenHooksIds := make(map[string]bool)
|
} else {
|
||||||
|
log.Printf("found %d hook(s) in file\n", len(newHooks))
|
||||||
|
|
||||||
log.Printf("found %d hook(s) in file\n", len(hooks))
|
for _, hook := range newHooks {
|
||||||
|
if matchLoadedHook(hook.ID) != nil {
|
||||||
for _, hook := range hooks {
|
log.Fatalf("error: hook with the id %s has already been loaded!\nplease check your hooks file for duplicate hooks ids!\n", hook.ID)
|
||||||
if seenHooksIds[hook.ID] == true {
|
}
|
||||||
log.Fatalf("error: hook with the id %s has already been loaded!\nplease check your hooks file for duplicate hooks ids!\n", hook.ID)
|
log.Printf("\tloaded: %s\n", hook.ID)
|
||||||
}
|
}
|
||||||
seenHooksIds[hook.ID] = true
|
|
||||||
log.Printf("\tloaded: %s\n", hook.ID)
|
loadedHooksFromFiles[hooksFilePath] = newHooks
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if *hotReload {
|
if !*verbose && !*noPanic && lenLoadedHooks() == 0 {
|
||||||
// set up file watcher
|
log.SetOutput(os.Stdout)
|
||||||
log.Printf("setting up file watcher for %s\n", *hooksFilePath)
|
log.Fatalln("couldn't load any hooks from file!\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")
|
||||||
|
}
|
||||||
|
|
||||||
|
if *hotReload {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
watcher, err = fsnotify.NewWatcher()
|
watcher, err = fsnotify.NewWatcher()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("error creating file watcher instance", err)
|
log.Fatal("error creating file watcher instance", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
defer watcher.Close()
|
defer watcher.Close()
|
||||||
|
|
||||||
go watchForFileChange()
|
for _, hooksFilePath := range hooksFiles {
|
||||||
|
// set up file watcher
|
||||||
|
log.Printf("setting up file watcher for %s\n", hooksFilePath)
|
||||||
|
|
||||||
err = watcher.Add(*hooksFilePath)
|
err = watcher.Add(hooksFilePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("error adding hooks file to the watcher", err)
|
log.Fatal("error adding hooks file to the watcher", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
go watchForFileChange()
|
||||||
}
|
}
|
||||||
|
|
||||||
l := negroni.NewLogger()
|
l := negroni.NewLogger()
|
||||||
|
@ -159,7 +185,7 @@ func hookHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
id := mux.Vars(r)["id"]
|
id := mux.Vars(r)["id"]
|
||||||
|
|
||||||
if matchedHook := hooks.Match(id); matchedHook != nil {
|
if matchedHook := matchLoadedHook(id); matchedHook != nil {
|
||||||
log.Printf("%s got matched\n", id)
|
log.Printf("%s got matched\n", id)
|
||||||
|
|
||||||
body, err := ioutil.ReadAll(r.Body)
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
|
@ -291,32 +317,48 @@ func handleHook(h *hook.Hook, headers, query, payload *map[string]interface{}, b
|
||||||
return string(out), err
|
return string(out), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func reloadHooks() {
|
func reloadHooks(hooksFilePath string) {
|
||||||
newHooks := hook.Hooks{}
|
hooksInFile := hook.Hooks{}
|
||||||
|
|
||||||
// parse and swap
|
// parse and swap
|
||||||
log.Printf("attempting to reload hooks from %s\n", *hooksFilePath)
|
log.Printf("attempting to reload hooks from %s\n", hooksFilePath)
|
||||||
|
|
||||||
err := newHooks.LoadFromFile(*hooksFilePath)
|
err := hooksInFile.LoadFromFile(hooksFilePath)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("couldn't load hooks from file! %+v\n", err)
|
log.Printf("couldn't load hooks from file! %+v\n", err)
|
||||||
} else {
|
} else {
|
||||||
seenHooksIds := make(map[string]bool)
|
seenHooksIds := make(map[string]bool)
|
||||||
|
|
||||||
log.Printf("found %d hook(s) in file\n", len(newHooks))
|
log.Printf("found %d hook(s) in file\n", len(hooksInFile))
|
||||||
|
|
||||||
for _, hook := range newHooks {
|
for _, hook := range hooksInFile {
|
||||||
if seenHooksIds[hook.ID] == true {
|
wasHookIDAlreadyLoaded := false
|
||||||
|
|
||||||
|
for _, loadedHook := range loadedHooksFromFiles[hooksFilePath] {
|
||||||
|
if loadedHook.ID == hook.ID {
|
||||||
|
wasHookIDAlreadyLoaded = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matchLoadedHook(hook.ID) != nil && !wasHookIDAlreadyLoaded) || seenHooksIds[hook.ID] == true {
|
||||||
log.Printf("error: hook with the id %s has already been loaded!\nplease check your hooks file for duplicate hooks ids!", hook.ID)
|
log.Printf("error: hook with the id %s has already been loaded!\nplease check your hooks file for duplicate hooks ids!", hook.ID)
|
||||||
log.Println("reverting hooks back to the previous configuration")
|
log.Println("reverting hooks back to the previous configuration")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
seenHooksIds[hook.ID] = true
|
seenHooksIds[hook.ID] = true
|
||||||
log.Printf("\tloaded: %s\n", hook.ID)
|
log.Printf("\tloaded: %s\n", hook.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
hooks = newHooks
|
loadedHooksFromFiles[hooksFilePath] = hooksInFile
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func reloadAllHooks() {
|
||||||
|
for _, hooksFilePath := range hooksFiles {
|
||||||
|
reloadHooks(hooksFilePath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -325,9 +367,8 @@ func watchForFileChange() {
|
||||||
select {
|
select {
|
||||||
case event := <-(*watcher).Events:
|
case event := <-(*watcher).Events:
|
||||||
if event.Op&fsnotify.Write == fsnotify.Write {
|
if event.Op&fsnotify.Write == fsnotify.Write {
|
||||||
log.Println("hooks file modified")
|
log.Printf("hooks file %s modified\n", event.Name)
|
||||||
|
reloadHooks(event.Name)
|
||||||
reloadHooks()
|
|
||||||
}
|
}
|
||||||
case err := <-(*watcher).Errors:
|
case err := <-(*watcher).Errors:
|
||||||
log.Println("watcher error:", err)
|
log.Println("watcher error:", err)
|
||||||
|
|
Loading…
Add table
Reference in a new issue