mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-06-14 02:20:53 +00:00
Introduce some RNode validation methods.
This commit is contained in:
@@ -7,6 +7,8 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
"sigs.k8s.io/kustomize/kyaml/errors"
|
"sigs.k8s.io/kustomize/kyaml/errors"
|
||||||
@@ -456,8 +458,8 @@ func (rn *RNode) Elements() ([]*RNode, error) {
|
|||||||
return elements, nil
|
return elements, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ElementValues returns a list of all observed values for a given field name in a
|
// ElementValues returns a list of all observed values for a given field name
|
||||||
// list of elements.
|
// in a list of elements.
|
||||||
// Returns error for non-SequenceNodes.
|
// Returns error for non-SequenceNodes.
|
||||||
func (rn *RNode) ElementValues(key string) ([]string, error) {
|
func (rn *RNode) ElementValues(key string) ([]string, error) {
|
||||||
if err := ErrorIfInvalid(rn, yaml.SequenceNode); err != nil {
|
if err := ErrorIfInvalid(rn, yaml.SequenceNode); err != nil {
|
||||||
@@ -600,6 +602,55 @@ func (rn *RNode) UnmarshalJSON(b []byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetValidatedMetadata returns metadata after subjecting it to some tests.
|
||||||
|
func (rn *RNode) GetValidatedMetadata() (ResourceMeta, error) {
|
||||||
|
m, err := rn.GetMeta()
|
||||||
|
if err != nil {
|
||||||
|
return m, err
|
||||||
|
}
|
||||||
|
if m.Kind == "" {
|
||||||
|
return m, fmt.Errorf("missing kind in object %v", m)
|
||||||
|
}
|
||||||
|
if strings.HasSuffix(m.Kind, "List") {
|
||||||
|
// A list doesn't require a name.
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
if m.NameMeta.Name == "" {
|
||||||
|
return m, fmt.Errorf("missing metadata.name in object %v", m)
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasNilEntryInList returns true if the RNode contains a list which has
|
||||||
|
// a nil item, along with the path to the missing item.
|
||||||
|
// TODO(broken): This was copied from
|
||||||
|
// api/k8sdeps/kunstruct/factory.go//checkListItemNil
|
||||||
|
// and doesn't do what it claims to do (see TODO in unit test and pr 1513).
|
||||||
|
func (rn *RNode) HasNilEntryInList() (bool, string) {
|
||||||
|
return hasNilEntryInList(rn.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasNilEntryInList(in interface{}) (bool, string) {
|
||||||
|
switch v := in.(type) {
|
||||||
|
case map[string]interface{}:
|
||||||
|
for key, s := range v {
|
||||||
|
if result, path := hasNilEntryInList(s); result {
|
||||||
|
return result, key + "/" + path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case []interface{}:
|
||||||
|
for index, s := range v {
|
||||||
|
if s == nil {
|
||||||
|
return true, ""
|
||||||
|
}
|
||||||
|
if result, path := hasNilEntryInList(s); result {
|
||||||
|
return result, "[" + strconv.Itoa(index) + "]/" + path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, ""
|
||||||
|
}
|
||||||
|
|
||||||
func FromMap(m map[string]interface{}) (*RNode, error) {
|
func FromMap(m map[string]interface{}) (*RNode, error) {
|
||||||
c, err := Marshal(m)
|
c, err := Marshal(m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -11,6 +11,212 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestRNodeHasNilEntryInList(t *testing.T) {
|
||||||
|
testConfigMap := map[string]interface{}{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "ConfigMap",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "winnie",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
type resultExpected struct {
|
||||||
|
hasNil bool
|
||||||
|
path string
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := map[string]struct {
|
||||||
|
theMap map[string]interface{}
|
||||||
|
rsExp resultExpected
|
||||||
|
}{
|
||||||
|
"actuallyNil": {
|
||||||
|
theMap: nil,
|
||||||
|
rsExp: resultExpected{},
|
||||||
|
},
|
||||||
|
"empty": {
|
||||||
|
theMap: map[string]interface{}{},
|
||||||
|
rsExp: resultExpected{},
|
||||||
|
},
|
||||||
|
"list": {
|
||||||
|
theMap: map[string]interface{}{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "List",
|
||||||
|
"items": []interface{}{
|
||||||
|
testConfigMap,
|
||||||
|
testConfigMap,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rsExp: resultExpected{},
|
||||||
|
},
|
||||||
|
"listWithNil": {
|
||||||
|
theMap: map[string]interface{}{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "List",
|
||||||
|
"items": []interface{}{
|
||||||
|
testConfigMap,
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rsExp: resultExpected{
|
||||||
|
hasNil: false, // TODO: This should be true.
|
||||||
|
path: "this/should/be/non-empty",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for n := range testCases {
|
||||||
|
tc := testCases[n]
|
||||||
|
t.Run(n, func(t *testing.T) {
|
||||||
|
rn, err := FromMap(tc.theMap)
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
hasNil, path := rn.HasNilEntryInList()
|
||||||
|
if tc.rsExp.hasNil {
|
||||||
|
if !assert.True(t, hasNil) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
if !assert.Equal(t, tc.rsExp.path, path) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if !assert.False(t, hasNil) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
if !assert.Empty(t, path) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRNodeGetValidatedMetadata(t *testing.T) {
|
||||||
|
testConfigMap := map[string]interface{}{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "ConfigMap",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "winnie",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
type resultExpected struct {
|
||||||
|
out ResourceMeta
|
||||||
|
errMsg string
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := map[string]struct {
|
||||||
|
theMap map[string]interface{}
|
||||||
|
rsExp resultExpected
|
||||||
|
}{
|
||||||
|
"actuallyNil": {
|
||||||
|
theMap: nil,
|
||||||
|
rsExp: resultExpected{
|
||||||
|
errMsg: "missing Resource metadata",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"empty": {
|
||||||
|
theMap: map[string]interface{}{},
|
||||||
|
rsExp: resultExpected{
|
||||||
|
errMsg: "missing Resource metadata",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"mostlyEmpty": {
|
||||||
|
theMap: map[string]interface{}{
|
||||||
|
"hey": "there",
|
||||||
|
},
|
||||||
|
rsExp: resultExpected{
|
||||||
|
errMsg: "missing Resource metadata",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"noNameConfigMap": {
|
||||||
|
theMap: map[string]interface{}{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "ConfigMap",
|
||||||
|
},
|
||||||
|
rsExp: resultExpected{
|
||||||
|
errMsg: "missing metadata.name",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"configmap": {
|
||||||
|
theMap: testConfigMap,
|
||||||
|
rsExp: resultExpected{
|
||||||
|
out: ResourceMeta{
|
||||||
|
TypeMeta: TypeMeta{
|
||||||
|
APIVersion: "v1",
|
||||||
|
Kind: "ConfigMap",
|
||||||
|
},
|
||||||
|
ObjectMeta: ObjectMeta{
|
||||||
|
NameMeta: NameMeta{
|
||||||
|
Name: "winnie",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"list": {
|
||||||
|
theMap: map[string]interface{}{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "List",
|
||||||
|
"items": []interface{}{
|
||||||
|
testConfigMap,
|
||||||
|
testConfigMap,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rsExp: resultExpected{
|
||||||
|
out: ResourceMeta{
|
||||||
|
TypeMeta: TypeMeta{
|
||||||
|
APIVersion: "v1",
|
||||||
|
Kind: "List",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"configmaplist": {
|
||||||
|
theMap: map[string]interface{}{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "ConfigMapList",
|
||||||
|
"items": []interface{}{
|
||||||
|
testConfigMap,
|
||||||
|
testConfigMap,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rsExp: resultExpected{
|
||||||
|
out: ResourceMeta{
|
||||||
|
TypeMeta: TypeMeta{
|
||||||
|
APIVersion: "v1",
|
||||||
|
Kind: "ConfigMapList",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for n := range testCases {
|
||||||
|
tc := testCases[n]
|
||||||
|
t.Run(n, func(t *testing.T) {
|
||||||
|
rn, err := FromMap(tc.theMap)
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
m, err := rn.GetValidatedMetadata()
|
||||||
|
if tc.rsExp.errMsg == "" {
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
if !assert.Equal(t, tc.rsExp.out, m) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if !assert.Error(t, err) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
if !assert.Contains(t, err.Error(), tc.rsExp.errMsg) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestRNodeFromMap(t *testing.T) {
|
func TestRNodeFromMap(t *testing.T) {
|
||||||
testConfigMap := map[string]interface{}{
|
testConfigMap := map[string]interface{}{
|
||||||
"apiVersion": "v1",
|
"apiVersion": "v1",
|
||||||
@@ -19,25 +225,25 @@ func TestRNodeFromMap(t *testing.T) {
|
|||||||
"name": "winnie",
|
"name": "winnie",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
type thingExpected struct {
|
type resultExpected struct {
|
||||||
out string
|
out string
|
||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
testCases := map[string]struct {
|
testCases := map[string]struct {
|
||||||
theMap map[string]interface{}
|
theMap map[string]interface{}
|
||||||
expected thingExpected
|
rsExp resultExpected
|
||||||
}{
|
}{
|
||||||
"actuallyNil": {
|
"actuallyNil": {
|
||||||
theMap: nil,
|
theMap: nil,
|
||||||
expected: thingExpected{
|
rsExp: resultExpected{
|
||||||
out: `{}`,
|
out: `{}`,
|
||||||
err: nil,
|
err: nil,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"empty": {
|
"empty": {
|
||||||
theMap: map[string]interface{}{},
|
theMap: map[string]interface{}{},
|
||||||
expected: thingExpected{
|
rsExp: resultExpected{
|
||||||
out: `{}`,
|
out: `{}`,
|
||||||
err: nil,
|
err: nil,
|
||||||
},
|
},
|
||||||
@@ -46,14 +252,14 @@ func TestRNodeFromMap(t *testing.T) {
|
|||||||
theMap: map[string]interface{}{
|
theMap: map[string]interface{}{
|
||||||
"hey": "there",
|
"hey": "there",
|
||||||
},
|
},
|
||||||
expected: thingExpected{
|
rsExp: resultExpected{
|
||||||
out: `hey: there`,
|
out: `hey: there`,
|
||||||
err: nil,
|
err: nil,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"configmap": {
|
"configmap": {
|
||||||
theMap: testConfigMap,
|
theMap: testConfigMap,
|
||||||
expected: thingExpected{
|
rsExp: resultExpected{
|
||||||
out: `
|
out: `
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: ConfigMap
|
kind: ConfigMap
|
||||||
@@ -72,7 +278,7 @@ metadata:
|
|||||||
testConfigMap,
|
testConfigMap,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: thingExpected{
|
rsExp: resultExpected{
|
||||||
out: `
|
out: `
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
items:
|
items:
|
||||||
@@ -98,7 +304,7 @@ kind: List
|
|||||||
testConfigMap,
|
testConfigMap,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: thingExpected{
|
rsExp: resultExpected{
|
||||||
out: `
|
out: `
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
items:
|
items:
|
||||||
@@ -121,12 +327,12 @@ kind: ConfigMapList
|
|||||||
tc := testCases[n]
|
tc := testCases[n]
|
||||||
t.Run(n, func(t *testing.T) {
|
t.Run(n, func(t *testing.T) {
|
||||||
rn, err := FromMap(tc.theMap)
|
rn, err := FromMap(tc.theMap)
|
||||||
if tc.expected.err == nil {
|
if tc.rsExp.err == nil {
|
||||||
if !assert.NoError(t, err) {
|
if !assert.NoError(t, err) {
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
if !assert.Equal(t,
|
if !assert.Equal(t,
|
||||||
strings.TrimSpace(tc.expected.out),
|
strings.TrimSpace(tc.rsExp.out),
|
||||||
strings.TrimSpace(rn.MustString())) {
|
strings.TrimSpace(rn.MustString())) {
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
@@ -134,7 +340,7 @@ kind: ConfigMapList
|
|||||||
if !assert.Error(t, err) {
|
if !assert.Error(t, err) {
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
if !assert.Equal(t, tc.expected.err, err) {
|
if !assert.Equal(t, tc.rsExp.err, err) {
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user