Skip to content

Commit 6fcdbfe

Browse files
committed
Add whereLabel* functions
* whereLabelExists * whereLabelDoesNotExist * whereLabelValueMatches
1 parent 7c84209 commit 6fcdbfe

File tree

3 files changed

+163
-34
lines changed

3 files changed

+163
-34
lines changed

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -310,12 +310,15 @@ For example, this is a JSON version of an emitted RuntimeContainer struct:
310310
* *`trimPrefix $prefix $string`*: If `$prefix` is a prefix of `$string`, return `$string` with `$prefix` trimmed from the beginning. Otherwise, return `$string` unchanged.
311311
* *`trimSuffix $suffix $string`*: If `$suffix` is a suffix of `$string`, return `$string` with `$suffix` trimmed from the end. Otherwise, return `$string` unchanged.
312312
* *`trim $string`*: Removes whitespace from both sides of `$string`.
313+
* *`when $condition $trueValue $falseValue`*: Returns the `$trueValue` when the `$condition` is `true` and the `$falseValue` otherwise
313314
* *`where $items $fieldPath $value`*: Filters an array or slice based on the values of a field path expression `$fieldPath`. A field path expression is a dot-delimited list of map keys or struct member names specifying the path from container to a nested value. Returns an array of items having that value.
314315
* *`whereExist $items $fieldPath`*: Like `where`, but returns only items where `$fieldPath` exists (is not nil).
315316
* *`whereNotExist $items $fieldPath`*: Like `where`, but returns only items where `$fieldPath` does not exist (is nil).
316317
* *`whereAny $items $fieldPath $sep $values`*: Like `where`, but the string value specified by `$fieldPath` is first split by `$sep` into a list of strings. The comparison value is a string slice with possible matches. Returns items which OR intersect these values.
317318
* *`whereAll $items $fieldPath $sep $values`*: Like `whereAny`, except all `$values` must exist in the `$fieldPath`.
318-
* *`when $condition $trueValue $falseValue`*: Returns the `$trueValue` when the `$condition` is `true` and the `$falseValue` otherwise
319+
* *`whereLabelExists $containers $label`*: Filters a slice of containers based on the existence of the label `$label`.
320+
* *`whereLabelDoesNotExist $containers $label`*: Filters a slice of containers based on the non-existence of the label `$label`.
321+
* *`whereLabelValueMatches $containers $label $pattern`*: Filters a slice of containers based on the existence of the label `$label` with values matching the regular expression `$pattern`.
319322

320323
===
321324

template.go

Lines changed: 78 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"os"
1414
"path/filepath"
1515
"reflect"
16+
"regexp"
1617
"strconv"
1718
"strings"
1819
"syscall"
@@ -103,7 +104,6 @@ func groupByKeys(entries interface{}, key string) ([]string, error) {
103104

104105
// Generalized where function
105106
func generalizedWhere(funcName string, entries interface{}, key string, test func(interface{}) bool) (interface{}, error) {
106-
107107
entriesVal, err := getArrayValues(funcName, entries)
108108

109109
if err != nil {
@@ -169,6 +169,48 @@ func whereAll(entries interface{}, key, sep string, cmp []string) (interface{},
169169
})
170170
}
171171

172+
// generalized whereLabel function
173+
func generalizedWhereLabel(funcName string, containers Context, label string, test func(string, bool) bool) (Context, error) {
174+
selection := make([]*RuntimeContainer, 0)
175+
176+
for i := 0; i < len(containers); i++ {
177+
container := containers[i]
178+
179+
value, ok := container.Labels[label]
180+
if test(value, ok) {
181+
selection = append(selection, container)
182+
}
183+
}
184+
185+
return selection, nil
186+
}
187+
188+
// selects containers that have a particular label
189+
func whereLabelExists(containers Context, label string) (Context, error) {
190+
return generalizedWhereLabel("whereLabelExists", containers, label, func(_ string, ok bool) bool {
191+
return ok
192+
})
193+
}
194+
195+
// selects containers that have don't have a particular label
196+
func whereLabelDoesNotExist(containers Context, label string) (Context, error) {
197+
return generalizedWhereLabel("whereLabelDoesNotExist", containers, label, func(_ string, ok bool) bool {
198+
return !ok
199+
})
200+
}
201+
202+
// selects containers with a particular label whose value matches a regular expression
203+
func whereLabelValueMatches(containers Context, label, pattern string) (Context, error) {
204+
rx, err := regexp.Compile(pattern)
205+
if err != nil {
206+
return nil, err
207+
}
208+
209+
return generalizedWhereLabel("whereLabelValueMatches", containers, label, func(value string, ok bool) bool {
210+
return ok && rx.MatchString(value)
211+
})
212+
}
213+
172214
// hasPrefix returns whether a given string is a prefix of another string
173215
func hasPrefix(prefix, s string) bool {
174216
return strings.HasPrefix(s, prefix)
@@ -344,38 +386,41 @@ func when(condition bool, trueValue, falseValue interface{}) interface{} {
344386

345387
func newTemplate(name string) *template.Template {
346388
tmpl := template.New(name).Funcs(template.FuncMap{
347-
"closest": arrayClosest,
348-
"coalesce": coalesce,
349-
"contains": contains,
350-
"dict": dict,
351-
"dir": dirList,
352-
"exists": exists,
353-
"first": arrayFirst,
354-
"groupBy": groupBy,
355-
"groupByKeys": groupByKeys,
356-
"groupByMulti": groupByMulti,
357-
"hasPrefix": hasPrefix,
358-
"hasSuffix": hasSuffix,
359-
"json": marshalJson,
360-
"intersect": intersect,
361-
"keys": keys,
362-
"last": arrayLast,
363-
"replace": strings.Replace,
364-
"parseBool": strconv.ParseBool,
365-
"parseJson": unmarshalJson,
366-
"queryEscape": url.QueryEscape,
367-
"sha1": hashSha1,
368-
"split": strings.Split,
369-
"splitN": strings.SplitN,
370-
"trimPrefix": trimPrefix,
371-
"trimSuffix": trimSuffix,
372-
"trim": trim,
373-
"where": where,
374-
"whereExist": whereExist,
375-
"whereNotExist": whereNotExist,
376-
"whereAny": whereAny,
377-
"whereAll": whereAll,
378-
"when": when,
389+
"closest": arrayClosest,
390+
"coalesce": coalesce,
391+
"contains": contains,
392+
"dict": dict,
393+
"dir": dirList,
394+
"exists": exists,
395+
"first": arrayFirst,
396+
"groupBy": groupBy,
397+
"groupByKeys": groupByKeys,
398+
"groupByMulti": groupByMulti,
399+
"hasPrefix": hasPrefix,
400+
"hasSuffix": hasSuffix,
401+
"json": marshalJson,
402+
"intersect": intersect,
403+
"keys": keys,
404+
"last": arrayLast,
405+
"replace": strings.Replace,
406+
"parseBool": strconv.ParseBool,
407+
"parseJson": unmarshalJson,
408+
"queryEscape": url.QueryEscape,
409+
"sha1": hashSha1,
410+
"split": strings.Split,
411+
"splitN": strings.SplitN,
412+
"trimPrefix": trimPrefix,
413+
"trimSuffix": trimSuffix,
414+
"trim": trim,
415+
"when": when,
416+
"where": where,
417+
"whereExist": whereExist,
418+
"whereNotExist": whereNotExist,
419+
"whereAny": whereAny,
420+
"whereAll": whereAll,
421+
"whereLabelExists": whereLabelExists,
422+
"whereLabelDoesNotExist": whereLabelDoesNotExist,
423+
"whereLabelValueMatches": whereLabelValueMatches,
379424
})
380425
return tmpl
381426
}

template_test.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,87 @@ func TestWhereRequires(t *testing.T) {
452452
tests.run(t, "whereAll")
453453
}
454454

455+
func TestWhereLabelExists(t *testing.T) {
456+
containers := []*RuntimeContainer{
457+
&RuntimeContainer{
458+
Labels: map[string]string{
459+
"com.example.foo": "foo",
460+
"com.example.bar": "bar",
461+
},
462+
ID: "1",
463+
},
464+
&RuntimeContainer{
465+
Labels: map[string]string{
466+
"com.example.bar": "bar",
467+
},
468+
ID: "2",
469+
},
470+
}
471+
472+
tests := templateTestList{
473+
{`{{whereLabelExists . "com.example.foo" | len}}`, containers, `1`},
474+
{`{{whereLabelExists . "com.example.bar" | len}}`, containers, `2`},
475+
{`{{whereLabelExists . "com.example.baz" | len}}`, containers, `0`},
476+
}
477+
478+
tests.run(t, "whereLabelExists")
479+
}
480+
481+
func TestWhereLabelDoesNotExist(t *testing.T) {
482+
containers := []*RuntimeContainer{
483+
&RuntimeContainer{
484+
Labels: map[string]string{
485+
"com.example.foo": "foo",
486+
"com.example.bar": "bar",
487+
},
488+
ID: "1",
489+
},
490+
&RuntimeContainer{
491+
Labels: map[string]string{
492+
"com.example.bar": "bar",
493+
},
494+
ID: "2",
495+
},
496+
}
497+
498+
tests := templateTestList{
499+
{`{{whereLabelDoesNotExist . "com.example.foo" | len}}`, containers, `1`},
500+
{`{{whereLabelDoesNotExist . "com.example.bar" | len}}`, containers, `0`},
501+
{`{{whereLabelDoesNotExist . "com.example.baz" | len}}`, containers, `2`},
502+
}
503+
504+
tests.run(t, "whereLabelDoesNotExist")
505+
}
506+
507+
func TestWhereLabelValueMatches(t *testing.T) {
508+
containers := []*RuntimeContainer{
509+
&RuntimeContainer{
510+
Labels: map[string]string{
511+
"com.example.foo": "foo",
512+
"com.example.bar": "bar",
513+
},
514+
ID: "1",
515+
},
516+
&RuntimeContainer{
517+
Labels: map[string]string{
518+
"com.example.bar": "BAR",
519+
},
520+
ID: "2",
521+
},
522+
}
523+
524+
tests := templateTestList{
525+
{`{{whereLabelValueMatches . "com.example.foo" "^foo$" | len}}`, containers, `1`},
526+
{`{{whereLabelValueMatches . "com.example.foo" "\\d+" | len}}`, containers, `0`},
527+
{`{{whereLabelValueMatches . "com.example.bar" "^bar$" | len}}`, containers, `1`},
528+
{`{{whereLabelValueMatches . "com.example.bar" "^(?i)bar$" | len}}`, containers, `2`},
529+
{`{{whereLabelValueMatches . "com.example.bar" ".*" | len}}`, containers, `2`},
530+
{`{{whereLabelValueMatches . "com.example.baz" ".*" | len}}`, containers, `0`},
531+
}
532+
533+
tests.run(t, "whereLabelValueMatches")
534+
}
535+
455536
func TestHasPrefix(t *testing.T) {
456537
const prefix = "tcp://"
457538
const str = "tcp://127.0.0.1:2375"

0 commit comments

Comments
 (0)