diff --git a/kyaml/filtersutil/doc.go b/kyaml/filtersutil/doc.go new file mode 100644 index 000000000..b5cb975fa --- /dev/null +++ b/kyaml/filtersutil/doc.go @@ -0,0 +1,6 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +// Package filtersutil provides utilities for working with yaml.Filter and +// kio.Filter interfaces. +package filtersutil diff --git a/kyaml/filtersutil/example_test.go b/kyaml/filtersutil/example_test.go new file mode 100644 index 000000000..3ac6415c9 --- /dev/null +++ b/kyaml/filtersutil/example_test.go @@ -0,0 +1,58 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package filtersutil_test + +import ( + "bytes" + "fmt" + "log" + "testing" + + "github.com/stretchr/testify/assert" + "sigs.k8s.io/kustomize/kyaml/filtersutil" + "sigs.k8s.io/kustomize/kyaml/kio" + "sigs.k8s.io/kustomize/kyaml/yaml" +) + +func TestApplyToJSON(t *testing.T) { + // testFilter sets `foo: bar` on each resource + testFilter := kio.FilterAll(yaml.FilterFunc( + func(node *yaml.RNode) (*yaml.RNode, error) { + set := yaml.SetField( + "foo", yaml.NewScalarRNode("bar")) + err := node.PipeE(set) + if !assert.NoError(t, err) { + t.FailNow() + } + return node, nil + })) + + obj1 := buffer{Buffer: bytes.NewBufferString(`{"kind": "Foo"}`)} + obj2 := buffer{Buffer: bytes.NewBufferString(`{"kind": "Bar"}`)} + err := filtersutil.ApplyToJSON(testFilter, obj1, obj2) + if err != nil { + log.Fatal(err) + } + + fmt.Println(obj1.String()) + fmt.Println(obj2.String()) + + // Output: + // {"foo":"bar","kind":"Foo"} + // {"foo":"bar","kind":"Bar"} +} + +type buffer struct { + *bytes.Buffer +} + +func (buff buffer) UnmarshalJSON(b []byte) error { + buff.Reset() + buff.Write(b) + return nil +} + +func (buff buffer) MarshalJSON() ([]byte, error) { + return buff.Bytes(), nil +} diff --git a/kyaml/filtersutil/filtersutil.go b/kyaml/filtersutil/filtersutil.go new file mode 100644 index 000000000..b01f961c0 --- /dev/null +++ b/kyaml/filtersutil/filtersutil.go @@ -0,0 +1,83 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package filtersutil + +import ( + "encoding/json" + + "sigs.k8s.io/kustomize/kyaml/errors" + "sigs.k8s.io/kustomize/kyaml/kio" + "sigs.k8s.io/kustomize/kyaml/yaml" +) + +// ApplyToJSON applies the filter to the json objects. +// +// ApplyToJSON marshals the objects into a slice of yaml.RNodes, runs +// the filter on the slice, and then unmarshals the values back. +// +// The filter must not create or delete objects because the objects +// are updated in place. +func ApplyToJSON(filter kio.Filter, objs ...marshalerUnmarshaler) error { + var nodes []*yaml.RNode + + // convert the json objects to rnodes + for i := range objs { + node, err := getRNode(objs[i]) + if err != nil { + return err + } + nodes = append(nodes, node) + } + + // apply the filter + nodes, err := filter.Filter(nodes) + if err != nil { + return err + } + if len(nodes) != len(objs) { + return errors.Errorf("filter cannot create or delete objects") + } + + // convert the rnodes to json objects + for i := range nodes { + err = setRNode(objs[i], nodes[0]) + if err != nil { + return err + } + } + + return nil +} + +type marshalerUnmarshaler interface { + json.Unmarshaler + json.Marshaler +} + +// getRNode converts k into an RNode +func getRNode(k json.Marshaler) (*yaml.RNode, error) { + j, err := k.MarshalJSON() + if err != nil { + return nil, err + } + return yaml.Parse(string(j)) +} + +// setRNode marshals node into k +func setRNode(k json.Unmarshaler, node *yaml.RNode) error { + s, err := node.String() + if err != nil { + return err + } + m := map[string]interface{}{} + if err := yaml.Unmarshal([]byte(s), &m); err != nil { + return err + } + + b, err := json.Marshal(m) + if err != nil { + return err + } + return k.UnmarshalJSON(b) +}