From 9ecb8eb2495e839d6f33a0f294d1fb8bd09a0aff Mon Sep 17 00:00:00 2001 From: oleksandr-codefresh Date: Tue, 10 Sep 2024 19:39:16 +0300 Subject: [PATCH 01/20] event-reporter: added utils methods to retrieve revisions metadata for application --- event_reporter/reporter/app_revision.go | 46 ++++++++++++- event_reporter/utils/app_revision.go | 85 +++++++++++++++++++++++++ 2 files changed, 130 insertions(+), 1 deletion(-) diff --git a/event_reporter/reporter/app_revision.go b/event_reporter/reporter/app_revision.go index 82cf90371b6f7..7c5747ae91966 100644 --- a/event_reporter/reporter/app_revision.go +++ b/event_reporter/reporter/app_revision.go @@ -2,9 +2,10 @@ package reporter import ( "context" - + "github.com/argoproj/argo-cd/v2/event_reporter/utils" "github.com/argoproj/argo-cd/v2/pkg/apiclient/application" appv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" + log "github.com/sirupsen/logrus" ) func (s *applicationEventReporter) getApplicationRevisionDetails(ctx context.Context, a *appv1.Application, revision string) (*appv1.RevisionMetadata, error) { @@ -16,3 +17,46 @@ func (s *applicationEventReporter) getApplicationRevisionDetails(ctx context.Con Project: &project, }) } + +func (s *applicationEventReporter) getCommitRevisionsDetails(ctx context.Context, a *appv1.Application, revisions []string) ([]*appv1.RevisionMetadata, error) { + project := a.Spec.GetProject() + rms := make([]*appv1.RevisionMetadata, 0) + + for _, revision := range revisions { + rm, err := s.applicationServiceClient.RevisionMetadata(ctx, &application.RevisionMetadataQuery{ + Name: &a.Name, + AppNamespace: &a.Namespace, + Revision: &revision, + Project: &project, + }) + if err != nil { + return nil, err + } + rms = append(rms, rm) + } + + return rms, nil +} + +func (s *applicationEventReporter) getApplicationRevisionsMetadata(ctx context.Context, logCtx *log.Entry, a *appv1.Application) (*utils.AppSyncRevisionsMetadata, error) { + result := &utils.AppSyncRevisionsMetadata{} + + // can be the latest revision of repository + operationSyncRevisionsMetadata, err := s.getCommitRevisionsDetails(ctx, a, utils.GetOperationSyncRevisions(a)) + + if err != nil { + logCtx.WithError(err).Warnf("failed to get application(%s) revisions metadata, resuming", a.GetName()) + } + + if operationSyncRevisionsMetadata != nil { + result.SyncRevisions = operationSyncRevisionsMetadata + } + // latest revision of repository where changes to app resource were actually made; empty if no changeRevisionі present + operationChangeRevisionsMetadata, err := s.getCommitRevisionsDetails(ctx, a, utils.GetOperationChangeRevisions(a)) + + if err == nil && operationChangeRevisionsMetadata != nil { + result.ChangeRevisions = operationChangeRevisionsMetadata + } + + return result, nil +} diff --git a/event_reporter/utils/app_revision.go b/event_reporter/utils/app_revision.go index 0f0027eacbd4c..d9ef8376fdb22 100644 --- a/event_reporter/utils/app_revision.go +++ b/event_reporter/utils/app_revision.go @@ -1,11 +1,23 @@ package utils import ( + "encoding/json" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" appv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" ) +type AppSyncRevisionsMetadata struct { + SyncRevisions []*appv1.RevisionMetadata `json:"syncRevisions" protobuf:"bytes,1,name=syncRevisions"` + ChangeRevisions []*appv1.RevisionMetadata `json:"changeRevisions" protobuf:"bytes,2,name=changeRevisions"` +} + +type RevisionsData struct { + Revision string `json:"revision,omitempty" protobuf:"bytes,1,opt,name=revision"` + Revisions []string `json:"revisions,omitempty" protobuf:"bytes,2,opt,name=revisions"` +} + func GetLatestAppHistoryId(a *appv1.Application) int64 { if lastHistory := getLatestAppHistoryItem(a); lastHistory != nil { return lastHistory.ID @@ -46,6 +58,59 @@ func GetOperationRevision(a *appv1.Application) string { return revision } +func GetOperationSyncRevisions(a *appv1.Application) []string { + var revisions []string + if a != nil { + // this value will be used in case if application hasn't resources, like empty gitsource + revisions = getRevisions(RevisionsData{ + Revision: a.Status.Sync.Revision, + Revisions: a.Status.Sync.Revisions, + }) + + if a.Status.OperationState != nil && a.Status.OperationState.Operation.Sync != nil { + revisions = getRevisions(RevisionsData{ + Revision: a.Status.OperationState.Operation.Sync.Revision, + Revisions: a.Status.OperationState.Operation.Sync.Revisions, + }) + } else if a.Operation != nil && a.Operation.Sync != nil { + revisions = getRevisions(RevisionsData{ + Revision: a.Operation.Sync.Revision, + Revisions: a.Operation.Sync.Revisions, + }) + } + } + + return revisions +} + +// for monorepo support: list with revisions where actual changes to source directory were committed +func GetOperationChangeRevisions(a *appv1.Application) []string { + revisions := []string{} + if a != nil { + // this value will be used in case if application hasn't resources, like empty gitsource + // TODO uncomment later + //if a.Status.OperationState != nil && a.Status.OperationState.Operation.Sync != nil { + // if a.Status.OperationState.Operation.Sync.ChangeRevision != "" { + // revisions = []string{a.Status.OperationState.Operation.Sync.ChangeRevision} + // } + //} else if a.Operation != nil && a.Operation.Sync != nil { + // if a.Operation.Sync.ChangeRevision != "" { + // revisions = []string{a.Operation.Sync.ChangeRevision} + // } + //} + } + + return revisions +} + +func getRevisions(rd RevisionsData) []string { + if rd.Revisions != nil { + return rd.Revisions + } + + return []string{rd.Revision} +} + func AddCommitDetailsToLabels(u *unstructured.Unstructured, revisionMetadata *appv1.RevisionMetadata) *unstructured.Unstructured { if revisionMetadata == nil || u == nil { return u @@ -61,3 +126,23 @@ func AddCommitDetailsToLabels(u *unstructured.Unstructured, revisionMetadata *ap return u } + +func AddCommitsDetailsToAnnotations(unstrApp *unstructured.Unstructured, revisionsMetadata *AppSyncRevisionsMetadata) *unstructured.Unstructured { + if revisionsMetadata == nil || unstrApp == nil { + return unstrApp + } + + if field, _, _ := unstructured.NestedFieldCopy(unstrApp.Object, "metadata", "annotations"); field == nil { + _ = unstructured.SetNestedStringMap(unstrApp.Object, map[string]string{}, "metadata", "annotations") + } + + jsonRevisionsMetadata, err := json.Marshal(revisionsMetadata) + + if err != nil { + return unstrApp + } + + _ = unstructured.SetNestedField(unstrApp.Object, jsonRevisionsMetadata, "metadata", "annotations", "app.meta.revisions-metadata") + + return unstrApp +} From 37f5a05bb90277212c52e0a218a6f3273fa30235 Mon Sep 17 00:00:00 2001 From: oleksandr-codefresh Date: Wed, 11 Sep 2024 09:25:14 +0300 Subject: [PATCH 02/20] event-reporter: report all revisions metadata instead single to support multisourced apps --- event_reporter/reporter/app_revision.go | 51 +++++++++------ .../reporter/application_event_reporter.go | 32 +++++----- event_reporter/reporter/event_payload.go | 63 ++++++++++--------- event_reporter/utils/app_revision.go | 40 +++++++++++- .../application/v1alpha1/types_codefresh.go | 16 +++++ 5 files changed, 136 insertions(+), 66 deletions(-) diff --git a/event_reporter/reporter/app_revision.go b/event_reporter/reporter/app_revision.go index 7c5747ae91966..cf40c8c578ccc 100644 --- a/event_reporter/reporter/app_revision.go +++ b/event_reporter/reporter/app_revision.go @@ -8,14 +8,19 @@ import ( log "github.com/sirupsen/logrus" ) -func (s *applicationEventReporter) getApplicationRevisionDetails(ctx context.Context, a *appv1.Application, revision string) (*appv1.RevisionMetadata, error) { - project := a.Spec.GetProject() - return s.applicationServiceClient.RevisionMetadata(ctx, &application.RevisionMetadataQuery{ - Name: &a.Name, - AppNamespace: &a.Namespace, - Revision: &revision, - Project: &project, - }) +// treats multi-sourced apps as single source and gets first revision details +func getApplicationLegacyRevisionDetails(a *appv1.Application, revisionsMetadata *utils.AppSyncRevisionsMetadata) *appv1.RevisionMetadata { + _, sourceIdx := a.Spec.GetNonRefSource() + + if sourceIdx == -1 { // single source app + sourceIdx = 0 + } + + if revisionsMetadata.SyncRevisions == nil || len(revisionsMetadata.SyncRevisions) == 0 { + return nil + } + + return revisionsMetadata.SyncRevisions[sourceIdx] } func (s *applicationEventReporter) getCommitRevisionsDetails(ctx context.Context, a *appv1.Application, revisions []string) ([]*appv1.RevisionMetadata, error) { @@ -41,21 +46,27 @@ func (s *applicationEventReporter) getCommitRevisionsDetails(ctx context.Context func (s *applicationEventReporter) getApplicationRevisionsMetadata(ctx context.Context, logCtx *log.Entry, a *appv1.Application) (*utils.AppSyncRevisionsMetadata, error) { result := &utils.AppSyncRevisionsMetadata{} - // can be the latest revision of repository - operationSyncRevisionsMetadata, err := s.getCommitRevisionsDetails(ctx, a, utils.GetOperationSyncRevisions(a)) + if source, _ := a.Spec.GetNonRefSource(); !source.IsHelm() && (a.Status.Sync.Revision != "" || a.Status.Sync.Revisions != nil || (a.Status.History != nil && len(a.Status.History) > 0)) { + // can be the latest revision of repository + operationSyncRevisionsMetadata, err := s.getCommitRevisionsDetails(ctx, a, utils.GetOperationSyncRevisions(a)) - if err != nil { - logCtx.WithError(err).Warnf("failed to get application(%s) revisions metadata, resuming", a.GetName()) - } + if err != nil { + logCtx.WithError(err).Warnf("failed to get application(%s) sync revisions metadata, resuming", a.GetName()) + } - if operationSyncRevisionsMetadata != nil { - result.SyncRevisions = operationSyncRevisionsMetadata - } - // latest revision of repository where changes to app resource were actually made; empty if no changeRevisionі present - operationChangeRevisionsMetadata, err := s.getCommitRevisionsDetails(ctx, a, utils.GetOperationChangeRevisions(a)) + if err == nil && operationSyncRevisionsMetadata != nil { + result.SyncRevisions = operationSyncRevisionsMetadata + } + // latest revision of repository where changes to app resource were actually made; empty if no changeRevisionі present + operationChangeRevisionsMetadata, err := s.getCommitRevisionsDetails(ctx, a, utils.GetOperationChangeRevisions(a)) - if err == nil && operationChangeRevisionsMetadata != nil { - result.ChangeRevisions = operationChangeRevisionsMetadata + if err != nil { + logCtx.WithError(err).Warnf("failed to get application(%s) change revisions metadata, resuming", a.GetName()) + } + + if err == nil && operationChangeRevisionsMetadata != nil { + result.ChangeRevisions = operationChangeRevisionsMetadata + } } return result, nil diff --git a/event_reporter/reporter/application_event_reporter.go b/event_reporter/reporter/application_event_reporter.go index 6524655ab22eb..6feabf1e569e2 100644 --- a/event_reporter/reporter/application_event_reporter.go +++ b/event_reporter/reporter/application_event_reporter.go @@ -166,14 +166,13 @@ func (s *applicationEventReporter) StreamApplicationEvents( // helm app hasnt revision // TODO: add check if it helm application - parentOperationRevision := utils.GetOperationRevision(parentApplicationEntity) - parentRevisionMetadata, err := s.getApplicationRevisionDetails(ctx, parentApplicationEntity, parentOperationRevision) + parentAppSyncRevisionsMetadata, err := s.getApplicationRevisionsMetadata(ctx, logCtx, parentApplicationEntity) if err != nil { logCtx.WithError(err).Warn("failed to get parent application's revision metadata, resuming") } utils.SetHealthStatusIfMissing(rs) - err = s.processResource(ctx, *rs, parentApplicationEntity, logCtx, ts, parentDesiredManifests, appTree, manifestGenErr, a, parentRevisionMetadata, appInstanceLabelKey, trackingMethod, desiredManifests.ApplicationVersions) + err = s.processResource(ctx, *rs, parentApplicationEntity, logCtx, ts, parentDesiredManifests, appTree, manifestGenErr, a, parentAppSyncRevisionsMetadata, appInstanceLabelKey, trackingMethod, desiredManifests.ApplicationVersions) if err != nil { s.metricsServer.IncErroredEventsCounter(metrics.MetricChildAppEventType, metrics.MetricEventUnknownErrorType, a.Name) return err @@ -203,7 +202,7 @@ func (s *applicationEventReporter) StreamApplicationEvents( s.metricsServer.ObserveEventProcessingDurationHistogramDuration(a.Name, metrics.MetricParentAppEventType, reconcileDuration) } - revisionMetadata, _ := s.getApplicationRevisionDetails(ctx, a, utils.GetOperationRevision(a)) + revisionsMetadata, _ := s.getApplicationRevisionsMetadata(ctx, logCtx, a) // for each resource in the application get desired and actual state, // then stream the event for _, rs := range a.Status.Resources { @@ -215,7 +214,7 @@ func (s *applicationEventReporter) StreamApplicationEvents( s.metricsServer.IncCachedIgnoredEventsCounter(metrics.MetricResourceEventType, a.Name) continue } - err := s.processResource(ctx, rs, a, logCtx, ts, desiredManifests, appTree, manifestGenErr, nil, revisionMetadata, appInstanceLabelKey, trackingMethod, nil) + err := s.processResource(ctx, rs, a, logCtx, ts, desiredManifests, appTree, manifestGenErr, nil, revisionsMetadata, appInstanceLabelKey, trackingMethod, nil) if err != nil { s.metricsServer.IncErroredEventsCounter(metrics.MetricResourceEventType, metrics.MetricEventUnknownErrorType, a.Name) return err @@ -227,21 +226,22 @@ func (s *applicationEventReporter) StreamApplicationEvents( func (s *applicationEventReporter) getAppForResourceReporting( rs appv1.ResourceStatus, ctx context.Context, + logCtx *log.Entry, a *appv1.Application, - revisionMetadata *appv1.RevisionMetadata, -) (*appv1.Application, *appv1.RevisionMetadata) { + syncRevisionsMetadata *utils.AppSyncRevisionsMetadata, +) (*appv1.Application, *utils.AppSyncRevisionsMetadata) { if rs.Kind != "Rollout" { // for rollout it's crucial to report always correct operationSyncRevision - return a, revisionMetadata + return a, syncRevisionsMetadata } latestAppStatus, err := s.appLister.Applications(a.Namespace).Get(a.Name) if err != nil { - return a, revisionMetadata + return a, syncRevisionsMetadata } - revisionMetadataToReport, err := s.getApplicationRevisionDetails(ctx, latestAppStatus, utils.GetOperationRevision(latestAppStatus)) + revisionMetadataToReport, err := s.getApplicationRevisionsMetadata(ctx, logCtx, latestAppStatus) if err != nil { - return a, revisionMetadata + return a, syncRevisionsMetadata } return latestAppStatus, revisionMetadataToReport @@ -257,7 +257,7 @@ func (s *applicationEventReporter) processResource( appTree *appv1.ApplicationTree, manifestGenErr bool, originalApplication *appv1.Application, - revisionMetadata *appv1.RevisionMetadata, + revisionsMetadata *utils.AppSyncRevisionsMetadata, appInstanceLabelKey string, trackingMethod appv1.TrackingMethod, applicationVersions *apiclient.ApplicationVersions, @@ -283,12 +283,12 @@ func (s *applicationEventReporter) processResource( return nil } - parentApplicationToReport, revisionMetadataToReport := s.getAppForResourceReporting(rs, ctx, parentApplication, revisionMetadata) + parentApplicationToReport, revisionMetadataToReport := s.getAppForResourceReporting(rs, ctx, logCtx, parentApplication, revisionsMetadata) - var originalAppRevisionMetadata *appv1.RevisionMetadata = nil + var originalAppRevisionMetadata *utils.AppSyncRevisionsMetadata = nil if originalApplication != nil { - originalAppRevisionMetadata, _ = s.getApplicationRevisionDetails(ctx, originalApplication, utils.GetOperationRevision(originalApplication)) + originalAppRevisionMetadata, _ = s.getApplicationRevisionsMetadata(ctx, logCtx, originalApplication) } ev, err := getResourceEventPayload(parentApplicationToReport, &rs, actualState, desiredState, appTree, manifestGenErr, ts, originalApplication, revisionMetadataToReport, originalAppRevisionMetadata, appInstanceLabelKey, trackingMethod, applicationVersions) @@ -305,7 +305,7 @@ func (s *applicationEventReporter) processResource( appName = appRes.Name } else { utils.LogWithResourceStatus(logCtx, rs).Info("streaming resource event") - appName = rs.Name + appName = parentApplication.Name } if err := s.codefreshClient.SendEvent(ctx, appName, ev); err != nil { diff --git a/event_reporter/reporter/event_payload.go b/event_reporter/reporter/event_payload.go index ecaa2f45c3e39..5097fe8b22723 100644 --- a/event_reporter/reporter/event_payload.go +++ b/event_reporter/reporter/event_payload.go @@ -28,8 +28,8 @@ func getResourceEventPayload( manifestGenErr bool, ts string, originalApplication *appv1.Application, // passed when rs is application - revisionMetadata *appv1.RevisionMetadata, - originalAppRevisionMetadata *appv1.RevisionMetadata, // passed when rs is application + revisionsMetadata *utils.AppSyncRevisionsMetadata, + originalAppRevisionsMetadata *utils.AppSyncRevisionsMetadata, // passed when rs is application appInstanceLabelKey string, trackingMethod appv1.TrackingMethod, applicationVersions *apiclient.ApplicationVersions, @@ -50,11 +50,15 @@ func getResourceEventPayload( object := []byte(*actualState.Manifest) - if originalAppRevisionMetadata != nil && len(object) != 0 { + if originalAppRevisionsMetadata != nil && len(object) != 0 { actualObject, err := appv1.UnmarshalToUnstructured(*actualState.Manifest) if err == nil { - actualObject = utils.AddCommitDetailsToLabels(actualObject, originalAppRevisionMetadata) + actualObject = utils.AddCommitsDetailsToAnnotations(actualObject, originalAppRevisionsMetadata) + if originalApplication != nil { + actualObject = utils.AddCommitDetailsToLabels(actualObject, getApplicationLegacyRevisionDetails(originalApplication, originalAppRevisionsMetadata)) + } + object, err = actualObject.MarshalJSON() if err != nil { return nil, fmt.Errorf("failed to marshal unstructured object: %w", err) @@ -74,8 +78,11 @@ func getResourceEventPayload( u.SetKind(rs.Kind) u.SetName(rs.Name) u.SetNamespace(rs.Namespace) - if originalAppRevisionMetadata != nil { - u = utils.AddCommitDetailsToLabels(u, originalAppRevisionMetadata) + if originalAppRevisionsMetadata != nil { + u = utils.AddCommitsDetailsToAnnotations(u, originalAppRevisionsMetadata) + if originalApplication != nil { + u = utils.AddCommitDetailsToLabels(u, getApplicationLegacyRevisionDetails(originalApplication, originalAppRevisionsMetadata)) + } } object, err = u.MarshalJSON() @@ -88,8 +95,11 @@ func getResourceEventPayload( if err != nil { return nil, fmt.Errorf("failed to add destination namespace to manifest: %w", err) } - if originalAppRevisionMetadata != nil { - unstructuredWithNamespace = utils.AddCommitDetailsToLabels(unstructuredWithNamespace, originalAppRevisionMetadata) + if originalAppRevisionsMetadata != nil { + unstructuredWithNamespace = utils.AddCommitsDetailsToAnnotations(unstructuredWithNamespace, originalAppRevisionsMetadata) + if originalApplication != nil { + unstructuredWithNamespace = utils.AddCommitDetailsToLabels(unstructuredWithNamespace, getApplicationLegacyRevisionDetails(originalApplication, originalAppRevisionsMetadata)) + } } object, _ = unstructuredWithNamespace.MarshalJSON() @@ -166,10 +176,13 @@ func getResourceEventPayload( TrackingMethod: string(trackingMethod), } - if revisionMetadata != nil { - source.CommitMessage = revisionMetadata.Message - source.CommitAuthor = revisionMetadata.Author - source.CommitDate = &revisionMetadata.Date + if revisionsMetadata != nil && revisionsMetadata.SyncRevisions != nil { + revisionMetadata := getApplicationLegacyRevisionDetails(parentApplication, revisionsMetadata) + if revisionMetadata != nil { + source.CommitMessage = revisionMetadata.Message + source.CommitAuthor = revisionMetadata.Author + source.CommitDate = &revisionMetadata.Date + } } if rs.Health != nil { @@ -225,27 +238,19 @@ func (s *applicationEventReporter) getApplicationEventPayload( syncFinished = a.Status.OperationState.FinishedAt } - applicationSource := a.Spec.GetSource() - if !applicationSource.IsHelm() && (a.Status.Sync.Revision != "" || (a.Status.History != nil && len(a.Status.History) > 0)) { - revisionMetadata, err := s.getApplicationRevisionDetails(ctx, a, utils.GetOperationRevision(a)) + revisionsMetadata, err := s.getApplicationRevisionsMetadata(ctx, logCtx, a) - if err != nil { - if !strings.Contains(err.Error(), "not found") { - return nil, fmt.Errorf("failed to get revision metadata: %w", err) - } - - logCtx.Warnf("failed to get revision metadata: %s, reporting application deletion event", err.Error()) - } else { - if obj.ObjectMeta.Labels == nil { - obj.ObjectMeta.Labels = map[string]string{} - } - - obj.ObjectMeta.Labels["app.meta.commit-date"] = revisionMetadata.Date.Format("2006-01-02T15:04:05.000Z") - obj.ObjectMeta.Labels["app.meta.commit-author"] = revisionMetadata.Author - obj.ObjectMeta.Labels["app.meta.commit-message"] = revisionMetadata.Message + if err != nil { + if !strings.Contains(err.Error(), "not found") { + return nil, fmt.Errorf("failed to get revision metadata: %w", err) } + + logCtx.Warnf("failed to get revision metadata: %s, reporting application deletion event", err.Error()) } + utils.AddCommitsDetailsToAppAnnotations(obj, revisionsMetadata) + utils.AddCommitsDetailsToAppLabels(&obj, getApplicationLegacyRevisionDetails(&obj, revisionsMetadata)) + object, err := json.Marshal(&obj) if err != nil { return nil, fmt.Errorf("failed to marshal application event") diff --git a/event_reporter/utils/app_revision.go b/event_reporter/utils/app_revision.go index d9ef8376fdb22..fd4265b2ade4b 100644 --- a/event_reporter/utils/app_revision.go +++ b/event_reporter/utils/app_revision.go @@ -127,6 +127,8 @@ func AddCommitDetailsToLabels(u *unstructured.Unstructured, revisionMetadata *ap return u } +var annotationRevisionKey = "app.meta.revisions-metadata" + func AddCommitsDetailsToAnnotations(unstrApp *unstructured.Unstructured, revisionsMetadata *AppSyncRevisionsMetadata) *unstructured.Unstructured { if revisionsMetadata == nil || unstrApp == nil { return unstrApp @@ -142,7 +144,43 @@ func AddCommitsDetailsToAnnotations(unstrApp *unstructured.Unstructured, revisio return unstrApp } - _ = unstructured.SetNestedField(unstrApp.Object, jsonRevisionsMetadata, "metadata", "annotations", "app.meta.revisions-metadata") + _ = unstructured.SetNestedField(unstrApp.Object, string(jsonRevisionsMetadata), "metadata", "annotations", annotationRevisionKey) return unstrApp } + +func AddCommitsDetailsToAppAnnotations(app appv1.Application, revisionsMetadata *AppSyncRevisionsMetadata) appv1.Application { + if revisionsMetadata == nil { + return app + } + + if app.ObjectMeta.Annotations == nil { + app.ObjectMeta.Annotations = map[string]string{} + } + + jsonRevisionsMetadata, err := json.Marshal(revisionsMetadata) + + if err != nil { + return app + } + + app.ObjectMeta.Annotations[annotationRevisionKey] = string(jsonRevisionsMetadata) + + return app +} + +func AddCommitsDetailsToAppLabels(app *appv1.Application, revisionMetadata *appv1.RevisionMetadata) *appv1.Application { + if revisionMetadata == nil { + return app + } + + if app.ObjectMeta.Labels == nil { + app.ObjectMeta.Labels = map[string]string{} + } + + app.ObjectMeta.Labels["app.meta.commit-date"] = revisionMetadata.Date.Format("2006-01-02T15:04:05.000Z") + app.ObjectMeta.Labels["app.meta.commit-author"] = revisionMetadata.Author + app.ObjectMeta.Labels["app.meta.commit-message"] = revisionMetadata.Message + + return app +} diff --git a/pkg/apis/application/v1alpha1/types_codefresh.go b/pkg/apis/application/v1alpha1/types_codefresh.go index ad46d19bbe208..9b022e299c8b1 100644 --- a/pkg/apis/application/v1alpha1/types_codefresh.go +++ b/pkg/apis/application/v1alpha1/types_codefresh.go @@ -16,3 +16,19 @@ func (a *Application) SetDefaultTypeMeta() { APIVersion: SchemeGroupVersion.String(), } } + +func (a *ApplicationSpec) GetNonRefSource() (*ApplicationSource, int) { + if a.HasMultipleSources() { + for idx, source := range a.Sources { + if !source.IsRef() { + return &source, idx + } + } + } + + if a.Source != nil { // single source app + return a.Source, -1 + } + + return nil, -2 +} From bf0e87a3884d08b7f35cb38f4bdcd22682f21f90 Mon Sep 17 00:00:00 2001 From: oleksandr-codefresh Date: Wed, 25 Sep 2024 09:17:24 +0300 Subject: [PATCH 03/20] event-reporter: added revision sha to reported value in anotations - "app.meta.revisions-metadata" --- event_reporter/reporter/app_revision.go | 11 +++++++---- event_reporter/utils/app_revision.go | 9 +++++++-- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/event_reporter/reporter/app_revision.go b/event_reporter/reporter/app_revision.go index cf40c8c578ccc..cceb36b7050c4 100644 --- a/event_reporter/reporter/app_revision.go +++ b/event_reporter/reporter/app_revision.go @@ -20,12 +20,12 @@ func getApplicationLegacyRevisionDetails(a *appv1.Application, revisionsMetadata return nil } - return revisionsMetadata.SyncRevisions[sourceIdx] + return revisionsMetadata.SyncRevisions[sourceIdx].Metadata } -func (s *applicationEventReporter) getCommitRevisionsDetails(ctx context.Context, a *appv1.Application, revisions []string) ([]*appv1.RevisionMetadata, error) { +func (s *applicationEventReporter) getCommitRevisionsDetails(ctx context.Context, a *appv1.Application, revisions []string) ([]*utils.RevisionWithMetadata, error) { project := a.Spec.GetProject() - rms := make([]*appv1.RevisionMetadata, 0) + rms := make([]*utils.RevisionWithMetadata, 0) for _, revision := range revisions { rm, err := s.applicationServiceClient.RevisionMetadata(ctx, &application.RevisionMetadataQuery{ @@ -37,7 +37,10 @@ func (s *applicationEventReporter) getCommitRevisionsDetails(ctx context.Context if err != nil { return nil, err } - rms = append(rms, rm) + rms = append(rms, &utils.RevisionWithMetadata{ + Revision: revision, + Metadata: rm, + }) } return rms, nil diff --git a/event_reporter/utils/app_revision.go b/event_reporter/utils/app_revision.go index fd4265b2ade4b..630310d32def1 100644 --- a/event_reporter/utils/app_revision.go +++ b/event_reporter/utils/app_revision.go @@ -9,8 +9,13 @@ import ( ) type AppSyncRevisionsMetadata struct { - SyncRevisions []*appv1.RevisionMetadata `json:"syncRevisions" protobuf:"bytes,1,name=syncRevisions"` - ChangeRevisions []*appv1.RevisionMetadata `json:"changeRevisions" protobuf:"bytes,2,name=changeRevisions"` + SyncRevisions []*RevisionWithMetadata `json:"syncRevisions" protobuf:"bytes,1,name=syncRevisions"` + ChangeRevisions []*RevisionWithMetadata `json:"changeRevisions" protobuf:"bytes,2,name=changeRevisions"` +} + +type RevisionWithMetadata struct { + Revision string `json:"revision,omitempty" protobuf:"bytes,1,opt,name=revision"` + Metadata *appv1.RevisionMetadata `json:"metadata,omitempty" protobuf:"bytes,2,name=metadata"` } type RevisionsData struct { From 1ad1112dba2db0bb82047429e0267bee596c9567 Mon Sep 17 00:00:00 2001 From: oleksandr-codefresh Date: Wed, 25 Sep 2024 09:26:07 +0300 Subject: [PATCH 04/20] event-reporter: added change revisions sha to reported value in anotations - app.meta.revisions-metadata --- event_reporter/utils/app_revision.go | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/event_reporter/utils/app_revision.go b/event_reporter/utils/app_revision.go index 630310d32def1..1e8c86c5a1040 100644 --- a/event_reporter/utils/app_revision.go +++ b/event_reporter/utils/app_revision.go @@ -90,19 +90,24 @@ func GetOperationSyncRevisions(a *appv1.Application) []string { // for monorepo support: list with revisions where actual changes to source directory were committed func GetOperationChangeRevisions(a *appv1.Application) []string { - revisions := []string{} + var revisions []string if a != nil { // this value will be used in case if application hasn't resources, like empty gitsource - // TODO uncomment later - //if a.Status.OperationState != nil && a.Status.OperationState.Operation.Sync != nil { - // if a.Status.OperationState.Operation.Sync.ChangeRevision != "" { - // revisions = []string{a.Status.OperationState.Operation.Sync.ChangeRevision} - // } - //} else if a.Operation != nil && a.Operation.Sync != nil { - // if a.Operation.Sync.ChangeRevision != "" { - // revisions = []string{a.Operation.Sync.ChangeRevision} - // } - //} + if a.Status.OperationState != nil && a.Status.OperationState.Operation.Sync != nil { + if a.Status.OperationState.Operation.Sync.ChangeRevision != "" || a.Status.OperationState.Operation.Sync.ChangeRevisions != nil { + revisions = getRevisions(RevisionsData{ + Revision: a.Status.OperationState.Operation.Sync.ChangeRevision, + Revisions: a.Status.OperationState.Operation.Sync.ChangeRevisions, + }) + } + } else if a.Operation != nil && a.Operation.Sync != nil { + if a.Operation.Sync.ChangeRevision != "" || a.Operation.Sync.ChangeRevisions != nil { + revisions = getRevisions(RevisionsData{ + Revision: a.Operation.Sync.ChangeRevision, + Revisions: a.Operation.Sync.ChangeRevisions, + }) + } + } } return revisions From 3dd3f0dff2ec7bfe207eb9cfffcca3bd1b57c0ef Mon Sep 17 00:00:00 2001 From: oleksandr-codefresh Date: Wed, 25 Sep 2024 09:27:18 +0300 Subject: [PATCH 05/20] event-reporter: updated changelog --- changelog/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog/CHANGELOG.md b/changelog/CHANGELOG.md index 4d55728e17ee7..e4fe910a101a9 100644 --- a/changelog/CHANGELOG.md +++ b/changelog/CHANGELOG.md @@ -1,2 +1,2 @@ ### Features -- feat: monorepo controller v1.0.0 \ No newline at end of file +- feat: event-reporter: report change revisions metadata in app annotations \ No newline at end of file From 54258ef825469d413ab270e2f3e5ce897fc26b19 Mon Sep 17 00:00:00 2001 From: oleksandr-codefresh Date: Fri, 27 Sep 2024 11:12:16 +0300 Subject: [PATCH 06/20] event-reporter: changes to anotations repoting - app.meta.revisions-metadata, report only revision in case of helm chart --- event_reporter/reporter/app_revision.go | 18 +++++++++++++----- event_reporter/utils/app_revision.go | 2 +- .../acr-controller-deployment.yaml | 6 ++++++ 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/event_reporter/reporter/app_revision.go b/event_reporter/reporter/app_revision.go index cceb36b7050c4..a9f66ab60ab87 100644 --- a/event_reporter/reporter/app_revision.go +++ b/event_reporter/reporter/app_revision.go @@ -23,11 +23,19 @@ func getApplicationLegacyRevisionDetails(a *appv1.Application, revisionsMetadata return revisionsMetadata.SyncRevisions[sourceIdx].Metadata } -func (s *applicationEventReporter) getCommitRevisionsDetails(ctx context.Context, a *appv1.Application, revisions []string) ([]*utils.RevisionWithMetadata, error) { +func (s *applicationEventReporter) getRevisionsDetails(ctx context.Context, a *appv1.Application, revisions []string) ([]*utils.RevisionWithMetadata, error) { project := a.Spec.GetProject() rms := make([]*utils.RevisionWithMetadata, 0) - for _, revision := range revisions { + for idx, revision := range revisions { + // report just revision for helm sources + if (a.Spec.HasMultipleSources() && a.Spec.Sources[idx].IsHelm()) || (a.Spec.Source != nil && a.Spec.Source.IsHelm()) { + rms = append(rms, &utils.RevisionWithMetadata{ + Revision: revision, + }) + continue + } + rm, err := s.applicationServiceClient.RevisionMetadata(ctx, &application.RevisionMetadataQuery{ Name: &a.Name, AppNamespace: &a.Namespace, @@ -49,9 +57,9 @@ func (s *applicationEventReporter) getCommitRevisionsDetails(ctx context.Context func (s *applicationEventReporter) getApplicationRevisionsMetadata(ctx context.Context, logCtx *log.Entry, a *appv1.Application) (*utils.AppSyncRevisionsMetadata, error) { result := &utils.AppSyncRevisionsMetadata{} - if source, _ := a.Spec.GetNonRefSource(); !source.IsHelm() && (a.Status.Sync.Revision != "" || a.Status.Sync.Revisions != nil || (a.Status.History != nil && len(a.Status.History) > 0)) { + if a.Status.Sync.Revision != "" || a.Status.Sync.Revisions != nil || (a.Status.History != nil && len(a.Status.History) > 0) { // can be the latest revision of repository - operationSyncRevisionsMetadata, err := s.getCommitRevisionsDetails(ctx, a, utils.GetOperationSyncRevisions(a)) + operationSyncRevisionsMetadata, err := s.getRevisionsDetails(ctx, a, utils.GetOperationSyncRevisions(a)) if err != nil { logCtx.WithError(err).Warnf("failed to get application(%s) sync revisions metadata, resuming", a.GetName()) @@ -61,7 +69,7 @@ func (s *applicationEventReporter) getApplicationRevisionsMetadata(ctx context.C result.SyncRevisions = operationSyncRevisionsMetadata } // latest revision of repository where changes to app resource were actually made; empty if no changeRevisionі present - operationChangeRevisionsMetadata, err := s.getCommitRevisionsDetails(ctx, a, utils.GetOperationChangeRevisions(a)) + operationChangeRevisionsMetadata, err := s.getRevisionsDetails(ctx, a, utils.GetOperationChangeRevisions(a)) if err != nil { logCtx.WithError(err).Warnf("failed to get application(%s) change revisions metadata, resuming", a.GetName()) diff --git a/event_reporter/utils/app_revision.go b/event_reporter/utils/app_revision.go index 1e8c86c5a1040..d824f5e0608ec 100644 --- a/event_reporter/utils/app_revision.go +++ b/event_reporter/utils/app_revision.go @@ -14,7 +14,7 @@ type AppSyncRevisionsMetadata struct { } type RevisionWithMetadata struct { - Revision string `json:"revision,omitempty" protobuf:"bytes,1,opt,name=revision"` + Revision string `json:"revision" protobuf:"bytes,1,name=revision"` Metadata *appv1.RevisionMetadata `json:"metadata,omitempty" protobuf:"bytes,2,name=metadata"` } diff --git a/manifests/base/acr-controller/acr-controller-deployment.yaml b/manifests/base/acr-controller/acr-controller-deployment.yaml index baf85faaf85df..d75ab9f05b55f 100644 --- a/manifests/base/acr-controller/acr-controller-deployment.yaml +++ b/manifests/base/acr-controller/acr-controller-deployment.yaml @@ -31,6 +31,12 @@ spec: secretKeyRef: key: token name: argocd-token + - name: ARGOCD_SERVER_ROOTPATH + valueFrom: + configMapKeyRef: + key: server.rootpath + name: argocd-cmd-params-cm + optional: true - name: ARGOCD_APPLICATION_NAMESPACES valueFrom: configMapKeyRef: From a9c0b2c6fb7b8be57f520c14a85e662bdaeacbb5 Mon Sep 17 00:00:00 2001 From: oleksandr-codefresh Date: Fri, 27 Sep 2024 17:28:38 +0300 Subject: [PATCH 07/20] event-reporter: changes after pr review --- event_reporter/utils/app_revision.go | 70 ++++++++++--------- .../application/v1alpha1/types_codefresh.go | 7 +- 2 files changed, 42 insertions(+), 35 deletions(-) diff --git a/event_reporter/utils/app_revision.go b/event_reporter/utils/app_revision.go index d824f5e0608ec..ba770a648b15f 100644 --- a/event_reporter/utils/app_revision.go +++ b/event_reporter/utils/app_revision.go @@ -65,24 +65,27 @@ func GetOperationRevision(a *appv1.Application) string { func GetOperationSyncRevisions(a *appv1.Application) []string { var revisions []string - if a != nil { - // this value will be used in case if application hasn't resources, like empty gitsource + + if a == nil { + return revisions + } + + // this value will be used in case if application hasn't resources, like empty gitsource + revisions = getRevisions(RevisionsData{ + Revision: a.Status.Sync.Revision, + Revisions: a.Status.Sync.Revisions, + }) + + if a.Status.OperationState != nil && a.Status.OperationState.Operation.Sync != nil { revisions = getRevisions(RevisionsData{ - Revision: a.Status.Sync.Revision, - Revisions: a.Status.Sync.Revisions, + Revision: a.Status.OperationState.Operation.Sync.Revision, + Revisions: a.Status.OperationState.Operation.Sync.Revisions, + }) + } else if a.Operation != nil && a.Operation.Sync != nil { + revisions = getRevisions(RevisionsData{ + Revision: a.Operation.Sync.Revision, + Revisions: a.Operation.Sync.Revisions, }) - - if a.Status.OperationState != nil && a.Status.OperationState.Operation.Sync != nil { - revisions = getRevisions(RevisionsData{ - Revision: a.Status.OperationState.Operation.Sync.Revision, - Revisions: a.Status.OperationState.Operation.Sync.Revisions, - }) - } else if a.Operation != nil && a.Operation.Sync != nil { - revisions = getRevisions(RevisionsData{ - Revision: a.Operation.Sync.Revision, - Revisions: a.Operation.Sync.Revisions, - }) - } } return revisions @@ -91,22 +94,25 @@ func GetOperationSyncRevisions(a *appv1.Application) []string { // for monorepo support: list with revisions where actual changes to source directory were committed func GetOperationChangeRevisions(a *appv1.Application) []string { var revisions []string - if a != nil { - // this value will be used in case if application hasn't resources, like empty gitsource - if a.Status.OperationState != nil && a.Status.OperationState.Operation.Sync != nil { - if a.Status.OperationState.Operation.Sync.ChangeRevision != "" || a.Status.OperationState.Operation.Sync.ChangeRevisions != nil { - revisions = getRevisions(RevisionsData{ - Revision: a.Status.OperationState.Operation.Sync.ChangeRevision, - Revisions: a.Status.OperationState.Operation.Sync.ChangeRevisions, - }) - } - } else if a.Operation != nil && a.Operation.Sync != nil { - if a.Operation.Sync.ChangeRevision != "" || a.Operation.Sync.ChangeRevisions != nil { - revisions = getRevisions(RevisionsData{ - Revision: a.Operation.Sync.ChangeRevision, - Revisions: a.Operation.Sync.ChangeRevisions, - }) - } + + if a == nil { + return revisions + } + + // this value will be used in case if application hasn't resources, like empty gitsource + if a.Status.OperationState != nil && a.Status.OperationState.Operation.Sync != nil { + if a.Status.OperationState.Operation.Sync.ChangeRevision != "" || a.Status.OperationState.Operation.Sync.ChangeRevisions != nil { + revisions = getRevisions(RevisionsData{ + Revision: a.Status.OperationState.Operation.Sync.ChangeRevision, + Revisions: a.Status.OperationState.Operation.Sync.ChangeRevisions, + }) + } + } else if a.Operation != nil && a.Operation.Sync != nil { + if a.Operation.Sync.ChangeRevision != "" || a.Operation.Sync.ChangeRevisions != nil { + revisions = getRevisions(RevisionsData{ + Revision: a.Operation.Sync.ChangeRevision, + Revisions: a.Operation.Sync.ChangeRevisions, + }) } } diff --git a/pkg/apis/application/v1alpha1/types_codefresh.go b/pkg/apis/application/v1alpha1/types_codefresh.go index 9b022e299c8b1..0546eaca92e9e 100644 --- a/pkg/apis/application/v1alpha1/types_codefresh.go +++ b/pkg/apis/application/v1alpha1/types_codefresh.go @@ -26,9 +26,10 @@ func (a *ApplicationSpec) GetNonRefSource() (*ApplicationSource, int) { } } - if a.Source != nil { // single source app - return a.Source, -1 + if a.Source == nil { + return nil, -2 } - return nil, -2 + // single source app + return a.Source, -1 } From 472aa638750a813be8df0f0893993399fb47b2c6 Mon Sep 17 00:00:00 2001 From: oleksandr-codefresh Date: Fri, 27 Sep 2024 18:01:42 +0300 Subject: [PATCH 08/20] event-reporter: fixed unit tests --- event_reporter/reporter/event_payload_test.go | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/event_reporter/reporter/event_payload_test.go b/event_reporter/reporter/event_payload_test.go index 25b8a8971b722..8e60befdcfeb8 100644 --- a/event_reporter/reporter/event_payload_test.go +++ b/event_reporter/reporter/event_payload_test.go @@ -2,6 +2,7 @@ package reporter import ( "encoding/json" + "github.com/argoproj/argo-cd/v2/event_reporter/utils" "testing" "github.com/stretchr/testify/assert" @@ -18,7 +19,13 @@ import ( func TestGetResourceEventPayload(t *testing.T) { t.Run("Deleting timestamp is empty", func(t *testing.T) { - app := v1alpha1.Application{} + app := v1alpha1.Application{ + Spec: v1alpha1.ApplicationSpec{ + Source: &v1alpha1.ApplicationSource{ + RepoURL: "test", + }, + }, + } rs := v1alpha1.ResourceStatus{} man := "{ \"key\" : \"manifest\" }" @@ -30,10 +37,14 @@ func TestGetResourceEventPayload(t *testing.T) { CompiledManifest: "{ \"key\" : \"manifest\" }", } appTree := v1alpha1.ApplicationTree{} - revisionMetadata := v1alpha1.RevisionMetadata{ - Author: "demo usert", - Date: metav1.Time{}, - Message: "some message", + revisionMetadata := utils.AppSyncRevisionsMetadata{ + SyncRevisions: []*utils.RevisionWithMetadata{&utils.RevisionWithMetadata{ + Metadata: &v1alpha1.RevisionMetadata{ + Author: "demo usert", + Date: metav1.Time{}, + Message: "some message", + }, + }}, } event, err := getResourceEventPayload(&app, &rs, &actualState, &desiredState, &appTree, true, "", nil, &revisionMetadata, nil, common.LabelKeyAppInstance, argo.TrackingMethodLabel, &repoApiclient.ApplicationVersions{}) @@ -48,7 +59,7 @@ func TestGetResourceEventPayload(t *testing.T) { assert.Equal(t, "{ \"key\" : \"manifest\" }", eventPayload.Source.ActualManifest) }) - t.Run("Deleting timestamp is empty", func(t *testing.T) { + t.Run("Deleting timestamp not empty", func(t *testing.T) { app := v1alpha1.Application{ ObjectMeta: metav1.ObjectMeta{ DeletionTimestamp: &metav1.Time{}, @@ -64,10 +75,8 @@ func TestGetResourceEventPayload(t *testing.T) { CompiledManifest: "{ \"key\" : \"manifest\" }", } appTree := v1alpha1.ApplicationTree{} - revisionMetadata := v1alpha1.RevisionMetadata{ - Author: "demo usert", - Date: metav1.Time{}, - Message: "some message", + revisionMetadata := utils.AppSyncRevisionsMetadata{ + SyncRevisions: []*utils.RevisionWithMetadata{}, } event, err := getResourceEventPayload(&app, &rs, &actualState, &desiredState, &appTree, true, "", nil, &revisionMetadata, nil, common.LabelKeyAppInstance, argo.TrackingMethodLabel, &repoApiclient.ApplicationVersions{}) From 9bc3265d09b62360da55ab84bd45e295f658180c Mon Sep 17 00:00:00 2001 From: oleksandr-codefresh Date: Fri, 27 Sep 2024 18:13:19 +0300 Subject: [PATCH 09/20] event-reporter: fix lint issues --- event_reporter/reporter/app_revision.go | 4 ++-- event_reporter/reporter/event_payload.go | 1 - event_reporter/reporter/event_payload_test.go | 5 +++-- event_reporter/utils/app_revision.go | 2 -- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/event_reporter/reporter/app_revision.go b/event_reporter/reporter/app_revision.go index a9f66ab60ab87..e58c9bdb10cba 100644 --- a/event_reporter/reporter/app_revision.go +++ b/event_reporter/reporter/app_revision.go @@ -2,8 +2,10 @@ package reporter import ( "context" + "github.com/argoproj/argo-cd/v2/event_reporter/utils" "github.com/argoproj/argo-cd/v2/pkg/apiclient/application" + appv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" log "github.com/sirupsen/logrus" ) @@ -60,7 +62,6 @@ func (s *applicationEventReporter) getApplicationRevisionsMetadata(ctx context.C if a.Status.Sync.Revision != "" || a.Status.Sync.Revisions != nil || (a.Status.History != nil && len(a.Status.History) > 0) { // can be the latest revision of repository operationSyncRevisionsMetadata, err := s.getRevisionsDetails(ctx, a, utils.GetOperationSyncRevisions(a)) - if err != nil { logCtx.WithError(err).Warnf("failed to get application(%s) sync revisions metadata, resuming", a.GetName()) } @@ -70,7 +71,6 @@ func (s *applicationEventReporter) getApplicationRevisionsMetadata(ctx context.C } // latest revision of repository where changes to app resource were actually made; empty if no changeRevisionі present operationChangeRevisionsMetadata, err := s.getRevisionsDetails(ctx, a, utils.GetOperationChangeRevisions(a)) - if err != nil { logCtx.WithError(err).Warnf("failed to get application(%s) change revisions metadata, resuming", a.GetName()) } diff --git a/event_reporter/reporter/event_payload.go b/event_reporter/reporter/event_payload.go index 5097fe8b22723..cbac9a9031d32 100644 --- a/event_reporter/reporter/event_payload.go +++ b/event_reporter/reporter/event_payload.go @@ -239,7 +239,6 @@ func (s *applicationEventReporter) getApplicationEventPayload( } revisionsMetadata, err := s.getApplicationRevisionsMetadata(ctx, logCtx, a) - if err != nil { if !strings.Contains(err.Error(), "not found") { return nil, fmt.Errorf("failed to get revision metadata: %w", err) diff --git a/event_reporter/reporter/event_payload_test.go b/event_reporter/reporter/event_payload_test.go index 8e60befdcfeb8..dc305a099c897 100644 --- a/event_reporter/reporter/event_payload_test.go +++ b/event_reporter/reporter/event_payload_test.go @@ -2,9 +2,10 @@ package reporter import ( "encoding/json" - "github.com/argoproj/argo-cd/v2/event_reporter/utils" "testing" + "github.com/argoproj/argo-cd/v2/event_reporter/utils" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -38,7 +39,7 @@ func TestGetResourceEventPayload(t *testing.T) { } appTree := v1alpha1.ApplicationTree{} revisionMetadata := utils.AppSyncRevisionsMetadata{ - SyncRevisions: []*utils.RevisionWithMetadata{&utils.RevisionWithMetadata{ + SyncRevisions: []*utils.RevisionWithMetadata{{ Metadata: &v1alpha1.RevisionMetadata{ Author: "demo usert", Date: metav1.Time{}, diff --git a/event_reporter/utils/app_revision.go b/event_reporter/utils/app_revision.go index ba770a648b15f..49c26d8657992 100644 --- a/event_reporter/utils/app_revision.go +++ b/event_reporter/utils/app_revision.go @@ -155,7 +155,6 @@ func AddCommitsDetailsToAnnotations(unstrApp *unstructured.Unstructured, revisio } jsonRevisionsMetadata, err := json.Marshal(revisionsMetadata) - if err != nil { return unstrApp } @@ -175,7 +174,6 @@ func AddCommitsDetailsToAppAnnotations(app appv1.Application, revisionsMetadata } jsonRevisionsMetadata, err := json.Marshal(revisionsMetadata) - if err != nil { return app } From 006f6dbe164620eda1fd753726c85625abb0ca2a Mon Sep 17 00:00:00 2001 From: oleksandr-codefresh Date: Mon, 30 Sep 2024 15:49:36 +0300 Subject: [PATCH 10/20] event-reporter: changes after pr reviev, fixing typo, added dedicated func a.Spec.IsHelmSource(idx), removed legacy code --- event_reporter/reporter/app_revision.go | 4 +- event_reporter/reporter/event_payload.go | 10 ---- event_reporter/utils/app_revision.go | 42 ++----------- event_reporter/utils/app_revision_test.go | 60 ------------------- .../application/v1alpha1/types_codefresh.go | 16 +++-- 5 files changed, 17 insertions(+), 115 deletions(-) diff --git a/event_reporter/reporter/app_revision.go b/event_reporter/reporter/app_revision.go index e58c9bdb10cba..5c0a53abebfd6 100644 --- a/event_reporter/reporter/app_revision.go +++ b/event_reporter/reporter/app_revision.go @@ -31,7 +31,7 @@ func (s *applicationEventReporter) getRevisionsDetails(ctx context.Context, a *a for idx, revision := range revisions { // report just revision for helm sources - if (a.Spec.HasMultipleSources() && a.Spec.Sources[idx].IsHelm()) || (a.Spec.Source != nil && a.Spec.Source.IsHelm()) { + if a.Spec.SourceUnderIdxIsHelm(idx) { rms = append(rms, &utils.RevisionWithMetadata{ Revision: revision, }) @@ -69,7 +69,7 @@ func (s *applicationEventReporter) getApplicationRevisionsMetadata(ctx context.C if err == nil && operationSyncRevisionsMetadata != nil { result.SyncRevisions = operationSyncRevisionsMetadata } - // latest revision of repository where changes to app resource were actually made; empty if no changeRevisionі present + // latest revision of repository where changes to app resource were actually made; empty if no changeRevision(-s) present operationChangeRevisionsMetadata, err := s.getRevisionsDetails(ctx, a, utils.GetOperationChangeRevisions(a)) if err != nil { logCtx.WithError(err).Warnf("failed to get application(%s) change revisions metadata, resuming", a.GetName()) diff --git a/event_reporter/reporter/event_payload.go b/event_reporter/reporter/event_payload.go index cbac9a9031d32..f02d2ebd66465 100644 --- a/event_reporter/reporter/event_payload.go +++ b/event_reporter/reporter/event_payload.go @@ -55,9 +55,6 @@ func getResourceEventPayload( if err == nil { actualObject = utils.AddCommitsDetailsToAnnotations(actualObject, originalAppRevisionsMetadata) - if originalApplication != nil { - actualObject = utils.AddCommitDetailsToLabels(actualObject, getApplicationLegacyRevisionDetails(originalApplication, originalAppRevisionsMetadata)) - } object, err = actualObject.MarshalJSON() if err != nil { @@ -80,9 +77,6 @@ func getResourceEventPayload( u.SetNamespace(rs.Namespace) if originalAppRevisionsMetadata != nil { u = utils.AddCommitsDetailsToAnnotations(u, originalAppRevisionsMetadata) - if originalApplication != nil { - u = utils.AddCommitDetailsToLabels(u, getApplicationLegacyRevisionDetails(originalApplication, originalAppRevisionsMetadata)) - } } object, err = u.MarshalJSON() @@ -97,9 +91,6 @@ func getResourceEventPayload( } if originalAppRevisionsMetadata != nil { unstructuredWithNamespace = utils.AddCommitsDetailsToAnnotations(unstructuredWithNamespace, originalAppRevisionsMetadata) - if originalApplication != nil { - unstructuredWithNamespace = utils.AddCommitDetailsToLabels(unstructuredWithNamespace, getApplicationLegacyRevisionDetails(originalApplication, originalAppRevisionsMetadata)) - } } object, _ = unstructuredWithNamespace.MarshalJSON() @@ -248,7 +239,6 @@ func (s *applicationEventReporter) getApplicationEventPayload( } utils.AddCommitsDetailsToAppAnnotations(obj, revisionsMetadata) - utils.AddCommitsDetailsToAppLabels(&obj, getApplicationLegacyRevisionDetails(&obj, revisionsMetadata)) object, err := json.Marshal(&obj) if err != nil { diff --git a/event_reporter/utils/app_revision.go b/event_reporter/utils/app_revision.go index 49c26d8657992..83280e63e226e 100644 --- a/event_reporter/utils/app_revision.go +++ b/event_reporter/utils/app_revision.go @@ -23,6 +23,8 @@ type RevisionsData struct { Revisions []string `json:"revisions,omitempty" protobuf:"bytes,2,opt,name=revisions"` } +const annotationRevisionKey = "app.meta.revisions-metadata" + func GetLatestAppHistoryId(a *appv1.Application) int64 { if lastHistory := getLatestAppHistoryItem(a); lastHistory != nil { return lastHistory.ID @@ -64,14 +66,12 @@ func GetOperationRevision(a *appv1.Application) string { } func GetOperationSyncRevisions(a *appv1.Application) []string { - var revisions []string - if a == nil { - return revisions + return []string{} } // this value will be used in case if application hasn't resources, like empty gitsource - revisions = getRevisions(RevisionsData{ + revisions := getRevisions(RevisionsData{ Revision: a.Status.Sync.Revision, Revisions: a.Status.Sync.Revisions, }) @@ -127,24 +127,6 @@ func getRevisions(rd RevisionsData) []string { return []string{rd.Revision} } -func AddCommitDetailsToLabels(u *unstructured.Unstructured, revisionMetadata *appv1.RevisionMetadata) *unstructured.Unstructured { - if revisionMetadata == nil || u == nil { - return u - } - - if field, _, _ := unstructured.NestedFieldCopy(u.Object, "metadata", "labels"); field == nil { - _ = unstructured.SetNestedStringMap(u.Object, map[string]string{}, "metadata", "labels") - } - - _ = unstructured.SetNestedField(u.Object, revisionMetadata.Date.Format("2006-01-02T15:04:05.000Z"), "metadata", "labels", "app.meta.commit-date") - _ = unstructured.SetNestedField(u.Object, revisionMetadata.Author, "metadata", "labels", "app.meta.commit-author") - _ = unstructured.SetNestedField(u.Object, revisionMetadata.Message, "metadata", "labels", "app.meta.commit-message") - - return u -} - -var annotationRevisionKey = "app.meta.revisions-metadata" - func AddCommitsDetailsToAnnotations(unstrApp *unstructured.Unstructured, revisionsMetadata *AppSyncRevisionsMetadata) *unstructured.Unstructured { if revisionsMetadata == nil || unstrApp == nil { return unstrApp @@ -182,19 +164,3 @@ func AddCommitsDetailsToAppAnnotations(app appv1.Application, revisionsMetadata return app } - -func AddCommitsDetailsToAppLabels(app *appv1.Application, revisionMetadata *appv1.RevisionMetadata) *appv1.Application { - if revisionMetadata == nil { - return app - } - - if app.ObjectMeta.Labels == nil { - app.ObjectMeta.Labels = map[string]string{} - } - - app.ObjectMeta.Labels["app.meta.commit-date"] = revisionMetadata.Date.Format("2006-01-02T15:04:05.000Z") - app.ObjectMeta.Labels["app.meta.commit-author"] = revisionMetadata.Author - app.ObjectMeta.Labels["app.meta.commit-message"] = revisionMetadata.Message - - return app -} diff --git a/event_reporter/utils/app_revision_test.go b/event_reporter/utils/app_revision_test.go index 852fb69ff2775..2f9049705d930 100644 --- a/event_reporter/utils/app_revision_test.go +++ b/event_reporter/utils/app_revision_test.go @@ -6,7 +6,6 @@ import ( "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" "github.com/stretchr/testify/assert" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "sigs.k8s.io/yaml" ) @@ -110,62 +109,3 @@ func StrToUnstructured(jsonStr string) *unstructured.Unstructured { } return &unstructured.Unstructured{Object: obj} } - -func TestAddCommitDetailsToLabels(t *testing.T) { - revisionMetadata := v1alpha1.RevisionMetadata{ - Author: "demo usert", - Date: metav1.Time{}, - Message: "some message", - } - - t.Run("set labels when lable object missing", func(t *testing.T) { - resource := StrToUnstructured(` - apiVersion: v1 - kind: Service - metadata: - name: helm-guestbook - namespace: default - resourceVersion: "123" - uid: "4" - spec: - selector: - app: guestbook - type: LoadBalancer - status: - loadBalancer: - ingress: - - hostname: localhost`, - ) - - result := AddCommitDetailsToLabels(resource, &revisionMetadata) - labels := result.GetLabels() - assert.Equal(t, revisionMetadata.Author, labels["app.meta.commit-author"]) - assert.Equal(t, revisionMetadata.Message, labels["app.meta.commit-message"]) - }) - - t.Run("set labels when labels present", func(t *testing.T) { - resource := StrToUnstructured(` - apiVersion: v1 - kind: Service - metadata: - name: helm-guestbook - namespace: default - labels: - link: http://my-grafana.com/pre-generated-link - spec: - selector: - app: guestbook - type: LoadBalancer - status: - loadBalancer: - ingress: - - hostname: localhost`, - ) - - result := AddCommitDetailsToLabels(resource, &revisionMetadata) - labels := result.GetLabels() - assert.Equal(t, revisionMetadata.Author, labels["app.meta.commit-author"]) - assert.Equal(t, revisionMetadata.Message, labels["app.meta.commit-message"]) - assert.Equal(t, "http://my-grafana.com/pre-generated-link", result.GetLabels()["link"]) - }) -} diff --git a/pkg/apis/application/v1alpha1/types_codefresh.go b/pkg/apis/application/v1alpha1/types_codefresh.go index 0546eaca92e9e..1e2d922b407a4 100644 --- a/pkg/apis/application/v1alpha1/types_codefresh.go +++ b/pkg/apis/application/v1alpha1/types_codefresh.go @@ -17,19 +17,25 @@ func (a *Application) SetDefaultTypeMeta() { } } -func (a *ApplicationSpec) GetNonRefSource() (*ApplicationSource, int) { - if a.HasMultipleSources() { - for idx, source := range a.Sources { +func (spec *ApplicationSpec) GetNonRefSource() (*ApplicationSource, int) { + if spec.HasMultipleSources() { + for idx, source := range spec.Sources { if !source.IsRef() { return &source, idx } } } - if a.Source == nil { + if spec.Source == nil { return nil, -2 } // single source app - return a.Source, -1 + return spec.Source, -1 +} + +func (spec *ApplicationSpec) SourceUnderIdxIsHelm(idx int) bool { + source := spec.GetSourcePtrByIndex(idx) + + return source != nil && source.IsHelm() } From fb704aeb225da2f8784be2ded5c4a31f3a6ebecc Mon Sep 17 00:00:00 2001 From: oleksandr-codefresh Date: Mon, 30 Sep 2024 16:24:09 +0300 Subject: [PATCH 11/20] event-reporter: refactoring of getApplicationLegacyRevisionDetails method --- event_reporter/reporter/app_revision.go | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/event_reporter/reporter/app_revision.go b/event_reporter/reporter/app_revision.go index 5c0a53abebfd6..d03114b29a3a1 100644 --- a/event_reporter/reporter/app_revision.go +++ b/event_reporter/reporter/app_revision.go @@ -11,18 +11,22 @@ import ( ) // treats multi-sourced apps as single source and gets first revision details -func getApplicationLegacyRevisionDetails(a *appv1.Application, revisionsMetadata *utils.AppSyncRevisionsMetadata) *appv1.RevisionMetadata { - _, sourceIdx := a.Spec.GetNonRefSource() +func getApplicationLegacyRevisionDetails(a *appv1.Application, revisionsWithMetadata *utils.AppSyncRevisionsMetadata) *appv1.RevisionMetadata { + if revisionsWithMetadata.SyncRevisions == nil || len(revisionsWithMetadata.SyncRevisions) == 0 { + return nil + } - if sourceIdx == -1 { // single source app - sourceIdx = 0 + sourceIdx := 0 + + if a.Spec.HasMultipleSources() { + _, sourceIdx = a.Spec.GetNonRefSource() } - if revisionsMetadata.SyncRevisions == nil || len(revisionsMetadata.SyncRevisions) == 0 { - return nil + if revisionWithMetadata := revisionsWithMetadata.SyncRevisions[sourceIdx]; revisionWithMetadata != nil { + return revisionWithMetadata.Metadata } - return revisionsMetadata.SyncRevisions[sourceIdx].Metadata + return nil } func (s *applicationEventReporter) getRevisionsDetails(ctx context.Context, a *appv1.Application, revisions []string) ([]*utils.RevisionWithMetadata, error) { From 91272b679dcd88ba7c581031b01936c26ea95a80 Mon Sep 17 00:00:00 2001 From: oleksandr-codefresh Date: Mon, 30 Sep 2024 18:27:30 +0300 Subject: [PATCH 12/20] event-reporter / app_revision_test.go: added some tests to AddCommitsDetailsToAnnotations, AddCommitsDetailsToAppAnnotations, getRevisions, getOperationSyncRevisions --- event_reporter/utils/app_revision_test.go | 274 +++++++++++++++++++++- 1 file changed, 273 insertions(+), 1 deletion(-) diff --git a/event_reporter/utils/app_revision_test.go b/event_reporter/utils/app_revision_test.go index 2f9049705d930..c6394b2a05285 100644 --- a/event_reporter/utils/app_revision_test.go +++ b/event_reporter/utils/app_revision_test.go @@ -1,6 +1,7 @@ package utils import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "testing" "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" @@ -101,7 +102,7 @@ func TestGetApplicationLatestRevision(t *testing.T) { }) } -func StrToUnstructured(jsonStr string) *unstructured.Unstructured { +func yamlToUnstructured(jsonStr string) *unstructured.Unstructured { obj := make(map[string]interface{}) err := yaml.Unmarshal([]byte(jsonStr), &obj) if err != nil { @@ -109,3 +110,274 @@ func StrToUnstructured(jsonStr string) *unstructured.Unstructured { } return &unstructured.Unstructured{Object: obj} } + +func jsonToAppSyncRevision(jsonStr string) *AppSyncRevisionsMetadata { + var obj AppSyncRevisionsMetadata + err := yaml.Unmarshal([]byte(jsonStr), &obj) + if err != nil { + panic(err) + } + return &obj +} + +func TestAddCommitsDetailsToAnnotations(t *testing.T) { + revisionMetadata := AppSyncRevisionsMetadata{ + SyncRevisions: []*RevisionWithMetadata{{ + Metadata: &v1alpha1.RevisionMetadata{ + Author: "demo usert", + Date: metav1.Time{}, + Message: "some message", + }, + }}, + } + + t.Run("set annotation when annotations object missing", func(t *testing.T) { + resource := yamlToUnstructured(` + apiVersion: v1 + kind: Service + metadata: + name: helm-guestbook + namespace: default + resourceVersion: "123" + uid: "4" + spec: + selector: + app: guestbook + type: LoadBalancer + status: + loadBalancer: + ingress: + - hostname: localhost`, + ) + + result := AddCommitsDetailsToAnnotations(resource, &revisionMetadata) + + revMetadatUnstructured := jsonToAppSyncRevision(result.GetAnnotations()[annotationRevisionKey]) + + assert.Equal(t, revisionMetadata.SyncRevisions[0].Metadata.Author, revMetadatUnstructured.SyncRevisions[0].Metadata.Author) + assert.Equal(t, revisionMetadata.SyncRevisions[0].Metadata.Message, revMetadatUnstructured.SyncRevisions[0].Metadata.Message) + }) + + t.Run("set annotation when annotations present", func(t *testing.T) { + resource := yamlToUnstructured(` + apiVersion: v1 + kind: Service + metadata: + name: helm-guestbook + namespace: default + annotations: + link: http://my-grafana.com/pre-generated-link + spec: + selector: + app: guestbook + type: LoadBalancer + status: + loadBalancer: + ingress: + - hostname: localhost`, + ) + + result := AddCommitsDetailsToAnnotations(resource, &revisionMetadata) + + revMetadatUnstructured := jsonToAppSyncRevision(result.GetAnnotations()[annotationRevisionKey]) + + assert.Equal(t, revisionMetadata.SyncRevisions[0].Metadata.Author, revMetadatUnstructured.SyncRevisions[0].Metadata.Author) + assert.Equal(t, revisionMetadata.SyncRevisions[0].Metadata.Message, revMetadatUnstructured.SyncRevisions[0].Metadata.Message) + }) +} + +func TestAddCommitsDetailsToAppAnnotations(t *testing.T) { + revisionMetadata := AppSyncRevisionsMetadata{ + SyncRevisions: []*RevisionWithMetadata{{ + Metadata: &v1alpha1.RevisionMetadata{ + Author: "demo usert", + Date: metav1.Time{}, + Message: "some message", + }, + }}, + } + + t.Run("set annotation when annotations object missing", func(t *testing.T) { + resource := v1alpha1.Application{ + ObjectMeta: metav1.ObjectMeta{}, + } + + result := AddCommitsDetailsToAppAnnotations(resource, &revisionMetadata) + + revMetadatUnstructured := jsonToAppSyncRevision(result.GetAnnotations()[annotationRevisionKey]) + + assert.Equal(t, revisionMetadata.SyncRevisions[0].Metadata.Author, revMetadatUnstructured.SyncRevisions[0].Metadata.Author) + assert.Equal(t, revisionMetadata.SyncRevisions[0].Metadata.Message, revMetadatUnstructured.SyncRevisions[0].Metadata.Message) + }) + + t.Run("set annotation when annotations present", func(t *testing.T) { + resource := v1alpha1.Application{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + "test": "value", + }, + }, + } + + result := AddCommitsDetailsToAppAnnotations(resource, &revisionMetadata) + + revMetadatUnstructured := jsonToAppSyncRevision(result.GetAnnotations()[annotationRevisionKey]) + + assert.Equal(t, revisionMetadata.SyncRevisions[0].Metadata.Author, revMetadatUnstructured.SyncRevisions[0].Metadata.Author) + assert.Equal(t, revisionMetadata.SyncRevisions[0].Metadata.Message, revMetadatUnstructured.SyncRevisions[0].Metadata.Message) + }) +} + +func TestGetRevisions(t *testing.T) { + t.Run("should return revisions when only they passed", func(t *testing.T) { + val := "test" + result := getRevisions(RevisionsData{ + Revisions: []string{val}, + }) + assert.Len(t, result, 1) + assert.Equal(t, val, result[0]) + }) + t.Run("should return revisions when revision also passed", func(t *testing.T) { + val := "test" + result := getRevisions(RevisionsData{ + Revisions: []string{val, "test2"}, + Revision: "fail", + }) + assert.Len(t, result, 2) + assert.Equal(t, val, result[0]) + }) + t.Run("should return revision", func(t *testing.T) { + val := "test" + result := getRevisions(RevisionsData{ + Revision: val, + }) + assert.Len(t, result, 1) + assert.Equal(t, val, result[0]) + }) +} + +func TestGetOperationSyncRevisions(t *testing.T) { + t.Run("should return Status.Sync.Revision like for new apps", func(t *testing.T) { + expectedResult := "test" + app := v1alpha1.Application{ + Status: v1alpha1.ApplicationStatus{ + Sync: v1alpha1.SyncStatus{ + Revision: expectedResult, + }, + }, + } + result := GetOperationSyncRevisions(&app) + + assert.Len(t, result, 1) + assert.Equal(t, expectedResult, result[0]) + }) + + t.Run("should return Status.Sync.Revisions like for new apps", func(t *testing.T) { + expectedResult := "multi-1" + app := v1alpha1.Application{ + Status: v1alpha1.ApplicationStatus{ + Sync: v1alpha1.SyncStatus{ + Revisions: []string{expectedResult, "multi-2"}, + Revision: "single", + }, + }, + } + + result := GetOperationSyncRevisions(&app) + + assert.Len(t, result, 2) + assert.Equal(t, expectedResult, result[0]) + }) + + t.Run("should return a.Status.OperationState.Operation.Sync.Revision", func(t *testing.T) { + expectedResult := "multi-1" + app := v1alpha1.Application{ + Status: v1alpha1.ApplicationStatus{ + Sync: v1alpha1.SyncStatus{ + Revision: "fallack", + }, + OperationState: &v1alpha1.OperationState{ + Operation: v1alpha1.Operation{ + Sync: &v1alpha1.SyncOperation{ + Revision: expectedResult, + }, + }, + }, + }, + } + + result := GetOperationSyncRevisions(&app) + + assert.Len(t, result, 1) + assert.Equal(t, expectedResult, result[0]) + }) + + t.Run("should return a.Status.OperationState.Operation.Sync.Revisions", func(t *testing.T) { + expectedResult := "multi-1" + + app := v1alpha1.Application{ + Status: v1alpha1.ApplicationStatus{ + Sync: v1alpha1.SyncStatus{ + Revision: "fallack", + }, + OperationState: &v1alpha1.OperationState{ + Operation: v1alpha1.Operation{ + Sync: &v1alpha1.SyncOperation{ + Revisions: []string{expectedResult, "multi-2"}, + Revision: "single", + }, + }, + }, + }, + } + + result := GetOperationSyncRevisions(&app) + + assert.Len(t, result, 2) + assert.Equal(t, expectedResult, result[0]) + }) + + t.Run("should return a.Operation.Sync.Revision for first app sync", func(t *testing.T) { + expectedResult := "multi-1" + app := v1alpha1.Application{ + Status: v1alpha1.ApplicationStatus{ + Sync: v1alpha1.SyncStatus{ + Revision: "fallack", + }, + }, + Operation: &v1alpha1.Operation{ + Sync: &v1alpha1.SyncOperation{ + Revision: expectedResult, + }, + }, + } + + result := GetOperationSyncRevisions(&app) + + assert.Len(t, result, 1) + assert.Equal(t, expectedResult, result[0]) + }) + + t.Run("should return a.Operation.Sync.Revisions for first app sync", func(t *testing.T) { + expectedResult := "multi-1" + + app := v1alpha1.Application{ + Status: v1alpha1.ApplicationStatus{ + Sync: v1alpha1.SyncStatus{ + Revision: "fallack", + }, + }, + Operation: &v1alpha1.Operation{ + Sync: &v1alpha1.SyncOperation{ + Revisions: []string{expectedResult, "multi-2"}, + Revision: "single", + }, + }, + } + + result := GetOperationSyncRevisions(&app) + + assert.Len(t, result, 2) + assert.Equal(t, expectedResult, result[0]) + }) +} From a8cc45e712a53fabd0caae7ffe8eb7cef37af625 Mon Sep 17 00:00:00 2001 From: oleksandr-codefresh Date: Tue, 1 Oct 2024 11:41:55 +0300 Subject: [PATCH 13/20] event-reporter / app_revision_test.go: added tests for GetRevisionsDetails method --- event_reporter/reporter/app_revision_test.go | 141 +++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 event_reporter/reporter/app_revision_test.go diff --git a/event_reporter/reporter/app_revision_test.go b/event_reporter/reporter/app_revision_test.go new file mode 100644 index 0000000000000..e0649e7615112 --- /dev/null +++ b/event_reporter/reporter/app_revision_test.go @@ -0,0 +1,141 @@ +package reporter + +import ( + "context" + "github.com/argoproj/argo-cd/v2/event_reporter/application/mocks" + "github.com/argoproj/argo-cd/v2/event_reporter/metrics" + "github.com/argoproj/argo-cd/v2/event_reporter/utils" + appclient "github.com/argoproj/argo-cd/v2/pkg/apiclient/application" + + "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" + servercache "github.com/argoproj/argo-cd/v2/server/cache" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "testing" +) + +func reporterWithMockedClient(t *testing.T, returnValue *v1alpha1.RevisionMetadata, returnError error) *applicationEventReporter { + appServiceClient := mocks.NewApplicationClient(t) + appServiceClient.On("RevisionMetadata", mock.Anything, mock.Anything, mock.Anything).Return(returnValue, returnError) + + return &applicationEventReporter{ + &servercache.Cache{}, + &MockCodefreshClient{}, + newAppLister(), + appServiceClient, + &metrics.MetricsServer{}, + } +} + +func TestGetRevisionsDetails(t *testing.T) { + + t.Run("should return revisions for single source app", func(t *testing.T) { + expectedRevision := "expected-revision" + expectedResult := []*utils.RevisionWithMetadata{{ + Revision: expectedRevision, + Metadata: &v1alpha1.RevisionMetadata{ + Author: "Test Author", + Message: "first commit", + }, + }} + + reporter := reporterWithMockedClient(t, expectedResult[0].Metadata, nil) + + app := v1alpha1.Application{ + Spec: v1alpha1.ApplicationSpec{ + Source: &v1alpha1.ApplicationSource{ + RepoURL: "https://my-site.com", + TargetRevision: "HEAD", + Path: ".", + }, + }, + } + + result, _ := reporter.getRevisionsDetails(context.Background(), &app, []string{expectedRevision}) + + assert.Equal(t, expectedResult, result) + }) + + t.Run("should return revisions for multi sourced apps", func(t *testing.T) { + expectedRevision1 := "expected-revision-1" + expectedRevision2 := "expected-revision-2" + expectedResult := []*utils.RevisionWithMetadata{{ + Revision: expectedRevision1, + Metadata: &v1alpha1.RevisionMetadata{ + Author: "Repo1 Author", + Message: "first commit repo 1", + }, + }, { + Revision: expectedRevision2, + Metadata: &v1alpha1.RevisionMetadata{ + Author: "Repo2 Author", + Message: "first commit repo 2", + }, + }} + + app := v1alpha1.Application{ + Spec: v1alpha1.ApplicationSpec{ + Sources: []v1alpha1.ApplicationSource{{ + RepoURL: "https://my-site.com/repo-1", + TargetRevision: "branch1", + Path: ".", + }, { + RepoURL: "https://my-site.com/repo-2", + TargetRevision: "branch2", + Path: ".", + }}, + }, + } + + project := app.Spec.GetProject() + + appServiceClient := mocks.NewApplicationClient(t) + appServiceClient.On("RevisionMetadata", mock.Anything, &appclient.RevisionMetadataQuery{ + Name: &app.Name, + AppNamespace: &app.Namespace, + Revision: &expectedRevision1, + Project: &project, + }).Return(expectedResult[0].Metadata, nil) + appServiceClient.On("RevisionMetadata", mock.Anything, &appclient.RevisionMetadataQuery{ + Name: &app.Name, + AppNamespace: &app.Namespace, + Revision: &expectedRevision2, + Project: &project, + }).Return(expectedResult[1].Metadata, nil) + + reporter := &applicationEventReporter{ + &servercache.Cache{}, + &MockCodefreshClient{}, + newAppLister(), + appServiceClient, + &metrics.MetricsServer{}, + } + + result, _ := reporter.getRevisionsDetails(context.Background(), &app, []string{expectedRevision1, expectedRevision2}) + + assert.Equal(t, expectedResult, result) + }) + + t.Run("should return only revision because of helm single source app", func(t *testing.T) { + expectedRevision := "expected-revision" + expectedResult := []*utils.RevisionWithMetadata{{ + Revision: expectedRevision, + }} + + reporter := reporterWithMockedClient(t, expectedResult[0].Metadata, nil) + + app := v1alpha1.Application{ + Spec: v1alpha1.ApplicationSpec{ + Source: &v1alpha1.ApplicationSource{ + RepoURL: "https://my-site.com", + TargetRevision: "HEAD", + Path: ".", + }, + }, + } + + result, _ := reporter.getRevisionsDetails(context.Background(), &app, []string{expectedRevision}) + + assert.Equal(t, expectedResult, result) + }) +} From 02c14a3fa0c0db6713b93210a563881fd290eaec Mon Sep 17 00:00:00 2001 From: oleksandr-codefresh Date: Tue, 1 Oct 2024 12:09:20 +0300 Subject: [PATCH 14/20] event-reporter: updated app client to support sourceIndex param in non-grpc mode --- event_reporter/application/client.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/event_reporter/application/client.go b/event_reporter/application/client.go index 1efe8c7962b83..cf8367ce472e4 100644 --- a/event_reporter/application/client.go +++ b/event_reporter/application/client.go @@ -107,6 +107,9 @@ func (c *httpApplicationClient) RevisionMetadata(ctx context.Context, in *appcli params := fmt.Sprintf("?appNamespace=%s&project=%s", *in.AppNamespace, *in.Project) + if in.SourceIndex != nil { + params += fmt.Sprintf("&sourceIndex=%d", *in.SourceIndex) + } url := fmt.Sprintf("%s/api/v1/applications/%s/revisions/%s/metadata%s", c.baseUrl, *in.Name, *in.Revision, params) revisionMetadata := &v1alpha1.RevisionMetadata{} err := c.execute(ctx, url, revisionMetadata) From 09fddac23ed7e482888e748d63afaae1711549a9 Mon Sep 17 00:00:00 2001 From: oleksandr-codefresh Date: Tue, 1 Oct 2024 12:10:11 +0300 Subject: [PATCH 15/20] event-reporter / app_revision.go: added sourceIndex param to applicationServiceClient.RevisionMetadata in order to properly support multisourced apps --- event_reporter/reporter/app_revision.go | 3 + event_reporter/reporter/app_revision_test.go | 76 +++++++++++++------- 2 files changed, 54 insertions(+), 25 deletions(-) diff --git a/event_reporter/reporter/app_revision.go b/event_reporter/reporter/app_revision.go index d03114b29a3a1..7500625893ec2 100644 --- a/event_reporter/reporter/app_revision.go +++ b/event_reporter/reporter/app_revision.go @@ -42,11 +42,14 @@ func (s *applicationEventReporter) getRevisionsDetails(ctx context.Context, a *a continue } + sourceIndex := int32(idx) + rm, err := s.applicationServiceClient.RevisionMetadata(ctx, &application.RevisionMetadataQuery{ Name: &a.Name, AppNamespace: &a.Namespace, Revision: &revision, Project: &project, + SourceIndex: &sourceIndex, }) if err != nil { return nil, err diff --git a/event_reporter/reporter/app_revision_test.go b/event_reporter/reporter/app_revision_test.go index e0649e7615112..b669cae5f8f56 100644 --- a/event_reporter/reporter/app_revision_test.go +++ b/event_reporter/reporter/app_revision_test.go @@ -2,33 +2,19 @@ package reporter import ( "context" + "testing" + "github.com/argoproj/argo-cd/v2/event_reporter/application/mocks" "github.com/argoproj/argo-cd/v2/event_reporter/metrics" "github.com/argoproj/argo-cd/v2/event_reporter/utils" - appclient "github.com/argoproj/argo-cd/v2/pkg/apiclient/application" - + "github.com/argoproj/argo-cd/v2/pkg/apiclient/application" "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" - servercache "github.com/argoproj/argo-cd/v2/server/cache" + "github.com/argoproj/argo-cd/v2/server/cache" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" - "testing" ) -func reporterWithMockedClient(t *testing.T, returnValue *v1alpha1.RevisionMetadata, returnError error) *applicationEventReporter { - appServiceClient := mocks.NewApplicationClient(t) - appServiceClient.On("RevisionMetadata", mock.Anything, mock.Anything, mock.Anything).Return(returnValue, returnError) - - return &applicationEventReporter{ - &servercache.Cache{}, - &MockCodefreshClient{}, - newAppLister(), - appServiceClient, - &metrics.MetricsServer{}, - } -} - func TestGetRevisionsDetails(t *testing.T) { - t.Run("should return revisions for single source app", func(t *testing.T) { expectedRevision := "expected-revision" expectedResult := []*utils.RevisionWithMetadata{{ @@ -39,8 +25,6 @@ func TestGetRevisionsDetails(t *testing.T) { }, }} - reporter := reporterWithMockedClient(t, expectedResult[0].Metadata, nil) - app := v1alpha1.Application{ Spec: v1alpha1.ApplicationSpec{ Source: &v1alpha1.ApplicationSource{ @@ -51,6 +35,26 @@ func TestGetRevisionsDetails(t *testing.T) { }, } + appServiceClient := mocks.NewApplicationClient(t) + project := app.Spec.GetProject() + sourceIdx1 := int32(0) + + appServiceClient.On("RevisionMetadata", mock.Anything, &application.RevisionMetadataQuery{ + Name: &app.Name, + AppNamespace: &app.Namespace, + Revision: &expectedResult[0].Revision, + Project: &project, + SourceIndex: &sourceIdx1, + }).Return(expectedResult[0].Metadata, nil) + + reporter := &applicationEventReporter{ + &cache.Cache{}, + &MockCodefreshClient{}, + newAppLister(), + appServiceClient, + &metrics.MetricsServer{}, + } + result, _ := reporter.getRevisionsDetails(context.Background(), &app, []string{expectedRevision}) assert.Equal(t, expectedResult, result) @@ -90,21 +94,25 @@ func TestGetRevisionsDetails(t *testing.T) { project := app.Spec.GetProject() appServiceClient := mocks.NewApplicationClient(t) - appServiceClient.On("RevisionMetadata", mock.Anything, &appclient.RevisionMetadataQuery{ + sourceIdx1 := int32(0) + sourceIdx2 := int32(1) + appServiceClient.On("RevisionMetadata", mock.Anything, &application.RevisionMetadataQuery{ Name: &app.Name, AppNamespace: &app.Namespace, Revision: &expectedRevision1, Project: &project, + SourceIndex: &sourceIdx1, }).Return(expectedResult[0].Metadata, nil) - appServiceClient.On("RevisionMetadata", mock.Anything, &appclient.RevisionMetadataQuery{ + appServiceClient.On("RevisionMetadata", mock.Anything, &application.RevisionMetadataQuery{ Name: &app.Name, AppNamespace: &app.Namespace, Revision: &expectedRevision2, Project: &project, + SourceIndex: &sourceIdx2, }).Return(expectedResult[1].Metadata, nil) reporter := &applicationEventReporter{ - &servercache.Cache{}, + &cache.Cache{}, &MockCodefreshClient{}, newAppLister(), appServiceClient, @@ -122,8 +130,6 @@ func TestGetRevisionsDetails(t *testing.T) { Revision: expectedRevision, }} - reporter := reporterWithMockedClient(t, expectedResult[0].Metadata, nil) - app := v1alpha1.Application{ Spec: v1alpha1.ApplicationSpec{ Source: &v1alpha1.ApplicationSource{ @@ -134,6 +140,26 @@ func TestGetRevisionsDetails(t *testing.T) { }, } + appServiceClient := mocks.NewApplicationClient(t) + project := app.Spec.GetProject() + sourceIdx1 := int32(0) + + appServiceClient.On("RevisionMetadata", mock.Anything, &application.RevisionMetadataQuery{ + Name: &app.Name, + AppNamespace: &app.Namespace, + Revision: &expectedResult[0].Revision, + Project: &project, + SourceIndex: &sourceIdx1, + }).Return(expectedResult[0].Metadata, nil) + + reporter := &applicationEventReporter{ + &cache.Cache{}, + &MockCodefreshClient{}, + newAppLister(), + appServiceClient, + &metrics.MetricsServer{}, + } + result, _ := reporter.getRevisionsDetails(context.Background(), &app, []string{expectedRevision}) assert.Equal(t, expectedResult, result) From 29db9f87dde4a07b118d7d38437aa3830a2b48a2 Mon Sep 17 00:00:00 2001 From: oleksandr-codefresh Date: Tue, 1 Oct 2024 16:06:54 +0300 Subject: [PATCH 16/20] event-reporter: lint fix --- event_reporter/utils/app_revision_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/event_reporter/utils/app_revision_test.go b/event_reporter/utils/app_revision_test.go index c6394b2a05285..ea3d33921d963 100644 --- a/event_reporter/utils/app_revision_test.go +++ b/event_reporter/utils/app_revision_test.go @@ -1,9 +1,10 @@ package utils import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "testing" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" "github.com/stretchr/testify/assert" From b395e0084fcc540a6db911128f521bf9a613de38 Mon Sep 17 00:00:00 2001 From: oleksandr-codefresh Date: Tue, 1 Oct 2024 16:10:07 +0300 Subject: [PATCH 17/20] event-reporter: fix lint issues --- event_reporter/reporter/app_revision.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/event_reporter/reporter/app_revision.go b/event_reporter/reporter/app_revision.go index 7500625893ec2..540b6db42afc6 100644 --- a/event_reporter/reporter/app_revision.go +++ b/event_reporter/reporter/app_revision.go @@ -5,13 +5,13 @@ import ( "github.com/argoproj/argo-cd/v2/event_reporter/utils" "github.com/argoproj/argo-cd/v2/pkg/apiclient/application" + "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" - appv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" log "github.com/sirupsen/logrus" ) // treats multi-sourced apps as single source and gets first revision details -func getApplicationLegacyRevisionDetails(a *appv1.Application, revisionsWithMetadata *utils.AppSyncRevisionsMetadata) *appv1.RevisionMetadata { +func getApplicationLegacyRevisionDetails(a *v1alpha1.Application, revisionsWithMetadata *utils.AppSyncRevisionsMetadata) *v1alpha1.RevisionMetadata { if revisionsWithMetadata.SyncRevisions == nil || len(revisionsWithMetadata.SyncRevisions) == 0 { return nil } @@ -29,7 +29,7 @@ func getApplicationLegacyRevisionDetails(a *appv1.Application, revisionsWithMeta return nil } -func (s *applicationEventReporter) getRevisionsDetails(ctx context.Context, a *appv1.Application, revisions []string) ([]*utils.RevisionWithMetadata, error) { +func (s *applicationEventReporter) getRevisionsDetails(ctx context.Context, a *v1alpha1.Application, revisions []string) ([]*utils.RevisionWithMetadata, error) { project := a.Spec.GetProject() rms := make([]*utils.RevisionWithMetadata, 0) @@ -63,7 +63,7 @@ func (s *applicationEventReporter) getRevisionsDetails(ctx context.Context, a *a return rms, nil } -func (s *applicationEventReporter) getApplicationRevisionsMetadata(ctx context.Context, logCtx *log.Entry, a *appv1.Application) (*utils.AppSyncRevisionsMetadata, error) { +func (s *applicationEventReporter) getApplicationRevisionsMetadata(ctx context.Context, logCtx *log.Entry, a *v1alpha1.Application) (*utils.AppSyncRevisionsMetadata, error) { result := &utils.AppSyncRevisionsMetadata{} if a.Status.Sync.Revision != "" || a.Status.Sync.Revisions != nil || (a.Status.History != nil && len(a.Status.History) > 0) { From d5c22f373e221143749c04a871fe58a29c8494d5 Mon Sep 17 00:00:00 2001 From: oleksandr-codefresh Date: Tue, 1 Oct 2024 16:21:01 +0300 Subject: [PATCH 18/20] event-reporter: fix lint issues --- event_reporter/reporter/app_revision_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/event_reporter/reporter/app_revision_test.go b/event_reporter/reporter/app_revision_test.go index b669cae5f8f56..4b9611738432f 100644 --- a/event_reporter/reporter/app_revision_test.go +++ b/event_reporter/reporter/app_revision_test.go @@ -10,6 +10,7 @@ import ( "github.com/argoproj/argo-cd/v2/pkg/apiclient/application" "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" "github.com/argoproj/argo-cd/v2/server/cache" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) From dc7b41495ec70a2c860ac49dede92d4f2543ec69 Mon Sep 17 00:00:00 2001 From: oleksandr-codefresh Date: Fri, 4 Oct 2024 15:59:59 +0300 Subject: [PATCH 19/20] event-reporter: added back regacy logic with setting of commit details to labels as new runtimes should work on old on-prem versions --- event_reporter/reporter/event_payload.go | 10 ++++ event_reporter/utils/app_revision.go | 32 ++++++++++++ event_reporter/utils/app_revision_test.go | 59 +++++++++++++++++++++++ 3 files changed, 101 insertions(+) diff --git a/event_reporter/reporter/event_payload.go b/event_reporter/reporter/event_payload.go index f02d2ebd66465..cbac9a9031d32 100644 --- a/event_reporter/reporter/event_payload.go +++ b/event_reporter/reporter/event_payload.go @@ -55,6 +55,9 @@ func getResourceEventPayload( if err == nil { actualObject = utils.AddCommitsDetailsToAnnotations(actualObject, originalAppRevisionsMetadata) + if originalApplication != nil { + actualObject = utils.AddCommitDetailsToLabels(actualObject, getApplicationLegacyRevisionDetails(originalApplication, originalAppRevisionsMetadata)) + } object, err = actualObject.MarshalJSON() if err != nil { @@ -77,6 +80,9 @@ func getResourceEventPayload( u.SetNamespace(rs.Namespace) if originalAppRevisionsMetadata != nil { u = utils.AddCommitsDetailsToAnnotations(u, originalAppRevisionsMetadata) + if originalApplication != nil { + u = utils.AddCommitDetailsToLabels(u, getApplicationLegacyRevisionDetails(originalApplication, originalAppRevisionsMetadata)) + } } object, err = u.MarshalJSON() @@ -91,6 +97,9 @@ func getResourceEventPayload( } if originalAppRevisionsMetadata != nil { unstructuredWithNamespace = utils.AddCommitsDetailsToAnnotations(unstructuredWithNamespace, originalAppRevisionsMetadata) + if originalApplication != nil { + unstructuredWithNamespace = utils.AddCommitDetailsToLabels(unstructuredWithNamespace, getApplicationLegacyRevisionDetails(originalApplication, originalAppRevisionsMetadata)) + } } object, _ = unstructuredWithNamespace.MarshalJSON() @@ -239,6 +248,7 @@ func (s *applicationEventReporter) getApplicationEventPayload( } utils.AddCommitsDetailsToAppAnnotations(obj, revisionsMetadata) + utils.AddCommitsDetailsToAppLabels(&obj, getApplicationLegacyRevisionDetails(&obj, revisionsMetadata)) object, err := json.Marshal(&obj) if err != nil { diff --git a/event_reporter/utils/app_revision.go b/event_reporter/utils/app_revision.go index 83280e63e226e..161f37f20d5a2 100644 --- a/event_reporter/utils/app_revision.go +++ b/event_reporter/utils/app_revision.go @@ -164,3 +164,35 @@ func AddCommitsDetailsToAppAnnotations(app appv1.Application, revisionsMetadata return app } + +func AddCommitDetailsToLabels(u *unstructured.Unstructured, revisionMetadata *appv1.RevisionMetadata) *unstructured.Unstructured { + if revisionMetadata == nil || u == nil { + return u + } + + if field, _, _ := unstructured.NestedFieldCopy(u.Object, "metadata", "labels"); field == nil { + _ = unstructured.SetNestedStringMap(u.Object, map[string]string{}, "metadata", "labels") + } + + _ = unstructured.SetNestedField(u.Object, revisionMetadata.Date.Format("2006-01-02T15:04:05.000Z"), "metadata", "labels", "app.meta.commit-date") + _ = unstructured.SetNestedField(u.Object, revisionMetadata.Author, "metadata", "labels", "app.meta.commit-author") + _ = unstructured.SetNestedField(u.Object, revisionMetadata.Message, "metadata", "labels", "app.meta.commit-message") + + return u +} + +func AddCommitsDetailsToAppLabels(app *appv1.Application, revisionMetadata *appv1.RevisionMetadata) *appv1.Application { + if revisionMetadata == nil { + return app + } + + if app.ObjectMeta.Labels == nil { + app.ObjectMeta.Labels = map[string]string{} + } + + app.ObjectMeta.Labels["app.meta.commit-date"] = revisionMetadata.Date.Format("2006-01-02T15:04:05.000Z") + app.ObjectMeta.Labels["app.meta.commit-author"] = revisionMetadata.Author + app.ObjectMeta.Labels["app.meta.commit-message"] = revisionMetadata.Message + + return app +} diff --git a/event_reporter/utils/app_revision_test.go b/event_reporter/utils/app_revision_test.go index ea3d33921d963..370edecdb2d5a 100644 --- a/event_reporter/utils/app_revision_test.go +++ b/event_reporter/utils/app_revision_test.go @@ -382,3 +382,62 @@ func TestGetOperationSyncRevisions(t *testing.T) { assert.Equal(t, expectedResult, result[0]) }) } + +func TestAddCommitDetailsToLabels(t *testing.T) { + revisionMetadata := v1alpha1.RevisionMetadata{ + Author: "demo usert", + Date: metav1.Time{}, + Message: "some message", + } + + t.Run("set labels when lable object missing", func(t *testing.T) { + resource := yamlToUnstructured(` + apiVersion: v1 + kind: Service + metadata: + name: helm-guestbook + namespace: default + resourceVersion: "123" + uid: "4" + spec: + selector: + app: guestbook + type: LoadBalancer + status: + loadBalancer: + ingress: + - hostname: localhost`, + ) + + result := AddCommitDetailsToLabels(resource, &revisionMetadata) + labels := result.GetLabels() + assert.Equal(t, revisionMetadata.Author, labels["app.meta.commit-author"]) + assert.Equal(t, revisionMetadata.Message, labels["app.meta.commit-message"]) + }) + + t.Run("set labels when labels present", func(t *testing.T) { + resource := yamlToUnstructured(` + apiVersion: v1 + kind: Service + metadata: + name: helm-guestbook + namespace: default + labels: + link: http://my-grafana.com/pre-generated-link + spec: + selector: + app: guestbook + type: LoadBalancer + status: + loadBalancer: + ingress: + - hostname: localhost`, + ) + + result := AddCommitDetailsToLabels(resource, &revisionMetadata) + labels := result.GetLabels() + assert.Equal(t, revisionMetadata.Author, labels["app.meta.commit-author"]) + assert.Equal(t, revisionMetadata.Message, labels["app.meta.commit-message"]) + assert.Equal(t, "http://my-grafana.com/pre-generated-link", result.GetLabels()["link"]) + }) +} From 33e0b9734c2872dfcd753906fa6b5fd72fcbc885 Mon Sep 17 00:00:00 2001 From: oleksandr-codefresh Date: Sat, 5 Oct 2024 15:27:07 +0300 Subject: [PATCH 20/20] event-reporter: added condition to not send empty array for ChangeRevisions metadata --- event_reporter/reporter/app_revision.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/event_reporter/reporter/app_revision.go b/event_reporter/reporter/app_revision.go index 540b6db42afc6..b4c68f346325f 100644 --- a/event_reporter/reporter/app_revision.go +++ b/event_reporter/reporter/app_revision.go @@ -82,7 +82,7 @@ func (s *applicationEventReporter) getApplicationRevisionsMetadata(ctx context.C logCtx.WithError(err).Warnf("failed to get application(%s) change revisions metadata, resuming", a.GetName()) } - if err == nil && operationChangeRevisionsMetadata != nil { + if err == nil && operationChangeRevisionsMetadata != nil && len(operationChangeRevisionsMetadata) > 0 { result.ChangeRevisions = operationChangeRevisionsMetadata } }