Merge pull request #161 from moorereason/templates

Allow hooks file to be parsed as a template
This commit is contained in:
Adnan Hajdarević 2017-11-09 12:00:14 +01:00 committed by GitHub
commit 01aa178bb4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 157 additions and 11 deletions

View file

@ -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)
}

View file

@ -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
View 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
View 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

View file

@ -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)