github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/cli/util/helm/diff_test.go (about) 1 /* 2 Copyright (C) 2022-2023 ApeCloud Co., Ltd 3 4 This file is part of KubeBlocks project 5 6 This program is free software: you can redistribute it and/or modify 7 it under the terms of the GNU Affero General Public License as published by 8 the Free Software Foundation, either version 3 of the License, or 9 (at your option) any later version. 10 11 This program is distributed in the hope that it will be useful 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 GNU Affero General Public License for more details. 15 16 You should have received a copy of the GNU Affero General Public License 17 along with this program. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20 package helm 21 22 import ( 23 . "github.com/onsi/ginkgo/v2" 24 . "github.com/onsi/gomega" 25 "golang.org/x/exp/maps" 26 27 "go.uber.org/zap/buffer" 28 helm "helm.sh/helm/v3/pkg/release" 29 "sigs.k8s.io/kustomize/kyaml/yaml" 30 ) 31 32 const ( 33 resourceName = "kubeblocks-opsrequest-editor-role, ClusterRole (rbac.authorization.k8s.io)" 34 exceptRemove = `--- kubeblocks-opsrequest-editor-role, ClusterRole (rbac.authorization.k8s.io) 0.5.1-fake 35 +++ 36 @@ -1,13 +1 @@ 37 -apiVersion: rbac.authorization.k8s.io/v1 38 -kind: ClusterRole 39 -metadata: 40 - labels: 41 - app.kubernetes.io/instance: kubeblocks 42 - app.kubernetes.io/managed-by: Helm 43 - app.kubernetes.io/name: kubeblocks 44 - name: kubeblocks-opsrequest-editor-role 45 -rules: 46 - change: 1 47 - slice: 48 - - {} 49 50 51 ` 52 exceptAdd = `--- kubeblocks-opsrequest-editor-role, ClusterRole (rbac.authorization.k8s.io) 0.5.1-fake 53 +++ kubeblocks-opsrequest-editor-role, ClusterRole (rbac.authorization.k8s.io) 0.5.2-fake 54 @@ -9,2 +9,3 @@ 55 rules: 56 + apiGroups: apps 57 change: 1 58 59 ` 60 exceptModify = `--- kubeblocks-opsrequest-editor-role, ClusterRole (rbac.authorization.k8s.io) 0.5.1-fake 61 +++ kubeblocks-opsrequest-editor-role, ClusterRole (rbac.authorization.k8s.io) 0.5.2-fake 62 @@ -9,3 +9,3 @@ 63 rules: 64 - apiGroups: appsv2 65 + apiGroups: apps 66 change: 1 67 68 ` 69 70 crdContent = `apiVersion: apiextensions.k8s.io/v1 71 kind: CustomResourceDefinition 72 metadata: 73 name: componentclassdefinitions.apps.kubeblocks.io 74 spec: 75 group: apps.kubeblocks.io 76 names: 77 categories: 78 - kubeblocks 79 kind: ComponentClassDefinition 80 scope: Cluster 81 versions: 82 - name: v1alpha1 83 schema: 84 openAPIV3Schema: 85 description: ComponentClassDefinition is the Schema for the componentclassdefinitions 86 API 87 properties: 88 apiVersion: 89 type: string 90 served: true 91 storage: true 92 subresources: 93 status: {}` 94 ) 95 96 var _ = Describe("helm diff", func() { 97 var obj map[any]any 98 var crdObj map[any]any 99 var content string 100 var release *helm.Release 101 var out buffer.Buffer 102 103 buildRelease := func(obj map[any]any) *helm.Release { 104 var res helm.Release 105 marshal, _ := yaml.Marshal(obj) 106 manifest := `--- 107 # Source: kubeblocks/templates/rbac/apps_backuppolicytemplate_editor_role.yaml 108 # permissions for end users to edit backuppolicytemplates.` + "\n" + string(marshal) 109 res.Manifest = manifest 110 return &res 111 } 112 113 exceptToMap := func(obj map[any]any, key string) map[any]any { 114 Expect(obj).ShouldNot(BeNil()) 115 m, ok := obj[key].(map[any]any) 116 Expect(ok).Should(BeTrue()) 117 return m 118 } 119 exceptToSlice := func(obj map[any]any, key string) []any { 120 Expect(obj).ShouldNot(BeNil()) 121 m, ok := obj[key].([]any) 122 Expect(ok).Should(BeTrue()) 123 return m 124 } 125 126 BeforeEach(func() { 127 obj = map[any]any{ 128 "apiVersion": "rbac.authorization.k8s.io/v1", 129 "kind": "ClusterRole", 130 "metadata": map[any]any{ 131 "name": "kubeblocks-opsrequest-editor-role", 132 "labels": map[any]any{ 133 "helm.sh/chart": "kubeblocks-0.5.1", 134 "app.kubernetes.io/name": "kubeblocks", 135 "app.kubernetes.io/instance": "kubeblocks", 136 "app.kubernetes.io/version": "0.5.1", 137 "app.kubernetes.io/managed-by": "Helm", 138 }, 139 }, 140 "rules": map[any]any{ 141 "description": "should be delete", 142 "change": 1, 143 "slice": []any{ 144 map[any]any{ 145 "description": "should be delete too", 146 }, 147 }, 148 }, 149 } 150 release = buildRelease(obj) 151 content = release.Manifest 152 }) 153 154 It("test metadata String", func() { 155 m := []metadata{ 156 {APIVersion: "rbac.authorization.k8s.io/v1", 157 Kind: "ClusterRole", 158 Metadata: struct { 159 Name string `yaml:"name"` 160 Labels map[string]string `yaml:"labels"` 161 }{ 162 "kubeblocks-opsrequest-editor-role", 163 map[string]string{}, 164 }, 165 }, {APIVersion: "v1", 166 Kind: "Service", 167 Metadata: struct { 168 Name string `yaml:"name"` 169 Labels map[string]string `yaml:"labels"` 170 }{ 171 "kubeblocks", 172 map[string]string{}, 173 }, 174 }, 175 } 176 e := []string{ 177 resourceName, 178 "kubeblocks, Service (v1)", 179 } 180 for i := range m { 181 Expect(m[i].String()).Should(Equal(e[i])) 182 } 183 }) 184 185 It("test sortedKey", func() { 186 manifest := map[string]*MappingResult{ 187 "b": nil, 188 "c": nil, 189 "a": nil, 190 } 191 Expect(sortedKeys(manifest)).Should(Equal([]string{"a", "b", "c"})) 192 }) 193 194 It("test delete obj label", func() { 195 testObj := obj 196 metadata := exceptToMap(testObj, "metadata") 197 labels := exceptToMap(metadata, "labels") 198 Expect(labels["helm.sh/chart"]).ShouldNot(BeNil()) 199 deleteLabel(&testObj, "helm.sh/chart") 200 Expect(labels["helm.sh/chart"]).Should(BeNil()) 201 }) 202 203 It("test delete obj field", func() { 204 testObj := obj 205 rules := exceptToMap(testObj, "rules") 206 slice := exceptToSlice(rules, "slice") 207 Expect(rules["description"]).ShouldNot(BeNil()) 208 Expect(slice[0]).ShouldNot(BeEmpty()) 209 deleteObjField(&testObj, "description") 210 Expect(rules["description"]).Should(BeNil()) 211 Expect(slice[0]).Should(BeEmpty()) 212 }) 213 214 Context("test ParseContent", func() { 215 216 It("test ParseContent", func() { 217 parseContent, err := ParseContent(content) 218 Expect(err).ShouldNot(HaveOccurred()) 219 Expect(parseContent.Name).Should(Equal(resourceName)) 220 Expect(parseContent.Kind).Should(Equal("ClusterRole")) 221 unusefulContent := `--- 222 # Source: kubeblocks/templates/rbac/apps_backuppolicytemplate_editor_role.yaml 223 # permissions for end users to edit backuppolicytemplates. 224 ` 225 parseContent, err = ParseContent(unusefulContent) 226 Expect(err).ShouldNot(HaveOccurred()) 227 Expect(parseContent).Should(BeNil()) 228 errorContent := `--- 229 # Source: kubeblocks/tJ!@JDASD!bASD!@D!@ 230 # permissions for end users to edit backuppolicytemplates. 231 ASDasdodh1*(!@#D!` 232 parseContent, err = ParseContent(errorContent) 233 Expect(err).Should(HaveOccurred()) 234 Expect(parseContent).Should(BeNil()) 235 }) 236 237 It("test black list", func() { 238 blackListContent := `apiVersion: v1 239 kind: ConfigMap 240 metadata: 241 name: grafana-chart-kubeblocks-values 242 data: 243 values-kubeblocks-override.yaml: |- 244 adminPassword: kubeblocks 245 adminUser: admin` 246 parseContent, err := ParseContent(blackListContent) 247 Expect(err).Should(Succeed()) 248 Expect(parseContent).Should(BeNil()) 249 }) 250 251 It("test Parse CRD", func() { 252 parseContent, err := ParseContent(crdContent) 253 Expect(err).Should(Succeed()) 254 Expect(parseContent).ShouldNot(BeNil()) 255 }) 256 }) 257 258 It("test buildManifestMapByRelease", func() { 259 releaseMap, err := buildManifestMapByRelease(release) 260 Expect(err).ShouldNot(HaveOccurred()) 261 Expect(releaseMap).Should(HaveKey(resourceName)) 262 }) 263 264 Context("test OutputDiff", func() { 265 BeforeEach(func() { 266 crdObj = map[any]any{ 267 "apiVersion": "apiextensions.k8s.io/v1", 268 "kind": "CustomResourceDefinition", 269 "metadata": map[any]any{ 270 "name": "kubeblocks-opsrequest-editor-role", 271 "labels": map[any]any{ 272 "helm.sh/chart": "kubeblocks-0.5.1", 273 }, 274 }, 275 "spec": map[any]any{ 276 "versions": []any{ 277 map[any]any{ 278 "schema": map[any]any{ 279 "openAPIV3Schema": map[any]any{ 280 "type": object, 281 "properties": map[any]any{ 282 "apiVersion": map[any]any{"type": "string"}, 283 "kind": map[any]any{"type": "string"}, 284 "metadata": map[any]any{"type": object}, 285 "spec": map[any]any{ 286 "properties": map[any]any{ 287 "fake": map[any]any{"type": "string"}, 288 }, 289 "type": object, 290 }, 291 }, 292 }, 293 }, 294 }, 295 }, 296 }, 297 } 298 }) 299 It("test OutputDiff CRD", func() { 300 crdRelease := buildRelease(crdObj) 301 Expect(OutputDiff(crdRelease, nil, "0.5.1-fake", "", &out, false)).ShouldNot(HaveOccurred()) 302 Expect(out.String()).Should(Equal(`CUSTOMRESOURCEDEFINITION MODE 303 kubeblocks-opsrequest-editor-role Removed 304 305 `)) 306 out.Reset() 307 Expect(OutputDiff(nil, crdRelease, "", "0.5.1-fake", &out, false)).Should(Succeed()) 308 Expect(out.String()).Should(Equal(`CUSTOMRESOURCEDEFINITION MODE 309 kubeblocks-opsrequest-editor-role Added 310 311 `)) 312 }) 313 314 It("test OutputDiff detail", func() { 315 out.Reset() 316 releaseA := release 317 Expect(OutputDiff(releaseA, nil, "0.5.1-fake", "", &out, true)).Should(Succeed()) 318 Expect(out.String()).Should(Equal(exceptRemove)) 319 out.Reset() 320 // add 321 otherObj := obj 322 rules := exceptToMap(otherObj, "rules") 323 rules["apiGroups"] = "apps" 324 releaseB := buildRelease(otherObj) 325 Expect(OutputDiff(releaseA, releaseB, "0.5.1-fake", "0.5.2-fake", &out, true)).Should(Succeed()) 326 Expect(out.String()).Should(Equal(exceptAdd)) 327 // modify 328 out.Reset() 329 rules["apiGroups"] = "appsv2" 330 releaseA = buildRelease(otherObj) 331 Expect(OutputDiff(releaseA, releaseB, "0.5.1-fake", "0.5.2-fake", &out, true)).Should(Succeed()) 332 Expect(out.String()).Should(Equal(exceptModify)) 333 }) 334 }) 335 336 Context("test outputCRDDiff", func() { 337 var apiA map[string]any 338 var properties = map[any]any{} 339 340 removeAPIAndAddAPI := func() map[string]any { 341 var newProperties = map[any]any{} 342 maps.Copy(newProperties, properties) 343 delete(newProperties, "namespace") 344 newProperties["newApi"] = map[any]any{TYPE: "boolean"} 345 temp := map[string]any{ 346 "spec": map[any]any{ 347 REQUIRED: []any{"name"}, 348 TYPE: object, 349 PROPERTIES: newProperties, 350 }, 351 } 352 return temp 353 } 354 355 modifyTheRequired := func() map[string]any { 356 temp := map[string]any{ 357 "spec": map[any]any{ 358 TYPE: object, 359 PROPERTIES: properties, 360 }, 361 } 362 return temp 363 } 364 365 modifyTheField := func() map[string]any { 366 var newProperties = map[any]any{} 367 maps.Copy(newProperties, properties) 368 newProperties["name"] = map[any]any{TYPE: "string", "maxLength": 63} 369 temp := map[string]any{ 370 "spec": map[any]any{ 371 REQUIRED: []any{"name"}, 372 TYPE: object, 373 PROPERTIES: newProperties, 374 }, 375 } 376 return temp 377 } 378 BeforeEach(func() { 379 properties = map[any]any{ 380 "name": map[any]any{TYPE: "string"}, 381 "namespace": map[any]any{TYPE: "string"}, 382 "defaultInstallValues": map[any]any{ 383 TYPE: array, 384 "items": map[any]any{ 385 TYPE: object, 386 PROPERTIES: map[any]any{ 387 "container1": map[any]any{TYPE: "string"}, 388 "container2": map[any]any{TYPE: "string"}, 389 }, 390 }, 391 }, 392 } 393 apiA = map[string]any{ 394 "spec": map[any]any{ 395 REQUIRED: []any{"name"}, 396 TYPE: object, 397 PROPERTIES: properties, 398 }, 399 } 400 }) 401 It("test Added and Removed", func() { 402 out.Reset() 403 apiB := removeAPIAndAddAPI() 404 outputCRDDiff(apiA, apiB, "Fake CRD", &out) 405 Expect(out.String()).Should(Equal(`Fake CRD 406 API IS-REQUIRED MODE DETAILS 407 spec.newApi false Added 408 spec.namespace false Removed {"type":"string"} 409 410 `)) 411 }) 412 It("test Modified", func() { 413 out.Reset() 414 apiB := modifyTheField() 415 outputCRDDiff(apiA, apiB, "Fake CRD", &out) 416 Expect(out.String()).Should(Equal(`Fake CRD 417 API IS-REQUIRED MODE DETAILS 418 spec.name true Modified {"type":"string"} -> {"maxLength":63,"type":"string"} 419 420 `)) 421 out.Reset() 422 apiB = modifyTheRequired() 423 outputCRDDiff(apiA, apiB, "Fake CRD", &out) 424 Expect(out.String()).Should(Equal(`Fake CRD 425 API IS-REQUIRED MODE DETAILS 426 spec.name true -> false Modified 427 428 `)) 429 }) 430 431 }) 432 })