diff --git a/kubewatch/pkg/resource/application/handler.go b/kubewatch/pkg/resource/application/handler.go index 614262585..44805a67e 100644 --- a/kubewatch/pkg/resource/application/handler.go +++ b/kubewatch/pkg/resource/application/handler.go @@ -53,52 +53,36 @@ func (impl *InformerImpl) GetSharedInformer(clusterLabels *informerBean.ClusterL _, err := acdInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{ AddFunc: func(obj interface{}) { - impl.logger.Debug("app added") - - if app, ok := obj.(*applicationBean.Application); ok { - impl.logger.Debugf("new app detected: %s, status: %s", app.Name, app.Status.Health.Status) - } + impl.logger.Debugw("ARGO_CD_APPLICATION: new application object found") }, UpdateFunc: func(old interface{}, new interface{}) { - impl.logger.Debug("app update detected") + impl.logger.Debugw("ARGO_CD_APPLICATION: application object update detected") statusTime := time.Now() if oldApp, ok := old.(*applicationBean.Application); ok { if newApp, ok := new.(*applicationBean.Application); ok { - if newApp.Status.History != nil && len(newApp.Status.History) > 0 { - if oldApp.Status.History == nil || len(oldApp.Status.History) == 0 { - impl.logger.Debug("new deployment detected") + // Check if the application has a new deployment history + if isNewDeploymentHistoryFound(oldApp, newApp) { + impl.logger.Debugw("ARGO_CD_APPLICATION: new deployment detected", "appName", newApp.Name, "status", newApp.Status.Health.Status) + impl.sendAppUpdate(clusterLabels.ClusterId, newApp, statusTime) + } else { + if IsApplicationObjectUpdated(impl.logger, oldApp, newApp) { impl.sendAppUpdate(clusterLabels.ClusterId, newApp, statusTime) + impl.logger.Debugw("ARGO_CD_APPLICATION: send update event for application object", "appName", oldApp.Name) } else { - impl.logger.Debugf("old deployment detected for update: %s, status:%s", oldApp.Name, oldApp.Status.Health.Status) - oldRevision := oldApp.Status.Sync.Revision - newRevision := newApp.Status.Sync.Revision - oldStatus := string(oldApp.Status.Health.Status) - newStatus := string(newApp.Status.Health.Status) - newSyncStatus := string(newApp.Status.Sync.Status) - oldSyncStatus := string(oldApp.Status.Sync.Status) - if (oldRevision != newRevision) || (oldStatus != newStatus) || (newSyncStatus != oldSyncStatus) { - impl.sendAppUpdate(clusterLabels.ClusterId, newApp, statusTime) - impl.logger.Debug("send update app:" + oldApp.Name + ", oldRevision: " + oldRevision + ", newRevision:" + - newRevision + ", oldStatus: " + oldStatus + ", newStatus: " + newStatus + - ", newSyncStatus: " + newSyncStatus + ", oldSyncStatus: " + oldSyncStatus) - } else { - impl.logger.Debug("skip updating app:" + oldApp.Name + ", oldRevision: " + oldRevision + ", newRevision:" + - newRevision + ", oldStatus: " + oldStatus + ", newStatus: " + newStatus + - ", newSyncStatus: " + newSyncStatus + ", oldSyncStatus: " + oldSyncStatus) - } + impl.logger.Debugw("ARGO_CD_APPLICATION: skip updating event for application object", "appName", oldApp.Name) } } } else { - log.Println("app update detected, but skip updating, there is no new app") + impl.logger.Errorw("ARGO_CD_APPLICATION: application object update detected, but could not cast to application object", "oldObj", old, "newObj", new) } } else { - log.Println("app update detected, but skip updating, there is no old app") + impl.logger.Errorw("ARGO_CD_APPLICATION: application object update detected, but could not cast to application object", "oldObj", old) } }, DeleteFunc: func(obj interface{}) { if app, ok := obj.(*applicationBean.Application); ok { statusTime := time.Now() - impl.logger.Debugf("app delete detected: %s, status:%s", app.Name, app.Status.Health.Status) + impl.logger.Debugw("ARGO_CD_APPLICATION: application object delete detected", "appName", app.Name, "status", app.Status.Health.Status) impl.sendAppDelete(clusterLabels.ClusterId, app, statusTime) } }, diff --git a/kubewatch/pkg/resource/application/util.go b/kubewatch/pkg/resource/application/util.go new file mode 100644 index 000000000..4522196ac --- /dev/null +++ b/kubewatch/pkg/resource/application/util.go @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2024. Devtron Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package application + +import ( + "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" + synccommon "github.com/argoproj/gitops-engine/pkg/sync/common" + "go.uber.org/zap" +) + +// isNewDeploymentHistoryFound checks +// if a new deployment is found by +// comparing the history of old and new application objects. +func isNewDeploymentHistoryFound(oldAppObj, newAppObj *v1alpha1.Application) bool { + if len(oldAppObj.Status.History) < len(newAppObj.Status.History) { + return true + } else if len(oldAppObj.Status.History) != 0 && len(oldAppObj.Status.History) == len(newAppObj.Status.History) { + return oldAppObj.Status.History.LastRevisionHistory().ID < newAppObj.Status.History.LastRevisionHistory().ID + } + return false +} + +// getApplicationLastSyncedResourcesCount returns the count of resources that were last synced in the application. +func getApplicationLastSyncedResourcesCount(appObj *v1alpha1.Application) int { + if appObj.Status.OperationState == nil || appObj.Status.OperationState.SyncResult == nil { + return 0 + } + return len(appObj.Status.OperationState.SyncResult.Resources) +} + +func getApplicationOperationRevision(appObj *v1alpha1.Application) string { + if appObj.Status.OperationState == nil || appObj.Status.OperationState.Operation.Sync == nil { + return "" + } + return appObj.Status.OperationState.Operation.Sync.Revision +} + +func getApplicationOperationPhase(appObj *v1alpha1.Application) synccommon.OperationPhase { + if appObj.Status.OperationState == nil { + return "" + } + return appObj.Status.OperationState.Phase +} + +func IsApplicationObjectUpdated(logger *zap.SugaredLogger, oldAppObj, newAppObj *v1alpha1.Application) bool { + // Check if the application sync revision has changed + oldRevision := oldAppObj.Status.Sync.Revision + newRevision := newAppObj.Status.Sync.Revision + // Check if the operation revision has changed + oldOperationRevision := getApplicationOperationRevision(oldAppObj) + newOperationRevision := getApplicationOperationRevision(newAppObj) + // Check if the health status has changed + oldStatus := string(oldAppObj.Status.Health.Status) + newStatus := string(newAppObj.Status.Health.Status) + // Check if the operation phase has changed + oldOperationPhase := getApplicationOperationPhase(oldAppObj) + newOperationPhase := getApplicationOperationPhase(newAppObj) + // Check if the last synced resources count has changed + oldAppLastSyncedResourcesCount := getApplicationLastSyncedResourcesCount(oldAppObj) + newAppLastSyncedResourcesCount := getApplicationLastSyncedResourcesCount(newAppObj) + + logger.Debugw("ARGO_CD_APPLICATION: update event captured with", "oldRevision", oldRevision, "newRevision", newRevision, + "oldOperationRevision", oldOperationRevision, "newOperationRevision", newOperationRevision, + "oldStatus", oldStatus, "newStatus", newStatus, + "oldOperationPhase", oldOperationPhase, "newOperationPhase", newOperationPhase, + "oldAppLastSyncedResourcesCount", oldAppLastSyncedResourcesCount, "newAppLastSyncedResourcesCount", newAppLastSyncedResourcesCount) + + return (oldRevision != newRevision) || (newOperationRevision != oldOperationRevision) || + (oldStatus != newStatus) || (oldOperationPhase != newOperationPhase) || + (oldAppLastSyncedResourcesCount != newAppLastSyncedResourcesCount) +}