sigs.k8s.io/cluster-api@v1.7.1/util/patch/utils_test.go (about) 1 /* 2 Copyright 2020 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package patch 18 19 import ( 20 "testing" 21 22 . "github.com/onsi/gomega" 23 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 24 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 25 "k8s.io/apimachinery/pkg/runtime/schema" 26 27 clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 28 ) 29 30 func TestToUnstructured(t *testing.T) { 31 t.Run("with a typed object", func(t *testing.T) { 32 g := NewWithT(t) 33 // Test with a typed object. 34 obj := &clusterv1.Cluster{ 35 ObjectMeta: metav1.ObjectMeta{ 36 Name: "cluster-1", 37 Namespace: "namespace-1", 38 }, 39 Spec: clusterv1.ClusterSpec{ 40 Paused: true, 41 }, 42 } 43 gvk := schema.GroupVersionKind{ 44 Group: clusterv1.GroupVersion.Group, 45 Kind: "Cluster", 46 Version: clusterv1.GroupVersion.Version, 47 } 48 newObj, err := toUnstructured(obj, gvk) 49 g.Expect(err).ToNot(HaveOccurred()) 50 g.Expect(newObj.GetName()).To(Equal(obj.Name)) 51 g.Expect(newObj.GetNamespace()).To(Equal(obj.Namespace)) 52 g.Expect(newObj.GetAPIVersion()).To(Equal(clusterv1.GroupVersion.String())) 53 g.Expect(newObj.GetKind()).To(Equal("Cluster")) 54 55 // Change a spec field and validate that it stays the same in the incoming object. 56 g.Expect(unstructured.SetNestedField(newObj.Object, false, "spec", "paused")).To(Succeed()) 57 g.Expect(obj.Spec.Paused).To(BeTrue()) 58 }) 59 60 t.Run("with an unstructured object", func(t *testing.T) { 61 g := NewWithT(t) 62 63 obj := &unstructured.Unstructured{ 64 Object: map[string]interface{}{ 65 "apiVersion": "test.x.y.z/v1", 66 "kind": "TestKind", 67 "metadata": map[string]interface{}{ 68 "name": "test-1", 69 "namespace": "namespace-1", 70 }, 71 "spec": map[string]interface{}{ 72 "paused": true, 73 }, 74 }, 75 } 76 gvk := schema.GroupVersionKind{ 77 Group: "test.x.y.z", 78 Kind: "TestKind", 79 Version: "v1", 80 } 81 82 newObj, err := toUnstructured(obj, gvk) 83 g.Expect(err).ToNot(HaveOccurred()) 84 g.Expect(newObj.GetName()).To(Equal(obj.GetName())) 85 g.Expect(newObj.GetNamespace()).To(Equal(obj.GetNamespace())) 86 g.Expect(newObj.GetAPIVersion()).To(Equal("test.x.y.z/v1")) 87 g.Expect(newObj.GetKind()).To(Equal("TestKind")) 88 89 // Validate that the maps point to different addresses. 90 g.Expect(obj.Object).ToNot(BeIdenticalTo(newObj.Object)) 91 92 // Change a spec field and validate that it stays the same in the incoming object. 93 g.Expect(unstructured.SetNestedField(newObj.Object, false, "spec", "paused")).To(Succeed()) 94 pausedValue, _, err := unstructured.NestedBool(obj.Object, "spec", "paused") 95 g.Expect(err).ToNot(HaveOccurred()) 96 g.Expect(pausedValue).To(BeTrue()) 97 98 // Change the name of the new object and make sure it doesn't change it the old one. 99 newObj.SetName("test-2") 100 g.Expect(obj.GetName()).To(Equal("test-1")) 101 }) 102 } 103 104 func TestUnsafeFocusedUnstructured(t *testing.T) { 105 t.Run("focus=spec, should only return spec and common fields", func(t *testing.T) { 106 g := NewWithT(t) 107 108 obj := &unstructured.Unstructured{ 109 Object: map[string]interface{}{ 110 "apiVersion": "test.x.y.z/v1", 111 "kind": "TestCluster", 112 "metadata": map[string]interface{}{ 113 "name": "test-1", 114 "namespace": "namespace-1", 115 }, 116 "spec": map[string]interface{}{ 117 "paused": true, 118 }, 119 "status": map[string]interface{}{ 120 "infrastructureReady": true, 121 "conditions": []interface{}{ 122 map[string]interface{}{ 123 "type": "Ready", 124 "status": "True", 125 }, 126 }, 127 }, 128 }, 129 } 130 131 newObj := unsafeUnstructuredCopy(obj, specPatch, true) 132 133 // Validate that common fields are always preserved. 134 g.Expect(newObj.Object["apiVersion"]).To(Equal(obj.Object["apiVersion"])) 135 g.Expect(newObj.Object["kind"]).To(Equal(obj.Object["kind"])) 136 g.Expect(newObj.Object["metadata"]).To(Equal(obj.Object["metadata"])) 137 138 // Validate that the spec has been preserved. 139 g.Expect(newObj.Object["spec"]).To(Equal(obj.Object["spec"])) 140 141 // Validate that the status is nil, but preserved in the original object. 142 g.Expect(newObj.Object["status"]).To(BeNil()) 143 g.Expect(obj.Object["status"]).ToNot(BeNil()) 144 g.Expect(obj.Object["status"].(map[string]interface{})["conditions"]).ToNot(BeNil()) 145 }) 146 147 t.Run("focus=status w/ condition-setter object, should only return status (without conditions) and common fields", func(t *testing.T) { 148 g := NewWithT(t) 149 150 obj := &unstructured.Unstructured{ 151 Object: map[string]interface{}{ 152 "apiVersion": "test.x.y.z/v1", 153 "kind": "TestCluster", 154 "metadata": map[string]interface{}{ 155 "name": "test-1", 156 "namespace": "namespace-1", 157 }, 158 "spec": map[string]interface{}{ 159 "paused": true, 160 }, 161 "status": map[string]interface{}{ 162 "infrastructureReady": true, 163 "conditions": []interface{}{ 164 map[string]interface{}{ 165 "type": "Ready", 166 "status": "True", 167 }, 168 }, 169 }, 170 }, 171 } 172 173 newObj := unsafeUnstructuredCopy(obj, statusPatch, true) 174 175 // Validate that common fields are always preserved. 176 g.Expect(newObj.Object["apiVersion"]).To(Equal(obj.Object["apiVersion"])) 177 g.Expect(newObj.Object["kind"]).To(Equal(obj.Object["kind"])) 178 g.Expect(newObj.Object["metadata"]).To(Equal(obj.Object["metadata"])) 179 180 // Validate that spec is nil in the new object, but still exists in the old copy. 181 g.Expect(newObj.Object["spec"]).To(BeNil()) 182 g.Expect(obj.Object["spec"]).To(Equal(map[string]interface{}{ 183 "paused": true, 184 })) 185 186 // Validate that the status has been copied, without conditions. 187 g.Expect(newObj.Object["status"]).To(HaveLen(1)) 188 g.Expect(newObj.Object["status"].(map[string]interface{})["infrastructureReady"]).To(BeTrue()) 189 g.Expect(newObj.Object["status"].(map[string]interface{})["conditions"]).To(BeNil()) 190 191 // When working with conditions, the inner map is going to be removed from the original object. 192 g.Expect(obj.Object["status"].(map[string]interface{})["conditions"]).To(BeNil()) 193 }) 194 195 t.Run("focus=status w/o condition-setter object, should only return status and common fields", func(t *testing.T) { 196 g := NewWithT(t) 197 198 obj := &unstructured.Unstructured{ 199 Object: map[string]interface{}{ 200 "apiVersion": "test.x.y.z/v1", 201 "kind": "TestCluster", 202 "metadata": map[string]interface{}{ 203 "name": "test-1", 204 "namespace": "namespace-1", 205 }, 206 "spec": map[string]interface{}{ 207 "paused": true, 208 "other": "field", 209 }, 210 "status": map[string]interface{}{ 211 "infrastructureReady": true, 212 "conditions": []interface{}{ 213 map[string]interface{}{ 214 "type": "Ready", 215 "status": "True", 216 }, 217 }, 218 }, 219 }, 220 } 221 222 newObj := unsafeUnstructuredCopy(obj, statusPatch, false) 223 224 // Validate that spec is nil in the new object, but still exists in the old copy. 225 g.Expect(newObj.Object["spec"]).To(BeNil()) 226 g.Expect(obj.Object["spec"]).To(Equal(map[string]interface{}{ 227 "paused": true, 228 "other": "field", 229 })) 230 231 // Validate that common fields are always preserved. 232 g.Expect(newObj.Object["apiVersion"]).To(Equal(obj.Object["apiVersion"])) 233 g.Expect(newObj.Object["kind"]).To(Equal(obj.Object["kind"])) 234 g.Expect(newObj.Object["metadata"]).To(Equal(obj.Object["metadata"])) 235 236 // Validate that the status has been copied, without conditions. 237 g.Expect(newObj.Object["status"]).To(HaveLen(2)) 238 g.Expect(newObj.Object["status"]).To(Equal(obj.Object["status"])) 239 240 // Make sure that we didn't modify the incoming object if this object isn't a condition setter. 241 g.Expect(obj.Object["status"].(map[string]interface{})["conditions"]).ToNot(BeNil()) 242 }) 243 }