Remove the wrappy layer.

This commit is contained in:
monopole
2021-03-10 05:38:39 -08:00
parent e4bbd04a43
commit 123a5d6e56
37 changed files with 1537 additions and 1750 deletions

View File

@@ -8,6 +8,7 @@ import (
"fmt"
"io/ioutil"
"log"
"regexp"
"strconv"
"strings"
@@ -336,6 +337,33 @@ func (rn *RNode) SetYNode(node *yaml.Node) {
*rn.value = *node
}
// GetKind returns the kind.
func (rn *RNode) GetKind() string {
node, err := rn.Pipe(FieldMatcher{Name: KindField})
if err != nil {
return ""
}
return GetValue(node)
}
// GetName returns the name.
func (rn *RNode) GetName() string {
f := rn.Field(MetadataField)
if f.IsNilOrEmpty() {
return ""
}
f = f.Value.Field(NameField)
if f.IsNilOrEmpty() {
return ""
}
return f.Value.YNode().Value
}
// SetName sets the metadata name field.
func (rn *RNode) SetName(name string) error {
return rn.SetMapField(NewScalarRNode(name), MetadataField, NameField)
}
// GetNamespace gets the metadata namespace field.
func (rn *RNode) GetNamespace() (string, error) {
meta, err := rn.GetMeta()
@@ -752,7 +780,7 @@ func (rn *RNode) GetValidatedMetadata() (ResourceMeta, error) {
return m, nil
}
// MatchesAnnotationSelector implements ifc.Kunstructured.
// MatchesAnnotationSelector returns true on a selector match to annotations.
func (rn *RNode) MatchesAnnotationSelector(selector string) (bool, error) {
s, err := labels.Parse(selector)
if err != nil {
@@ -765,7 +793,7 @@ func (rn *RNode) MatchesAnnotationSelector(selector string) (bool, error) {
return s.Matches(labels.Set(slice)), nil
}
// MatchesLabelSelector implements ifc.Kunstructured.
// MatchesLabelSelector returns true on a selector match to labels.
func (rn *RNode) MatchesLabelSelector(selector string) (bool, error) {
s, err := labels.Parse(selector)
if err != nil {
@@ -859,3 +887,123 @@ func checkKey(key string, elems []*Node) bool {
}
return count == len(elems)
}
// Deprecated: use pipes instead.
// GetSlice returns the contents of the slice field at the given path.
func (rn *RNode) GetSlice(path string) ([]interface{}, error) {
value, err := rn.GetFieldValue(path)
if err != nil {
return nil, err
}
if sliceValue, ok := value.([]interface{}); ok {
return sliceValue, nil
}
return nil, fmt.Errorf("node %s is not a slice", path)
}
// Deprecated: use pipes instead.
// GetString returns the contents of the string field at the given path.
func (rn *RNode) GetString(path string) (string, error) {
value, err := rn.GetFieldValue(path)
if err != nil {
return "", err
}
if v, ok := value.(string); ok {
return v, nil
}
return "", fmt.Errorf("node %s is not a string: %v", path, value)
}
// Deprecated: use slash paths instead.
// GetFieldValue finds period delimited fields.
// TODO: When doing kustomize var replacement, which is likely a
// a primary use of this function and the reason it returns interface{}
// rather than string, we do conversion from Nodes to Go types and back
// to nodes. We should figure out how to do replacement using raw nodes,
// assuming we keep the var feature in kustomize.
// The other end of this is: refvar.go:updateNodeValue.
func (rn *RNode) GetFieldValue(path string) (interface{}, error) {
fields := convertSliceIndex(strings.Split(path, "."))
rn, err := rn.Pipe(Lookup(fields...))
if err != nil {
return nil, err
}
if rn == nil {
return nil, NoFieldError{path}
}
yn := rn.YNode()
// If this is an alias node, resolve it
if yn.Kind == yaml.AliasNode {
yn = yn.Alias
}
// Return value as map for DocumentNode and MappingNode kinds
if yn.Kind == yaml.DocumentNode || yn.Kind == yaml.MappingNode {
var result map[string]interface{}
if err := yn.Decode(&result); err != nil {
return nil, err
}
return result, err
}
// Return value as slice for SequenceNode kind
if yn.Kind == yaml.SequenceNode {
var result []interface{}
if err := yn.Decode(&result); err != nil {
return nil, err
}
return result, nil
}
if yn.Kind != yaml.ScalarNode {
return nil, fmt.Errorf("expected ScalarNode, got Kind=%d", yn.Kind)
}
switch yn.Tag {
case NodeTagString:
return yn.Value, nil
case NodeTagInt:
return strconv.Atoi(yn.Value)
case NodeTagFloat:
return strconv.ParseFloat(yn.Value, 64)
case NodeTagBool:
return strconv.ParseBool(yn.Value)
default:
// Possibly this should be an error or log.
return yn.Value, nil
}
}
// convertSliceIndex traverses the items in `fields` and find
// if there is a slice index in the item and change it to a
// valid Lookup field path. For example, 'ports[0]' will be
// converted to 'ports' and '0'.
func convertSliceIndex(fields []string) []string {
var res []string
for _, s := range fields {
if !strings.HasSuffix(s, "]") {
res = append(res, s)
continue
}
re := regexp.MustCompile(`^(.*)\[(\d+)\]$`)
groups := re.FindStringSubmatch(s)
if len(groups) == 0 {
// no match, add to result
res = append(res, s)
continue
}
if groups[1] != "" {
res = append(res, groups[1])
}
res = append(res, groups[2])
}
return res
}
type NoFieldError struct {
Field string
}
func (e NoFieldError) Error() string {
return fmt.Sprintf("no field named '%s'", e.Field)
}

View File

@@ -10,6 +10,7 @@ import (
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
)
func TestRNodeHasNilEntryInList(t *testing.T) {
@@ -1130,3 +1131,627 @@ func TestRNodeMatchesLabelSelector(t *testing.T) {
}
}
}
const (
deploymentLittleJson = `{"apiVersion":"apps/v1","kind":"Deployment",` +
`"metadata":{"name":"homer","namespace":"simpsons"}}`
deploymentBiggerJson = `
{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"name": "homer",
"namespace": "simpsons",
"labels": {
"fruit": "apple",
"veggie": "carrot"
},
"annotations": {
"area": "51",
"greeting": "Take me to your leader."
}
},
"spec": {
"template": {
"spec": {
"containers": [
{
"env": [
{
"name": "CM_FOO",
"valueFrom": {
"configMapKeyRef": {
"key": "somekey",
"name": "myCm"
}
}
},
{
"name": "SECRET_FOO",
"valueFrom": {
"secretKeyRef": {
"key": "someKey",
"name": "mySecret"
}
}
}
],
"image": "nginx:1.7.9",
"name": "nginx"
}
]
}
}
}
}
`
bigMapYaml = `Kind: Service
complextree:
- field1:
- boolfield: true
floatsubfield: 1.01
intsubfield: 1010
stringsubfield: idx1010
- boolfield: false
floatsubfield: 1.011
intsubfield: 1011
stringsubfield: idx1011
field2:
- boolfield: true
floatsubfield: 1.02
intsubfield: 1020
stringsubfield: idx1020
- boolfield: false
floatsubfield: 1.021
intsubfield: 1021
stringsubfield: idx1021
- field1:
- boolfield: true
floatsubfield: 1.11
intsubfield: 1110
stringsubfield: idx1110
- boolfield: false
floatsubfield: 1.111
intsubfield: 1111
stringsubfield: idx1111
field2:
- boolfield: true
floatsubfield: 1.112
intsubfield: 1120
stringsubfield: idx1120
- boolfield: false
floatsubfield: 1.1121
intsubfield: 1121
stringsubfield: idx1121
metadata:
labels:
app: application-name
name: service-name
spec:
ports:
port: 80
that:
- idx0
- idx1
- idx2
- idx3
these:
- field1:
- idx010
- idx011
field2:
- idx020
- idx021
- field1:
- idx110
- idx111
field2:
- idx120
- idx121
- field1:
- idx210
- idx211
field2:
- idx220
- idx221
this:
is:
aBool: true
aFloat: 1.001
aNilValue: null
aNumber: 1000
anEmptyMap: {}
anEmptySlice: []
those:
- field1: idx0foo
field2: idx0bar
- field1: idx1foo
field2: idx1bar
- field1: idx2foo
field2: idx2bar
`
)
func TestGetFieldValueReturnsMap(t *testing.T) {
rn := NewRNode(nil)
if err := rn.UnmarshalJSON([]byte(deploymentBiggerJson)); err != nil {
t.Fatalf("unexpected unmarshaljson err: %v", err)
}
expected := map[string]interface{}{
"fruit": "apple",
"veggie": "carrot",
}
actual, err := rn.GetFieldValue("metadata.labels")
if err != nil {
t.Fatalf("error getting field value: %v", err)
}
if diff := cmp.Diff(expected, actual); diff != "" {
t.Fatalf("actual map does not deep equal expected map:\n%v", diff)
}
}
func TestGetFieldValueReturnsStuff(t *testing.T) {
wn := NewRNode(nil)
if err := wn.UnmarshalJSON([]byte(deploymentBiggerJson)); err != nil {
t.Fatalf("unexpected unmarshaljson err: %v", err)
}
expected := []interface{}{
map[string]interface{}{
"env": []interface{}{
map[string]interface{}{
"name": "CM_FOO",
"valueFrom": map[string]interface{}{
"configMapKeyRef": map[string]interface{}{
"key": "somekey",
"name": "myCm",
},
},
},
map[string]interface{}{
"name": "SECRET_FOO",
"valueFrom": map[string]interface{}{
"secretKeyRef": map[string]interface{}{
"key": "someKey",
"name": "mySecret",
},
},
},
},
"image": string("nginx:1.7.9"),
"name": string("nginx"),
},
}
actual, err := wn.GetFieldValue("spec.template.spec.containers")
if err != nil {
t.Fatalf("error getting field value: %v", err)
}
if diff := cmp.Diff(expected, actual); diff != "" {
t.Fatalf("actual map does not deep equal expected map:\n%v", diff)
}
// Cannot go deeper yet.
_, err = wn.GetFieldValue("spec.template.spec.containers.env")
if err == nil {
t.Fatalf("expected err %v", err)
}
}
func makeBigMap() map[string]interface{} {
return map[string]interface{}{
"Kind": "Service",
"metadata": map[string]interface{}{
"labels": map[string]interface{}{
"app": "application-name",
},
"name": "service-name",
},
"spec": map[string]interface{}{
"ports": map[string]interface{}{
"port": int64(80),
},
},
"this": map[string]interface{}{
"is": map[string]interface{}{
"aNumber": int64(1000),
"aFloat": float64(1.001),
"aNilValue": nil,
"aBool": true,
"anEmptyMap": map[string]interface{}{},
"anEmptySlice": []interface{}{},
/*
TODO: test for unrecognizable (e.g. a function)
"unrecognizable": testing.InternalExample{
Name: "fooBar",
},
*/
},
},
"that": []interface{}{
"idx0",
"idx1",
"idx2",
"idx3",
},
"those": []interface{}{
map[string]interface{}{
"field1": "idx0foo",
"field2": "idx0bar",
},
map[string]interface{}{
"field1": "idx1foo",
"field2": "idx1bar",
},
map[string]interface{}{
"field1": "idx2foo",
"field2": "idx2bar",
},
},
"these": []interface{}{
map[string]interface{}{
"field1": []interface{}{"idx010", "idx011"},
"field2": []interface{}{"idx020", "idx021"},
},
map[string]interface{}{
"field1": []interface{}{"idx110", "idx111"},
"field2": []interface{}{"idx120", "idx121"},
},
map[string]interface{}{
"field1": []interface{}{"idx210", "idx211"},
"field2": []interface{}{"idx220", "idx221"},
},
},
"complextree": []interface{}{
map[string]interface{}{
"field1": []interface{}{
map[string]interface{}{
"stringsubfield": "idx1010",
"intsubfield": int64(1010),
"floatsubfield": float64(1.010),
"boolfield": true,
},
map[string]interface{}{
"stringsubfield": "idx1011",
"intsubfield": int64(1011),
"floatsubfield": float64(1.011),
"boolfield": false,
},
},
"field2": []interface{}{
map[string]interface{}{
"stringsubfield": "idx1020",
"intsubfield": int64(1020),
"floatsubfield": float64(1.020),
"boolfield": true,
},
map[string]interface{}{
"stringsubfield": "idx1021",
"intsubfield": int64(1021),
"floatsubfield": float64(1.021),
"boolfield": false,
},
},
},
map[string]interface{}{
"field1": []interface{}{
map[string]interface{}{
"stringsubfield": "idx1110",
"intsubfield": int64(1110),
"floatsubfield": float64(1.110),
"boolfield": true,
},
map[string]interface{}{
"stringsubfield": "idx1111",
"intsubfield": int64(1111),
"floatsubfield": float64(1.111),
"boolfield": false,
},
},
"field2": []interface{}{
map[string]interface{}{
"stringsubfield": "idx1120",
"intsubfield": int64(1120),
"floatsubfield": float64(1.1120),
"boolfield": true,
},
map[string]interface{}{
"stringsubfield": "idx1121",
"intsubfield": int64(1121),
"floatsubfield": float64(1.1121),
"boolfield": false,
},
},
},
},
}
}
func TestBasicYamlOperationFromMap(t *testing.T) {
bytes, err := Marshal(makeBigMap())
if err != nil {
t.Fatalf("unexpected yaml.Marshal err: %v", err)
}
if string(bytes) != bigMapYaml {
t.Fatalf("unexpected string equality")
}
rNode, err := Parse(string(bytes))
if err != nil {
t.Fatalf("unexpected yaml.Marshal err: %v", err)
}
rNodeString := rNode.MustString()
// The result from MustString has more indentation
// than bigMapYaml.
rNodeStrings := strings.Split(rNodeString, "\n")
bigMapStrings := strings.Split(bigMapYaml, "\n")
if len(rNodeStrings) != len(bigMapStrings) {
t.Fatalf("line count mismatch")
}
for i := range rNodeStrings {
s1 := strings.TrimSpace(rNodeStrings[i])
s2 := strings.TrimSpace(bigMapStrings[i])
if s1 != s2 {
t.Fatalf("expected '%s'=='%s'", s1, s2)
}
}
}
func TestGetFieldValueReturnsSlice(t *testing.T) {
bytes, err := yaml.Marshal(makeBigMap())
if err != nil {
t.Fatalf("unexpected yaml.Marshal err: %v", err)
}
rNode, err := Parse(string(bytes))
if err != nil {
t.Fatalf("unexpected yaml.Marshal err: %v", err)
}
expected := []interface{}{"idx0", "idx1", "idx2", "idx3"}
actual, err := rNode.GetFieldValue("that")
if err != nil {
t.Fatalf("error getting slice: %v", err)
}
if diff := cmp.Diff(expected, actual); diff != "" {
t.Fatalf("actual slice does not deep equal expected slice:\n%v", diff)
}
}
func TestGetFieldValueReturnsSliceOfMappings(t *testing.T) {
bytes, err := yaml.Marshal(makeBigMap())
if err != nil {
t.Fatalf("unexpected yaml.Marshal err: %v", err)
}
rn, err := Parse(string(bytes))
if err != nil {
t.Fatalf("unexpected yaml.Marshal err: %v", err)
}
expected := []interface{}{
map[string]interface{}{
"field1": "idx0foo",
"field2": "idx0bar",
},
map[string]interface{}{
"field1": "idx1foo",
"field2": "idx1bar",
},
map[string]interface{}{
"field1": "idx2foo",
"field2": "idx2bar",
},
}
actual, err := rn.GetFieldValue("those")
if err != nil {
t.Fatalf("error getting slice: %v", err)
}
if diff := cmp.Diff(expected, actual); diff != "" {
t.Fatalf("actual slice does not deep equal expected slice:\n%v", diff)
}
}
func TestGetFieldValueReturnsString(t *testing.T) {
rn := NewRNode(nil)
if err := rn.UnmarshalJSON([]byte(deploymentBiggerJson)); err != nil {
t.Fatalf("unexpected unmarshaljson err: %v", err)
}
actual, err := rn.GetFieldValue("metadata.labels.fruit")
if err != nil {
t.Fatalf("error getting field value: %v", err)
}
v, ok := actual.(string)
if !ok || v != "apple" {
t.Fatalf("unexpected value '%v'", actual)
}
}
func TestGetFieldValueResolvesAlias(t *testing.T) {
yamlWithAlias := `
foo: &a theValue
bar: *a
`
rn, err := Parse(yamlWithAlias)
if err != nil {
t.Fatalf("unexpected yaml parse error: %v", err)
}
actual, err := rn.GetFieldValue("bar")
if err != nil {
t.Fatalf("error getting field value: %v", err)
}
v, ok := actual.(string)
if !ok || v != "theValue" {
t.Fatalf("unexpected value '%v'", actual)
}
}
func TestGetString(t *testing.T) {
rn := NewRNode(nil)
if err := rn.UnmarshalJSON([]byte(deploymentBiggerJson)); err != nil {
t.Fatalf("unexpected unmarshaljson err: %v", err)
}
expected := "carrot"
actual, err := rn.GetString("metadata.labels.veggie")
if err != nil {
t.Fatalf("error getting string: %v", err)
}
if expected != actual {
t.Fatalf("expected '%s', got '%s'", expected, actual)
}
}
func TestGetSlice(t *testing.T) {
bytes, err := yaml.Marshal(makeBigMap())
if err != nil {
t.Fatalf("unexpected yaml.Marshal err: %v", err)
}
rn, err := Parse(string(bytes))
if err != nil {
t.Fatalf("unexpected yaml.Marshal err: %v", err)
}
expected := []interface{}{"idx0", "idx1", "idx2", "idx3"}
actual, err := rn.GetSlice("that")
if err != nil {
t.Fatalf("error getting slice: %v", err)
}
if diff := cmp.Diff(expected, actual); diff != "" {
t.Fatalf("actual slice does not deep equal expected slice:\n%v", diff)
}
}
func TestRoundTripJSON(t *testing.T) {
rn := NewRNode(nil)
err := rn.UnmarshalJSON([]byte(deploymentLittleJson))
if err != nil {
t.Fatalf("unexpected UnmarshalJSON err: %v", err)
}
data, err := rn.MarshalJSON()
if err != nil {
t.Fatalf("unexpected MarshalJSON err: %v", err)
}
actual := string(data)
if actual != deploymentLittleJson {
t.Fatalf("expected %s, got %s", deploymentLittleJson, actual)
}
}
func TestGettingFields(t *testing.T) {
rn := NewRNode(nil)
err := rn.UnmarshalJSON([]byte(deploymentBiggerJson))
if err != nil {
t.Fatalf("unexpected unmarshaljson err: %v", err)
}
expected := "Deployment"
actual := rn.GetKind()
if expected != actual {
t.Fatalf("expected '%s', got '%s'", expected, actual)
}
expected = "homer"
actual = rn.GetName()
if expected != actual {
t.Fatalf("expected '%s', got '%s'", expected, actual)
}
actualMap, err := rn.GetLabels()
if err != nil {
t.Fatalf("unexpected err '%v'", err)
}
v, ok := actualMap["fruit"]
if !ok || v != "apple" {
t.Fatalf("unexpected labels '%v'", actualMap)
}
actualMap, err = rn.GetAnnotations()
if err != nil {
t.Fatalf("unexpected err '%v'", err)
}
v, ok = actualMap["greeting"]
if !ok || v != "Take me to your leader." {
t.Fatalf("unexpected annotations '%v'", actualMap)
}
}
func TestMapEmpty(t *testing.T) {
newNodeMap, err := NewRNode(nil).Map()
assert.NoError(t, err)
assert.Equal(t, 0, len(newNodeMap))
}
func TestMap(t *testing.T) {
rn := NewRNode(nil)
if err := rn.UnmarshalJSON([]byte(deploymentLittleJson)); err != nil {
t.Fatalf("unexpected unmarshaljson err: %v", err)
}
expected := map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": map[string]interface{}{
"name": "homer",
"namespace": "simpsons",
},
}
actual, err := rn.Map()
assert.NoError(t, err)
if diff := cmp.Diff(expected, actual); diff != "" {
t.Fatalf("actual map does not deep equal expected map:\n%v", diff)
}
}
func TestSetName(t *testing.T) {
rn := NewRNode(nil)
if err := rn.UnmarshalJSON([]byte(deploymentBiggerJson)); err != nil {
t.Fatalf("unexpected unmarshaljson err: %v", err)
}
rn.SetName("marge")
if expected, actual := "marge", rn.GetName(); expected != actual {
t.Fatalf("expected '%s', got '%s'", expected, actual)
}
}
func TestSetNamespace(t *testing.T) {
rn := NewRNode(nil)
if err := rn.UnmarshalJSON([]byte(deploymentBiggerJson)); err != nil {
t.Fatalf("unexpected unmarshaljson err: %v", err)
}
rn.SetNamespace("flanders")
meta, _ := rn.GetMeta()
if expected, actual := "flanders", meta.Namespace; expected != actual {
t.Fatalf("expected '%s', got '%s'", expected, actual)
}
}
func TestSetLabels(t *testing.T) {
rn := NewRNode(nil)
if err := rn.UnmarshalJSON([]byte(deploymentBiggerJson)); err != nil {
t.Fatalf("unexpected unmarshaljson err: %v", err)
}
rn.SetLabels(map[string]string{
"label1": "foo",
"label2": "bar",
})
labels, err := rn.GetLabels()
assert.NoError(t, err)
if expected, actual := 2, len(labels); expected != actual {
t.Fatalf("expected '%d', got '%d'", expected, actual)
}
if expected, actual := "foo", labels["label1"]; expected != actual {
t.Fatalf("expected '%s', got '%s'", expected, actual)
}
if expected, actual := "bar", labels["label2"]; expected != actual {
t.Fatalf("expected '%s', got '%s'", expected, actual)
}
}
func TestGetAnnotations(t *testing.T) {
rn := NewRNode(nil)
if err := rn.UnmarshalJSON([]byte(deploymentBiggerJson)); err != nil {
t.Fatalf("unexpected unmarshaljson err: %v", err)
}
rn.SetAnnotations(map[string]string{
"annotation1": "foo",
"annotation2": "bar",
})
annotations, err := rn.GetAnnotations()
assert.NoError(t, err)
if expected, actual := 2, len(annotations); expected != actual {
t.Fatalf("expected '%d', got '%d'", expected, actual)
}
if expected, actual := "foo", annotations["annotation1"]; expected != actual {
t.Fatalf("expected '%s', got '%s'", expected, actual)
}
if expected, actual := "bar", annotations["annotation2"]; expected != actual {
t.Fatalf("expected '%s', got '%s'", expected, actual)
}
}