2018-05-11 18:50:32 +00:00
package controller
import (
"context"
"encoding/json"
2023-11-02 15:51:16 +00:00
"errors"
2018-05-11 18:50:32 +00:00
"fmt"
2021-01-28 00:13:29 +00:00
"reflect"
2025-06-06 11:27:02 +00:00
"slices"
2022-08-10 09:39:10 +00:00
"strings"
2023-11-02 15:51:16 +00:00
goSync "sync"
2018-05-11 18:50:32 +00:00
"time"
2026-02-12 14:29:40 +00:00
synccommon "github.com/argoproj/argo-cd/gitops-engine/pkg/sync/common"
2025-01-03 16:10:00 +00:00
corev1 "k8s.io/api/core/v1"
2023-11-02 15:51:16 +00:00
2026-02-12 14:29:40 +00:00
"github.com/argoproj/argo-cd/gitops-engine/pkg/diff"
"github.com/argoproj/argo-cd/gitops-engine/pkg/health"
"github.com/argoproj/argo-cd/gitops-engine/pkg/sync"
hookutil "github.com/argoproj/argo-cd/gitops-engine/pkg/sync/hook"
"github.com/argoproj/argo-cd/gitops-engine/pkg/sync/ignore"
resourceutil "github.com/argoproj/argo-cd/gitops-engine/pkg/sync/resource"
"github.com/argoproj/argo-cd/gitops-engine/pkg/sync/syncwaves"
kubeutil "github.com/argoproj/argo-cd/gitops-engine/pkg/utils/kube"
2018-11-30 18:32:31 +00:00
log "github.com/sirupsen/logrus"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2019-08-19 15:14:48 +00:00
"k8s.io/apimachinery/pkg/runtime/schema"
2018-11-30 18:32:31 +00:00
"k8s.io/apimachinery/pkg/types"
2018-07-07 07:54:06 +00:00
2025-01-10 21:14:00 +00:00
"github.com/argoproj/argo-cd/v3/common"
statecache "github.com/argoproj/argo-cd/v3/controller/cache"
"github.com/argoproj/argo-cd/v3/controller/metrics"
"github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
appclientset "github.com/argoproj/argo-cd/v3/pkg/client/clientset/versioned"
"github.com/argoproj/argo-cd/v3/reposerver/apiclient"
2025-05-20 19:48:09 +00:00
applog "github.com/argoproj/argo-cd/v3/util/app/log"
2025-01-10 21:14:00 +00:00
"github.com/argoproj/argo-cd/v3/util/app/path"
"github.com/argoproj/argo-cd/v3/util/argo"
argodiff "github.com/argoproj/argo-cd/v3/util/argo/diff"
"github.com/argoproj/argo-cd/v3/util/argo/normalizers"
appstatecache "github.com/argoproj/argo-cd/v3/util/cache/appstate"
"github.com/argoproj/argo-cd/v3/util/db"
2025-11-03 21:48:14 +00:00
"github.com/argoproj/argo-cd/v3/util/env"
2025-01-10 21:14:00 +00:00
"github.com/argoproj/argo-cd/v3/util/gpg"
2025-05-21 16:25:32 +00:00
utilio "github.com/argoproj/argo-cd/v3/util/io"
2025-01-10 21:14:00 +00:00
"github.com/argoproj/argo-cd/v3/util/settings"
"github.com/argoproj/argo-cd/v3/util/stats"
2018-05-11 18:50:32 +00:00
)
2025-11-03 21:48:14 +00:00
var (
ErrCompareStateRepo = errors . New ( "failed to get repo objects" )
processManifestGeneratePathsEnabled = env . ParseBoolFromEnv ( "ARGOCD_APPLICATIONSET_CONTROLLER_PROCESS_MANIFEST_GENERATE_PATHS" , true )
)
2023-11-02 15:51:16 +00:00
2024-06-11 15:41:55 +00:00
type resourceInfoProviderStub struct { }
2020-05-15 17:01:18 +00:00
func ( r * resourceInfoProviderStub ) IsNamespaced ( _ schema . GroupKind ) ( bool , error ) {
return false , nil
}
2018-12-03 18:27:43 +00:00
type managedResource struct {
2021-01-28 00:13:29 +00:00
Target * unstructured . Unstructured
Live * unstructured . Unstructured
Diff diff . DiffResult
Group string
Version string
Kind string
Namespace string
Name string
Hook bool
ResourceVersion string
2018-11-28 21:38:02 +00:00
}
2018-05-11 18:50:32 +00:00
// AppStateManager defines methods which allow to compare application spec and actual application state.
type AppStateManager interface {
2025-11-03 21:48:14 +00:00
CompareAppState ( app * v1alpha1 . Application , project * v1alpha1 . AppProject , revisions [ ] string , sources [ ] v1alpha1 . ApplicationSource , noCache , noRevisionCache bool , localObjects [ ] string , hasMultipleSources bool ) ( * comparisonResult , error )
2025-07-03 17:16:58 +00:00
SyncAppState ( app * v1alpha1 . Application , project * v1alpha1 . AppProject , state * v1alpha1 . OperationState )
2025-09-08 15:14:48 +00:00
GetRepoObjs ( ctx context . Context , app * v1alpha1 . Application , sources [ ] v1alpha1 . ApplicationSource , appLabelKey string , revisions [ ] string , noCache , noRevisionCache , verifySignature bool , proj * v1alpha1 . AppProject , sendRuntimeState bool ) ( [ ] * unstructured . Unstructured , [ ] * apiclient . ManifestResponse , bool , error )
2018-05-11 18:50:32 +00:00
}
2021-11-03 19:46:05 +00:00
// comparisonResult holds the state of an application after the reconciliation
2018-12-04 10:52:57 +00:00
type comparisonResult struct {
2020-05-15 17:01:18 +00:00
syncStatus * v1alpha1 . SyncStatus
2025-05-20 15:26:07 +00:00
healthStatus health . HealthStatusCode
2020-05-15 17:01:18 +00:00
resources [ ] v1alpha1 . ResourceStatus
managedResources [ ] managedResource
reconciliationResult sync . ReconciliationResult
2022-01-13 21:00:17 +00:00
diffConfig argodiff . DiffConfig
2020-05-15 17:01:18 +00:00
appSourceType v1alpha1 . ApplicationSourceType
2022-12-16 20:47:08 +00:00
// appSourceTypes stores the SourceType for each application source under sources field
appSourceTypes [ ] v1alpha1 . ApplicationSourceType
2020-03-16 18:51:59 +00:00
// timings maps phases of comparison to the duration it took to complete (for statistical purposes)
2023-12-18 16:40:23 +00:00
timings map [ string ] time . Duration
diffResultList * diff . DiffResultList
hasPostDeleteHooks bool
2025-12-05 20:27:03 +00:00
hasPreDeleteHooks bool
2025-07-14 14:36:05 +00:00
// revisionsMayHaveChanges indicates if there are any possibilities that the revisions contain changes
revisionsMayHaveChanges bool
2018-12-04 10:52:57 +00:00
}
2020-07-02 20:47:56 +00:00
func ( res * comparisonResult ) GetSyncStatus ( ) * v1alpha1 . SyncStatus {
return res . syncStatus
}
2025-05-20 15:26:07 +00:00
func ( res * comparisonResult ) GetHealthStatus ( ) health . HealthStatusCode {
2020-07-02 20:47:56 +00:00
return res . healthStatus
}
2018-12-04 10:52:57 +00:00
// appStateManager allows to compare applications to git
2018-09-20 16:48:54 +00:00
type appStateManager struct {
2022-08-17 21:03:24 +00:00
metricsServer * metrics . MetricsServer
db db . ArgoDB
settingsMgr * settings . SettingsManager
appclientset appclientset . Interface
kubectl kubeutil . Kubectl
2025-02-07 17:26:03 +00:00
onKubectlRun kubeutil . OnKubectlRunFunc
2022-08-17 21:03:24 +00:00
repoClientset apiclient . Clientset
liveStateCache statecache . LiveStateCache
cache * appstatecache . Cache
namespace string
statusRefreshTimeout time . Duration
resourceTracking argo . ResourceTracking
persistResourceHealth bool
2023-11-02 15:51:16 +00:00
repoErrorCache goSync . Map
repoErrorGracePeriod time . Duration
2023-12-18 20:37:13 +00:00
serverSideDiff bool
2024-04-26 09:24:02 +00:00
ignoreNormalizerOpts normalizers . IgnoreNormalizerOpts
2018-05-11 18:50:32 +00:00
}
2023-12-18 16:40:23 +00:00
// GetRepoObjs will generate the manifests for the given application delegating the
2023-11-29 16:08:29 +00:00
// task to the repo-server. It returns the list of generated manifests as unstructured
// objects. It also returns the full response from all calls to the repo server as the
// second argument.
2025-09-08 15:14:48 +00:00
func ( m * appStateManager ) GetRepoObjs ( ctx context . Context , app * v1alpha1 . Application , sources [ ] v1alpha1 . ApplicationSource , appLabelKey string , revisions [ ] string , noCache , noRevisionCache , verifySignature bool , proj * v1alpha1 . AppProject , sendRuntimeState bool ) ( [ ] * unstructured . Unstructured , [ ] * apiclient . ManifestResponse , bool , error ) {
2020-03-16 18:51:59 +00:00
ts := stats . NewTimingStats ( )
2025-09-08 15:14:48 +00:00
helmRepos , err := m . db . ListHelmRepositories ( ctx )
2018-12-03 23:15:37 +00:00
if err != nil {
2024-09-06 15:40:48 +00:00
return nil , nil , false , fmt . Errorf ( "failed to list Helm repositories: %w" , err )
2018-12-03 23:15:37 +00:00
}
2021-04-24 00:32:28 +00:00
permittedHelmRepos , err := argo . GetPermittedRepos ( proj , helmRepos )
if err != nil {
2024-09-06 15:40:48 +00:00
return nil , nil , false , fmt . Errorf ( "failed to get permitted Helm repositories for project %q: %w" , proj . Name , err )
2021-04-24 00:32:28 +00:00
}
2022-12-16 20:47:08 +00:00
2025-09-08 15:14:48 +00:00
ociRepos , err := m . db . ListOCIRepositories ( ctx )
2025-06-06 11:27:02 +00:00
if err != nil {
return nil , nil , false , fmt . Errorf ( "failed to list OCI repositories: %w" , err )
}
permittedOCIRepos , err := argo . GetPermittedRepos ( proj , ociRepos )
if err != nil {
return nil , nil , false , fmt . Errorf ( "failed to get permitted OCI repositories for project %q: %w" , proj . Name , err )
}
2020-03-16 18:51:59 +00:00
ts . AddCheckpoint ( "repo_ms" )
2025-09-08 15:14:48 +00:00
helmRepositoryCredentials , err := m . db . GetAllHelmRepositoryCredentials ( ctx )
2021-04-19 20:17:26 +00:00
if err != nil {
2024-09-06 15:40:48 +00:00
return nil , nil , false , fmt . Errorf ( "failed to get Helm credentials: %w" , err )
2021-04-19 20:17:26 +00:00
}
2021-04-24 00:32:28 +00:00
permittedHelmCredentials , err := argo . GetPermittedReposCredentials ( proj , helmRepositoryCredentials )
if err != nil {
2024-09-06 15:40:48 +00:00
return nil , nil , false , fmt . Errorf ( "failed to get permitted Helm credentials for project %q: %w" , proj . Name , err )
2021-04-24 00:32:28 +00:00
}
2018-05-11 18:50:32 +00:00
2025-09-08 15:14:48 +00:00
ociRepositoryCredentials , err := m . db . GetAllOCIRepositoryCredentials ( ctx )
2025-06-06 11:27:02 +00:00
if err != nil {
return nil , nil , false , fmt . Errorf ( "failed to get OCI credentials: %w" , err )
}
permittedOCICredentials , err := argo . GetPermittedReposCredentials ( proj , ociRepositoryCredentials )
if err != nil {
return nil , nil , false , fmt . Errorf ( "failed to get permitted OCI credentials for project %q: %w" , proj . Name , err )
}
2022-02-17 05:03:04 +00:00
enabledSourceTypes , err := m . settingsMgr . GetEnabledSourceTypes ( )
if err != nil {
2024-09-06 15:40:48 +00:00
return nil , nil , false , fmt . Errorf ( "failed to get enabled source types: %w" , err )
2022-02-17 05:03:04 +00:00
}
2020-03-16 18:51:59 +00:00
ts . AddCheckpoint ( "plugins_ms" )
2019-02-22 22:56:11 +00:00
2020-04-15 19:04:31 +00:00
kustomizeSettings , err := m . settingsMgr . GetKustomizeSettings ( )
if err != nil {
2024-09-06 15:40:48 +00:00
return nil , nil , false , fmt . Errorf ( "failed to get Kustomize settings: %w" , err )
2020-04-15 19:04:31 +00:00
}
2021-09-28 15:35:17 +00:00
2022-02-17 19:51:17 +00:00
helmOptions , err := m . settingsMgr . GetHelmSettings ( )
if err != nil {
2024-09-06 15:40:48 +00:00
return nil , nil , false , fmt . Errorf ( "failed to get Helm settings: %w" , err )
2022-02-17 19:51:17 +00:00
}
2022-12-16 20:47:08 +00:00
2025-05-15 08:26:47 +00:00
trackingMethod , err := m . settingsMgr . GetTrackingMethod ( )
if err != nil {
return nil , nil , false , fmt . Errorf ( "failed to get trackingMethod: %w" , err )
}
2024-10-05 00:54:37 +00:00
installationID , err := m . settingsMgr . GetInstallationID ( )
if err != nil {
return nil , nil , false , fmt . Errorf ( "failed to get installation ID: %w" , err )
}
2025-09-08 15:14:48 +00:00
destCluster , err := argo . GetDestinationCluster ( ctx , app . Spec . Destination , m . db )
2025-01-13 18:15:42 +00:00
if err != nil {
return nil , nil , false , fmt . Errorf ( "failed to get destination cluster: %w" , err )
}
2020-03-16 18:51:59 +00:00
ts . AddCheckpoint ( "build_options_ms" )
2025-02-26 18:36:48 +00:00
var serverVersion string
var apiResources [ ] kubeutil . APIResourceInfo
if sendRuntimeState {
serverVersion , apiResources , err = m . liveStateCache . GetVersionsInfo ( destCluster )
if err != nil {
return nil , nil , false , fmt . Errorf ( "failed to get cluster version for cluster %q: %w" , destCluster . Server , err )
}
2019-09-11 23:37:00 +00:00
}
2022-12-16 20:47:08 +00:00
conn , repoClient , err := m . repoClientset . NewRepoServerClient ( )
2018-05-11 18:50:32 +00:00
if err != nil {
2024-09-06 15:40:48 +00:00
return nil , nil , false , fmt . Errorf ( "failed to connect to repo server: %w" , err )
2018-05-11 18:50:32 +00:00
}
2025-05-21 16:25:32 +00:00
defer utilio . Close ( conn )
2022-12-16 20:47:08 +00:00
2023-06-20 16:08:21 +00:00
manifestInfos := make ( [ ] * apiclient . ManifestResponse , 0 )
2022-12-16 20:47:08 +00:00
targetObjs := make ( [ ] * unstructured . Unstructured , 0 )
2020-05-15 17:01:18 +00:00
2022-12-16 20:47:08 +00:00
// Store the map of all sources having ref field into a map for applications with sources field
2024-06-10 21:54:07 +00:00
// If it's for a rollback process, the refSources[*].targetRevision fields are the desired
// revisions for the rollback
2025-09-08 15:14:48 +00:00
refSources , err := argo . GetRefSources ( ctx , sources , app . Spec . Project , m . db . GetRepository , revisions )
2019-08-16 19:55:36 +00:00
if err != nil {
2024-09-06 15:40:48 +00:00
return nil , nil , false , fmt . Errorf ( "failed to get ref sources: %w" , err )
2022-12-16 20:47:08 +00:00
}
2026-02-03 14:57:48 +00:00
var syncedRefSources v1alpha1 . RefTargetRevisionMapping
if app . Spec . HasMultipleSources ( ) {
syncedRefSources = argo . GetSyncedRefSources ( refSources , sources , app . Status . Sync . Revisions )
}
2025-07-14 14:36:05 +00:00
revisionsMayHaveChanges := false
2024-09-07 16:12:33 +00:00
2024-09-06 15:40:48 +00:00
keyManifestGenerateAnnotationVal , keyManifestGenerateAnnotationExists := app . Annotations [ v1alpha1 . AnnotationKeyManifestGeneratePaths ]
2022-12-16 20:47:08 +00:00
for i , source := range sources {
if len ( revisions ) < len ( sources ) || revisions [ i ] == "" {
revisions [ i ] = source . TargetRevision
}
2025-09-08 15:14:48 +00:00
repo , err := m . db . GetRepository ( ctx , source . RepoURL , proj . Name )
2022-12-16 20:47:08 +00:00
if err != nil {
2024-09-06 15:40:48 +00:00
return nil , nil , false , fmt . Errorf ( "failed to get repo %q: %w" , source . RepoURL , err )
2022-12-16 20:47:08 +00:00
}
2024-04-04 20:01:11 +00:00
syncedRevision := app . Status . Sync . Revision
if app . Spec . HasMultipleSources ( ) {
if i < len ( app . Status . Sync . Revisions ) {
syncedRevision = app . Status . Sync . Revisions [ i ]
} else {
syncedRevision = ""
}
}
2024-09-06 15:40:48 +00:00
revision := revisions [ i ]
2024-12-16 21:59:09 +00:00
appNamespace := app . Spec . Destination . Namespace
apiVersions := argo . APIResourcesToStrings ( apiResources , true )
2025-11-03 21:48:14 +00:00
updateRevisions := processManifestGeneratePathsEnabled &&
// updating revisions result is not required if automated sync is not enabled
app . Spec . SyncPolicy != nil && app . Spec . SyncPolicy . Automated != nil &&
// using updating revisions gains performance only if manifest generation is required.
// just reading pre-generated manifests is comparable to updating revisions time-wise
app . Status . SourceType != v1alpha1 . ApplicationSourceTypeDirectory
2026-02-03 14:57:48 +00:00
if updateRevisions && repo . Depth == 0 && syncedRevision != "" && ! source . IsRef ( ) && keyManifestGenerateAnnotationExists && keyManifestGenerateAnnotationVal != "" && ( syncedRevision != revision || app . Spec . HasMultipleSources ( ) ) {
2024-04-04 20:01:11 +00:00
// Validate the manifest-generate-path annotation to avoid generating manifests if it has not changed.
2025-09-08 15:14:48 +00:00
updateRevisionResult , err := repoClient . UpdateRevisionForPaths ( ctx , & apiclient . UpdateRevisionForPathsRequest {
2024-04-04 20:01:11 +00:00
Repo : repo ,
2024-09-06 15:40:48 +00:00
Revision : revision ,
2024-04-04 20:01:11 +00:00
SyncedRevision : syncedRevision ,
2024-09-06 15:40:48 +00:00
NoRevisionCache : noRevisionCache ,
2026-01-22 15:08:58 +00:00
Paths : path . GetSourceRefreshPaths ( app , source ) ,
2024-04-04 20:01:11 +00:00
AppLabelKey : appLabelKey ,
AppName : app . InstanceName ( m . namespace ) ,
2024-12-16 21:59:09 +00:00
Namespace : appNamespace ,
2024-04-04 20:01:11 +00:00
ApplicationSource : & source ,
KubeVersion : serverVersion ,
2024-12-16 21:59:09 +00:00
ApiVersions : apiVersions ,
2025-05-15 08:26:47 +00:00
TrackingMethod : trackingMethod ,
2024-04-04 20:01:11 +00:00
RefSources : refSources ,
2026-02-03 14:57:48 +00:00
SyncedRefSources : syncedRefSources ,
2024-04-04 20:01:11 +00:00
HasMultipleSources : app . Spec . HasMultipleSources ( ) ,
2024-10-05 00:54:37 +00:00
InstallationID : installationID ,
2024-04-04 20:01:11 +00:00
} )
if err != nil {
2024-09-06 15:40:48 +00:00
return nil , nil , false , fmt . Errorf ( "failed to compare revisions for source %d of %d: %w" , i + 1 , len ( sources ) , err )
}
2026-02-03 14:57:48 +00:00
2024-09-06 15:40:48 +00:00
if updateRevisionResult . Changes {
2025-07-14 14:36:05 +00:00
revisionsMayHaveChanges = true
2024-09-06 15:40:48 +00:00
}
// Generate manifests should use same revision as updateRevisionForPaths, because HEAD revision may be different between these two calls
if updateRevisionResult . Revision != "" {
revision = updateRevisionResult . Revision
2024-04-04 20:01:11 +00:00
}
2026-02-03 14:57:48 +00:00
} else if ! source . IsRef ( ) {
2025-07-14 14:36:05 +00:00
// revisionsMayHaveChanges is set to true if at least one revision is not possible to be updated
revisionsMayHaveChanges = true
2024-04-04 20:01:11 +00:00
}
2025-06-06 11:27:02 +00:00
repos := permittedHelmRepos
helmRepoCreds := permittedHelmCredentials
// If the source is OCI, there is a potential for an OCI image to be a Helm chart and that said chart in
// turn would have OCI dependencies. To ensure that those dependencies can be resolved, add them to the repos
// list.
if source . IsOCI ( ) {
repos = slices . Clone ( permittedHelmRepos )
helmRepoCreds = slices . Clone ( permittedHelmCredentials )
repos = append ( repos , permittedOCIRepos ... )
helmRepoCreds = append ( helmRepoCreds , permittedOCICredentials ... )
}
2024-09-06 15:40:48 +00:00
log . Debugf ( "Generating Manifest for source %s revision %s" , source , revision )
2025-09-08 15:14:48 +00:00
manifestInfo , err := repoClient . GenerateManifest ( ctx , & apiclient . ManifestRequest {
2024-09-24 16:14:02 +00:00
Repo : repo ,
2025-06-06 11:27:02 +00:00
Repos : repos ,
2024-09-24 16:14:02 +00:00
Revision : revision ,
NoCache : noCache ,
NoRevisionCache : noRevisionCache ,
AppLabelKey : appLabelKey ,
AppName : app . InstanceName ( m . namespace ) ,
2024-12-16 21:59:09 +00:00
Namespace : appNamespace ,
2024-09-24 16:14:02 +00:00
ApplicationSource : & source ,
2025-07-16 15:12:25 +00:00
KustomizeOptions : kustomizeSettings ,
2024-09-24 16:14:02 +00:00
KubeVersion : serverVersion ,
2024-12-16 21:59:09 +00:00
ApiVersions : apiVersions ,
2024-09-24 16:14:02 +00:00
VerifySignature : verifySignature ,
2025-06-06 11:27:02 +00:00
HelmRepoCreds : helmRepoCreds ,
2025-05-15 08:26:47 +00:00
TrackingMethod : trackingMethod ,
2024-09-24 16:14:02 +00:00
EnabledSourceTypes : enabledSourceTypes ,
HelmOptions : helmOptions ,
HasMultipleSources : app . Spec . HasMultipleSources ( ) ,
RefSources : refSources ,
ProjectName : proj . Name ,
ProjectSourceRepos : proj . Spec . SourceRepos ,
AnnotationManifestGeneratePaths : app . GetAnnotation ( v1alpha1 . AnnotationKeyManifestGeneratePaths ) ,
2024-10-05 00:54:37 +00:00
InstallationID : installationID ,
2022-12-16 20:47:08 +00:00
} )
if err != nil {
2024-09-06 15:40:48 +00:00
return nil , nil , false , fmt . Errorf ( "failed to generate manifest for source %d of %d: %w" , i + 1 , len ( sources ) , err )
2022-12-16 20:47:08 +00:00
}
targetObj , err := unmarshalManifests ( manifestInfo . Manifests )
if err != nil {
2024-09-06 15:40:48 +00:00
return nil , nil , false , fmt . Errorf ( "failed to unmarshal manifests for source %d of %d: %w" , i + 1 , len ( sources ) , err )
2022-12-16 20:47:08 +00:00
}
targetObjs = append ( targetObjs , targetObj ... )
2023-06-20 16:08:21 +00:00
manifestInfos = append ( manifestInfos , manifestInfo )
2019-08-16 19:55:36 +00:00
}
2020-05-15 17:01:18 +00:00
2024-08-22 03:37:27 +00:00
ts . AddCheckpoint ( "manifests_ms" )
2025-05-20 19:48:09 +00:00
logCtx := log . WithFields ( applog . GetAppLogFields ( app ) )
2020-03-16 18:51:59 +00:00
for k , v := range ts . Timings ( ) {
logCtx = logCtx . WithField ( k , v . Milliseconds ( ) )
}
logCtx = logCtx . WithField ( "time_ms" , time . Since ( ts . StartTime ) . Milliseconds ( ) )
2023-12-18 16:40:23 +00:00
logCtx . Info ( "GetRepoObjs stats" )
2024-09-06 15:40:48 +00:00
2025-07-14 14:36:05 +00:00
return targetObjs , manifestInfos , revisionsMayHaveChanges , nil
2019-06-18 02:09:43 +00:00
}
2024-12-16 21:59:09 +00:00
// ResolveGitRevision will resolve the given revision to a full commit SHA. Only works for git.
2025-11-03 21:48:14 +00:00
func ( m * appStateManager ) ResolveGitRevision ( repoURL , revision string ) ( string , error ) {
2024-12-16 21:59:09 +00:00
conn , repoClient , err := m . repoClientset . NewRepoServerClient ( )
if err != nil {
return "" , fmt . Errorf ( "failed to connect to repo server: %w" , err )
}
2025-05-21 16:25:32 +00:00
defer utilio . Close ( conn )
2024-12-16 21:59:09 +00:00
repo , err := m . db . GetRepository ( context . Background ( ) , repoURL , "" )
if err != nil {
return "" , fmt . Errorf ( "failed to get repo %q: %w" , repoURL , err )
}
// Mock the app. The repo-server only needs to know whether the "chart" field is populated.
app := & v1alpha1 . Application {
Spec : v1alpha1 . ApplicationSpec {
Source : & v1alpha1 . ApplicationSource {
RepoURL : repoURL ,
TargetRevision : revision ,
} ,
} ,
}
resp , err := repoClient . ResolveRevision ( context . Background ( ) , & apiclient . ResolveRevisionRequest {
Repo : repo ,
App : app ,
AmbiguousRevision : revision ,
} )
if err != nil {
return "" , fmt . Errorf ( "failed to determine whether the dry source has changed: %w" , err )
}
return resp . Revision , nil
}
2020-05-15 17:01:18 +00:00
func unmarshalManifests ( manifests [ ] string ) ( [ ] * unstructured . Unstructured , error ) {
2018-07-07 07:54:06 +00:00
targetObjs := make ( [ ] * unstructured . Unstructured , 0 )
2019-06-18 02:09:43 +00:00
for _ , manifest := range manifests {
2018-05-11 18:50:32 +00:00
obj , err := v1alpha1 . UnmarshalToUnstructured ( manifest )
if err != nil {
2020-05-15 17:01:18 +00:00
return nil , err
2018-07-07 07:54:06 +00:00
}
2020-05-15 17:01:18 +00:00
targetObjs = append ( targetObjs , obj )
2018-05-11 18:50:32 +00:00
}
2020-05-15 17:01:18 +00:00
return targetObjs , nil
2018-07-10 21:45:18 +00:00
}
2018-05-11 18:50:32 +00:00
2019-03-19 04:19:08 +00:00
func DeduplicateTargetObjects (
2019-03-18 20:21:03 +00:00
namespace string ,
objs [ ] * unstructured . Unstructured ,
2020-05-15 17:01:18 +00:00
infoProvider kubeutil . ResourceInfoProvider ,
2019-03-18 20:21:03 +00:00
) ( [ ] * unstructured . Unstructured , [ ] v1alpha1 . ApplicationCondition , error ) {
targetByKey := make ( map [ kubeutil . ResourceKey ] [ ] * unstructured . Unstructured )
for i := range objs {
obj := objs [ i ]
2020-06-08 22:35:20 +00:00
if obj == nil {
continue
}
2020-05-15 17:01:18 +00:00
isNamespaced := kubeutil . IsNamespacedOrUnknown ( infoProvider , obj . GroupVersionKind ( ) . GroupKind ( ) )
2019-03-18 20:21:03 +00:00
if ! isNamespaced {
obj . SetNamespace ( "" )
} else if obj . GetNamespace ( ) == "" {
obj . SetNamespace ( namespace )
}
key := kubeutil . GetResourceKey ( obj )
2020-07-21 17:15:41 +00:00
if key . Name == "" && obj . GetGenerateName ( ) != "" {
key . Name = fmt . Sprintf ( "%s%d" , obj . GetGenerateName ( ) , i )
}
2019-03-18 20:21:03 +00:00
targetByKey [ key ] = append ( targetByKey [ key ] , obj )
}
conditions := make ( [ ] v1alpha1 . ApplicationCondition , 0 )
result := make ( [ ] * unstructured . Unstructured , 0 )
for key , targets := range targetByKey {
if len ( targets ) > 1 {
2019-10-17 02:29:52 +00:00
now := metav1 . Now ( )
2023-06-01 16:48:09 +00:00
conditions = append ( conditions , v1alpha1 . ApplicationCondition {
Type : v1alpha1 . ApplicationConditionRepeatedResourceWarning ,
2019-10-17 02:29:52 +00:00
Message : fmt . Sprintf ( "Resource %s appeared %d times among application resources." , key . String ( ) , len ( targets ) ) ,
LastTransitionTime : & now ,
2019-03-18 20:21:03 +00:00
} )
}
result = append ( result , targets [ len ( targets ) - 1 ] )
}
return result , conditions , nil
}
2025-03-13 13:34:20 +00:00
// normalizeClusterScopeTracking will set the app instance tracking metadata on malformed cluster-scoped resources where
// metadata.namespace is not empty. The repo-server doesn't know which resources are cluster-scoped, so it may apply
// an incorrect tracking annotation using the metadata.namespace. This function will correct that.
func normalizeClusterScopeTracking ( targetObjs [ ] * unstructured . Unstructured , infoProvider kubeutil . ResourceInfoProvider , setAppInstance func ( * unstructured . Unstructured ) error ) error {
for i := len ( targetObjs ) - 1 ; i >= 0 ; i -- {
targetObj := targetObjs [ i ]
if targetObj == nil {
continue
}
gvk := targetObj . GroupVersionKind ( )
if ! kubeutil . IsNamespacedOrUnknown ( infoProvider , gvk . GroupKind ( ) ) {
if targetObj . GetNamespace ( ) != "" {
targetObj . SetNamespace ( "" )
err := setAppInstance ( targetObj )
if err != nil {
return fmt . Errorf ( "failed to set app instance label on cluster-scoped resource %s/%s: %w" , gvk . String ( ) , targetObj . GetName ( ) , err )
}
}
}
}
return nil
}
2022-01-13 21:00:17 +00:00
// getComparisonSettings will return the system level settings related to the
// diff/normalization process.
2025-05-15 08:26:47 +00:00
func ( m * appStateManager ) getComparisonSettings ( ) ( string , map [ string ] v1alpha1 . ResourceOverride , * settings . ResourcesFilter , string , string , error ) {
2019-06-21 22:59:05 +00:00
resourceOverrides , err := m . settingsMgr . GetResourceOverrides ( )
if err != nil {
2025-05-15 08:26:47 +00:00
return "" , nil , nil , "" , "" , err
2019-06-21 22:59:05 +00:00
}
appLabelKey , err := m . settingsMgr . GetAppInstanceLabelKey ( )
if err != nil {
2025-05-15 08:26:47 +00:00
return "" , nil , nil , "" , "" , err
2019-02-22 21:19:10 +00:00
}
2020-03-16 18:51:59 +00:00
resFilter , err := m . settingsMgr . GetResourcesFilter ( )
if err != nil {
2025-05-15 08:26:47 +00:00
return "" , nil , nil , "" , "" , err
2024-10-05 00:54:37 +00:00
}
installationID , err := m . settingsMgr . GetInstallationID ( )
if err != nil {
2025-05-15 08:26:47 +00:00
return "" , nil , nil , "" , "" , err
2020-03-16 18:51:59 +00:00
}
2025-05-15 08:26:47 +00:00
trackingMethod , err := m . settingsMgr . GetTrackingMethod ( )
if err != nil {
return "" , nil , nil , "" , "" , err
}
return appLabelKey , resourceOverrides , resFilter , installationID , trackingMethod , nil
2019-08-20 15:43:29 +00:00
}
2020-06-22 16:21:53 +00:00
// verifyGnuPGSignature verifies the result of a GnuPG operation for a given git
// revision.
2023-06-01 16:48:09 +00:00
func verifyGnuPGSignature ( revision string , project * v1alpha1 . AppProject , manifestInfo * apiclient . ManifestResponse ) [ ] v1alpha1 . ApplicationCondition {
2020-06-22 16:21:53 +00:00
now := metav1 . Now ( )
2023-06-01 16:48:09 +00:00
conditions := make ( [ ] v1alpha1 . ApplicationCondition , 0 )
2020-10-20 01:21:06 +00:00
// We need to have some data in the verification result to parse, otherwise there was no signature
2020-06-22 16:21:53 +00:00
if manifestInfo . VerifyResult != "" {
2021-03-03 22:39:47 +00:00
verifyResult := gpg . ParseGitCommitVerification ( manifestInfo . VerifyResult )
switch verifyResult . Result {
case gpg . VerifyResultGood :
// This is the only case we allow to sync to, but we need to make sure signing key is allowed
validKey := false
for _ , k := range project . Spec . SignatureKeys {
if gpg . KeyID ( k . KeyID ) == gpg . KeyID ( verifyResult . KeyID ) && gpg . KeyID ( k . KeyID ) != "" {
validKey = true
break
2020-06-22 16:21:53 +00:00
}
2021-03-03 22:39:47 +00:00
}
if ! validKey {
msg := fmt . Sprintf ( "Found good signature made with %s key %s, but this key is not allowed in AppProject" ,
verifyResult . Cipher , verifyResult . KeyID )
2020-06-22 16:21:53 +00:00
conditions = append ( conditions , v1alpha1 . ApplicationCondition { Type : v1alpha1 . ApplicationConditionComparisonError , Message : msg , LastTransitionTime : & now } )
}
2021-03-03 22:39:47 +00:00
case gpg . VerifyResultInvalid :
msg := fmt . Sprintf ( "Found signature made with %s key %s, but verification result was invalid: '%s'" ,
verifyResult . Cipher , verifyResult . KeyID , verifyResult . Message )
conditions = append ( conditions , v1alpha1 . ApplicationCondition { Type : v1alpha1 . ApplicationConditionComparisonError , Message : msg , LastTransitionTime : & now } )
default :
msg := fmt . Sprintf ( "Could not verify commit signature on revision '%s', check logs for more information." , revision )
conditions = append ( conditions , v1alpha1 . ApplicationCondition { Type : v1alpha1 . ApplicationConditionComparisonError , Message : msg , LastTransitionTime : & now } )
2020-06-22 16:21:53 +00:00
}
} else {
msg := fmt . Sprintf ( "Target revision %s in Git is not signed, but a signature is required" , revision )
conditions = append ( conditions , v1alpha1 . ApplicationCondition { Type : v1alpha1 . ApplicationConditionComparisonError , Message : msg , LastTransitionTime : & now } )
}
return conditions
}
2023-09-13 15:45:06 +00:00
func isManagedNamespace ( ns * unstructured . Unstructured , app * v1alpha1 . Application ) bool {
return ns != nil && ns . GetKind ( ) == kubeutil . NamespaceKind && ns . GetName ( ) == app . Spec . Destination . Namespace && app . Spec . SyncPolicy != nil && app . Spec . SyncPolicy . ManagedNamespaceMetadata != nil
}
2019-08-20 15:43:29 +00:00
// CompareAppState compares application git state to the live app state, using the specified
// revision and supplied source. If revision or overrides are empty, then compares against
// revision and overrides in the app spec.
2025-11-03 21:48:14 +00:00
func ( m * appStateManager ) CompareAppState ( app * v1alpha1 . Application , project * v1alpha1 . AppProject , revisions [ ] string , sources [ ] v1alpha1 . ApplicationSource , noCache , noRevisionCache bool , localManifests [ ] string , hasMultipleSources bool ) ( * comparisonResult , error ) {
2020-03-16 18:51:59 +00:00
ts := stats . NewTimingStats ( )
2025-07-16 17:39:30 +00:00
logCtx := log . WithFields ( applog . GetAppLogFields ( app ) )
2021-12-14 18:49:55 +00:00
2025-07-16 17:39:30 +00:00
// Build initial sync status
syncStatus := & v1alpha1 . SyncStatus {
ComparedTo : v1alpha1 . ComparedTo {
Destination : app . Spec . Destination ,
IgnoreDifferences : app . Spec . IgnoreDifferences ,
} ,
Status : v1alpha1 . SyncStatusCodeUnknown ,
}
if hasMultipleSources {
syncStatus . ComparedTo . Sources = sources
syncStatus . Revisions = revisions
} else {
if len ( sources ) > 0 {
syncStatus . ComparedTo . Source = sources [ 0 ]
} else {
logCtx . Warn ( "CompareAppState: sources should not be empty" )
}
if len ( revisions ) > 0 {
syncStatus . Revision = revisions [ 0 ]
}
}
2019-08-20 15:43:29 +00:00
2025-07-16 17:39:30 +00:00
appLabelKey , resourceOverrides , resFilter , installationID , trackingMethod , err := m . getComparisonSettings ( )
ts . AddCheckpoint ( "settings_ms" )
if err != nil {
2025-09-11 15:19:00 +00:00
log . Infof ( "Basic comparison settings cannot be loaded, using unknown comparison: %s" , err . Error ( ) )
2025-07-16 17:39:30 +00:00
return & comparisonResult { syncStatus : syncStatus , healthStatus : health . HealthStatusUnknown } , nil
2019-08-20 15:43:29 +00:00
}
2020-06-22 16:21:53 +00:00
// When signature keys are defined in the project spec, we need to verify the signature on the Git revision
2025-03-27 16:37:52 +00:00
verifySignature := len ( project . Spec . SignatureKeys ) > 0 && gpg . IsGPGEnabled ( )
2020-06-22 16:21:53 +00:00
2019-08-20 15:43:29 +00:00
// do best effort loading live and target state to present as much information about app state as possible
2018-07-12 19:40:21 +00:00
failedToLoadObjs := false
2018-07-10 21:45:18 +00:00
conditions := make ( [ ] v1alpha1 . ApplicationCondition , 0 )
2019-06-18 02:09:43 +00:00
2025-01-13 18:15:42 +00:00
destCluster , err := argo . GetDestinationCluster ( context . Background ( ) , app . Spec . Destination , m . db )
if err != nil {
return nil , err
}
2025-05-20 19:48:09 +00:00
logCtx . Infof ( "Comparing app state (cluster: %s, namespace: %s)" , app . Spec . Destination . Server , app . Spec . Destination . Namespace )
2019-08-20 15:43:29 +00:00
2019-06-18 02:09:43 +00:00
var targetObjs [ ] * unstructured . Unstructured
2019-10-17 02:29:52 +00:00
now := metav1 . Now ( )
2019-06-18 02:09:43 +00:00
2023-06-20 16:08:21 +00:00
var manifestInfos [ ] * apiclient . ManifestResponse
2023-10-18 15:17:00 +00:00
targetNsExists := false
2022-12-16 20:47:08 +00:00
2025-07-14 14:36:05 +00:00
var revisionsMayHaveChanges bool
2024-09-06 15:40:48 +00:00
2019-06-18 02:09:43 +00:00
if len ( localManifests ) == 0 {
2022-12-16 20:47:08 +00:00
// If the length of revisions is not same as the length of sources,
// we take the revisions from the sources directly for all the sources.
if len ( revisions ) != len ( sources ) {
revisions = make ( [ ] string , 0 )
for _ , source := range sources {
revisions = append ( revisions , source . TargetRevision )
}
}
2025-09-08 15:14:48 +00:00
targetObjs , manifestInfos , revisionsMayHaveChanges , err = m . GetRepoObjs ( context . Background ( ) , app , sources , appLabelKey , revisions , noCache , noRevisionCache , verifySignature , project , true )
2019-06-18 02:09:43 +00:00
if err != nil {
targetObjs = make ( [ ] * unstructured . Unstructured , 0 )
2024-12-20 16:22:28 +00:00
msg := "Failed to load target state: " + err . Error ( )
2023-08-03 22:06:19 +00:00
conditions = append ( conditions , v1alpha1 . ApplicationCondition { Type : v1alpha1 . ApplicationConditionComparisonError , Message : msg , LastTransitionTime : & now } )
2023-11-02 15:51:16 +00:00
if firstSeen , ok := m . repoErrorCache . Load ( app . Name ) ; ok {
if time . Since ( firstSeen . ( time . Time ) ) <= m . repoErrorGracePeriod && ! noRevisionCache {
// if first seen is less than grace period and it's not a Level 3 comparison,
// ignore error and short circuit
logCtx . Debugf ( "Ignoring repo error %v, already encountered error in grace period" , err . Error ( ) )
2025-03-27 16:37:52 +00:00
return nil , ErrCompareStateRepo
2023-11-02 15:51:16 +00:00
}
} else if ! noRevisionCache {
logCtx . Debugf ( "Ignoring repo error %v, new occurrence" , err . Error ( ) )
m . repoErrorCache . Store ( app . Name , time . Now ( ) )
2025-03-27 16:37:52 +00:00
return nil , ErrCompareStateRepo
2023-11-02 15:51:16 +00:00
}
2019-06-18 02:09:43 +00:00
failedToLoadObjs = true
2023-11-02 15:51:16 +00:00
} else {
m . repoErrorCache . Delete ( app . Name )
2019-06-18 02:09:43 +00:00
}
} else {
2020-06-22 16:21:53 +00:00
// Prevent applying local manifests for now when signature verification is enabled
// This is also enforced on API level, but as a last resort, we also enforce it here
if gpg . IsGPGEnabled ( ) && verifySignature {
msg := "Cannot use local manifests when signature verification is required"
2019-06-18 02:09:43 +00:00
targetObjs = make ( [ ] * unstructured . Unstructured , 0 )
2020-06-22 16:21:53 +00:00
conditions = append ( conditions , v1alpha1 . ApplicationCondition { Type : v1alpha1 . ApplicationConditionComparisonError , Message : msg , LastTransitionTime : & now } )
2019-06-18 02:09:43 +00:00
failedToLoadObjs = true
2020-06-22 16:21:53 +00:00
} else {
targetObjs , err = unmarshalManifests ( localManifests )
if err != nil {
targetObjs = make ( [ ] * unstructured . Unstructured , 0 )
2024-12-20 16:22:28 +00:00
msg := "Failed to load local manifests: " + err . Error ( )
2023-08-03 22:06:19 +00:00
conditions = append ( conditions , v1alpha1 . ApplicationCondition { Type : v1alpha1 . ApplicationConditionComparisonError , Message : msg , LastTransitionTime : & now } )
2020-06-22 16:21:53 +00:00
failedToLoadObjs = true
}
2019-06-18 02:09:43 +00:00
}
2022-12-16 20:47:08 +00:00
// empty out manifestInfoMap
2023-06-20 16:08:21 +00:00
manifestInfos = make ( [ ] * apiclient . ManifestResponse , 0 )
2018-07-10 21:45:18 +00:00
}
2020-03-16 18:51:59 +00:00
ts . AddCheckpoint ( "git_ms" )
2019-06-18 02:09:43 +00:00
2020-05-15 17:01:18 +00:00
var infoProvider kubeutil . ResourceInfoProvider
2025-01-13 18:15:42 +00:00
infoProvider , err = m . liveStateCache . GetClusterCache ( destCluster )
2020-05-15 17:01:18 +00:00
if err != nil {
infoProvider = & resourceInfoProviderStub { }
}
2025-03-13 13:34:20 +00:00
err = normalizeClusterScopeTracking ( targetObjs , infoProvider , func ( u * unstructured . Unstructured ) error {
2025-05-15 08:26:47 +00:00
return m . resourceTracking . SetAppInstance ( u , appLabelKey , app . InstanceName ( m . namespace ) , app . Spec . Destination . Namespace , v1alpha1 . TrackingMethod ( trackingMethod ) , installationID )
2025-03-13 13:34:20 +00:00
} )
if err != nil {
msg := "Failed to normalize cluster-scoped resource tracking: " + err . Error ( )
conditions = append ( conditions , v1alpha1 . ApplicationCondition { Type : v1alpha1 . ApplicationConditionComparisonError , Message : msg , LastTransitionTime : & now } )
}
2020-05-15 17:01:18 +00:00
targetObjs , dedupConditions , err := DeduplicateTargetObjects ( app . Spec . Destination . Namespace , targetObjs , infoProvider )
2019-03-18 20:21:03 +00:00
if err != nil {
2024-12-20 16:22:28 +00:00
msg := "Failed to deduplicate target state: " + err . Error ( )
2023-08-03 22:06:19 +00:00
conditions = append ( conditions , v1alpha1 . ApplicationCondition { Type : v1alpha1 . ApplicationConditionComparisonError , Message : msg , LastTransitionTime : & now } )
2019-03-18 20:21:03 +00:00
}
conditions = append ( conditions , dedupConditions ... )
2025-03-13 13:34:20 +00:00
2020-03-16 18:51:59 +00:00
for i := len ( targetObjs ) - 1 ; i >= 0 ; i -- {
targetObj := targetObjs [ i ]
gvk := targetObj . GroupVersionKind ( )
2025-01-13 18:15:42 +00:00
if resFilter . IsExcludedResource ( gvk . Group , gvk . Kind , destCluster . Server ) {
2020-03-16 18:51:59 +00:00
targetObjs = append ( targetObjs [ : i ] , targetObjs [ i + 1 : ] ... )
conditions = append ( conditions , v1alpha1 . ApplicationCondition {
Type : v1alpha1 . ApplicationConditionExcludedResourceWarning ,
Message : fmt . Sprintf ( "Resource %s/%s %s is excluded in the settings" , gvk . Group , gvk . Kind , targetObj . GetName ( ) ) ,
LastTransitionTime : & now ,
} )
2019-07-02 20:56:25 +00:00
}
2023-10-18 15:17:00 +00:00
// If we reach this path, this means that a namespace has been both defined in Git, as well in the
// application's managedNamespaceMetadata. We want to ensure that this manifest is the one being used instead
// of what is present in managedNamespaceMetadata.
if isManagedNamespace ( targetObj , app ) {
targetNsExists = true
}
2019-07-02 20:56:25 +00:00
}
2020-03-16 18:51:59 +00:00
ts . AddCheckpoint ( "dedup_ms" )
2019-07-02 20:56:25 +00:00
2025-01-13 18:15:42 +00:00
liveObjByKey , err := m . liveStateCache . GetManagedLiveObjs ( destCluster , app , targetObjs )
2018-07-10 21:45:18 +00:00
if err != nil {
2018-11-28 21:38:02 +00:00
liveObjByKey = make ( map [ kubeutil . ResourceKey ] * unstructured . Unstructured )
2024-12-20 16:22:28 +00:00
msg := "Failed to load live state: " + err . Error ( )
2023-08-03 22:06:19 +00:00
conditions = append ( conditions , v1alpha1 . ApplicationCondition { Type : v1alpha1 . ApplicationConditionComparisonError , Message : msg , LastTransitionTime : & now } )
2018-07-12 19:40:21 +00:00
failedToLoadObjs = true
2018-07-10 21:45:18 +00:00
}
2022-12-16 20:47:08 +00:00
2023-02-24 18:30:18 +00:00
logCtx . Debugf ( "Retrieved live manifests" )
2019-12-26 22:08:14 +00:00
// filter out all resources which are not permitted in the application project
for k , v := range liveObjByKey {
2025-01-13 18:15:42 +00:00
permitted , err := project . IsLiveResourcePermitted ( v , destCluster , func ( project string ) ( [ ] * v1alpha1 . Cluster , error ) {
2023-08-03 22:06:19 +00:00
clusters , err := m . db . GetProjectClusters ( context . TODO ( ) , project )
if err != nil {
2024-06-11 19:33:22 +00:00
return nil , fmt . Errorf ( "failed to get clusters for project %q: %w" , project , err )
2023-08-03 22:06:19 +00:00
}
return clusters , nil
2022-09-08 11:33:10 +00:00
} )
if err != nil {
2023-08-03 22:06:19 +00:00
msg := fmt . Sprintf ( "Failed to check if live resource %q is permitted in project %q: %s" , k . String ( ) , app . Spec . Project , err . Error ( ) )
conditions = append ( conditions , v1alpha1 . ApplicationCondition { Type : v1alpha1 . ApplicationConditionComparisonError , Message : msg , LastTransitionTime : & now } )
2022-09-08 11:33:10 +00:00
failedToLoadObjs = true
continue
}
if ! permitted {
2019-12-26 22:08:14 +00:00
delete ( liveObjByKey , k )
}
}
2018-11-28 21:38:02 +00:00
for _ , liveObj := range liveObjByKey {
2018-12-01 07:54:01 +00:00
if liveObj != nil {
2025-05-15 08:26:47 +00:00
appInstanceName := m . resourceTracking . GetAppName ( liveObj , appLabelKey , v1alpha1 . TrackingMethod ( trackingMethod ) , installationID )
2022-08-10 09:39:10 +00:00
if appInstanceName != "" && appInstanceName != app . InstanceName ( m . namespace ) {
fqInstanceName := strings . ReplaceAll ( appInstanceName , "_" , "/" )
2018-07-11 20:00:48 +00:00
conditions = append ( conditions , v1alpha1 . ApplicationCondition {
2019-10-17 02:29:52 +00:00
Type : v1alpha1 . ApplicationConditionSharedResourceWarning ,
2022-08-10 09:39:10 +00:00
Message : fmt . Sprintf ( "%s/%s is part of applications %s and %s" , liveObj . GetKind ( ) , liveObj . GetName ( ) , app . QualifiedName ( ) , fqInstanceName ) ,
2019-10-17 02:29:52 +00:00
LastTransitionTime : & now ,
2018-07-11 20:00:48 +00:00
} )
}
2023-09-13 15:45:06 +00:00
// For the case when a namespace is managed with `managedNamespaceMetadata` AND it has resource tracking
// enabled (e.g. someone manually adds resource tracking labels or annotations), we need to do some
// bookkeeping in order to prevent the managed namespace from being pruned.
//
// Live namespaces which are managed namespaces (i.e. application namespaces which are managed with
// CreateNamespace=true and has non-nil managedNamespaceMetadata) will (usually) not have a corresponding
// entry in source control. In order for the namespace not to risk being pruned, we'll need to generate a
// namespace which we can compare the live namespace with. For that, we'll do the same as is done in
// gitops-engine, the difference here being that we create a managed namespace which is only used for comparison.
2023-10-18 15:17:00 +00:00
//
// targetNsExists == true implies that it already exists as a target, so no need to add the namespace to the
// targetObjs array.
if isManagedNamespace ( liveObj , app ) && ! targetNsExists {
2025-01-03 16:10:00 +00:00
nsSpec := & corev1 . Namespace { TypeMeta : metav1 . TypeMeta { APIVersion : "v1" , Kind : kubeutil . NamespaceKind } , ObjectMeta : metav1 . ObjectMeta { Name : liveObj . GetName ( ) } }
2023-09-13 15:45:06 +00:00
managedNs , err := kubeutil . ToUnstructured ( nsSpec )
if err != nil {
conditions = append ( conditions , v1alpha1 . ApplicationCondition { Type : v1alpha1 . ApplicationConditionComparisonError , Message : err . Error ( ) , LastTransitionTime : & now } )
failedToLoadObjs = true
continue
}
// No need to care about the return value here, we just want the modified managedNs
2024-08-15 11:29:51 +00:00
_ , err = syncNamespace ( app . Spec . SyncPolicy ) ( managedNs , liveObj )
2023-09-13 15:45:06 +00:00
if err != nil {
conditions = append ( conditions , v1alpha1 . ApplicationCondition { Type : v1alpha1 . ApplicationConditionComparisonError , Message : err . Error ( ) , LastTransitionTime : & now } )
failedToLoadObjs = true
} else {
targetObjs = append ( targetObjs , managedNs )
}
}
2018-07-11 20:00:48 +00:00
}
}
2025-12-05 20:27:03 +00:00
hasPreDeleteHooks := false
2023-12-18 16:40:23 +00:00
hasPostDeleteHooks := false
2025-12-05 20:27:03 +00:00
// Filter out PreDelete and PostDelete hooks from targetObjs since they should not be synced
// as regular resources. They are only executed during deletion.
var targetObjsForSync [ ] * unstructured . Unstructured
2023-12-18 16:40:23 +00:00
for _ , obj := range targetObjs {
2025-12-05 20:27:03 +00:00
if isPreDeleteHook ( obj ) {
hasPreDeleteHooks = true
// Skip PreDelete hooks - they are not synced, only executed during deletion
continue
}
2023-12-18 16:40:23 +00:00
if isPostDeleteHook ( obj ) {
hasPostDeleteHooks = true
2025-12-05 20:27:03 +00:00
// Skip PostDelete hooks - they are not synced, only executed after deletion
continue
2023-12-18 16:40:23 +00:00
}
2025-12-05 20:27:03 +00:00
targetObjsForSync = append ( targetObjsForSync , obj )
2023-12-18 16:40:23 +00:00
}
2018-07-11 20:00:48 +00:00
2025-12-05 20:27:03 +00:00
reconciliation := sync . Reconcile ( targetObjsForSync , liveObjByKey , app . Spec . Destination . Namespace , infoProvider )
2020-03-16 18:51:59 +00:00
ts . AddCheckpoint ( "live_ms" )
2020-05-13 20:34:43 +00:00
compareOptions , err := m . settingsMgr . GetResourceCompareOptions ( )
if err != nil {
log . Warnf ( "Could not get compare options from ConfigMap (assuming defaults): %v" , err )
2020-10-27 21:10:24 +00:00
compareOptions = settings . GetDefaultDiffOptions ( )
2020-05-13 20:34:43 +00:00
}
2022-12-16 20:47:08 +00:00
manifestRevisions := make ( [ ] string , 0 )
2023-06-20 16:08:21 +00:00
for _ , manifestInfo := range manifestInfos {
2022-12-16 20:47:08 +00:00
manifestRevisions = append ( manifestRevisions , manifestInfo . Revision )
}
2020-05-13 20:34:43 +00:00
2023-12-18 20:37:13 +00:00
serverSideDiff := m . serverSideDiff ||
resourceutil . HasAnnotationOption ( app , common . AnnotationCompareOptions , "ServerSideDiff=true" )
// This allows turning SSD off for a given app if it is enabled at the
// controller level
if resourceutil . HasAnnotationOption ( app , common . AnnotationCompareOptions , "ServerSideDiff=false" ) {
serverSideDiff = false
}
useDiffCache := useDiffCache ( noCache , manifestInfos , sources , app , manifestRevisions , m . statusRefreshTimeout , serverSideDiff , logCtx )
2021-01-28 00:13:29 +00:00
2021-12-22 16:57:48 +00:00
diffConfigBuilder := argodiff . NewDiffConfigBuilder ( ) .
2024-04-26 09:24:02 +00:00
WithDiffSettings ( app . Spec . IgnoreDifferences , resourceOverrides , compareOptions . IgnoreAggregatedRoles , m . ignoreNormalizerOpts ) .
2021-12-22 16:57:48 +00:00
WithTracking ( appLabelKey , string ( trackingMethod ) )
2021-10-07 16:51:29 +00:00
2023-11-29 16:08:29 +00:00
if useDiffCache {
diffConfigBuilder . WithCache ( m . cache , app . InstanceName ( m . namespace ) )
2021-01-28 00:13:29 +00:00
} else {
2023-11-29 16:08:29 +00:00
diffConfigBuilder . WithNoCache ( )
2021-01-28 00:13:29 +00:00
}
2022-05-31 17:21:22 +00:00
2023-12-18 20:37:13 +00:00
if resourceutil . HasAnnotationOption ( app , common . AnnotationCompareOptions , "IncludeMutationWebhook=true" ) {
diffConfigBuilder . WithIgnoreMutationWebhook ( false )
}
2025-01-13 18:15:42 +00:00
gvkParser , err := m . getGVKParser ( destCluster )
2022-05-31 17:21:22 +00:00
if err != nil {
conditions = append ( conditions , v1alpha1 . ApplicationCondition { Type : v1alpha1 . ApplicationConditionUnknownError , Message : err . Error ( ) , LastTransitionTime : & now } )
}
diffConfigBuilder . WithGVKParser ( gvkParser )
2022-08-05 23:16:35 +00:00
diffConfigBuilder . WithManager ( common . ArgoCDSSAManager )
2023-12-18 20:37:13 +00:00
diffConfigBuilder . WithServerSideDiff ( serverSideDiff )
if serverSideDiff {
2025-02-07 17:26:03 +00:00
applier , cleanup , err := m . getServerSideDiffDryRunApplier ( destCluster )
2023-12-18 20:37:13 +00:00
if err != nil {
2025-02-07 17:26:03 +00:00
log . Errorf ( "CompareAppState error getting server side diff dry run applier: %s" , err )
2023-12-18 20:37:13 +00:00
conditions = append ( conditions , v1alpha1 . ApplicationCondition { Type : v1alpha1 . ApplicationConditionUnknownError , Message : err . Error ( ) , LastTransitionTime : & now } )
}
defer cleanup ( )
2025-02-07 17:26:03 +00:00
diffConfigBuilder . WithServerSideDryRunner ( diff . NewK8sServerSideDryRunner ( applier ) )
2023-12-18 20:37:13 +00:00
}
2022-08-05 23:16:35 +00:00
// enable structured merge diff if application syncs with server-side apply
if app . Spec . SyncPolicy != nil && app . Spec . SyncPolicy . SyncOptions . HasOption ( "ServerSideApply=true" ) {
diffConfigBuilder . WithStructuredMergeDiff ( true )
}
2022-05-31 17:21:22 +00:00
// it is necessary to ignore the error at this point to avoid creating duplicated
2021-12-22 16:57:48 +00:00
// application conditions as argo.StateDiffs will validate this diffConfig again.
diffConfig , _ := diffConfigBuilder . Build ( )
2021-01-28 00:13:29 +00:00
2021-12-22 16:57:48 +00:00
diffResults , err := argodiff . StateDiffs ( reconciliation . Live , reconciliation . Target , diffConfig )
2018-05-11 18:50:32 +00:00
if err != nil {
2019-08-20 15:43:29 +00:00
diffResults = & diff . DiffResultList { }
failedToLoadObjs = true
2024-12-20 16:22:28 +00:00
msg := "Failed to compare desired state to live state: " + err . Error ( )
2023-08-03 22:06:19 +00:00
conditions = append ( conditions , v1alpha1 . ApplicationCondition { Type : v1alpha1 . ApplicationConditionComparisonError , Message : msg , LastTransitionTime : & now } )
2018-05-11 18:50:32 +00:00
}
2020-03-16 18:51:59 +00:00
ts . AddCheckpoint ( "diff_ms" )
2018-07-10 21:45:18 +00:00
2018-12-04 10:52:57 +00:00
syncCode := v1alpha1 . SyncStatusCodeSynced
2020-05-15 17:01:18 +00:00
managedResources := make ( [ ] managedResource , len ( reconciliation . Target ) )
resourceSummaries := make ( [ ] v1alpha1 . ResourceStatus , len ( reconciliation . Target ) )
for i , targetObj := range reconciliation . Target {
liveObj := reconciliation . Live [ i ]
2019-06-07 15:46:11 +00:00
obj := liveObj
2018-11-28 21:38:02 +00:00
if obj == nil {
2019-06-07 15:46:11 +00:00
obj = targetObj
2018-11-28 21:38:02 +00:00
}
if obj == nil {
continue
}
gvk := obj . GroupVersionKind ( )
2025-05-15 08:26:47 +00:00
isSelfReferencedObj := m . isSelfReferencedObj ( liveObj , targetObj , app . GetName ( ) , v1alpha1 . TrackingMethod ( trackingMethod ) , installationID )
2022-07-06 18:12:34 +00:00
2018-12-04 01:39:55 +00:00
resState := v1alpha1 . ResourceStatus {
2019-07-02 16:43:29 +00:00
Namespace : obj . GetNamespace ( ) ,
Name : obj . GetName ( ) ,
Kind : gvk . Kind ,
Version : gvk . Version ,
Group : gvk . Group ,
2023-12-18 16:40:23 +00:00
Hook : isHook ( obj ) ,
2022-07-06 18:12:34 +00:00
RequiresPruning : targetObj == nil && liveObj != nil && isSelfReferencedObj ,
2024-10-24 07:08:24 +00:00
RequiresDeletionConfirmation : targetObj != nil && resourceutil . HasAnnotationOption ( targetObj , synccommon . AnnotationSyncOptions , synccommon . SyncOptionDeleteRequireConfirm ) ||
2026-01-09 15:40:52 +00:00
liveObj != nil && resourceutil . HasAnnotationOption ( liveObj , synccommon . AnnotationSyncOptions , synccommon . SyncOptionDeleteRequireConfirm ) ||
targetObj != nil && resourceutil . HasAnnotationOption ( targetObj , synccommon . AnnotationSyncOptions , synccommon . SyncOptionPruneRequireConfirm ) ||
liveObj != nil && resourceutil . HasAnnotationOption ( liveObj , synccommon . AnnotationSyncOptions , synccommon . SyncOptionPruneRequireConfirm ) ,
2018-05-11 18:50:32 +00:00
}
2022-10-13 22:13:04 +00:00
if targetObj != nil {
resState . SyncWave = int64 ( syncwaves . Wave ( targetObj ) )
2026-02-06 23:28:18 +00:00
} else if resState . Hook {
for _ , hookObj := range reconciliation . Hooks {
if hookObj . GetName ( ) == liveObj . GetName ( ) && hookObj . GetKind ( ) == liveObj . GetKind ( ) && hookObj . GetNamespace ( ) == liveObj . GetNamespace ( ) {
resState . SyncWave = int64 ( syncwaves . Wave ( hookObj ) )
break
}
}
2022-10-13 22:13:04 +00:00
}
2018-11-28 21:38:02 +00:00
2020-03-13 17:52:24 +00:00
var diffResult diff . DiffResult
if i < len ( diffResults . Diffs ) {
diffResult = diffResults . Diffs [ i ]
} else {
2020-06-08 22:12:35 +00:00
diffResult = diff . DiffResult { Modified : false , NormalizedLive : [ ] byte ( "{}" ) , PredictedLive : [ ] byte ( "{}" ) }
2020-03-13 17:52:24 +00:00
}
2023-09-13 15:45:06 +00:00
// For the case when a namespace is managed with `managedNamespaceMetadata` AND it has resource tracking
// enabled (e.g. someone manually adds resource tracking labels or annotations), we need to do some
// bookkeeping in order to ensure that it's not considered `OutOfSync` (since it does not exist in source
// control).
//
// This is in addition to the bookkeeping we do (see `isManagedNamespace` and its references) to prevent said
// namespace from being pruned.
isManagedNs := isManagedNamespace ( targetObj , app ) && liveObj == nil
2025-01-22 21:13:51 +00:00
switch {
case resState . Hook || ignore . Ignore ( obj ) || ( targetObj != nil && hookutil . Skip ( targetObj ) ) || ! isSelfReferencedObj :
2022-07-06 18:12:34 +00:00
// For resource hooks, skipped resources or objects that may have
// been created by another controller with annotations copied from
// the source object, don't store sync status, and do not affect
// overall sync status
2025-01-22 21:13:51 +00:00
case ! isManagedNs && ( diffResult . Modified || targetObj == nil || liveObj == nil ) :
2018-12-03 18:27:43 +00:00
// Set resource state to OutOfSync since one of the following is true:
// * target and live resource are different
// * target resource not defined and live resource is extra
// * target resource present but live resource is missing
2018-12-04 01:39:55 +00:00
resState . Status = v1alpha1 . SyncStatusCodeOutOfSync
2019-06-07 15:46:11 +00:00
// we ignore the status if the obj needs pruning AND we have the annotation
needsPruning := targetObj == nil && liveObj != nil
2025-03-27 16:37:52 +00:00
if ! needsPruning || ! resourceutil . HasAnnotationOption ( obj , common . AnnotationCompareOptions , "IgnoreExtraneous" ) {
2019-06-07 15:46:11 +00:00
syncCode = v1alpha1 . SyncStatusCodeOutOfSync
}
2025-01-22 21:13:51 +00:00
default :
2018-12-04 01:39:55 +00:00
resState . Status = v1alpha1 . SyncStatusCodeSynced
2018-05-11 18:50:32 +00:00
}
2019-12-26 22:08:14 +00:00
// set unknown status to all resource that are not permitted in the app project
2025-01-13 18:15:42 +00:00
isNamespaced , err := m . liveStateCache . IsNamespaced ( destCluster , gvk . GroupKind ( ) )
2025-12-03 20:55:28 +00:00
if ! project . IsGroupKindNamePermitted ( gvk . GroupKind ( ) , obj . GetName ( ) , isNamespaced && err == nil ) {
2019-12-26 22:08:14 +00:00
resState . Status = v1alpha1 . SyncStatusCodeUnknown
}
2020-06-29 23:33:13 +00:00
if isNamespaced && obj . GetNamespace ( ) == "" {
2023-06-01 16:48:09 +00:00
conditions = append ( conditions , v1alpha1 . ApplicationCondition { Type : v1alpha1 . ApplicationConditionInvalidSpecError , Message : fmt . Sprintf ( "Namespace for %s %s is missing." , obj . GetName ( ) , gvk . String ( ) ) , LastTransitionTime : & now } )
2020-06-29 23:33:13 +00:00
}
2019-10-29 18:44:34 +00:00
// we can't say anything about the status if we were unable to get the target objects
if failedToLoadObjs {
resState . Status = v1alpha1 . SyncStatusCodeUnknown
}
2021-01-28 00:13:29 +00:00
resourceVersion := ""
if liveObj != nil {
resourceVersion = liveObj . GetResourceVersion ( )
}
2018-12-03 18:27:43 +00:00
managedResources [ i ] = managedResource {
2021-01-28 00:13:29 +00:00
Name : resState . Name ,
Namespace : resState . Namespace ,
Group : resState . Group ,
Kind : resState . Kind ,
Version : resState . Version ,
Live : liveObj ,
Target : targetObj ,
Diff : diffResult ,
Hook : resState . Hook ,
ResourceVersion : resourceVersion ,
2018-05-11 18:50:32 +00:00
}
2018-11-28 21:38:02 +00:00
resourceSummaries [ i ] = resState
2018-05-11 18:50:32 +00:00
}
2018-11-28 21:38:02 +00:00
2018-07-12 19:40:21 +00:00
if failedToLoadObjs {
2018-12-04 10:52:57 +00:00
syncCode = v1alpha1 . SyncStatusCodeUnknown
2023-05-19 13:55:08 +00:00
} else if app . HasChangedManagedNamespaceMetadata ( ) {
syncCode = v1alpha1 . SyncStatusCodeOutOfSync
2018-07-12 19:40:21 +00:00
}
2022-12-16 20:47:08 +00:00
2025-07-16 17:39:30 +00:00
syncStatus . Status = syncCode
// Update the initial revision to the resolved manifest SHA
2022-12-16 20:47:08 +00:00
if hasMultipleSources {
2025-07-16 17:39:30 +00:00
syncStatus . Revisions = manifestRevisions
} else if len ( manifestRevisions ) > 0 {
syncStatus . Revision = manifestRevisions [ 0 ]
2018-12-04 10:52:57 +00:00
}
2022-12-16 20:47:08 +00:00
2020-03-16 18:51:59 +00:00
ts . AddCheckpoint ( "sync_ms" )
2018-12-04 10:52:57 +00:00
2022-08-17 21:03:24 +00:00
healthStatus , err := setApplicationHealth ( managedResources , resourceSummaries , resourceOverrides , app , m . persistResourceHealth )
2018-12-04 10:52:57 +00:00
if err != nil {
2024-12-20 16:22:28 +00:00
conditions = append ( conditions , v1alpha1 . ApplicationCondition { Type : v1alpha1 . ApplicationConditionComparisonError , Message : "error setting app health: " + err . Error ( ) , LastTransitionTime : & now } )
2018-12-04 10:52:57 +00:00
}
2020-06-22 16:21:53 +00:00
// Git has already performed the signature verification via its GPG interface, and the result is available
2020-10-20 01:21:06 +00:00
// in the manifest info received from the repository server. We now need to form our opinion about the result
2020-06-22 16:21:53 +00:00
// and stop processing if we do not agree about the outcome.
2023-06-20 16:08:21 +00:00
for _ , manifestInfo := range manifestInfos {
2022-12-16 20:47:08 +00:00
if gpg . IsGPGEnabled ( ) && verifySignature && manifestInfo != nil {
conditions = append ( conditions , verifyGnuPGSignature ( manifestInfo . Revision , project , manifestInfo ) ... )
}
2020-06-22 16:21:53 +00:00
}
2018-12-04 10:52:57 +00:00
compRes := comparisonResult {
2025-07-16 17:39:30 +00:00
syncStatus : syncStatus ,
2025-07-14 14:36:05 +00:00
healthStatus : healthStatus ,
resources : resourceSummaries ,
managedResources : managedResources ,
reconciliationResult : reconciliation ,
diffConfig : diffConfig ,
diffResultList : diffResults ,
hasPostDeleteHooks : hasPostDeleteHooks ,
2025-12-05 20:27:03 +00:00
hasPreDeleteHooks : hasPreDeleteHooks ,
2025-07-14 14:36:05 +00:00
revisionsMayHaveChanges : revisionsMayHaveChanges ,
2019-03-08 03:24:47 +00:00
}
2022-12-16 20:47:08 +00:00
if hasMultipleSources {
2023-06-20 16:08:21 +00:00
for _ , manifestInfo := range manifestInfos {
2023-06-01 16:48:09 +00:00
compRes . appSourceTypes = append ( compRes . appSourceTypes , v1alpha1 . ApplicationSourceType ( manifestInfo . SourceType ) )
2022-12-16 20:47:08 +00:00
}
} else {
2023-06-20 16:08:21 +00:00
for _ , manifestInfo := range manifestInfos {
2022-12-16 20:47:08 +00:00
compRes . appSourceType = v1alpha1 . ApplicationSourceType ( manifestInfo . SourceType )
break
}
2018-09-11 21:28:53 +00:00
}
2022-12-16 20:47:08 +00:00
2023-06-01 16:48:09 +00:00
app . Status . SetConditions ( conditions , map [ v1alpha1 . ApplicationConditionType ] bool {
v1alpha1 . ApplicationConditionComparisonError : true ,
v1alpha1 . ApplicationConditionSharedResourceWarning : true ,
v1alpha1 . ApplicationConditionRepeatedResourceWarning : true ,
v1alpha1 . ApplicationConditionExcludedResourceWarning : true ,
2019-10-11 00:26:53 +00:00
} )
2020-03-16 18:51:59 +00:00
ts . AddCheckpoint ( "health_ms" )
compRes . timings = ts . Timings ( )
2023-11-02 15:51:16 +00:00
return & compRes , nil
2018-05-11 18:50:32 +00:00
}
2023-11-29 16:08:29 +00:00
// useDiffCache will determine if the diff should be calculated based
// on the existing live state cache or not.
2023-12-18 20:37:13 +00:00
func useDiffCache ( noCache bool , manifestInfos [ ] * apiclient . ManifestResponse , sources [ ] v1alpha1 . ApplicationSource , app * v1alpha1 . Application , manifestRevisions [ ] string , statusRefreshTimeout time . Duration , serverSideDiff bool , log * log . Entry ) bool {
2023-11-29 16:08:29 +00:00
if noCache {
log . WithField ( "useDiffCache" , "false" ) . Debug ( "noCache is true" )
return false
}
2023-12-18 20:37:13 +00:00
refreshType , refreshRequested := app . IsRefreshRequested ( )
2023-11-29 16:08:29 +00:00
if refreshRequested {
2023-12-18 20:37:13 +00:00
log . WithField ( "useDiffCache" , "false" ) . Debugf ( "refresh type %s requested" , string ( refreshType ) )
2023-11-29 16:08:29 +00:00
return false
}
2023-12-18 20:37:13 +00:00
// serverSideDiff should still use cache even if status is expired.
// This is an attempt to avoid hitting k8s API server too frequently during
// app refresh with serverSideDiff is enabled. If there are negative side
// effects identified with this approach, the serverSideDiff should be removed
// from this condition.
if app . Status . Expired ( statusRefreshTimeout ) && ! serverSideDiff {
2023-11-29 16:08:29 +00:00
log . WithField ( "useDiffCache" , "false" ) . Debug ( "app.status.expired" )
return false
}
if len ( manifestInfos ) != len ( sources ) {
log . WithField ( "useDiffCache" , "false" ) . Debug ( "manifestInfos len != sources len" )
return false
}
revisionChanged := ! reflect . DeepEqual ( app . Status . GetRevisions ( ) , manifestRevisions )
if revisionChanged {
log . WithField ( "useDiffCache" , "false" ) . Debug ( "revisionChanged" )
return false
}
2025-07-03 02:31:19 +00:00
if ! specEqualsCompareTo ( app . Spec , sources , app . Status . Sync . ComparedTo ) {
2023-11-29 16:08:29 +00:00
log . WithField ( "useDiffCache" , "false" ) . Debug ( "specChanged" )
return false
}
log . WithField ( "useDiffCache" , "true" ) . Debug ( "using diff cache" )
return true
}
2024-10-18 13:36:42 +00:00
// specEqualsCompareTo compares the application spec to the comparedTo status. It normalizes the destination to match
// the comparedTo destination before comparing. It does not mutate the original spec or comparedTo.
2025-07-03 02:31:19 +00:00
func specEqualsCompareTo ( spec v1alpha1 . ApplicationSpec , sources [ ] v1alpha1 . ApplicationSource , comparedTo v1alpha1 . ComparedTo ) bool {
2024-10-18 13:36:42 +00:00
// Make a copy to be sure we don't mutate the original.
specCopy := spec . DeepCopy ( )
2025-07-03 02:31:19 +00:00
compareToSpec := specCopy . BuildComparedToStatus ( sources )
return reflect . DeepEqual ( comparedTo , compareToSpec )
2024-10-18 13:36:42 +00:00
}
2023-12-22 16:50:33 +00:00
func ( m * appStateManager ) persistRevisionHistory (
app * v1alpha1 . Application ,
revision string ,
source v1alpha1 . ApplicationSource ,
revisions [ ] string ,
sources [ ] v1alpha1 . ApplicationSource ,
hasMultipleSources bool ,
startedAt metav1 . Time ,
initiatedBy v1alpha1 . OperationInitiator ,
) error {
2019-03-04 08:56:36 +00:00
var nextID int64
2018-05-15 18:05:46 +00:00
if len ( app . Status . History ) > 0 {
2020-06-10 12:28:40 +00:00
nextID = app . Status . History . LastRevisionHistory ( ) . ID + 1
2018-05-11 18:50:32 +00:00
}
2022-12-16 20:47:08 +00:00
if hasMultipleSources {
app . Status . History = append ( app . Status . History , v1alpha1 . RevisionHistory {
DeployedAt : metav1 . NewTime ( time . Now ( ) . UTC ( ) ) ,
DeployStartedAt : & startedAt ,
ID : nextID ,
Sources : sources ,
Revisions : revisions ,
2023-12-22 16:50:33 +00:00
InitiatedBy : initiatedBy ,
2022-12-16 20:47:08 +00:00
} )
} else {
app . Status . History = append ( app . Status . History , v1alpha1 . RevisionHistory {
Revision : revision ,
DeployedAt : metav1 . NewTime ( time . Now ( ) . UTC ( ) ) ,
DeployStartedAt : & startedAt ,
ID : nextID ,
Source : source ,
2023-12-22 16:50:33 +00:00
InitiatedBy : initiatedBy ,
2022-12-16 20:47:08 +00:00
} )
}
2018-05-11 18:50:32 +00:00
2019-12-13 19:14:43 +00:00
app . Status . History = app . Status . History . Trunc ( app . Spec . GetRevisionHistoryLimit ( ) )
2018-05-11 18:50:32 +00:00
2018-12-04 10:52:57 +00:00
patch , err := json . Marshal ( map [ string ] map [ string ] [ ] v1alpha1 . RevisionHistory {
2018-05-11 18:50:32 +00:00
"status" : {
2019-12-13 19:14:43 +00:00
"history" : app . Status . History ,
2018-05-11 18:50:32 +00:00
} ,
} )
if err != nil {
2022-10-19 19:21:32 +00:00
return fmt . Errorf ( "error marshaling revision history patch: %w" , err )
2018-05-11 18:50:32 +00:00
}
2022-08-10 09:39:10 +00:00
_ , err = m . appclientset . ArgoprojV1alpha1 ( ) . Applications ( app . Namespace ) . Patch ( context . Background ( ) , app . Name , types . MergePatchType , patch , metav1 . PatchOptions { } )
2018-05-11 18:50:32 +00:00
return err
}
2020-05-15 17:01:18 +00:00
// NewAppStateManager creates new instance of AppStateManager
2018-05-11 18:50:32 +00:00
func NewAppStateManager (
2018-05-14 18:36:08 +00:00
db db . ArgoDB ,
2018-05-11 18:50:32 +00:00
appclientset appclientset . Interface ,
2019-07-13 00:17:23 +00:00
repoClientset apiclient . Clientset ,
2018-05-11 18:50:32 +00:00
namespace string ,
2018-09-10 17:14:14 +00:00
kubectl kubeutil . Kubectl ,
2025-02-07 17:26:03 +00:00
onKubectlRun kubeutil . OnKubectlRunFunc ,
2019-06-21 22:59:05 +00:00
settingsMgr * settings . SettingsManager ,
2018-11-28 21:38:02 +00:00
liveStateCache statecache . LiveStateCache ,
2019-04-29 19:42:59 +00:00
metricsServer * metrics . MetricsServer ,
2021-01-28 00:13:29 +00:00
cache * appstatecache . Cache ,
statusRefreshTimeout time . Duration ,
2021-10-01 16:18:01 +00:00
resourceTracking argo . ResourceTracking ,
2022-08-17 21:03:24 +00:00
persistResourceHealth bool ,
2023-11-02 15:51:16 +00:00
repoErrorGracePeriod time . Duration ,
2023-12-18 20:37:13 +00:00
serverSideDiff bool ,
2024-04-26 09:24:02 +00:00
ignoreNormalizerOpts normalizers . IgnoreNormalizerOpts ,
2018-05-11 18:50:32 +00:00
) AppStateManager {
2018-09-20 16:48:54 +00:00
return & appStateManager {
2022-08-17 21:03:24 +00:00
liveStateCache : liveStateCache ,
cache : cache ,
db : db ,
appclientset : appclientset ,
kubectl : kubectl ,
2025-02-07 17:26:03 +00:00
onKubectlRun : onKubectlRun ,
2022-08-17 21:03:24 +00:00
repoClientset : repoClientset ,
namespace : namespace ,
settingsMgr : settingsMgr ,
metricsServer : metricsServer ,
statusRefreshTimeout : statusRefreshTimeout ,
resourceTracking : resourceTracking ,
persistResourceHealth : persistResourceHealth ,
2023-11-02 15:51:16 +00:00
repoErrorGracePeriod : repoErrorGracePeriod ,
2023-12-18 20:37:13 +00:00
serverSideDiff : serverSideDiff ,
2024-04-26 09:24:02 +00:00
ignoreNormalizerOpts : ignoreNormalizerOpts ,
2018-05-11 18:50:32 +00:00
}
}
2022-07-06 18:12:34 +00:00
// isSelfReferencedObj returns whether the given obj is managed by the application
2022-11-03 19:02:13 +00:00
// according to the values of the tracking id (aka app instance value) annotation.
// It returns true when all of the properties of the tracking id (app name, namespace,
// group and kind) match the properties of the live object, or if the tracking method
// used does not provide the required properties for matching.
// Reference: https://github.com/argoproj/argo-cd/issues/8683
2025-01-07 15:12:56 +00:00
func ( m * appStateManager ) isSelfReferencedObj ( live , config * unstructured . Unstructured , appName string , trackingMethod v1alpha1 . TrackingMethod , installationID string ) bool {
2022-11-03 19:02:13 +00:00
if live == nil {
2022-07-06 18:12:34 +00:00
return true
}
// If tracking method doesn't contain required metadata for this check,
// we are not able to determine and just assume the object to be managed.
2025-05-15 08:26:47 +00:00
if trackingMethod == v1alpha1 . TrackingMethodLabel {
2022-07-06 18:12:34 +00:00
return true
}
2022-11-03 19:02:13 +00:00
// config != nil is the best-case scenario for constructing an accurate
// Tracking ID. `config` is the "desired state" (from git/helm/etc.).
// Using the desired state is important when there is an ApiGroup upgrade.
// When upgrading, the comparison must be made with the new tracking ID.
// Example:
// live resource annotation will be:
// ingress-app:extensions/Ingress:default/some-ingress
// when it should be:
// ingress-app:networking.k8s.io/Ingress:default/some-ingress
// More details in: https://github.com/argoproj/argo-cd/pull/11012
var aiv argo . AppInstanceValue
if config != nil {
aiv = argo . UnstructuredToAppInstanceValue ( config , appName , "" )
return isSelfReferencedObj ( live , aiv )
}
// If config is nil then compare the live resource with the value
// of the annotation. In this case, in order to validate if obj is
// managed by this application, the values from the annotation have
// to match the properties from the live object. Cluster scoped objects
// carry the app's destination namespace in the tracking annotation,
// but are unique in GVK + name combination.
2025-01-07 15:12:56 +00:00
appInstance := m . resourceTracking . GetAppInstance ( live , trackingMethod , installationID )
2022-07-06 18:12:34 +00:00
if appInstance != nil {
2022-11-03 19:02:13 +00:00
return isSelfReferencedObj ( live , * appInstance )
2022-07-06 18:12:34 +00:00
}
return true
}
2022-11-03 19:02:13 +00:00
// isSelfReferencedObj returns true if the given Tracking ID (`aiv`) matches
// the given object. It returns false when the ID doesn't match. This sometimes
// happens when a tracking label or annotation gets accidentally copied to a
// different resource.
func isSelfReferencedObj ( obj * unstructured . Unstructured , aiv argo . AppInstanceValue ) bool {
return ( obj . GetNamespace ( ) == aiv . Namespace || obj . GetNamespace ( ) == "" ) &&
obj . GetName ( ) == aiv . Name &&
obj . GetObjectKind ( ) . GroupVersionKind ( ) . Group == aiv . Group &&
obj . GetObjectKind ( ) . GroupVersionKind ( ) . Kind == aiv . Kind
}