github.com/oam-dev/kubevela@v1.9.11/pkg/controller/core.oam.dev/v1beta1/application/revision.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  	"sort"
    22  
    23  	"github.com/hashicorp/go-version"
    24  	"github.com/kubevela/pkg/util/k8s"
    25  	"github.com/pkg/errors"
    26  	corev1 "k8s.io/api/core/v1"
    27  	apiequality "k8s.io/apimachinery/pkg/api/equality"
    28  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    29  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    30  	ktypes "k8s.io/apimachinery/pkg/types"
    31  	utilfeature "k8s.io/apiserver/pkg/util/feature"
    32  	"k8s.io/klog/v2"
    33  	"k8s.io/utils/pointer"
    34  	"sigs.k8s.io/controller-runtime/pkg/client"
    35  
    36  	"github.com/kubevela/pkg/util/compression"
    37  
    38  	"github.com/kubevela/pkg/controller/sharding"
    39  	monitorContext "github.com/kubevela/pkg/monitor/context"
    40  	workflowv1alpha1 "github.com/kubevela/workflow/api/v1alpha1"
    41  
    42  	"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
    43  	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha1"
    44  	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
    45  	"github.com/oam-dev/kubevela/pkg/appfile"
    46  	"github.com/oam-dev/kubevela/pkg/cache"
    47  	"github.com/oam-dev/kubevela/pkg/component"
    48  	"github.com/oam-dev/kubevela/pkg/controller/utils"
    49  	"github.com/oam-dev/kubevela/pkg/features"
    50  	"github.com/oam-dev/kubevela/pkg/monitor/metrics"
    51  	"github.com/oam-dev/kubevela/pkg/oam"
    52  	"github.com/oam-dev/kubevela/pkg/oam/util"
    53  )
    54  
    55  type contextKey int
    56  
    57  var (
    58  	// DisableAllComponentRevision disable component revision creation
    59  	DisableAllComponentRevision = false
    60  	// DisableAllApplicationRevision disable application revision creation
    61  	DisableAllApplicationRevision = false
    62  )
    63  
    64  func contextWithComponentNamespace(ctx context.Context, ns string) context.Context {
    65  	return context.WithValue(ctx, ComponentNamespaceContextKey, ns)
    66  }
    67  
    68  func componentNamespaceFromContext(ctx context.Context) string {
    69  	ns, _ := ctx.Value(ComponentNamespaceContextKey).(string)
    70  	return ns
    71  }
    72  
    73  func contextWithReplicaKey(ctx context.Context, key string) context.Context {
    74  	return context.WithValue(ctx, ReplicaKeyContextKey, key)
    75  }
    76  
    77  func replicaKeyFromContext(ctx context.Context) string {
    78  	key, _ := ctx.Value(ReplicaKeyContextKey).(string)
    79  	return key
    80  }
    81  
    82  // PrepareCurrentAppRevision will generate a pure revision without metadata and rendered result
    83  // the generated revision will be compare with the last revision to see if there's any difference.
    84  func (h *AppHandler) PrepareCurrentAppRevision(ctx context.Context, af *appfile.Appfile) error {
    85  	if ctx, ok := ctx.(monitorContext.Context); ok {
    86  		subCtx := ctx.Fork("prepare-current-appRevision", monitorContext.DurationMetric(func(v float64) {
    87  			metrics.AppReconcileStageDurationHistogram.WithLabelValues("prepare-current-apprev").Observe(v)
    88  		}))
    89  		defer subCtx.Commit("finish prepare current appRevision")
    90  	}
    91  
    92  	if af.AppRevision != nil {
    93  		h.isNewRevision = false
    94  		h.latestAppRev = af.AppRevision
    95  		h.currentAppRev = af.AppRevision
    96  		h.currentRevHash = af.AppRevisionHash
    97  		return nil
    98  	}
    99  
   100  	appRev, appRevisionHash, err := h.gatherRevisionSpec(af)
   101  	if err != nil {
   102  		return err
   103  	}
   104  	h.currentAppRev = appRev
   105  	h.currentRevHash = appRevisionHash
   106  	if err := h.getLatestAppRevision(ctx); err != nil {
   107  		return err
   108  	}
   109  
   110  	var needGenerateRevision bool
   111  	h.isNewRevision, needGenerateRevision, err = h.currentAppRevIsNew(ctx)
   112  	if err != nil {
   113  		return err
   114  	}
   115  	if h.isNewRevision && needGenerateRevision {
   116  		h.currentAppRev.Name, _ = utils.GetAppNextRevision(h.app)
   117  	}
   118  
   119  	// MUST pass app revision name to appfile
   120  	// appfile depends it to render resources and do health checking
   121  	af.AppRevisionName = h.currentAppRev.Name
   122  	af.AppRevisionHash = h.currentRevHash
   123  	return nil
   124  }
   125  
   126  // gatherRevisionSpec will gather all revision spec without metadata and rendered result.
   127  // the gathered Revision spec will be enough to calculate the hash and compare with the old revision
   128  func (h *AppHandler) gatherRevisionSpec(af *appfile.Appfile) (*v1beta1.ApplicationRevision, string, error) {
   129  	copiedApp := h.app.DeepCopy()
   130  	// We better to remove all object status in the appRevision
   131  	copiedApp.Status = common.AppStatus{}
   132  	appRev := &v1beta1.ApplicationRevision{
   133  		Spec: v1beta1.ApplicationRevisionSpec{
   134  			ApplicationRevisionCompressibleFields: v1beta1.ApplicationRevisionCompressibleFields{
   135  				Application:             *copiedApp,
   136  				ComponentDefinitions:    make(map[string]*v1beta1.ComponentDefinition),
   137  				WorkloadDefinitions:     make(map[string]v1beta1.WorkloadDefinition),
   138  				TraitDefinitions:        make(map[string]*v1beta1.TraitDefinition),
   139  				PolicyDefinitions:       make(map[string]v1beta1.PolicyDefinition),
   140  				WorkflowStepDefinitions: make(map[string]*v1beta1.WorkflowStepDefinition),
   141  				Policies:                make(map[string]v1alpha1.Policy),
   142  			},
   143  		},
   144  	}
   145  	for _, w := range af.ParsedComponents {
   146  		if w == nil {
   147  			continue
   148  		}
   149  		if w.FullTemplate.ComponentDefinition != nil {
   150  			cd := w.FullTemplate.ComponentDefinition.DeepCopy()
   151  			cd.Status = v1beta1.ComponentDefinitionStatus{}
   152  			appRev.Spec.ComponentDefinitions[w.FullTemplate.ComponentDefinition.Name] = cd.DeepCopy()
   153  		}
   154  		if w.FullTemplate.WorkloadDefinition != nil {
   155  			wd := w.FullTemplate.WorkloadDefinition.DeepCopy()
   156  			wd.Status = v1beta1.WorkloadDefinitionStatus{}
   157  			appRev.Spec.WorkloadDefinitions[w.FullTemplate.WorkloadDefinition.Name] = *wd
   158  		}
   159  		for _, t := range w.Traits {
   160  			if t == nil {
   161  				continue
   162  			}
   163  			if t.FullTemplate.TraitDefinition != nil {
   164  				td := t.FullTemplate.TraitDefinition.DeepCopy()
   165  				td.Status = v1beta1.TraitDefinitionStatus{}
   166  				appRev.Spec.TraitDefinitions[t.FullTemplate.TraitDefinition.Name] = td.DeepCopy()
   167  			}
   168  		}
   169  	}
   170  	for _, p := range af.ParsedPolicies {
   171  		if p == nil || p.FullTemplate == nil {
   172  			continue
   173  		}
   174  		if p.FullTemplate.PolicyDefinition != nil {
   175  			pd := p.FullTemplate.PolicyDefinition.DeepCopy()
   176  			pd.Status = v1beta1.PolicyDefinitionStatus{}
   177  			appRev.Spec.PolicyDefinitions[p.FullTemplate.PolicyDefinition.Name] = *pd
   178  		}
   179  	}
   180  	for name, def := range af.RelatedComponentDefinitions {
   181  		appRev.Spec.ComponentDefinitions[name] = def.DeepCopy()
   182  	}
   183  	for name, def := range af.RelatedTraitDefinitions {
   184  		appRev.Spec.TraitDefinitions[name] = def.DeepCopy()
   185  	}
   186  	for name, def := range af.RelatedWorkflowStepDefinitions {
   187  		appRev.Spec.WorkflowStepDefinitions[name] = def.DeepCopy()
   188  	}
   189  	for name, po := range af.ExternalPolicies {
   190  		appRev.Spec.Policies[name] = *po
   191  	}
   192  	var err error
   193  	if appRev.Spec.ReferredObjects, err = component.ConvertUnstructuredsToReferredObjects(af.ReferredObjects); err != nil {
   194  		return nil, "", errors.Wrapf(err, "failed to marshal referred object")
   195  	}
   196  	appRev.Spec.Workflow = af.ExternalWorkflow
   197  
   198  	appRevisionHash, err := ComputeAppRevisionHash(appRev)
   199  	if err != nil {
   200  		klog.ErrorS(err, "Failed to compute hash of appRevision for application", "application", klog.KObj(h.app))
   201  		return appRev, "", errors.Wrapf(err, "failed to compute app revision hash")
   202  	}
   203  	return appRev, appRevisionHash, nil
   204  }
   205  
   206  func (h *AppHandler) getLatestAppRevision(ctx context.Context) error {
   207  	if DisableAllApplicationRevision {
   208  		return nil
   209  	}
   210  	if h.app.Status.LatestRevision == nil || len(h.app.Status.LatestRevision.Name) == 0 {
   211  		return nil
   212  	}
   213  	latestRevName := h.app.Status.LatestRevision.Name
   214  	latestAppRev := &v1beta1.ApplicationRevision{}
   215  	if err := h.Get(ctx, client.ObjectKey{Name: latestRevName, Namespace: h.app.Namespace}, latestAppRev); err != nil {
   216  		klog.ErrorS(err, "Failed to get latest app revision", "appRevisionName", latestRevName)
   217  		return errors.Wrapf(err, "fail to get latest app revision %s", latestRevName)
   218  	}
   219  	h.latestAppRev = latestAppRev
   220  	return nil
   221  }
   222  
   223  // ComputeAppRevisionHash computes a single hash value for an appRevision object
   224  // Spec of Application/WorkloadDefinitions/ComponentDefinitions/TraitDefinitions/ScopeDefinitions will be taken into compute
   225  func ComputeAppRevisionHash(appRevision *v1beta1.ApplicationRevision) (string, error) {
   226  	// Calculate Hash for New Mode with workflow and policy
   227  	revHash := struct {
   228  		ApplicationSpecHash        string
   229  		WorkloadDefinitionHash     map[string]string
   230  		ComponentDefinitionHash    map[string]string
   231  		TraitDefinitionHash        map[string]string
   232  		ScopeDefinitionHash        map[string]string
   233  		PolicyDefinitionHash       map[string]string
   234  		WorkflowStepDefinitionHash map[string]string
   235  		PolicyHash                 map[string]string
   236  		WorkflowHash               string
   237  		ReferredObjectsHash        string
   238  	}{
   239  		WorkloadDefinitionHash:     make(map[string]string),
   240  		ComponentDefinitionHash:    make(map[string]string),
   241  		TraitDefinitionHash:        make(map[string]string),
   242  		ScopeDefinitionHash:        make(map[string]string),
   243  		PolicyDefinitionHash:       make(map[string]string),
   244  		WorkflowStepDefinitionHash: make(map[string]string),
   245  		PolicyHash:                 make(map[string]string),
   246  	}
   247  	var err error
   248  	revHash.ApplicationSpecHash, err = utils.ComputeSpecHash(appRevision.Spec.Application.Spec)
   249  	if err != nil {
   250  		return "", err
   251  	}
   252  	for key, wd := range appRevision.Spec.WorkloadDefinitions {
   253  		wdCopy := wd
   254  		hash, err := utils.ComputeSpecHash(&wdCopy.Spec)
   255  		if err != nil {
   256  			return "", err
   257  		}
   258  		revHash.WorkloadDefinitionHash[key] = hash
   259  	}
   260  	for key, cd := range appRevision.Spec.ComponentDefinitions {
   261  		hash, err := utils.ComputeSpecHash(&cd.Spec)
   262  		if err != nil {
   263  			return "", err
   264  		}
   265  		revHash.ComponentDefinitionHash[key] = hash
   266  	}
   267  	for key, td := range appRevision.Spec.TraitDefinitions {
   268  		hash, err := utils.ComputeSpecHash(&td.Spec)
   269  		if err != nil {
   270  			return "", err
   271  		}
   272  		revHash.TraitDefinitionHash[key] = hash
   273  	}
   274  	for key, pd := range appRevision.Spec.PolicyDefinitions {
   275  		pdCopy := pd
   276  		hash, err := utils.ComputeSpecHash(&pdCopy.Spec)
   277  		if err != nil {
   278  			return "", err
   279  		}
   280  		revHash.PolicyDefinitionHash[key] = hash
   281  	}
   282  	for key, wd := range appRevision.Spec.WorkflowStepDefinitions {
   283  		hash, err := utils.ComputeSpecHash(&wd.Spec)
   284  		if err != nil {
   285  			return "", err
   286  		}
   287  		revHash.WorkflowStepDefinitionHash[key] = hash
   288  	}
   289  	for key, po := range appRevision.Spec.Policies {
   290  		hash, err := utils.ComputeSpecHash(po.Properties)
   291  		if err != nil {
   292  			return "", err
   293  		}
   294  		revHash.PolicyHash[key] = hash + po.Type
   295  	}
   296  	if appRevision.Spec.Workflow != nil {
   297  		revHash.WorkflowHash, err = utils.ComputeSpecHash(appRevision.Spec.Workflow.Steps)
   298  		if err != nil {
   299  			return "", err
   300  		}
   301  	}
   302  	revHash.ReferredObjectsHash, err = utils.ComputeSpecHash(appRevision.Spec.ReferredObjects)
   303  	if err != nil {
   304  		return "", err
   305  	}
   306  	return utils.ComputeSpecHash(&revHash)
   307  }
   308  
   309  // currentAppRevIsNew check application revision already exist or not
   310  func (h *AppHandler) currentAppRevIsNew(ctx context.Context) (bool, bool, error) {
   311  	// the last revision doesn't exist.
   312  	if h.app.Status.LatestRevision == nil || DisableAllApplicationRevision {
   313  		return true, true, nil
   314  	}
   315  
   316  	isLatestRev := deepEqualAppInRevision(h.latestAppRev, h.currentAppRev)
   317  	if metav1.HasAnnotation(h.app.ObjectMeta, oam.AnnotationAutoUpdate) {
   318  		isLatestRev = h.app.Status.LatestRevision.RevisionHash == h.currentRevHash && DeepEqualRevision(h.latestAppRev, h.currentAppRev)
   319  	}
   320  	if h.latestAppRev != nil && oam.GetPublishVersion(h.app) != oam.GetPublishVersion(h.latestAppRev) {
   321  		isLatestRev = false
   322  	}
   323  
   324  	// diff the latest revision first
   325  	if isLatestRev {
   326  		appSpec := h.currentAppRev.Spec.Application.Spec
   327  		traitDef := h.currentAppRev.Spec.TraitDefinitions
   328  		workflowStepDef := h.currentAppRev.Spec.WorkflowStepDefinitions
   329  		h.currentAppRev = h.latestAppRev.DeepCopy()
   330  		h.currentRevHash = h.app.Status.LatestRevision.RevisionHash
   331  		h.currentAppRev.Spec.Application.Spec = appSpec
   332  		h.currentAppRev.Spec.TraitDefinitions = traitDef
   333  		h.currentAppRev.Spec.WorkflowStepDefinitions = workflowStepDef
   334  		return false, false, nil
   335  	}
   336  
   337  	revs, err := GetAppRevisions(ctx, h.Client, h.app.Name, h.app.Namespace)
   338  	if err != nil {
   339  		klog.ErrorS(err, "Failed to list app revision", "appName", h.app.Name)
   340  		return false, false, errors.Wrap(err, "failed to list app revision")
   341  	}
   342  
   343  	for _, _rev := range revs {
   344  		rev := _rev.DeepCopy()
   345  		if rev.GetLabels()[oam.LabelAppRevisionHash] == h.currentRevHash &&
   346  			DeepEqualRevision(rev, h.currentAppRev) &&
   347  			oam.GetPublishVersion(rev) == oam.GetPublishVersion(h.app) {
   348  			// we set currentAppRev to existRevision
   349  			h.currentAppRev = rev
   350  			return true, false, nil
   351  		}
   352  	}
   353  
   354  	// if reach here, it has different spec
   355  	return true, true, nil
   356  }
   357  
   358  // DeepEqualRevision will compare the spec of Application and Definition to see if the Application is the same revision
   359  // Spec of AC and Component will not be compared as they are generated by the application and definitions
   360  // Note the Spec compare can only work when the RawExtension are decoded well in the RawExtension.Object instead of in RawExtension.Raw(bytes)
   361  func DeepEqualRevision(old, new *v1beta1.ApplicationRevision) bool {
   362  	if len(old.Spec.WorkloadDefinitions) != len(new.Spec.WorkloadDefinitions) {
   363  		return false
   364  	}
   365  	oldTraitDefinitions := old.Spec.TraitDefinitions
   366  	newTraitDefinitions := new.Spec.TraitDefinitions
   367  	if len(oldTraitDefinitions) != len(newTraitDefinitions) {
   368  		return false
   369  	}
   370  	if len(old.Spec.ComponentDefinitions) != len(new.Spec.ComponentDefinitions) {
   371  		return false
   372  	}
   373  	for key, wd := range new.Spec.WorkloadDefinitions {
   374  		if !apiequality.Semantic.DeepEqual(old.Spec.WorkloadDefinitions[key].Spec, wd.Spec) {
   375  			return false
   376  		}
   377  	}
   378  	for key, cd := range new.Spec.ComponentDefinitions {
   379  		if !apiequality.Semantic.DeepEqual(old.Spec.ComponentDefinitions[key].Spec, cd.Spec) {
   380  			return false
   381  		}
   382  	}
   383  	for key, td := range newTraitDefinitions {
   384  		if !apiequality.Semantic.DeepEqual(oldTraitDefinitions[key].Spec, td.Spec) {
   385  			return false
   386  		}
   387  	}
   388  	return deepEqualAppInRevision(old, new)
   389  }
   390  
   391  func deepEqualPolicy(old, new v1alpha1.Policy) bool {
   392  	return old.Type == new.Type && apiequality.Semantic.DeepEqual(old.Properties, new.Properties)
   393  }
   394  
   395  func deepEqualWorkflow(old, new workflowv1alpha1.Workflow) bool {
   396  	return apiequality.Semantic.DeepEqual(old.Steps, new.Steps)
   397  }
   398  
   399  const velaVersionNumberToCompareWorkflow = "v1.5.7"
   400  
   401  func deepEqualAppSpec(old, new *v1beta1.Application) bool {
   402  	oldSpec, newSpec := old.Spec.DeepCopy(), new.Spec.DeepCopy()
   403  	// legacy code: KubeVela version before v1.5.7 & v1.6.0-alpha.4 does not
   404  	// record workflow in application spec in application revision. The comparison
   405  	// need to bypass the equality check of workflow to prevent unintended rerun
   406  	curVerNum := k8s.GetAnnotation(old, oam.AnnotationKubeVelaVersion)
   407  	publishVersion := k8s.GetAnnotation(old, oam.AnnotationPublishVersion)
   408  	if publishVersion == "" && curVerNum != "" {
   409  		cmpVer, _ := version.NewVersion(velaVersionNumberToCompareWorkflow)
   410  		if curVer, err := version.NewVersion(curVerNum); err == nil && curVer.LessThan(cmpVer) {
   411  			oldSpec.Workflow = nil
   412  			newSpec.Workflow = nil
   413  		}
   414  	}
   415  	return apiequality.Semantic.DeepEqual(oldSpec, newSpec)
   416  }
   417  
   418  func deepEqualAppInRevision(old, new *v1beta1.ApplicationRevision) bool {
   419  	if len(old.Spec.Policies) != len(new.Spec.Policies) {
   420  		return false
   421  	}
   422  	for key, po := range new.Spec.Policies {
   423  		if !deepEqualPolicy(old.Spec.Policies[key], po) {
   424  			return false
   425  		}
   426  	}
   427  	if (old.Spec.Workflow == nil) != (new.Spec.Workflow == nil) {
   428  		return false
   429  	}
   430  	if old.Spec.Workflow != nil && new.Spec.Workflow != nil {
   431  		if !deepEqualWorkflow(*old.Spec.Workflow, *new.Spec.Workflow) {
   432  			return false
   433  		}
   434  	}
   435  	return deepEqualAppSpec(&old.Spec.Application, &new.Spec.Application)
   436  }
   437  
   438  // FinalizeAndApplyAppRevision finalise AppRevision object and apply it
   439  func (h *AppHandler) FinalizeAndApplyAppRevision(ctx context.Context) error {
   440  	if DisableAllApplicationRevision {
   441  		return nil
   442  	}
   443  
   444  	if ctx, ok := ctx.(monitorContext.Context); ok {
   445  		subCtx := ctx.Fork("apply-app-revision", monitorContext.DurationMetric(func(v float64) {
   446  			metrics.AppReconcileStageDurationHistogram.WithLabelValues("apply-apprev").Observe(v)
   447  		}))
   448  		defer subCtx.Commit("finish apply app revision")
   449  	}
   450  	appRev := h.currentAppRev
   451  	appRev.Namespace = h.app.Namespace
   452  	appRev.SetGroupVersionKind(v1beta1.ApplicationRevisionGroupVersionKind)
   453  	// pass application's annotations & labels to app revision
   454  	appRev.SetAnnotations(h.app.GetAnnotations())
   455  	delete(appRev.Annotations, oam.AnnotationLastAppliedConfiguration)
   456  	appRev.SetLabels(h.app.GetLabels())
   457  	util.AddLabels(appRev, map[string]string{
   458  		oam.LabelAppName:         h.app.GetName(),
   459  		oam.LabelAppRevisionHash: h.currentRevHash,
   460  	})
   461  	// ApplicationRevision must use Application as ctrl-owner
   462  	appRev.SetOwnerReferences([]metav1.OwnerReference{{
   463  		APIVersion: v1beta1.SchemeGroupVersion.String(),
   464  		Kind:       v1beta1.ApplicationKind,
   465  		Name:       h.app.Name,
   466  		UID:        h.app.UID,
   467  		Controller: pointer.Bool(true),
   468  	}})
   469  	sharding.PropagateScheduledShardIDLabel(h.app, appRev)
   470  
   471  	gotAppRev := &v1beta1.ApplicationRevision{}
   472  	if err := h.Get(ctx, client.ObjectKey{Name: appRev.Name, Namespace: appRev.Namespace}, gotAppRev); err != nil {
   473  		if apierrors.IsNotFound(err) {
   474  			return h.Create(ctx, appRev)
   475  		}
   476  		return err
   477  	}
   478  	if apiequality.Semantic.DeepEqual(gotAppRev.Spec, appRev.Spec) &&
   479  		apiequality.Semantic.DeepEqual(gotAppRev.GetLabels(), appRev.GetLabels()) &&
   480  		apiequality.Semantic.DeepEqual(gotAppRev.GetAnnotations(), appRev.GetAnnotations()) {
   481  		return nil
   482  	}
   483  	appRev.ResourceVersion = gotAppRev.ResourceVersion
   484  
   485  	// Set compression types (if enabled)
   486  	if utilfeature.DefaultMutableFeatureGate.Enabled(features.GzipApplicationRevision) {
   487  		appRev.Spec.Compression.SetType(compression.Gzip)
   488  	}
   489  	if utilfeature.DefaultMutableFeatureGate.Enabled(features.ZstdApplicationRevision) {
   490  		appRev.Spec.Compression.SetType(compression.Zstd)
   491  	}
   492  
   493  	return h.Update(ctx, appRev)
   494  }
   495  
   496  // UpdateAppLatestRevisionStatus only call to update app's latest revision status after applying manifests successfully
   497  // otherwise it will override previous revision which is used during applying to do GC jobs
   498  func (h *AppHandler) UpdateAppLatestRevisionStatus(ctx context.Context, patchStatus statusPatcher) error {
   499  	if DisableAllApplicationRevision {
   500  		return nil
   501  	}
   502  	if !h.isNewRevision {
   503  		// skip update if app revision is not changed
   504  		return nil
   505  	}
   506  	if ctx, ok := ctx.(monitorContext.Context); ok {
   507  		subCtx := ctx.Fork("update-apprev-status", monitorContext.DurationMetric(func(v float64) {
   508  			metrics.AppReconcileStageDurationHistogram.WithLabelValues("update-apprev-status").Observe(v)
   509  		}))
   510  		defer subCtx.Commit("application revision status updated")
   511  	}
   512  	revName := h.currentAppRev.Name
   513  	revNum, _ := util.ExtractRevisionNum(revName, "-")
   514  	h.app.Status.LatestRevision = &common.Revision{
   515  		Name:         h.currentAppRev.Name,
   516  		Revision:     int64(revNum),
   517  		RevisionHash: h.currentRevHash,
   518  	}
   519  	if err := patchStatus(ctx, h.app, common.ApplicationRendering); err != nil {
   520  		klog.InfoS("Failed to update the latest appConfig revision to status", "application", klog.KObj(h.app),
   521  			"latest revision", revName, "err", err)
   522  		return err
   523  	}
   524  	klog.InfoS("Successfully update application latest revision status", "application", klog.KObj(h.app),
   525  		"latest revision", revName)
   526  	return nil
   527  }
   528  
   529  // UpdateApplicationRevisionStatus update application revision status
   530  func (h *AppHandler) UpdateApplicationRevisionStatus(ctx context.Context, appRev *v1beta1.ApplicationRevision, wfStatus *common.WorkflowStatus) {
   531  	if appRev == nil || DisableAllApplicationRevision {
   532  		return
   533  	}
   534  	appRev.Status.Succeeded = wfStatus.Phase == workflowv1alpha1.WorkflowStateSucceeded
   535  	appRev.Status.Workflow = wfStatus
   536  
   537  	// Versioned the context backend values.
   538  	if wfStatus.ContextBackend != nil {
   539  		var cm corev1.ConfigMap
   540  		if err := h.Client.Get(ctx, ktypes.NamespacedName{Namespace: wfStatus.ContextBackend.Namespace, Name: wfStatus.ContextBackend.Name}, &cm); err != nil {
   541  			klog.Error(err, "[UpdateApplicationRevisionStatus] failed to load the context values", "ApplicationRevision", appRev.Name)
   542  		}
   543  		appRev.Status.WorkflowContext = cm.Data
   544  	}
   545  
   546  	if err := h.Client.Status().Update(ctx, appRev); err != nil {
   547  		if logCtx, ok := ctx.(monitorContext.Context); ok {
   548  			logCtx.Error(err, "[UpdateApplicationRevisionStatus] failed to update application revision status", "ApplicationRevision", appRev.Name)
   549  		} else {
   550  			klog.Error(err, "[UpdateApplicationRevisionStatus] failed to update application revision status", "ApplicationRevision", appRev.Name)
   551  		}
   552  	}
   553  }
   554  
   555  // GetAppRevisions get application revisions by label
   556  func GetAppRevisions(ctx context.Context, cli client.Client, appName string, appNs string) ([]v1beta1.ApplicationRevision, error) {
   557  	appRevisionList := new(v1beta1.ApplicationRevisionList)
   558  	var err error
   559  	if cache.OptimizeListOp {
   560  		err = cli.List(ctx, appRevisionList, client.MatchingFields{cache.AppIndex: appNs + "/" + appName})
   561  	} else {
   562  		err = cli.List(ctx, appRevisionList, client.InNamespace(appNs), client.MatchingLabels{oam.LabelAppName: appName})
   563  	}
   564  	if err != nil {
   565  		return nil, err
   566  	}
   567  	return appRevisionList.Items, nil
   568  }
   569  
   570  // GetSortedAppRevisions get application revisions by revision number
   571  func GetSortedAppRevisions(ctx context.Context, cli client.Client, appName string, appNs string) ([]v1beta1.ApplicationRevision, error) {
   572  	revs, err := GetAppRevisions(ctx, cli, appName, appNs)
   573  	if err != nil {
   574  		return nil, err
   575  	}
   576  	sort.Slice(revs, func(i, j int) bool {
   577  		ir, _ := util.ExtractRevisionNum(revs[i].Name, "-")
   578  		ij, _ := util.ExtractRevisionNum(revs[j].Name, "-")
   579  		return ir < ij
   580  	})
   581  	return revs, nil
   582  }