Filter helper for fallback field lookup

This commit is contained in:
Katrina Verey
2021-08-10 16:07:54 -07:00
parent 7b5e43d343
commit 7a41e479c9
2 changed files with 102 additions and 1 deletions

View File

@@ -424,12 +424,46 @@ func Lookup(path ...string) PathGetter {
return PathGetter{Path: path}
}
// Lookup returns a PathGetter to lookup a field by its path and create it if it doesn't already
// LookupCreate returns a PathGetter to lookup a field by its path and create it if it doesn't already
// exist.
func LookupCreate(kind yaml.Kind, path ...string) PathGetter {
return PathGetter{Path: path, Create: kind}
}
// ConventionalContainerPaths is a list of paths at which containers typically appear in workload APIs.
// It is intended for use with LookupFirstMatch.
var ConventionalContainerPaths = [][]string{
// e.g. Deployment, ReplicaSet, DaemonSet, Job, StatefulSet
{"spec", "template", "spec", "containers"},
// e.g. CronJob
{"spec", "jobTemplate", "spec", "template", "spec", "containers"},
// e.g. Pod
{"spec", "containers"},
// e.g. PodTemplate
{"template", "spec", "containers"},
}
// LookupFirstMatch returns a Filter for locating a value that may exist at one of several possible paths.
// For example, it can be used with ConventionalContainerPaths to find the containers field in a standard workload resource.
// If more than one of the paths exists in the resource, the first will be returned. If none exist,
// nil will be returned. If an error is encountered during lookup, it will be returned.
func LookupFirstMatch(paths [][]string) Filter {
return FilterFunc(func(object *RNode) (*RNode, error) {
var result *RNode
var err error
for _, path := range paths {
result, err = object.Pipe(PathGetter{Path: path})
if err != nil {
return nil, errors.Wrap(err)
}
if result != nil {
return result, nil
}
}
return nil, nil
})
}
// PathGetter returns the RNode under Path.
type PathGetter struct {
Kind string `yaml:"kind,omitempty"`

View File

@@ -8,6 +8,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"sigs.k8s.io/kustomize/kyaml/internal/forked/github.com/go-yaml/yaml"
. "sigs.k8s.io/kustomize/kyaml/yaml"
)
@@ -724,6 +725,72 @@ j: k
assert.Nil(t, rn)
}
func TestLookupFirstMatch(t *testing.T) {
tests := []struct {
name string
paths [][]string
wantPath []string
}{
{
name: "finds path that exists",
paths: [][]string{{"spec", "jobTemplate", "spec", "template", "spec", "containers"}},
wantPath: []string{"spec", "jobTemplate", "spec", "template", "spec", "containers"},
},
{
name: "chooses first path when multiple exist: containers example",
paths: ConventionalContainerPaths,
wantPath: []string{"spec", "template", "spec", "containers"},
},
{
name: "chooses first path when multiple exist: annotations example",
paths: [][]string{
{"metadata", "annotations", "example.kustomize.io/new"},
{"metadata", "annotations", "example.kustomize.io/deprecated"},
},
wantPath: []string{"metadata", "annotations", "example.kustomize.io/new"},
},
{
name: "returns nil when path does not exist",
paths: [][]string{
{"metadata", "annotations", "example.kustomize.io/does-not-exist"},
{"metadata", "annotations", "example.kustomize.io/also-not-exist"},
},
wantPath: nil,
},
}
for _, tt := range tests {
s := `
apiVersion: example.kustomize.io/v1
kind: Custom
metadata:
annotations:
example.kustomize.io/deprecated: foo
example.kustomize.io/new: foo
spec:
template:
spec:
containers:
- name: foo
jobTemplate:
spec:
template:
spec:
containers:
- name: foo
`
resource := MustParse(s)
t.Run(tt.name, func(t *testing.T) {
result, err := LookupFirstMatch(tt.paths).Filter(resource)
require.NoError(t, err)
if tt.wantPath != nil {
assert.Equal(t, tt.wantPath, result.FieldPath())
} else {
assert.Nil(t, result)
}
})
}
}
func TestFieldSetter(t *testing.T) {
// Change field
node, err := Parse(`