sigs.k8s.io/cluster-api@v1.7.1/internal/util/ssa/managedfields_test.go (about) 1 /* 2 Copyright 2023 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 ssa contains utils related to ssa. 18 package ssa 19 20 import ( 21 "context" 22 "encoding/json" 23 "testing" 24 25 . "github.com/onsi/gomega" 26 corev1 "k8s.io/api/core/v1" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 "sigs.k8s.io/controller-runtime/pkg/client" 29 "sigs.k8s.io/controller-runtime/pkg/client/fake" 30 31 "sigs.k8s.io/cluster-api/internal/contract" 32 ) 33 34 func TestDropManagedFields(t *testing.T) { 35 ctx := context.Background() 36 37 ssaManager := "ssa-manager" 38 39 fieldV1Map := map[string]interface{}{ 40 "f:metadata": map[string]interface{}{ 41 "f:name": map[string]interface{}{}, 42 "f:labels": map[string]interface{}{}, 43 "f:annotations": map[string]interface{}{}, 44 "f:finalizers": map[string]interface{}{}, 45 }, 46 } 47 fieldV1, err := json.Marshal(fieldV1Map) 48 if err != nil { 49 panic(err) 50 } 51 52 objWithoutSSAManager := &corev1.ConfigMap{ 53 ObjectMeta: metav1.ObjectMeta{ 54 Name: "cm-1", 55 Namespace: "default", 56 ManagedFields: []metav1.ManagedFieldsEntry{{ 57 Manager: classicManager, 58 Operation: metav1.ManagedFieldsOperationUpdate, 59 FieldsType: "FieldsV1", 60 FieldsV1: &metav1.FieldsV1{Raw: fieldV1}, 61 }}, 62 Labels: map[string]string{ 63 "label-1": "value-1", 64 }, 65 Annotations: map[string]string{ 66 "annotation-1": "value-1", 67 }, 68 Finalizers: []string{"test-finalizer"}, 69 }, 70 Data: map[string]string{ 71 "test-key": "test-value", 72 }, 73 } 74 75 objectWithSSAManager := &corev1.ConfigMap{ 76 ObjectMeta: metav1.ObjectMeta{ 77 Name: "cm-2", 78 Namespace: "default", 79 ManagedFields: []metav1.ManagedFieldsEntry{ 80 { 81 Manager: classicManager, 82 Operation: metav1.ManagedFieldsOperationUpdate, 83 FieldsType: "FieldsV1", 84 FieldsV1: &metav1.FieldsV1{Raw: fieldV1}, 85 }, 86 { 87 Manager: ssaManager, 88 Operation: metav1.ManagedFieldsOperationApply, 89 }, 90 }, 91 Labels: map[string]string{ 92 "label-1": "value-1", 93 }, 94 Annotations: map[string]string{ 95 "annotation-1": "value-1", 96 }, 97 Finalizers: []string{"test-finalizer"}, 98 }, 99 Data: map[string]string{ 100 "test-key": "test-value", 101 }, 102 } 103 104 tests := []struct { 105 name string 106 obj client.Object 107 wantOwnershipToDrop bool 108 }{ 109 { 110 name: "should drop ownership of fields if there is no entry for ssaManager", 111 obj: objWithoutSSAManager, 112 wantOwnershipToDrop: true, 113 }, 114 { 115 name: "should not drop ownership of fields if there is an entry for ssaManager", 116 obj: objectWithSSAManager, 117 wantOwnershipToDrop: false, 118 }, 119 } 120 121 for _, tt := range tests { 122 t.Run(tt.name, func(t *testing.T) { 123 g := NewWithT(t) 124 fakeClient := fake.NewClientBuilder().WithObjects(tt.obj).Build() 125 labelsAndAnnotationsManagedFieldPaths := []contract.Path{ 126 {"f:metadata", "f:annotations"}, 127 {"f:metadata", "f:labels"}, 128 } 129 g.Expect(DropManagedFields(ctx, fakeClient, tt.obj, ssaManager, labelsAndAnnotationsManagedFieldPaths)).To(Succeed()) 130 if tt.wantOwnershipToDrop { 131 g.Expect(tt.obj.GetManagedFields()).ShouldNot(MatchFieldOwnership( 132 classicManager, 133 metav1.ManagedFieldsOperationUpdate, 134 contract.Path{"f:metadata", "f:labels"}, 135 )) 136 g.Expect(tt.obj.GetManagedFields()).ShouldNot(MatchFieldOwnership( 137 classicManager, 138 metav1.ManagedFieldsOperationUpdate, 139 contract.Path{"f:metadata", "f:annotations"}, 140 )) 141 } else { 142 g.Expect(tt.obj.GetManagedFields()).Should(MatchFieldOwnership( 143 classicManager, 144 metav1.ManagedFieldsOperationUpdate, 145 contract.Path{"f:metadata", "f:labels"}, 146 )) 147 g.Expect(tt.obj.GetManagedFields()).Should(MatchFieldOwnership( 148 classicManager, 149 metav1.ManagedFieldsOperationUpdate, 150 contract.Path{"f:metadata", "f:annotations"}, 151 )) 152 } 153 // Verify ownership of other fields is not affected. 154 g.Expect(tt.obj.GetManagedFields()).Should(MatchFieldOwnership( 155 classicManager, 156 metav1.ManagedFieldsOperationUpdate, 157 contract.Path{"f:metadata", "f:finalizers"}, 158 )) 159 }) 160 } 161 } 162 163 func TestCleanUpManagedFieldsForSSAAdoption(t *testing.T) { 164 ctx := context.Background() 165 166 ssaManager := "ssa-manager" 167 168 objWithoutAnyManager := &corev1.ConfigMap{ 169 ObjectMeta: metav1.ObjectMeta{ 170 Name: "cm-1", 171 Namespace: "default", 172 }, 173 Data: map[string]string{ 174 "test-key": "test-value", 175 }, 176 } 177 objWithOnlyClassicManager := &corev1.ConfigMap{ 178 ObjectMeta: metav1.ObjectMeta{ 179 Name: "cm-1", 180 Namespace: "default", 181 ManagedFields: []metav1.ManagedFieldsEntry{{ 182 Manager: classicManager, 183 Operation: metav1.ManagedFieldsOperationUpdate, 184 }}, 185 }, 186 Data: map[string]string{ 187 "test-key": "test-value", 188 }, 189 } 190 objWithOnlySSAManager := &corev1.ConfigMap{ 191 ObjectMeta: metav1.ObjectMeta{ 192 Name: "cm-1", 193 Namespace: "default", 194 ManagedFields: []metav1.ManagedFieldsEntry{{ 195 Manager: ssaManager, 196 Operation: metav1.ManagedFieldsOperationApply, 197 }}, 198 }, 199 Data: map[string]string{ 200 "test-key": "test-value", 201 }, 202 } 203 objWithClassicManagerAndSSAManager := &corev1.ConfigMap{ 204 ObjectMeta: metav1.ObjectMeta{ 205 Name: "cm-1", 206 Namespace: "default", 207 ManagedFields: []metav1.ManagedFieldsEntry{ 208 { 209 Manager: classicManager, 210 Operation: metav1.ManagedFieldsOperationUpdate, 211 }, 212 { 213 Manager: ssaManager, 214 Operation: metav1.ManagedFieldsOperationApply, 215 }, 216 }, 217 }, 218 Data: map[string]string{ 219 "test-key": "test-value", 220 }, 221 } 222 223 tests := []struct { 224 name string 225 obj client.Object 226 wantEntryForClassicManager bool 227 }{ 228 { 229 name: "should add an entry for ssaManager if it does not have one", 230 obj: objWithoutAnyManager, 231 wantEntryForClassicManager: false, 232 }, 233 { 234 name: "should add an entry for ssaManager and drop entry for classic manager if it exists", 235 obj: objWithOnlyClassicManager, 236 wantEntryForClassicManager: false, 237 }, 238 { 239 name: "should keep the entry for ssa-manager if it already has one (no-op)", 240 obj: objWithOnlySSAManager, 241 wantEntryForClassicManager: false, 242 }, 243 { 244 name: "should keep the entry with ssa-manager if it already has one - should not drop other manager entries (no-op)", 245 obj: objWithClassicManagerAndSSAManager, 246 wantEntryForClassicManager: true, 247 }, 248 } 249 250 for _, tt := range tests { 251 t.Run(tt.name, func(t *testing.T) { 252 g := NewWithT(t) 253 fakeClient := fake.NewClientBuilder().WithObjects(tt.obj).Build() 254 g.Expect(CleanUpManagedFieldsForSSAAdoption(ctx, fakeClient, tt.obj, ssaManager)).Should(Succeed()) 255 g.Expect(tt.obj.GetManagedFields()).Should( 256 ContainElement(MatchManagedFieldsEntry(ssaManager, metav1.ManagedFieldsOperationApply))) 257 if tt.wantEntryForClassicManager { 258 g.Expect(tt.obj.GetManagedFields()).Should( 259 ContainElement(MatchManagedFieldsEntry(classicManager, metav1.ManagedFieldsOperationUpdate))) 260 } else { 261 g.Expect(tt.obj.GetManagedFields()).ShouldNot( 262 ContainElement(MatchManagedFieldsEntry(classicManager, metav1.ManagedFieldsOperationUpdate))) 263 } 264 }) 265 } 266 }