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  }