Skip to content

Commit f11c238

Browse files
committed
add methods to allow filtering by gvk
1 parent 3508ce0 commit f11c238

File tree

5 files changed

+300
-6
lines changed

5 files changed

+300
-6
lines changed

go/fn/examples/example_filter_GVK_test.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,9 @@ func updateReplicas(rl *fn.ResourceList) (bool, error) {
3535
}
3636
var replicas int
3737
rl.FunctionConfig.GetOrDie(&replicas, "replicas")
38-
for i, obj := range rl.Items {
39-
if obj.IsGVK("apps/v1", "Deployment") {
40-
rl.Items[i].SetOrDie(replicas, "spec", "replicas")
41-
}
38+
deployments := rl.Items.SelectByGvk("apps/v1", "Deployment")
39+
for i := range deployments {
40+
rl.Items[i].SetOrDie(replicas, "spec", "replicas")
4241
}
4342
return true, nil
4443
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package example
2+
3+
import (
4+
"os"
5+
6+
"github.com/GoogleContainerTools/kpt-functions-sdk/go/fn"
7+
)
8+
9+
// This example implements a function that selectively includes or excludes some resources.
10+
11+
func Example_selectExclude() {
12+
if err := fn.AsMain(fn.ResourceListProcessorFunc(selectResources)); err != nil {
13+
os.Exit(1)
14+
}
15+
}
16+
17+
// selectResources keeps all resources with the GVK apps/v1 Deployment that do
18+
// NOT have the label foo=bar, and removes the rest.
19+
func selectResources(rl *fn.ResourceList) (bool, error) {
20+
rl.Items = rl.Items.SelectByGvk("apps/v1", "Deployment").
21+
ExcludeByLabels(map[string]string{"foo": "bar"})
22+
return true, nil
23+
}

go/fn/object.go

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"fmt"
1919
"reflect"
2020
"strconv"
21+
"strings"
2122

2223
"github.com/GoogleContainerTools/kpt-functions-sdk/go/fn/internal"
2324
"sigs.k8s.io/kustomize/kyaml/kio/kioutil"
@@ -690,6 +691,18 @@ func (o *KubeObject) GetAnnotation(k string) string {
690691
return v
691692
}
692693

694+
// HasAnnotations returns whether the KubeObject has the provided annotations
695+
func (o *KubeObject) HasAnnotations(annotations map[string]string) bool {
696+
kubeObjectLabels := o.GetAnnotations()
697+
for k, v := range annotations {
698+
kubeObjectValue, found := kubeObjectLabels[k]
699+
if !found || kubeObjectValue != v {
700+
return false
701+
}
702+
}
703+
return true
704+
}
705+
693706
// RemoveAnnotationsIfEmpty removes the annotations field when it has zero annotations.
694707
func (o *KubeObject) RemoveAnnotationsIfEmpty() error {
695708
annotations, found, err := o.obj.GetNestedStringMap("metadata", "annotations")
@@ -721,6 +734,18 @@ func (o *KubeObject) GetLabels() map[string]string {
721734
return v
722735
}
723736

737+
// HasLabels returns whether the KubeObject has the provided labels
738+
func (o *KubeObject) HasLabels(labels map[string]string) bool {
739+
kubeObjectLabels := o.GetLabels()
740+
for k, v := range labels {
741+
kubeObjectValue, found := kubeObjectLabels[k]
742+
if !found || kubeObjectValue != v {
743+
return false
744+
}
745+
}
746+
return true
747+
}
748+
724749
func (o *KubeObject) PathAnnotation() string {
725750
anno := o.GetAnnotation(kioutil.PathAnnotation)
726751
return anno
@@ -759,6 +784,107 @@ func (o KubeObjects) Less(i, j int) bool {
759784
return idStrI < idStrJ
760785
}
761786

787+
func (o KubeObjects) String() string {
788+
var elems []string
789+
for _, obj := range o {
790+
elems = append(elems, strings.TrimSpace(obj.String()))
791+
}
792+
return strings.Join(elems, "\n---\n")
793+
}
794+
795+
// Select will return the subset of objects in KubeObjects such that f(object) returns 'true'.
796+
func (o KubeObjects) Select(f func(o *KubeObject) bool) KubeObjects {
797+
var result KubeObjects
798+
for _, obj := range o {
799+
if f(obj) {
800+
result = append(result, obj)
801+
}
802+
}
803+
return result
804+
}
805+
806+
// SelectByGvk will return the subset of objects that matches the provided GVK.
807+
func (o KubeObjects) SelectByGvk(apiVersion, kind string) KubeObjects {
808+
return o.Select(func(o *KubeObject) bool {
809+
return o.IsGVK(apiVersion, kind)
810+
})
811+
}
812+
813+
// ExcludeByGvk will return the subset of objects that do not match the provided GVK.
814+
func (o KubeObjects) ExcludeByGvk(apiVersion, kind string) KubeObjects {
815+
return o.Select(func(o *KubeObject) bool {
816+
return !o.IsGVK(apiVersion, kind)
817+
})
818+
}
819+
820+
// SelectByName will return the subset of objects that matches the provided name.
821+
func (o KubeObjects) SelectByName(name string) KubeObjects {
822+
return o.Select(func(o *KubeObject) bool {
823+
return o.GetName() == name
824+
})
825+
}
826+
827+
// ExcludeByName will return the subset of objects that do not match the provided name.
828+
func (o KubeObjects) ExcludeByName(name string) KubeObjects {
829+
return o.Select(func(o *KubeObject) bool {
830+
return o.GetName() != name
831+
})
832+
}
833+
834+
// SelectByNamespace will return the subset of objects that matches the provided namespace.
835+
func (o KubeObjects) SelectByNamespace(namespace string) KubeObjects {
836+
return o.Select(func(o *KubeObject) bool {
837+
return o.GetNamespace() == namespace
838+
})
839+
}
840+
841+
// ExcludeByNamespace will return the subset of objects that do not match the provided namespace.
842+
func (o KubeObjects) ExcludeByNamespace(namespace string) KubeObjects {
843+
return o.Select(func(o *KubeObject) bool {
844+
return o.GetNamespace() != namespace
845+
})
846+
}
847+
848+
// SelectByLabels will return the subset of objects that matches the provided labels.
849+
func (o KubeObjects) SelectByLabels(labels map[string]string) KubeObjects {
850+
return o.Select(func(o *KubeObject) bool {
851+
return o.HasLabels(labels)
852+
})
853+
}
854+
855+
// ExcludeByLabels will return the subset of objects that do not match the provided labels.
856+
func (o KubeObjects) ExcludeByLabels(labels map[string]string) KubeObjects {
857+
return o.Select(func(o *KubeObject) bool {
858+
return !o.HasLabels(labels)
859+
})
860+
}
861+
862+
// SelectByAnnotations will return the subset of objects that matches the provided labels.
863+
func (o KubeObjects) SelectByAnnotations(annotations map[string]string) KubeObjects {
864+
return o.Select(func(o *KubeObject) bool {
865+
return o.HasAnnotations(annotations)
866+
})
867+
}
868+
869+
// ExcludeByAnnotations will return the subset of objects that do not match the provided labels.
870+
func (o KubeObjects) ExcludeByAnnotations(annotations map[string]string) KubeObjects {
871+
return o.Select(func(o *KubeObject) bool {
872+
return !o.HasAnnotations(annotations)
873+
})
874+
}
875+
876+
// SelectMetaResources will return the subset of objects that are meta resources. Currently, this
877+
// is just the Kptfile.
878+
func (o KubeObjects) SelectMetaResources() KubeObjects {
879+
return o.SelectByGvk("kpt.dev/v1", "Kptfile")
880+
}
881+
882+
// ExcludeMetaResources will return the subset of objects that are not meta resources. Currently, this
883+
// is just the Kptfile.
884+
func (o KubeObjects) ExcludeMetaResources() KubeObjects {
885+
return o.ExcludeByGvk("kpt.dev/v1", "Kptfile")
886+
}
887+
762888
func (o *KubeObject) IsEmpty() bool {
763889
return yaml.IsYNodeEmptyMap(o.obj.Node())
764890
}

go/fn/object_test.go

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"testing"
77

88
"github.com/google/go-cmp/cmp"
9+
"github.com/stretchr/testify/assert"
910
)
1011

1112
func TestIsNamespaceScoped(t *testing.T) {
@@ -284,3 +285,148 @@ func TestGetMap(t *testing.T) {
284285
t.Errorf("unexpected value for GetMap(%q); got %v, want nil", "notExists", got)
285286
}
286287
}
288+
289+
func TestSelectors(t *testing.T) {
290+
deployment := `
291+
apiVersion: apps/v1
292+
kind: Deployment
293+
metadata:
294+
name: example
295+
namespace: default
296+
labels:
297+
abc: def
298+
foo: baz
299+
annotations:
300+
bar: foo
301+
`
302+
service := `
303+
apiVersion: apps/v1
304+
kind: Service
305+
metadata:
306+
name: example
307+
namespace: my-namespace
308+
labels:
309+
foo: baz
310+
annotations:
311+
foo: bar
312+
`
313+
d, err := ParseKubeObject([]byte(deployment))
314+
assert.NoError(t, err)
315+
s, err := ParseKubeObject([]byte(service))
316+
assert.NoError(t, err)
317+
input := KubeObjects{d, s}
318+
319+
// select all resources with labels foo=baz
320+
items := input.SelectByLabels(map[string]string{"foo": "baz"})
321+
assert.Equal(t, items.String(), `apiVersion: apps/v1
322+
kind: Deployment
323+
metadata:
324+
name: example
325+
namespace: default
326+
labels:
327+
abc: def
328+
foo: baz
329+
annotations:
330+
bar: foo
331+
---
332+
apiVersion: apps/v1
333+
kind: Service
334+
metadata:
335+
name: example
336+
namespace: my-namespace
337+
labels:
338+
foo: baz
339+
annotations:
340+
foo: bar`)
341+
342+
// select all deployments
343+
items = input.SelectByGvk("apps/v1", "Deployment")
344+
assert.Equal(t, items.String(), `apiVersion: apps/v1
345+
kind: Deployment
346+
metadata:
347+
name: example
348+
namespace: default
349+
labels:
350+
abc: def
351+
foo: baz
352+
annotations:
353+
bar: foo`)
354+
355+
// exclude all services
356+
items = input.ExcludeByGvk("apps/v1", "Service")
357+
assert.Equal(t, items.String(), `apiVersion: apps/v1
358+
kind: Deployment
359+
metadata:
360+
name: example
361+
namespace: default
362+
labels:
363+
abc: def
364+
foo: baz
365+
annotations:
366+
bar: foo`)
367+
368+
// include resources with the label abc: def
369+
items = input.SelectByLabels(map[string]string{"abc": "def"})
370+
assert.Equal(t, items.String(), `apiVersion: apps/v1
371+
kind: Deployment
372+
metadata:
373+
name: example
374+
namespace: default
375+
labels:
376+
abc: def
377+
foo: baz
378+
annotations:
379+
bar: foo`)
380+
381+
// exclude all resources with the annotation foo=bar
382+
items = input.ExcludeByAnnotations(map[string]string{"foo": "bar"})
383+
assert.Equal(t, items.String(), `apiVersion: apps/v1
384+
kind: Deployment
385+
metadata:
386+
name: example
387+
namespace: default
388+
labels:
389+
abc: def
390+
foo: baz
391+
annotations:
392+
bar: foo`)
393+
394+
// include resources named 'example' that are Not in namespace default
395+
items = input.SelectByName("example").ExcludeByNamespace("default")
396+
assert.Equal(t, items.String(), `apiVersion: apps/v1
397+
kind: Service
398+
metadata:
399+
name: example
400+
namespace: my-namespace
401+
labels:
402+
foo: baz
403+
annotations:
404+
foo: bar`)
405+
406+
// add the label "g=h" to all resources with annotation "bar=foo"
407+
items = input.SelectByAnnotations(map[string]string{"bar": "foo"})
408+
for _, obj := range items {
409+
obj.SetLabel("g", "h")
410+
}
411+
assert.Equal(t, input.String(), `apiVersion: apps/v1
412+
kind: Deployment
413+
metadata:
414+
name: example
415+
namespace: default
416+
labels:
417+
abc: def
418+
foo: baz
419+
g: h
420+
annotations:
421+
bar: foo
422+
---
423+
apiVersion: apps/v1
424+
kind: Service
425+
metadata:
426+
name: example
427+
namespace: my-namespace
428+
labels:
429+
foo: baz
430+
annotations:
431+
foo: bar`)
432+
}

go/fn/resourcelist.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ type ResourceList struct {
3333
// Items will be a slice containing the Deployment and Service resources
3434
// Mutating functions will alter this field during processing.
3535
// This field is required.
36-
Items []*KubeObject `yaml:"items" json:"items"`
36+
Items KubeObjects `yaml:"items" json:"items"`
3737

3838
// FunctionConfig is the ResourceList.functionConfig input value.
3939
//
@@ -178,7 +178,7 @@ func (rl *ResourceList) ToYAML() ([]byte, error) {
178178

179179
// Sort sorts the ResourceList.items by apiVersion, kind, namespace and name.
180180
func (rl *ResourceList) Sort() {
181-
sort.Sort(KubeObjects(rl.Items))
181+
sort.Sort(rl.Items)
182182
}
183183

184184
// UpsertObjectToItems adds an object to ResourceList.items. The input object can

0 commit comments

Comments
 (0)