Skip to content

Commit

Permalink
add methods to allow filtering by gvk
Browse files Browse the repository at this point in the history
  • Loading branch information
natasha41575 committed May 26, 2022
1 parent 3508ce0 commit f11c238
Show file tree
Hide file tree
Showing 5 changed files with 300 additions and 6 deletions.
7 changes: 3 additions & 4 deletions go/fn/examples/example_filter_GVK_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,9 @@ func updateReplicas(rl *fn.ResourceList) (bool, error) {
}
var replicas int
rl.FunctionConfig.GetOrDie(&replicas, "replicas")
for i, obj := range rl.Items {
if obj.IsGVK("apps/v1", "Deployment") {
rl.Items[i].SetOrDie(replicas, "spec", "replicas")
}
deployments := rl.Items.SelectByGvk("apps/v1", "Deployment")
for i := range deployments {
rl.Items[i].SetOrDie(replicas, "spec", "replicas")
}
return true, nil
}
23 changes: 23 additions & 0 deletions go/fn/examples/example_select_exclude_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package example

import (
"os"

"github.com/GoogleContainerTools/kpt-functions-sdk/go/fn"
)

// This example implements a function that selectively includes or excludes some resources.

func Example_selectExclude() {
if err := fn.AsMain(fn.ResourceListProcessorFunc(selectResources)); err != nil {
os.Exit(1)
}
}

// selectResources keeps all resources with the GVK apps/v1 Deployment that do
// NOT have the label foo=bar, and removes the rest.
func selectResources(rl *fn.ResourceList) (bool, error) {
rl.Items = rl.Items.SelectByGvk("apps/v1", "Deployment").
ExcludeByLabels(map[string]string{"foo": "bar"})
return true, nil
}
126 changes: 126 additions & 0 deletions go/fn/object.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"fmt"
"reflect"
"strconv"
"strings"

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

// HasAnnotations returns whether the KubeObject has the provided annotations
func (o *KubeObject) HasAnnotations(annotations map[string]string) bool {
kubeObjectLabels := o.GetAnnotations()
for k, v := range annotations {
kubeObjectValue, found := kubeObjectLabels[k]
if !found || kubeObjectValue != v {
return false
}
}
return true
}

// RemoveAnnotationsIfEmpty removes the annotations field when it has zero annotations.
func (o *KubeObject) RemoveAnnotationsIfEmpty() error {
annotations, found, err := o.obj.GetNestedStringMap("metadata", "annotations")
Expand Down Expand Up @@ -721,6 +734,18 @@ func (o *KubeObject) GetLabels() map[string]string {
return v
}

// HasLabels returns whether the KubeObject has the provided labels
func (o *KubeObject) HasLabels(labels map[string]string) bool {
kubeObjectLabels := o.GetLabels()
for k, v := range labels {
kubeObjectValue, found := kubeObjectLabels[k]
if !found || kubeObjectValue != v {
return false
}
}
return true
}

func (o *KubeObject) PathAnnotation() string {
anno := o.GetAnnotation(kioutil.PathAnnotation)
return anno
Expand Down Expand Up @@ -759,6 +784,107 @@ func (o KubeObjects) Less(i, j int) bool {
return idStrI < idStrJ
}

func (o KubeObjects) String() string {
var elems []string
for _, obj := range o {
elems = append(elems, strings.TrimSpace(obj.String()))
}
return strings.Join(elems, "\n---\n")
}

// Select will return the subset of objects in KubeObjects such that f(object) returns 'true'.
func (o KubeObjects) Select(f func(o *KubeObject) bool) KubeObjects {
var result KubeObjects
for _, obj := range o {
if f(obj) {
result = append(result, obj)
}
}
return result
}

// SelectByGvk will return the subset of objects that matches the provided GVK.
func (o KubeObjects) SelectByGvk(apiVersion, kind string) KubeObjects {
return o.Select(func(o *KubeObject) bool {
return o.IsGVK(apiVersion, kind)
})
}

// ExcludeByGvk will return the subset of objects that do not match the provided GVK.
func (o KubeObjects) ExcludeByGvk(apiVersion, kind string) KubeObjects {
return o.Select(func(o *KubeObject) bool {
return !o.IsGVK(apiVersion, kind)
})
}

// SelectByName will return the subset of objects that matches the provided name.
func (o KubeObjects) SelectByName(name string) KubeObjects {
return o.Select(func(o *KubeObject) bool {
return o.GetName() == name
})
}

// ExcludeByName will return the subset of objects that do not match the provided name.
func (o KubeObjects) ExcludeByName(name string) KubeObjects {
return o.Select(func(o *KubeObject) bool {
return o.GetName() != name
})
}

// SelectByNamespace will return the subset of objects that matches the provided namespace.
func (o KubeObjects) SelectByNamespace(namespace string) KubeObjects {
return o.Select(func(o *KubeObject) bool {
return o.GetNamespace() == namespace
})
}

// ExcludeByNamespace will return the subset of objects that do not match the provided namespace.
func (o KubeObjects) ExcludeByNamespace(namespace string) KubeObjects {
return o.Select(func(o *KubeObject) bool {
return o.GetNamespace() != namespace
})
}

// SelectByLabels will return the subset of objects that matches the provided labels.
func (o KubeObjects) SelectByLabels(labels map[string]string) KubeObjects {
return o.Select(func(o *KubeObject) bool {
return o.HasLabels(labels)
})
}

// ExcludeByLabels will return the subset of objects that do not match the provided labels.
func (o KubeObjects) ExcludeByLabels(labels map[string]string) KubeObjects {
return o.Select(func(o *KubeObject) bool {
return !o.HasLabels(labels)
})
}

// SelectByAnnotations will return the subset of objects that matches the provided labels.
func (o KubeObjects) SelectByAnnotations(annotations map[string]string) KubeObjects {
return o.Select(func(o *KubeObject) bool {
return o.HasAnnotations(annotations)
})
}

// ExcludeByAnnotations will return the subset of objects that do not match the provided labels.
func (o KubeObjects) ExcludeByAnnotations(annotations map[string]string) KubeObjects {
return o.Select(func(o *KubeObject) bool {
return !o.HasAnnotations(annotations)
})
}

// SelectMetaResources will return the subset of objects that are meta resources. Currently, this
// is just the Kptfile.
func (o KubeObjects) SelectMetaResources() KubeObjects {
return o.SelectByGvk("kpt.dev/v1", "Kptfile")
}

// ExcludeMetaResources will return the subset of objects that are not meta resources. Currently, this
// is just the Kptfile.
func (o KubeObjects) ExcludeMetaResources() KubeObjects {
return o.ExcludeByGvk("kpt.dev/v1", "Kptfile")
}

func (o *KubeObject) IsEmpty() bool {
return yaml.IsYNodeEmptyMap(o.obj.Node())
}
Expand Down
146 changes: 146 additions & 0 deletions go/fn/object_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"testing"

"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
)

func TestIsNamespaceScoped(t *testing.T) {
Expand Down Expand Up @@ -284,3 +285,148 @@ func TestGetMap(t *testing.T) {
t.Errorf("unexpected value for GetMap(%q); got %v, want nil", "notExists", got)
}
}

func TestSelectors(t *testing.T) {
deployment := `
apiVersion: apps/v1
kind: Deployment
metadata:
name: example
namespace: default
labels:
abc: def
foo: baz
annotations:
bar: foo
`
service := `
apiVersion: apps/v1
kind: Service
metadata:
name: example
namespace: my-namespace
labels:
foo: baz
annotations:
foo: bar
`
d, err := ParseKubeObject([]byte(deployment))
assert.NoError(t, err)
s, err := ParseKubeObject([]byte(service))
assert.NoError(t, err)
input := KubeObjects{d, s}

// select all resources with labels foo=baz
items := input.SelectByLabels(map[string]string{"foo": "baz"})
assert.Equal(t, items.String(), `apiVersion: apps/v1
kind: Deployment
metadata:
name: example
namespace: default
labels:
abc: def
foo: baz
annotations:
bar: foo
---
apiVersion: apps/v1
kind: Service
metadata:
name: example
namespace: my-namespace
labels:
foo: baz
annotations:
foo: bar`)

// select all deployments
items = input.SelectByGvk("apps/v1", "Deployment")
assert.Equal(t, items.String(), `apiVersion: apps/v1
kind: Deployment
metadata:
name: example
namespace: default
labels:
abc: def
foo: baz
annotations:
bar: foo`)

// exclude all services
items = input.ExcludeByGvk("apps/v1", "Service")
assert.Equal(t, items.String(), `apiVersion: apps/v1
kind: Deployment
metadata:
name: example
namespace: default
labels:
abc: def
foo: baz
annotations:
bar: foo`)

// include resources with the label abc: def
items = input.SelectByLabels(map[string]string{"abc": "def"})
assert.Equal(t, items.String(), `apiVersion: apps/v1
kind: Deployment
metadata:
name: example
namespace: default
labels:
abc: def
foo: baz
annotations:
bar: foo`)

// exclude all resources with the annotation foo=bar
items = input.ExcludeByAnnotations(map[string]string{"foo": "bar"})
assert.Equal(t, items.String(), `apiVersion: apps/v1
kind: Deployment
metadata:
name: example
namespace: default
labels:
abc: def
foo: baz
annotations:
bar: foo`)

// include resources named 'example' that are Not in namespace default
items = input.SelectByName("example").ExcludeByNamespace("default")
assert.Equal(t, items.String(), `apiVersion: apps/v1
kind: Service
metadata:
name: example
namespace: my-namespace
labels:
foo: baz
annotations:
foo: bar`)

// add the label "g=h" to all resources with annotation "bar=foo"
items = input.SelectByAnnotations(map[string]string{"bar": "foo"})
for _, obj := range items {
obj.SetLabel("g", "h")
}
assert.Equal(t, input.String(), `apiVersion: apps/v1
kind: Deployment
metadata:
name: example
namespace: default
labels:
abc: def
foo: baz
g: h
annotations:
bar: foo
---
apiVersion: apps/v1
kind: Service
metadata:
name: example
namespace: my-namespace
labels:
foo: baz
annotations:
foo: bar`)
}
4 changes: 2 additions & 2 deletions go/fn/resourcelist.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ type ResourceList struct {
// Items will be a slice containing the Deployment and Service resources
// Mutating functions will alter this field during processing.
// This field is required.
Items []*KubeObject `yaml:"items" json:"items"`
Items KubeObjects `yaml:"items" json:"items"`

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

// Sort sorts the ResourceList.items by apiVersion, kind, namespace and name.
func (rl *ResourceList) Sort() {
sort.Sort(KubeObjects(rl.Items))
sort.Sort(rl.Items)
}

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

0 comments on commit f11c238

Please sign in to comment.