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  }