mirror of
https://github.com/adnanh/webhook.git
synced 2025-05-12 16:44:43 +00:00
Merge pull request #161 from moorereason/templates
Allow hooks file to be parsed as a template
This commit is contained in:
commit
01aa178bb4
5 changed files with 157 additions and 11 deletions
31
hook/hook.go
31
hook/hook.go
|
@ -1,6 +1,7 @@
|
|||
package hook
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
|
@ -18,6 +19,7 @@ import (
|
|||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
)
|
||||
|
@ -544,8 +546,10 @@ func (h *Hook) ExtractCommandArgumentsForFile(headers, query, payload *map[strin
|
|||
// 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 {
|
||||
// LoadFromFile attempts to load hooks from the specified file, which
|
||||
// can be either JSON or YAML. The asTemplate parameter causes the file
|
||||
// contents to be parsed as a Go text/template prior to unmarshalling.
|
||||
func (h *Hooks) LoadFromFile(path string, asTemplate bool) error {
|
||||
if path == "" {
|
||||
return nil
|
||||
}
|
||||
|
@ -557,6 +561,24 @@ func (h *Hooks) LoadFromFile(path string) error {
|
|||
return e
|
||||
}
|
||||
|
||||
if asTemplate {
|
||||
funcMap := template.FuncMap{"getenv": getenv}
|
||||
|
||||
tmpl, err := template.New("hooks").Funcs(funcMap).Parse(string(file))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
|
||||
err = tmpl.Execute(&buf, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
file = buf.Bytes()
|
||||
}
|
||||
|
||||
e = yaml.Unmarshal(file, h)
|
||||
return e
|
||||
}
|
||||
|
@ -705,3 +727,8 @@ func (r MatchRule) Evaluate(headers, query, payload *map[string]interface{}, bod
|
|||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// getenv provides a template function to retrieve OS environment variables.
|
||||
func getenv(s string) string {
|
||||
return os.Getenv(s)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package hook
|
||||
|
||||
import (
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
@ -229,26 +230,55 @@ func TestHookExtractCommandArgumentsForEnv(t *testing.T) {
|
|||
}
|
||||
|
||||
var hooksLoadFromFileTests = []struct {
|
||||
path string
|
||||
ok bool
|
||||
path string
|
||||
asTemplate bool
|
||||
ok bool
|
||||
}{
|
||||
{"../hooks.json.example", true},
|
||||
{"../hooks.yaml.example", true},
|
||||
{"", true},
|
||||
{"../hooks.json.example", false, true},
|
||||
{"../hooks.yaml.example", false, true},
|
||||
{"../hooks.json.tmpl.example", true, true},
|
||||
{"../hooks.yaml.tmpl.example", true, true},
|
||||
{"", false, true},
|
||||
// failures
|
||||
{"missing.json", false},
|
||||
{"missing.json", false, false},
|
||||
}
|
||||
|
||||
func TestHooksLoadFromFile(t *testing.T) {
|
||||
secret := `foo"123`
|
||||
os.Setenv("XXXTEST_SECRET", secret)
|
||||
|
||||
for _, tt := range hooksLoadFromFileTests {
|
||||
h := &Hooks{}
|
||||
err := h.LoadFromFile(tt.path)
|
||||
err := h.LoadFromFile(tt.path, tt.asTemplate)
|
||||
if (err == nil) != tt.ok {
|
||||
t.Errorf(err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHooksTemplateLoadFromFile(t *testing.T) {
|
||||
secret := `foo"123`
|
||||
os.Setenv("XXXTEST_SECRET", secret)
|
||||
|
||||
for _, tt := range hooksLoadFromFileTests {
|
||||
if !tt.asTemplate {
|
||||
continue
|
||||
}
|
||||
|
||||
h := &Hooks{}
|
||||
err := h.LoadFromFile(tt.path, tt.asTemplate)
|
||||
if (err == nil) != tt.ok {
|
||||
t.Errorf(err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
s := (*h.Match("webhook").TriggerRule.And)[0].Match.Secret
|
||||
if s != secret {
|
||||
t.Errorf("Expected secret of %q, got %q", secret, s)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var hooksMatchTests = []struct {
|
||||
id string
|
||||
hooks Hooks
|
||||
|
|
60
hooks.json.tmpl.example
Normal file
60
hooks.json.tmpl.example
Normal file
|
@ -0,0 +1,60 @@
|
|||
[
|
||||
{
|
||||
"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": "{{ getenv "XXXTEST_SECRET" | js }}",
|
||||
"parameter":
|
||||
{
|
||||
"source": "header",
|
||||
"name": "X-Hub-Signature"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"match":
|
||||
{
|
||||
"type": "value",
|
||||
"value": "refs/heads/master",
|
||||
"parameter":
|
||||
{
|
||||
"source": "payload",
|
||||
"name": "ref"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
28
hooks.yaml.tmpl.example
Normal file
28
hooks.yaml.tmpl.example
Normal file
|
@ -0,0 +1,28 @@
|
|||
- 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: "{{ getenv "XXXTEST_SECRET" | js }}"
|
||||
parameter:
|
||||
source: header
|
||||
name: X-Hub-Signature
|
||||
- match:
|
||||
type: value
|
||||
value: refs/heads/master
|
||||
parameter:
|
||||
source: payload
|
||||
name: ref
|
|
@ -34,6 +34,7 @@ var (
|
|||
hotReload = flag.Bool("hotreload", false, "watch hooks file for changes and reload them automatically")
|
||||
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")
|
||||
asTemplate = flag.Bool("template", false, "parse hooks file as a Go template")
|
||||
cert = flag.String("cert", "cert.pem", "path to the HTTPS certificate pem file")
|
||||
key = flag.String("key", "key.pem", "path to the HTTPS certificate private key pem file")
|
||||
justDisplayVersion = flag.Bool("version", false, "display webhook version and quit")
|
||||
|
@ -99,7 +100,7 @@ func main() {
|
|||
|
||||
newHooks := hook.Hooks{}
|
||||
|
||||
err := newHooks.LoadFromFile(hooksFilePath)
|
||||
err := newHooks.LoadFromFile(hooksFilePath, *asTemplate)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("couldn't load hooks from file! %+v\n", err)
|
||||
|
@ -407,7 +408,7 @@ func reloadHooks(hooksFilePath string) {
|
|||
// parse and swap
|
||||
log.Printf("attempting to reload hooks from %s\n", hooksFilePath)
|
||||
|
||||
err := hooksInFile.LoadFromFile(hooksFilePath)
|
||||
err := hooksInFile.LoadFromFile(hooksFilePath, *asTemplate)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("couldn't load hooks from file! %+v\n", err)
|
||||
|
|
Loading…
Add table
Reference in a new issue