github.com/oam-dev/kubevela@v1.9.11/pkg/controller/core.oam.dev/v1beta1/application/revision_test.go (about) 1 /* 2 Copyright 2021 The KubeVela 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 application 18 19 import ( 20 "context" 21 "encoding/json" 22 "fmt" 23 "reflect" 24 "testing" 25 "time" 26 27 workflowv1alpha1 "github.com/kubevela/workflow/api/v1alpha1" 28 . "github.com/onsi/ginkgo/v2" 29 . "github.com/onsi/gomega" 30 "github.com/stretchr/testify/require" 31 32 corev1 "k8s.io/api/core/v1" 33 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 34 "k8s.io/apimachinery/pkg/runtime" 35 "k8s.io/apimachinery/pkg/types" 36 "sigs.k8s.io/yaml" 37 38 "github.com/oam-dev/kubevela/apis/core.oam.dev/common" 39 "github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1" 40 "github.com/oam-dev/kubevela/pkg/appfile" 41 "github.com/oam-dev/kubevela/pkg/oam" 42 "github.com/oam-dev/kubevela/pkg/oam/util" 43 ) 44 45 var _ = Describe("test generate revision ", func() { 46 var appRevision1, appRevision2 v1beta1.ApplicationRevision 47 var app v1beta1.Application 48 cd := v1beta1.ComponentDefinition{} 49 webCompDef := v1beta1.ComponentDefinition{} 50 var handler *AppHandler 51 var namespaceName string 52 var ns corev1.Namespace 53 ctx := context.Background() 54 55 BeforeEach(func() { 56 namespaceName = randomNamespaceName("apply-app-test") 57 ns = corev1.Namespace{ 58 ObjectMeta: metav1.ObjectMeta{ 59 Name: namespaceName, 60 }, 61 } 62 63 componentDefJson, _ := yaml.YAMLToJSON([]byte(componentDefYaml)) 64 Expect(json.Unmarshal(componentDefJson, &cd)).Should(BeNil()) 65 cd.ResourceVersion = "" 66 Expect(k8sClient.Create(ctx, &cd)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{})) 67 68 webserverCDJson, _ := yaml.YAMLToJSON([]byte(webComponentDefYaml)) 69 Expect(json.Unmarshal(webserverCDJson, &webCompDef)).Should(BeNil()) 70 webCompDef.ResourceVersion = "" 71 Expect(k8sClient.Create(ctx, &webCompDef)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{})) 72 73 By("Create the Namespace for test") 74 Expect(k8sClient.Create(ctx, &ns)).Should(Succeed()) 75 76 app = v1beta1.Application{ 77 TypeMeta: metav1.TypeMeta{ 78 Kind: "Application", 79 APIVersion: "core.oam.dev/v1beta1", 80 }, 81 ObjectMeta: metav1.ObjectMeta{ 82 Name: "revision-apply-test", 83 Namespace: namespaceName, 84 UID: "f97e2615-3822-4c62-a3bd-fb880e0bcec5", 85 }, 86 Spec: v1beta1.ApplicationSpec{ 87 Components: []common.ApplicationComponent{ 88 { 89 Type: cd.Name, 90 Name: "express-server", 91 Properties: &runtime.RawExtension{ 92 Raw: []byte(`{"image": "oamdev/testapp:v1", "cmd": ["node", "server.js"]}`), 93 }, 94 }, 95 }, 96 }, 97 } 98 // create the application 99 Expect(k8sClient.Create(ctx, &app)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{})) 100 101 appRevision1 = v1beta1.ApplicationRevision{ 102 ObjectMeta: metav1.ObjectMeta{ 103 Name: "appRevision1", 104 }, 105 Spec: v1beta1.ApplicationRevisionSpec{ 106 ApplicationRevisionCompressibleFields: v1beta1.ApplicationRevisionCompressibleFields{ 107 ComponentDefinitions: make(map[string]*v1beta1.ComponentDefinition), 108 TraitDefinitions: make(map[string]*v1beta1.TraitDefinition), 109 }, 110 }, 111 } 112 appRevision1.Spec.Application = app 113 appRevision1.Spec.ComponentDefinitions[cd.Name] = cd.DeepCopy() 114 115 appRevision2 = *appRevision1.DeepCopy() 116 appRevision2.Name = "appRevision2" 117 118 _handler, err := NewAppHandler(ctx, reconciler, &app) 119 Expect(err).Should(Succeed()) 120 handler = _handler 121 }) 122 123 AfterEach(func() { 124 By("[TEST] Clean up resources after an integration test") 125 Expect(k8sClient.Delete(context.TODO(), &ns)).Should(Succeed()) 126 }) 127 128 verifyEqual := func() { 129 appHash1, err := ComputeAppRevisionHash(&appRevision1) 130 Expect(err).Should(Succeed()) 131 appHash2, err := ComputeAppRevisionHash(&appRevision2) 132 Expect(err).Should(Succeed()) 133 Expect(appHash1).Should(Equal(appHash2)) 134 // and compare 135 Expect(DeepEqualRevision(&appRevision1, &appRevision2)).Should(BeTrue()) 136 } 137 138 verifyNotEqual := func() { 139 appHash1, err := ComputeAppRevisionHash(&appRevision1) 140 Expect(err).Should(Succeed()) 141 appHash2, err := ComputeAppRevisionHash(&appRevision2) 142 Expect(err).Should(Succeed()) 143 Expect(appHash1).ShouldNot(Equal(appHash2)) 144 Expect(DeepEqualRevision(&appRevision1, &appRevision2)).ShouldNot(BeTrue()) 145 } 146 147 It("Test same app revisions should produce same hash and equal", func() { 148 verifyEqual() 149 }) 150 151 It("Test app revisions with different application spec should produce different hash and not equal", func() { 152 // change application setting 153 appRevision2.Spec.Application.Spec.Components[0].Properties.Raw = 154 []byte(`{"image": "oamdev/testapp:v2", "cmd": ["node", "server.js"]}`) 155 156 verifyNotEqual() 157 }) 158 159 It("Test app revisions with different application spec should produce different hash and not equal", func() { 160 // add a component definition 161 appRevision1.Spec.ComponentDefinitions[webCompDef.Name] = webCompDef.DeepCopy() 162 163 verifyNotEqual() 164 }) 165 166 It("Test application revision compare", func() { 167 By("Apply the application") 168 appParser := appfile.NewApplicationParser(reconciler.Client, reconciler.pd) 169 ctx = util.SetNamespaceInCtx(ctx, app.Namespace) 170 generatedAppfile, err := appParser.GenerateAppFile(ctx, &app) 171 Expect(err).Should(Succeed()) 172 Expect(handler.PrepareCurrentAppRevision(ctx, generatedAppfile)).Should(Succeed()) 173 Expect(handler.FinalizeAndApplyAppRevision(ctx)).Should(Succeed()) 174 prevHash := generatedAppfile.AppRevisionHash 175 handler.app.Status.LatestRevision = &common.Revision{Name: generatedAppfile.AppRevisionName, Revision: 1, RevisionHash: generatedAppfile.AppRevisionHash} 176 generatedAppfile.ParsedComponents[0].FullTemplate.ComponentDefinition = nil 177 generatedAppfile.RelatedComponentDefinitions = map[string]*v1beta1.ComponentDefinition{} 178 Expect(handler.PrepareCurrentAppRevision(ctx, generatedAppfile)).Should(Succeed()) 179 nonChangeHash := generatedAppfile.AppRevisionHash 180 handler.app.Annotations = map[string]string{oam.AnnotationAutoUpdate: "true"} 181 Expect(handler.PrepareCurrentAppRevision(ctx, generatedAppfile)).Should(Succeed()) 182 changedHash := generatedAppfile.AppRevisionHash 183 Expect(nonChangeHash).Should(Equal(prevHash)) 184 Expect(changedHash).ShouldNot(Equal(prevHash)) 185 }) 186 187 It("Test apply success for none rollout case", func() { 188 By("Apply the application") 189 appParser := appfile.NewApplicationParser(reconciler.Client, reconciler.pd) 190 ctx = util.SetNamespaceInCtx(ctx, app.Namespace) 191 annoKey1 := "testKey1" 192 app.SetAnnotations(map[string]string{annoKey1: "true"}) 193 generatedAppfile, err := appParser.GenerateAppFile(ctx, &app) 194 Expect(err).Should(Succeed()) 195 Expect(handler.PrepareCurrentAppRevision(ctx, generatedAppfile)).Should(Succeed()) 196 Expect(handler.FinalizeAndApplyAppRevision(ctx)).Should(Succeed()) 197 Expect(handler.UpdateAppLatestRevisionStatus(ctx, reconciler.patchStatus)).Should(Succeed()) 198 199 curApp := &v1beta1.Application{} 200 Eventually( 201 func() error { 202 return handler.Get(ctx, 203 types.NamespacedName{Namespace: ns.Name, Name: app.Name}, 204 curApp) 205 }, 206 time.Second*10, time.Millisecond*500).Should(BeNil()) 207 Expect(curApp.Status.LatestRevision.Revision).Should(BeEquivalentTo(1)) 208 By("Verify the created appRevision is exactly what it is") 209 curAppRevision := &v1beta1.ApplicationRevision{} 210 Eventually( 211 func() error { 212 return handler.Get(ctx, 213 types.NamespacedName{Namespace: ns.Name, Name: curApp.Status.LatestRevision.Name}, 214 curAppRevision) 215 }, 216 time.Second*5, time.Millisecond*500).Should(BeNil()) 217 appHash1, err := ComputeAppRevisionHash(curAppRevision) 218 Expect(err).Should(Succeed()) 219 Expect(curAppRevision.GetLabels()[oam.LabelAppRevisionHash]).Should(Equal(appHash1)) 220 Expect(appHash1).Should(Equal(curApp.Status.LatestRevision.RevisionHash)) 221 ctrlOwner := metav1.GetControllerOf(curAppRevision) 222 Expect(ctrlOwner).ShouldNot(BeNil()) 223 Expect(ctrlOwner.Kind).Should(Equal(v1beta1.ApplicationKind)) 224 Expect(len(curAppRevision.GetOwnerReferences())).Should(BeEquivalentTo(1)) 225 226 By("Apply the application again without any spec change") 227 annoKey2 := "testKey2" 228 app.SetAnnotations(map[string]string{annoKey2: "true"}) 229 lastRevision := curApp.Status.LatestRevision.Name 230 Expect(handler.PrepareCurrentAppRevision(ctx, generatedAppfile)).Should(Succeed()) 231 Expect(handler.FinalizeAndApplyAppRevision(ctx)).Should(Succeed()) 232 Eventually( 233 func() error { 234 return handler.Get(ctx, 235 types.NamespacedName{Namespace: ns.Name, Name: app.Name}, 236 curApp) 237 }, 238 time.Second*10, time.Millisecond*500).Should(BeNil()) 239 // no new revision should be created 240 Expect(curApp.Status.LatestRevision.Name).Should(Equal(lastRevision)) 241 Expect(curApp.Status.LatestRevision.RevisionHash).Should(Equal(appHash1)) 242 By("Verify the appRevision is not changed") 243 // reset appRev 244 curAppRevision = &v1beta1.ApplicationRevision{} 245 Eventually( 246 func() error { 247 return handler.Get(ctx, 248 types.NamespacedName{Namespace: ns.Name, Name: lastRevision}, 249 curAppRevision) 250 }, 251 time.Second*5, time.Millisecond*500).Should(BeNil()) 252 Expect(err).Should(Succeed()) 253 Expect(curAppRevision.GetLabels()[oam.LabelAppRevisionHash]).Should(Equal(appHash1)) 254 255 By("Change the application and apply again") 256 // bump the image tag 257 app.ResourceVersion = curApp.ResourceVersion 258 app.Spec.Components[0].Properties = &runtime.RawExtension{ 259 Raw: []byte(`{"image": "oamdev/testapp:v2", "cmd": ["node", "server.js"]}`), 260 } 261 // persist the app 262 Expect(k8sClient.Update(ctx, &app)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{})) 263 generatedAppfile, err = appParser.GenerateAppFile(ctx, &app) 264 Expect(err).Should(Succeed()) 265 handler.app = &app 266 Expect(handler.PrepareCurrentAppRevision(ctx, generatedAppfile)).Should(Succeed()) 267 Expect(handler.FinalizeAndApplyAppRevision(ctx)).Should(Succeed()) 268 Expect(handler.UpdateAppLatestRevisionStatus(ctx, reconciler.patchStatus)).Should(Succeed()) 269 Eventually( 270 func() error { 271 return handler.Get(ctx, 272 types.NamespacedName{Namespace: ns.Name, Name: app.Name}, 273 curApp) 274 }, 275 time.Second*10, time.Millisecond*500).Should(BeNil()) 276 // new revision should be created 277 Expect(curApp.Status.LatestRevision.Name).ShouldNot(Equal(lastRevision)) 278 Expect(curApp.Status.LatestRevision.Revision).Should(BeEquivalentTo(2)) 279 Expect(curApp.Status.LatestRevision.RevisionHash).ShouldNot(Equal(appHash1)) 280 By("Verify the appRevision is changed") 281 // reset appRev 282 curAppRevision = &v1beta1.ApplicationRevision{} 283 Eventually( 284 func() error { 285 return handler.Get(ctx, 286 types.NamespacedName{Namespace: ns.Name, Name: curApp.Status.LatestRevision.Name}, 287 curAppRevision) 288 }, 289 time.Second*5, time.Millisecond*500).Should(BeNil()) 290 appHash2, err := ComputeAppRevisionHash(curAppRevision) 291 Expect(err).Should(Succeed()) 292 Expect(appHash1).ShouldNot(Equal(appHash2)) 293 Expect(curAppRevision.GetLabels()[oam.LabelAppRevisionHash]).Should(Equal(appHash2)) 294 Expect(curApp.Status.LatestRevision.RevisionHash).Should(Equal(appHash2)) 295 296 By("Change the application same as v1 and apply again") 297 // bump the image tag 298 app.ResourceVersion = curApp.ResourceVersion 299 app.Spec.Components[0].Properties = &runtime.RawExtension{ 300 Raw: []byte(`{"image": "oamdev/testapp:v1", "cmd": ["node", "server.js"]}`), 301 } 302 // persist the app 303 Expect(k8sClient.Update(ctx, &app)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{})) 304 generatedAppfile, err = appParser.GenerateAppFile(ctx, &app) 305 Expect(err).Should(Succeed()) 306 handler.app = &app 307 Expect(handler.PrepareCurrentAppRevision(ctx, generatedAppfile)).Should(Succeed()) 308 Expect(handler.FinalizeAndApplyAppRevision(ctx)).Should(Succeed()) 309 Expect(handler.UpdateAppLatestRevisionStatus(ctx, reconciler.patchStatus)).Should(Succeed()) 310 Eventually( 311 func() error { 312 return handler.Get(ctx, 313 types.NamespacedName{Namespace: ns.Name, Name: app.Name}, 314 curApp) 315 }, 316 time.Second*10, time.Millisecond*500).Should(BeNil()) 317 // new revision should be different with lastRevision 318 Expect(curApp.Status.LatestRevision.Name).Should(Equal(lastRevision)) 319 Expect(curApp.Status.LatestRevision.Revision).Should(BeEquivalentTo(1)) 320 Expect(curApp.Status.LatestRevision.RevisionHash).ShouldNot(Equal(appHash2)) 321 By("Verify the appRevision is changed") 322 // reset appRev 323 curAppRevision = &v1beta1.ApplicationRevision{} 324 Eventually( 325 func() error { 326 return handler.Get(ctx, 327 types.NamespacedName{Namespace: ns.Name, Name: curApp.Status.LatestRevision.Name}, 328 curAppRevision) 329 }, 330 time.Second*5, time.Millisecond*500).Should(BeNil()) 331 appHash3, err := ComputeAppRevisionHash(curAppRevision) 332 Expect(err).Should(Succeed()) 333 Expect(appHash2).ShouldNot(Equal(appHash3)) 334 Expect(curAppRevision.GetLabels()[oam.LabelAppRevisionHash]).Should(Equal(appHash3)) 335 Expect(curApp.Status.LatestRevision.RevisionHash).Should(Equal(appHash3)) 336 }) 337 338 It("Test apply passes all label and annotation from app to appRevision", func() { 339 By("Apply the application") 340 appParser := appfile.NewApplicationParser(reconciler.Client, reconciler.pd) 341 ctx = util.SetNamespaceInCtx(ctx, app.Namespace) 342 labelKey1 := "labelKey1" 343 app.SetLabels(map[string]string{labelKey1: "true"}) 344 annoKey1 := "annoKey1" 345 app.SetAnnotations(map[string]string{annoKey1: "true"}) 346 generatedAppfile, err := appParser.GenerateAppFile(ctx, &app) 347 Expect(err).Should(Succeed()) 348 Expect(handler.PrepareCurrentAppRevision(ctx, generatedAppfile)).Should(Succeed()) 349 Expect(handler.FinalizeAndApplyAppRevision(ctx)).Should(Succeed()) 350 Expect(handler.UpdateAppLatestRevisionStatus(ctx, reconciler.patchStatus)).Should(Succeed()) 351 352 curApp := &v1beta1.Application{} 353 Eventually( 354 func() error { 355 return handler.Get(ctx, types.NamespacedName{Namespace: ns.Name, Name: app.Name}, curApp) 356 }, time.Second*10, time.Millisecond*500).Should(BeNil()) 357 Expect(curApp.Status.LatestRevision.Revision).Should(BeEquivalentTo(1)) 358 By("Verify the created appRevision is exactly what it is") 359 curAppRevision := &v1beta1.ApplicationRevision{} 360 Eventually( 361 func() error { 362 return handler.Get(ctx, 363 types.NamespacedName{Namespace: ns.Name, Name: curApp.Status.LatestRevision.Name}, 364 curAppRevision) 365 }, 366 time.Second*5, time.Millisecond*500).Should(BeNil()) 367 appHash1, err := ComputeAppRevisionHash(curAppRevision) 368 Expect(err).Should(Succeed()) 369 Expect(curAppRevision.GetLabels()[oam.LabelAppRevisionHash]).Should(Equal(appHash1)) 370 Expect(appHash1).Should(Equal(curApp.Status.LatestRevision.RevisionHash)) 371 Expect(curAppRevision.GetLabels()[labelKey1]).Should(Equal("true")) 372 Expect(curAppRevision.GetAnnotations()[annoKey1]).Should(Equal("true")) 373 annoKey2 := "testKey2" 374 app.SetAnnotations(map[string]string{annoKey2: "true"}) 375 labelKey2 := "labelKey2" 376 app.SetLabels(map[string]string{labelKey2: "true"}) 377 lastRevision := curApp.Status.LatestRevision.Name 378 Expect(handler.PrepareCurrentAppRevision(ctx, generatedAppfile)).Should(Succeed()) 379 Expect(handler.FinalizeAndApplyAppRevision(ctx)).Should(Succeed()) 380 Eventually( 381 func() error { 382 return handler.Get(ctx, types.NamespacedName{Namespace: ns.Name, Name: app.Name}, curApp) 383 }, time.Second*10, time.Millisecond*500).Should(BeNil()) 384 // no new revision should be created 385 Expect(curApp.Status.LatestRevision.Name).Should(Equal(lastRevision)) 386 Expect(curApp.Status.LatestRevision.RevisionHash).Should(Equal(appHash1)) 387 By("Verify the appRevision is not changed") 388 // reset appRev 389 curAppRevision = &v1beta1.ApplicationRevision{} 390 Eventually( 391 func() error { 392 return handler.Get(ctx, types.NamespacedName{Namespace: ns.Name, Name: lastRevision}, curAppRevision) 393 }, time.Second*5, time.Millisecond*500).Should(BeNil()) 394 Expect(err).Should(Succeed()) 395 Expect(curAppRevision.GetLabels()[oam.LabelAppRevisionHash]).Should(Equal(appHash1)) 396 Expect(curAppRevision.GetLabels()[labelKey1]).Should(BeEmpty()) 397 Expect(curAppRevision.GetLabels()[labelKey2]).Should(Equal("true")) 398 Expect(curAppRevision.GetAnnotations()[annoKey1]).Should(BeEmpty()) 399 Expect(curAppRevision.GetAnnotations()[annoKey2]).Should(Equal("true")) 400 }) 401 }) 402 403 var _ = Describe("Test PrepareCurrentAppRevision", func() { 404 var app v1beta1.Application 405 var apprev v1beta1.ApplicationRevision 406 ctx := context.Background() 407 var handler *AppHandler 408 409 BeforeEach(func() { 410 // prepare ComponentDefinition 411 var compd v1beta1.ComponentDefinition 412 Expect(yaml.Unmarshal([]byte(componentDefYaml), &compd)).To(Succeed()) 413 Expect(k8sClient.Create(ctx, &compd)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{})) 414 415 // prepare WorkflowStepDefinition 416 wsdYaml := ` 417 apiVersion: core.oam.dev/v1beta1 418 kind: WorkflowStepDefinition 419 metadata: 420 annotations: 421 definition.oam.dev/description: Apply application for your workflow steps 422 labels: 423 custom.definition.oam.dev/ui-hidden: "true" 424 name: apply-application 425 namespace: vela-system 426 spec: 427 schematic: 428 cue: 429 template: | 430 import ( 431 "vela/op" 432 ) 433 434 // apply application 435 output: op.#ApplyApplication & {} 436 ` 437 var wsd v1beta1.WorkflowStepDefinition 438 Expect(yaml.Unmarshal([]byte(wsdYaml), &wsd)).To(Succeed()) 439 Expect(k8sClient.Create(ctx, &wsd)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{})) 440 441 // prepare application and application revision 442 appYaml := ` 443 apiVersion: core.oam.dev/v1beta1 444 kind: Application 445 metadata: 446 name: backport-1-2-test-demo 447 namespace: default 448 spec: 449 components: 450 - name: backport-1-2-test-demo 451 properties: 452 image: nginx 453 type: worker 454 workflow: 455 steps: 456 - name: apply 457 type: apply-application 458 status: 459 latestRevision: 460 name: backport-1-2-test-demo-v1 461 revision: 1 462 revisionHash: 38ddf4e721073703 463 ` 464 Expect(yaml.Unmarshal([]byte(appYaml), &app)).To(Succeed()) 465 Expect(k8sClient.Create(ctx, &app)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{})) 466 467 // prepare application revision 468 apprevYaml := ` 469 apiVersion: core.oam.dev/v1beta1 470 kind: ApplicationRevision 471 metadata: 472 name: backport-1-2-test-demo-v1 473 namespace: default 474 ownerReferences: 475 - apiVersion: core.oam.dev/v1beta1 476 controller: true 477 kind: Application 478 name: backport-1-2-test-demo 479 uid: b69fab34-7058-412b-994d-1465a9421f06 480 spec: 481 application: 482 apiVersion: core.oam.dev/v1beta1 483 kind: Application 484 metadata: 485 name: backport-1-2-test-demo 486 namespace: default 487 spec: 488 components: 489 - name: backport-1-2-test-demo 490 properties: 491 image: nginx 492 type: worker 493 status: {} 494 componentDefinitions: 495 webservice: 496 apiVersion: core.oam.dev/v1beta1 497 kind: ComponentDefinition 498 metadata: 499 annotations: 500 definition.oam.dev/description: Describes long-running, scalable, containerized 501 services that have a stable network endpoint to receive external network 502 traffic from customers. 503 meta.helm.sh/release-name: kubevela 504 meta.helm.sh/release-namespace: vela-system 505 labels: 506 app.kubernetes.io/managed-by: Helm 507 name: webservice 508 namespace: vela-system 509 spec: 510 schematic: 511 cue: 512 template: "import (\n\t\"strconv\"\n)\n\nmountsArray: {\n\tpvc: *[\n\t\tfor 513 v in parameter.volumeMounts.pvc {\n\t\t\t{\n\t\t\t\tmountPath: v.mountPath\n\t\t\t\tname: 514 \ v.name\n\t\t\t}\n\t\t},\n\t] | []\n\n\tconfigMap: *[\n\t\t\tfor 515 v in parameter.volumeMounts.configMap {\n\t\t\t{\n\t\t\t\tmountPath: 516 v.mountPath\n\t\t\t\tname: v.name\n\t\t\t}\n\t\t},\n\t] | []\n\n\tsecret: 517 *[\n\t\tfor v in parameter.volumeMounts.secret {\n\t\t\t{\n\t\t\t\tmountPath: 518 v.mountPath\n\t\t\t\tname: v.name\n\t\t\t}\n\t\t},\n\t] | []\n\n\temptyDir: 519 *[\n\t\t\tfor v in parameter.volumeMounts.emptyDir {\n\t\t\t{\n\t\t\t\tmountPath: 520 v.mountPath\n\t\t\t\tname: v.name\n\t\t\t}\n\t\t},\n\t] | []\n\n\thostPath: 521 *[\n\t\t\tfor v in parameter.volumeMounts.hostPath {\n\t\t\t{\n\t\t\t\tmountPath: 522 v.mountPath\n\t\t\t\tname: v.name\n\t\t\t}\n\t\t},\n\t] | []\n}\nvolumesArray: 523 {\n\tpvc: *[\n\t\tfor v in parameter.volumeMounts.pvc {\n\t\t\t{\n\t\t\t\tname: 524 v.name\n\t\t\t\tpersistentVolumeClaim: claimName: v.claimName\n\t\t\t}\n\t\t},\n\t] 525 | []\n\n\tconfigMap: *[\n\t\t\tfor v in parameter.volumeMounts.configMap 526 {\n\t\t\t{\n\t\t\t\tname: v.name\n\t\t\t\tconfigMap: {\n\t\t\t\t\tdefaultMode: 527 v.defaultMode\n\t\t\t\t\tname: v.cmName\n\t\t\t\t\tif v.items 528 != _|_ {\n\t\t\t\t\t\titems: v.items\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t] 529 | []\n\n\tsecret: *[\n\t\tfor v in parameter.volumeMounts.secret {\n\t\t\t{\n\t\t\t\tname: 530 v.name\n\t\t\t\tsecret: {\n\t\t\t\t\tdefaultMode: v.defaultMode\n\t\t\t\t\tsecretName: 531 \ v.secretName\n\t\t\t\t\tif v.items != _|_ {\n\t\t\t\t\t\titems: v.items\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t] 532 | []\n\n\temptyDir: *[\n\t\t\tfor v in parameter.volumeMounts.emptyDir 533 {\n\t\t\t{\n\t\t\t\tname: v.name\n\t\t\t\temptyDir: medium: v.medium\n\t\t\t}\n\t\t},\n\t] 534 | []\n\n\thostPath: *[\n\t\t\tfor v in parameter.volumeMounts.hostPath 535 {\n\t\t\t{\n\t\t\t\tname: v.name\n\t\t\t\thostPath: path: v.path\n\t\t\t}\n\t\t},\n\t] 536 | []\n}\noutput: {\n\tapiVersion: \"apps/v1\"\n\tkind: \"Deployment\"\n\tspec: 537 {\n\t\tselector: matchLabels: \"app.oam.dev/component\": context.name\n\n\t\ttemplate: 538 {\n\t\t\tmetadata: {\n\t\t\t\tlabels: {\n\t\t\t\t\tif parameter.labels 539 != _|_ {\n\t\t\t\t\t\tparameter.labels\n\t\t\t\t\t}\n\t\t\t\t\tif parameter.addRevisionLabel 540 {\n\t\t\t\t\t\t\"app.oam.dev/revision\": context.revision\n\t\t\t\t\t}\n\t\t\t\t\t\"app.oam.dev/component\": 541 context.name\n\t\t\t\t}\n\t\t\t\tif parameter.annotations != _|_ {\n\t\t\t\t\tannotations: 542 parameter.annotations\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tspec: {\n\t\t\t\tcontainers: 543 [{\n\t\t\t\t\tname: context.name\n\t\t\t\t\timage: parameter.image\n\t\t\t\t\tif 544 parameter[\"port\"] != _|_ && parameter[\"ports\"] == _|_ {\n\t\t\t\t\t\tports: 545 [{\n\t\t\t\t\t\t\tcontainerPort: parameter.port\n\t\t\t\t\t\t}]\n\t\t\t\t\t}\n\t\t\t\t\tif 546 parameter[\"ports\"] != _|_ {\n\t\t\t\t\t\tports: [ for v in parameter.ports 547 {\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tcontainerPort: v.port\n\t\t\t\t\t\t\t\tprotocol: 548 \ v.protocol\n\t\t\t\t\t\t\t\tif v.name != _|_ {\n\t\t\t\t\t\t\t\t\tname: 549 v.name\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif v.name == _|_ {\n\t\t\t\t\t\t\t\t\tname: 550 \"port-\" + strconv.FormatInt(v.port, 10)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}}]\n\t\t\t\t\t}\n\n\t\t\t\t\tif 551 parameter[\"imagePullPolicy\"] != _|_ {\n\t\t\t\t\t\timagePullPolicy: 552 parameter.imagePullPolicy\n\t\t\t\t\t}\n\n\t\t\t\t\tif parameter[\"cmd\"] 553 != _|_ {\n\t\t\t\t\t\tcommand: parameter.cmd\n\t\t\t\t\t}\n\n\t\t\t\t\tif 554 parameter[\"env\"] != _|_ {\n\t\t\t\t\t\tenv: parameter.env\n\t\t\t\t\t}\n\n\t\t\t\t\tif 555 context[\"config\"] != _|_ {\n\t\t\t\t\t\tenv: context.config\n\t\t\t\t\t}\n\n\t\t\t\t\tif 556 parameter[\"cpu\"] != _|_ {\n\t\t\t\t\t\tresources: {\n\t\t\t\t\t\t\tlimits: 557 cpu: parameter.cpu\n\t\t\t\t\t\t\trequests: cpu: parameter.cpu\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif 558 parameter[\"memory\"] != _|_ {\n\t\t\t\t\t\tresources: {\n\t\t\t\t\t\t\tlimits: 559 memory: parameter.memory\n\t\t\t\t\t\t\trequests: memory: parameter.memory\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif 560 parameter[\"volumes\"] != _|_ && parameter[\"volumeMounts\"] == _|_ 561 {\n\t\t\t\t\t\tvolumeMounts: [ for v in parameter.volumes {\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tmountPath: 562 v.mountPath\n\t\t\t\t\t\t\t\tname: v.name\n\t\t\t\t\t\t\t}}]\n\t\t\t\t\t}\n\n\t\t\t\t\tif 563 parameter[\"volumeMounts\"] != _|_ {\n\t\t\t\t\t\tvolumeMounts: mountsArray.pvc 564 + mountsArray.configMap + mountsArray.secret + mountsArray.emptyDir 565 + mountsArray.hostPath\n\t\t\t\t\t}\n\n\t\t\t\t\tif parameter[\"livenessProbe\"] 566 != _|_ {\n\t\t\t\t\t\tlivenessProbe: parameter.livenessProbe\n\t\t\t\t\t}\n\n\t\t\t\t\tif 567 parameter[\"readinessProbe\"] != _|_ {\n\t\t\t\t\t\treadinessProbe: 568 parameter.readinessProbe\n\t\t\t\t\t}\n\n\t\t\t\t}]\n\n\t\t\t\tif parameter[\"hostAliases\"] 569 != _|_ {\n\t\t\t\t\t// +patchKey=ip\n\t\t\t\t\thostAliases: parameter.hostAliases\n\t\t\t\t}\n\n\t\t\t\tif 570 parameter[\"imagePullSecrets\"] != _|_ {\n\t\t\t\t\timagePullSecrets: 571 [ for v in parameter.imagePullSecrets {\n\t\t\t\t\t\tname: v\n\t\t\t\t\t},\n\t\t\t\t\t]\n\t\t\t\t}\n\n\t\t\t\tif 572 parameter[\"volumes\"] != _|_ && parameter[\"volumeMounts\"] == _|_ 573 {\n\t\t\t\t\tvolumes: [ for v in parameter.volumes {\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tname: 574 v.name\n\t\t\t\t\t\t\tif v.type == \"pvc\" {\n\t\t\t\t\t\t\t\tpersistentVolumeClaim: 575 claimName: v.claimName\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif v.type == 576 \"configMap\" {\n\t\t\t\t\t\t\t\tconfigMap: {\n\t\t\t\t\t\t\t\t\tdefaultMode: 577 v.defaultMode\n\t\t\t\t\t\t\t\t\tname: v.cmName\n\t\t\t\t\t\t\t\t\tif 578 v.items != _|_ {\n\t\t\t\t\t\t\t\t\t\titems: v.items\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif 579 v.type == \"secret\" {\n\t\t\t\t\t\t\t\tsecret: {\n\t\t\t\t\t\t\t\t\tdefaultMode: 580 v.defaultMode\n\t\t\t\t\t\t\t\t\tsecretName: v.secretName\n\t\t\t\t\t\t\t\t\tif 581 v.items != _|_ {\n\t\t\t\t\t\t\t\t\t\titems: v.items\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif 582 v.type == \"emptyDir\" {\n\t\t\t\t\t\t\t\temptyDir: medium: v.medium\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}]\n\t\t\t\t}\n\n\t\t\t\tif 583 parameter[\"volumeMounts\"] != _|_ {\n\t\t\t\t\tvolumes: volumesArray.pvc 584 + volumesArray.configMap + volumesArray.secret + volumesArray.emptyDir 585 + volumesArray.hostPath\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\nexposePorts: 586 [\n\tfor v in parameter.ports if v.expose == true {\n\t\tport: v.port\n\t\ttargetPort: 587 v.port\n\t\tif v.name != _|_ {\n\t\t\tname: v.name\n\t\t}\n\t\tif v.name 588 == _|_ {\n\t\t\tname: \"port-\" + strconv.FormatInt(v.port, 10)\n\t\t}\n\t},\n]\noutputs: 589 {\n\tif len(exposePorts) != 0 {\n\t\twebserviceExpose: {\n\t\t\tapiVersion: 590 \"v1\"\n\t\t\tkind: \"Service\"\n\t\t\tmetadata: name: context.name\n\t\t\tspec: 591 {\n\t\t\t\tselector: \"app.oam.dev/component\": context.name\n\t\t\t\tports: 592 exposePorts\n\t\t\t\ttype: parameter.exposeType\n\t\t\t}\n\t\t}\n\t}\n}\nparameter: 593 {\n\t// +usage=Specify the labels in the workload\n\tlabels?: [string]: 594 string\n\n\t// +usage=Specify the annotations in the workload\n\tannotations?: 595 [string]: string\n\n\t// +usage=Which image would you like to use for 596 your service\n\t// +short=i\n\timage: string\n\n\t// +usage=Specify 597 image pull policy for your service\n\timagePullPolicy?: \"Always\" | 598 \"Never\" | \"IfNotPresent\"\n\n\t// +usage=Specify image pull secrets 599 for your service\n\timagePullSecrets?: [...string]\n\n\t// +ignore\n\t// 600 +usage=Deprecated field, please use ports instead\n\t// +short=p\n\tport?: 601 int\n\n\t// +usage=Which ports do you want customer traffic sent to, 602 defaults to 80\n\tports?: [...{\n\t\t// +usage=Number of port to expose 603 on the pod's IP address\n\t\tport: int\n\t\t// +usage=Name of the port\n\t\tname?: 604 string\n\t\t// +usage=Protocol for port. Must be UDP, TCP, or SCTP\n\t\tprotocol: 605 *\"TCP\" | \"UDP\" | \"SCTP\"\n\t\t// +usage=Specify if the port should 606 be exposed\n\t\texpose: *false | bool\n\t}]\n\n\t// +ignore\n\t// +usage=Specify 607 what kind of Service you want. options: \"ClusterIP\", \"NodePort\", 608 \"LoadBalancer\", \"ExternalName\"\n\texposeType: *\"ClusterIP\" | \"NodePort\" 609 | \"LoadBalancer\" | \"ExternalName\"\n\n\t// +ignore\n\t// +usage=If 610 addRevisionLabel is true, the revision label will be added to the underlying 611 pods\n\taddRevisionLabel: *false | bool\n\n\t// +usage=Commands to run 612 in the container\n\tcmd?: [...string]\n\n\t// +usage=Define arguments 613 by using environment variables\n\tenv?: [...{\n\t\t// +usage=Environment 614 variable name\n\t\tname: string\n\t\t// +usage=The value of the environment 615 variable\n\t\tvalue?: string\n\t\t// +usage=Specifies a source the value 616 of this var should come from\n\t\tvalueFrom?: {\n\t\t\t// +usage=Selects 617 a key of a secret in the pod's namespace\n\t\t\tsecretKeyRef?: {\n\t\t\t\t// 618 +usage=The name of the secret in the pod's namespace to select from\n\t\t\t\tname: 619 string\n\t\t\t\t// +usage=The key of the secret to select from. Must 620 be a valid secret key\n\t\t\t\tkey: string\n\t\t\t}\n\t\t\t// +usage=Selects 621 a key of a config map in the pod's namespace\n\t\t\tconfigMapKeyRef?: 622 {\n\t\t\t\t// +usage=The name of the config map in the pod's namespace 623 to select from\n\t\t\t\tname: string\n\t\t\t\t// +usage=The key of the 624 config map to select from. Must be a valid secret key\n\t\t\t\tkey: 625 string\n\t\t\t}\n\t\t}\n\t}]\n\n\t// +usage=Number of CPU units for 626 the service, like \n\tcpu?: string\n\n\t// 627 +usage=Specifies the attributes of the memory resource required for 628 the container.\n\tmemory?: string\n\n\tvolumeMounts?: {\n\t\t// +usage=Mount 629 PVC type volume\n\t\tpvc?: [...{\n\t\t\tname: string\n\t\t\tmountPath: 630 string\n\t\t\t// +usage=The name of the PVC\n\t\t\tclaimName: string\n\t\t}]\n\t\t// 631 +usage=Mount ConfigMap type volume\n\t\tconfigMap?: [...{\n\t\t\tname: 632 \ string\n\t\t\tmountPath: string\n\t\t\tdefaultMode: *420 | 633 int\n\t\t\tcmName: string\n\t\t\titems?: [...{\n\t\t\t\tkey: string\n\t\t\t\tpath: 634 string\n\t\t\t\tmode: *511 | int\n\t\t\t}]\n\t\t}]\n\t\t// +usage=Mount 635 Secret type volume\n\t\tsecret?: [...{\n\t\t\tname: string\n\t\t\tmountPath: 636 \ string\n\t\t\tdefaultMode: *420 | int\n\t\t\tsecretName: string\n\t\t\titems?: 637 [...{\n\t\t\t\tkey: string\n\t\t\t\tpath: string\n\t\t\t\tmode: *511 638 | int\n\t\t\t}]\n\t\t}]\n\t\t// +usage=Mount EmptyDir type volume\n\t\temptyDir?: 639 [...{\n\t\t\tname: string\n\t\t\tmountPath: string\n\t\t\tmedium: 640 \ *\"\" | \"Memory\"\n\t\t}]\n\t\t// +usage=Mount HostPath type volume\n\t\thostPath?: 641 [...{\n\t\t\tname: string\n\t\t\tmountPath: string\n\t\t\tpath: 642 \ string\n\t\t}]\n\t}\n\n\t// +usage=Deprecated field, use volumeMounts 643 instead.\n\tvolumes?: [...{\n\t\tname: string\n\t\tmountPath: string\n\t\t// 644 +usage=Specify volume type, options: \"pvc\",\"configMap\",\"secret\",\"emptyDir\"\n\t\ttype: 645 \"pvc\" | \"configMap\" | \"secret\" | \"emptyDir\"\n\t\tif type == 646 \"pvc\" {\n\t\t\tclaimName: string\n\t\t}\n\t\tif type == \"configMap\" 647 {\n\t\t\tdefaultMode: *420 | int\n\t\t\tcmName: string\n\t\t\titems?: 648 [...{\n\t\t\t\tkey: string\n\t\t\t\tpath: string\n\t\t\t\tmode: *511 649 | int\n\t\t\t}]\n\t\t}\n\t\tif type == \"secret\" {\n\t\t\tdefaultMode: 650 *420 | int\n\t\t\tsecretName: string\n\t\t\titems?: [...{\n\t\t\t\tkey: 651 \ string\n\t\t\t\tpath: string\n\t\t\t\tmode: *511 | int\n\t\t\t}]\n\t\t}\n\t\tif 652 type == \"emptyDir\" {\n\t\t\tmedium: *\"\" | \"Memory\"\n\t\t}\n\t}]\n\n\t// 653 +usage=Instructions for assessing whether the container is alive.\n\tlivenessProbe?: 654 #HealthProbe\n\n\t// +usage=Instructions for assessing whether the container 655 is in a suitable state to serve traffic.\n\treadinessProbe?: #HealthProbe\n\n\t// 656 +usage=Specify the hostAliases to add\n\thostAliases?: [...{\n\t\tip: 657 string\n\t\thostnames: [...string]\n\t}]\n}\n#HealthProbe: {\n\n\t// 658 +usage=Instructions for assessing container health by executing a command. 659 Either this attribute or the httpGet attribute or the tcpSocket attribute 660 MUST be specified. This attribute is mutually exclusive with both the 661 httpGet attribute and the tcpSocket attribute.\n\texec?: {\n\t\t// +usage=A 662 command to be executed inside the container to assess its health. Each 663 space delimited token of the command is a separate array element. Commands 664 exiting 0 are considered to be successful probes, whilst all other exit 665 codes are considered failures.\n\t\tcommand: [...string]\n\t}\n\n\t// 666 +usage=Instructions for assessing container health by executing an HTTP 667 GET request. Either this attribute or the exec attribute or the tcpSocket 668 attribute MUST be specified. This attribute is mutually exclusive with 669 both the exec attribute and the tcpSocket attribute.\n\thttpGet?: {\n\t\t// 670 +usage=The endpoint, relative to the port, to which the HTTP GET request 671 should be directed.\n\t\tpath: string\n\t\t// +usage=The TCP socket 672 within the container to which the HTTP GET request should be directed.\n\t\tport: 673 int\n\t\thttpHeaders?: [...{\n\t\t\tname: string\n\t\t\tvalue: string\n\t\t}]\n\t}\n\n\t// 674 +usage=Instructions for assessing container health by probing a TCP 675 socket. Either this attribute or the exec attribute or the httpGet attribute 676 MUST be specified. This attribute is mutually exclusive with both the 677 exec attribute and the httpGet attribute.\n\ttcpSocket?: {\n\t\t// +usage=The 678 TCP socket within the container that should be probed to assess container 679 health.\n\t\tport: int\n\t}\n\n\t// +usage=Number of seconds after the 680 container is started before the first probe is initiated.\n\tinitialDelaySeconds: 681 *0 | int\n\n\t// +usage=How often, in seconds, to execute the probe.\n\tperiodSeconds: 682 *10 | int\n\n\t// +usage=Number of seconds after which the probe times 683 out.\n\ttimeoutSeconds: *1 | int\n\n\t// +usage=Minimum consecutive 684 successes for the probe to be considered successful after having failed.\n\tsuccessThreshold: 685 *1 | int\n\n\t// +usage=Number of consecutive failures required to determine 686 the container is not alive (liveness probe) or not ready (readiness 687 probe).\n\tfailureThreshold: *3 | int\n}\n" 688 status: 689 customStatus: "ready: {\n\treadyReplicas: *0 | int\n} & {\n\tif context.output.status.readyReplicas 690 != _|_ {\n\t\treadyReplicas: context.output.status.readyReplicas\n\t}\n}\nmessage: 691 \"Ready:\\(ready.readyReplicas)/\\(context.output.spec.replicas)\"" 692 healthPolicy: "ready: {\n\tupdatedReplicas: *0 | int\n\treadyReplicas: 693 \ *0 | int\n\treplicas: *0 | int\n\tobservedGeneration: 694 *0 | int\n} & {\n\tif context.output.status.updatedReplicas != _|_ {\n\t\tupdatedReplicas: 695 context.output.status.updatedReplicas\n\t}\n\tif context.output.status.readyReplicas 696 != _|_ {\n\t\treadyReplicas: context.output.status.readyReplicas\n\t}\n\tif 697 context.output.status.replicas != _|_ {\n\t\treplicas: context.output.status.replicas\n\t}\n\tif 698 context.output.status.observedGeneration != _|_ {\n\t\tobservedGeneration: 699 context.output.status.observedGeneration\n\t}\n}\nisHealth: (context.output.spec.replicas 700 == ready.readyReplicas) && (context.output.spec.replicas == ready.updatedReplicas) 701 && (context.output.spec.replicas == ready.replicas) && (ready.observedGeneration 702 == context.output.metadata.generation || ready.observedGeneration > context.output.metadata.generation)" 703 workload: 704 definition: 705 apiVersion: apps/v1 706 kind: Deployment 707 type: deployments.apps 708 status: {} 709 status: {} 710 ` 711 Expect(yaml.Unmarshal([]byte(apprevYaml), &apprev)).To(Succeed()) 712 // simulate 1.2 version that WorkflowStepDefinitions are not patched in appliacation revision 713 apprev.ObjectMeta.OwnerReferences[0].UID = app.ObjectMeta.UID 714 Expect(k8sClient.Create(ctx, &apprev)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{})) 715 716 // prepare handler 717 _handler, err := NewAppHandler(ctx, reconciler, &app) 718 Expect(err).Should(Succeed()) 719 handler = _handler 720 721 }) 722 723 It("Test currentAppRevIsNew func", func() { 724 By("Backport 1.2 version that WorkflowStepDefinitions are not patched to application revision") 725 // generate appfile 726 appfile, err := appfile.NewApplicationParser(reconciler.Client, reconciler.pd).GenerateAppFile(ctx, &app) 727 ctx = util.SetNamespaceInCtx(ctx, app.Namespace) 728 Expect(err).To(Succeed()) 729 Expect(handler.PrepareCurrentAppRevision(ctx, appfile)).Should(Succeed()) 730 731 // prepare apprev 732 thisWSD := handler.currentAppRev.Spec.WorkflowStepDefinitions 733 Expect(len(thisWSD) > 0 && func() bool { 734 expected := appfile.RelatedWorkflowStepDefinitions 735 for i, w := range thisWSD { 736 expW := expected[i] 737 if !reflect.DeepEqual(w, expW) { 738 fmt.Printf("appfile wsd:%s apprev wsd%s", w.Name, expW.Name) 739 return false 740 } 741 } 742 return true 743 }()).Should(BeTrue()) 744 }) 745 }) 746 747 func TestDeepEqualAppInRevision(t *testing.T) { 748 oldRev := &v1beta1.ApplicationRevision{} 749 newRev := &v1beta1.ApplicationRevision{} 750 newRev.Spec.Application.Spec.Workflow = &v1beta1.Workflow{ 751 Steps: []workflowv1alpha1.WorkflowStep{{ 752 WorkflowStepBase: workflowv1alpha1.WorkflowStepBase{ 753 Type: "deploy", 754 Name: "deploy", 755 }, 756 }}, 757 } 758 require.False(t, deepEqualAppInRevision(oldRev, newRev)) 759 metav1.SetMetaDataAnnotation(&oldRev.Spec.Application.ObjectMeta, oam.AnnotationKubeVelaVersion, "v1.6.0-alpha.5") 760 require.False(t, deepEqualAppInRevision(oldRev, newRev)) 761 metav1.SetMetaDataAnnotation(&oldRev.Spec.Application.ObjectMeta, oam.AnnotationKubeVelaVersion, "v1.5.0") 762 require.True(t, deepEqualAppInRevision(oldRev, newRev)) 763 }