diff --git a/api/krusty/keepemptyarray_test.go b/api/krusty/keepemptyarray_test.go new file mode 100644 index 000000000..924e32f1c --- /dev/null +++ b/api/krusty/keepemptyarray_test.go @@ -0,0 +1,48 @@ +package krusty_test + +import ( + "testing" + + kusttest_test "sigs.k8s.io/kustomize/api/testutils/kusttest" +) + +func TestKeepEmptyArray(t *testing.T) { + th := kusttest_test.MakeHarness(t) + th.WriteF("/app/resources.yaml", ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: testing123 +spec: + replicas: 1 + selector: null + template: + spec: + containers: + - name: event + image: testing123 + imagePullPolicy: IfNotPresent + imagePullSecrets: []`) + + th.WriteK("/app", ` +resources: +- resources.yaml`) + + m := th.Run("/app", th.MakeDefaultOptions()) + th.AssertActualEqualsExpected(m, ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: testing123 +spec: + replicas: 1 + selector: null + template: + spec: + containers: + - image: testing123 + imagePullPolicy: IfNotPresent + name: event + imagePullSecrets: [] +`) +} diff --git a/kustomize/go.mod b/kustomize/go.mod index 39328093c..32113e082 100644 --- a/kustomize/go.mod +++ b/kustomize/go.mod @@ -16,3 +16,8 @@ exclude ( github.com/russross/blackfriday v2.0.0+incompatible sigs.k8s.io/kustomize/api v0.2.0 ) + +replace ( + sigs.k8s.io/kustomize/api => ../api + sigs.k8s.io/kustomize/kyaml => ../kyaml +) diff --git a/kyaml/yaml/types.go b/kyaml/yaml/types.go index 862a0ecc8..98b4e2cce 100644 --- a/kyaml/yaml/types.go +++ b/kyaml/yaml/types.go @@ -28,27 +28,20 @@ func NullNode() *RNode { // IsMissingOrNull returns true if the RNode is nil or contains and explicitly null value. func IsMissingOrNull(node *RNode) bool { - if node == nil || node.YNode() == nil || node.YNode().Tag == NullNodeTag { - return true - } - return false + return node == nil || node.YNode() == nil || node.YNode().Tag == NullNodeTag } // IsEmpty returns true if the RNode is MissingOrNull, or is either a MappingNode with -// no fields, or a SequenceNode with no elements. +// no fields. func IsEmpty(node *RNode) bool { - if node == nil || node.YNode() == nil || node.YNode().Tag == NullNodeTag { + if IsMissingOrNull(node) { return true } - if node.YNode().Kind == yaml.MappingNode && len(node.YNode().Content) == 0 { - return true - } - if node.YNode().Kind == yaml.SequenceNode && len(node.YNode().Content) == 0 { - return true - } - - return false + // Empty sequence is a special case and temporarily not considered as empty here. + // Some users may want to keep empty sequence for compatibility reason. + // For example, use JSON 6902 patch. + return node.YNode().Kind == yaml.MappingNode && len(node.YNode().Content) == 0 } func IsNull(node *RNode) bool { @@ -189,6 +182,28 @@ func NewListRNode(values ...string) *RNode { return seq } +// NewMapRNode returns a new Map *RNode containing the provided values +func NewMapRNode(values *map[string]string) *RNode { + m := &RNode{value: &yaml.Node{ + Kind: yaml.MappingNode, + }} + if values == nil { + return m + } + + for k, v := range *values { + m.value.Content = append(m.value.Content, &yaml.Node{ + Kind: yaml.ScalarNode, + Value: k, + }, &yaml.Node{ + Kind: yaml.ScalarNode, + Value: v, + }) + } + + return m +} + // NewRNode returns a new RNode pointer containing the provided Node. func NewRNode(value *yaml.Node) *RNode { return &RNode{value: value} diff --git a/kyaml/yaml/types_test.go b/kyaml/yaml/types_test.go index b963d5b28..56fd9a0a2 100644 --- a/kyaml/yaml/types_test.go +++ b/kyaml/yaml/types_test.go @@ -166,3 +166,74 @@ type: string } assert.Equal(t, expected, actual) } + +func TestIsMissingOrNull(t *testing.T) { + if !IsMissingOrNull(nil) { + t.Fatalf("input: nil") + } + // missing value or null value + if !IsMissingOrNull(NewRNode(nil)) { + t.Fatalf("input: nil value") + } + + if IsMissingOrNull(NewScalarRNode("foo")) { + t.Fatalf("input: valid node") + } + // node with NullNodeTag + if !IsMissingOrNull(NullNode()) { + t.Fatalf("input: with NullNodeTag") + } +} + +func TestIsEmpty(t *testing.T) { + if !IsEmpty(nil) { + t.Fatalf("input: nil") + } + + // missing value or null value + if !IsEmpty(NewRNode(nil)) { + t.Fatalf("input: nil value") + } + // not array or map + if IsEmpty(NewScalarRNode("foo")) { + t.Fatalf("input: not array or map") + } +} + +func TestIsEmpty_Arrays(t *testing.T) { + node := NewListRNode() + // empty array. empty array is not expected as empty + if IsEmpty(node) { + t.Fatalf("input: empty array") + } + // array with 1 item + node = NewListRNode("foo") + if IsEmpty(node) { + t.Fatalf("input: array with 1 item") + } + // delete the item in array + node.value.Content = nil + if IsEmpty(node) { + t.Fatalf("input: empty array") + } +} + +func TestIsEmpty_Maps(t *testing.T) { + node := NewMapRNode(nil) + // empty map + if !IsEmpty(node) { + t.Fatalf("input: empty map") + } + // map with 1 item + node = NewMapRNode(&map[string]string{ + "foo": "bar", + }) + if IsEmpty(node) { + t.Fatalf("input: map with 1 item") + } + // delete the item in map + node.value.Content = nil + if !IsEmpty(node) { + t.Fatalf("input: empty map") + } +}