Skip to content

Commit

Permalink
add replacement filter to support replacmenttransformer
Browse files Browse the repository at this point in the history
  • Loading branch information
natasha41575 committed Mar 22, 2021
1 parent 710db98 commit 14b64b1
Show file tree
Hide file tree
Showing 5 changed files with 527 additions and 2 deletions.
4 changes: 4 additions & 0 deletions api/filters/replacement/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// Package replacement contains a kio.Filter implementation of the kustomize
// replacement transformer (accepts sources and looks for targets to replace
// their values with values from the sources).
package replacement
53 changes: 53 additions & 0 deletions api/filters/replacement/example_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Copyright 2020 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0

package replacement

import (
"bytes"
"log"
"os"

"sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/yaml"
)

func ExampleFilter() {
f := Filter{}
err := yaml.Unmarshal([]byte(`
replacements:
- source:
value: 99
target:
objref:
kind: Foo
fieldrefs:
- spec.replicas`), &f)
if err != nil {
log.Fatal(err)
}

err = kio.Pipeline{
Inputs: []kio.Reader{&kio.ByteReader{Reader: bytes.NewBufferString(`
apiVersion: example.com/v1
kind: Foo
metadata:
name: instance
spec:
replicas: 3
`)}},
Filters: []kio.Filter{f},
Outputs: []kio.Writer{kio.ByteWriter{Writer: os.Stdout}},
}.Execute()
if err != nil {
log.Fatal(err)
}

// Output:
// apiVersion: example.com/v1
// kind: Foo
// metadata:
// name: instance
// spec:
// replicas: 99
}
118 changes: 118 additions & 0 deletions api/filters/replacement/replacement.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0

package replacement

import (
"fmt"
"strings"

"sigs.k8s.io/kustomize/api/types"
"sigs.k8s.io/kustomize/kyaml/yaml"
)

type Filter struct {
Replacements []types.Replacement
}

const DefaultFieldRef = ".metadata.name"

// Filter replaces values of targets with values from sources
// TODO (#3492): Connect this to a replacement transformer plugin
func (f Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
for _, r := range f.Replacements {
value, err := getReplacement(nodes, r.Source)
if err != nil {
return nil, err
}
nodes, err = applyReplacement(nodes, value, r.Target)
if err != nil {
return nil, err
}
}
return nodes, nil
}

func applyReplacement(nodes []*yaml.RNode, value *yaml.RNode, target *types.ReplTarget) ([]*yaml.RNode, error) {
targets, err := getMatches(nodes, target.ObjRef)
if err != nil {
return nil, err
}
for _, n := range targets {
err := applyToNode(n, value, target)
if err != nil {
return nil, err
}
}
return nodes, nil
}

func applyToNode(node *yaml.RNode, value *yaml.RNode, target *types.ReplTarget) error {
if len(target.FieldRefs) == 0 {
target.FieldRefs = []string{DefaultFieldRef}
}
for i := range target.FieldRefs {
fieldref := strings.Split(target.FieldRefs[i], ".")
// TODO (#3492): Support using map keys in the fieldref (e.g. .spec.containers[name=nginx])
t, err := node.Pipe(yaml.Lookup(fieldref...))
if err != nil {
return err
}
if t != nil {
t.SetYNode(value.YNode())
}
}
return nil
}

func getReplacement(nodes []*yaml.RNode, source *types.ReplSource) (*yaml.RNode, error) {
if source.Value != "" {
if source.ObjRef != nil || source.FieldRef != "" {
return nil, fmt.Errorf("objref and fieldref must be empty if source value is provided")
}
return yaml.Parse(source.Value)
}

matches, err := getMatches(nodes, source.ObjRef)
if err != nil {
return nil, err
}
if len(matches) > 1 {
return nil, fmt.Errorf("found more than one resources matching from %v", source.ObjRef)
}
if len(matches) == 0 {
return nil, fmt.Errorf("failed to find a resource matching from %v", source.ObjRef)
}

fieldRef := source.FieldRef
if fieldRef == "" {
fieldRef = DefaultFieldRef
}
fieldRefSlice := strings.Split(fieldRef, ".")

// TODO (#3492): Support using map keys in the fieldref (e.g. .spec.containers[name=nginx])
rn, err := matches[0].Pipe(yaml.Lookup(fieldRefSlice...))
if err != nil {
return nil, err
}
return rn, nil
}

// getMatches finds the node that matches the objRef
func getMatches(nodes []*yaml.RNode, objRef *types.Target) ([]*yaml.RNode, error) {
var matches []*yaml.RNode
for _, n := range nodes {
ns, err := n.GetNamespace()
if err != nil {
return nil, err
}
apiVersion := yaml.GetValue(n.Field(yaml.APIVersionField).Value)
if (n.GetName() == objRef.Name || objRef.Name == "") &&
(n.GetKind() == objRef.Kind || objRef.Kind == "") &&
(ns == objRef.Namespace || objRef.Namespace == "") &&
(apiVersion == objRef.ApiVersion() || objRef.ApiVersion() == "") {
matches = append(matches, n)
}
}
return matches, nil
}
Loading

0 comments on commit 14b64b1

Please sign in to comment.