github.com/argoproj/argo-cd@v1.8.7/controller/state.go (about)

     1  package controller
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"time"
     8  
     9  	"github.com/argoproj/gitops-engine/pkg/diff"
    10  	"github.com/argoproj/gitops-engine/pkg/health"
    11  	"github.com/argoproj/gitops-engine/pkg/sync"
    12  	hookutil "github.com/argoproj/gitops-engine/pkg/sync/hook"
    13  	"github.com/argoproj/gitops-engine/pkg/sync/ignore"
    14  	resourceutil "github.com/argoproj/gitops-engine/pkg/sync/resource"
    15  	kubeutil "github.com/argoproj/gitops-engine/pkg/utils/kube"
    16  	log "github.com/sirupsen/logrus"
    17  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    18  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    19  	"k8s.io/apimachinery/pkg/runtime/schema"
    20  	"k8s.io/apimachinery/pkg/types"
    21  	"k8s.io/client-go/tools/cache"
    22  
    23  	"github.com/argoproj/argo-cd/common"
    24  	statecache "github.com/argoproj/argo-cd/controller/cache"
    25  	"github.com/argoproj/argo-cd/controller/metrics"
    26  	"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
    27  	appv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
    28  	appclientset "github.com/argoproj/argo-cd/pkg/client/clientset/versioned"
    29  	"github.com/argoproj/argo-cd/reposerver/apiclient"
    30  	"github.com/argoproj/argo-cd/util/argo"
    31  	"github.com/argoproj/argo-cd/util/db"
    32  	"github.com/argoproj/argo-cd/util/gpg"
    33  	argohealth "github.com/argoproj/argo-cd/util/health"
    34  	"github.com/argoproj/argo-cd/util/io"
    35  	"github.com/argoproj/argo-cd/util/settings"
    36  	"github.com/argoproj/argo-cd/util/stats"
    37  )
    38  
    39  type resourceInfoProviderStub struct {
    40  }
    41  
    42  func (r *resourceInfoProviderStub) IsNamespaced(_ schema.GroupKind) (bool, error) {
    43  	return false, nil
    44  }
    45  
    46  type managedResource struct {
    47  	Target    *unstructured.Unstructured
    48  	Live      *unstructured.Unstructured
    49  	Diff      diff.DiffResult
    50  	Group     string
    51  	Version   string
    52  	Kind      string
    53  	Namespace string
    54  	Name      string
    55  	Hook      bool
    56  }
    57  
    58  func GetLiveObjsForApplicationHealth(resources []managedResource, statuses []appv1.ResourceStatus) ([]*appv1.ResourceStatus, []*unstructured.Unstructured) {
    59  	liveObjs := make([]*unstructured.Unstructured, 0)
    60  	resStatuses := make([]*appv1.ResourceStatus, 0)
    61  	for i, resource := range resources {
    62  		if resource.Target != nil && hookutil.Skip(resource.Target) {
    63  			continue
    64  		}
    65  
    66  		liveObjs = append(liveObjs, resource.Live)
    67  		resStatuses = append(resStatuses, &statuses[i])
    68  	}
    69  	return resStatuses, liveObjs
    70  }
    71  
    72  // AppStateManager defines methods which allow to compare application spec and actual application state.
    73  type AppStateManager interface {
    74  	CompareAppState(app *v1alpha1.Application, project *appv1.AppProject, revision string, source v1alpha1.ApplicationSource, noCache bool, localObjects []string) *comparisonResult
    75  	SyncAppState(app *v1alpha1.Application, state *v1alpha1.OperationState)
    76  }
    77  
    78  type comparisonResult struct {
    79  	syncStatus           *v1alpha1.SyncStatus
    80  	healthStatus         *v1alpha1.HealthStatus
    81  	resources            []v1alpha1.ResourceStatus
    82  	managedResources     []managedResource
    83  	reconciliationResult sync.ReconciliationResult
    84  	diffNormalizer       diff.Normalizer
    85  	appSourceType        v1alpha1.ApplicationSourceType
    86  	// timings maps phases of comparison to the duration it took to complete (for statistical purposes)
    87  	timings map[string]time.Duration
    88  }
    89  
    90  func (res *comparisonResult) GetSyncStatus() *v1alpha1.SyncStatus {
    91  	return res.syncStatus
    92  }
    93  
    94  func (res *comparisonResult) GetHealthStatus() *v1alpha1.HealthStatus {
    95  	return res.healthStatus
    96  }
    97  
    98  // appStateManager allows to compare applications to git
    99  type appStateManager struct {
   100  	metricsServer  *metrics.MetricsServer
   101  	db             db.ArgoDB
   102  	settingsMgr    *settings.SettingsManager
   103  	appclientset   appclientset.Interface
   104  	projInformer   cache.SharedIndexInformer
   105  	kubectl        kubeutil.Kubectl
   106  	repoClientset  apiclient.Clientset
   107  	liveStateCache statecache.LiveStateCache
   108  	namespace      string
   109  }
   110  
   111  func (m *appStateManager) getRepoObjs(app *v1alpha1.Application, source v1alpha1.ApplicationSource, appLabelKey, revision string, noCache, verifySignature bool) ([]*unstructured.Unstructured, *apiclient.ManifestResponse, error) {
   112  	ts := stats.NewTimingStats()
   113  	helmRepos, err := m.db.ListHelmRepositories(context.Background())
   114  	if err != nil {
   115  		return nil, nil, err
   116  	}
   117  	ts.AddCheckpoint("helm_ms")
   118  	repo, err := m.db.GetRepository(context.Background(), source.RepoURL)
   119  	if err != nil {
   120  		return nil, nil, err
   121  	}
   122  	ts.AddCheckpoint("repo_ms")
   123  	conn, repoClient, err := m.repoClientset.NewRepoServerClient()
   124  	if err != nil {
   125  		return nil, nil, err
   126  	}
   127  	defer io.Close(conn)
   128  
   129  	if revision == "" {
   130  		revision = source.TargetRevision
   131  	}
   132  
   133  	plugins, err := m.settingsMgr.GetConfigManagementPlugins()
   134  	if err != nil {
   135  		return nil, nil, err
   136  	}
   137  	ts.AddCheckpoint("plugins_ms")
   138  	tools := make([]*appv1.ConfigManagementPlugin, len(plugins))
   139  	for i := range plugins {
   140  		tools[i] = &plugins[i]
   141  	}
   142  
   143  	kustomizeSettings, err := m.settingsMgr.GetKustomizeSettings()
   144  	if err != nil {
   145  		return nil, nil, err
   146  	}
   147  	kustomizeOptions, err := kustomizeSettings.GetOptions(app.Spec.Source)
   148  	if err != nil {
   149  		return nil, nil, err
   150  	}
   151  	ts.AddCheckpoint("build_options_ms")
   152  	serverVersion, apiGroups, err := m.liveStateCache.GetVersionsInfo(app.Spec.Destination.Server)
   153  	if err != nil {
   154  		return nil, nil, err
   155  	}
   156  	ts.AddCheckpoint("version_ms")
   157  	manifestInfo, err := repoClient.GenerateManifest(context.Background(), &apiclient.ManifestRequest{
   158  		Repo:              repo,
   159  		Repos:             helmRepos,
   160  		Revision:          revision,
   161  		NoCache:           noCache,
   162  		AppLabelKey:       appLabelKey,
   163  		AppLabelValue:     app.Name,
   164  		Namespace:         app.Spec.Destination.Namespace,
   165  		ApplicationSource: &source,
   166  		Plugins:           tools,
   167  		KustomizeOptions:  kustomizeOptions,
   168  		KubeVersion:       serverVersion,
   169  		ApiVersions:       argo.APIGroupsToVersions(apiGroups),
   170  		VerifySignature:   verifySignature,
   171  	})
   172  	if err != nil {
   173  		return nil, nil, err
   174  	}
   175  	targetObjs, err := unmarshalManifests(manifestInfo.Manifests)
   176  
   177  	if err != nil {
   178  		return nil, nil, err
   179  	}
   180  
   181  	ts.AddCheckpoint("unmarshal_ms")
   182  	logCtx := log.WithField("application", app.Name)
   183  	for k, v := range ts.Timings() {
   184  		logCtx = logCtx.WithField(k, v.Milliseconds())
   185  	}
   186  	logCtx = logCtx.WithField("time_ms", time.Since(ts.StartTime).Milliseconds())
   187  	logCtx.Info("getRepoObjs stats")
   188  	return targetObjs, manifestInfo, nil
   189  }
   190  
   191  func unmarshalManifests(manifests []string) ([]*unstructured.Unstructured, error) {
   192  	targetObjs := make([]*unstructured.Unstructured, 0)
   193  	for _, manifest := range manifests {
   194  		obj, err := v1alpha1.UnmarshalToUnstructured(manifest)
   195  		if err != nil {
   196  			return nil, err
   197  		}
   198  		targetObjs = append(targetObjs, obj)
   199  	}
   200  	return targetObjs, nil
   201  }
   202  
   203  func DeduplicateTargetObjects(
   204  	namespace string,
   205  	objs []*unstructured.Unstructured,
   206  	infoProvider kubeutil.ResourceInfoProvider,
   207  ) ([]*unstructured.Unstructured, []v1alpha1.ApplicationCondition, error) {
   208  
   209  	targetByKey := make(map[kubeutil.ResourceKey][]*unstructured.Unstructured)
   210  	for i := range objs {
   211  		obj := objs[i]
   212  		if obj == nil {
   213  			continue
   214  		}
   215  		isNamespaced := kubeutil.IsNamespacedOrUnknown(infoProvider, obj.GroupVersionKind().GroupKind())
   216  		if !isNamespaced {
   217  			obj.SetNamespace("")
   218  		} else if obj.GetNamespace() == "" {
   219  			obj.SetNamespace(namespace)
   220  		}
   221  		key := kubeutil.GetResourceKey(obj)
   222  		if key.Name == "" && obj.GetGenerateName() != "" {
   223  			key.Name = fmt.Sprintf("%s%d", obj.GetGenerateName(), i)
   224  		}
   225  		targetByKey[key] = append(targetByKey[key], obj)
   226  	}
   227  	conditions := make([]v1alpha1.ApplicationCondition, 0)
   228  	result := make([]*unstructured.Unstructured, 0)
   229  	for key, targets := range targetByKey {
   230  		if len(targets) > 1 {
   231  			now := metav1.Now()
   232  			conditions = append(conditions, appv1.ApplicationCondition{
   233  				Type:               appv1.ApplicationConditionRepeatedResourceWarning,
   234  				Message:            fmt.Sprintf("Resource %s appeared %d times among application resources.", key.String(), len(targets)),
   235  				LastTransitionTime: &now,
   236  			})
   237  		}
   238  		result = append(result, targets[len(targets)-1])
   239  	}
   240  
   241  	return result, conditions, nil
   242  }
   243  
   244  func (m *appStateManager) getComparisonSettings(app *appv1.Application) (string, map[string]v1alpha1.ResourceOverride, diff.Normalizer, *settings.ResourcesFilter, error) {
   245  	resourceOverrides, err := m.settingsMgr.GetResourceOverrides()
   246  	if err != nil {
   247  		return "", nil, nil, nil, err
   248  	}
   249  	appLabelKey, err := m.settingsMgr.GetAppInstanceLabelKey()
   250  	if err != nil {
   251  		return "", nil, nil, nil, err
   252  	}
   253  	diffNormalizer, err := argo.NewDiffNormalizer(app.Spec.IgnoreDifferences, resourceOverrides)
   254  	if err != nil {
   255  		return "", nil, nil, nil, err
   256  	}
   257  	resFilter, err := m.settingsMgr.GetResourcesFilter()
   258  	if err != nil {
   259  		return "", nil, nil, nil, err
   260  	}
   261  	return appLabelKey, resourceOverrides, diffNormalizer, resFilter, nil
   262  }
   263  
   264  // verifyGnuPGSignature verifies the result of a GnuPG operation for a given git
   265  // revision.
   266  func verifyGnuPGSignature(revision string, project *appv1.AppProject, manifestInfo *apiclient.ManifestResponse) []appv1.ApplicationCondition {
   267  	now := metav1.Now()
   268  	conditions := make([]appv1.ApplicationCondition, 0)
   269  	// We need to have some data in the verification result to parse, otherwise there was no signature
   270  	if manifestInfo.VerifyResult != "" {
   271  		verifyResult, err := gpg.ParseGitCommitVerification(manifestInfo.VerifyResult)
   272  		if err != nil {
   273  			conditions = append(conditions, v1alpha1.ApplicationCondition{Type: v1alpha1.ApplicationConditionComparisonError, Message: err.Error(), LastTransitionTime: &now})
   274  			log.Errorf("Error while verifying git commit for revision %s: %s", revision, err.Error())
   275  		} else {
   276  			switch verifyResult.Result {
   277  			case gpg.VerifyResultGood:
   278  				// This is the only case we allow to sync to, but we need to make sure signing key is allowed
   279  				validKey := false
   280  				for _, k := range project.Spec.SignatureKeys {
   281  					if gpg.KeyID(k.KeyID) == gpg.KeyID(verifyResult.KeyID) && gpg.KeyID(k.KeyID) != "" {
   282  						validKey = true
   283  						break
   284  					}
   285  				}
   286  				if !validKey {
   287  					msg := fmt.Sprintf("Found good signature made with %s key %s, but this key is not allowed in AppProject",
   288  						verifyResult.Cipher, verifyResult.KeyID)
   289  					conditions = append(conditions, v1alpha1.ApplicationCondition{Type: v1alpha1.ApplicationConditionComparisonError, Message: msg, LastTransitionTime: &now})
   290  				}
   291  			case gpg.VerifyResultInvalid:
   292  				msg := fmt.Sprintf("Found signature made with %s key %s, but verification result was invalid: '%s'",
   293  					verifyResult.Cipher, verifyResult.KeyID, verifyResult.Message)
   294  				conditions = append(conditions, v1alpha1.ApplicationCondition{Type: v1alpha1.ApplicationConditionComparisonError, Message: msg, LastTransitionTime: &now})
   295  			default:
   296  				msg := fmt.Sprintf("Could not verify commit signature on revision '%s', check logs for more information.", revision)
   297  				conditions = append(conditions, v1alpha1.ApplicationCondition{Type: v1alpha1.ApplicationConditionComparisonError, Message: msg, LastTransitionTime: &now})
   298  			}
   299  		}
   300  	} else {
   301  		msg := fmt.Sprintf("Target revision %s in Git is not signed, but a signature is required", revision)
   302  		conditions = append(conditions, v1alpha1.ApplicationCondition{Type: v1alpha1.ApplicationConditionComparisonError, Message: msg, LastTransitionTime: &now})
   303  	}
   304  
   305  	return conditions
   306  }
   307  
   308  // CompareAppState compares application git state to the live app state, using the specified
   309  // revision and supplied source. If revision or overrides are empty, then compares against
   310  // revision and overrides in the app spec.
   311  func (m *appStateManager) CompareAppState(app *v1alpha1.Application, project *appv1.AppProject, revision string, source v1alpha1.ApplicationSource, noCache bool, localManifests []string) *comparisonResult {
   312  	ts := stats.NewTimingStats()
   313  	appLabelKey, resourceOverrides, diffNormalizer, resFilter, err := m.getComparisonSettings(app)
   314  	ts.AddCheckpoint("settings_ms")
   315  
   316  	// return unknown comparison result if basic comparison settings cannot be loaded
   317  	if err != nil {
   318  		return &comparisonResult{
   319  			syncStatus: &v1alpha1.SyncStatus{
   320  				ComparedTo: appv1.ComparedTo{Source: source, Destination: app.Spec.Destination},
   321  				Status:     appv1.SyncStatusCodeUnknown,
   322  			},
   323  			healthStatus: &appv1.HealthStatus{Status: health.HealthStatusUnknown},
   324  		}
   325  	}
   326  
   327  	// When signature keys are defined in the project spec, we need to verify the signature on the Git revision
   328  	verifySignature := false
   329  	if project.Spec.SignatureKeys != nil && len(project.Spec.SignatureKeys) > 0 && gpg.IsGPGEnabled() {
   330  		verifySignature = true
   331  	}
   332  
   333  	// do best effort loading live and target state to present as much information about app state as possible
   334  	failedToLoadObjs := false
   335  	conditions := make([]v1alpha1.ApplicationCondition, 0)
   336  
   337  	logCtx := log.WithField("application", app.Name)
   338  	logCtx.Infof("Comparing app state (cluster: %s, namespace: %s)", app.Spec.Destination.Server, app.Spec.Destination.Namespace)
   339  
   340  	var targetObjs []*unstructured.Unstructured
   341  	var manifestInfo *apiclient.ManifestResponse
   342  	now := metav1.Now()
   343  
   344  	if len(localManifests) == 0 {
   345  		targetObjs, manifestInfo, err = m.getRepoObjs(app, source, appLabelKey, revision, noCache, verifySignature)
   346  		if err != nil {
   347  			targetObjs = make([]*unstructured.Unstructured, 0)
   348  			conditions = append(conditions, v1alpha1.ApplicationCondition{Type: v1alpha1.ApplicationConditionComparisonError, Message: err.Error(), LastTransitionTime: &now})
   349  			failedToLoadObjs = true
   350  		}
   351  	} else {
   352  		// Prevent applying local manifests for now when signature verification is enabled
   353  		// This is also enforced on API level, but as a last resort, we also enforce it here
   354  		if gpg.IsGPGEnabled() && verifySignature {
   355  			msg := "Cannot use local manifests when signature verification is required"
   356  			targetObjs = make([]*unstructured.Unstructured, 0)
   357  			conditions = append(conditions, v1alpha1.ApplicationCondition{Type: v1alpha1.ApplicationConditionComparisonError, Message: msg, LastTransitionTime: &now})
   358  			failedToLoadObjs = true
   359  		} else {
   360  			targetObjs, err = unmarshalManifests(localManifests)
   361  			if err != nil {
   362  				targetObjs = make([]*unstructured.Unstructured, 0)
   363  				conditions = append(conditions, v1alpha1.ApplicationCondition{Type: v1alpha1.ApplicationConditionComparisonError, Message: err.Error(), LastTransitionTime: &now})
   364  				failedToLoadObjs = true
   365  			}
   366  		}
   367  		manifestInfo = nil
   368  	}
   369  	ts.AddCheckpoint("git_ms")
   370  
   371  	var infoProvider kubeutil.ResourceInfoProvider
   372  	infoProvider, err = m.liveStateCache.GetClusterCache(app.Spec.Destination.Server)
   373  	if err != nil {
   374  		infoProvider = &resourceInfoProviderStub{}
   375  	}
   376  	targetObjs, dedupConditions, err := DeduplicateTargetObjects(app.Spec.Destination.Namespace, targetObjs, infoProvider)
   377  	if err != nil {
   378  		conditions = append(conditions, v1alpha1.ApplicationCondition{Type: v1alpha1.ApplicationConditionComparisonError, Message: err.Error(), LastTransitionTime: &now})
   379  	}
   380  	conditions = append(conditions, dedupConditions...)
   381  	for i := len(targetObjs) - 1; i >= 0; i-- {
   382  		targetObj := targetObjs[i]
   383  		gvk := targetObj.GroupVersionKind()
   384  		if resFilter.IsExcludedResource(gvk.Group, gvk.Kind, app.Spec.Destination.Server) {
   385  			targetObjs = append(targetObjs[:i], targetObjs[i+1:]...)
   386  			conditions = append(conditions, v1alpha1.ApplicationCondition{
   387  				Type:               v1alpha1.ApplicationConditionExcludedResourceWarning,
   388  				Message:            fmt.Sprintf("Resource %s/%s %s is excluded in the settings", gvk.Group, gvk.Kind, targetObj.GetName()),
   389  				LastTransitionTime: &now,
   390  			})
   391  		}
   392  	}
   393  	ts.AddCheckpoint("dedup_ms")
   394  
   395  	liveObjByKey, err := m.liveStateCache.GetManagedLiveObjs(app, targetObjs)
   396  	if err != nil {
   397  		liveObjByKey = make(map[kubeutil.ResourceKey]*unstructured.Unstructured)
   398  		conditions = append(conditions, v1alpha1.ApplicationCondition{Type: v1alpha1.ApplicationConditionComparisonError, Message: err.Error(), LastTransitionTime: &now})
   399  		failedToLoadObjs = true
   400  	}
   401  	logCtx.Debugf("Retrieved lived manifests")
   402  
   403  	// filter out all resources which are not permitted in the application project
   404  	for k, v := range liveObjByKey {
   405  		if !project.IsLiveResourcePermitted(v, app.Spec.Destination.Server) {
   406  			delete(liveObjByKey, k)
   407  		}
   408  	}
   409  
   410  	for _, liveObj := range liveObjByKey {
   411  		if liveObj != nil {
   412  			appInstanceName := kubeutil.GetAppInstanceLabel(liveObj, appLabelKey)
   413  			if appInstanceName != "" && appInstanceName != app.Name {
   414  				conditions = append(conditions, v1alpha1.ApplicationCondition{
   415  					Type:               v1alpha1.ApplicationConditionSharedResourceWarning,
   416  					Message:            fmt.Sprintf("%s/%s is part of applications %s and %s", liveObj.GetKind(), liveObj.GetName(), app.Name, appInstanceName),
   417  					LastTransitionTime: &now,
   418  				})
   419  			}
   420  		}
   421  	}
   422  
   423  	reconciliation := sync.Reconcile(targetObjs, liveObjByKey, app.Spec.Destination.Namespace, infoProvider)
   424  	ts.AddCheckpoint("live_ms")
   425  
   426  	compareOptions, err := m.settingsMgr.GetResourceCompareOptions()
   427  	if err != nil {
   428  		log.Warnf("Could not get compare options from ConfigMap (assuming defaults): %v", err)
   429  		compareOptions = settings.GetDefaultDiffOptions()
   430  	}
   431  
   432  	logCtx.Debugf("built managed objects list")
   433  	// Do the actual comparison
   434  	diffResults, err := diff.DiffArray(
   435  		reconciliation.Target, reconciliation.Live,
   436  		diff.WithNormalizer(diffNormalizer),
   437  		diff.IgnoreAggregatedRoles(compareOptions.IgnoreAggregatedRoles))
   438  	if err != nil {
   439  		diffResults = &diff.DiffResultList{}
   440  		failedToLoadObjs = true
   441  		conditions = append(conditions, v1alpha1.ApplicationCondition{Type: v1alpha1.ApplicationConditionComparisonError, Message: err.Error(), LastTransitionTime: &now})
   442  	}
   443  	ts.AddCheckpoint("diff_ms")
   444  
   445  	syncCode := v1alpha1.SyncStatusCodeSynced
   446  	managedResources := make([]managedResource, len(reconciliation.Target))
   447  	resourceSummaries := make([]v1alpha1.ResourceStatus, len(reconciliation.Target))
   448  	for i, targetObj := range reconciliation.Target {
   449  		liveObj := reconciliation.Live[i]
   450  		obj := liveObj
   451  		if obj == nil {
   452  			obj = targetObj
   453  		}
   454  		if obj == nil {
   455  			continue
   456  		}
   457  		gvk := obj.GroupVersionKind()
   458  
   459  		resState := v1alpha1.ResourceStatus{
   460  			Namespace:       obj.GetNamespace(),
   461  			Name:            obj.GetName(),
   462  			Kind:            gvk.Kind,
   463  			Version:         gvk.Version,
   464  			Group:           gvk.Group,
   465  			Hook:            hookutil.IsHook(obj),
   466  			RequiresPruning: targetObj == nil && liveObj != nil,
   467  		}
   468  
   469  		var diffResult diff.DiffResult
   470  		if i < len(diffResults.Diffs) {
   471  			diffResult = diffResults.Diffs[i]
   472  		} else {
   473  			diffResult = diff.DiffResult{Modified: false, NormalizedLive: []byte("{}"), PredictedLive: []byte("{}")}
   474  		}
   475  		if resState.Hook || ignore.Ignore(obj) || (targetObj != nil && hookutil.Skip(targetObj)) {
   476  			// For resource hooks or skipped resources, don't store sync status, and do not affect overall sync status
   477  		} else if diffResult.Modified || targetObj == nil || liveObj == nil {
   478  			// Set resource state to OutOfSync since one of the following is true:
   479  			// * target and live resource are different
   480  			// * target resource not defined and live resource is extra
   481  			// * target resource present but live resource is missing
   482  			resState.Status = v1alpha1.SyncStatusCodeOutOfSync
   483  			// we ignore the status if the obj needs pruning AND we have the annotation
   484  			needsPruning := targetObj == nil && liveObj != nil
   485  			if !(needsPruning && resourceutil.HasAnnotationOption(obj, common.AnnotationCompareOptions, "IgnoreExtraneous")) {
   486  				syncCode = v1alpha1.SyncStatusCodeOutOfSync
   487  			}
   488  		} else {
   489  			resState.Status = v1alpha1.SyncStatusCodeSynced
   490  		}
   491  		// set unknown status to all resource that are not permitted in the app project
   492  		isNamespaced, err := m.liveStateCache.IsNamespaced(app.Spec.Destination.Server, gvk.GroupKind())
   493  		if !project.IsGroupKindPermitted(gvk.GroupKind(), isNamespaced && err == nil) {
   494  			resState.Status = v1alpha1.SyncStatusCodeUnknown
   495  		}
   496  
   497  		if isNamespaced && obj.GetNamespace() == "" {
   498  			conditions = append(conditions, appv1.ApplicationCondition{Type: v1alpha1.ApplicationConditionInvalidSpecError, Message: fmt.Sprintf("Namespace for %s %s is missing.", obj.GetName(), gvk.String()), LastTransitionTime: &now})
   499  		}
   500  
   501  		// we can't say anything about the status if we were unable to get the target objects
   502  		if failedToLoadObjs {
   503  			resState.Status = v1alpha1.SyncStatusCodeUnknown
   504  		}
   505  		managedResources[i] = managedResource{
   506  			Name:      resState.Name,
   507  			Namespace: resState.Namespace,
   508  			Group:     resState.Group,
   509  			Kind:      resState.Kind,
   510  			Version:   resState.Version,
   511  			Live:      liveObj,
   512  			Target:    targetObj,
   513  			Diff:      diffResult,
   514  			Hook:      resState.Hook,
   515  		}
   516  		resourceSummaries[i] = resState
   517  	}
   518  
   519  	if failedToLoadObjs {
   520  		syncCode = v1alpha1.SyncStatusCodeUnknown
   521  	}
   522  	syncStatus := v1alpha1.SyncStatus{
   523  		ComparedTo: appv1.ComparedTo{
   524  			Source:      source,
   525  			Destination: app.Spec.Destination,
   526  		},
   527  		Status: syncCode,
   528  	}
   529  	if manifestInfo != nil {
   530  		syncStatus.Revision = manifestInfo.Revision
   531  	}
   532  	ts.AddCheckpoint("sync_ms")
   533  
   534  	resSumForAppHealth, liveObjsForAppHealth := GetLiveObjsForApplicationHealth(managedResources, resourceSummaries)
   535  	healthStatus, err := argohealth.SetApplicationHealth(resSumForAppHealth, liveObjsForAppHealth, resourceOverrides, func(obj *unstructured.Unstructured) bool {
   536  		return !isSelfReferencedApp(app, kubeutil.GetObjectRef(obj))
   537  	})
   538  
   539  	if err != nil {
   540  		conditions = append(conditions, appv1.ApplicationCondition{Type: v1alpha1.ApplicationConditionComparisonError, Message: err.Error(), LastTransitionTime: &now})
   541  	}
   542  
   543  	// Git has already performed the signature verification via its GPG interface, and the result is available
   544  	// in the manifest info received from the repository server. We now need to form our opinion about the result
   545  	// and stop processing if we do not agree about the outcome.
   546  	if gpg.IsGPGEnabled() && verifySignature && manifestInfo != nil {
   547  		conditions = append(conditions, verifyGnuPGSignature(revision, project, manifestInfo)...)
   548  	}
   549  
   550  	compRes := comparisonResult{
   551  		syncStatus:           &syncStatus,
   552  		healthStatus:         healthStatus,
   553  		resources:            resourceSummaries,
   554  		managedResources:     managedResources,
   555  		reconciliationResult: reconciliation,
   556  		diffNormalizer:       diffNormalizer,
   557  	}
   558  	if manifestInfo != nil {
   559  		compRes.appSourceType = v1alpha1.ApplicationSourceType(manifestInfo.SourceType)
   560  	}
   561  	app.Status.SetConditions(conditions, map[appv1.ApplicationConditionType]bool{
   562  		appv1.ApplicationConditionComparisonError:         true,
   563  		appv1.ApplicationConditionSharedResourceWarning:   true,
   564  		appv1.ApplicationConditionRepeatedResourceWarning: true,
   565  		appv1.ApplicationConditionExcludedResourceWarning: true,
   566  	})
   567  	ts.AddCheckpoint("health_ms")
   568  	compRes.timings = ts.Timings()
   569  	return &compRes
   570  }
   571  
   572  func (m *appStateManager) persistRevisionHistory(app *v1alpha1.Application, revision string, source v1alpha1.ApplicationSource, startedAt metav1.Time) error {
   573  	var nextID int64
   574  	if len(app.Status.History) > 0 {
   575  		nextID = app.Status.History.LastRevisionHistory().ID + 1
   576  	}
   577  	app.Status.History = append(app.Status.History, v1alpha1.RevisionHistory{
   578  		Revision:        revision,
   579  		DeployedAt:      metav1.NewTime(time.Now().UTC()),
   580  		DeployStartedAt: &startedAt,
   581  		ID:              nextID,
   582  		Source:          source,
   583  	})
   584  
   585  	app.Status.History = app.Status.History.Trunc(app.Spec.GetRevisionHistoryLimit())
   586  
   587  	patch, err := json.Marshal(map[string]map[string][]v1alpha1.RevisionHistory{
   588  		"status": {
   589  			"history": app.Status.History,
   590  		},
   591  	})
   592  	if err != nil {
   593  		return err
   594  	}
   595  	_, err = m.appclientset.ArgoprojV1alpha1().Applications(m.namespace).Patch(context.Background(), app.Name, types.MergePatchType, patch, metav1.PatchOptions{})
   596  	return err
   597  }
   598  
   599  // NewAppStateManager creates new instance of AppStateManager
   600  func NewAppStateManager(
   601  	db db.ArgoDB,
   602  	appclientset appclientset.Interface,
   603  	repoClientset apiclient.Clientset,
   604  	namespace string,
   605  	kubectl kubeutil.Kubectl,
   606  	settingsMgr *settings.SettingsManager,
   607  	liveStateCache statecache.LiveStateCache,
   608  	projInformer cache.SharedIndexInformer,
   609  	metricsServer *metrics.MetricsServer,
   610  ) AppStateManager {
   611  	return &appStateManager{
   612  		liveStateCache: liveStateCache,
   613  		db:             db,
   614  		appclientset:   appclientset,
   615  		kubectl:        kubectl,
   616  		repoClientset:  repoClientset,
   617  		namespace:      namespace,
   618  		settingsMgr:    settingsMgr,
   619  		projInformer:   projInformer,
   620  		metricsServer:  metricsServer,
   621  	}
   622  }