Skip to content

Commit b828d00

Browse files
Merge pull request #32 from segmentio/yolken-add-better-default-funcs
Add lookup function
2 parents d2a425f + b0cfcc3 commit b828d00

File tree

3 files changed

+140
-5
lines changed

3 files changed

+140
-5
lines changed

pkg/util/template.go

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,22 @@ import (
88
"net/url"
99
"os"
1010
"path/filepath"
11+
"reflect"
1112
"strings"
1213
"text/template"
1314

1415
"github.com/Masterminds/sprig/v3"
1516
"github.com/ghodss/yaml"
1617
)
1718

18-
var extraTemplateFuncs = template.FuncMap{
19-
"urlEncode": url.QueryEscape,
20-
"toYaml": toYaml,
21-
}
19+
var (
20+
extraTemplateFuncs = template.FuncMap{
21+
"lookup": lookup,
22+
"pathLookup": pathLookup,
23+
"toYaml": toYaml,
24+
"urlEncode": url.QueryEscape,
25+
}
26+
)
2227

2328
// ApplyTemplate runs golang templating on all files in the provided path,
2429
// replacing them in-place with their templated versions.
@@ -214,6 +219,51 @@ func configMapEntriesGenerator(
214219
}
215220
}
216221

222+
// lookup does a dot-separated path lookup on the input map. If a key on the path is
223+
// not found, it returns nil. If the input or any of its children on the lookup path is not
224+
// a map, it returns an error.
225+
func lookup(input interface{}, path string) (interface{}, error) {
226+
obj := reflect.ValueOf(input)
227+
components := strings.Split(path, ".")
228+
229+
for i := 0; i < len(components); {
230+
switch obj.Kind() {
231+
case reflect.Map:
232+
obj = obj.MapIndex(reflect.ValueOf(components[i]))
233+
i++
234+
case reflect.Ptr, reflect.Interface:
235+
if obj.IsNil() {
236+
return nil, nil
237+
}
238+
239+
// Get the thing being pointed to or interfaced, don't advance index
240+
obj = obj.Elem()
241+
default:
242+
if obj.IsValid() {
243+
// An object was found, but it's not a map. Return an error.
244+
return nil, fmt.Errorf(
245+
"Tried to traverse a value that's not a map (kind=%s)",
246+
obj.Kind(),
247+
)
248+
}
249+
250+
// An intermediate key wasn't found
251+
return nil, nil
252+
}
253+
}
254+
255+
if !obj.IsValid() {
256+
// The last key wasn't found
257+
return nil, nil
258+
}
259+
return obj.Interface(), nil
260+
}
261+
262+
// pathLookup is the same as lookup, but with the arguments flipped.
263+
func pathLookup(path string, input interface{}) (interface{}, error) {
264+
return lookup(input, path)
265+
}
266+
217267
func toYaml(input interface{}) (string, error) {
218268
bytes, err := yaml.Marshal(input)
219269
if err != nil {

pkg/util/template_test.go

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,3 +136,88 @@ func fileContents(t *testing.T, path string) string {
136136

137137
return string(contents)
138138
}
139+
140+
func TestLookup(t *testing.T) {
141+
m := map[string]interface{}{
142+
"key1": "value1",
143+
"key2": map[string]interface{}{
144+
"key3": map[string]interface{}{
145+
"key4": "value4",
146+
},
147+
"key5": 1234,
148+
},
149+
"key6": nil,
150+
}
151+
152+
type testCase struct {
153+
input interface{}
154+
path string
155+
expectedResult interface{}
156+
expectErr bool
157+
}
158+
159+
testCases := []testCase{
160+
{
161+
input: m,
162+
path: "bad-key",
163+
expectedResult: nil,
164+
},
165+
{
166+
input: m,
167+
path: "",
168+
expectedResult: nil,
169+
},
170+
{
171+
input: nil,
172+
path: "key1",
173+
expectedResult: nil,
174+
},
175+
{
176+
input: "not a map",
177+
path: "key1",
178+
expectedResult: nil,
179+
expectErr: true,
180+
},
181+
{
182+
input: m,
183+
path: "key1",
184+
expectedResult: "value1",
185+
},
186+
{
187+
input: &m,
188+
path: "key1",
189+
expectedResult: "value1",
190+
},
191+
{
192+
input: m,
193+
path: "key1.not-a-map",
194+
expectedResult: nil,
195+
expectErr: true,
196+
},
197+
{
198+
input: m,
199+
path: "key2.key3.key4",
200+
expectedResult: "value4",
201+
},
202+
{
203+
input: m,
204+
path: "key2.key5",
205+
expectedResult: 1234,
206+
},
207+
{
208+
input: m,
209+
path: "key6.nil-key",
210+
expectedResult: nil,
211+
},
212+
}
213+
214+
for index, tc := range testCases {
215+
result, err := lookup(tc.input, tc.path)
216+
assert.Equal(t, tc.expectedResult, result, "Unexpected result for case %d", index)
217+
if tc.expectErr {
218+
assert.Error(t, err, "Did not get expected error in case %d", index)
219+
} else {
220+
assert.NoError(t, err, "Got unexpected error in case %d", index)
221+
}
222+
}
223+
}

pkg/version/version.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
package version
22

33
// Version stores the current kubeapply version.
4-
const Version = "0.0.27"
4+
const Version = "0.0.28"

0 commit comments

Comments
 (0)