From 18b0573bc468d8e44f52ab0865371024512128f6 Mon Sep 17 00:00:00 2001 From: Florent Aide Date: Thu, 26 May 2016 23:33:56 +0200 Subject: [PATCH] Add support for naming env variables (#75) * Adding ignore patterns * Adding support for env var naming * Fixed typo in docstring * Adding tests for the env var extraction w & w/o explicit naming * remove coverage script from ignore patterns * Adding the coverage script to help see which code is tested and which is not * remove coverage script from sources * Ignore coverage script from sources tree --- .gitignore | 4 ++ hook/hook.go | 13 ++++- hook/hook_test.go | 137 +++++++++++++++++++++++++++++++++------------- 3 files changed, 113 insertions(+), 41 deletions(-) diff --git a/.gitignore b/.gitignore index e69de29..ee20f29 100644 --- a/.gitignore +++ b/.gitignore @@ -0,0 +1,4 @@ +.idea +.cover +coverage +webhook diff --git a/hook/hook.go b/hook/hook.go index d23993e..d0115e8 100644 --- a/hook/hook.go +++ b/hook/hook.go @@ -196,8 +196,9 @@ func ExtractParameterAsString(s string, params interface{}) (string, bool) { // Argument type specifies the parameter key name and the source it should // be extracted from type Argument struct { - Source string `json:"source,omitempty"` - Name string `json:"name,omitempty"` + Source string `json:"source,omitempty"` + Name string `json:"name,omitempty"` + EnvName string `json:"envname,omitempty"` } // Get Argument method returns the value for the Argument's key name @@ -364,7 +365,13 @@ func (h *Hook) ExtractCommandArgumentsForEnv(headers, query, payload *map[string for i := range h.PassEnvironmentToCommand { if arg, ok := h.PassEnvironmentToCommand[i].Get(headers, query, payload); ok { - args = append(args, EnvNamespace+h.PassEnvironmentToCommand[i].Name+"="+arg) + if h.PassEnvironmentToCommand[i].EnvName != "" { + // first try to use the EnvName if specified + args = append(args, EnvNamespace+h.PassEnvironmentToCommand[i].EnvName+"="+arg) + } else { + // then fallback on the name + args = append(args, EnvNamespace+h.PassEnvironmentToCommand[i].Name+"="+arg) + } } else { return args, &ArgumentError{h.PassEnvironmentToCommand[i]} } diff --git a/hook/hook_test.go b/hook/hook_test.go index f4d5b8f..472787a 100644 --- a/hook/hook_test.go +++ b/hook/hook_test.go @@ -80,7 +80,7 @@ var argumentGetTests = []struct { func TestArgumentGet(t *testing.T) { for _, tt := range argumentGetTests { - a := Argument{tt.source, tt.name} + a := Argument{tt.source, tt.name, ""} value, ok := a.Get(tt.headers, tt.query, tt.payload) if ok != tt.ok || value != tt.value { t.Errorf("failed to get {%q, %q}:\nexpected {value:%#v, ok:%#v},\ngot {value:%#v, ok:%#v}", tt.source, tt.name, tt.value, tt.ok, value, ok) @@ -94,14 +94,14 @@ var hookParseJSONParametersTests = []struct { rheaders, rquery, rpayload *map[string]interface{} ok bool }{ - {[]Argument{Argument{"header", "a"}}, &map[string]interface{}{"a": `{"b": "y"}`}, nil, nil, &map[string]interface{}{"a": map[string]interface{}{"b": "y"}}, nil, nil, true}, - {[]Argument{Argument{"url", "a"}}, nil, &map[string]interface{}{"a": `{"b": "y"}`}, nil, nil, &map[string]interface{}{"a": map[string]interface{}{"b": "y"}}, nil, true}, - {[]Argument{Argument{"payload", "a"}}, nil, nil, &map[string]interface{}{"a": `{"b": "y"}`}, nil, nil, &map[string]interface{}{"a": map[string]interface{}{"b": "y"}}, true}, - {[]Argument{Argument{"header", "z"}}, &map[string]interface{}{"z": `{}`}, nil, nil, &map[string]interface{}{"z": map[string]interface{}{}}, nil, nil, true}, + {[]Argument{Argument{"header", "a", ""}}, &map[string]interface{}{"a": `{"b": "y"}`}, nil, nil, &map[string]interface{}{"a": map[string]interface{}{"b": "y"}}, nil, nil, true}, + {[]Argument{Argument{"url", "a", ""}}, nil, &map[string]interface{}{"a": `{"b": "y"}`}, nil, nil, &map[string]interface{}{"a": map[string]interface{}{"b": "y"}}, nil, true}, + {[]Argument{Argument{"payload", "a", ""}}, nil, nil, &map[string]interface{}{"a": `{"b": "y"}`}, nil, nil, &map[string]interface{}{"a": map[string]interface{}{"b": "y"}}, true}, + {[]Argument{Argument{"header", "z", ""}}, &map[string]interface{}{"z": `{}`}, nil, nil, &map[string]interface{}{"z": map[string]interface{}{}}, nil, nil, true}, // failures - {[]Argument{Argument{"header", "z"}}, &map[string]interface{}{"z": ``}, nil, nil, &map[string]interface{}{"z": ``}, nil, nil, false}, // empty string - {[]Argument{Argument{"header", "y"}}, &map[string]interface{}{"X": `{}`}, nil, nil, &map[string]interface{}{"X": `{}`}, nil, nil, false}, // missing parameter - {[]Argument{Argument{"string", "z"}}, &map[string]interface{}{"z": ``}, nil, nil, &map[string]interface{}{"z": ``}, nil, nil, false}, // invalid argument source + {[]Argument{Argument{"header", "z", ""}}, &map[string]interface{}{"z": ``}, nil, nil, &map[string]interface{}{"z": ``}, nil, nil, false}, // empty string + {[]Argument{Argument{"header", "y", ""}}, &map[string]interface{}{"X": `{}`}, nil, nil, &map[string]interface{}{"X": `{}`}, nil, nil, false}, // missing parameter + {[]Argument{Argument{"string", "z", ""}}, &map[string]interface{}{"z": ``}, nil, nil, &map[string]interface{}{"z": ``}, nil, nil, false}, // invalid argument source } func TestHookParseJSONParameters(t *testing.T) { @@ -121,9 +121,9 @@ var hookExtractCommandArgumentsTests = []struct { value []string ok bool }{ - {"test", []Argument{Argument{"header", "a"}}, &map[string]interface{}{"a": "z"}, nil, nil, []string{"test", "z"}, true}, + {"test", []Argument{Argument{"header", "a", ""}}, &map[string]interface{}{"a": "z"}, nil, nil, []string{"test", "z"}, true}, // failures - {"fail", []Argument{Argument{"payload", "a"}}, &map[string]interface{}{"a": "z"}, nil, nil, []string{"fail", ""}, false}, + {"fail", []Argument{Argument{"payload", "a", ""}}, &map[string]interface{}{"a": "z"}, nil, nil, []string{"fail", ""}, false}, } func TestHookExtractCommandArguments(t *testing.T) { @@ -136,6 +136,67 @@ func TestHookExtractCommandArguments(t *testing.T) { } } +// Here we test the extraction of env variables when the user defined a hook +// with the "pass-environment-to-command" directive +// we test both cases where the name of the data is used as the name of the +// env key & the case where the hook definition sets the env var name to a +// fixed value using the envname construct like so:: +// [ +// { +// "id": "push", +// "execute-command": "bb2mm", +// "command-working-directory": "/tmp", +// "pass-environment-to-command": +// [ +// { +// "source": "entire-payload", +// "envname": "PAYLOAD" +// }, +// ] +// } +// ] +var hookExtractCommandArgumentsForEnvTests = []struct { + exec string + args []Argument + headers, query, payload *map[string]interface{} + value []string + ok bool +}{ + // successes + { + "test", + []Argument{Argument{"header", "a", ""}}, + &map[string]interface{}{"a": "z"}, nil, nil, + []string{"HOOK_a=z"}, + true, + }, + { + "test", + []Argument{Argument{"header", "a", "MYKEY"}}, + &map[string]interface{}{"a": "z"}, nil, nil, + []string{"HOOK_MYKEY=z"}, + true, + }, + // failures + { + "fail", + []Argument{Argument{"payload", "a", ""}}, + &map[string]interface{}{"a": "z"}, nil, nil, + []string{}, + false, + }, +} + +func TestHookExtractCommandArgumentsForEnv(t *testing.T) { + for _, tt := range hookExtractCommandArgumentsForEnvTests { + h := &Hook{ExecuteCommand: tt.exec, PassEnvironmentToCommand: tt.args} + value, err := h.ExtractCommandArgumentsForEnv(tt.headers, tt.query, tt.payload) + if (err == nil) != tt.ok || !reflect.DeepEqual(value, tt.value) { + t.Errorf("failed to extract args for env {cmd=%q, args=%v}:\nexpected %#v, ok: %v\ngot %#v, ok: %v", tt.exec, tt.args, tt.value, tt.ok, value, (err == nil)) + } + } +} + var hooksLoadFromFileTests = []struct { path string ok bool @@ -182,16 +243,16 @@ var matchRuleTests = []struct { ok bool err bool }{ - {"value", "", "", "z", Argument{"header", "a"}, &map[string]interface{}{"a": "z"}, nil, nil, []byte{}, true, false}, - {"regex", "^z", "", "z", Argument{"header", "a"}, &map[string]interface{}{"a": "z"}, nil, nil, []byte{}, true, false}, - {"payload-hash-sha1", "", "secret", "", Argument{"header", "a"}, &map[string]interface{}{"a": "b17e04cbb22afa8ffbff8796fc1894ed27badd9e"}, nil, nil, []byte(`{"a": "z"}`), true, false}, + {"value", "", "", "z", Argument{"header", "a", ""}, &map[string]interface{}{"a": "z"}, nil, nil, []byte{}, true, false}, + {"regex", "^z", "", "z", Argument{"header", "a", ""}, &map[string]interface{}{"a": "z"}, nil, nil, []byte{}, true, false}, + {"payload-hash-sha1", "", "secret", "", Argument{"header", "a", ""}, &map[string]interface{}{"a": "b17e04cbb22afa8ffbff8796fc1894ed27badd9e"}, nil, nil, []byte(`{"a": "z"}`), true, false}, // failures - {"value", "", "", "X", Argument{"header", "a"}, &map[string]interface{}{"a": "z"}, nil, nil, []byte{}, false, false}, - {"regex", "^X", "", "", Argument{"header", "a"}, &map[string]interface{}{"a": "z"}, nil, nil, []byte{}, false, false}, - {"value", "", "2", "X", Argument{"header", "a"}, &map[string]interface{}{"y": "z"}, nil, nil, []byte{}, false, false}, // reference invalid header + {"value", "", "", "X", Argument{"header", "a", ""}, &map[string]interface{}{"a": "z"}, nil, nil, []byte{}, false, false}, + {"regex", "^X", "", "", Argument{"header", "a", ""}, &map[string]interface{}{"a": "z"}, nil, nil, []byte{}, false, false}, + {"value", "", "2", "X", Argument{"header", "a", ""}, &map[string]interface{}{"y": "z"}, nil, nil, []byte{}, false, false}, // reference invalid header // errors - {"regex", "*", "", "", Argument{"header", "a"}, &map[string]interface{}{"a": "z"}, nil, nil, []byte{}, false, true}, // invalid regex - {"payload-hash-sha1", "", "secret", "", Argument{"header", "a"}, &map[string]interface{}{"a": ""}, nil, nil, []byte{}, false, true}, // invalid hmac + {"regex", "*", "", "", Argument{"header", "a", ""}, &map[string]interface{}{"a": "z"}, nil, nil, []byte{}, false, true}, // invalid regex + {"payload-hash-sha1", "", "secret", "", Argument{"header", "a", ""}, &map[string]interface{}{"a": ""}, nil, nil, []byte{}, false, true}, // invalid hmac } func TestMatchRule(t *testing.T) { @@ -215,8 +276,8 @@ var andRuleTests = []struct { { "(a=z, b=y): a=z && b=y", AndRule{ - {Match: &MatchRule{"value", "", "", "z", Argument{"header", "a"}}}, - {Match: &MatchRule{"value", "", "", "y", Argument{"header", "b"}}}, + {Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", ""}}}, + {Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", ""}}}, }, &map[string]interface{}{"a": "z", "b": "y"}, nil, nil, []byte{}, true, false, @@ -224,8 +285,8 @@ var andRuleTests = []struct { { "(a=z, b=Y): a=z && b=y", AndRule{ - {Match: &MatchRule{"value", "", "", "z", Argument{"header", "a"}}}, - {Match: &MatchRule{"value", "", "", "y", Argument{"header", "b"}}}, + {Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", ""}}}, + {Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", ""}}}, }, &map[string]interface{}{"a": "z", "b": "Y"}, nil, nil, []byte{}, false, false, @@ -234,22 +295,22 @@ var andRuleTests = []struct { { "(a=z, b=y, c=x, d=w=, e=X, f=X): a=z && (b=y && c=x) && (d=w || e=v) && !f=u", AndRule{ - {Match: &MatchRule{"value", "", "", "z", Argument{"header", "a"}}}, + {Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", ""}}}, { And: &AndRule{ - {Match: &MatchRule{"value", "", "", "y", Argument{"header", "b"}}}, - {Match: &MatchRule{"value", "", "", "x", Argument{"header", "c"}}}, + {Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", ""}}}, + {Match: &MatchRule{"value", "", "", "x", Argument{"header", "c", ""}}}, }, }, { Or: &OrRule{ - {Match: &MatchRule{"value", "", "", "w", Argument{"header", "d"}}}, - {Match: &MatchRule{"value", "", "", "v", Argument{"header", "e"}}}, + {Match: &MatchRule{"value", "", "", "w", Argument{"header", "d", ""}}}, + {Match: &MatchRule{"value", "", "", "v", Argument{"header", "e", ""}}}, }, }, { Not: &NotRule{ - Match: &MatchRule{"value", "", "", "u", Argument{"header", "f"}}, + Match: &MatchRule{"value", "", "", "u", Argument{"header", "f", ""}}, }, }, }, @@ -260,7 +321,7 @@ var andRuleTests = []struct { // failures { "invalid rule", - AndRule{{Match: &MatchRule{"value", "", "", "X", Argument{"header", "a"}}}}, + AndRule{{Match: &MatchRule{"value", "", "", "X", Argument{"header", "a", ""}}}}, &map[string]interface{}{"y": "z"}, nil, nil, nil, false, false, }, @@ -286,8 +347,8 @@ var orRuleTests = []struct { { "(a=z, b=X): a=z || b=y", OrRule{ - {Match: &MatchRule{"value", "", "", "z", Argument{"header", "a"}}}, - {Match: &MatchRule{"value", "", "", "y", Argument{"header", "b"}}}, + {Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", ""}}}, + {Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", ""}}}, }, &map[string]interface{}{"a": "z", "b": "X"}, nil, nil, []byte{}, true, false, @@ -295,8 +356,8 @@ var orRuleTests = []struct { { "(a=X, b=y): a=z || b=y", OrRule{ - {Match: &MatchRule{"value", "", "", "z", Argument{"header", "a"}}}, - {Match: &MatchRule{"value", "", "", "y", Argument{"header", "b"}}}, + {Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", ""}}}, + {Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", ""}}}, }, &map[string]interface{}{"a": "X", "b": "y"}, nil, nil, []byte{}, true, false, @@ -304,8 +365,8 @@ var orRuleTests = []struct { { "(a=Z, b=Y): a=z || b=y", OrRule{ - {Match: &MatchRule{"value", "", "", "z", Argument{"header", "a"}}}, - {Match: &MatchRule{"value", "", "", "y", Argument{"header", "b"}}}, + {Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", ""}}}, + {Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", ""}}}, }, &map[string]interface{}{"a": "Z", "b": "Y"}, nil, nil, []byte{}, false, false, @@ -314,7 +375,7 @@ var orRuleTests = []struct { { "invalid rule", OrRule{ - {Match: &MatchRule{"value", "", "", "z", Argument{"header", "a"}}}, + {Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", ""}}}, }, &map[string]interface{}{"y": "Z"}, nil, nil, []byte{}, false, false, @@ -338,8 +399,8 @@ var notRuleTests = []struct { ok bool err bool }{ - {"(a=z): !a=X", NotRule{Match: &MatchRule{"value", "", "", "X", Argument{"header", "a"}}}, &map[string]interface{}{"a": "z"}, nil, nil, []byte{}, true, false}, - {"(a=z): !a=z", NotRule{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a"}}}, &map[string]interface{}{"a": "z"}, nil, nil, []byte{}, false, false}, + {"(a=z): !a=X", NotRule{Match: &MatchRule{"value", "", "", "X", Argument{"header", "a", ""}}}, &map[string]interface{}{"a": "z"}, nil, nil, []byte{}, true, false}, + {"(a=z): !a=z", NotRule{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", ""}}}, &map[string]interface{}{"a": "z"}, nil, nil, []byte{}, false, false}, } func TestNotRule(t *testing.T) {