From 79d38c7eafaf43671c2279217ab3cfce7b1f13bf Mon Sep 17 00:00:00 2001 From: Jorge Turrado Date: Thu, 4 Apr 2024 01:39:38 +0200 Subject: [PATCH 01/16] feat(application-controller): Add support for rollback multi-source applications Signed-off-by: Jorge Turrado --- assets/swagger.json | 38 +++++++ cmd/argocd/commands/admin/app.go | 2 +- cmd/argocd/commands/app_test.go | 36 +++++++ controller/appcontroller.go | 2 +- controller/hook.go | 2 +- controller/state.go | 20 ++-- controller/state_test.go | 65 ++++++------ controller/sync.go | 27 +++-- pkg/apiclient/application/application.pb.go | 78 ++++++++++++++- pkg/apiclient/repository/repository.pb.go | 84 ++++++++++++++-- pkg/apis/application/v1alpha1/types.go | 10 ++ server/application/application.go | 60 ++++++++++-- server/application/application.proto | 4 + server/application/application_test.go | 42 +++++++- server/repository/repository.go | 36 +++++-- server/repository/repository.proto | 4 + server/repository/repository_test.go | 64 +++++++++++- .../application-create-panel.tsx | 2 +- .../application-deployment-history.scss | 7 ++ .../application-deployment-history.tsx | 98 ++++++++++++++----- .../revision-metadata-rows.tsx | 12 ++- .../application-details.tsx | 24 +++-- .../application-operation-state.tsx | 10 +- .../application-status-panel.tsx | 38 ++++--- .../revision-metadata-panel.tsx | 4 +- .../resource-details/resource-details.tsx | 72 +++++++------- ui/src/app/applications/components/utils.tsx | 43 +++++++- .../shared/services/applications-service.ts | 8 +- ui/src/app/shared/services/repo-service.ts | 4 +- util/argo/ref_sources.go | 62 ++++++++++++ util/argo/ref_sources_test.go | 95 ++++++++++++++++++ 31 files changed, 862 insertions(+), 191 deletions(-) create mode 100644 util/argo/ref_sources.go create mode 100644 util/argo/ref_sources_test.go diff --git a/assets/swagger.json b/assets/swagger.json index f9c83a63eb66e..7d863dc71a028 100644 --- a/assets/swagger.json +++ b/assets/swagger.json @@ -1633,6 +1633,20 @@ "type": "string", "name": "project", "in": "query" + }, + { + "type": "integer", + "format": "int32", + "description": "source index (for multi source apps).", + "name": "sourceIndex", + "in": "query" + }, + { + "type": "integer", + "format": "int32", + "description": "versionId from historical data (for multi source apps).", + "name": "versionId", + "in": "query" } ], "responses": { @@ -1683,6 +1697,20 @@ "type": "string", "name": "project", "in": "query" + }, + { + "type": "integer", + "format": "int32", + "description": "source index (for multi source apps).", + "name": "sourceIndex", + "in": "query" + }, + { + "type": "integer", + "format": "int32", + "description": "versionId from historical data (for multi source apps).", + "name": "versionId", + "in": "query" } ], "responses": { @@ -5130,6 +5158,16 @@ }, "source": { "$ref": "#/definitions/v1alpha1ApplicationSource" + }, + "sourceIndex": { + "type": "integer", + "format": "int32", + "title": "source index (for multi source apps)" + }, + "versionId": { + "type": "integer", + "format": "int32", + "title": "versionId from historical data (for multi source apps)" } } }, diff --git a/cmd/argocd/commands/admin/app.go b/cmd/argocd/commands/admin/app.go index 7374a6315978e..1add1bee50b55 100644 --- a/cmd/argocd/commands/admin/app.go +++ b/cmd/argocd/commands/admin/app.go @@ -434,7 +434,7 @@ func reconcileApplications( sources = append(sources, app.Spec.GetSource()) revisions = append(revisions, app.Spec.GetSource().TargetRevision) - res, err := appStateManager.CompareAppState(&app, proj, revisions, sources, false, false, nil, false) + res, err := appStateManager.CompareAppState(&app, proj, revisions, sources, false, false, nil, false, false) if err != nil { return nil, err } diff --git a/cmd/argocd/commands/app_test.go b/cmd/argocd/commands/app_test.go index b146ca2458a1e..584516a97ea62 100644 --- a/cmd/argocd/commands/app_test.go +++ b/cmd/argocd/commands/app_test.go @@ -212,6 +212,42 @@ func TestPrintTreeViewDetailedAppGet(t *testing.T) { assert.Contains(t, output, "numalogic-rollout-demo-5dcd5457d5-6trpt") assert.Contains(t, output, "Degraded") assert.Contains(t, output, "Readiness Gate failed") +} + +func TestFindRevisionHistoryWithoutPassedIdWithMultipleSources(t *testing.T) { + + histories := v1alpha1.RevisionHistories{} + + histories = append(histories, v1alpha1.RevisionHistory{ID: 1}) + histories = append(histories, v1alpha1.RevisionHistory{ID: 2}) + histories = append(histories, v1alpha1.RevisionHistory{ID: 3}) + + status := v1alpha1.ApplicationStatus{ + Resources: nil, + Sync: v1alpha1.SyncStatus{}, + Health: v1alpha1.HealthStatus{}, + History: histories, + Conditions: nil, + ReconciledAt: nil, + OperationState: nil, + ObservedAt: nil, + SourceType: "", + Summary: v1alpha1.ApplicationSummary{}, + } + + application := v1alpha1.Application{ + Status: status, + } + + history, err := findRevisionHistory(&application, -1) + + if err != nil { + t.Fatal("Find revision history should fail without errors") + } + + if history == nil { + t.Fatal("History should be found") + } } diff --git a/controller/appcontroller.go b/controller/appcontroller.go index 115e522fde897..111be12c68249 100644 --- a/controller/appcontroller.go +++ b/controller/appcontroller.go @@ -1594,7 +1594,7 @@ func (ctrl *ApplicationController) processAppRefreshQueueItem() (processNext boo compareResult, err := ctrl.appStateManager.CompareAppState(app, project, revisions, sources, refreshType == appv1.RefreshTypeHard, - comparisonLevel == CompareWithLatestForceResolve, localManifests, hasMultipleSources) + comparisonLevel == CompareWithLatestForceResolve, localManifests, hasMultipleSources, false) if goerrors.Is(err, CompareStateRepoError) { logCtx.Warnf("Ignoring temporary failed attempt to compare app state against repo: %v", err) diff --git a/controller/hook.go b/controller/hook.go index 0c019ac6a1e08..451e25f4df7a3 100644 --- a/controller/hook.go +++ b/controller/hook.go @@ -51,7 +51,7 @@ func (ctrl *ApplicationController) executePostDeleteHooks(app *v1alpha1.Applicat revisions = append(revisions, src.TargetRevision) } - targets, _, err := ctrl.appStateManager.GetRepoObjs(app, app.Spec.GetSources(), appLabelKey, revisions, false, false, false, proj) + targets, _, err := ctrl.appStateManager.GetRepoObjs(app, app.Spec.GetSources(), appLabelKey, revisions, false, false, false, proj, false) if err != nil { return false, err } diff --git a/controller/state.go b/controller/state.go index f94446a0f588a..080d74559d571 100644 --- a/controller/state.go +++ b/controller/state.go @@ -71,9 +71,9 @@ type managedResource struct { // AppStateManager defines methods which allow to compare application spec and actual application state. type AppStateManager interface { - CompareAppState(app *v1alpha1.Application, project *v1alpha1.AppProject, revisions []string, sources []v1alpha1.ApplicationSource, noCache bool, noRevisionCache bool, localObjects []string, hasMultipleSources bool) (*comparisonResult, error) + CompareAppState(app *v1alpha1.Application, project *v1alpha1.AppProject, revisions []string, sources []v1alpha1.ApplicationSource, noCache bool, noRevisionCache bool, localObjects []string, hasMultipleSources bool, rollback bool) (*comparisonResult, error) SyncAppState(app *v1alpha1.Application, state *v1alpha1.OperationState) - GetRepoObjs(app *v1alpha1.Application, sources []v1alpha1.ApplicationSource, appLabelKey string, revisions []string, noCache, noRevisionCache, verifySignature bool, proj *v1alpha1.AppProject) ([]*unstructured.Unstructured, []*apiclient.ManifestResponse, error) + GetRepoObjs(app *v1alpha1.Application, sources []v1alpha1.ApplicationSource, appLabelKey string, revisions []string, noCache, noRevisionCache, verifySignature bool, proj *v1alpha1.AppProject, rollback bool) ([]*unstructured.Unstructured, []*apiclient.ManifestResponse, error) } // comparisonResult holds the state of an application after the reconciliation @@ -126,7 +126,7 @@ type appStateManager struct { // task to the repo-server. It returns the list of generated manifests as unstructured // objects. It also returns the full response from all calls to the repo server as the // second argument. -func (m *appStateManager) GetRepoObjs(app *v1alpha1.Application, sources []v1alpha1.ApplicationSource, appLabelKey string, revisions []string, noCache, noRevisionCache, verifySignature bool, proj *v1alpha1.AppProject) ([]*unstructured.Unstructured, []*apiclient.ManifestResponse, error) { +func (m *appStateManager) GetRepoObjs(app *v1alpha1.Application, sources []v1alpha1.ApplicationSource, appLabelKey string, revisions []string, noCache, noRevisionCache, verifySignature bool, proj *v1alpha1.AppProject, rollback bool) ([]*unstructured.Unstructured, []*apiclient.ManifestResponse, error) { ts := stats.NewTimingStats() helmRepos, err := m.db.ListHelmRepositories(context.Background()) if err != nil { @@ -178,7 +178,14 @@ func (m *appStateManager) GetRepoObjs(app *v1alpha1.Application, sources []v1alp targetObjs := make([]*unstructured.Unstructured, 0) // Store the map of all sources having ref field into a map for applications with sources field - refSources, err := argo.GetRefSources(context.Background(), app.Spec, m.db.GetRepository) + // If it's for a rollback process, the refSources[*].targetRevision fields are the desired + // revisions for the rollback + refSources, err := argo.GetRefSources(context.Background(), argo.GetRefSourcesOptions{ + Sources: sources, + Db: m.db, + Revisions: revisions, + IsRollback: rollback, + }) if err != nil { return nil, nil, fmt.Errorf("failed to get ref sources: %v", err) } @@ -264,7 +271,6 @@ func (m *appStateManager) GetRepoObjs(app *v1alpha1.Application, sources []v1alp return nil, nil, fmt.Errorf("failed to unmarshal manifests for source %d of %d: %w", i+1, len(sources), err) } targetObjs = append(targetObjs, targetObj...) - manifestInfos = append(manifestInfos, manifestInfo) } @@ -395,7 +401,7 @@ func isManagedNamespace(ns *unstructured.Unstructured, app *v1alpha1.Application // CompareAppState compares application git state to the live app state, using the specified // revision and supplied source. If revision or overrides are empty, then compares against // revision and overrides in the app spec. -func (m *appStateManager) CompareAppState(app *v1alpha1.Application, project *v1alpha1.AppProject, revisions []string, sources []v1alpha1.ApplicationSource, noCache bool, noRevisionCache bool, localManifests []string, hasMultipleSources bool) (*comparisonResult, error) { +func (m *appStateManager) CompareAppState(app *v1alpha1.Application, project *v1alpha1.AppProject, revisions []string, sources []v1alpha1.ApplicationSource, noCache bool, noRevisionCache bool, localManifests []string, hasMultipleSources bool, rollback bool) (*comparisonResult, error) { ts := stats.NewTimingStats() appLabelKey, resourceOverrides, resFilter, err := m.getComparisonSettings() @@ -453,7 +459,7 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, project *v1 } } - targetObjs, manifestInfos, err = m.GetRepoObjs(app, sources, appLabelKey, revisions, noCache, noRevisionCache, verifySignature, project) + targetObjs, manifestInfos, err = m.GetRepoObjs(app, sources, appLabelKey, revisions, noCache, noRevisionCache, verifySignature, project, rollback) if err != nil { targetObjs = make([]*unstructured.Unstructured, 0) msg := fmt.Sprintf("Failed to load target state: %s", err.Error()) diff --git a/controller/state_test.go b/controller/state_test.go index e988be702c265..93b7022d1d4d4 100644 --- a/controller/state_test.go +++ b/controller/state_test.go @@ -49,7 +49,7 @@ func TestCompareAppStateEmpty(t *testing.T) { sources = append(sources, app.Spec.GetSource()) revisions := make([]string, 0) revisions = append(revisions, "") - compRes, err := ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, sources, false, false, nil, false) + compRes, err := ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, sources, false, false, nil, false, false) assert.NoError(t, err) assert.NotNil(t, compRes) assert.NotNil(t, compRes.syncStatus) @@ -67,18 +67,18 @@ func TestCompareAppStateRepoError(t *testing.T) { sources = append(sources, app.Spec.GetSource()) revisions := make([]string, 0) revisions = append(revisions, "") - compRes, err := ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, sources, false, false, nil, false) + compRes, err := ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, sources, false, false, nil, false, false) assert.Nil(t, compRes) assert.EqualError(t, err, CompareStateRepoError.Error()) // expect to still get compare state error to as inside grace period - compRes, err = ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, sources, false, false, nil, false) + compRes, err = ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, sources, false, false, nil, false, false) assert.Nil(t, compRes) assert.EqualError(t, err, CompareStateRepoError.Error()) time.Sleep(10 * time.Second) // expect to not get error as outside of grace period, but status should be unknown - compRes, err = ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, sources, false, false, nil, false) + compRes, err = ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, sources, false, false, nil, false, false) assert.NotNil(t, compRes) assert.NoError(t, err) assert.Equal(t, compRes.syncStatus.Status, argoappv1.SyncStatusCodeUnknown) @@ -113,7 +113,7 @@ func TestCompareAppStateNamespaceMetadataDiffers(t *testing.T) { sources = append(sources, app.Spec.GetSource()) revisions := make([]string, 0) revisions = append(revisions, "") - compRes, err := ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, sources, false, false, nil, false) + compRes, err := ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, sources, false, false, nil, false, false) assert.NoError(t, err) assert.NotNil(t, compRes) assert.NotNil(t, compRes.syncStatus) @@ -162,7 +162,7 @@ func TestCompareAppStateNamespaceMetadataDiffersToManifest(t *testing.T) { sources = append(sources, app.Spec.GetSource()) revisions := make([]string, 0) revisions = append(revisions, "") - compRes, err := ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, sources, false, false, nil, false) + compRes, err := ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, sources, false, false, nil, false, false) assert.NoError(t, err) assert.NotNil(t, compRes) assert.NotNil(t, compRes.syncStatus) @@ -220,7 +220,7 @@ func TestCompareAppStateNamespaceMetadata(t *testing.T) { sources = append(sources, app.Spec.GetSource()) revisions := make([]string, 0) revisions = append(revisions, "") - compRes, err := ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, sources, false, false, nil, false) + compRes, err := ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, sources, false, false, nil, false, false) assert.NoError(t, err) assert.NotNil(t, compRes) assert.NotNil(t, compRes.syncStatus) @@ -279,7 +279,7 @@ func TestCompareAppStateNamespaceMetadataIsTheSame(t *testing.T) { sources = append(sources, app.Spec.GetSource()) revisions := make([]string, 0) revisions = append(revisions, "") - compRes, err := ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, sources, false, false, nil, false) + compRes, err := ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, sources, false, false, nil, false, false) assert.NoError(t, err) assert.NotNil(t, compRes) assert.NotNil(t, compRes.syncStatus) @@ -307,7 +307,7 @@ func TestCompareAppStateMissing(t *testing.T) { sources = append(sources, app.Spec.GetSource()) revisions := make([]string, 0) revisions = append(revisions, "") - compRes, err := ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, sources, false, false, nil, false) + compRes, err := ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, sources, false, false, nil, false, false) assert.NoError(t, err) assert.NotNil(t, compRes) assert.NotNil(t, compRes.syncStatus) @@ -339,7 +339,7 @@ func TestCompareAppStateExtra(t *testing.T) { sources = append(sources, app.Spec.GetSource()) revisions := make([]string, 0) revisions = append(revisions, "") - compRes, err := ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, sources, false, false, nil, false) + compRes, err := ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, sources, false, false, nil, false, false) assert.NoError(t, err) assert.NotNil(t, compRes) assert.Equal(t, argoappv1.SyncStatusCodeOutOfSync, compRes.syncStatus.Status) @@ -370,7 +370,7 @@ func TestCompareAppStateHook(t *testing.T) { sources = append(sources, app.Spec.GetSource()) revisions := make([]string, 0) revisions = append(revisions, "") - compRes, err := ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, sources, false, false, nil, false) + compRes, err := ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, sources, false, false, nil, false, false) assert.NoError(t, err) assert.NotNil(t, compRes) assert.Equal(t, argoappv1.SyncStatusCodeSynced, compRes.syncStatus.Status) @@ -402,7 +402,7 @@ func TestCompareAppStateSkipHook(t *testing.T) { sources = append(sources, app.Spec.GetSource()) revisions := make([]string, 0) revisions = append(revisions, "") - compRes, err := ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, sources, false, false, nil, false) + compRes, err := ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, sources, false, false, nil, false, false) assert.NoError(t, err) assert.NotNil(t, compRes) assert.Equal(t, argoappv1.SyncStatusCodeSynced, compRes.syncStatus.Status) @@ -433,7 +433,7 @@ func TestCompareAppStateCompareOptionIgnoreExtraneous(t *testing.T) { sources = append(sources, app.Spec.GetSource()) revisions := make([]string, 0) revisions = append(revisions, "") - compRes, err := ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, sources, false, false, nil, false) + compRes, err := ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, sources, false, false, nil, false, false) assert.NoError(t, err) assert.NotNil(t, compRes) @@ -466,7 +466,7 @@ func TestCompareAppStateExtraHook(t *testing.T) { sources = append(sources, app.Spec.GetSource()) revisions := make([]string, 0) revisions = append(revisions, "") - compRes, err := ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, sources, false, false, nil, false) + compRes, err := ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, sources, false, false, nil, false, false) assert.NoError(t, err) assert.NotNil(t, compRes) @@ -495,7 +495,7 @@ func TestAppRevisionsSingleSource(t *testing.T) { app := newFakeApp() revisions := make([]string, 0) revisions = append(revisions, "") - compRes, err := ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, app.Spec.GetSources(), false, false, nil, app.Spec.HasMultipleSources()) + compRes, err := ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, app.Spec.GetSources(), false, false, nil, app.Spec.HasMultipleSources(), false) assert.NoError(t, err) assert.NotNil(t, compRes) assert.NotNil(t, compRes.syncStatus) @@ -535,7 +535,7 @@ func TestAppRevisionsMultiSource(t *testing.T) { app := newFakeMultiSourceApp() revisions := make([]string, 0) revisions = append(revisions, "") - compRes, err := ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, app.Spec.GetSources(), false, false, nil, app.Spec.HasMultipleSources()) + compRes, err := ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, app.Spec.GetSources(), false, false, nil, app.Spec.HasMultipleSources(), false) assert.NoError(t, err) assert.NotNil(t, compRes) assert.NotNil(t, compRes.syncStatus) @@ -583,7 +583,7 @@ func TestCompareAppStateDuplicatedNamespacedResources(t *testing.T) { sources = append(sources, app.Spec.GetSource()) revisions := make([]string, 0) revisions = append(revisions, "") - compRes, err := ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, sources, false, false, nil, false) + compRes, err := ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, sources, false, false, nil, false, false) assert.NoError(t, err) assert.NotNil(t, compRes) @@ -620,7 +620,7 @@ func TestCompareAppStateManagedNamespaceMetadataWithLiveNsDoesNotGetPruned(t *te }, } ctrl := newFakeController(&data, nil) - compRes, err := ctrl.appStateManager.CompareAppState(app, &defaultProj, []string{}, app.Spec.Sources, false, false, nil, false) + compRes, err := ctrl.appStateManager.CompareAppState(app, &defaultProj, []string{}, app.Spec.Sources, false, false, nil, false, false) assert.NoError(t, err) assert.NotNil(t, compRes) @@ -674,8 +674,7 @@ func TestCompareAppStateWithManifestGeneratePath(t *testing.T) { ctrl := newFakeController(&data, nil) revisions := make([]string, 0) revisions = append(revisions, "abc123") - compRes, err := ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, app.Spec.GetSources(), false, false, nil, false) - + compRes, err := ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, app.Spec.GetSources(), false, false, nil, false, false) assert.NoError(t, err) assert.NotNil(t, compRes) assert.Equal(t, argoappv1.SyncStatusCodeSynced, compRes.syncStatus.Status) @@ -712,7 +711,7 @@ func TestSetHealth(t *testing.T) { sources = append(sources, app.Spec.GetSource()) revisions := make([]string, 0) revisions = append(revisions, "") - compRes, err := ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, sources, false, false, nil, false) + compRes, err := ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, sources, false, false, nil, false, false) assert.NoError(t, err) assert.Equal(t, health.HealthStatusHealthy, compRes.healthStatus.Status) @@ -749,7 +748,7 @@ func TestSetHealthSelfReferencedApp(t *testing.T) { sources = append(sources, app.Spec.GetSource()) revisions := make([]string, 0) revisions = append(revisions, "") - compRes, err := ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, sources, false, false, nil, false) + compRes, err := ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, sources, false, false, nil, false, false) assert.NoError(t, err) assert.Equal(t, health.HealthStatusHealthy, compRes.healthStatus.Status) @@ -824,7 +823,7 @@ func TestReturnUnknownComparisonStateOnSettingLoadError(t *testing.T) { sources = append(sources, app.Spec.GetSource()) revisions := make([]string, 0) revisions = append(revisions, "") - compRes, err := ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, sources, false, false, nil, false) + compRes, err := ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, sources, false, false, nil, false, false) assert.NoError(t, err) assert.Equal(t, health.HealthStatusUnknown, compRes.healthStatus.Status) @@ -965,7 +964,7 @@ func TestSignedResponseNoSignatureRequired(t *testing.T) { sources = append(sources, app.Spec.GetSource()) revisions := make([]string, 0) revisions = append(revisions, "") - compRes, err := ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, sources, false, false, nil, false) + compRes, err := ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, sources, false, false, nil, false, false) assert.NoError(t, err) assert.NotNil(t, compRes) assert.NotNil(t, compRes.syncStatus) @@ -992,7 +991,7 @@ func TestSignedResponseNoSignatureRequired(t *testing.T) { sources = append(sources, app.Spec.GetSource()) revisions := make([]string, 0) revisions = append(revisions, "") - compRes, err := ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, sources, false, false, nil, false) + compRes, err := ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, sources, false, false, nil, false, false) assert.NoError(t, err) assert.NotNil(t, compRes) assert.NotNil(t, compRes.syncStatus) @@ -1024,7 +1023,7 @@ func TestSignedResponseSignatureRequired(t *testing.T) { sources = append(sources, app.Spec.GetSource()) revisions := make([]string, 0) revisions = append(revisions, "") - compRes, err := ctrl.appStateManager.CompareAppState(app, &signedProj, revisions, sources, false, false, nil, false) + compRes, err := ctrl.appStateManager.CompareAppState(app, &signedProj, revisions, sources, false, false, nil, false, false) assert.NoError(t, err) assert.NotNil(t, compRes) assert.NotNil(t, compRes.syncStatus) @@ -1051,7 +1050,7 @@ func TestSignedResponseSignatureRequired(t *testing.T) { sources = append(sources, app.Spec.GetSource()) revisions := make([]string, 0) revisions = append(revisions, "abc123") - compRes, err := ctrl.appStateManager.CompareAppState(app, &signedProj, revisions, sources, false, false, nil, false) + compRes, err := ctrl.appStateManager.CompareAppState(app, &signedProj, revisions, sources, false, false, nil, false, false) assert.NoError(t, err) assert.NotNil(t, compRes) assert.NotNil(t, compRes.syncStatus) @@ -1078,7 +1077,7 @@ func TestSignedResponseSignatureRequired(t *testing.T) { sources = append(sources, app.Spec.GetSource()) revisions := make([]string, 0) revisions = append(revisions, "abc123") - compRes, err := ctrl.appStateManager.CompareAppState(app, &signedProj, revisions, sources, false, false, nil, false) + compRes, err := ctrl.appStateManager.CompareAppState(app, &signedProj, revisions, sources, false, false, nil, false, false) assert.NoError(t, err) assert.NotNil(t, compRes) assert.NotNil(t, compRes.syncStatus) @@ -1105,7 +1104,7 @@ func TestSignedResponseSignatureRequired(t *testing.T) { sources = append(sources, app.Spec.GetSource()) revisions := make([]string, 0) revisions = append(revisions, "abc123") - compRes, err := ctrl.appStateManager.CompareAppState(app, &signedProj, revisions, sources, false, false, nil, false) + compRes, err := ctrl.appStateManager.CompareAppState(app, &signedProj, revisions, sources, false, false, nil, false, false) assert.NoError(t, err) assert.NotNil(t, compRes) assert.NotNil(t, compRes.syncStatus) @@ -1135,7 +1134,7 @@ func TestSignedResponseSignatureRequired(t *testing.T) { sources = append(sources, app.Spec.GetSource()) revisions := make([]string, 0) revisions = append(revisions, "abc123") - compRes, err := ctrl.appStateManager.CompareAppState(app, &testProj, revisions, sources, false, false, nil, false) + compRes, err := ctrl.appStateManager.CompareAppState(app, &testProj, revisions, sources, false, false, nil, false, false) assert.NoError(t, err) assert.NotNil(t, compRes) assert.NotNil(t, compRes.syncStatus) @@ -1165,7 +1164,7 @@ func TestSignedResponseSignatureRequired(t *testing.T) { sources = append(sources, app.Spec.GetSource()) revisions := make([]string, 0) revisions = append(revisions, "abc123") - compRes, err := ctrl.appStateManager.CompareAppState(app, &signedProj, revisions, sources, false, false, localManifests, false) + compRes, err := ctrl.appStateManager.CompareAppState(app, &signedProj, revisions, sources, false, false, localManifests, false, false) assert.NoError(t, err) assert.NotNil(t, compRes) assert.NotNil(t, compRes.syncStatus) @@ -1195,7 +1194,7 @@ func TestSignedResponseSignatureRequired(t *testing.T) { sources = append(sources, app.Spec.GetSource()) revisions := make([]string, 0) revisions = append(revisions, "abc123") - compRes, err := ctrl.appStateManager.CompareAppState(app, &signedProj, revisions, sources, false, false, nil, false) + compRes, err := ctrl.appStateManager.CompareAppState(app, &signedProj, revisions, sources, false, false, nil, false, false) assert.NoError(t, err) assert.NotNil(t, compRes) assert.NotNil(t, compRes.syncStatus) @@ -1225,7 +1224,7 @@ func TestSignedResponseSignatureRequired(t *testing.T) { sources = append(sources, app.Spec.GetSource()) revisions := make([]string, 0) revisions = append(revisions, "abc123") - compRes, err := ctrl.appStateManager.CompareAppState(app, &signedProj, revisions, sources, false, false, localManifests, false) + compRes, err := ctrl.appStateManager.CompareAppState(app, &signedProj, revisions, sources, false, false, localManifests, false, false) assert.NoError(t, err) assert.NotNil(t, compRes) assert.NotNil(t, compRes.syncStatus) diff --git a/controller/sync.go b/controller/sync.go index c2f2c28473d92..0b88331029c68 100644 --- a/controller/sync.go +++ b/controller/sync.go @@ -109,21 +109,24 @@ func (m *appStateManager) SyncAppState(app *v1alpha1.Application, state *v1alpha return } - if syncOp.Source == nil || (syncOp.Sources != nil && len(syncOp.Sources) > 0) { - // normal sync case (where source is taken from app.spec.sources) + rollback := syncOp.Source != nil && !app.Spec.HasMultipleSources() || + syncOp.Sources != nil && app.Spec.HasMultipleSources() + + if rollback { + // rollback case if app.Spec.HasMultipleSources() { - sources = app.Spec.Sources + sources = state.Operation.Sync.Sources } else { - // normal sync case (where source is taken from app.spec.source) - source = app.Spec.GetSource() + source = *state.Operation.Sync.Source sources = make([]v1alpha1.ApplicationSource, 0) } } else { - // rollback case + // normal sync case (where source is taken from app.spec.sources) if app.Spec.HasMultipleSources() { - sources = state.Operation.Sync.Sources + sources = app.Spec.Sources } else { - source = *state.Operation.Sync.Source + // normal sync case (where source is taken from app.spec.source) + source = app.Spec.GetSource() sources = make([]v1alpha1.ApplicationSource, 0) } } @@ -171,19 +174,13 @@ func (m *appStateManager) SyncAppState(app *v1alpha1.Application, state *v1alpha return } - if app.Spec.HasMultipleSources() { - revisions = syncRes.Revisions - } else { - revisions = append(revisions, revision) - } - if !app.Spec.HasMultipleSources() { sources = []v1alpha1.ApplicationSource{source} revisions = []string{revision} } // ignore error if CompareStateRepoError, this shouldn't happen as noRevisionCache is true - compareResult, err := m.CompareAppState(app, proj, revisions, sources, false, true, syncOp.Manifests, app.Spec.HasMultipleSources()) + compareResult, err := m.CompareAppState(app, proj, revisions, sources, false, true, syncOp.Manifests, app.Spec.HasMultipleSources(), rollback) if err != nil && !goerrors.Is(err, CompareStateRepoError) { state.Phase = common.OperationError state.Message = err.Error() diff --git a/pkg/apiclient/application/application.pb.go b/pkg/apiclient/application/application.pb.go index 2f87272d3ed3e..4cccc205cfc75 100644 --- a/pkg/apiclient/application/application.pb.go +++ b/pkg/apiclient/application/application.pb.go @@ -214,8 +214,12 @@ type RevisionMetadataQuery struct { // the revision of the app Revision *string `protobuf:"bytes,2,req,name=revision" json:"revision,omitempty"` // the application's namespace - AppNamespace *string `protobuf:"bytes,3,opt,name=appNamespace" json:"appNamespace,omitempty"` - Project *string `protobuf:"bytes,4,opt,name=project" json:"project,omitempty"` + AppNamespace *string `protobuf:"bytes,3,opt,name=appNamespace" json:"appNamespace,omitempty"` + Project *string `protobuf:"bytes,4,opt,name=project" json:"project,omitempty"` + // source index (for multi source apps) + SourceIndex *int32 `protobuf:"varint,5,opt,name=sourceIndex" json:"sourceIndex,omitempty"` + // versionId from historical data (for multi source apps) + VersionId *int32 `protobuf:"varint,6,opt,name=versionId" json:"versionId,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -282,6 +286,20 @@ func (m *RevisionMetadataQuery) GetProject() string { return "" } +func (m *RevisionMetadataQuery) GetSourceIndex() int32 { + if m != nil && m.SourceIndex != nil { + return *m.SourceIndex + } + return 0 +} + +func (m *RevisionMetadataQuery) GetVersionId() int32 { + if m != nil && m.VersionId != nil { + return *m.VersionId + } + return 0 +} + // ApplicationEventsQuery is a query for application resource events type ApplicationResourceEventsQuery struct { Name *string `protobuf:"bytes,1,req,name=name" json:"name,omitempty"` @@ -4373,6 +4391,16 @@ func (m *RevisionMetadataQuery) MarshalToSizedBuffer(dAtA []byte) (int, error) { i -= len(m.XXX_unrecognized) copy(dAtA[i:], m.XXX_unrecognized) } + if m.VersionId != nil { + i = encodeVarintApplication(dAtA, i, uint64(*m.VersionId)) + i-- + dAtA[i] = 0x30 + } + if m.SourceIndex != nil { + i = encodeVarintApplication(dAtA, i, uint64(*m.SourceIndex)) + i-- + dAtA[i] = 0x28 + } if m.Project != nil { i -= len(*m.Project) copy(dAtA[i:], *m.Project) @@ -6714,6 +6742,12 @@ func (m *RevisionMetadataQuery) Size() (n int) { l = len(*m.Project) n += 1 + l + sovApplication(uint64(l)) } + if m.SourceIndex != nil { + n += 1 + sovApplication(uint64(*m.SourceIndex)) + } + if m.VersionId != nil { + n += 1 + sovApplication(uint64(*m.VersionId)) + } if m.XXX_unrecognized != nil { n += len(m.XXX_unrecognized) } @@ -8332,6 +8366,46 @@ func (m *RevisionMetadataQuery) Unmarshal(dAtA []byte) error { s := string(dAtA[iNdEx:postIndex]) m.Project = &s iNdEx = postIndex + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field SourceIndex", wireType) + } + var v int32 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApplication + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int32(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.SourceIndex = &v + case 6: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field VersionId", wireType) + } + var v int32 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApplication + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int32(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.VersionId = &v default: iNdEx = preIndex skippy, err := skipApplication(dAtA[iNdEx:]) diff --git a/pkg/apiclient/repository/repository.pb.go b/pkg/apiclient/repository/repository.pb.go index 3e7938829a26b..6aeddbd1209a5 100644 --- a/pkg/apiclient/repository/repository.pb.go +++ b/pkg/apiclient/repository/repository.pb.go @@ -163,12 +163,16 @@ func (m *AppInfo) GetPath() string { // RepoAppDetailsQuery contains query information for app details request type RepoAppDetailsQuery struct { - Source *v1alpha1.ApplicationSource `protobuf:"bytes,1,opt,name=source,proto3" json:"source,omitempty"` - AppName string `protobuf:"bytes,2,opt,name=appName,proto3" json:"appName,omitempty"` - AppProject string `protobuf:"bytes,3,opt,name=appProject,proto3" json:"appProject,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + Source *v1alpha1.ApplicationSource `protobuf:"bytes,1,opt,name=source,proto3" json:"source,omitempty"` + AppName string `protobuf:"bytes,2,opt,name=appName,proto3" json:"appName,omitempty"` + AppProject string `protobuf:"bytes,3,opt,name=appProject,proto3" json:"appProject,omitempty"` + // source index (for multi source apps) + SourceIndex int32 `protobuf:"varint,4,opt,name=sourceIndex,proto3" json:"sourceIndex,omitempty"` + // versionId from historical data (for multi source apps) + VersionId int32 `protobuf:"varint,5,opt,name=versionId,proto3" json:"versionId,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *RepoAppDetailsQuery) Reset() { *m = RepoAppDetailsQuery{} } @@ -225,6 +229,20 @@ func (m *RepoAppDetailsQuery) GetAppProject() string { return "" } +func (m *RepoAppDetailsQuery) GetSourceIndex() int32 { + if m != nil { + return m.SourceIndex + } + return 0 +} + +func (m *RepoAppDetailsQuery) GetVersionId() int32 { + if m != nil { + return m.VersionId + } + return 0 +} + // RepoAppsResponse contains applications of specified repository type RepoAppsResponse struct { Items []*AppInfo `protobuf:"bytes,1,rep,name=items,proto3" json:"items,omitempty"` @@ -1485,6 +1503,16 @@ func (m *RepoAppDetailsQuery) MarshalToSizedBuffer(dAtA []byte) (int, error) { i -= len(m.XXX_unrecognized) copy(dAtA[i:], m.XXX_unrecognized) } + if m.VersionId != 0 { + i = encodeVarintRepository(dAtA, i, uint64(m.VersionId)) + i-- + dAtA[i] = 0x28 + } + if m.SourceIndex != 0 { + i = encodeVarintRepository(dAtA, i, uint64(m.SourceIndex)) + i-- + dAtA[i] = 0x20 + } if len(m.AppProject) > 0 { i -= len(m.AppProject) copy(dAtA[i:], m.AppProject) @@ -1974,6 +2002,12 @@ func (m *RepoAppDetailsQuery) Size() (n int) { if l > 0 { n += 1 + l + sovRepository(uint64(l)) } + if m.SourceIndex != 0 { + n += 1 + sovRepository(uint64(m.SourceIndex)) + } + if m.VersionId != 0 { + n += 1 + sovRepository(uint64(m.VersionId)) + } if m.XXX_unrecognized != nil { n += len(m.XXX_unrecognized) } @@ -2579,6 +2613,44 @@ func (m *RepoAppDetailsQuery) Unmarshal(dAtA []byte) error { } m.AppProject = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field SourceIndex", wireType) + } + m.SourceIndex = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRepository + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.SourceIndex |= int32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field VersionId", wireType) + } + m.VersionId = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRepository + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.VersionId |= int32(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipRepository(dAtA[iNdEx:]) diff --git a/pkg/apis/application/v1alpha1/types.go b/pkg/apis/application/v1alpha1/types.go index 2f0ec0ec2bd94..428fdcc00d255 100644 --- a/pkg/apis/application/v1alpha1/types.go +++ b/pkg/apis/application/v1alpha1/types.go @@ -206,6 +206,11 @@ func (s ApplicationSources) Equals(other ApplicationSources) bool { return true } +// IsZero returns true if the application source is considered empty +func (a ApplicationSources) IsZero() bool { + return len(a) == 0 +} + func (a *ApplicationSpec) GetSource() ApplicationSource { // if Application has multiple sources, return the first source in sources if a.HasMultipleSources() { @@ -257,6 +262,11 @@ func (a *ApplicationSource) AllowsConcurrentProcessing() bool { return true } +// IsRef returns true when the application source is of type Ref +func (a *ApplicationSource) IsRef() bool { + return a.Ref != "" +} + // IsHelm returns true when the application source is of type Helm func (a *ApplicationSource) IsHelm() bool { return a.Chart != "" diff --git a/server/application/application.go b/server/application/application.go index 591e4d4e1a9f4..e90ee523873a9 100644 --- a/server/application/application.go +++ b/server/application/application.go @@ -38,6 +38,7 @@ import ( argocommon "github.com/argoproj/argo-cd/v2/common" "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" appclientset "github.com/argoproj/argo-cd/v2/pkg/client/clientset/versioned" applisters "github.com/argoproj/argo-cd/v2/pkg/client/listers/application/v1alpha1" @@ -1498,7 +1499,32 @@ func (s *Server) RevisionMetadata(ctx context.Context, q *application.RevisionMe return nil, err } - source := a.Spec.GetSource() + var source *v1alpha1.ApplicationSource + if a.Spec.HasMultipleSources() { + // If the historical data is empty (because the app hasn't been sysnced yet) + // we can use the source, if not (the app has been synced at least once) + // we have to use the history because sources can be added/removed + if len(a.Status.History) == 0 { + source = &a.Spec.Sources[*q.SourceIndex] + } else { + // the source count can change during the time, we cannot just trust in .status.sync + // because if a source has been added/removed, the revisions there won't match + // as this is only used for the UI and not internally, we can use the historical data + // using the specific revisionId + for _, h := range a.Status.History { + if h.ID == int64(*q.VersionId) { + source = &h.Sources[*q.SourceIndex] + } + } + } + if source == nil { + return nil, fmt.Errorf("revision not found: %w", err) + } + } else { + s := a.Spec.GetSource() + source = &s + } + repo, err := s.db.GetRepository(ctx, source.RepoURL, proj.Name) if err != nil { return nil, fmt.Errorf("error getting repository by URL: %w", err) @@ -1521,10 +1547,29 @@ func (s *Server) RevisionChartDetails(ctx context.Context, q *application.Revisi if err != nil { return nil, err } - if a.Spec.Source.Chart == "" { - return nil, fmt.Errorf("no chart found for application: %v", a.QualifiedName()) + + var source *v1alpha1.ApplicationSource + if a.Spec.HasMultipleSources() { + // the source count can change during the time, we cannot just trust in .status.sync + // because if a source has been added/removed, the revisions there won't match + // as this is only used for the UI and not internally, we can use the historical data + // using the specific revisionId + for _, h := range a.Status.History { + if h.ID == int64(*q.VersionId) { + source = &h.Sources[*q.SourceIndex] + } + } + if source == nil { + return nil, fmt.Errorf("revision not found: %w", err) + } + } else { + source = a.Spec.Source + } + + if source.Chart == "" { + return nil, fmt.Errorf("no chart found for application: %v", q.GetName()) } - repo, err := s.db.GetRepository(ctx, a.Spec.Source.RepoURL, a.Spec.Project) + repo, err := s.db.GetRepository(ctx, source.RepoURL, a.Spec.Project) if err != nil { return nil, fmt.Errorf("error getting repository by URL: %w", err) } @@ -1535,7 +1580,7 @@ func (s *Server) RevisionChartDetails(ctx context.Context, q *application.Revisi defer ioutil.Close(conn) return repoClient.GetRevisionChartDetails(ctx, &apiclient.RepoServerRevisionChartDetailsRequest{ Repo: repo, - Name: a.Spec.Source.Chart, + Name: source.Chart, Revision: q.GetRevision(), }) } @@ -1970,9 +2015,10 @@ func (s *Server) Rollback(ctx context.Context, rollbackReq *application.Applicat if deploymentInfo == nil { return nil, status.Errorf(codes.InvalidArgument, "application %s does not have deployment with id %v", a.QualifiedName(), rollbackReq.GetId()) } - if deploymentInfo.Source.IsZero() { + if deploymentInfo.Source.IsZero() && deploymentInfo.Sources.IsZero() { // Since source type was introduced to history starting with v0.12, and is now required for // rollback, we cannot support rollback to revisions deployed using Argo CD v0.11 or below + // As multi source doesn't use app.Source, we need to check to the Sources length return nil, status.Errorf(codes.FailedPrecondition, "cannot rollback to revision deployed with Argo CD v0.11 or lower. sync to revision instead.") } @@ -1985,11 +2031,13 @@ func (s *Server) Rollback(ctx context.Context, rollbackReq *application.Applicat op := appv1.Operation{ Sync: &appv1.SyncOperation{ Revision: deploymentInfo.Revision, + Revisions: deploymentInfo.Revisions, DryRun: rollbackReq.GetDryRun(), Prune: rollbackReq.GetPrune(), SyncOptions: syncOptions, SyncStrategy: &appv1.SyncStrategy{Apply: &appv1.SyncStrategyApply{}}, Source: &deploymentInfo.Source, + Sources: deploymentInfo.Sources, }, InitiatedBy: appv1.OperationInitiator{Username: session.Username(ctx)}, } diff --git a/server/application/application.proto b/server/application/application.proto index 2a70e1c518c09..945c0c417c65c 100644 --- a/server/application/application.proto +++ b/server/application/application.proto @@ -51,6 +51,10 @@ message RevisionMetadataQuery{ // the application's namespace optional string appNamespace = 3; optional string project = 4; + // source index (for multi source apps) + optional int32 sourceIndex = 5; + // versionId from historical data (for multi source apps) + optional int32 versionId = 6; } // ApplicationEventsQuery is a query for application resource events diff --git a/server/application/application_test.go b/server/application/application_test.go index 219933b8c1ea1..09cf53d53547c 100644 --- a/server/application/application_test.go +++ b/server/application/application_test.go @@ -753,8 +753,42 @@ func TestNoAppEnumeration(t *testing.T) { }, } }) + testAppMulti := newTestApp(func(app *appsv1.Application) { + app.Name = "test-multi" + app.Spec.Sources = appsv1.ApplicationSources{ + appsv1.ApplicationSource{ + TargetRevision: "something-old", + }, + appsv1.ApplicationSource{ + TargetRevision: "something-old", + }, + } + app.Status.Resources = []appsv1.ResourceStatus{ + { + Group: deployment.GroupVersionKind().Group, + Kind: deployment.GroupVersionKind().Kind, + Version: deployment.GroupVersionKind().Version, + Name: deployment.Name, + Namespace: deployment.Namespace, + Status: "Synced", + }, + } + app.Status.History = []appsv1.RevisionHistory{ + { + ID: 1, + Sources: appsv1.ApplicationSources{ + appsv1.ApplicationSource{ + TargetRevision: "something-old", + }, + appsv1.ApplicationSource{ + TargetRevision: "something-old", + }, + }, + }, + } + }) testDeployment := kube.MustToUnstructured(&deployment) - appServer := newTestAppServerWithEnforcerConfigure(f, t, map[string]string{}, testApp, testHelmApp, testDeployment) + appServer := newTestAppServerWithEnforcerConfigure(f, t, map[string]string{}, testApp, testHelmApp, testAppMulti, testDeployment) noRoleCtx := context.Background() // nolint:staticcheck @@ -880,6 +914,8 @@ func TestNoAppEnumeration(t *testing.T) { t.Run("RevisionMetadata", func(t *testing.T) { _, err := appServer.RevisionMetadata(adminCtx, &application.RevisionMetadataQuery{Name: ptr.To("test")}) assert.NoError(t, err) + _, err = appServer.RevisionMetadata(adminCtx, &application.RevisionMetadataQuery{Name: ptr.To("test-multi"), SourceIndex: ptr.Int32(0), VersionId: pointer.Int32(1)}) + assert.NoError(t, err) _, err = appServer.RevisionMetadata(noRoleCtx, &application.RevisionMetadataQuery{Name: ptr.To("test")}) assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence") _, err = appServer.RevisionMetadata(adminCtx, &application.RevisionMetadataQuery{Name: ptr.To("doest-not-exist")}) @@ -939,7 +975,9 @@ func TestNoAppEnumeration(t *testing.T) { unsetSyncRunningOperationState(t, appServer) _, err := appServer.Rollback(adminCtx, &application.ApplicationRollbackRequest{Name: ptr.To("test")}) assert.NoError(t, err) - _, err = appServer.Rollback(noRoleCtx, &application.ApplicationRollbackRequest{Name: ptr.To("test")}) + _, err = appServer.Rollback(adminCtx, &application.ApplicationRollbackRequest{Name: pointer.To("test-multi"), Id: pointer.Int64(1)}) + assert.NoError(t, err) + _, err = appServer.Rollback(noRoleCtx, &application.ApplicationRollbackRequest{Name: pointer.To("test")}) assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence") _, err = appServer.Rollback(adminCtx, &application.ApplicationRollbackRequest{Name: ptr.To("doest-not-exist")}) assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence") diff --git a/server/repository/repository.go b/server/repository/repository.go index e43990452ff3f..ef9b8e527e72c 100644 --- a/server/repository/repository.go +++ b/server/repository/repository.go @@ -315,7 +315,7 @@ func (s *Server) GetAppDetails(ctx context.Context, q *repositorypkg.RepoAppDeta return nil, errPermissionDenied } // verify caller is not making a request with arbitrary source values which were not in our history - if !isSourceInHistory(app, *q.Source) { + if !isSourceInHistory(app, *q.Source, q.SourceIndex, q.VersionId) { return nil, errPermissionDenied } } @@ -600,17 +600,36 @@ func (s *Server) isRepoPermittedInProject(ctx context.Context, repo string, proj // isSourceInHistory checks if the supplied application source is either our current application // source, or was something which we synced to previously. -func isSourceInHistory(app *v1alpha1.Application, source v1alpha1.ApplicationSource) bool { +func isSourceInHistory(app *v1alpha1.Application, source v1alpha1.ApplicationSource, index int32, versionId int32) bool { + + if app.Spec.HasMultipleSources() { + appSources := app.Spec.GetSources() + for _, s := range appSources { + if source.Equals(&s) { + return true + } + } + + // In case of multi source apps, we have to check the specific versionID because users + // could have removed/added new sources and we cannot check all the versions due to that + for _, h := range app.Status.History { + if h.ID == int64(versionId) { + if h.Revisions == nil { + continue + } + h.Sources[index].TargetRevision = h.Revisions[index] + if source.Equals(&h.Sources[index]) { + return true + } + } + } + return false + } + appSource := app.Spec.GetSource() if source.Equals(&appSource) { return true } - appSources := app.Spec.GetSources() - for _, s := range appSources { - if source.Equals(&s) { - return true - } - } // Iterate history. When comparing items in our history, use the actual synced revision to // compare with the supplied source.targetRevision in the request. This is because // history[].source.targetRevision is ambiguous (e.g. HEAD), whereas @@ -621,5 +640,6 @@ func isSourceInHistory(app *v1alpha1.Application, source v1alpha1.ApplicationSou return true } } + return false } diff --git a/server/repository/repository.proto b/server/repository/repository.proto index eebf09ae75ce0..379cbdeabf9cc 100644 --- a/server/repository/repository.proto +++ b/server/repository/repository.proto @@ -30,6 +30,10 @@ message RepoAppDetailsQuery { github.com.argoproj.argo_cd.v2.pkg.apis.application.v1alpha1.ApplicationSource source = 1; string appName = 2; string appProject = 3; + // source index (for multi source apps) + int32 sourceIndex = 4; + // versionId from historical data (for multi source apps) + int32 versionId = 5; } // RepoAppsResponse contains applications of specified repository diff --git a/server/repository/repository_test.go b/server/repository/repository_test.go index 768c88b2f4afe..fcbb0dd976eac 100644 --- a/server/repository/repository_test.go +++ b/server/repository/repository_test.go @@ -169,7 +169,10 @@ var ( Status: appsv1.ApplicationStatus{ History: appsv1.RevisionHistories{ { - Revision: "HEAD", + ID: 1, + Revisions: []string{ + "abcdef123567", + }, Sources: []appsv1.ApplicationSource{ { RepoURL: "https://helm.elastic.co", @@ -811,6 +814,65 @@ func TestRepositoryServerGetAppDetails(t *testing.T) { assert.NoError(t, err) assert.Equal(t, expectedResp, *resp) }) + + t.Run("Test_ExistingAppMultiSourceNotInHistory", func(t *testing.T) { + repoServerClient := mocks.RepoServerServiceClient{} + repoServerClientset := mocks.Clientset{RepoServerServiceClient: &repoServerClient} + enforcer := newEnforcer(kubeclientset) + + url := "https://helm.elastic.co" + helmRepos := []*appsv1.Repository{{Repo: url}, {Repo: url}} + db := &dbmocks.ArgoDB{} + db.On("ListHelmRepositories", context.TODO(), mock.Anything).Return(helmRepos, nil) + db.On("GetRepository", context.TODO(), url).Return(&appsv1.Repository{Repo: url}, nil) + db.On("GetProjectRepositories", context.TODO(), "default").Return(nil, nil) + db.On("GetProjectClusters", context.TODO(), "default").Return(nil, nil) + expectedResp := apiclient.RepoAppDetailsResponse{Type: "Helm"} + repoServerClient.On("GetAppDetails", context.TODO(), mock.Anything).Return(&expectedResp, nil) + appLister, projLister := newAppAndProjLister(defaultProj, multiSourceApp001) + + differentSource := multiSourceApp001.Spec.Sources[0].DeepCopy() + differentSource.Helm.ValueFiles = []string{"/etc/passwd"} + + s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, appLister, projLister, testNamespace, settingsMgr) + resp, err := s.GetAppDetails(context.TODO(), &repository.RepoAppDetailsQuery{ + Source: differentSource, + AppName: multiSourceApp001AppName, + AppProject: "default", + SourceIndex: 0, + VersionId: 1, + }) + assert.Equal(t, errPermissionDenied, err) + assert.Nil(t, resp) + }) + t.Run("Test_ExistingAppMultiSourceInHistory", func(t *testing.T) { + repoServerClient := mocks.RepoServerServiceClient{} + repoServerClientset := mocks.Clientset{RepoServerServiceClient: &repoServerClient} + enforcer := newEnforcer(kubeclientset) + + url := "https://helm.elastic.co" + db := &dbmocks.ArgoDB{} + db.On("GetRepository", context.TODO(), url).Return(&appsv1.Repository{Repo: url}, nil) + db.On("ListHelmRepositories", context.TODO(), mock.Anything).Return(nil, nil) + db.On("GetProjectRepositories", context.TODO(), "default").Return(nil, nil) + db.On("GetProjectClusters", context.TODO(), "default").Return(nil, nil) + expectedResp := apiclient.RepoAppDetailsResponse{Type: "Directory"} + repoServerClient.On("GetAppDetails", context.TODO(), mock.Anything).Return(&expectedResp, nil) + appLister, projLister := newAppAndProjLister(defaultProj, multiSourceApp001) + previousSource := multiSourceApp001.Status.History[0].Sources[0].DeepCopy() + previousSource.TargetRevision = multiSourceApp001.Status.History[0].Revisions[0] + + s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, appLister, projLister, testNamespace, settingsMgr) + resp, err := s.GetAppDetails(context.TODO(), &repository.RepoAppDetailsQuery{ + Source: previousSource, + AppName: multiSourceApp001AppName, + AppProject: "default", + SourceIndex: 0, + VersionId: 1, + }) + assert.NoError(t, err) + assert.Equal(t, expectedResp, *resp) + }) } type fixtures struct { diff --git a/ui/src/app/applications/components/application-create-panel/application-create-panel.tsx b/ui/src/app/applications/components/application-create-panel/application-create-panel.tsx index 6c46f6ebfa947..40190ea0ff051 100644 --- a/ui/src/app/applications/components/application-create-panel/application-create-panel.tsx +++ b/ui/src/app/applications/components/application-create-panel/application-create-panel.tsx @@ -447,7 +447,7 @@ export const ApplicationCreatePanel = (props: { }} load={async src => { if (src.repoURL && src.targetRevision && (src.path || src.chart)) { - return services.repos.appDetails(src, src.appName, app.spec.project).catch(() => ({ + return services.repos.appDetails(src, src.appName, app.spec.project, 0, 0).catch(() => ({ type: 'Directory', details: {} })); diff --git a/ui/src/app/applications/components/application-deployment-history/application-deployment-history.scss b/ui/src/app/applications/components/application-deployment-history/application-deployment-history.scss index 8c31a357529f2..b370878a36520 100644 --- a/ui/src/app/applications/components/application-deployment-history/application-deployment-history.scss +++ b/ui/src/app/applications/components/application-deployment-history/application-deployment-history.scss @@ -43,4 +43,11 @@ .white-box__details p { margin-left: -1em; } + + .separator { + height: 2px; + margin: 1em 0; + width: 100%; + background-color: $argo-color-gray-5; + } } diff --git a/ui/src/app/applications/components/application-deployment-history/application-deployment-history.tsx b/ui/src/app/applications/components/application-deployment-history/application-deployment-history.tsx index 37908fb1a35b8..2e4514821d0d0 100644 --- a/ui/src/app/applications/components/application-deployment-history/application-deployment-history.tsx +++ b/ui/src/app/applications/components/application-deployment-history/application-deployment-history.tsx @@ -26,7 +26,6 @@ export const ApplicationDeploymentHistory = ({ const runEnd = nextDeployedAt ? moment(nextDeployedAt) : moment(); return {...info, nextDeployedAt, durationMs: runEnd.diff(moment(info.deployedAt)) / 1000}; }); - return (
{recentDeployments.map((info, index) => ( @@ -58,9 +57,7 @@ export const ApplicationDeploymentHistory = ({
-
Revision:
-
( @@ -79,28 +76,83 @@ export const ApplicationDeploymentHistory = ({
{selectedRollbackDeploymentIndex === index ? ( - - - services.repos.appDetails(src, src.appName, app.spec.project)}> - {(details: models.RepoAppDetails) => ( + info.sources === undefined ? ( + +
+
+
Revision:
+
+ +
+
+
+ + services.repos.appDetails(src, src.appName, app.spec.project, 0, recentDeployments[index].id)}> + {(details: models.RepoAppDetails) => ( +
+ +
+ )} +
+
+ ) : ( + info.sources.map((source, i) => ( + + {i > 0 ?
: null}
- +
+
Revision:
+
+ +
+
- )} - - + + + services.repos.appDetails(src, src.appName, app.spec.project, i, recentDeployments[index].id)}> + {(details: models.RepoAppDetails) => ( +
+ +
+ )} +
+ + )) + ) ) : null}
diff --git a/ui/src/app/applications/components/application-deployment-history/revision-metadata-rows.tsx b/ui/src/app/applications/components/application-deployment-history/revision-metadata-rows.tsx index 3fa7c62ed1caa..1043d0bfa5659 100644 --- a/ui/src/app/applications/components/application-deployment-history/revision-metadata-rows.tsx +++ b/ui/src/app/applications/components/application-deployment-history/revision-metadata-rows.tsx @@ -4,10 +4,14 @@ import {Timestamp} from '../../../shared/components/timestamp'; import {ApplicationSource, RevisionMetadata, ChartDetails} from '../../../shared/models'; import {services} from '../../../shared/services'; -export const RevisionMetadataRows = (props: {applicationName: string; applicationNamespace: string; source: ApplicationSource}) => { +export const RevisionMetadataRows = (props: {applicationName: string; applicationNamespace: string; source: ApplicationSource; index: number; versionId: number}) => { if (props.source.chart) { return ( - services.applications.revisionChartDetails(input.applicationName, input.applicationNamespace, input.source.targetRevision)}> + + services.applications.revisionChartDetails(input.applicationName, input.applicationNamespace, input.source.targetRevision, input.index, input.versionId) + }> {(m: ChartDetails) => (
@@ -44,7 +48,9 @@ export const RevisionMetadataRows = (props: {applicationName: string; applicatio ); } return ( - services.applications.revisionMetadata(input.applicationName, input.applicationNamespace, input.source.targetRevision)}> + services.applications.revisionMetadata(input.applicationName, input.applicationNamespace, input.source.targetRevision, input.index, input.versionId)}> {(m: RevisionMetadata) => (
diff --git a/ui/src/app/applications/components/application-details/application-details.tsx b/ui/src/app/applications/components/application-details/application-details.tsx index 790919f5c271a..9da12bff17be5 100644 --- a/ui/src/app/applications/components/application-details/application-details.tsx +++ b/ui/src/app/applications/components/application-details/application-details.tsx @@ -24,7 +24,7 @@ import {ResourceDetails} from '../resource-details/resource-details'; import * as AppUtils from '../utils'; import {ApplicationResourceList} from './application-resource-list'; import {Filters, FiltersProps} from './application-resource-filter'; -import {getAppDefaultSource, urlPattern, helpTip} from '../utils'; +import {getAppDefaultSource, getAppCurrentVersion, urlPattern} from '../utils'; import {ChartDetails, ResourceStatus} from '../../../shared/models'; import {ApplicationsDetailsAppDropdown} from './application-details-app-dropdown'; import {useSidebarTarget} from '../../../sidebar/sidebar'; @@ -655,7 +655,7 @@ export class ApplicationDetails extends React.Component - services.applications.revisionChartDetails(input.metadata.name, input.metadata.namespace, this.state.revision) + services.applications.revisionChartDetails(input.metadata.name, input.metadata.namespace, this.state.revision, 0, 0) }> {(m: ChartDetails) => (
@@ -699,7 +699,13 @@ export class ApplicationDetails extends React.Component - services.applications.revisionMetadata(application.metadata.name, application.metadata.namespace, this.state.revision) + services.applications.revisionMetadata( + application.metadata.name, + application.metadata.namespace, + this.state.revision, + 0, + getAppCurrentVersion(application) + ) }> {metadata => (
@@ -767,7 +773,6 @@ export class ApplicationDetails extends React.Component {prop.actionLabel}; - const hasMultipleSources = app.spec.sources && app.spec.sources.length > 0; return [ { iconClassName: 'fa fa-info-circle', @@ -793,18 +798,11 @@ export class ApplicationDetails extends React.Component - - {helpTip('Rollback is not supported for apps with multiple sources')} - - ) : ( - - ), + title: , action: () => { this.setRollbackPanelVisible(0); }, - disabled: !app.status.operationState || hasMultipleSources + disabled: !app.status.operationState }, { iconClassName: 'fa fa-times-circle', diff --git a/ui/src/app/applications/components/application-operation-state/application-operation-state.tsx b/ui/src/app/applications/components/application-operation-state/application-operation-state.tsx index 0f5bbac2615a2..7af1482d6d387 100644 --- a/ui/src/app/applications/components/application-operation-state/application-operation-state.tsx +++ b/ui/src/app/applications/components/application-operation-state/application-operation-state.tsx @@ -93,7 +93,15 @@ export const ApplicationOperationState: React.StatelessComponent = ({appl }); } if (operationState.syncResult) { - operationAttributes.push({title: 'REVISION', value: }); + operationAttributes.push({ + title: 'REVISION', + value: ( + + ) + }); } let initiator = ''; if (operationState.operation.initiatedBy) { diff --git a/ui/src/app/applications/components/application-status-panel/application-status-panel.tsx b/ui/src/app/applications/components/application-status-panel/application-status-panel.tsx index 7c2b65cd3ce27..0794d67610ef3 100644 --- a/ui/src/app/applications/components/application-status-panel/application-status-panel.tsx +++ b/ui/src/app/applications/components/application-status-panel/application-status-panel.tsx @@ -5,9 +5,10 @@ import {Revision} from '../../../shared/components/revision'; import {Timestamp} from '../../../shared/components/timestamp'; import * as models from '../../../shared/models'; import {services} from '../../../shared/services'; -import {ApplicationSyncWindowStatusIcon, ComparisonStatusIcon, getAppDefaultSource, getAppOperationState} from '../utils'; -import {getConditionCategory, HealthStatusIcon, OperationState, syncStatusMessage, helpTip} from '../utils'; +import {ApplicationSyncWindowStatusIcon, ComparisonStatusIcon, getAppDefaultSource, getAppDefaultSyncRevisionExtra, getAppOperationState} from '../utils'; +import {getConditionCategory, HealthStatusIcon, OperationState, syncStatusMessage, getAppDefaultSyncRevision} from '../utils'; import {RevisionMetadataPanel} from './revision-metadata-panel'; +import * as utils from '../utils'; import './application-status-panel.scss'; @@ -32,16 +33,11 @@ const sectionLabel = (info: SectionInfo) => ( ); -const sectionHeader = (info: SectionInfo, hasMultipleSources: boolean, onClick?: () => any) => { +const sectionHeader = (info: SectionInfo, onClick?: () => any) => { return (
{sectionLabel(info)} - {onClick && ( - - )} + {onClick &&
); }; @@ -66,11 +62,12 @@ export const ApplicationStatusPanel = ({application, showDiff, showOperation, sh const statusExtensions = services.extensions.getStatusPanelExtensions(); + let revision = getAppDefaultSyncRevision(application) + const infos = cntByCategory.get('info'); const warnings = cntByCategory.get('warning'); const errors = cntByCategory.get('error'); const source = getAppDefaultSource(application); - const hasMultipleSources = application.spec.sources && application.spec.sources.length > 0; return (
@@ -89,8 +86,7 @@ export const ApplicationStatusPanel = ({application, showDiff, showOperation, sh title: 'SYNC STATUS', helpContent: 'Whether or not the version of your app is up to date with your repo. You may wish to sync your app if it is out-of-sync.' }, - hasMultipleSources, - () => showMetadataInfo(application.status.sync ? application.status.sync.revision : '') + () => showMetadataInfo((revision += getAppDefaultSyncRevisionExtra(application))) )}
@@ -107,13 +103,14 @@ export const ApplicationStatusPanel = ({application, showDiff, showOperation, sh
{application.spec.syncPolicy?.automated ? 'Auto sync is enabled.' : 'Auto sync is not enabled.'}
- {application.status && application.status.sync && application.status.sync.revision && !application.spec.source.chart && ( + {application.status && application.status.sync && application.status.sync.revision && revision && !application.spec.source.chart && (
)} @@ -130,29 +127,28 @@ export const ApplicationStatusPanel = ({application, showDiff, showOperation, sh daysSinceLastSynchronized + ' days since last sync. Click for the status of that sync.' }, - hasMultipleSources, - () => showMetadataInfo(appOperationState.syncResult ? appOperationState.syncResult.revision : '') + () => showMetadataInfo((revision += getAppDefaultSyncRevisionExtra(application))) )}
showOperation && showOperation()}> {' '} - {appOperationState.syncResult && appOperationState.syncResult.revision && ( + {appOperationState.syncResult && revision && (
- to + to
)}
-
{appOperationState.phase}
- {(appOperationState.syncResult && appOperationState.syncResult.revision && ( + {(appOperationState.syncResult && revision && ( )) ||
{appOperationState.message}
} diff --git a/ui/src/app/applications/components/application-status-panel/revision-metadata-panel.tsx b/ui/src/app/applications/components/application-status-panel/revision-metadata-panel.tsx index fea9a0c8e2c4b..085958d0f1cf5 100644 --- a/ui/src/app/applications/components/application-status-panel/revision-metadata-panel.tsx +++ b/ui/src/app/applications/components/application-status-panel/revision-metadata-panel.tsx @@ -3,12 +3,12 @@ import * as React from 'react'; import {Timestamp} from '../../../shared/components/timestamp'; import {services} from '../../../shared/services'; -export const RevisionMetadataPanel = (props: {appName: string; appNamespace: string; type: string; revision: string}) => { +export const RevisionMetadataPanel = (props: {appName: string; appNamespace: string; type: string; revision: string; versionId: number}) => { if (props.type === 'helm') { return ; } return ( - services.applications.revisionMetadata(props.appName, props.appNamespace, props.revision)} errorRenderer={() =>
}> + services.applications.revisionMetadata(props.appName, props.appNamespace, props.revision, 0, props.versionId)} errorRenderer={() =>
}> {m => ( Promise; + updateApp: (app: Application, query: { validate?: boolean }) => Promise; application: Application; isAppSelected: boolean; tree: ApplicationTree; @@ -34,7 +34,7 @@ interface ResourceDetailsProps { } export const ResourceDetails = (props: ResourceDetailsProps) => { - const {selectedNode, updateApp, application, isAppSelected, tree} = {...props}; + const { selectedNode, updateApp, application, isAppSelected, tree } = { ...props }; const [activeContainer, setActiveContainer] = useState(); const appContext = React.useContext(Context); const tab = new URLSearchParams(appContext.history.location.search).get('tab'); @@ -159,7 +159,7 @@ export const ResourceDetails = (props: ResourceDetailsProps) => { { title: 'SUMMARY', key: 'summary', - content: updateApp(app, query)} /> + content: updateApp(app, query)} /> }, { title: 'SOURCES', @@ -168,7 +168,7 @@ export const ResourceDetails = (props: ResourceDetailsProps) => { getSources(app)}> {(details: RepoAppDetails[]) => ( updateApp(app, query)} + save={(app: models.Application, query: { validate?: boolean }) => updateApp(app, query)} application={application} details={details[0]} detailsList={details} @@ -233,7 +233,7 @@ export const ResourceDetails = (props: ResourceDetailsProps) => { const extensions = selectedNode?.kind ? services.extensions.getResourceTabs(selectedNode?.group || '', selectedNode?.kind) : []; return ( -
+
{selectedNode && ( { }); const controlled = managedResources.find(item => AppUtils.isSameNode(selectedNode, item)); const summary = application.status.resources.find(item => AppUtils.isSameNode(selectedNode, item)); - const controlledState = (controlled && summary && {summary, state: controlled}) || null; - const resQuery = {...selectedNode}; + const controlledState = (controlled && summary && { summary, state: controlled }) || null; + const resQuery = { ...selectedNode }; if (controlled && controlled.targetState) { resQuery.version = AppUtils.parseApiVersion(controlled.targetState.apiVersion).version; } @@ -280,33 +280,33 @@ export const ResourceDetails = (props: ResourceDetailsProps) => { const logsAllowed = await services.accounts.canI('logs', 'get', application.spec.project + '/' + application.metadata.name); const execAllowed = execEnabled && (await services.accounts.canI('exec', 'create', application.spec.project + '/' + application.metadata.name)); const links = await services.applications.getResourceLinks(application.metadata.name, application.metadata.namespace, selectedNode).catch(() => null); - return {controlledState, liveState, events, podState, execEnabled, execAllowed, logsAllowed, links, childResources}; + return { controlledState, liveState, events, podState, execEnabled, execAllowed, logsAllowed, links, childResources }; }}> {data => (
-
+
- {ResourceLabel({kind: selectedNode.kind})} + {ResourceLabel({ kind: selectedNode.kind })}

{selectedNode.name}

{data.controlledState && ( - + )} {(selectedNode as ResourceTreeNode).health && } @@ -349,7 +349,7 @@ export const ResourceDetails = (props: ResourceDetailsProps) => { data.logsAllowed )} selectedTabKey={props.tab} - onTabSelected={selected => appContext.navigation.goto('.', {tab: selected}, {replace: true})} + onTabSelected={selected => appContext.navigation.goto('.', { tab: selected }, { replace: true })} /> )} @@ -360,7 +360,7 @@ export const ResourceDetails = (props: ResourceDetailsProps) => { navTransparent={true} tabs={getApplicationTabs()} selectedTabKey={tab} - onTabSelected={selected => appContext.navigation.goto('.', {tab: selected}, {replace: true})} + onTabSelected={selected => appContext.navigation.goto('.', { tab: selected }, { replace: true })} /> )}
@@ -369,7 +369,7 @@ export const ResourceDetails = (props: ResourceDetailsProps) => { // Maintain compatibility with single source field. Remove else block when source field is removed async function getSources(app: models.Application) { - const listOfDetails = new Array(); + const listOfDetails = new Array(); const sources: models.ApplicationSource[] = app.spec.sources; if (sources) { const length = sources.length; diff --git a/ui/src/app/applications/components/utils.tsx b/ui/src/app/applications/components/utils.tsx index 2ba5f5d5d50bf..9a30ac8464e34 100644 --- a/ui/src/app/applications/components/utils.tsx +++ b/ui/src/app/applications/components/utils.tsx @@ -705,16 +705,20 @@ export function renderResourceButtons( export function syncStatusMessage(app: appModels.Application) { const source = getAppDefaultSource(app); + const revision = getAppDefaultSyncRevision(app); const rev = app.status.sync.revision || source.targetRevision || 'HEAD'; let message = source.targetRevision || 'HEAD'; - if (app.status.sync.revision) { + if (revision) { if (source.chart) { - message += ' (' + app.status.sync.revision + ')'; - } else if (app.status.sync.revision.length >= 7 && !app.status.sync.revision.startsWith(source.targetRevision)) { - message += ' (' + app.status.sync.revision.substr(0, 7) + ')'; + message += ' (' + revision + ')'; + } else if (revision.length >= 7 && !revision.startsWith(source.targetRevision)) { + message += ' (' + revision.substr(0, 7) + ')'; } } + + message += getAppDefaultSyncRevisionExtra(app); + switch (app.status.sync.status) { case appModels.SyncStatuses.Synced: return ( @@ -1105,6 +1109,37 @@ export function getAppDefaultSource(app?: appModels.Application) { return app.spec.sources && app.spec.sources.length > 0 ? app.spec.sources[0] : app.spec.source; } +// getAppDefaultSyncRevision gets the first app revisions from `status.sync.revisions` or, if that list is missing or empty, the `revision` +// field. +export function getAppDefaultSyncRevision(app?: appModels.Application) { + if (!app || !app.status || !app.status.sync) { + return ''; + } + return app.status.sync.revisions && app.status.sync.revisions.length > 0 ? app.status.sync.revisions[0] : app.status.sync.revision; +} + +// getAppCurrentVersion gets the first app revisions from `status.sync.revisions` or, if that list is missing or empty, the `revision` +// field. +export function getAppCurrentVersion(app?: appModels.Application) { + if (!app || !app.status || !app.status.history) { + return 0; + } + return app.status.history[app.status.history.length - 1].id; +} + +// getAppDefaultSyncRevisionExtra gets the extra message with others revision count +export function getAppDefaultSyncRevisionExtra(app?: appModels.Application) { + if (!app || !app.status || !app.status.sync) { + return ''; + } + + if (app.status.sync.revisions && app.status.sync.revisions.length > 0) { + return ` and (${app.status.sync.revisions.length - 1}) more`; + } + + return ''; +} + export function getAppSpecDefaultSource(spec: appModels.ApplicationSpec) { return spec.sources && spec.sources.length > 0 ? spec.sources[0] : spec.source; } diff --git a/ui/src/app/shared/services/applications-service.ts b/ui/src/app/shared/services/applications-service.ts index fb53a7a09c4ee..c131e9cf592c7 100644 --- a/ui/src/app/shared/services/applications-service.ts +++ b/ui/src/app/shared/services/applications-service.ts @@ -53,17 +53,21 @@ export class ApplicationsService { .then(res => res.body as models.ApplicationSyncWindowState); } - public revisionMetadata(name: string, appNamespace: string, revision: string): Promise { + public revisionMetadata(name: string, appNamespace: string, revision: string, sourceIndex: number, versionId: number): Promise { return requests .get(`/applications/${name}/revisions/${revision || 'HEAD'}/metadata`) .query({appNamespace}) + .query({sourceIndex}) + .query({versionId}) .then(res => res.body as models.RevisionMetadata); } - public revisionChartDetails(name: string, appNamespace: string, revision: string): Promise { + public revisionChartDetails(name: string, appNamespace: string, revision: string, sourceIndex: number, versionId: number): Promise { return requests .get(`/applications/${name}/revisions/${revision || 'HEAD'}/chartdetails`) .query({appNamespace}) + .query({sourceIndex}) + .query({versionId}) .then(res => res.body as models.ChartDetails); } diff --git a/ui/src/app/shared/services/repo-service.ts b/ui/src/app/shared/services/repo-service.ts index 86f75a0b19162..7b2ca1d3c58dc 100644 --- a/ui/src/app/shared/services/repo-service.ts +++ b/ui/src/app/shared/services/repo-service.ts @@ -212,10 +212,10 @@ export class RepositoriesService { return requests.get(`/repositories/${encodeURIComponent(repo)}/helmcharts`).then(res => (res.body.items as models.HelmChart[]) || []); } - public appDetails(source: models.ApplicationSource, appName: string, appProject: string): Promise { + public appDetails(source: models.ApplicationSource, appName: string, appProject: string, sourceIndex: number, versionId: number): Promise { return requests .post(`/repositories/${encodeURIComponent(source.repoURL)}/appdetails`) - .send({source, appName, appProject}) + .send({source, appName, appProject, sourceIndex, versionId}) .then(res => res.body as models.RepoAppDetails); } } diff --git a/util/argo/ref_sources.go b/util/argo/ref_sources.go new file mode 100644 index 0000000000000..0f9f0644ba552 --- /dev/null +++ b/util/argo/ref_sources.go @@ -0,0 +1,62 @@ +package argo + +import ( + "context" + "fmt" + "regexp" + + argoappv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" + "github.com/argoproj/argo-cd/v2/util/db" +) + +type GetRefSourcesOptions struct { + Sources argoappv1.ApplicationSources + Db db.ArgoDB + Revisions []string + IsRollback bool +} + +// GetRefSources creates a map of ref keys (from the sources' 'ref' fields) to information about the referenced source. +// This function also validates the references use allowed characters and does not define the same ref key more than +// once (which would lead to ambiguous references). +// In case of rollback, this function also updates the targetRevision to the proper revision +func GetRefSources(ctx context.Context, opts GetRefSourcesOptions) (argoappv1.RefTargetRevisionMapping, error) { + refSources := make(argoappv1.RefTargetRevisionMapping) + if len(opts.Sources) > 1 { + // Validate first to avoid unnecessary DB calls. + refKeys := make(map[string]bool) + for _, source := range opts.Sources { + if source.Ref != "" { + isValidRefKey := regexp.MustCompile(`^[a-zA-Z0-9_-]+$`).MatchString + if !isValidRefKey(source.Ref) { + return nil, fmt.Errorf("sources.ref %s cannot contain any special characters except '_' and '-'", source.Ref) + } + refKey := "$" + source.Ref + if _, ok := refKeys[refKey]; ok { + return nil, fmt.Errorf("invalid sources: multiple sources had the same `ref` key") + } + refKeys[refKey] = true + } + } + // Get Repositories for all sources before generating Manifests + for i, source := range opts.Sources { + if source.Ref != "" { + repo, err := opts.Db.GetRepository(ctx, source.RepoURL) + if err != nil { + return nil, fmt.Errorf("failed to get repository %s: %v", source.RepoURL, err) + } + refKey := "$" + source.Ref + revision := source.TargetRevision + if opts.IsRollback { + revision = opts.Revisions[i] + } + refSources[refKey] = &argoappv1.RefTarget{ + Repo: *repo, + TargetRevision: revision, + Chart: source.Chart, + } + } + } + } + return refSources, nil +} diff --git a/util/argo/ref_sources_test.go b/util/argo/ref_sources_test.go new file mode 100644 index 0000000000000..f6a9efef75828 --- /dev/null +++ b/util/argo/ref_sources_test.go @@ -0,0 +1,95 @@ +package argo + +import ( + "context" + "errors" + "fmt" + "path/filepath" + "testing" + + argoappv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" + dbmocks "github.com/argoproj/argo-cd/v2/util/db/mocks" + "github.com/stretchr/testify/assert" +) + +func Test_GetRefSources(t *testing.T) { + repoPath, err := filepath.Abs("./../..") + assert.NoError(t, err) + + getMultiSourceAppSpec := func(sources argoappv1.ApplicationSources) *argoappv1.ApplicationSpec { + return &argoappv1.ApplicationSpec{ + Sources: sources, + } + } + + repo := &argoappv1.Repository{Repo: fmt.Sprintf("file://%s", repoPath)} + + t.Run("target ref exists", func(t *testing.T) { + repoDB := &dbmocks.ArgoDB{} + repoDB.On("GetRepository", context.Background(), repo.Repo).Return(repo, nil) + + argoSpec := getMultiSourceAppSpec(argoappv1.ApplicationSources{ + {RepoURL: fmt.Sprintf("file://%s", repoPath), Ref: "source-1_2"}, + {RepoURL: fmt.Sprintf("file://%s", repoPath)}, + }) + + refSources, err := GetRefSources(context.Background(), GetRefSourcesOptions{ + Sources: argoSpec.Sources, + Db: repoDB, + }) + + expectedRefSource := argoappv1.RefTargetRevisionMapping{ + "$source-1_2": &argoappv1.RefTarget{ + Repo: *repo, + }, + } + assert.NoError(t, err) + assert.Len(t, refSources, 1) + assert.Equal(t, expectedRefSource, refSources) + }) + + t.Run("target ref does not exist", func(t *testing.T) { + repoDB := &dbmocks.ArgoDB{} + repoDB.On("GetRepository", context.Background(), "file://does-not-exist").Return(nil, errors.New("repo does not exist")) + + argoSpec := getMultiSourceAppSpec(argoappv1.ApplicationSources{ + {RepoURL: "file://does-not-exist", Ref: "source1"}, + }) + + refSources, err := GetRefSources(context.Background(), GetRefSourcesOptions{ + Sources: argoSpec.Sources, + Db: repoDB, + }) + + assert.Error(t, err) + assert.Empty(t, refSources) + }) + + t.Run("invalid ref", func(t *testing.T) { + argoSpec := getMultiSourceAppSpec(argoappv1.ApplicationSources{ + {RepoURL: "file://does-not-exist", Ref: "%invalid-name%"}, + }) + + refSources, err := GetRefSources(context.TODO(), GetRefSourcesOptions{ + Sources: argoSpec.Sources, + Db: &dbmocks.ArgoDB{}, + }) + assert.Error(t, err) + assert.Empty(t, refSources) + }) + + t.Run("duplicate ref keys", func(t *testing.T) { + argoSpec := getMultiSourceAppSpec(argoappv1.ApplicationSources{ + {RepoURL: "file://does-not-exist", Ref: "source1"}, + {RepoURL: "file://does-not-exist", Ref: "source1"}, + }) + + refSources, err := GetRefSources(context.TODO(), GetRefSourcesOptions{ + Sources: argoSpec.Sources, + Db: &dbmocks.ArgoDB{}, + }) + + assert.Error(t, err) + assert.Empty(t, refSources) + }) +} From 8d1ab8882f0daba56f0f2ada150590dd528fb3ea Mon Sep 17 00:00:00 2001 From: Jorge Turrado Date: Thu, 4 Apr 2024 01:52:07 +0200 Subject: [PATCH 02/16] regenerate codegen after rebase Signed-off-by: Jorge Turrado --- pkg/client/clientset/versioned/clientset.go | 3 +- .../informers/externalversions/factory.go | 79 +------------------ 2 files changed, 6 insertions(+), 76 deletions(-) diff --git a/pkg/client/clientset/versioned/clientset.go b/pkg/client/clientset/versioned/clientset.go index 869b10d0f82d6..0c0911e0387c5 100644 --- a/pkg/client/clientset/versioned/clientset.go +++ b/pkg/client/clientset/versioned/clientset.go @@ -17,7 +17,8 @@ type Interface interface { ArgoprojV1alpha1() argoprojv1alpha1.ArgoprojV1alpha1Interface } -// Clientset contains the clients for groups. +// Clientset contains the clients for groups. Each group has exactly one +// version included in a Clientset. type Clientset struct { *discovery.DiscoveryClient argoprojV1alpha1 *argoprojv1alpha1.ArgoprojV1alpha1Client diff --git a/pkg/client/informers/externalversions/factory.go b/pkg/client/informers/externalversions/factory.go index 7d04eeb35ed52..57bd66c672490 100644 --- a/pkg/client/informers/externalversions/factory.go +++ b/pkg/client/informers/externalversions/factory.go @@ -31,11 +31,6 @@ type sharedInformerFactory struct { // startedInformers is used for tracking which informers have been started. // This allows Start() to be called multiple times safely. startedInformers map[reflect.Type]bool - // wg tracks how many goroutines were started. - wg sync.WaitGroup - // shuttingDown is true when Shutdown has been called. It may still be running - // because it needs to wait for goroutines. - shuttingDown bool } // WithCustomResyncConfig sets a custom resync period for the specified informer types. @@ -96,39 +91,20 @@ func NewSharedInformerFactoryWithOptions(client versioned.Interface, defaultResy return factory } +// Start initializes all requested informers. func (f *sharedInformerFactory) Start(stopCh <-chan struct{}) { f.lock.Lock() defer f.lock.Unlock() - if f.shuttingDown { - return - } - for informerType, informer := range f.informers { if !f.startedInformers[informerType] { - f.wg.Add(1) - // We need a new variable in each loop iteration, - // otherwise the goroutine would use the loop variable - // and that keeps changing. - informer := informer - go func() { - defer f.wg.Done() - informer.Run(stopCh) - }() + go informer.Run(stopCh) f.startedInformers[informerType] = true } } } -func (f *sharedInformerFactory) Shutdown() { - f.lock.Lock() - f.shuttingDown = true - f.lock.Unlock() - - // Will return immediately if there is nothing to wait for. - f.wg.Wait() -} - +// WaitForCacheSync waits for all started informers' cache were synced. func (f *sharedInformerFactory) WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool { informers := func() map[reflect.Type]cache.SharedIndexInformer { f.lock.Lock() @@ -175,57 +151,10 @@ func (f *sharedInformerFactory) InformerFor(obj runtime.Object, newFunc internal // SharedInformerFactory provides shared informers for resources in all known // API group versions. -// -// It is typically used like this: -// -// ctx, cancel := context.Background() -// defer cancel() -// factory := NewSharedInformerFactory(client, resyncPeriod) -// defer factory.WaitForStop() // Returns immediately if nothing was started. -// genericInformer := factory.ForResource(resource) -// typedInformer := factory.SomeAPIGroup().V1().SomeType() -// factory.Start(ctx.Done()) // Start processing these informers. -// synced := factory.WaitForCacheSync(ctx.Done()) -// for v, ok := range synced { -// if !ok { -// fmt.Fprintf(os.Stderr, "caches failed to sync: %v", v) -// return -// } -// } -// -// // Creating informers can also be created after Start, but then -// // Start must be called again: -// anotherGenericInformer := factory.ForResource(resource) -// factory.Start(ctx.Done()) type SharedInformerFactory interface { internalinterfaces.SharedInformerFactory - - // Start initializes all requested informers. They are handled in goroutines - // which run until the stop channel gets closed. - Start(stopCh <-chan struct{}) - - // Shutdown marks a factory as shutting down. At that point no new - // informers can be started anymore and Start will return without - // doing anything. - // - // In addition, Shutdown blocks until all goroutines have terminated. For that - // to happen, the close channel(s) that they were started with must be closed, - // either before Shutdown gets called or while it is waiting. - // - // Shutdown may be called multiple times, even concurrently. All such calls will - // block until all goroutines have terminated. - Shutdown() - - // WaitForCacheSync blocks until all started informers' caches were synced - // or the stop channel gets closed. - WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool - - // ForResource gives generic access to a shared informer of the matching type. ForResource(resource schema.GroupVersionResource) (GenericInformer, error) - - // InternalInformerFor returns the SharedIndexInformer for obj using an internal - // client. - InformerFor(obj runtime.Object, newFunc internalinterfaces.NewInformerFunc) cache.SharedIndexInformer + WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool Argoproj() application.Interface } From 5a566e8d1f7edc04b77f517d7a173cb836f5ad78 Mon Sep 17 00:00:00 2001 From: Jorge Turrado Date: Thu, 4 Apr 2024 02:07:19 +0200 Subject: [PATCH 03/16] fix tests Signed-off-by: Jorge Turrado --- util/argo/ref_sources_test.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/util/argo/ref_sources_test.go b/util/argo/ref_sources_test.go index f6a9efef75828..d15b963296e40 100644 --- a/util/argo/ref_sources_test.go +++ b/util/argo/ref_sources_test.go @@ -54,6 +54,7 @@ func Test_GetRefSources(t *testing.T) { argoSpec := getMultiSourceAppSpec(argoappv1.ApplicationSources{ {RepoURL: "file://does-not-exist", Ref: "source1"}, + {RepoURL: fmt.Sprintf("file://%s", repoPath)}, }) refSources, err := GetRefSources(context.Background(), GetRefSourcesOptions{ @@ -92,4 +93,21 @@ func Test_GetRefSources(t *testing.T) { assert.Error(t, err) assert.Empty(t, refSources) }) + + t.Run("target ref does not fail when single source", func(t *testing.T) { + repoDB := &dbmocks.ArgoDB{} + repoDB.On("GetRepository", context.Background(), repo.Repo).Return(repo, nil) + + argoSpec := getMultiSourceAppSpec(argoappv1.ApplicationSources{ + {RepoURL: fmt.Sprintf("file://%s", repoPath)}, + }) + + refSources, err := GetRefSources(context.Background(), GetRefSourcesOptions{ + Sources: argoSpec.Sources, + Db: repoDB, + }) + + assert.NoError(t, err) + assert.Empty(t, refSources) + }) } From 91d4f5d444599f41fe09514f31c618f83ca6a2ba Mon Sep 17 00:00:00 2001 From: Jorge Turrado Date: Thu, 4 Apr 2024 02:14:55 +0200 Subject: [PATCH 04/16] fix front linting Signed-off-by: Jorge Turrado --- .../application-status-panel/application-status-panel.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ui/src/app/applications/components/application-status-panel/application-status-panel.tsx b/ui/src/app/applications/components/application-status-panel/application-status-panel.tsx index 0794d67610ef3..6945dade58c1e 100644 --- a/ui/src/app/applications/components/application-status-panel/application-status-panel.tsx +++ b/ui/src/app/applications/components/application-status-panel/application-status-panel.tsx @@ -62,8 +62,7 @@ export const ApplicationStatusPanel = ({application, showDiff, showOperation, sh const statusExtensions = services.extensions.getStatusPanelExtensions(); - let revision = getAppDefaultSyncRevision(application) - + let revision = getAppDefaultSyncRevision(application); const infos = cntByCategory.get('info'); const warnings = cntByCategory.get('warning'); const errors = cntByCategory.get('error'); From 5aa1a9ccaa9fca178d73891bf5ea78d76ab6b389 Mon Sep 17 00:00:00 2001 From: Jorge Turrado Date: Thu, 4 Apr 2024 02:45:07 +0200 Subject: [PATCH 05/16] update test Signed-off-by: Jorge Turrado --- util/argo/ref_sources_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/util/argo/ref_sources_test.go b/util/argo/ref_sources_test.go index d15b963296e40..f68956a732db7 100644 --- a/util/argo/ref_sources_test.go +++ b/util/argo/ref_sources_test.go @@ -67,8 +67,12 @@ func Test_GetRefSources(t *testing.T) { }) t.Run("invalid ref", func(t *testing.T) { + repoDB := &dbmocks.ArgoDB{} + repoDB.On("GetRepository", context.Background(), repo.Repo).Return(repo, nil) + argoSpec := getMultiSourceAppSpec(argoappv1.ApplicationSources{ {RepoURL: "file://does-not-exist", Ref: "%invalid-name%"}, + {RepoURL: fmt.Sprintf("file://%s", repoPath)}, }) refSources, err := GetRefSources(context.TODO(), GetRefSourcesOptions{ From cdf945a9140e6707392ab7c1302b2ef5408f5a53 Mon Sep 17 00:00:00 2001 From: Jorge Turrado Date: Thu, 4 Apr 2024 03:25:46 +0200 Subject: [PATCH 06/16] update codegen Signed-off-by: Jorge Turrado --- pkg/client/clientset/versioned/clientset.go | 3 +- .../informers/externalversions/factory.go | 79 ++++++++++++++++++- 2 files changed, 76 insertions(+), 6 deletions(-) diff --git a/pkg/client/clientset/versioned/clientset.go b/pkg/client/clientset/versioned/clientset.go index 0c0911e0387c5..869b10d0f82d6 100644 --- a/pkg/client/clientset/versioned/clientset.go +++ b/pkg/client/clientset/versioned/clientset.go @@ -17,8 +17,7 @@ type Interface interface { ArgoprojV1alpha1() argoprojv1alpha1.ArgoprojV1alpha1Interface } -// Clientset contains the clients for groups. Each group has exactly one -// version included in a Clientset. +// Clientset contains the clients for groups. type Clientset struct { *discovery.DiscoveryClient argoprojV1alpha1 *argoprojv1alpha1.ArgoprojV1alpha1Client diff --git a/pkg/client/informers/externalversions/factory.go b/pkg/client/informers/externalversions/factory.go index 57bd66c672490..7d04eeb35ed52 100644 --- a/pkg/client/informers/externalversions/factory.go +++ b/pkg/client/informers/externalversions/factory.go @@ -31,6 +31,11 @@ type sharedInformerFactory struct { // startedInformers is used for tracking which informers have been started. // This allows Start() to be called multiple times safely. startedInformers map[reflect.Type]bool + // wg tracks how many goroutines were started. + wg sync.WaitGroup + // shuttingDown is true when Shutdown has been called. It may still be running + // because it needs to wait for goroutines. + shuttingDown bool } // WithCustomResyncConfig sets a custom resync period for the specified informer types. @@ -91,20 +96,39 @@ func NewSharedInformerFactoryWithOptions(client versioned.Interface, defaultResy return factory } -// Start initializes all requested informers. func (f *sharedInformerFactory) Start(stopCh <-chan struct{}) { f.lock.Lock() defer f.lock.Unlock() + if f.shuttingDown { + return + } + for informerType, informer := range f.informers { if !f.startedInformers[informerType] { - go informer.Run(stopCh) + f.wg.Add(1) + // We need a new variable in each loop iteration, + // otherwise the goroutine would use the loop variable + // and that keeps changing. + informer := informer + go func() { + defer f.wg.Done() + informer.Run(stopCh) + }() f.startedInformers[informerType] = true } } } -// WaitForCacheSync waits for all started informers' cache were synced. +func (f *sharedInformerFactory) Shutdown() { + f.lock.Lock() + f.shuttingDown = true + f.lock.Unlock() + + // Will return immediately if there is nothing to wait for. + f.wg.Wait() +} + func (f *sharedInformerFactory) WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool { informers := func() map[reflect.Type]cache.SharedIndexInformer { f.lock.Lock() @@ -151,11 +175,58 @@ func (f *sharedInformerFactory) InformerFor(obj runtime.Object, newFunc internal // SharedInformerFactory provides shared informers for resources in all known // API group versions. +// +// It is typically used like this: +// +// ctx, cancel := context.Background() +// defer cancel() +// factory := NewSharedInformerFactory(client, resyncPeriod) +// defer factory.WaitForStop() // Returns immediately if nothing was started. +// genericInformer := factory.ForResource(resource) +// typedInformer := factory.SomeAPIGroup().V1().SomeType() +// factory.Start(ctx.Done()) // Start processing these informers. +// synced := factory.WaitForCacheSync(ctx.Done()) +// for v, ok := range synced { +// if !ok { +// fmt.Fprintf(os.Stderr, "caches failed to sync: %v", v) +// return +// } +// } +// +// // Creating informers can also be created after Start, but then +// // Start must be called again: +// anotherGenericInformer := factory.ForResource(resource) +// factory.Start(ctx.Done()) type SharedInformerFactory interface { internalinterfaces.SharedInformerFactory - ForResource(resource schema.GroupVersionResource) (GenericInformer, error) + + // Start initializes all requested informers. They are handled in goroutines + // which run until the stop channel gets closed. + Start(stopCh <-chan struct{}) + + // Shutdown marks a factory as shutting down. At that point no new + // informers can be started anymore and Start will return without + // doing anything. + // + // In addition, Shutdown blocks until all goroutines have terminated. For that + // to happen, the close channel(s) that they were started with must be closed, + // either before Shutdown gets called or while it is waiting. + // + // Shutdown may be called multiple times, even concurrently. All such calls will + // block until all goroutines have terminated. + Shutdown() + + // WaitForCacheSync blocks until all started informers' caches were synced + // or the stop channel gets closed. WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool + // ForResource gives generic access to a shared informer of the matching type. + ForResource(resource schema.GroupVersionResource) (GenericInformer, error) + + // InternalInformerFor returns the SharedIndexInformer for obj using an internal + // client. + InformerFor(obj runtime.Object, newFunc internalinterfaces.NewInformerFunc) cache.SharedIndexInformer + Argoproj() application.Interface } From 5547b99a6547ba9823184f1af5d54bdd6b36500a Mon Sep 17 00:00:00 2001 From: Jorge Turrado Ferrero Date: Tue, 9 Apr 2024 22:05:36 +0200 Subject: [PATCH 07/16] Update server/application/application.go Co-authored-by: Ishita Sequeira <46771830+ishitasequeira@users.noreply.github.com> Signed-off-by: Jorge Turrado Ferrero --- server/application/application.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/application/application.go b/server/application/application.go index e90ee523873a9..6a3761ac5a072 100644 --- a/server/application/application.go +++ b/server/application/application.go @@ -1501,7 +1501,7 @@ func (s *Server) RevisionMetadata(ctx context.Context, q *application.RevisionMe var source *v1alpha1.ApplicationSource if a.Spec.HasMultipleSources() { - // If the historical data is empty (because the app hasn't been sysnced yet) + // If the historical data is empty (because the app hasn't been synced yet) // we can use the source, if not (the app has been synced at least once) // we have to use the history because sources can be added/removed if len(a.Status.History) == 0 { From 9ba82f4adedd268f01b112f20d3e7a18307cdb79 Mon Sep 17 00:00:00 2001 From: Jorge Turrado Date: Tue, 9 Apr 2024 22:22:53 +0200 Subject: [PATCH 08/16] apply feedback Signed-off-by: Jorge Turrado --- cmd/argocd/commands/app.go | 4 ++-- controller/sync.go | 4 +--- reposerver/repository/repository.go | 2 +- util/argo/ref_sources.go | 4 ++-- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/cmd/argocd/commands/app.go b/cmd/argocd/commands/app.go index 2cd13748a6b85..349eb7af4ee85 100644 --- a/cmd/argocd/commands/app.go +++ b/cmd/argocd/commands/app.go @@ -642,7 +642,7 @@ func printAppSourceDetails(appSrc *argoappv1.ApplicationSource) { if appSrc.Path != "" { fmt.Printf(printOpFmtStr, " Path:", appSrc.Path) } - if appSrc.Ref != "" { + if appSrc.IsRef() { fmt.Printf(printOpFmtStr, " Ref:", appSrc.Ref) } if appSrc.Helm != nil && len(appSrc.Helm.ValueFiles) > 0 { @@ -922,7 +922,7 @@ func NewApplicationUnsetCommand(clientOpts *argocdclient.ClientOptions) *cobra.C func unset(source *argoappv1.ApplicationSource, opts unsetOpts) (updated bool, nothingToUnset bool) { needToUnsetRef := false - if opts.ref && source.Ref != "" { + if opts.ref && source.IsRef() { source.Ref = "" updated = true needToUnsetRef = true diff --git a/controller/sync.go b/controller/sync.go index 0b88331029c68..4c3c22a51aa8b 100644 --- a/controller/sync.go +++ b/controller/sync.go @@ -109,9 +109,7 @@ func (m *appStateManager) SyncAppState(app *v1alpha1.Application, state *v1alpha return } - rollback := syncOp.Source != nil && !app.Spec.HasMultipleSources() || - syncOp.Sources != nil && app.Spec.HasMultipleSources() - + rollback := len(syncOp.Sources) > 0 || syncOp.Source != nil if rollback { // rollback case if app.Spec.HasMultipleSources() { diff --git a/reposerver/repository/repository.go b/reposerver/repository/repository.go index ac6c6cd6d6c7e..46781b7bdd09f 100644 --- a/reposerver/repository/repository.go +++ b/reposerver/repository/repository.go @@ -514,7 +514,7 @@ func (s *Service) GenerateManifest(ctx context.Context, q *apiclient.ManifestReq var err error // Skip this path for ref only sources - if q.HasMultipleSources && q.ApplicationSource.Path == "" && q.ApplicationSource.Chart == "" && q.ApplicationSource.Ref != "" { + if q.HasMultipleSources && q.ApplicationSource.Path == "" && !q.ApplicationSource.IsHelm() && q.ApplicationSource.IsRef() { log.Debugf("Skipping manifest generation for ref only source for application: %s and ref %s", q.AppName, q.ApplicationSource.Ref) _, revision, err := s.newClientResolveRevision(q.Repo, q.Revision, git.WithCache(s.cache, !q.NoRevisionCache && !q.NoCache)) res = &apiclient.ManifestResponse{ diff --git a/util/argo/ref_sources.go b/util/argo/ref_sources.go index 0f9f0644ba552..ccaa5c5650f5c 100644 --- a/util/argo/ref_sources.go +++ b/util/argo/ref_sources.go @@ -26,7 +26,7 @@ func GetRefSources(ctx context.Context, opts GetRefSourcesOptions) (argoappv1.Re // Validate first to avoid unnecessary DB calls. refKeys := make(map[string]bool) for _, source := range opts.Sources { - if source.Ref != "" { + if source.IsRef() { isValidRefKey := regexp.MustCompile(`^[a-zA-Z0-9_-]+$`).MatchString if !isValidRefKey(source.Ref) { return nil, fmt.Errorf("sources.ref %s cannot contain any special characters except '_' and '-'", source.Ref) @@ -40,7 +40,7 @@ func GetRefSources(ctx context.Context, opts GetRefSourcesOptions) (argoappv1.Re } // Get Repositories for all sources before generating Manifests for i, source := range opts.Sources { - if source.Ref != "" { + if source.IsRef() { repo, err := opts.Db.GetRepository(ctx, source.RepoURL) if err != nil { return nil, fmt.Errorf("failed to get repository %s: %v", source.RepoURL, err) From 56c2bd2e87f8f4a70eec2ba54285a171221fabd5 Mon Sep 17 00:00:00 2001 From: Jorge Turrado Date: Tue, 9 Apr 2024 22:52:35 +0200 Subject: [PATCH 09/16] fix errors Signed-off-by: Jorge Turrado --- reposerver/repository/repository_test.go | 14 ++++++++++++++ server/application/application.go | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/reposerver/repository/repository_test.go b/reposerver/repository/repository_test.go index ae1a054f08de5..b24be54c669f3 100644 --- a/reposerver/repository/repository_test.go +++ b/reposerver/repository/repository_test.go @@ -532,6 +532,7 @@ func TestHelmChartReferencingExternalValues_InvalidRefs(t *testing.T) { // Empty refsource service := newService(t, ".") +<<<<<<< HEAD getRepository := func(ctx context.Context, url string, project string) (*argoappv1.Repository, error) { return &argoappv1.Repository{ Repo: "https://git.example.com/test/repo", @@ -539,6 +540,12 @@ func TestHelmChartReferencingExternalValues_InvalidRefs(t *testing.T) { } refSources, err := argo.GetRefSources(context.Background(), spec, getRepository) +======= + refSources, err := argo.GetRefSources(context.Background(), argo.GetRefSourcesOptions{ + Sources: spec.Sources, + Db: repoDB, + }) +>>>>>>> aec91ff53 (fix errors) require.NoError(t, err) request := &apiclient.ManifestRequest{Repo: &argoappv1.Repository{}, ApplicationSource: &spec.Sources[0], NoCache: true, RefSources: refSources, HasMultipleSources: true, ProjectName: "something", @@ -551,7 +558,14 @@ func TestHelmChartReferencingExternalValues_InvalidRefs(t *testing.T) { service = newService(t, ".") spec.Sources[1].Ref = "Invalid" +<<<<<<< HEAD refSources, err = argo.GetRefSources(context.Background(), spec, getRepository) +======= + refSources, err = argo.GetRefSources(context.Background(), argo.GetRefSourcesOptions{ + Sources: spec.Sources, + Db: repoDB, + }) +>>>>>>> aec91ff53 (fix errors) require.NoError(t, err) request = &apiclient.ManifestRequest{Repo: &argoappv1.Repository{}, ApplicationSource: &spec.Sources[0], NoCache: true, RefSources: refSources, HasMultipleSources: true, ProjectName: "something", diff --git a/server/application/application.go b/server/application/application.go index 6a3761ac5a072..69f39583123c6 100644 --- a/server/application/application.go +++ b/server/application/application.go @@ -1501,7 +1501,7 @@ func (s *Server) RevisionMetadata(ctx context.Context, q *application.RevisionMe var source *v1alpha1.ApplicationSource if a.Spec.HasMultipleSources() { - // If the historical data is empty (because the app hasn't been synced yet) + // If the historical data is empty (because the app hasn't been synced yet) // we can use the source, if not (the app has been synced at least once) // we have to use the history because sources can be added/removed if len(a.Status.History) == 0 { From fe5e4ba1c174589ee9e00e17c078ceb0b4b685ed Mon Sep 17 00:00:00 2001 From: Jorge Turrado Date: Sun, 14 Apr 2024 23:06:49 +0200 Subject: [PATCH 10/16] add support for switching between single and multi Signed-off-by: Jorge Turrado --- controller/sync.go | 15 ++++--- reposerver/repository/repository.go | 2 +- server/application/application.go | 64 +++++++++++++++++++------- server/repository/repository.go | 70 ++++++++++++++++++++++------- 4 files changed, 110 insertions(+), 41 deletions(-) diff --git a/controller/sync.go b/controller/sync.go index 4c3c22a51aa8b..8346ad7fa9227 100644 --- a/controller/sync.go +++ b/controller/sync.go @@ -109,14 +109,17 @@ func (m *appStateManager) SyncAppState(app *v1alpha1.Application, state *v1alpha return } + isMultiSourceRevision := app.Spec.HasMultipleSources() rollback := len(syncOp.Sources) > 0 || syncOp.Source != nil if rollback { // rollback case - if app.Spec.HasMultipleSources() { + if len(state.Operation.Sync.Sources) > 0 { sources = state.Operation.Sync.Sources + isMultiSourceRevision = true } else { source = *state.Operation.Sync.Source sources = make([]v1alpha1.ApplicationSource, 0) + isMultiSourceRevision = false } } else { // normal sync case (where source is taken from app.spec.sources) @@ -138,7 +141,7 @@ func (m *appStateManager) SyncAppState(app *v1alpha1.Application, state *v1alpha // status.operationState.syncResult.source. must be set properly since auto-sync relies // on this information to decide if it should sync (if source is different than the last // sync attempt) - if app.Spec.HasMultipleSources() { + if isMultiSourceRevision { syncRes.Sources = sources } else { syncRes.Source = source @@ -149,7 +152,7 @@ func (m *appStateManager) SyncAppState(app *v1alpha1.Application, state *v1alpha // if we get here, it means we did not remember a commit SHA which we should be syncing to. // This typically indicates we are just about to begin a brand new sync/rollback operation. // Take the value in the requested operation. We will resolve this to a SHA later. - if app.Spec.HasMultipleSources() { + if isMultiSourceRevision { if len(revisions) != len(sources) { revisions = syncOp.Revisions } @@ -172,13 +175,13 @@ func (m *appStateManager) SyncAppState(app *v1alpha1.Application, state *v1alpha return } - if !app.Spec.HasMultipleSources() { + if !isMultiSourceRevision { sources = []v1alpha1.ApplicationSource{source} revisions = []string{revision} } // ignore error if CompareStateRepoError, this shouldn't happen as noRevisionCache is true - compareResult, err := m.CompareAppState(app, proj, revisions, sources, false, true, syncOp.Manifests, app.Spec.HasMultipleSources(), rollback) + compareResult, err := m.CompareAppState(app, proj, revisions, sources, false, true, syncOp.Manifests, isMultiSourceRevision, rollback) if err != nil && !goerrors.Is(err, CompareStateRepoError) { state.Phase = common.OperationError state.Message = err.Error() @@ -394,7 +397,7 @@ func (m *appStateManager) SyncAppState(app *v1alpha1.Application, state *v1alpha logEntry.WithField("duration", time.Since(start)).Info("sync/terminate complete") if !syncOp.DryRun && len(syncOp.Resources) == 0 && state.Phase.Successful() { - err := m.persistRevisionHistory(app, compareResult.syncStatus.Revision, source, compareResult.syncStatus.Revisions, compareResult.syncStatus.ComparedTo.Sources, app.Spec.HasMultipleSources(), state.StartedAt, state.Operation.InitiatedBy) + err := m.persistRevisionHistory(app, compareResult.syncStatus.Revision, source, compareResult.syncStatus.Revisions, compareResult.syncStatus.ComparedTo.Sources, isMultiSourceRevision, state.StartedAt, state.Operation.InitiatedBy) if err != nil { state.Phase = common.OperationError state.Message = fmt.Sprintf("failed to record sync to history: %v", err) diff --git a/reposerver/repository/repository.go b/reposerver/repository/repository.go index 46781b7bdd09f..b225b8fb19602 100644 --- a/reposerver/repository/repository.go +++ b/reposerver/repository/repository.go @@ -2016,7 +2016,7 @@ func (s *Service) GetAppDetails(ctx context.Context, q *apiclient.RepoServerAppD } settings := operationSettings{allowConcurrent: q.Source.AllowsConcurrentProcessing(), noCache: q.NoCache, noRevisionCache: q.NoCache || q.NoRevisionCache} - err := s.runRepoOperation(ctx, q.Source.TargetRevision, q.Repo, q.Source, false, cacheFn, operation, settings, false, nil) + err := s.runRepoOperation(ctx, q.Source.TargetRevision, q.Repo, q.Source, false, cacheFn, operation, settings, len(q.RefSources) > 0, q.RefSources) return res, err } diff --git a/server/application/application.go b/server/application/application.go index 69f39583123c6..2d8e5f277e177 100644 --- a/server/application/application.go +++ b/server/application/application.go @@ -1500,29 +1500,59 @@ func (s *Server) RevisionMetadata(ctx context.Context, q *application.RevisionMe } var source *v1alpha1.ApplicationSource - if a.Spec.HasMultipleSources() { - // If the historical data is empty (because the app hasn't been synced yet) - // we can use the source, if not (the app has been synced at least once) - // we have to use the history because sources can be added/removed - if len(a.Status.History) == 0 { + + // To support changes between single source and multi source revisions + // we have to calculate if the operation has to be done as multisource or not. + // There are 2 different scenarios, checking current revision and historic revision + // - Current revision (VersionId is nil or 0): + // - The application is multi source and required version too -> multi source + // - The application is single source and the required version too -> single source + // - The application is multi source and the required version is single source -> single source + // - The application is single source and the required version is multi source -> multi source + // - Historic revision: + // - The application is multi source and the previous one too -> multi source + // - The application is single source and the previous one too -> single source + // - The application is multi source and the previous one is single source -> multi source + // - The application is single source and the previous one is multi source -> single source + isRevisionMultiSource := a.Spec.HasMultipleSources() + emptyHistory := len(a.Status.History) == 0 + if !emptyHistory { + for _, h := range a.Status.History { + if h.ID == int64(*q.VersionId) { + isRevisionMultiSource = len(h.Revisions) > 0 + break + } + } + } + + // If the historical data is empty (because the app hasn't been synced yet) + // we can use the source, if not (the app has been synced at least once) + // we have to use the history because sources can be added/removed + if emptyHistory { + if isRevisionMultiSource { source = &a.Spec.Sources[*q.SourceIndex] } else { - // the source count can change during the time, we cannot just trust in .status.sync - // because if a source has been added/removed, the revisions there won't match - // as this is only used for the UI and not internally, we can use the historical data - // using the specific revisionId - for _, h := range a.Status.History { - if h.ID == int64(*q.VersionId) { + s := a.Spec.GetSource() + source = &s + } + } else { + // the source count can change during the time, we cannot just trust in .status.sync + // because if a source has been added/removed, the revisions there won't match + // as this is only used for the UI and not internally, we can use the historical data + // using the specific revisionId + for _, h := range a.Status.History { + if h.ID == int64(*q.VersionId) { + h := h + if isRevisionMultiSource { source = &h.Sources[*q.SourceIndex] + } else { + source = &h.Source } } } - if source == nil { - return nil, fmt.Errorf("revision not found: %w", err) - } - } else { - s := a.Spec.GetSource() - source = &s + } + if source == nil { + return nil, fmt.Errorf("revision not found: %w", err) } repo, err := s.db.GetRepository(ctx, source.RepoURL, proj.Name) diff --git a/server/repository/repository.go b/server/repository/repository.go index ef9b8e527e72c..8bba3d63572ee 100644 --- a/server/repository/repository.go +++ b/server/repository/repository.go @@ -345,6 +345,11 @@ func (s *Server) GetAppDetails(ctx context.Context, q *repositorypkg.RepoAppDeta if err != nil { return nil, err } + refSources, err := s.getRefSourcesFromHistory(app, *q.Source, q.SourceIndex, q.VersionId) + if err != nil { + return nil, err + } + return repoClient.GetAppDetails(ctx, &apiclient.RepoServerAppDetailsQuery{ Repo: repo, Source: q.Source, @@ -352,6 +357,7 @@ func (s *Server) GetAppDetails(ctx context.Context, q *repositorypkg.RepoAppDeta KustomizeOptions: kustomizeOptions, HelmOptions: helmOptions, AppName: q.AppName, + RefSources: refSources, }) } @@ -601,7 +607,8 @@ func (s *Server) isRepoPermittedInProject(ctx context.Context, repo string, proj // isSourceInHistory checks if the supplied application source is either our current application // source, or was something which we synced to previously. func isSourceInHistory(app *v1alpha1.Application, source v1alpha1.ApplicationSource, index int32, versionId int32) bool { - + // We have to check if the spec is within the source or sources split + // and then iterate over the historical if app.Spec.HasMultipleSources() { appSources := app.Spec.GetSources() for _, s := range appSources { @@ -609,10 +616,22 @@ func isSourceInHistory(app *v1alpha1.Application, source v1alpha1.ApplicationSou return true } } + } else { + appSource := app.Spec.GetSource() + if source.Equals(&appSource) { + return true + } + } - // In case of multi source apps, we have to check the specific versionID because users - // could have removed/added new sources and we cannot check all the versions due to that - for _, h := range app.Status.History { + // Iterate history. When comparing items in our history, use the actual synced revision to + // compare with the supplied source.targetRevision in the request. This is because + // history[].source.targetRevision is ambiguous (e.g. HEAD), whereas + // history[].revision will contain the explicit SHA + // In case of multi source apps, we have to check the specific versionID because users + // could have removed/added new sources and we cannot check all the versions due to that + for _, h := range app.Status.History { + // multi source revision + if len(h.Sources) > 0 { if h.ID == int64(versionId) { if h.Revisions == nil { continue @@ -622,24 +641,41 @@ func isSourceInHistory(app *v1alpha1.Application, source v1alpha1.ApplicationSou return true } } + } else { // single source revision + h.Source.TargetRevision = h.Revision + if source.Equals(&h.Source) { + return true + } } - return false } - appSource := app.Spec.GetSource() - if source.Equals(&appSource) { - return true - } - // Iterate history. When comparing items in our history, use the actual synced revision to - // compare with the supplied source.targetRevision in the request. This is because - // history[].source.targetRevision is ambiguous (e.g. HEAD), whereas - // history[].revision will contain the explicit SHA + return false +} + +// getRefSourcesFromHistory returns the RefSources based on the historical data +func (s *Server) getRefSourcesFromHistory(app *v1alpha1.Application, source v1alpha1.ApplicationSource, index int32, versionId int32) (v1alpha1.RefTargetRevisionMapping, error) { for _, h := range app.Status.History { - h.Source.TargetRevision = h.Revision - if source.Equals(&h.Source) { - return true + // As RefSources are only available for multisource apps, + // single source apps are discarded + if len(h.Sources) > 0 { + if h.ID == int64(versionId) { + if h.Revisions == nil { + continue + } + revisionSource := &h.Sources[index] + revisionSource.TargetRevision = h.Revisions[index] + if !source.Equals(revisionSource) { + continue + } + return argo.GetRefSources(context.Background(), argo.GetRefSourcesOptions{ + Sources: h.Sources, + Db: s.db, + Revisions: h.Revisions, + IsRollback: true, + }) + } } } - return false + return nil, nil } From 5d6561fccc256d1f2c5dade16f7685497c164c0a Mon Sep 17 00:00:00 2001 From: Jorge Turrado Date: Mon, 15 Apr 2024 00:19:27 +0200 Subject: [PATCH 11/16] fix dereference issue Signed-off-by: Jorge Turrado --- server/application/application.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/server/application/application.go b/server/application/application.go index 2d8e5f277e177..c50f3ff88b2e1 100644 --- a/server/application/application.go +++ b/server/application/application.go @@ -1499,6 +1499,11 @@ func (s *Server) RevisionMetadata(ctx context.Context, q *application.RevisionMe return nil, err } + var versionId int64 = 0 + if q.VersionId != nil { + versionId = int64(*q.VersionId) + } + var source *v1alpha1.ApplicationSource // To support changes between single source and multi source revisions @@ -1518,7 +1523,7 @@ func (s *Server) RevisionMetadata(ctx context.Context, q *application.RevisionMe emptyHistory := len(a.Status.History) == 0 if !emptyHistory { for _, h := range a.Status.History { - if h.ID == int64(*q.VersionId) { + if h.ID == versionId { isRevisionMultiSource = len(h.Revisions) > 0 break } @@ -1541,7 +1546,7 @@ func (s *Server) RevisionMetadata(ctx context.Context, q *application.RevisionMe // as this is only used for the UI and not internally, we can use the historical data // using the specific revisionId for _, h := range a.Status.History { - if h.ID == int64(*q.VersionId) { + if h.ID == versionId { h := h if isRevisionMultiSource { source = &h.Sources[*q.SourceIndex] From c5038eb8a98591aa310d47d4ca4520f9bc13034f Mon Sep 17 00:00:00 2001 From: Jorge Turrado Date: Mon, 15 Apr 2024 00:53:20 +0200 Subject: [PATCH 12/16] remove unnecesary code Signed-off-by: Jorge Turrado --- server/repository/repository.go | 33 --------------------------------- 1 file changed, 33 deletions(-) diff --git a/server/repository/repository.go b/server/repository/repository.go index 8bba3d63572ee..ac6865e31cddc 100644 --- a/server/repository/repository.go +++ b/server/repository/repository.go @@ -345,10 +345,6 @@ func (s *Server) GetAppDetails(ctx context.Context, q *repositorypkg.RepoAppDeta if err != nil { return nil, err } - refSources, err := s.getRefSourcesFromHistory(app, *q.Source, q.SourceIndex, q.VersionId) - if err != nil { - return nil, err - } return repoClient.GetAppDetails(ctx, &apiclient.RepoServerAppDetailsQuery{ Repo: repo, @@ -357,7 +353,6 @@ func (s *Server) GetAppDetails(ctx context.Context, q *repositorypkg.RepoAppDeta KustomizeOptions: kustomizeOptions, HelmOptions: helmOptions, AppName: q.AppName, - RefSources: refSources, }) } @@ -651,31 +646,3 @@ func isSourceInHistory(app *v1alpha1.Application, source v1alpha1.ApplicationSou return false } - -// getRefSourcesFromHistory returns the RefSources based on the historical data -func (s *Server) getRefSourcesFromHistory(app *v1alpha1.Application, source v1alpha1.ApplicationSource, index int32, versionId int32) (v1alpha1.RefTargetRevisionMapping, error) { - for _, h := range app.Status.History { - // As RefSources are only available for multisource apps, - // single source apps are discarded - if len(h.Sources) > 0 { - if h.ID == int64(versionId) { - if h.Revisions == nil { - continue - } - revisionSource := &h.Sources[index] - revisionSource.TargetRevision = h.Revisions[index] - if !source.Equals(revisionSource) { - continue - } - return argo.GetRefSources(context.Background(), argo.GetRefSourcesOptions{ - Sources: h.Sources, - Db: s.db, - Revisions: h.Revisions, - IsRollback: true, - }) - } - } - } - - return nil, nil -} From 1a7fe085fceaacfada20724def18b2543e4cdf49 Mon Sep 17 00:00:00 2001 From: Jorge Turrado Date: Sun, 9 Jun 2024 22:52:12 +0200 Subject: [PATCH 13/16] Rebase master Signed-off-by: Jorge Turrado --- controller/state.go | 7 +- pkg/apiclient/application/application.pb.go | 342 ++++++++++---------- pkg/apiclient/repository/repository.pb.go | 148 ++++----- reposerver/repository/repository_test.go | 24 +- server/application/application.go | 2 +- server/application/application_test.go | 7 +- server/repository/repository_test.go | 4 +- util/argo/argo.go | 12 +- util/argo/argo_test.go | 8 +- util/argo/ref_sources.go | 62 ---- util/argo/ref_sources_test.go | 117 ------- util/webhook/webhook.go | 2 +- 12 files changed, 273 insertions(+), 462 deletions(-) delete mode 100644 util/argo/ref_sources.go delete mode 100644 util/argo/ref_sources_test.go diff --git a/controller/state.go b/controller/state.go index 080d74559d571..0c0cf90e65eb3 100644 --- a/controller/state.go +++ b/controller/state.go @@ -180,12 +180,7 @@ func (m *appStateManager) GetRepoObjs(app *v1alpha1.Application, sources []v1alp // Store the map of all sources having ref field into a map for applications with sources field // If it's for a rollback process, the refSources[*].targetRevision fields are the desired // revisions for the rollback - refSources, err := argo.GetRefSources(context.Background(), argo.GetRefSourcesOptions{ - Sources: sources, - Db: m.db, - Revisions: revisions, - IsRollback: rollback, - }) + refSources, err := argo.GetRefSources(context.Background(), app.Spec, m.db.GetRepository, revisions, rollback) if err != nil { return nil, nil, fmt.Errorf("failed to get ref sources: %v", err) } diff --git a/pkg/apiclient/application/application.pb.go b/pkg/apiclient/application/application.pb.go index 4cccc205cfc75..2f73469d1049f 100644 --- a/pkg/apiclient/application/application.pb.go +++ b/pkg/apiclient/application/application.pb.go @@ -2842,177 +2842,179 @@ func init() { } var fileDescriptor_df6e82b174b5eaec = []byte{ - // 2711 bytes of a gzipped FileDescriptorProto + // 2742 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x5a, 0x4d, 0x8c, 0x1b, 0x49, - 0x15, 0xa6, 0xec, 0xb1, 0xc7, 0xf3, 0x3c, 0x93, 0x9f, 0xda, 0x64, 0xe8, 0x75, 0x66, 0x83, 0xd3, - 0xf9, 0x9b, 0x4c, 0x32, 0x76, 0x62, 0x02, 0xca, 0xce, 0xee, 0x0a, 0x92, 0xc9, 0x2f, 0x4c, 0xb2, - 0xa1, 0x27, 0x21, 0x68, 0x39, 0x40, 0x6d, 0xbb, 0xc6, 0xd3, 0x4c, 0xbb, 0xbb, 0xd3, 0xdd, 0x76, - 0x34, 0x0a, 0xb9, 0x2c, 0xca, 0x05, 0xad, 0x40, 0xc0, 0x1e, 0x10, 0x42, 0x80, 0x16, 0xad, 0x84, - 0x10, 0x88, 0x0b, 0x42, 0x48, 0x08, 0x09, 0x2e, 0x08, 0x0e, 0x48, 0x2b, 0x38, 0x72, 0x41, 0x11, - 0xe2, 0x08, 0x97, 0x3d, 0x23, 0x54, 0xd5, 0x55, 0xdd, 0xd5, 0xfe, 0x69, 0x7b, 0xb0, 0xd1, 0xe6, - 0xd6, 0xaf, 0x5c, 0xf5, 0xde, 0xf7, 0x5e, 0xbd, 0x7a, 0xef, 0xd5, 0x2b, 0xc3, 0x89, 0x80, 0xfa, - 0x5d, 0xea, 0xd7, 0x89, 0xe7, 0xd9, 0x96, 0x49, 0x42, 0xcb, 0x75, 0xd4, 0xef, 0x9a, 0xe7, 0xbb, - 0xa1, 0x8b, 0xcb, 0xca, 0x50, 0x65, 0xa9, 0xe5, 0xba, 0x2d, 0x9b, 0xd6, 0x89, 0x67, 0xd5, 0x89, - 0xe3, 0xb8, 0x21, 0x1f, 0x0e, 0xa2, 0xa9, 0x15, 0x7d, 0xe7, 0x52, 0x50, 0xb3, 0x5c, 0xfe, 0xab, - 0xe9, 0xfa, 0xb4, 0xde, 0xbd, 0x50, 0x6f, 0x51, 0x87, 0xfa, 0x24, 0xa4, 0x4d, 0x31, 0xe7, 0x62, - 0x32, 0xa7, 0x4d, 0xcc, 0x6d, 0xcb, 0xa1, 0xfe, 0x6e, 0xdd, 0xdb, 0x69, 0xb1, 0x81, 0xa0, 0xde, - 0xa6, 0x21, 0x19, 0xb4, 0x6a, 0xa3, 0x65, 0x85, 0xdb, 0x9d, 0x37, 0x6b, 0xa6, 0xdb, 0xae, 0x13, - 0xbf, 0xe5, 0x7a, 0xbe, 0xfb, 0x15, 0xfe, 0xb1, 0x6a, 0x36, 0xeb, 0xdd, 0x46, 0xc2, 0x40, 0xd5, - 0xa5, 0x7b, 0x81, 0xd8, 0xde, 0x36, 0xe9, 0xe7, 0x76, 0x6d, 0x04, 0x37, 0x9f, 0x7a, 0xae, 0xb0, - 0x0d, 0xff, 0xb4, 0x42, 0xd7, 0xdf, 0x55, 0x3e, 0x23, 0x36, 0xfa, 0x07, 0x08, 0x0e, 0x5c, 0x4e, - 0xe4, 0x7d, 0xae, 0x43, 0xfd, 0x5d, 0x8c, 0x61, 0xc6, 0x21, 0x6d, 0xaa, 0xa1, 0x2a, 0x5a, 0x9e, - 0x33, 0xf8, 0x37, 0xd6, 0x60, 0xd6, 0xa7, 0x5b, 0x3e, 0x0d, 0xb6, 0xb5, 0x1c, 0x1f, 0x96, 0x24, - 0xae, 0x40, 0x89, 0x09, 0xa7, 0x66, 0x18, 0x68, 0xf9, 0x6a, 0x7e, 0x79, 0xce, 0x88, 0x69, 0xbc, - 0x0c, 0xfb, 0x7d, 0x1a, 0xb8, 0x1d, 0xdf, 0xa4, 0x9f, 0xa7, 0x7e, 0x60, 0xb9, 0x8e, 0x36, 0xc3, - 0x57, 0xf7, 0x0e, 0x33, 0x2e, 0x01, 0xb5, 0xa9, 0x19, 0xba, 0xbe, 0x56, 0xe0, 0x53, 0x62, 0x9a, - 0xe1, 0x61, 0xc0, 0xb5, 0x62, 0x84, 0x87, 0x7d, 0x63, 0x1d, 0xe6, 0x89, 0xe7, 0xdd, 0x21, 0x6d, - 0x1a, 0x78, 0xc4, 0xa4, 0xda, 0x2c, 0xff, 0x2d, 0x35, 0xc6, 0x30, 0x0b, 0x24, 0x5a, 0x89, 0x03, - 0x93, 0xa4, 0xbe, 0x0e, 0x73, 0x77, 0xdc, 0x26, 0x1d, 0xae, 0x6e, 0x2f, 0xfb, 0x5c, 0x3f, 0x7b, - 0xfd, 0x29, 0x82, 0xc3, 0x06, 0xed, 0x5a, 0x0c, 0xff, 0x6d, 0x1a, 0x92, 0x26, 0x09, 0x49, 0x2f, - 0xc7, 0x5c, 0xcc, 0xb1, 0x02, 0x25, 0x5f, 0x4c, 0xd6, 0x72, 0x7c, 0x3c, 0xa6, 0xfb, 0xa4, 0xe5, - 0xb3, 0x95, 0x89, 0x4c, 0x18, 0x2b, 0xf3, 0x4f, 0x04, 0x47, 0x95, 0x3d, 0x34, 0x84, 0x65, 0xaf, - 0x75, 0xa9, 0x13, 0x06, 0xc3, 0x01, 0x9d, 0x83, 0x83, 0x72, 0x13, 0x7a, 0xf5, 0xec, 0xff, 0x81, - 0x41, 0x54, 0x07, 0x25, 0x44, 0x75, 0x0c, 0x57, 0xa1, 0x2c, 0xe9, 0xfb, 0xb7, 0xae, 0x0a, 0x98, - 0xea, 0x50, 0x9f, 0xa2, 0x85, 0x6c, 0x45, 0x8b, 0x69, 0x45, 0xdf, 0x47, 0xa0, 0x29, 0x8a, 0xde, - 0x26, 0x8e, 0xb5, 0x45, 0x83, 0x70, 0x5c, 0x9b, 0xa3, 0xe9, 0xd9, 0x9c, 0x39, 0x76, 0xa4, 0xd5, - 0x5d, 0x76, 0x9e, 0x58, 0xfc, 0xd0, 0x0a, 0xd5, 0xfc, 0x72, 0xde, 0xe8, 0x1d, 0xc6, 0x4b, 0x30, - 0x27, 0x65, 0x06, 0x5a, 0x91, 0xbb, 0x61, 0x32, 0xa0, 0x1f, 0x83, 0xb9, 0xeb, 0x96, 0x4d, 0xd7, - 0xb7, 0x3b, 0xce, 0x0e, 0x3e, 0x04, 0x05, 0x93, 0x7d, 0x70, 0x1d, 0xe6, 0x8d, 0x88, 0xd0, 0xbf, - 0x85, 0xe0, 0xd8, 0x30, 0xad, 0x1f, 0x58, 0xe1, 0x36, 0x5b, 0x1f, 0x0c, 0x53, 0xdf, 0xdc, 0xa6, - 0xe6, 0x4e, 0xd0, 0x69, 0x4b, 0x97, 0x93, 0xf4, 0x84, 0x2e, 0xf7, 0x53, 0x04, 0xcb, 0x23, 0x31, - 0x3d, 0xf0, 0x89, 0xe7, 0x51, 0x1f, 0x5f, 0x87, 0xc2, 0x43, 0xf6, 0x03, 0x3f, 0x60, 0xe5, 0x46, - 0xad, 0xa6, 0x06, 0xe8, 0x91, 0x5c, 0x6e, 0x7e, 0xc4, 0x88, 0x96, 0xe3, 0x9a, 0x34, 0x4f, 0x8e, - 0xf3, 0x59, 0x4c, 0xf1, 0x89, 0xad, 0xc8, 0xe6, 0xf3, 0x69, 0x57, 0x8a, 0x30, 0xe3, 0x11, 0x3f, - 0xd4, 0x0f, 0xc3, 0x0b, 0xe9, 0xe3, 0xe1, 0xb9, 0x4e, 0x40, 0xf5, 0xdf, 0xa4, 0xbd, 0x69, 0xdd, - 0xa7, 0x24, 0xa4, 0x06, 0x7d, 0xd8, 0xa1, 0x41, 0x88, 0x77, 0x40, 0xcd, 0x19, 0xdc, 0xaa, 0xe5, - 0xc6, 0xad, 0x5a, 0x12, 0x74, 0x6b, 0x32, 0xe8, 0xf2, 0x8f, 0x2f, 0x99, 0xcd, 0x5a, 0xb7, 0x51, - 0xf3, 0x76, 0x5a, 0x35, 0x16, 0xc2, 0x53, 0xc8, 0x64, 0x08, 0x57, 0x55, 0x35, 0x54, 0xee, 0x78, - 0x11, 0x8a, 0x1d, 0x2f, 0xa0, 0x7e, 0xc8, 0x35, 0x2b, 0x19, 0x82, 0x62, 0xfb, 0xd7, 0x25, 0xb6, - 0xd5, 0x24, 0x61, 0xb4, 0x3f, 0x25, 0x23, 0xa6, 0xf5, 0xdf, 0xa6, 0xd1, 0xdf, 0xf7, 0x9a, 0x1f, - 0x16, 0x7a, 0x15, 0x65, 0x2e, 0x8d, 0x52, 0xf5, 0xa0, 0x7c, 0xda, 0x83, 0x7e, 0x99, 0xc6, 0x7f, - 0x95, 0xda, 0x34, 0xc1, 0x3f, 0xc8, 0x99, 0x35, 0x98, 0x35, 0x49, 0x60, 0x92, 0xa6, 0x94, 0x22, - 0x49, 0x16, 0xc8, 0x3c, 0xdf, 0xf5, 0x48, 0x8b, 0x73, 0xba, 0xeb, 0xda, 0x96, 0xb9, 0x2b, 0xc4, - 0xf5, 0xff, 0xd0, 0xe7, 0xf8, 0x33, 0xd9, 0x8e, 0x5f, 0x48, 0xc3, 0x3e, 0x0e, 0xe5, 0xcd, 0x5d, - 0xc7, 0x7c, 0xdd, 0x8b, 0x0e, 0xf7, 0x21, 0x28, 0x58, 0x21, 0x6d, 0x07, 0x1a, 0xe2, 0x07, 0x3b, - 0x22, 0xf4, 0xff, 0x14, 0x60, 0x51, 0xd1, 0x8d, 0x2d, 0xc8, 0xd2, 0x2c, 0x2b, 0x4a, 0x2d, 0x42, - 0xb1, 0xe9, 0xef, 0x1a, 0x1d, 0x47, 0x38, 0x80, 0xa0, 0x98, 0x60, 0xcf, 0xef, 0x38, 0x11, 0xfc, - 0x92, 0x11, 0x11, 0x78, 0x0b, 0x4a, 0x41, 0xc8, 0xaa, 0x84, 0xd6, 0x2e, 0x07, 0x5e, 0x6e, 0x7c, - 0x66, 0xb2, 0x4d, 0x67, 0xd0, 0x37, 0x05, 0x47, 0x23, 0xe6, 0x8d, 0x1f, 0xb2, 0x98, 0x16, 0x05, - 0xba, 0x40, 0x9b, 0xad, 0xe6, 0x97, 0xcb, 0x8d, 0xcd, 0xc9, 0x05, 0xbd, 0xee, 0xb1, 0x0a, 0x47, - 0xc9, 0x60, 0x46, 0x22, 0x85, 0x85, 0xd1, 0xb6, 0x88, 0x0f, 0x81, 0xc8, 0xe6, 0xc9, 0x00, 0xfe, - 0x02, 0x14, 0x2c, 0x67, 0xcb, 0x0d, 0xb4, 0x39, 0x0e, 0xe6, 0xca, 0x64, 0x60, 0x6e, 0x39, 0x5b, - 0xae, 0x11, 0x31, 0xc4, 0x0f, 0x61, 0xc1, 0xa7, 0xa1, 0xbf, 0x2b, 0xad, 0xa0, 0x01, 0xb7, 0xeb, - 0x67, 0x27, 0x93, 0x60, 0xa8, 0x2c, 0x8d, 0xb4, 0x04, 0xbc, 0x06, 0xe5, 0x20, 0xf1, 0x31, 0xad, - 0xcc, 0x05, 0x6a, 0x29, 0x46, 0x8a, 0x0f, 0x1a, 0xea, 0xe4, 0x3e, 0xef, 0x9e, 0xcf, 0xf6, 0xee, - 0x85, 0x91, 0x59, 0x6d, 0xdf, 0x18, 0x59, 0x6d, 0x7f, 0x6f, 0x56, 0xfb, 0x37, 0x82, 0xa5, 0xbe, - 0xe0, 0xb4, 0xe9, 0xd1, 0xcc, 0x63, 0x40, 0x60, 0x26, 0xf0, 0xa8, 0xc9, 0x33, 0x55, 0xb9, 0x71, - 0x7b, 0x6a, 0xd1, 0x8a, 0xcb, 0xe5, 0xac, 0xb3, 0x02, 0xea, 0x84, 0x71, 0xe1, 0x87, 0x08, 0x3e, - 0xaa, 0xc8, 0xbc, 0x4b, 0x42, 0x73, 0x3b, 0x4b, 0x59, 0x76, 0x7e, 0xd9, 0x1c, 0x91, 0x97, 0x23, - 0x82, 0x59, 0x95, 0x7f, 0xdc, 0xdb, 0xf5, 0x18, 0x40, 0xf6, 0x4b, 0x32, 0x30, 0x61, 0xf1, 0xf4, - 0x33, 0x04, 0x15, 0x35, 0x86, 0xbb, 0xb6, 0xfd, 0x26, 0x31, 0x77, 0xb2, 0x40, 0xee, 0x83, 0x9c, - 0xd5, 0xe4, 0x08, 0xf3, 0x46, 0xce, 0x6a, 0xee, 0x31, 0x18, 0xf5, 0xc2, 0x2d, 0x66, 0xc3, 0x9d, - 0x4d, 0xc3, 0xfd, 0xa0, 0x07, 0xae, 0x0c, 0x09, 0x19, 0x70, 0x97, 0x60, 0xce, 0xe9, 0x29, 0x64, - 0x93, 0x81, 0x01, 0x05, 0x6c, 0xae, 0xaf, 0x80, 0xd5, 0x60, 0xb6, 0x1b, 0x5f, 0x53, 0xd8, 0xcf, - 0x92, 0x64, 0x2a, 0xb6, 0x7c, 0xb7, 0xe3, 0x09, 0xa3, 0x47, 0x04, 0x43, 0xb1, 0x63, 0x39, 0x4d, - 0xad, 0x18, 0xa1, 0x60, 0xdf, 0x7b, 0xbf, 0x98, 0xa4, 0xd4, 0xfe, 0x79, 0x0e, 0x3e, 0x36, 0x40, - 0xed, 0x91, 0xfe, 0xf4, 0x7c, 0xe8, 0x1e, 0x7b, 0xf5, 0xec, 0x50, 0xaf, 0x2e, 0x8d, 0xf2, 0xea, - 0xb9, 0x6c, 0x7b, 0x41, 0xda, 0x5e, 0x3f, 0xc9, 0x41, 0x75, 0x80, 0xbd, 0x46, 0x97, 0x13, 0xcf, - 0x8d, 0xc1, 0xb6, 0x5c, 0x5f, 0x78, 0x49, 0xc9, 0x88, 0x08, 0x76, 0xce, 0x5c, 0xdf, 0xdb, 0x26, - 0x0e, 0xf7, 0x8e, 0x92, 0x21, 0xa8, 0x09, 0x4d, 0xf5, 0xf5, 0x1c, 0x68, 0xd2, 0x3e, 0x97, 0x4d, - 0x6e, 0xad, 0x8e, 0xf3, 0xfc, 0x9b, 0x68, 0x11, 0x8a, 0x84, 0xa3, 0x15, 0x4e, 0x25, 0xa8, 0x3e, - 0x63, 0x94, 0xb2, 0x8d, 0x31, 0x97, 0x36, 0xc6, 0x53, 0x04, 0x47, 0xd2, 0xc6, 0x08, 0x36, 0xac, - 0x20, 0x94, 0x97, 0x03, 0xbc, 0x05, 0xb3, 0x91, 0x9c, 0xa8, 0xb4, 0x2b, 0x37, 0x36, 0x26, 0x4d, - 0xf8, 0x29, 0xc3, 0x4b, 0xe6, 0xfa, 0xcb, 0x70, 0x64, 0x60, 0x94, 0x13, 0x30, 0x2a, 0x50, 0x92, - 0x45, 0x8e, 0xd8, 0x9a, 0x98, 0xd6, 0x9f, 0xce, 0xa4, 0x53, 0x8e, 0xdb, 0xdc, 0x70, 0x5b, 0x19, - 0xf7, 0xfd, 0xec, 0xed, 0x64, 0xa6, 0x72, 0x9b, 0xca, 0xd5, 0x5e, 0x92, 0x6c, 0x9d, 0xe9, 0x3a, - 0x21, 0xb1, 0x1c, 0xea, 0x8b, 0xac, 0x98, 0x0c, 0xb0, 0x6d, 0x08, 0x2c, 0xc7, 0xa4, 0x9b, 0xd4, - 0x74, 0x9d, 0x66, 0xc0, 0xf7, 0x33, 0x6f, 0xa4, 0xc6, 0xf0, 0x4d, 0x98, 0xe3, 0xf4, 0x3d, 0xab, - 0x1d, 0xa5, 0x81, 0x72, 0x63, 0xa5, 0x16, 0xf5, 0xd0, 0x6a, 0x6a, 0x0f, 0x2d, 0xb1, 0x61, 0x9b, - 0x86, 0xa4, 0xd6, 0xbd, 0x50, 0x63, 0x2b, 0x8c, 0x64, 0x31, 0xc3, 0x12, 0x12, 0xcb, 0xde, 0xb0, - 0x1c, 0x5e, 0x78, 0x32, 0x51, 0xc9, 0x00, 0x73, 0x95, 0x2d, 0xd7, 0xb6, 0xdd, 0x47, 0xf2, 0xdc, - 0x44, 0x14, 0x5b, 0xd5, 0x71, 0x42, 0xcb, 0xe6, 0xf2, 0x23, 0x47, 0x48, 0x06, 0xf8, 0x2a, 0xcb, - 0x0e, 0xa9, 0x2f, 0x0e, 0x8c, 0xa0, 0x62, 0x67, 0x2c, 0x47, 0x6d, 0x21, 0x79, 0x5e, 0x23, 0xb7, - 0x9d, 0x57, 0xdd, 0xb6, 0xf7, 0x28, 0x2c, 0x0c, 0xe8, 0x8d, 0xf0, 0x2e, 0x19, 0xed, 0x5a, 0x6e, - 0x87, 0xd5, 0x54, 0xbc, 0xf4, 0x90, 0x74, 0x9f, 0x2b, 0xef, 0xcf, 0x76, 0xe5, 0x03, 0x69, 0x57, - 0xfe, 0x1d, 0x82, 0xd2, 0x86, 0xdb, 0xba, 0xe6, 0x84, 0xfe, 0x2e, 0xbf, 0x25, 0xb9, 0x4e, 0x48, - 0x1d, 0xe9, 0x2f, 0x92, 0x64, 0x9b, 0x10, 0x5a, 0x6d, 0xba, 0x19, 0x92, 0xb6, 0x27, 0x6a, 0xac, - 0x3d, 0x6d, 0x42, 0xbc, 0x98, 0x19, 0xc6, 0x26, 0x41, 0xc8, 0x4f, 0x7c, 0xc9, 0xe0, 0xdf, 0x4c, - 0x85, 0x78, 0xc2, 0x66, 0xe8, 0x8b, 0xe3, 0x9e, 0x1a, 0x53, 0x5d, 0xac, 0x10, 0x61, 0x13, 0xa4, - 0xde, 0x86, 0x17, 0xe3, 0xe2, 0xff, 0x1e, 0xf5, 0xdb, 0x96, 0x43, 0xb2, 0xa3, 0xf7, 0x18, 0xed, - 0xb9, 0x8c, 0xbb, 0xa7, 0x9b, 0x3a, 0x74, 0xac, 0x96, 0x7e, 0x60, 0x39, 0x4d, 0xf7, 0x51, 0xc6, - 0xe1, 0x99, 0x4c, 0xe0, 0x5f, 0xd2, 0x1d, 0x3a, 0x45, 0x62, 0x7c, 0xd2, 0x6f, 0xc2, 0x02, 0x8b, - 0x09, 0x5d, 0x2a, 0x7e, 0x10, 0x61, 0x47, 0x1f, 0xd6, 0x2c, 0x49, 0x78, 0x18, 0xe9, 0x85, 0x78, - 0x03, 0xf6, 0x93, 0x20, 0xb0, 0x5a, 0x0e, 0x6d, 0x4a, 0x5e, 0xb9, 0xb1, 0x79, 0xf5, 0x2e, 0x8d, - 0xae, 0xdd, 0x7c, 0x86, 0xd8, 0x6f, 0x49, 0xea, 0x5f, 0x43, 0x70, 0x78, 0x20, 0x93, 0xf8, 0xe4, - 0x20, 0x25, 0x8c, 0x57, 0xa0, 0x14, 0x98, 0xdb, 0xb4, 0xd9, 0xb1, 0xa9, 0xec, 0x45, 0x49, 0x9a, - 0xfd, 0xd6, 0xec, 0x44, 0xbb, 0x2f, 0xd2, 0x48, 0x4c, 0xe3, 0xa3, 0x00, 0x6d, 0xe2, 0x74, 0x88, - 0xcd, 0x21, 0xcc, 0x70, 0x08, 0xca, 0x88, 0xbe, 0x04, 0x95, 0x41, 0xae, 0x23, 0x7a, 0x3c, 0xff, - 0x42, 0xb0, 0x4f, 0x06, 0x55, 0xb1, 0xbb, 0xcb, 0xb0, 0x5f, 0x31, 0xc3, 0x9d, 0x64, 0xa3, 0x7b, - 0x87, 0x47, 0x04, 0x4c, 0xe9, 0x25, 0xf9, 0x74, 0x93, 0xbc, 0x9b, 0x6a, 0x73, 0x8f, 0x9d, 0xef, - 0xd0, 0x94, 0xea, 0xc7, 0xaf, 0x82, 0x76, 0x9b, 0x38, 0xa4, 0x45, 0x9b, 0xb1, 0xda, 0xb1, 0x8b, - 0x7d, 0x59, 0x6d, 0x56, 0x4c, 0xdc, 0x1a, 0x88, 0x4b, 0x2d, 0x6b, 0x6b, 0x4b, 0x36, 0x3e, 0x7c, - 0x28, 0x6d, 0x58, 0xce, 0x0e, 0xbb, 0x3f, 0x33, 0x8d, 0x43, 0x2b, 0xb4, 0xa5, 0x75, 0x23, 0x02, - 0x1f, 0x80, 0x7c, 0xc7, 0xb7, 0x85, 0x07, 0xb0, 0x4f, 0x5c, 0x85, 0x72, 0x93, 0x06, 0xa6, 0x6f, - 0x79, 0x62, 0xff, 0x79, 0xd3, 0x58, 0x19, 0x62, 0xfb, 0x60, 0x99, 0xae, 0xb3, 0x6e, 0x93, 0x20, - 0x90, 0x09, 0x28, 0x1e, 0xd0, 0x5f, 0x85, 0x05, 0x26, 0x33, 0x51, 0xf3, 0x6c, 0x5a, 0xcd, 0xc3, - 0x29, 0xf8, 0x12, 0x9e, 0x44, 0x4c, 0xe0, 0x05, 0x96, 0xf7, 0x2f, 0x7b, 0x9e, 0x60, 0x32, 0x66, - 0x39, 0x94, 0x1f, 0x94, 0x3f, 0x07, 0xf6, 0x4a, 0x1b, 0x7f, 0x3b, 0x0e, 0x58, 0x3d, 0x27, 0xd4, - 0xef, 0x5a, 0x26, 0xc5, 0xdf, 0x46, 0x30, 0xc3, 0x44, 0xe3, 0x97, 0x86, 0x1d, 0x4b, 0xee, 0xaf, - 0x95, 0xe9, 0x5d, 0x84, 0x99, 0x34, 0x7d, 0xe9, 0xad, 0xbf, 0xfe, 0xe3, 0x3b, 0xb9, 0x45, 0x7c, - 0x88, 0xbf, 0x70, 0x75, 0x2f, 0xa8, 0xaf, 0x4d, 0x01, 0x7e, 0x1b, 0x01, 0x16, 0x75, 0x90, 0xf2, - 0x86, 0x80, 0xcf, 0x0e, 0x83, 0x38, 0xe0, 0xad, 0xa1, 0xf2, 0x92, 0x92, 0x55, 0x6a, 0xa6, 0xeb, - 0x53, 0x96, 0x43, 0xf8, 0x04, 0x0e, 0x60, 0x85, 0x03, 0x38, 0x81, 0xf5, 0x41, 0x00, 0xea, 0x8f, - 0x99, 0x45, 0x9f, 0xd4, 0x69, 0x24, 0xf7, 0x5d, 0x04, 0x85, 0x07, 0xfc, 0x0e, 0x31, 0xc2, 0x48, - 0x9b, 0x53, 0x33, 0x12, 0x17, 0xc7, 0xd1, 0xea, 0xc7, 0x39, 0xd2, 0x97, 0xf0, 0x11, 0x89, 0x34, - 0x08, 0x7d, 0x4a, 0xda, 0x29, 0xc0, 0xe7, 0x11, 0x7e, 0x0f, 0x41, 0x31, 0x6a, 0x1e, 0xe3, 0x93, - 0xc3, 0x50, 0xa6, 0x9a, 0xcb, 0x95, 0xe9, 0x75, 0x62, 0xf5, 0x33, 0x1c, 0xe3, 0x71, 0x7d, 0xe0, - 0x76, 0xae, 0xa5, 0xfa, 0xb4, 0xef, 0x20, 0xc8, 0xdf, 0xa0, 0x23, 0xfd, 0x6d, 0x8a, 0xe0, 0xfa, - 0x0c, 0x38, 0x60, 0xab, 0xf1, 0x8f, 0x11, 0xbc, 0x78, 0x83, 0x86, 0x83, 0xd3, 0x23, 0x5e, 0x1e, - 0x9d, 0xb3, 0x84, 0xdb, 0x9d, 0x1d, 0x63, 0x66, 0x9c, 0x17, 0xea, 0x1c, 0xd9, 0x19, 0x7c, 0x3a, - 0xcb, 0x09, 0x83, 0x5d, 0xc7, 0x7c, 0x24, 0x70, 0xfc, 0x09, 0xc1, 0x81, 0xde, 0xb7, 0x3e, 0x9c, - 0x4e, 0xa8, 0x03, 0x9f, 0x02, 0x2b, 0x77, 0x26, 0x8d, 0xb2, 0x69, 0xa6, 0xfa, 0x65, 0x8e, 0xfc, - 0x15, 0xfc, 0x72, 0x16, 0xf2, 0xb8, 0x13, 0x57, 0x7f, 0x2c, 0x3f, 0x9f, 0xf0, 0x77, 0x69, 0x0e, - 0xfb, 0xcf, 0x08, 0x0e, 0x49, 0xbe, 0xeb, 0xdb, 0xc4, 0x0f, 0xaf, 0x52, 0x56, 0x43, 0x07, 0x63, - 0xe9, 0x33, 0x61, 0xd6, 0x50, 0xe5, 0xe9, 0xd7, 0xb8, 0x2e, 0x9f, 0xc2, 0xaf, 0xed, 0x59, 0x17, - 0x93, 0xb1, 0x69, 0x0a, 0xd8, 0x6f, 0x21, 0x98, 0xbf, 0x41, 0xc3, 0xdb, 0x71, 0x37, 0xf8, 0xe4, - 0x58, 0x2f, 0x4c, 0x95, 0xa5, 0x9a, 0xf2, 0x1c, 0x2e, 0x7f, 0x8a, 0x5d, 0x64, 0x95, 0x83, 0x3b, - 0x8d, 0x4f, 0x66, 0x81, 0x4b, 0x3a, 0xd0, 0xef, 0x22, 0x38, 0xac, 0x82, 0x48, 0x5e, 0xe6, 0x3e, - 0xb1, 0xb7, 0xf7, 0x2e, 0xf1, 0x6a, 0x36, 0x02, 0x5d, 0x83, 0xa3, 0x3b, 0xa7, 0x0f, 0x76, 0xe0, - 0x76, 0x1f, 0x8a, 0x35, 0xb4, 0xb2, 0x8c, 0xf0, 0xef, 0x11, 0x14, 0xa3, 0x66, 0xec, 0x70, 0x1b, - 0xa5, 0x5e, 0x92, 0xa6, 0x19, 0x0d, 0xc4, 0x6e, 0x57, 0xce, 0x0f, 0x36, 0xa8, 0xba, 0x5e, 0xba, - 0x6a, 0x8d, 0x5b, 0x39, 0x1d, 0xc6, 0x7e, 0x85, 0x00, 0x92, 0x86, 0x32, 0x3e, 0x93, 0xad, 0x87, - 0xd2, 0x74, 0xae, 0x4c, 0xb7, 0xa5, 0xac, 0xd7, 0xb8, 0x3e, 0xcb, 0x95, 0x6a, 0x66, 0x0c, 0xf1, - 0xa8, 0xb9, 0x16, 0x35, 0x9f, 0x7f, 0x84, 0xa0, 0xc0, 0xfb, 0x78, 0xf8, 0xc4, 0x30, 0xcc, 0x6a, - 0x9b, 0x6f, 0x9a, 0xa6, 0x3f, 0xc5, 0xa1, 0x56, 0x1b, 0x59, 0x81, 0x78, 0x0d, 0xad, 0xe0, 0x2e, - 0x14, 0xa3, 0xce, 0xd9, 0x70, 0xf7, 0x48, 0x75, 0xd6, 0x2a, 0xd5, 0x8c, 0xc2, 0x20, 0x72, 0x54, - 0x91, 0x03, 0x56, 0x46, 0xe5, 0x80, 0x19, 0x16, 0xa6, 0xf1, 0xf1, 0xac, 0x20, 0xfe, 0x7f, 0x30, - 0xcc, 0x59, 0x8e, 0xee, 0xa4, 0x5e, 0x1d, 0x95, 0x07, 0x98, 0x75, 0xbe, 0x8b, 0xe0, 0x40, 0x6f, - 0x71, 0x8d, 0x8f, 0xf4, 0xc4, 0x4c, 0xf5, 0xae, 0x51, 0x49, 0x5b, 0x71, 0x58, 0x61, 0xae, 0x7f, - 0x9a, 0xa3, 0x58, 0xc3, 0x97, 0x46, 0x9e, 0x8c, 0x3b, 0x32, 0xea, 0x30, 0x46, 0xab, 0xc9, 0xeb, - 0xd8, 0xaf, 0x11, 0xcc, 0x4b, 0xbe, 0xf7, 0x7c, 0x4a, 0xb3, 0x61, 0x4d, 0xef, 0x20, 0x30, 0x59, - 0xfa, 0xab, 0x1c, 0xfe, 0x27, 0xf1, 0xc5, 0x31, 0xe1, 0x4b, 0xd8, 0xab, 0x21, 0x43, 0xfa, 0x07, - 0x04, 0x07, 0x1f, 0x44, 0x7e, 0xff, 0x21, 0xe1, 0x5f, 0xe7, 0xf8, 0x5f, 0xc3, 0xaf, 0x64, 0xd4, - 0x79, 0xa3, 0xd4, 0x38, 0x8f, 0xf0, 0x2f, 0x10, 0x94, 0xe4, 0xab, 0x0a, 0x3e, 0x3d, 0xf4, 0x60, - 0xa4, 0xdf, 0x5d, 0xa6, 0xe9, 0xcc, 0xa2, 0xa8, 0xd1, 0x4f, 0x64, 0xa6, 0x53, 0x21, 0x9f, 0x39, - 0xf4, 0x3b, 0x08, 0x70, 0x7c, 0x67, 0x8e, 0x6f, 0xd1, 0xf8, 0x54, 0x4a, 0xd4, 0xd0, 0xc6, 0x4c, - 0xe5, 0xf4, 0xc8, 0x79, 0xe9, 0x54, 0xba, 0x92, 0x99, 0x4a, 0xdd, 0x58, 0xfe, 0x37, 0x10, 0x94, - 0x6f, 0xd0, 0xf8, 0x0e, 0x92, 0x61, 0xcb, 0xf4, 0xa3, 0x50, 0x65, 0x79, 0xf4, 0x44, 0x81, 0xe8, - 0x1c, 0x47, 0x74, 0x0a, 0x67, 0x9b, 0x4a, 0x02, 0xf8, 0x3e, 0x82, 0x85, 0xbb, 0xaa, 0x8b, 0xe2, - 0x73, 0xa3, 0x24, 0xa5, 0x22, 0xf9, 0xf8, 0xb8, 0x3e, 0xce, 0x71, 0xad, 0xea, 0x63, 0xe1, 0x5a, - 0x13, 0xef, 0x2b, 0x3f, 0x40, 0xd1, 0x25, 0xb6, 0xa7, 0x9f, 0xfd, 0xbf, 0xda, 0x2d, 0xa3, 0x2d, - 0xae, 0x5f, 0xe4, 0xf8, 0x6a, 0xf8, 0xdc, 0x38, 0xf8, 0xea, 0xa2, 0xc9, 0x8d, 0xbf, 0x87, 0xe0, - 0x20, 0x7f, 0x6b, 0x50, 0x19, 0xf7, 0xa4, 0x98, 0x61, 0x2f, 0x13, 0x63, 0xa4, 0x18, 0x11, 0x7f, - 0xf4, 0x3d, 0x81, 0x5a, 0x93, 0xef, 0x08, 0xdf, 0x44, 0xb0, 0x4f, 0x26, 0x35, 0xb1, 0xbb, 0xab, - 0xa3, 0x0c, 0xb7, 0xd7, 0x24, 0x28, 0xdc, 0x6d, 0x65, 0x3c, 0x77, 0x7b, 0x0f, 0xc1, 0xac, 0xe8, - 0xe6, 0x67, 0x94, 0x0a, 0x4a, 0xbb, 0xbf, 0xd2, 0xd3, 0xe3, 0x10, 0xcd, 0x60, 0xfd, 0x8b, 0x5c, - 0xec, 0x7d, 0x5c, 0xcf, 0x12, 0xeb, 0xb9, 0xcd, 0xa0, 0xfe, 0x58, 0x74, 0x62, 0x9f, 0xd4, 0x6d, - 0xb7, 0x15, 0xbc, 0xa1, 0xe3, 0xcc, 0x84, 0xc8, 0xe6, 0x9c, 0x47, 0x38, 0x84, 0x39, 0xe6, 0x1c, - 0xbc, 0x71, 0x82, 0xab, 0x3d, 0x6d, 0x96, 0xbe, 0x9e, 0x4a, 0xa5, 0xd2, 0xd7, 0x88, 0x49, 0x32, - 0xa0, 0xb8, 0xc6, 0xe2, 0x63, 0x99, 0x62, 0xb9, 0xa0, 0xb7, 0x11, 0x1c, 0x54, 0xbd, 0x3d, 0x12, - 0x3f, 0xb6, 0xaf, 0x67, 0xa1, 0x10, 0x45, 0x35, 0x5e, 0x19, 0xcb, 0x91, 0x38, 0x9c, 0x2b, 0xd7, - 0xff, 0xf8, 0xec, 0x28, 0x7a, 0xff, 0xd9, 0x51, 0xf4, 0xf7, 0x67, 0x47, 0xd1, 0x1b, 0x97, 0xc6, - 0xfb, 0x8f, 0xaf, 0x69, 0x5b, 0xd4, 0x09, 0x55, 0xf6, 0xff, 0x0d, 0x00, 0x00, 0xff, 0xff, 0x57, - 0x9a, 0x85, 0xd1, 0xc9, 0x2c, 0x00, 0x00, + 0x15, 0xa6, 0xec, 0xf1, 0x8c, 0xe7, 0x79, 0x26, 0x93, 0xd4, 0x26, 0x83, 0xd7, 0x99, 0x0d, 0xde, + 0x4e, 0xb2, 0x71, 0x26, 0x19, 0x3b, 0x31, 0x01, 0x65, 0x67, 0x77, 0x05, 0xc9, 0xe4, 0x17, 0x26, + 0xd9, 0xd0, 0x93, 0x10, 0xb4, 0x1c, 0xa0, 0xb6, 0xbb, 0xc6, 0xd3, 0x4c, 0xbb, 0xbb, 0xd3, 0xdd, + 0x76, 0x18, 0x85, 0x5c, 0x16, 0xed, 0x05, 0xad, 0x40, 0xc0, 0x1e, 0x10, 0x42, 0x80, 0x16, 0xad, + 0x84, 0x10, 0x88, 0x0b, 0x42, 0x48, 0x08, 0x09, 0x0e, 0x20, 0x38, 0x20, 0xad, 0xe0, 0xc8, 0x05, + 0x45, 0x88, 0x23, 0x5c, 0xf6, 0x8c, 0x50, 0x55, 0x57, 0x75, 0x57, 0xfb, 0xa7, 0xed, 0xc1, 0x46, + 0x9b, 0x5b, 0xbf, 0x72, 0xd5, 0x7b, 0xdf, 0x7b, 0xf5, 0xea, 0xbd, 0x57, 0xaf, 0x0c, 0x27, 0x02, + 0xea, 0x77, 0xa9, 0xdf, 0x20, 0x9e, 0x67, 0x5b, 0x06, 0x09, 0x2d, 0xd7, 0x51, 0xbf, 0xeb, 0x9e, + 0xef, 0x86, 0x2e, 0x2e, 0x29, 0x43, 0x95, 0x95, 0x96, 0xeb, 0xb6, 0x6c, 0xda, 0x20, 0x9e, 0xd5, + 0x20, 0x8e, 0xe3, 0x86, 0x7c, 0x38, 0x88, 0xa6, 0x56, 0xb4, 0xdd, 0x8b, 0x41, 0xdd, 0x72, 0xf9, + 0xaf, 0x86, 0xeb, 0xd3, 0x46, 0xf7, 0x7c, 0xa3, 0x45, 0x1d, 0xea, 0x93, 0x90, 0x9a, 0x62, 0xce, + 0x85, 0x64, 0x4e, 0x9b, 0x18, 0x3b, 0x96, 0x43, 0xfd, 0xbd, 0x86, 0xb7, 0xdb, 0x62, 0x03, 0x41, + 0xa3, 0x4d, 0x43, 0x32, 0x68, 0xd5, 0x66, 0xcb, 0x0a, 0x77, 0x3a, 0xaf, 0xd7, 0x0d, 0xb7, 0xdd, + 0x20, 0x7e, 0xcb, 0xf5, 0x7c, 0xf7, 0x4b, 0xfc, 0x63, 0xcd, 0x30, 0x1b, 0xdd, 0x66, 0xc2, 0x40, + 0xd5, 0xa5, 0x7b, 0x9e, 0xd8, 0xde, 0x0e, 0xe9, 0xe7, 0x76, 0x75, 0x04, 0x37, 0x9f, 0x7a, 0xae, + 0xb0, 0x0d, 0xff, 0xb4, 0x42, 0xd7, 0xdf, 0x53, 0x3e, 0x23, 0x36, 0xda, 0xfb, 0x08, 0x0e, 0x5e, + 0x4a, 0xe4, 0x7d, 0xa6, 0x43, 0xfd, 0x3d, 0x8c, 0x61, 0xc6, 0x21, 0x6d, 0x5a, 0x46, 0x55, 0x54, + 0x9b, 0xd7, 0xf9, 0x37, 0x2e, 0xc3, 0x9c, 0x4f, 0xb7, 0x7d, 0x1a, 0xec, 0x94, 0x73, 0x7c, 0x58, + 0x92, 0xb8, 0x02, 0x45, 0x26, 0x9c, 0x1a, 0x61, 0x50, 0xce, 0x57, 0xf3, 0xb5, 0x79, 0x3d, 0xa6, + 0x71, 0x0d, 0x96, 0x7c, 0x1a, 0xb8, 0x1d, 0xdf, 0xa0, 0x9f, 0xa5, 0x7e, 0x60, 0xb9, 0x4e, 0x79, + 0x86, 0xaf, 0xee, 0x1d, 0x66, 0x5c, 0x02, 0x6a, 0x53, 0x23, 0x74, 0xfd, 0x72, 0x81, 0x4f, 0x89, + 0x69, 0x86, 0x87, 0x01, 0x2f, 0xcf, 0x46, 0x78, 0xd8, 0x37, 0xd6, 0x60, 0x81, 0x78, 0xde, 0x6d, + 0xd2, 0xa6, 0x81, 0x47, 0x0c, 0x5a, 0x9e, 0xe3, 0xbf, 0xa5, 0xc6, 0x18, 0x66, 0x81, 0xa4, 0x5c, + 0xe4, 0xc0, 0x24, 0xa9, 0x6d, 0xc0, 0xfc, 0x6d, 0xd7, 0xa4, 0xc3, 0xd5, 0xed, 0x65, 0x9f, 0xeb, + 0x67, 0xaf, 0xfd, 0x1e, 0xc1, 0x11, 0x9d, 0x76, 0x2d, 0x86, 0xff, 0x16, 0x0d, 0x89, 0x49, 0x42, + 0xd2, 0xcb, 0x31, 0x17, 0x73, 0xac, 0x40, 0xd1, 0x17, 0x93, 0xcb, 0x39, 0x3e, 0x1e, 0xd3, 0x7d, + 0xd2, 0xf2, 0xd9, 0xca, 0x44, 0x26, 0x94, 0x24, 0xae, 0x42, 0x29, 0xb2, 0xe5, 0x4d, 0xc7, 0xa4, + 0x5f, 0xe6, 0xd6, 0x2b, 0xe8, 0xea, 0x10, 0x5e, 0x81, 0xf9, 0x6e, 0x64, 0xe7, 0x9b, 0x26, 0xb7, + 0x62, 0x41, 0x4f, 0x06, 0xb4, 0x7f, 0x22, 0x38, 0xa6, 0xf8, 0x80, 0x2e, 0x76, 0xe6, 0x6a, 0x97, + 0x3a, 0x61, 0x30, 0x5c, 0xa1, 0xb3, 0x70, 0x48, 0x6e, 0x62, 0xaf, 0x9d, 0xfa, 0x7f, 0x60, 0x2a, + 0xaa, 0x83, 0x52, 0x45, 0x75, 0x8c, 0x29, 0x22, 0xe9, 0x7b, 0x37, 0xaf, 0x08, 0x35, 0xd5, 0xa1, + 0x3e, 0x43, 0x15, 0xb2, 0x0d, 0x35, 0x9b, 0x32, 0x94, 0xf6, 0x1e, 0x82, 0xb2, 0xa2, 0xe8, 0x2d, + 0xe2, 0x58, 0xdb, 0x34, 0x08, 0xc7, 0xdd, 0x33, 0x34, 0xc5, 0x3d, 0xab, 0xc1, 0x52, 0xa4, 0xd5, + 0x1d, 0x76, 0x1e, 0x59, 0xfc, 0x29, 0x17, 0xaa, 0xf9, 0x5a, 0x5e, 0xef, 0x1d, 0x66, 0x7b, 0x27, + 0x65, 0x06, 0xe5, 0x59, 0xee, 0xc6, 0xc9, 0x80, 0xf6, 0x3c, 0xcc, 0x5f, 0xb3, 0x6c, 0xba, 0xb1, + 0xd3, 0x71, 0x76, 0xf1, 0x61, 0x28, 0x18, 0xec, 0x83, 0xeb, 0xb0, 0xa0, 0x47, 0x84, 0xf6, 0x4d, + 0x04, 0xcf, 0x0f, 0xd3, 0xfa, 0xbe, 0x15, 0xee, 0xb0, 0xf5, 0xc1, 0x30, 0xf5, 0x8d, 0x1d, 0x6a, + 0xec, 0x06, 0x9d, 0xb6, 0x74, 0x59, 0x49, 0x4f, 0xa6, 0xbe, 0xf6, 0x13, 0x04, 0xb5, 0x91, 0x98, + 0xee, 0xfb, 0xc4, 0xf3, 0xa8, 0x8f, 0xaf, 0x41, 0xe1, 0x01, 0xfb, 0x81, 0x1f, 0xd0, 0x52, 0xb3, + 0x5e, 0x57, 0x03, 0xfc, 0x48, 0x2e, 0x37, 0x3e, 0xa4, 0x47, 0xcb, 0x71, 0x5d, 0x9a, 0x27, 0xc7, + 0xf9, 0x2c, 0xa7, 0xf8, 0xc4, 0x56, 0x64, 0xf3, 0xf9, 0xb4, 0xcb, 0xb3, 0x30, 0xe3, 0x11, 0x3f, + 0xd4, 0x8e, 0xc0, 0x33, 0xe9, 0xe3, 0xe1, 0xb9, 0x4e, 0x40, 0xb5, 0x5f, 0xa7, 0xbd, 0x69, 0xc3, + 0xa7, 0x24, 0xa4, 0x3a, 0x7d, 0xd0, 0xa1, 0x41, 0x88, 0x77, 0x41, 0xcd, 0x39, 0xdc, 0xaa, 0xa5, + 0xe6, 0xcd, 0x7a, 0x12, 0xb4, 0xeb, 0x32, 0x68, 0xf3, 0x8f, 0x2f, 0x18, 0x66, 0xbd, 0xdb, 0xac, + 0x7b, 0xbb, 0xad, 0x3a, 0x4b, 0x01, 0x29, 0x64, 0x32, 0x05, 0xa8, 0xaa, 0xea, 0x2a, 0x77, 0xbc, + 0x0c, 0xb3, 0x1d, 0x2f, 0xa0, 0x7e, 0xc8, 0x35, 0x2b, 0xea, 0x82, 0x62, 0xfb, 0xd7, 0x25, 0xb6, + 0x65, 0x92, 0x30, 0xda, 0x9f, 0xa2, 0x1e, 0xd3, 0xda, 0x6f, 0xd2, 0xe8, 0xef, 0x79, 0xe6, 0x07, + 0x85, 0x5e, 0x45, 0x99, 0x4b, 0xa3, 0x54, 0x3d, 0x28, 0x9f, 0xf6, 0xa0, 0x5f, 0xa4, 0xf1, 0x5f, + 0xa1, 0x36, 0x4d, 0xf0, 0x0f, 0x72, 0xe6, 0x32, 0xcc, 0x19, 0x24, 0x30, 0x88, 0x29, 0xa5, 0x48, + 0x92, 0x05, 0x32, 0xcf, 0x77, 0x3d, 0xd2, 0xe2, 0x9c, 0xee, 0xb8, 0xb6, 0x65, 0xec, 0x09, 0x71, + 0xfd, 0x3f, 0xf4, 0x39, 0xfe, 0x4c, 0xb6, 0xe3, 0x17, 0xd2, 0xb0, 0x8f, 0x43, 0x69, 0x6b, 0xcf, + 0x31, 0x5e, 0xf5, 0xa2, 0xc3, 0x7d, 0x18, 0x0a, 0x56, 0x48, 0xdb, 0x41, 0x19, 0xf1, 0x83, 0x1d, + 0x11, 0xda, 0x7f, 0x0a, 0xb0, 0xac, 0xe8, 0xc6, 0x16, 0x64, 0x69, 0x96, 0x15, 0xa5, 0x96, 0x61, + 0xd6, 0xf4, 0xf7, 0xf4, 0x8e, 0x23, 0x1c, 0x40, 0x50, 0x4c, 0xb0, 0xe7, 0x77, 0x9c, 0x08, 0x7e, + 0x51, 0x8f, 0x08, 0xbc, 0x0d, 0xc5, 0x20, 0x64, 0x55, 0x46, 0x6b, 0x8f, 0x03, 0x2f, 0x35, 0x3f, + 0x35, 0xd9, 0xa6, 0x33, 0xe8, 0x5b, 0x82, 0xa3, 0x1e, 0xf3, 0xc6, 0x0f, 0x58, 0x4c, 0x8b, 0x02, + 0x5d, 0x50, 0x9e, 0xab, 0xe6, 0x6b, 0xa5, 0xe6, 0xd6, 0xe4, 0x82, 0x5e, 0xf5, 0x58, 0x85, 0xa4, + 0x64, 0x30, 0x3d, 0x91, 0xc2, 0xc2, 0x68, 0x5b, 0xc4, 0x87, 0x40, 0x54, 0x03, 0xc9, 0x00, 0xfe, + 0x1c, 0x14, 0x2c, 0x67, 0xdb, 0x0d, 0xca, 0xf3, 0x1c, 0xcc, 0xe5, 0xc9, 0xc0, 0xdc, 0x74, 0xb6, + 0x5d, 0x3d, 0x62, 0x88, 0x1f, 0xc0, 0xa2, 0x4f, 0x43, 0x7f, 0x4f, 0x5a, 0xa1, 0x0c, 0xdc, 0xae, + 0x9f, 0x9e, 0x4c, 0x82, 0xae, 0xb2, 0xd4, 0xd3, 0x12, 0xf0, 0x3a, 0x94, 0x82, 0xc4, 0xc7, 0xca, + 0x25, 0x2e, 0xb0, 0x9c, 0x62, 0xa4, 0xf8, 0xa0, 0xae, 0x4e, 0xee, 0xf3, 0xee, 0x85, 0x6c, 0xef, + 0x5e, 0x1c, 0x99, 0xd5, 0x0e, 0x8c, 0x91, 0xd5, 0x96, 0x7a, 0xb3, 0xda, 0xbf, 0x11, 0xac, 0xf4, + 0x05, 0xa7, 0x2d, 0x8f, 0x66, 0x1e, 0x03, 0x02, 0x33, 0x81, 0x47, 0x0d, 0x9e, 0xa9, 0x4a, 0xcd, + 0x5b, 0x53, 0x8b, 0x56, 0x5c, 0x2e, 0x67, 0x9d, 0x15, 0x50, 0x27, 0x8c, 0x0b, 0x3f, 0x40, 0xf0, + 0x61, 0x45, 0xe6, 0x1d, 0x12, 0x1a, 0x3b, 0x59, 0xca, 0xb2, 0xf3, 0xcb, 0xe6, 0x88, 0xbc, 0x1c, + 0x11, 0xcc, 0xaa, 0xfc, 0xe3, 0xee, 0x9e, 0xc7, 0x00, 0xb2, 0x5f, 0x92, 0x81, 0x09, 0x8b, 0xa7, + 0x9f, 0x22, 0xa8, 0xa8, 0x31, 0xdc, 0xb5, 0xed, 0xd7, 0x89, 0xb1, 0x9b, 0x05, 0xf2, 0x00, 0xe4, + 0x2c, 0x93, 0x23, 0xcc, 0xeb, 0x39, 0xcb, 0xdc, 0x67, 0x30, 0xea, 0x85, 0x3b, 0x9b, 0x0d, 0x77, + 0x2e, 0x0d, 0xf7, 0xfd, 0x1e, 0xb8, 0x32, 0x24, 0x64, 0xc0, 0x5d, 0x81, 0x79, 0xa7, 0xa7, 0x90, + 0x4d, 0x06, 0x06, 0x14, 0xb0, 0xb9, 0xbe, 0x02, 0xb6, 0x0c, 0x73, 0xdd, 0xf8, 0x9a, 0xc3, 0x7e, + 0x96, 0x24, 0x53, 0xb1, 0xe5, 0xbb, 0x1d, 0x4f, 0x18, 0x3d, 0x22, 0x18, 0x8a, 0x5d, 0xcb, 0x61, + 0x25, 0x39, 0x47, 0xc1, 0xbe, 0xf7, 0x7f, 0xb1, 0x49, 0xa9, 0xfd, 0xb3, 0x1c, 0x7c, 0x64, 0x80, + 0xda, 0x23, 0xfd, 0xe9, 0xe9, 0xd0, 0x3d, 0xf6, 0xea, 0xb9, 0xa1, 0x5e, 0x5d, 0x1c, 0xe5, 0xd5, + 0xf3, 0xd9, 0xf6, 0x82, 0xb4, 0xbd, 0x7e, 0x9c, 0x83, 0xea, 0x00, 0x7b, 0x8d, 0x2e, 0x27, 0x9e, + 0x1a, 0x83, 0x6d, 0xbb, 0xbe, 0xf0, 0x92, 0xa2, 0x1e, 0x11, 0xec, 0x9c, 0xb9, 0xbe, 0xb7, 0x43, + 0x1c, 0xee, 0x1d, 0x45, 0x5d, 0x50, 0x13, 0x9a, 0xea, 0x6b, 0x39, 0x28, 0x4b, 0xfb, 0x5c, 0x32, + 0xb8, 0xb5, 0x3a, 0xce, 0xd3, 0x6f, 0xa2, 0x65, 0x98, 0x25, 0x1c, 0xad, 0x70, 0x2a, 0x41, 0xf5, + 0x19, 0xa3, 0x98, 0x6d, 0x8c, 0xf9, 0xb4, 0x31, 0xde, 0x44, 0x70, 0x34, 0x6d, 0x8c, 0x60, 0xd3, + 0x0a, 0x42, 0x79, 0x39, 0xc0, 0xdb, 0x30, 0x17, 0xc9, 0x89, 0x4a, 0xbb, 0x52, 0x73, 0x73, 0xd2, + 0x84, 0x9f, 0x32, 0xbc, 0x64, 0xae, 0xbd, 0x08, 0x47, 0x07, 0x46, 0x39, 0x01, 0xa3, 0x02, 0x45, + 0x59, 0xe4, 0x88, 0xad, 0x89, 0x69, 0xed, 0xcd, 0x99, 0x74, 0xca, 0x71, 0xcd, 0x4d, 0xb7, 0x95, + 0x71, 0xdf, 0xcf, 0xde, 0x4e, 0x66, 0x2a, 0xd7, 0x54, 0xae, 0xf6, 0x92, 0x64, 0xeb, 0x0c, 0xd7, + 0x09, 0x89, 0xe5, 0x50, 0x5f, 0x64, 0xc5, 0x64, 0x80, 0x6d, 0x43, 0x60, 0x39, 0x06, 0xdd, 0xa2, + 0x86, 0xeb, 0x98, 0x01, 0xdf, 0xcf, 0xbc, 0x9e, 0x1a, 0xc3, 0x37, 0x60, 0x9e, 0xd3, 0x77, 0xad, + 0x76, 0x94, 0x06, 0x4a, 0xcd, 0xd5, 0x7a, 0xd4, 0x83, 0xab, 0xab, 0x3d, 0xb8, 0xc4, 0x86, 0x6d, + 0x1a, 0x92, 0x7a, 0xf7, 0x7c, 0x9d, 0xad, 0xd0, 0x93, 0xc5, 0x0c, 0x4b, 0x48, 0x2c, 0x7b, 0xd3, + 0x72, 0x78, 0xe1, 0xc9, 0x44, 0x25, 0x03, 0xcc, 0x55, 0xb6, 0x5d, 0xdb, 0x76, 0x1f, 0xca, 0x73, + 0x13, 0x51, 0x6c, 0x55, 0xc7, 0x09, 0x2d, 0x9b, 0xcb, 0x8f, 0x1c, 0x21, 0x19, 0xe0, 0xab, 0x2c, + 0x3b, 0xa4, 0xbe, 0x38, 0x30, 0x82, 0x8a, 0x9d, 0xb1, 0x14, 0xb5, 0x95, 0xe4, 0x79, 0x8d, 0xdc, + 0x76, 0x41, 0x75, 0xdb, 0xde, 0xa3, 0xb0, 0x38, 0xa0, 0x37, 0xc2, 0xbb, 0x6c, 0xb4, 0x6b, 0xb9, + 0x1d, 0x56, 0x53, 0xf1, 0xd2, 0x43, 0xd2, 0x7d, 0xae, 0xbc, 0x94, 0xed, 0xca, 0x07, 0xd3, 0xae, + 0xfc, 0x5b, 0x04, 0xc5, 0x4d, 0xb7, 0x75, 0xd5, 0x09, 0xfd, 0x3d, 0x7e, 0x4b, 0x72, 0x9d, 0x90, + 0x3a, 0xd2, 0x5f, 0x24, 0xc9, 0x36, 0x21, 0xb4, 0xda, 0x74, 0x2b, 0x24, 0x6d, 0x4f, 0xd4, 0x58, + 0xfb, 0xda, 0x84, 0x78, 0x31, 0x33, 0x8c, 0x4d, 0x82, 0x90, 0x9f, 0xf8, 0xa2, 0xce, 0xbf, 0x99, + 0x0a, 0xf1, 0x84, 0xad, 0xd0, 0x17, 0xc7, 0x3d, 0x35, 0xa6, 0xba, 0x58, 0x21, 0xc2, 0x26, 0x48, + 0xad, 0x0d, 0xcf, 0xc6, 0xc5, 0xff, 0x5d, 0xea, 0xb7, 0x2d, 0x87, 0x64, 0x47, 0xef, 0x31, 0xda, + 0x7b, 0x19, 0x77, 0x4f, 0x37, 0x75, 0xe8, 0x58, 0x2d, 0x7d, 0xdf, 0x72, 0x4c, 0xf7, 0x61, 0xc6, + 0xe1, 0x99, 0x4c, 0xe0, 0x5f, 0xd2, 0x1d, 0x3a, 0x45, 0x62, 0x7c, 0xd2, 0x6f, 0xc0, 0x22, 0x8b, + 0x09, 0x5d, 0x2a, 0x7e, 0x10, 0x61, 0x47, 0x1b, 0xd6, 0x2c, 0x49, 0x78, 0xe8, 0xe9, 0x85, 0x78, + 0x13, 0x96, 0x48, 0x10, 0x58, 0x2d, 0x87, 0x9a, 0x92, 0x57, 0x6e, 0x6c, 0x5e, 0xbd, 0x4b, 0xa3, + 0x6b, 0x37, 0x9f, 0x21, 0xf6, 0x5b, 0x92, 0xda, 0x57, 0x11, 0x1c, 0x19, 0xc8, 0x24, 0x3e, 0x39, + 0x48, 0x09, 0xe3, 0x15, 0x28, 0x06, 0xc6, 0x0e, 0x35, 0x3b, 0x36, 0x95, 0xbd, 0x28, 0x49, 0xb3, + 0xdf, 0xcc, 0x4e, 0xb4, 0xfb, 0x22, 0x8d, 0xc4, 0x34, 0x3e, 0x06, 0xd0, 0x26, 0x4e, 0x87, 0xd8, + 0x1c, 0xc2, 0x0c, 0x87, 0xa0, 0x8c, 0x68, 0x2b, 0x50, 0x19, 0xe4, 0x3a, 0xa2, 0xc7, 0xf3, 0x2f, + 0x04, 0x07, 0x64, 0x50, 0x15, 0xbb, 0x5b, 0x83, 0x25, 0xc5, 0x0c, 0xb7, 0x93, 0x8d, 0xee, 0x1d, + 0x1e, 0x11, 0x30, 0xa5, 0x97, 0xe4, 0xd3, 0x4d, 0xf6, 0x6e, 0xaa, 0x4d, 0x3e, 0x76, 0xbe, 0x43, + 0x53, 0xaa, 0x1f, 0xbf, 0x02, 0xe5, 0x5b, 0xc4, 0x21, 0x2d, 0x6a, 0xc6, 0x6a, 0xc7, 0x2e, 0xf6, + 0x45, 0xb5, 0x59, 0x31, 0x71, 0x6b, 0x20, 0x2e, 0xb5, 0xac, 0xed, 0x6d, 0xd9, 0xf8, 0xf0, 0xa1, + 0xb8, 0x69, 0x39, 0xbb, 0xec, 0xfe, 0xcc, 0x34, 0x0e, 0xad, 0xd0, 0x96, 0xd6, 0x8d, 0x08, 0x7c, + 0x10, 0xf2, 0x1d, 0xdf, 0x16, 0x1e, 0xc0, 0x3e, 0x71, 0x15, 0x4a, 0x26, 0x0d, 0x0c, 0xdf, 0xf2, + 0xc4, 0xfe, 0xf3, 0xa6, 0xb1, 0x32, 0xc4, 0xf6, 0xc1, 0x32, 0x5c, 0x67, 0xc3, 0x26, 0x41, 0x20, + 0x13, 0x50, 0x3c, 0xa0, 0xbd, 0x0c, 0x8b, 0x4c, 0x66, 0xa2, 0xe6, 0x99, 0xb4, 0x9a, 0x47, 0x52, + 0xf0, 0x25, 0x3c, 0x89, 0x98, 0xc0, 0x33, 0x2c, 0xef, 0x5f, 0xf2, 0x3c, 0xc1, 0x64, 0xcc, 0x72, + 0x28, 0x3f, 0x28, 0x7f, 0x0e, 0xec, 0x95, 0x36, 0xff, 0x76, 0x1c, 0xb0, 0x7a, 0x4e, 0xa8, 0xdf, + 0xb5, 0x0c, 0x8a, 0xbf, 0x85, 0x60, 0x86, 0x89, 0xc6, 0xcf, 0x0d, 0x3b, 0x96, 0xdc, 0x5f, 0x2b, + 0xd3, 0xbb, 0x08, 0x33, 0x69, 0xda, 0xca, 0x1b, 0x7f, 0xfd, 0xc7, 0xb7, 0x73, 0xcb, 0xf8, 0x30, + 0x7f, 0x21, 0xeb, 0x9e, 0x57, 0x5f, 0xab, 0x02, 0xfc, 0x16, 0x02, 0x2c, 0xea, 0x20, 0xe5, 0x0d, + 0x01, 0x9f, 0x19, 0x06, 0x71, 0xc0, 0x5b, 0x43, 0xe5, 0x39, 0x25, 0xab, 0xd4, 0x0d, 0xd7, 0xa7, + 0x2c, 0x87, 0xf0, 0x09, 0x1c, 0xc0, 0x2a, 0x07, 0x70, 0x02, 0x6b, 0x83, 0x00, 0x34, 0x1e, 0x31, + 0x8b, 0x3e, 0x6e, 0xd0, 0x48, 0xee, 0x3b, 0x08, 0x0a, 0xf7, 0xf9, 0x1d, 0x62, 0x84, 0x91, 0xb6, + 0xa6, 0x66, 0x24, 0x2e, 0x8e, 0xa3, 0xd5, 0x8e, 0x73, 0xa4, 0xcf, 0xe1, 0xa3, 0x12, 0x69, 0x10, + 0xfa, 0x94, 0xb4, 0x53, 0x80, 0xcf, 0x21, 0xfc, 0x2e, 0x82, 0xd9, 0xa8, 0x79, 0x8c, 0x4f, 0x0e, + 0x43, 0x99, 0x6a, 0x2e, 0x57, 0xa6, 0xd7, 0x89, 0xd5, 0x4e, 0x73, 0x8c, 0xc7, 0xb5, 0x81, 0xdb, + 0xb9, 0x9e, 0xea, 0xd3, 0xbe, 0x8d, 0x20, 0x7f, 0x9d, 0x8e, 0xf4, 0xb7, 0x29, 0x82, 0xeb, 0x33, + 0xe0, 0x80, 0xad, 0xc6, 0x3f, 0x42, 0xf0, 0xec, 0x75, 0x1a, 0x0e, 0x4e, 0x8f, 0xb8, 0x36, 0x3a, + 0x67, 0x09, 0xb7, 0x3b, 0x33, 0xc6, 0xcc, 0x38, 0x2f, 0x34, 0x38, 0xb2, 0xd3, 0xf8, 0x54, 0x96, + 0x13, 0x06, 0x7b, 0x8e, 0xf1, 0x50, 0xe0, 0xf8, 0x13, 0x82, 0x83, 0xbd, 0x6f, 0x85, 0x38, 0x9d, + 0x50, 0x07, 0x3e, 0x25, 0x56, 0x6e, 0x4f, 0x1a, 0x65, 0xd3, 0x4c, 0xb5, 0x4b, 0x1c, 0xf9, 0x4b, + 0xf8, 0xc5, 0x2c, 0xe4, 0x71, 0x27, 0xae, 0xf1, 0x48, 0x7e, 0x3e, 0xe6, 0xef, 0xda, 0x1c, 0xf6, + 0x9f, 0x11, 0x1c, 0x96, 0x7c, 0x37, 0x76, 0x88, 0x1f, 0x5e, 0xa1, 0xac, 0x86, 0x0e, 0xc6, 0xd2, + 0x67, 0xc2, 0xac, 0xa1, 0xca, 0xd3, 0xae, 0x72, 0x5d, 0x3e, 0x81, 0x5f, 0xd9, 0xb7, 0x2e, 0x06, + 0x63, 0x63, 0x0a, 0xd8, 0x6f, 0x20, 0x58, 0xb8, 0x4e, 0xc3, 0x5b, 0x71, 0x37, 0xf8, 0xe4, 0x58, + 0x2f, 0x4c, 0x95, 0x95, 0xba, 0xf2, 0x9c, 0x2e, 0x7f, 0x8a, 0x5d, 0x64, 0x8d, 0x83, 0x3b, 0x85, + 0x4f, 0x66, 0x81, 0x4b, 0x3a, 0xd0, 0xef, 0x20, 0x38, 0xa2, 0x82, 0x48, 0x5e, 0xe6, 0x3e, 0xb6, + 0xbf, 0xf7, 0x2e, 0xf1, 0x6a, 0x36, 0x02, 0x5d, 0x93, 0xa3, 0x3b, 0xab, 0x0d, 0x76, 0xe0, 0x76, + 0x1f, 0x8a, 0x75, 0xb4, 0x5a, 0x43, 0xf8, 0x77, 0x08, 0x66, 0xa3, 0x66, 0xec, 0x70, 0x1b, 0xa5, + 0x5e, 0x92, 0xa6, 0x19, 0x0d, 0xc4, 0x6e, 0x57, 0xce, 0x0d, 0x36, 0xa8, 0xba, 0x5e, 0xba, 0x6a, + 0x9d, 0x5b, 0x39, 0x1d, 0xc6, 0x7e, 0x89, 0x00, 0x92, 0x86, 0x32, 0x3e, 0x9d, 0xad, 0x87, 0xd2, + 0x74, 0xae, 0x4c, 0xb7, 0xa5, 0xac, 0xd5, 0xb9, 0x3e, 0xb5, 0x4a, 0x35, 0x33, 0x86, 0x78, 0xd4, + 0x58, 0x8f, 0x9a, 0xcf, 0x3f, 0x44, 0x50, 0xe0, 0x7d, 0x3c, 0x7c, 0x62, 0x18, 0x66, 0xb5, 0xcd, + 0x37, 0x4d, 0xd3, 0xbf, 0xc0, 0xa1, 0x56, 0x9b, 0x59, 0x81, 0x78, 0x1d, 0xad, 0xe2, 0x2e, 0xcc, + 0x46, 0x9d, 0xb3, 0xe1, 0xee, 0x91, 0xea, 0xac, 0x55, 0xaa, 0x19, 0x85, 0x41, 0xe4, 0xa8, 0x22, + 0x07, 0xac, 0x8e, 0xca, 0x01, 0x33, 0x2c, 0x4c, 0xe3, 0xe3, 0x59, 0x41, 0xfc, 0xff, 0x60, 0x98, + 0x33, 0x1c, 0xdd, 0x49, 0xad, 0x3a, 0x2a, 0x0f, 0x30, 0xeb, 0x7c, 0x07, 0xc1, 0xc1, 0xde, 0xe2, + 0x1a, 0x1f, 0xed, 0x89, 0x99, 0xea, 0x5d, 0xa3, 0x92, 0xb6, 0xe2, 0xb0, 0xc2, 0x5c, 0xfb, 0x24, + 0x47, 0xb1, 0x8e, 0x2f, 0x8e, 0x3c, 0x19, 0xb7, 0x65, 0xd4, 0x61, 0x8c, 0xd6, 0x92, 0xd7, 0xb1, + 0x5f, 0x21, 0x58, 0x90, 0x7c, 0xef, 0xfa, 0x94, 0x66, 0xc3, 0x9a, 0xde, 0x41, 0x60, 0xb2, 0xb4, + 0x97, 0x39, 0xfc, 0x8f, 0xe3, 0x0b, 0x63, 0xc2, 0x97, 0xb0, 0xd7, 0x42, 0x86, 0xf4, 0x0f, 0x08, + 0x0e, 0xdd, 0x8f, 0xfc, 0xfe, 0x03, 0xc2, 0xbf, 0xc1, 0xf1, 0xbf, 0x82, 0x5f, 0xca, 0xa8, 0xf3, + 0x46, 0xa9, 0x71, 0x0e, 0xe1, 0x9f, 0x23, 0x28, 0xca, 0x57, 0x15, 0x7c, 0x6a, 0xe8, 0xc1, 0x48, + 0xbf, 0xbb, 0x4c, 0xd3, 0x99, 0x45, 0x51, 0xa3, 0x9d, 0xc8, 0x4c, 0xa7, 0x42, 0x3e, 0x73, 0xe8, + 0xb7, 0x11, 0xe0, 0xf8, 0xce, 0x1c, 0xdf, 0xa2, 0xf1, 0x0b, 0x29, 0x51, 0x43, 0x1b, 0x33, 0x95, + 0x53, 0x23, 0xe7, 0xa5, 0x53, 0xe9, 0x6a, 0x66, 0x2a, 0x75, 0x63, 0xf9, 0x5f, 0x47, 0x50, 0xba, + 0x4e, 0xe3, 0x3b, 0x48, 0x86, 0x2d, 0xd3, 0x8f, 0x42, 0x95, 0xda, 0xe8, 0x89, 0x02, 0xd1, 0x59, + 0x8e, 0xe8, 0x05, 0x9c, 0x6d, 0x2a, 0x09, 0xe0, 0x7b, 0x08, 0x16, 0xef, 0xa8, 0x2e, 0x8a, 0xcf, + 0x8e, 0x92, 0x94, 0x8a, 0xe4, 0xe3, 0xe3, 0xfa, 0x28, 0xc7, 0xb5, 0xa6, 0x8d, 0x85, 0x6b, 0x5d, + 0xbc, 0xaf, 0x7c, 0x1f, 0x45, 0x97, 0xd8, 0x9e, 0x7e, 0xf6, 0xff, 0x6a, 0xb7, 0x8c, 0xb6, 0xb8, + 0x76, 0x81, 0xe3, 0xab, 0xe3, 0xb3, 0xe3, 0xe0, 0x6b, 0x88, 0x26, 0x37, 0xfe, 0x2e, 0x82, 0x43, + 0xfc, 0xad, 0x41, 0x65, 0xdc, 0x93, 0x62, 0x86, 0xbd, 0x4c, 0x8c, 0x91, 0x62, 0x44, 0xfc, 0xd1, + 0xf6, 0x05, 0x6a, 0x5d, 0xbe, 0x23, 0x7c, 0x03, 0xc1, 0x01, 0x99, 0xd4, 0xc4, 0xee, 0xae, 0x8d, + 0x32, 0xdc, 0x7e, 0x93, 0xa0, 0x70, 0xb7, 0xd5, 0xf1, 0xdc, 0xed, 0x5d, 0x04, 0x73, 0xa2, 0x9b, + 0x9f, 0x51, 0x2a, 0x28, 0xed, 0xfe, 0x4a, 0x4f, 0x8f, 0x43, 0x34, 0x83, 0xb5, 0xcf, 0x73, 0xb1, + 0xf7, 0x70, 0x23, 0x4b, 0xac, 0xe7, 0x9a, 0x41, 0xe3, 0x91, 0xe8, 0xc4, 0x3e, 0x6e, 0xd8, 0x6e, + 0x2b, 0x78, 0x4d, 0xc3, 0x99, 0x09, 0x91, 0xcd, 0x39, 0x87, 0x70, 0x08, 0xf3, 0xcc, 0x39, 0x78, + 0xe3, 0x04, 0x57, 0x7b, 0xda, 0x2c, 0x7d, 0x3d, 0x95, 0x4a, 0xa5, 0xaf, 0x11, 0x93, 0x64, 0x40, + 0x71, 0x8d, 0xc5, 0xcf, 0x67, 0x8a, 0xe5, 0x82, 0xde, 0x42, 0x70, 0x48, 0xf5, 0xf6, 0x48, 0xfc, + 0xd8, 0xbe, 0x9e, 0x85, 0x42, 0x14, 0xd5, 0x78, 0x75, 0x2c, 0x47, 0xe2, 0x70, 0x2e, 0x5f, 0xfb, + 0xe3, 0x93, 0x63, 0xe8, 0xbd, 0x27, 0xc7, 0xd0, 0xdf, 0x9f, 0x1c, 0x43, 0xaf, 0x5d, 0x1c, 0xef, + 0x3f, 0xc2, 0x86, 0x6d, 0x51, 0x27, 0x54, 0xd9, 0xff, 0x37, 0x00, 0x00, 0xff, 0xff, 0x45, 0x63, + 0x3b, 0x00, 0x09, 0x2d, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. diff --git a/pkg/apiclient/repository/repository.pb.go b/pkg/apiclient/repository/repository.pb.go index 6aeddbd1209a5..8dbb20ce7bc70 100644 --- a/pkg/apiclient/repository/repository.pb.go +++ b/pkg/apiclient/repository/repository.pb.go @@ -730,79 +730,81 @@ func init() { } var fileDescriptor_8d38260443475705 = []byte{ - // 1147 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x57, 0x5f, 0x6f, 0xdc, 0x44, - 0x10, 0x97, 0x93, 0xe6, 0x9a, 0x4c, 0x9a, 0xf4, 0xb2, 0x09, 0xc5, 0x5c, 0xd3, 0x34, 0x72, 0x4b, - 0x15, 0xa2, 0x62, 0x37, 0x87, 0x10, 0xa8, 0x08, 0xa4, 0xcb, 0x1f, 0x35, 0x11, 0x11, 0x29, 0xae, - 0xc2, 0x03, 0x02, 0xa1, 0x8d, 0x6f, 0x72, 0xe7, 0xd6, 0x67, 0x6f, 0x77, 0xf7, 0x0e, 0x4e, 0x55, - 0x5f, 0x78, 0x42, 0x82, 0x17, 0x84, 0x90, 0x78, 0x43, 0x48, 0x48, 0x3c, 0xf0, 0x05, 0xf8, 0x08, - 0x3c, 0x22, 0xf1, 0x05, 0x50, 0xc4, 0xe7, 0x40, 0x68, 0x77, 0x7d, 0xb6, 0x2f, 0xb9, 0xbb, 0xa4, - 0x22, 0xe4, 0x6d, 0xf7, 0x37, 0xe3, 0x99, 0xdf, 0xfe, 0x3c, 0x33, 0x6b, 0x83, 0x23, 0x90, 0x77, - 0x90, 0x7b, 0x1c, 0x59, 0x22, 0x42, 0x99, 0xf0, 0x6e, 0x61, 0xe9, 0x32, 0x9e, 0xc8, 0x84, 0x40, - 0x8e, 0x54, 0x16, 0x1b, 0x49, 0xd2, 0x88, 0xd0, 0xa3, 0x2c, 0xf4, 0x68, 0x1c, 0x27, 0x92, 0xca, - 0x30, 0x89, 0x85, 0xf1, 0xac, 0xec, 0x36, 0x42, 0xd9, 0x6c, 0x1f, 0xb8, 0x41, 0xd2, 0xf2, 0x28, - 0x6f, 0x24, 0x8c, 0x27, 0x8f, 0xf5, 0xe2, 0xf5, 0xa0, 0xee, 0x75, 0xaa, 0x1e, 0x7b, 0xd2, 0x50, - 0x4f, 0x0a, 0x8f, 0x32, 0x16, 0x85, 0x81, 0x7e, 0xd6, 0xeb, 0xac, 0xd1, 0x88, 0x35, 0xe9, 0x9a, - 0xd7, 0xc0, 0x18, 0x39, 0x95, 0x58, 0x4f, 0xa3, 0x6d, 0x9d, 0x12, 0x4d, 0xd3, 0x3a, 0x95, 0xbe, - 0xd3, 0x85, 0x19, 0x1f, 0x59, 0x52, 0x63, 0x4c, 0x7c, 0xd8, 0x46, 0xde, 0x25, 0x04, 0x2e, 0x29, - 0x27, 0xdb, 0x5a, 0xb6, 0x56, 0xa6, 0x7c, 0xbd, 0x26, 0x15, 0x98, 0xe4, 0xd8, 0x09, 0x45, 0x98, - 0xc4, 0xf6, 0x98, 0xc6, 0xb3, 0x3d, 0xb1, 0xe1, 0x32, 0x65, 0xec, 0x03, 0xda, 0x42, 0x7b, 0x5c, - 0x9b, 0x7a, 0x5b, 0xb2, 0x04, 0x40, 0x19, 0x7b, 0xc8, 0x93, 0xc7, 0x18, 0x48, 0xfb, 0x92, 0x36, - 0x16, 0x10, 0x67, 0x0d, 0x2e, 0xd7, 0x18, 0xdb, 0x89, 0x0f, 0x13, 0x95, 0x54, 0x76, 0x19, 0xf6, - 0x92, 0xaa, 0xb5, 0xc2, 0x18, 0x95, 0xcd, 0x34, 0xa1, 0x5e, 0x3b, 0xbf, 0x59, 0x30, 0x9f, 0xd2, - 0xdd, 0x44, 0x49, 0xc3, 0x28, 0x25, 0xdd, 0x80, 0x92, 0x48, 0xda, 0x3c, 0x30, 0x11, 0xa6, 0xab, - 0x7b, 0x6e, 0xae, 0x8e, 0xdb, 0x53, 0x47, 0x2f, 0x3e, 0x0b, 0xea, 0x6e, 0xa7, 0xea, 0xb2, 0x27, - 0x0d, 0x57, 0x69, 0xed, 0x16, 0xb4, 0x76, 0x7b, 0x5a, 0xbb, 0xb5, 0x1c, 0x7c, 0xa4, 0xc3, 0xfa, - 0x69, 0xf8, 0xe2, 0x69, 0xc7, 0x46, 0x9d, 0x76, 0xfc, 0xc4, 0x69, 0xdf, 0x85, 0x72, 0x4f, 0x68, - 0x1f, 0x05, 0x4b, 0x62, 0x81, 0xe4, 0x35, 0x98, 0x08, 0x25, 0xb6, 0x84, 0x6d, 0x2d, 0x8f, 0xaf, - 0x4c, 0x57, 0xe7, 0xdd, 0xc2, 0xeb, 0x49, 0xa5, 0xf1, 0x8d, 0x87, 0x13, 0xc0, 0x94, 0x7a, 0x7c, - 0xf8, 0x3b, 0x72, 0xe0, 0xca, 0x61, 0xa2, 0xa8, 0xe2, 0x21, 0x47, 0x61, 0x64, 0x9b, 0xf4, 0xfb, - 0xb0, 0x53, 0x39, 0xfe, 0x34, 0x01, 0x57, 0x35, 0xc9, 0x20, 0x40, 0x31, 0xba, 0x1e, 0xda, 0x02, - 0x79, 0x9c, 0xcb, 0x90, 0xed, 0x95, 0x8d, 0x51, 0x21, 0x3e, 0x4f, 0x78, 0x3d, 0xcd, 0x90, 0xed, - 0xc9, 0x6d, 0x98, 0x11, 0xa2, 0xf9, 0x90, 0x87, 0x1d, 0x2a, 0xf1, 0x7d, 0xec, 0xa6, 0x45, 0xd1, - 0x0f, 0xaa, 0x08, 0x61, 0x2c, 0x30, 0x68, 0x73, 0xb4, 0x27, 0xf4, 0x29, 0xb2, 0x3d, 0xb9, 0x0b, - 0x73, 0x32, 0x12, 0x1b, 0x51, 0x88, 0xb1, 0xdc, 0x40, 0x2e, 0x37, 0xa9, 0xa4, 0x76, 0x49, 0x47, - 0x39, 0x69, 0x20, 0xab, 0x50, 0xee, 0x03, 0x55, 0xca, 0xcb, 0xda, 0xf9, 0x04, 0x9e, 0x95, 0xe0, - 0x54, 0x7f, 0x09, 0xea, 0x33, 0x82, 0xc1, 0xf4, 0xf9, 0x16, 0x61, 0x0a, 0x63, 0x7a, 0x10, 0xe1, - 0x5e, 0x10, 0xda, 0xd3, 0x9a, 0x5e, 0x0e, 0x90, 0x7b, 0x30, 0x6f, 0x2a, 0xaf, 0xa6, 0x54, 0xcd, - 0xce, 0x79, 0x45, 0x07, 0x18, 0x64, 0x22, 0xcb, 0x30, 0x9d, 0xc1, 0x3b, 0x9b, 0xf6, 0xcc, 0xb2, - 0xb5, 0x32, 0xee, 0x17, 0x21, 0xf2, 0x36, 0xbc, 0x9c, 0x6f, 0x63, 0x21, 0x69, 0x14, 0xe9, 0xd2, - 0xdc, 0xd9, 0xb4, 0x67, 0xb5, 0xf7, 0x30, 0x33, 0x79, 0x0f, 0x2a, 0x99, 0x69, 0x2b, 0x96, 0xc8, - 0x19, 0x0f, 0x05, 0xae, 0x53, 0x81, 0xfb, 0x3c, 0xb2, 0xaf, 0x6a, 0x52, 0x23, 0x3c, 0xc8, 0x02, - 0x4c, 0x30, 0x9e, 0x7c, 0xd1, 0xb5, 0xcb, 0xda, 0xd5, 0x6c, 0x54, 0x0f, 0xb0, 0xb4, 0x84, 0xe6, - 0x4c, 0x0f, 0xa4, 0x5b, 0x52, 0x85, 0x85, 0x46, 0xc0, 0x1e, 0x21, 0xef, 0x84, 0x01, 0xd6, 0x82, - 0x20, 0x69, 0xc7, 0x5a, 0x73, 0xa2, 0xdd, 0x06, 0xda, 0x88, 0x0b, 0x44, 0xd7, 0xe8, 0xb6, 0x94, - 0x6c, 0x9d, 0x8a, 0x30, 0xa8, 0xb5, 0x65, 0xd3, 0x9e, 0xd7, 0xc2, 0x0e, 0xb0, 0x38, 0xb3, 0x70, - 0x45, 0x95, 0x68, 0xaf, 0x87, 0x9c, 0x5f, 0x2c, 0x98, 0x53, 0xc0, 0x06, 0x47, 0x2a, 0xd1, 0xc7, - 0xa7, 0x6d, 0x14, 0x92, 0x7c, 0x52, 0xa8, 0xda, 0xe9, 0xea, 0xf6, 0x7f, 0x1b, 0x07, 0x7e, 0xd6, - 0x95, 0x69, 0xfd, 0x5f, 0x83, 0x52, 0x9b, 0x09, 0xe4, 0x32, 0xed, 0xb2, 0x74, 0xa7, 0x6a, 0x23, - 0xe0, 0x58, 0x17, 0x7b, 0x71, 0xd4, 0xd5, 0xc5, 0x3f, 0xe9, 0xe7, 0x80, 0xf3, 0xd4, 0x10, 0xdd, - 0x67, 0xf5, 0x8b, 0x22, 0x5a, 0xfd, 0x67, 0xd6, 0xe4, 0x34, 0x60, 0x2a, 0x3e, 0xf9, 0xc6, 0x82, - 0x4b, 0xbb, 0xa1, 0x90, 0xe4, 0xa5, 0xe2, 0xc0, 0xc9, 0xc6, 0x4b, 0x65, 0xf7, 0xbc, 0x58, 0xa8, - 0x24, 0xce, 0xcd, 0x2f, 0xff, 0xfc, 0xfb, 0xbb, 0xb1, 0x6b, 0x64, 0x41, 0x5f, 0x8b, 0x9d, 0xb5, - 0xfc, 0x0e, 0x0a, 0x51, 0x7c, 0x35, 0x66, 0x91, 0xaf, 0x2d, 0x18, 0x7f, 0x80, 0x43, 0xd9, 0x9c, - 0x9b, 0x26, 0xce, 0x2d, 0xcd, 0xe4, 0x06, 0xb9, 0x3e, 0x88, 0x89, 0xf7, 0x4c, 0xed, 0x9e, 0x93, - 0xef, 0x2d, 0x28, 0x2b, 0xde, 0x7e, 0xc1, 0x76, 0x31, 0x42, 0x2d, 0x8e, 0x12, 0x8a, 0x7c, 0x0a, - 0x93, 0x86, 0xd6, 0xe1, 0x50, 0x3a, 0xe5, 0x7e, 0xf8, 0x50, 0x38, 0x2b, 0x3a, 0xa4, 0x43, 0x96, - 0x47, 0x9c, 0xd8, 0xe3, 0x2a, 0x64, 0xcb, 0x84, 0x57, 0xd7, 0x13, 0x79, 0xe5, 0x78, 0xf8, 0xec, - 0xeb, 0xa0, 0xb2, 0x38, 0xc8, 0x94, 0xf5, 0xe2, 0x99, 0xd2, 0x51, 0x95, 0xe2, 0x5b, 0x0b, 0x66, - 0x1e, 0xa0, 0xcc, 0xef, 0x71, 0x72, 0x73, 0x40, 0xe4, 0xe2, 0x1d, 0x5f, 0x71, 0x86, 0x3b, 0x64, - 0x04, 0xde, 0xd1, 0x04, 0xde, 0x74, 0xee, 0x0d, 0x26, 0x60, 0x2e, 0x71, 0x1d, 0x67, 0xdf, 0xdf, - 0xd5, 0x54, 0xea, 0x26, 0xc2, 0x7d, 0x6b, 0x95, 0x74, 0x34, 0xa5, 0x6d, 0x8c, 0x5a, 0x1b, 0x4d, - 0xca, 0xe5, 0x50, 0x99, 0x97, 0x8a, 0x70, 0xee, 0x9e, 0x91, 0x70, 0x35, 0x89, 0x15, 0x72, 0x67, - 0x94, 0x0a, 0x4d, 0x8c, 0x5a, 0x81, 0x49, 0xf3, 0x83, 0x05, 0x25, 0x33, 0xbd, 0xc8, 0x8d, 0xe3, - 0x19, 0xfb, 0xa6, 0xda, 0x39, 0xb6, 0xc2, 0xab, 0x9a, 0xe3, 0xa2, 0x33, 0xb0, 0xd6, 0xee, 0xeb, - 0xe1, 0xa1, 0x5a, 0xf3, 0x47, 0x0b, 0xca, 0x3d, 0x0a, 0xbd, 0x67, 0x2f, 0x8e, 0xa4, 0x73, 0x3a, - 0x49, 0xf2, 0xb3, 0x05, 0x25, 0x33, 0x51, 0x4f, 0xf2, 0xea, 0x9b, 0xb4, 0xe7, 0xc8, 0x6b, 0xcd, - 0xbc, 0xe0, 0xca, 0x88, 0x32, 0xd7, 0x54, 0x9e, 0xe7, 0x42, 0xfe, 0x6a, 0x41, 0xb9, 0x47, 0x67, - 0xb8, 0x90, 0xff, 0x17, 0x61, 0xf7, 0xc5, 0x08, 0x13, 0x0a, 0xa5, 0x4d, 0x8c, 0x50, 0xe2, 0xb0, - 0x16, 0xb0, 0x8f, 0xc3, 0x59, 0xf1, 0xdf, 0x31, 0x33, 0x76, 0x75, 0xd4, 0x8c, 0x55, 0x82, 0x34, - 0xa1, 0x6c, 0x52, 0x14, 0xf4, 0x78, 0xe1, 0x64, 0xb7, 0xce, 0x90, 0x8c, 0x3c, 0x83, 0xd9, 0x8f, - 0x68, 0x14, 0x2a, 0x65, 0xcd, 0x77, 0x2d, 0xb9, 0x7e, 0x62, 0x92, 0xe4, 0xdf, 0xbb, 0x23, 0xb2, - 0x55, 0x75, 0xb6, 0xbb, 0xce, 0xed, 0x51, 0x7d, 0xdd, 0x49, 0x53, 0x19, 0x25, 0xd7, 0xb7, 0x7e, - 0x3f, 0x5a, 0xb2, 0xfe, 0x38, 0x5a, 0xb2, 0xfe, 0x3a, 0x5a, 0xb2, 0x3e, 0x7e, 0xeb, 0x6c, 0x7f, - 0x80, 0x81, 0xfe, 0x30, 0x2d, 0xfc, 0xab, 0x1d, 0x94, 0xf4, 0xcf, 0xda, 0x1b, 0xff, 0x06, 0x00, - 0x00, 0xff, 0xff, 0xb4, 0x10, 0xe0, 0x77, 0x91, 0x0e, 0x00, 0x00, + // 1178 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x57, 0x5d, 0x6f, 0x1b, 0x45, + 0x17, 0xd6, 0x26, 0x8d, 0x93, 0x9c, 0x7c, 0xd4, 0x99, 0xe4, 0xed, 0xbb, 0xb8, 0x6e, 0x1a, 0x6d, + 0x4b, 0x15, 0xa2, 0xb2, 0x6e, 0x8c, 0x10, 0xa8, 0x08, 0x24, 0xe7, 0x43, 0x4d, 0x44, 0x44, 0xca, + 0x56, 0xe1, 0x02, 0x81, 0xd0, 0x64, 0x7d, 0x62, 0x6f, 0xbb, 0xde, 0x9d, 0xce, 0x8c, 0x4d, 0xad, + 0xaa, 0x37, 0x5c, 0x21, 0xc1, 0x0d, 0x42, 0x48, 0xdc, 0x21, 0x24, 0x24, 0x2e, 0xf8, 0x23, 0x5c, + 0x22, 0xf1, 0x07, 0x50, 0xc4, 0x8f, 0xe0, 0x0a, 0xa1, 0x99, 0x59, 0xef, 0xae, 0x13, 0xdb, 0x49, + 0x45, 0xc8, 0xdd, 0xcc, 0x73, 0xce, 0x9e, 0xf3, 0xcc, 0xb3, 0xe7, 0x9c, 0x9d, 0x05, 0x47, 0x20, + 0xef, 0x20, 0xaf, 0x70, 0x64, 0xb1, 0x08, 0x64, 0xcc, 0xbb, 0xb9, 0xa5, 0xcb, 0x78, 0x2c, 0x63, + 0x02, 0x19, 0x52, 0x2a, 0x37, 0xe2, 0xb8, 0x11, 0x62, 0x85, 0xb2, 0xa0, 0x42, 0xa3, 0x28, 0x96, + 0x54, 0x06, 0x71, 0x24, 0x8c, 0x67, 0x69, 0xaf, 0x11, 0xc8, 0x66, 0xfb, 0xd0, 0xf5, 0xe3, 0x56, + 0x85, 0xf2, 0x46, 0xcc, 0x78, 0xfc, 0x58, 0x2f, 0x5e, 0xf7, 0xeb, 0x95, 0x4e, 0xb5, 0xc2, 0x9e, + 0x34, 0xd4, 0x93, 0xa2, 0x42, 0x19, 0x0b, 0x03, 0x5f, 0x3f, 0x5b, 0xe9, 0xac, 0xd3, 0x90, 0x35, + 0xe9, 0x7a, 0xa5, 0x81, 0x11, 0x72, 0x2a, 0xb1, 0x9e, 0x44, 0xdb, 0x3e, 0x23, 0x9a, 0xa6, 0x75, + 0x26, 0x7d, 0xa7, 0x0b, 0x73, 0x1e, 0xb2, 0xb8, 0xc6, 0x98, 0xf8, 0xb0, 0x8d, 0xbc, 0x4b, 0x08, + 0x5c, 0x51, 0x4e, 0xb6, 0xb5, 0x62, 0xad, 0x4e, 0x7b, 0x7a, 0x4d, 0x4a, 0x30, 0xc5, 0xb1, 0x13, + 0x88, 0x20, 0x8e, 0xec, 0x31, 0x8d, 0xa7, 0x7b, 0x62, 0xc3, 0x24, 0x65, 0xec, 0x03, 0xda, 0x42, + 0x7b, 0x5c, 0x9b, 0x7a, 0x5b, 0xb2, 0x0c, 0x40, 0x19, 0x7b, 0xc8, 0xe3, 0xc7, 0xe8, 0x4b, 0xfb, + 0x8a, 0x36, 0xe6, 0x10, 0x67, 0x1d, 0x26, 0x6b, 0x8c, 0xed, 0x46, 0x47, 0xb1, 0x4a, 0x2a, 0xbb, + 0x0c, 0x7b, 0x49, 0xd5, 0x5a, 0x61, 0x8c, 0xca, 0x66, 0x92, 0x50, 0xaf, 0x9d, 0xbf, 0x2c, 0x58, + 0x4c, 0xe8, 0x6e, 0xa1, 0xa4, 0x41, 0x98, 0x90, 0x6e, 0x40, 0x41, 0xc4, 0x6d, 0xee, 0x9b, 0x08, + 0x33, 0xd5, 0x7d, 0x37, 0x53, 0xc7, 0xed, 0xa9, 0xa3, 0x17, 0x9f, 0xf9, 0x75, 0xb7, 0x53, 0x75, + 0xd9, 0x93, 0x86, 0xab, 0xb4, 0x76, 0x73, 0x5a, 0xbb, 0x3d, 0xad, 0xdd, 0x5a, 0x06, 0x3e, 0xd2, + 0x61, 0xbd, 0x24, 0x7c, 0xfe, 0xb4, 0x63, 0xa3, 0x4e, 0x3b, 0x7e, 0xf2, 0xb4, 0x64, 0x05, 0x66, + 0x4c, 0x8c, 0xdd, 0xa8, 0x8e, 0xcf, 0xb4, 0x1c, 0x13, 0x5e, 0x1e, 0x22, 0x65, 0x98, 0xee, 0x20, + 0x57, 0xa2, 0xee, 0xd6, 0xed, 0x09, 0x6d, 0xcf, 0x00, 0xe7, 0x5d, 0x28, 0xf6, 0x5e, 0x94, 0x87, + 0x82, 0xc5, 0x91, 0x40, 0xf2, 0x1a, 0x4c, 0x04, 0x12, 0x5b, 0xc2, 0xb6, 0x56, 0xc6, 0x57, 0x67, + 0xaa, 0x8b, 0x6e, 0xee, 0xf5, 0x26, 0xd2, 0x7a, 0xc6, 0xc3, 0xf1, 0x61, 0x5a, 0x3d, 0x3e, 0xfc, + 0x1d, 0x3b, 0x30, 0x7b, 0x14, 0xab, 0xa3, 0xe2, 0x11, 0x47, 0x61, 0x64, 0x9f, 0xf2, 0xfa, 0xb0, + 0xb3, 0xce, 0xe8, 0xfc, 0x38, 0x01, 0x57, 0x35, 0x49, 0xdf, 0x47, 0x31, 0xba, 0x9e, 0xda, 0x02, + 0x79, 0x94, 0xc9, 0x98, 0xee, 0x95, 0x8d, 0x51, 0x21, 0x3e, 0x8f, 0x79, 0x3d, 0xc9, 0x90, 0xee, + 0xc9, 0x6d, 0x98, 0x13, 0xa2, 0xf9, 0x90, 0x07, 0x1d, 0x2a, 0xf1, 0x7d, 0xec, 0x26, 0x45, 0xd5, + 0x0f, 0xaa, 0x08, 0x41, 0x24, 0xd0, 0x6f, 0x73, 0xd4, 0x32, 0x4e, 0x79, 0xe9, 0x9e, 0xdc, 0x85, + 0x05, 0x19, 0x8a, 0xcd, 0x30, 0xc0, 0x48, 0x6e, 0x22, 0x97, 0x5b, 0x54, 0x52, 0xbb, 0xa0, 0xa3, + 0x9c, 0x36, 0x90, 0x35, 0x28, 0xf6, 0x81, 0x2a, 0xe5, 0xa4, 0x76, 0x3e, 0x85, 0xa7, 0x25, 0x3c, + 0xdd, 0x5f, 0xc2, 0xfa, 0x8c, 0x60, 0x30, 0x7d, 0xbe, 0x32, 0x4c, 0x63, 0x44, 0x0f, 0x43, 0xdc, + 0xf7, 0x03, 0x7b, 0x46, 0xd3, 0xcb, 0x00, 0x72, 0x0f, 0x16, 0x4d, 0xe5, 0xd6, 0x94, 0xaa, 0xe9, + 0x39, 0x67, 0x75, 0x80, 0x41, 0x26, 0x55, 0x57, 0x29, 0xbc, 0xbb, 0x65, 0xcf, 0xad, 0x58, 0xab, + 0xe3, 0x5e, 0x1e, 0x22, 0x6f, 0xc3, 0xff, 0xb3, 0x6d, 0x24, 0x24, 0x0d, 0x43, 0x5d, 0xda, 0xbb, + 0x5b, 0xf6, 0xbc, 0xf6, 0x1e, 0x66, 0x26, 0xef, 0x41, 0x29, 0x35, 0x6d, 0x47, 0x12, 0x39, 0xe3, + 0x81, 0xc0, 0x0d, 0x2a, 0xf0, 0x80, 0x87, 0xf6, 0x55, 0x4d, 0x6a, 0x84, 0x07, 0x59, 0x82, 0x09, + 0xc6, 0xe3, 0x67, 0x5d, 0xbb, 0xa8, 0x5d, 0xcd, 0x46, 0xf5, 0x10, 0x4b, 0x4a, 0x68, 0xc1, 0xf4, + 0x50, 0xb2, 0x25, 0x55, 0x58, 0x6a, 0xf8, 0xec, 0x11, 0xf2, 0x4e, 0xe0, 0x63, 0xcd, 0xf7, 0xe3, + 0x76, 0xa4, 0x35, 0x27, 0xda, 0x6d, 0xa0, 0x8d, 0xb8, 0x40, 0x74, 0x8d, 0xee, 0x48, 0xc9, 0x36, + 0xa8, 0x08, 0xfc, 0x5a, 0x5b, 0x36, 0xed, 0x45, 0x2d, 0xec, 0x00, 0x8b, 0x33, 0x0f, 0xb3, 0xaa, + 0x44, 0x7b, 0x3d, 0xe4, 0xfc, 0x6c, 0xc1, 0x82, 0x02, 0x36, 0x39, 0x52, 0x89, 0x1e, 0x3e, 0x6d, + 0xa3, 0x90, 0xe4, 0x93, 0x5c, 0xd5, 0xce, 0x54, 0x77, 0xfe, 0xdd, 0x38, 0xf1, 0xd2, 0xae, 0x4c, + 0xea, 0xff, 0x1a, 0x14, 0xda, 0x4c, 0x20, 0x97, 0x49, 0x97, 0x25, 0x3b, 0x55, 0x1b, 0x3e, 0xc7, + 0xba, 0xd8, 0x8f, 0xc2, 0xae, 0x2e, 0xfe, 0x29, 0x2f, 0x03, 0x9c, 0xa7, 0x86, 0xe8, 0x01, 0xab, + 0x5f, 0x16, 0xd1, 0xea, 0xdf, 0xf3, 0x26, 0xa7, 0x01, 0x13, 0xf1, 0xc9, 0xd7, 0x16, 0x5c, 0xd9, + 0x0b, 0x84, 0x24, 0xff, 0xcb, 0x0f, 0x9c, 0x74, 0xbc, 0x94, 0xf6, 0x2e, 0x8a, 0x85, 0x4a, 0xe2, + 0xdc, 0xfc, 0xe2, 0xf7, 0x3f, 0xbf, 0x1d, 0xbb, 0x46, 0x96, 0xf4, 0x67, 0xb5, 0xb3, 0x9e, 0x7d, + 0xc3, 0x02, 0x14, 0x5f, 0x8e, 0x59, 0xe4, 0x2b, 0x0b, 0xc6, 0x1f, 0xe0, 0x50, 0x36, 0x17, 0xa6, + 0x89, 0x73, 0x4b, 0x33, 0xb9, 0x41, 0xae, 0x0f, 0x62, 0x52, 0x79, 0xae, 0x76, 0x2f, 0xc8, 0x77, + 0x16, 0x14, 0x15, 0x6f, 0x2f, 0x67, 0xbb, 0x1c, 0xa1, 0xca, 0xa3, 0x84, 0x22, 0x9f, 0xc2, 0x94, + 0xa1, 0x75, 0x34, 0x94, 0x4e, 0xb1, 0x1f, 0x3e, 0x12, 0xce, 0xaa, 0x0e, 0xe9, 0x90, 0x95, 0x11, + 0x27, 0xae, 0x70, 0x15, 0xb2, 0x65, 0xc2, 0xab, 0xcf, 0x13, 0x79, 0xe5, 0x64, 0xf8, 0xf4, 0x76, + 0x51, 0x2a, 0x0f, 0x32, 0xa5, 0xbd, 0x78, 0xae, 0x74, 0x54, 0xa5, 0xf8, 0xc6, 0x82, 0xb9, 0x07, + 0x28, 0xb3, 0x7b, 0x00, 0xb9, 0x39, 0x20, 0x72, 0xfe, 0x8e, 0x50, 0x72, 0x86, 0x3b, 0xa4, 0x04, + 0xde, 0xd1, 0x04, 0xde, 0x74, 0xee, 0x0d, 0x26, 0x60, 0xbe, 0xd6, 0x3a, 0xce, 0x81, 0xb7, 0xa7, + 0xa9, 0xd4, 0x4d, 0x84, 0xfb, 0xd6, 0x1a, 0xe9, 0x68, 0x4a, 0x3b, 0x18, 0xb6, 0x36, 0x9b, 0x94, + 0xcb, 0xa1, 0x32, 0x2f, 0xe7, 0xe1, 0xcc, 0x3d, 0x25, 0xe1, 0x6a, 0x12, 0xab, 0xe4, 0xce, 0x28, + 0x15, 0x9a, 0x18, 0xb6, 0x7c, 0x93, 0xe6, 0x7b, 0x0b, 0x0a, 0x66, 0x7a, 0x91, 0x1b, 0x27, 0x33, + 0xf6, 0x4d, 0xb5, 0x0b, 0x6c, 0x85, 0x57, 0x35, 0xc7, 0xb2, 0x33, 0xb0, 0xd6, 0xee, 0xeb, 0xe1, + 0xa1, 0x5a, 0xf3, 0x07, 0x0b, 0x8a, 0x3d, 0x0a, 0xbd, 0x67, 0x2f, 0x8f, 0xa4, 0x73, 0x36, 0x49, + 0xf2, 0x93, 0x05, 0x05, 0x33, 0x51, 0x4f, 0xf3, 0xea, 0x9b, 0xb4, 0x17, 0xc8, 0x6b, 0xdd, 0xbc, + 0xe0, 0xd2, 0x88, 0x32, 0xd7, 0x54, 0x5e, 0x64, 0x42, 0xfe, 0x62, 0x41, 0xb1, 0x47, 0x67, 0xb8, + 0x90, 0xff, 0x15, 0x61, 0xf7, 0xe5, 0x08, 0x13, 0x0a, 0x85, 0x2d, 0x0c, 0x51, 0xe2, 0xb0, 0x16, + 0xb0, 0x4f, 0xc2, 0x69, 0xf1, 0xdf, 0x31, 0x33, 0x76, 0x6d, 0xd4, 0x8c, 0x55, 0x82, 0x34, 0xa1, + 0x68, 0x52, 0xe4, 0xf4, 0x78, 0xe9, 0x64, 0xb7, 0xce, 0x91, 0x8c, 0x3c, 0x87, 0xf9, 0x8f, 0x68, + 0x18, 0x28, 0x65, 0xcd, 0xbd, 0x96, 0x5c, 0x3f, 0x35, 0x49, 0xb2, 0xfb, 0xee, 0x88, 0x6c, 0x55, + 0x9d, 0xed, 0xae, 0x73, 0x7b, 0x54, 0x5f, 0x77, 0x92, 0x54, 0x46, 0xc9, 0x8d, 0xed, 0x5f, 0x8f, + 0x97, 0xad, 0xdf, 0x8e, 0x97, 0xad, 0x3f, 0x8e, 0x97, 0xad, 0x8f, 0xdf, 0x3a, 0xdf, 0x1f, 0xa4, + 0xaf, 0x2f, 0xa6, 0xb9, 0x7f, 0xbd, 0xc3, 0x82, 0xfe, 0xd9, 0x7b, 0xe3, 0x9f, 0x00, 0x00, 0x00, + 0xff, 0xff, 0x56, 0xc6, 0x8e, 0x59, 0xd1, 0x0e, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. diff --git a/reposerver/repository/repository_test.go b/reposerver/repository/repository_test.go index b24be54c669f3..ccdd29231fca1 100644 --- a/reposerver/repository/repository_test.go +++ b/reposerver/repository/repository_test.go @@ -503,7 +503,7 @@ func TestHelmChartReferencingExternalValues(t *testing.T) { return &argoappv1.Repository{ Repo: "https://git.example.com/test/repo", }, nil - }) + }, []string{}, false) require.NoError(t, err) request := &apiclient.ManifestRequest{Repo: &argoappv1.Repository{}, ApplicationSource: &spec.Sources[0], NoCache: true, RefSources: refSources, HasMultipleSources: true, ProjectName: "something", ProjectSourceRepos: []string{"*"}} @@ -532,20 +532,13 @@ func TestHelmChartReferencingExternalValues_InvalidRefs(t *testing.T) { // Empty refsource service := newService(t, ".") -<<<<<<< HEAD getRepository := func(ctx context.Context, url string, project string) (*argoappv1.Repository, error) { return &argoappv1.Repository{ Repo: "https://git.example.com/test/repo", }, nil } - refSources, err := argo.GetRefSources(context.Background(), spec, getRepository) -======= - refSources, err := argo.GetRefSources(context.Background(), argo.GetRefSourcesOptions{ - Sources: spec.Sources, - Db: repoDB, - }) ->>>>>>> aec91ff53 (fix errors) + refSources, err := argo.GetRefSources(context.Background(), spec, getRepository, []string{}, false) require.NoError(t, err) request := &apiclient.ManifestRequest{Repo: &argoappv1.Repository{}, ApplicationSource: &spec.Sources[0], NoCache: true, RefSources: refSources, HasMultipleSources: true, ProjectName: "something", @@ -558,14 +551,7 @@ func TestHelmChartReferencingExternalValues_InvalidRefs(t *testing.T) { service = newService(t, ".") spec.Sources[1].Ref = "Invalid" -<<<<<<< HEAD - refSources, err = argo.GetRefSources(context.Background(), spec, getRepository) -======= - refSources, err = argo.GetRefSources(context.Background(), argo.GetRefSourcesOptions{ - Sources: spec.Sources, - Db: repoDB, - }) ->>>>>>> aec91ff53 (fix errors) + refSources, err = argo.GetRefSources(context.Background(), spec, getRepository, []string{}, false) require.NoError(t, err) request = &apiclient.ManifestRequest{Repo: &argoappv1.Repository{}, ApplicationSource: &spec.Sources[0], NoCache: true, RefSources: refSources, HasMultipleSources: true, ProjectName: "something", @@ -579,7 +565,7 @@ func TestHelmChartReferencingExternalValues_InvalidRefs(t *testing.T) { spec.Sources[1].Ref = "ref" spec.Sources[1].Chart = "helm-chart" - refSources, err = argo.GetRefSources(context.Background(), spec, getRepository) + refSources, err = argo.GetRefSources(context.Background(), spec, getRepository, []string{}, false) require.NoError(t, err) request = &apiclient.ManifestRequest{Repo: &argoappv1.Repository{}, ApplicationSource: &spec.Sources[0], NoCache: true, RefSources: refSources, HasMultipleSources: true, ProjectName: "something", @@ -616,7 +602,7 @@ func TestHelmChartReferencingExternalValues_OutOfBounds_Symlink(t *testing.T) { return &argoappv1.Repository{ Repo: "https://git.example.com/test/repo", }, nil - }) + }, []string{}, false) require.NoError(t, err) request := &apiclient.ManifestRequest{Repo: &argoappv1.Repository{}, ApplicationSource: &spec.Sources[0], NoCache: true, RefSources: refSources, HasMultipleSources: true} _, err = service.GenerateManifest(context.Background(), request) diff --git a/server/application/application.go b/server/application/application.go index c50f3ff88b2e1..430ae0182088a 100644 --- a/server/application/application.go +++ b/server/application/application.go @@ -489,7 +489,7 @@ func (s *Server) GetManifests(ctx context.Context, q *application.ApplicationMan } // Store the map of all sources having ref field into a map for applications with sources field - refSources, err := argo.GetRefSources(context.Background(), *appSpec, s.db.GetRepository) + refSources, err := argo.GetRefSources(context.Background(), *appSpec, s.db.GetRepository, []string{}, false) if err != nil { return fmt.Errorf("failed to get ref sources: %v", err) } diff --git a/server/application/application_test.go b/server/application/application_test.go index 09cf53d53547c..ec177cf39e762 100644 --- a/server/application/application_test.go +++ b/server/application/application_test.go @@ -37,6 +37,7 @@ import ( "k8s.io/client-go/rest" kubetesting "k8s.io/client-go/testing" k8scache "k8s.io/client-go/tools/cache" + "k8s.io/utils/pointer" "k8s.io/utils/ptr" "sigs.k8s.io/yaml" @@ -914,7 +915,7 @@ func TestNoAppEnumeration(t *testing.T) { t.Run("RevisionMetadata", func(t *testing.T) { _, err := appServer.RevisionMetadata(adminCtx, &application.RevisionMetadataQuery{Name: ptr.To("test")}) assert.NoError(t, err) - _, err = appServer.RevisionMetadata(adminCtx, &application.RevisionMetadataQuery{Name: ptr.To("test-multi"), SourceIndex: ptr.Int32(0), VersionId: pointer.Int32(1)}) + _, err = appServer.RevisionMetadata(adminCtx, &application.RevisionMetadataQuery{Name: ptr.To("test-multi"), SourceIndex: ptr.To(int32(0)), VersionId: pointer.Int32(1)}) assert.NoError(t, err) _, err = appServer.RevisionMetadata(noRoleCtx, &application.RevisionMetadataQuery{Name: ptr.To("test")}) assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence") @@ -975,9 +976,9 @@ func TestNoAppEnumeration(t *testing.T) { unsetSyncRunningOperationState(t, appServer) _, err := appServer.Rollback(adminCtx, &application.ApplicationRollbackRequest{Name: ptr.To("test")}) assert.NoError(t, err) - _, err = appServer.Rollback(adminCtx, &application.ApplicationRollbackRequest{Name: pointer.To("test-multi"), Id: pointer.Int64(1)}) + _, err = appServer.Rollback(adminCtx, &application.ApplicationRollbackRequest{Name: ptr.To("test-multi"), Id: ptr.To(int64(1))}) assert.NoError(t, err) - _, err = appServer.Rollback(noRoleCtx, &application.ApplicationRollbackRequest{Name: pointer.To("test")}) + _, err = appServer.Rollback(noRoleCtx, &application.ApplicationRollbackRequest{Name: ptr.To("test")}) assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence") _, err = appServer.Rollback(adminCtx, &application.ApplicationRollbackRequest{Name: ptr.To("doest-not-exist")}) assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence") diff --git a/server/repository/repository_test.go b/server/repository/repository_test.go index fcbb0dd976eac..bcd26561949fd 100644 --- a/server/repository/repository_test.go +++ b/server/repository/repository_test.go @@ -824,7 +824,7 @@ func TestRepositoryServerGetAppDetails(t *testing.T) { helmRepos := []*appsv1.Repository{{Repo: url}, {Repo: url}} db := &dbmocks.ArgoDB{} db.On("ListHelmRepositories", context.TODO(), mock.Anything).Return(helmRepos, nil) - db.On("GetRepository", context.TODO(), url).Return(&appsv1.Repository{Repo: url}, nil) + db.On("GetRepository", context.TODO(), url, "default").Return(&appsv1.Repository{Repo: url}, nil) db.On("GetProjectRepositories", context.TODO(), "default").Return(nil, nil) db.On("GetProjectClusters", context.TODO(), "default").Return(nil, nil) expectedResp := apiclient.RepoAppDetailsResponse{Type: "Helm"} @@ -852,7 +852,7 @@ func TestRepositoryServerGetAppDetails(t *testing.T) { url := "https://helm.elastic.co" db := &dbmocks.ArgoDB{} - db.On("GetRepository", context.TODO(), url).Return(&appsv1.Repository{Repo: url}, nil) + db.On("GetRepository", context.TODO(), url, "default").Return(&appsv1.Repository{Repo: url}, nil) db.On("ListHelmRepositories", context.TODO(), mock.Anything).Return(nil, nil) db.On("GetProjectRepositories", context.TODO(), "default").Return(nil, nil) db.On("GetProjectClusters", context.TODO(), "default").Return(nil, nil) diff --git a/util/argo/argo.go b/util/argo/argo.go index 424bcfd0b564f..345fc61e7c70a 100644 --- a/util/argo/argo.go +++ b/util/argo/argo.go @@ -418,7 +418,7 @@ func validateRepo(ctx context.Context, } } - refSources, err := GetRefSources(ctx, app.Spec, db.GetRepository) + refSources, err := GetRefSources(ctx, app.Spec, db.GetRepository, []string{}, false) if err != nil { return nil, fmt.Errorf("error getting ref sources: %w", err) } @@ -446,7 +446,7 @@ func validateRepo(ctx context.Context, // GetRefSources creates a map of ref keys (from the sources' 'ref' fields) to information about the referenced source. // This function also validates the references use allowed characters and does not define the same ref key more than // once (which would lead to ambiguous references). -func GetRefSources(ctx context.Context, spec argoappv1.ApplicationSpec, getRepository func(ctx context.Context, url string, project string) (*argoappv1.Repository, error)) (argoappv1.RefTargetRevisionMapping, error) { +func GetRefSources(ctx context.Context, spec argoappv1.ApplicationSpec, getRepository func(ctx context.Context, url string, project string) (*argoappv1.Repository, error), revisions []string, isRollback bool) (argoappv1.RefTargetRevisionMapping, error) { refSources := make(argoappv1.RefTargetRevisionMapping) if spec.HasMultipleSources() { // Validate first to avoid unnecessary DB calls. @@ -465,16 +465,20 @@ func GetRefSources(ctx context.Context, spec argoappv1.ApplicationSpec, getRepos } } // Get Repositories for all sources before generating Manifests - for _, source := range spec.Sources { + for i, source := range spec.Sources { if source.Ref != "" { repo, err := getRepository(ctx, source.RepoURL, spec.Project) if err != nil { return nil, fmt.Errorf("failed to get repository %s: %v", source.RepoURL, err) } refKey := "$" + source.Ref + revision := source.TargetRevision + if isRollback { + revision = revisions[i] + } refSources[refKey] = &argoappv1.RefTarget{ Repo: *repo, - TargetRevision: source.TargetRevision, + TargetRevision: revision, Chart: source.Chart, } } diff --git a/util/argo/argo_test.go b/util/argo/argo_test.go index 2a0de59f1e8d1..7b1a593d02a4f 100644 --- a/util/argo/argo_test.go +++ b/util/argo/argo_test.go @@ -1277,7 +1277,7 @@ func Test_GetRefSources(t *testing.T) { refSources, err := GetRefSources(context.Background(), *argoSpec, func(ctx context.Context, url string, project string) (*argoappv1.Repository, error) { return repo, nil - }) + }, []string{}, false) expectedRefSource := argoappv1.RefTargetRevisionMapping{ "$source-1_2": &argoappv1.RefTarget{ @@ -1296,7 +1296,7 @@ func Test_GetRefSources(t *testing.T) { refSources, err := GetRefSources(context.Background(), *argoSpec, func(ctx context.Context, url string, project string) (*argoappv1.Repository, error) { return nil, errors.New("repo does not exist") - }) + }, []string{}, false) assert.Error(t, err) assert.Empty(t, refSources) @@ -1309,7 +1309,7 @@ func Test_GetRefSources(t *testing.T) { refSources, err := GetRefSources(context.TODO(), *argoSpec, func(ctx context.Context, url string, project string) (*argoappv1.Repository, error) { return nil, err - }) + }, []string{}, false) assert.Error(t, err) assert.Empty(t, refSources) @@ -1323,7 +1323,7 @@ func Test_GetRefSources(t *testing.T) { refSources, err := GetRefSources(context.TODO(), *argoSpec, func(ctx context.Context, url string, project string) (*argoappv1.Repository, error) { return nil, err - }) + }, []string{}, false) assert.Error(t, err) assert.Empty(t, refSources) diff --git a/util/argo/ref_sources.go b/util/argo/ref_sources.go deleted file mode 100644 index ccaa5c5650f5c..0000000000000 --- a/util/argo/ref_sources.go +++ /dev/null @@ -1,62 +0,0 @@ -package argo - -import ( - "context" - "fmt" - "regexp" - - argoappv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" - "github.com/argoproj/argo-cd/v2/util/db" -) - -type GetRefSourcesOptions struct { - Sources argoappv1.ApplicationSources - Db db.ArgoDB - Revisions []string - IsRollback bool -} - -// GetRefSources creates a map of ref keys (from the sources' 'ref' fields) to information about the referenced source. -// This function also validates the references use allowed characters and does not define the same ref key more than -// once (which would lead to ambiguous references). -// In case of rollback, this function also updates the targetRevision to the proper revision -func GetRefSources(ctx context.Context, opts GetRefSourcesOptions) (argoappv1.RefTargetRevisionMapping, error) { - refSources := make(argoappv1.RefTargetRevisionMapping) - if len(opts.Sources) > 1 { - // Validate first to avoid unnecessary DB calls. - refKeys := make(map[string]bool) - for _, source := range opts.Sources { - if source.IsRef() { - isValidRefKey := regexp.MustCompile(`^[a-zA-Z0-9_-]+$`).MatchString - if !isValidRefKey(source.Ref) { - return nil, fmt.Errorf("sources.ref %s cannot contain any special characters except '_' and '-'", source.Ref) - } - refKey := "$" + source.Ref - if _, ok := refKeys[refKey]; ok { - return nil, fmt.Errorf("invalid sources: multiple sources had the same `ref` key") - } - refKeys[refKey] = true - } - } - // Get Repositories for all sources before generating Manifests - for i, source := range opts.Sources { - if source.IsRef() { - repo, err := opts.Db.GetRepository(ctx, source.RepoURL) - if err != nil { - return nil, fmt.Errorf("failed to get repository %s: %v", source.RepoURL, err) - } - refKey := "$" + source.Ref - revision := source.TargetRevision - if opts.IsRollback { - revision = opts.Revisions[i] - } - refSources[refKey] = &argoappv1.RefTarget{ - Repo: *repo, - TargetRevision: revision, - Chart: source.Chart, - } - } - } - } - return refSources, nil -} diff --git a/util/argo/ref_sources_test.go b/util/argo/ref_sources_test.go deleted file mode 100644 index f68956a732db7..0000000000000 --- a/util/argo/ref_sources_test.go +++ /dev/null @@ -1,117 +0,0 @@ -package argo - -import ( - "context" - "errors" - "fmt" - "path/filepath" - "testing" - - argoappv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" - dbmocks "github.com/argoproj/argo-cd/v2/util/db/mocks" - "github.com/stretchr/testify/assert" -) - -func Test_GetRefSources(t *testing.T) { - repoPath, err := filepath.Abs("./../..") - assert.NoError(t, err) - - getMultiSourceAppSpec := func(sources argoappv1.ApplicationSources) *argoappv1.ApplicationSpec { - return &argoappv1.ApplicationSpec{ - Sources: sources, - } - } - - repo := &argoappv1.Repository{Repo: fmt.Sprintf("file://%s", repoPath)} - - t.Run("target ref exists", func(t *testing.T) { - repoDB := &dbmocks.ArgoDB{} - repoDB.On("GetRepository", context.Background(), repo.Repo).Return(repo, nil) - - argoSpec := getMultiSourceAppSpec(argoappv1.ApplicationSources{ - {RepoURL: fmt.Sprintf("file://%s", repoPath), Ref: "source-1_2"}, - {RepoURL: fmt.Sprintf("file://%s", repoPath)}, - }) - - refSources, err := GetRefSources(context.Background(), GetRefSourcesOptions{ - Sources: argoSpec.Sources, - Db: repoDB, - }) - - expectedRefSource := argoappv1.RefTargetRevisionMapping{ - "$source-1_2": &argoappv1.RefTarget{ - Repo: *repo, - }, - } - assert.NoError(t, err) - assert.Len(t, refSources, 1) - assert.Equal(t, expectedRefSource, refSources) - }) - - t.Run("target ref does not exist", func(t *testing.T) { - repoDB := &dbmocks.ArgoDB{} - repoDB.On("GetRepository", context.Background(), "file://does-not-exist").Return(nil, errors.New("repo does not exist")) - - argoSpec := getMultiSourceAppSpec(argoappv1.ApplicationSources{ - {RepoURL: "file://does-not-exist", Ref: "source1"}, - {RepoURL: fmt.Sprintf("file://%s", repoPath)}, - }) - - refSources, err := GetRefSources(context.Background(), GetRefSourcesOptions{ - Sources: argoSpec.Sources, - Db: repoDB, - }) - - assert.Error(t, err) - assert.Empty(t, refSources) - }) - - t.Run("invalid ref", func(t *testing.T) { - repoDB := &dbmocks.ArgoDB{} - repoDB.On("GetRepository", context.Background(), repo.Repo).Return(repo, nil) - - argoSpec := getMultiSourceAppSpec(argoappv1.ApplicationSources{ - {RepoURL: "file://does-not-exist", Ref: "%invalid-name%"}, - {RepoURL: fmt.Sprintf("file://%s", repoPath)}, - }) - - refSources, err := GetRefSources(context.TODO(), GetRefSourcesOptions{ - Sources: argoSpec.Sources, - Db: &dbmocks.ArgoDB{}, - }) - assert.Error(t, err) - assert.Empty(t, refSources) - }) - - t.Run("duplicate ref keys", func(t *testing.T) { - argoSpec := getMultiSourceAppSpec(argoappv1.ApplicationSources{ - {RepoURL: "file://does-not-exist", Ref: "source1"}, - {RepoURL: "file://does-not-exist", Ref: "source1"}, - }) - - refSources, err := GetRefSources(context.TODO(), GetRefSourcesOptions{ - Sources: argoSpec.Sources, - Db: &dbmocks.ArgoDB{}, - }) - - assert.Error(t, err) - assert.Empty(t, refSources) - }) - - t.Run("target ref does not fail when single source", func(t *testing.T) { - repoDB := &dbmocks.ArgoDB{} - repoDB.On("GetRepository", context.Background(), repo.Repo).Return(repo, nil) - - argoSpec := getMultiSourceAppSpec(argoappv1.ApplicationSources{ - {RepoURL: fmt.Sprintf("file://%s", repoPath)}, - }) - - refSources, err := GetRefSources(context.Background(), GetRefSourcesOptions{ - Sources: argoSpec.Sources, - Db: repoDB, - }) - - assert.NoError(t, err) - assert.Empty(t, refSources) - }) -} diff --git a/util/webhook/webhook.go b/util/webhook/webhook.go index b83e05d2dfb22..7ce7bea420fa9 100644 --- a/util/webhook/webhook.go +++ b/util/webhook/webhook.go @@ -344,7 +344,7 @@ func (a *ArgoCDWebhookHandler) storePreviouslyCachedManifests(app *v1alpha1.Appl return fmt.Errorf("error getting cluster info: %w", err) } - refSources, err := argo.GetRefSources(context.Background(), app.Spec, a.db.GetRepository) + refSources, err := argo.GetRefSources(context.Background(), app.Spec, a.db.GetRepository, []string{}, false) if err != nil { return fmt.Errorf("error getting ref sources: %w", err) } From bd973274965f0cf83ba27601d5b231b36a8bdac9 Mon Sep 17 00:00:00 2001 From: Jorge Turrado Date: Sun, 9 Jun 2024 23:58:35 +0200 Subject: [PATCH 14/16] fix style Signed-off-by: Jorge Turrado --- server/application/application_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/server/application/application_test.go b/server/application/application_test.go index ec177cf39e762..294ee1a6ba724 100644 --- a/server/application/application_test.go +++ b/server/application/application_test.go @@ -37,7 +37,6 @@ import ( "k8s.io/client-go/rest" kubetesting "k8s.io/client-go/testing" k8scache "k8s.io/client-go/tools/cache" - "k8s.io/utils/pointer" "k8s.io/utils/ptr" "sigs.k8s.io/yaml" @@ -915,7 +914,7 @@ func TestNoAppEnumeration(t *testing.T) { t.Run("RevisionMetadata", func(t *testing.T) { _, err := appServer.RevisionMetadata(adminCtx, &application.RevisionMetadataQuery{Name: ptr.To("test")}) assert.NoError(t, err) - _, err = appServer.RevisionMetadata(adminCtx, &application.RevisionMetadataQuery{Name: ptr.To("test-multi"), SourceIndex: ptr.To(int32(0)), VersionId: pointer.Int32(1)}) + _, err = appServer.RevisionMetadata(adminCtx, &application.RevisionMetadataQuery{Name: ptr.To("test-multi"), SourceIndex: ptr.To(int32(0)), VersionId: ptr.To(int32(1))}) assert.NoError(t, err) _, err = appServer.RevisionMetadata(noRoleCtx, &application.RevisionMetadataQuery{Name: ptr.To("test")}) assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence") From b11288415820910d9b1ce370e155a39aa8258b20 Mon Sep 17 00:00:00 2001 From: Jorge Turrado Date: Mon, 10 Jun 2024 01:27:24 +0200 Subject: [PATCH 15/16] fix reference Signed-off-by: Jorge Turrado --- controller/state.go | 2 +- reposerver/repository/repository_test.go | 10 +-- server/application/application.go | 2 +- .../resource-details/resource-details.tsx | 76 +++++++++---------- util/argo/argo.go | 12 +-- util/argo/argo_test.go | 10 ++- util/webhook/webhook.go | 9 ++- 7 files changed, 65 insertions(+), 56 deletions(-) diff --git a/controller/state.go b/controller/state.go index 0c0cf90e65eb3..d0e5a159287e9 100644 --- a/controller/state.go +++ b/controller/state.go @@ -180,7 +180,7 @@ func (m *appStateManager) GetRepoObjs(app *v1alpha1.Application, sources []v1alp // Store the map of all sources having ref field into a map for applications with sources field // If it's for a rollback process, the refSources[*].targetRevision fields are the desired // revisions for the rollback - refSources, err := argo.GetRefSources(context.Background(), app.Spec, m.db.GetRepository, revisions, rollback) + refSources, err := argo.GetRefSources(context.Background(), sources, app.Spec.Project, m.db.GetRepository, revisions, rollback) if err != nil { return nil, nil, fmt.Errorf("failed to get ref sources: %v", err) } diff --git a/reposerver/repository/repository_test.go b/reposerver/repository/repository_test.go index ccdd29231fca1..f5f0735078940 100644 --- a/reposerver/repository/repository_test.go +++ b/reposerver/repository/repository_test.go @@ -499,7 +499,7 @@ func TestHelmChartReferencingExternalValues(t *testing.T) { {Ref: "ref", RepoURL: "https://git.example.com/test/repo"}, }, } - refSources, err := argo.GetRefSources(context.Background(), spec, func(ctx context.Context, url string, project string) (*argoappv1.Repository, error) { + refSources, err := argo.GetRefSources(context.Background(), spec.Sources, spec.Project, func(ctx context.Context, url string, project string) (*argoappv1.Repository, error) { return &argoappv1.Repository{ Repo: "https://git.example.com/test/repo", }, nil @@ -538,7 +538,7 @@ func TestHelmChartReferencingExternalValues_InvalidRefs(t *testing.T) { }, nil } - refSources, err := argo.GetRefSources(context.Background(), spec, getRepository, []string{}, false) + refSources, err := argo.GetRefSources(context.Background(), spec.Sources, spec.Project, getRepository, []string{}, false) require.NoError(t, err) request := &apiclient.ManifestRequest{Repo: &argoappv1.Repository{}, ApplicationSource: &spec.Sources[0], NoCache: true, RefSources: refSources, HasMultipleSources: true, ProjectName: "something", @@ -551,7 +551,7 @@ func TestHelmChartReferencingExternalValues_InvalidRefs(t *testing.T) { service = newService(t, ".") spec.Sources[1].Ref = "Invalid" - refSources, err = argo.GetRefSources(context.Background(), spec, getRepository, []string{}, false) + refSources, err = argo.GetRefSources(context.Background(), spec.Sources, spec.Project, getRepository, []string{}, false) require.NoError(t, err) request = &apiclient.ManifestRequest{Repo: &argoappv1.Repository{}, ApplicationSource: &spec.Sources[0], NoCache: true, RefSources: refSources, HasMultipleSources: true, ProjectName: "something", @@ -565,7 +565,7 @@ func TestHelmChartReferencingExternalValues_InvalidRefs(t *testing.T) { spec.Sources[1].Ref = "ref" spec.Sources[1].Chart = "helm-chart" - refSources, err = argo.GetRefSources(context.Background(), spec, getRepository, []string{}, false) + refSources, err = argo.GetRefSources(context.Background(), spec.Sources, spec.Project, getRepository, []string{}, false) require.NoError(t, err) request = &apiclient.ManifestRequest{Repo: &argoappv1.Repository{}, ApplicationSource: &spec.Sources[0], NoCache: true, RefSources: refSources, HasMultipleSources: true, ProjectName: "something", @@ -598,7 +598,7 @@ func TestHelmChartReferencingExternalValues_OutOfBounds_Symlink(t *testing.T) { {Ref: "ref", RepoURL: "https://git.example.com/test/repo"}, }, } - refSources, err := argo.GetRefSources(context.Background(), spec, func(ctx context.Context, url string, project string) (*argoappv1.Repository, error) { + refSources, err := argo.GetRefSources(context.Background(), spec.Sources, spec.Project, func(ctx context.Context, url string, project string) (*argoappv1.Repository, error) { return &argoappv1.Repository{ Repo: "https://git.example.com/test/repo", }, nil diff --git a/server/application/application.go b/server/application/application.go index 430ae0182088a..762a456969e8c 100644 --- a/server/application/application.go +++ b/server/application/application.go @@ -489,7 +489,7 @@ func (s *Server) GetManifests(ctx context.Context, q *application.ApplicationMan } // Store the map of all sources having ref field into a map for applications with sources field - refSources, err := argo.GetRefSources(context.Background(), *appSpec, s.db.GetRepository, []string{}, false) + refSources, err := argo.GetRefSources(context.Background(), sources, appSpec.Project, s.db.GetRepository, []string{}, false) if err != nil { return fmt.Errorf("failed to get ref sources: %v", err) } diff --git a/ui/src/app/applications/components/resource-details/resource-details.tsx b/ui/src/app/applications/components/resource-details/resource-details.tsx index e5fe64e5aa8c7..4f2b0043f250c 100644 --- a/ui/src/app/applications/components/resource-details/resource-details.tsx +++ b/ui/src/app/applications/components/resource-details/resource-details.tsx @@ -1,24 +1,24 @@ -import { DataLoader, DropDown, Tab, Tabs } from 'argo-ui'; +import {DataLoader, DropDown, Tab, Tabs} from 'argo-ui'; import * as React from 'react'; -import { useState } from 'react'; -import { EventsList, YamlEditor } from '../../../shared/components'; +import {useState} from 'react'; +import {EventsList, YamlEditor} from '../../../shared/components'; import * as models from '../../../shared/models'; -import { ErrorBoundary } from '../../../shared/components/error-boundary/error-boundary'; -import { Context } from '../../../shared/context'; -import { Application, ApplicationTree, AppSourceType, Event, RepoAppDetails, ResourceNode, State, SyncStatuses } from '../../../shared/models'; -import { services } from '../../../shared/services'; -import { ResourceTabExtension } from '../../../shared/services/extensions-service'; -import { NodeInfo, SelectNode } from '../application-details/application-details'; -import { ApplicationNodeInfo } from '../application-node-info/application-node-info'; -import { ApplicationParameters } from '../application-parameters/application-parameters'; -import { ApplicationResourceEvents } from '../application-resource-events/application-resource-events'; -import { ResourceTreeNode } from '../application-resource-tree/application-resource-tree'; -import { ApplicationResourcesDiff } from '../application-resources-diff/application-resources-diff'; -import { ApplicationSummary } from '../application-summary/application-summary'; -import { PodsLogsViewer } from '../pod-logs-viewer/pod-logs-viewer'; -import { PodTerminalViewer } from '../pod-terminal-viewer/pod-terminal-viewer'; -import { ResourceIcon } from '../resource-icon'; -import { ResourceLabel } from '../resource-label'; +import {ErrorBoundary} from '../../../shared/components/error-boundary/error-boundary'; +import {Context} from '../../../shared/context'; +import {Application, ApplicationTree, AppSourceType, Event, RepoAppDetails, ResourceNode, State, SyncStatuses} from '../../../shared/models'; +import {services} from '../../../shared/services'; +import {ResourceTabExtension} from '../../../shared/services/extensions-service'; +import {NodeInfo, SelectNode} from '../application-details/application-details'; +import {ApplicationNodeInfo} from '../application-node-info/application-node-info'; +import {ApplicationParameters} from '../application-parameters/application-parameters'; +import {ApplicationResourceEvents} from '../application-resource-events/application-resource-events'; +import {ResourceTreeNode} from '../application-resource-tree/application-resource-tree'; +import {ApplicationResourcesDiff} from '../application-resources-diff/application-resources-diff'; +import {ApplicationSummary} from '../application-summary/application-summary'; +import {PodsLogsViewer} from '../pod-logs-viewer/pod-logs-viewer'; +import {PodTerminalViewer} from '../pod-terminal-viewer/pod-terminal-viewer'; +import {ResourceIcon} from '../resource-icon'; +import {ResourceLabel} from '../resource-label'; import * as AppUtils from '../utils'; import './resource-details.scss'; @@ -26,7 +26,7 @@ const jsonMergePatch = require('json-merge-patch'); interface ResourceDetailsProps { selectedNode: ResourceNode; - updateApp: (app: Application, query: { validate?: boolean }) => Promise; + updateApp: (app: Application, query: {validate?: boolean}) => Promise; application: Application; isAppSelected: boolean; tree: ApplicationTree; @@ -34,7 +34,7 @@ interface ResourceDetailsProps { } export const ResourceDetails = (props: ResourceDetailsProps) => { - const { selectedNode, updateApp, application, isAppSelected, tree } = { ...props }; + const {selectedNode, updateApp, application, isAppSelected, tree} = {...props}; const [activeContainer, setActiveContainer] = useState(); const appContext = React.useContext(Context); const tab = new URLSearchParams(appContext.history.location.search).get('tab'); @@ -159,7 +159,7 @@ export const ResourceDetails = (props: ResourceDetailsProps) => { { title: 'SUMMARY', key: 'summary', - content: updateApp(app, query)} /> + content: updateApp(app, query)} /> }, { title: 'SOURCES', @@ -168,7 +168,7 @@ export const ResourceDetails = (props: ResourceDetailsProps) => { getSources(app)}> {(details: RepoAppDetails[]) => ( updateApp(app, query)} + save={(app: models.Application, query: {validate?: boolean}) => updateApp(app, query)} application={application} details={details[0]} detailsList={details} @@ -233,7 +233,7 @@ export const ResourceDetails = (props: ResourceDetailsProps) => { const extensions = selectedNode?.kind ? services.extensions.getResourceTabs(selectedNode?.group || '', selectedNode?.kind) : []; return ( -
+
{selectedNode && ( { }); const controlled = managedResources.find(item => AppUtils.isSameNode(selectedNode, item)); const summary = application.status.resources.find(item => AppUtils.isSameNode(selectedNode, item)); - const controlledState = (controlled && summary && { summary, state: controlled }) || null; - const resQuery = { ...selectedNode }; + const controlledState = (controlled && summary && {summary, state: controlled}) || null; + const resQuery = {...selectedNode}; if (controlled && controlled.targetState) { resQuery.version = AppUtils.parseApiVersion(controlled.targetState.apiVersion).version; } @@ -280,33 +280,33 @@ export const ResourceDetails = (props: ResourceDetailsProps) => { const logsAllowed = await services.accounts.canI('logs', 'get', application.spec.project + '/' + application.metadata.name); const execAllowed = execEnabled && (await services.accounts.canI('exec', 'create', application.spec.project + '/' + application.metadata.name)); const links = await services.applications.getResourceLinks(application.metadata.name, application.metadata.namespace, selectedNode).catch(() => null); - return { controlledState, liveState, events, podState, execEnabled, execAllowed, logsAllowed, links, childResources }; + return {controlledState, liveState, events, podState, execEnabled, execAllowed, logsAllowed, links, childResources}; }}> {data => (
-
+
- {ResourceLabel({ kind: selectedNode.kind })} + {ResourceLabel({kind: selectedNode.kind})}

{selectedNode.name}

{data.controlledState && ( - + )} {(selectedNode as ResourceTreeNode).health && } @@ -349,7 +349,7 @@ export const ResourceDetails = (props: ResourceDetailsProps) => { data.logsAllowed )} selectedTabKey={props.tab} - onTabSelected={selected => appContext.navigation.goto('.', { tab: selected }, { replace: true })} + onTabSelected={selected => appContext.navigation.goto('.', {tab: selected}, {replace: true})} /> )} @@ -360,7 +360,7 @@ export const ResourceDetails = (props: ResourceDetailsProps) => { navTransparent={true} tabs={getApplicationTabs()} selectedTabKey={tab} - onTabSelected={selected => appContext.navigation.goto('.', { tab: selected }, { replace: true })} + onTabSelected={selected => appContext.navigation.goto('.', {tab: selected}, {replace: true})} /> )}
@@ -369,13 +369,13 @@ export const ResourceDetails = (props: ResourceDetailsProps) => { // Maintain compatibility with single source field. Remove else block when source field is removed async function getSources(app: models.Application) { - const listOfDetails = new Array(); + const listOfDetails = new Array(); const sources: models.ApplicationSource[] = app.spec.sources; if (sources) { const length = sources.length; for (let i = 0; i < length; i++) { const aSource = sources[i]; - const repoDetail = await services.repos.appDetails(aSource, app.metadata.name, app.spec.project).catch(() => ({ + const repoDetail = await services.repos.appDetails(aSource, app.metadata.name, app.spec.project, i, 0).catch(() => ({ type: 'Directory' as AppSourceType, path: aSource.path })); @@ -385,7 +385,7 @@ async function getSources(app: models.Application) { } return listOfDetails; } else { - const repoDetail = await services.repos.appDetails(AppUtils.getAppDefaultSource(app), app.metadata.name, app.spec.project).catch(() => ({ + const repoDetail = await services.repos.appDetails(AppUtils.getAppDefaultSource(app), app.metadata.name, app.spec.project, 0, 0).catch(() => ({ type: 'Directory' as AppSourceType, path: AppUtils.getAppDefaultSource(app).path })); diff --git a/util/argo/argo.go b/util/argo/argo.go index 345fc61e7c70a..b65461c728de0 100644 --- a/util/argo/argo.go +++ b/util/argo/argo.go @@ -418,7 +418,7 @@ func validateRepo(ctx context.Context, } } - refSources, err := GetRefSources(ctx, app.Spec, db.GetRepository, []string{}, false) + refSources, err := GetRefSources(ctx, sources, app.Spec.Project, db.GetRepository, []string{}, false) if err != nil { return nil, fmt.Errorf("error getting ref sources: %w", err) } @@ -446,12 +446,12 @@ func validateRepo(ctx context.Context, // GetRefSources creates a map of ref keys (from the sources' 'ref' fields) to information about the referenced source. // This function also validates the references use allowed characters and does not define the same ref key more than // once (which would lead to ambiguous references). -func GetRefSources(ctx context.Context, spec argoappv1.ApplicationSpec, getRepository func(ctx context.Context, url string, project string) (*argoappv1.Repository, error), revisions []string, isRollback bool) (argoappv1.RefTargetRevisionMapping, error) { +func GetRefSources(ctx context.Context, sources argoappv1.ApplicationSources, project string, getRepository func(ctx context.Context, url string, project string) (*argoappv1.Repository, error), revisions []string, isRollback bool) (argoappv1.RefTargetRevisionMapping, error) { refSources := make(argoappv1.RefTargetRevisionMapping) - if spec.HasMultipleSources() { + if len(sources) > 1 { // Validate first to avoid unnecessary DB calls. refKeys := make(map[string]bool) - for _, source := range spec.Sources { + for _, source := range sources { if source.Ref != "" { isValidRefKey := regexp.MustCompile(`^[a-zA-Z0-9_-]+$`).MatchString if !isValidRefKey(source.Ref) { @@ -465,9 +465,9 @@ func GetRefSources(ctx context.Context, spec argoappv1.ApplicationSpec, getRepos } } // Get Repositories for all sources before generating Manifests - for i, source := range spec.Sources { + for i, source := range sources { if source.Ref != "" { - repo, err := getRepository(ctx, source.RepoURL, spec.Project) + repo, err := getRepository(ctx, source.RepoURL, project) if err != nil { return nil, fmt.Errorf("failed to get repository %s: %v", source.RepoURL, err) } diff --git a/util/argo/argo_test.go b/util/argo/argo_test.go index 7b1a593d02a4f..4b24c12467ea0 100644 --- a/util/argo/argo_test.go +++ b/util/argo/argo_test.go @@ -1275,7 +1275,7 @@ func Test_GetRefSources(t *testing.T) { {RepoURL: fmt.Sprintf("file://%s", repoPath)}, }) - refSources, err := GetRefSources(context.Background(), *argoSpec, func(ctx context.Context, url string, project string) (*argoappv1.Repository, error) { + refSources, err := GetRefSources(context.Background(), argoSpec.Sources, argoSpec.Project, func(ctx context.Context, url string, project string) (*argoappv1.Repository, error) { return repo, nil }, []string{}, false) @@ -1292,9 +1292,10 @@ func Test_GetRefSources(t *testing.T) { t.Run("target ref does not exist", func(t *testing.T) { argoSpec := getMultiSourceAppSpec(argoappv1.ApplicationSources{ {RepoURL: "file://does-not-exist", Ref: "source1"}, + {RepoURL: fmt.Sprintf("file://%s", repoPath)}, }) - refSources, err := GetRefSources(context.Background(), *argoSpec, func(ctx context.Context, url string, project string) (*argoappv1.Repository, error) { + refSources, err := GetRefSources(context.Background(), argoSpec.Sources, argoSpec.Project, func(ctx context.Context, url string, project string) (*argoappv1.Repository, error) { return nil, errors.New("repo does not exist") }, []string{}, false) @@ -1305,9 +1306,10 @@ func Test_GetRefSources(t *testing.T) { t.Run("invalid ref", func(t *testing.T) { argoSpec := getMultiSourceAppSpec(argoappv1.ApplicationSources{ {RepoURL: "file://does-not-exist", Ref: "%invalid-name%"}, + {RepoURL: fmt.Sprintf("file://%s", repoPath)}, }) - refSources, err := GetRefSources(context.TODO(), *argoSpec, func(ctx context.Context, url string, project string) (*argoappv1.Repository, error) { + refSources, err := GetRefSources(context.TODO(), argoSpec.Sources, argoSpec.Project, func(ctx context.Context, url string, project string) (*argoappv1.Repository, error) { return nil, err }, []string{}, false) @@ -1321,7 +1323,7 @@ func Test_GetRefSources(t *testing.T) { {RepoURL: "file://does-not-exist", Ref: "source1"}, }) - refSources, err := GetRefSources(context.TODO(), *argoSpec, func(ctx context.Context, url string, project string) (*argoappv1.Repository, error) { + refSources, err := GetRefSources(context.TODO(), argoSpec.Sources, argoSpec.Project, func(ctx context.Context, url string, project string) (*argoappv1.Repository, error) { return nil, err }, []string{}, false) diff --git a/util/webhook/webhook.go b/util/webhook/webhook.go index 7ce7bea420fa9..d56e41861963a 100644 --- a/util/webhook/webhook.go +++ b/util/webhook/webhook.go @@ -344,7 +344,14 @@ func (a *ArgoCDWebhookHandler) storePreviouslyCachedManifests(app *v1alpha1.Appl return fmt.Errorf("error getting cluster info: %w", err) } - refSources, err := argo.GetRefSources(context.Background(), app.Spec, a.db.GetRepository, []string{}, false) + var sources v1alpha1.ApplicationSources + if app.Spec.HasMultipleSources() { + sources = app.Spec.GetSources() + } else { + sources = append(sources, app.Spec.GetSource()) + } + + refSources, err := argo.GetRefSources(context.Background(), sources, app.Spec.Project, a.db.GetRepository, []string{}, false) if err != nil { return fmt.Errorf("error getting ref sources: %w", err) } From 49a8b31457436d0318c92388cea7c80ff2e52f9e Mon Sep 17 00:00:00 2001 From: Jorge Turrado Date: Mon, 10 Jun 2024 19:27:02 +0200 Subject: [PATCH 16/16] add a comment Signed-off-by: Jorge Turrado --- server/application/application.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/server/application/application.go b/server/application/application.go index 762a456969e8c..ade1bb6d1e83f 100644 --- a/server/application/application.go +++ b/server/application/application.go @@ -1547,6 +1547,12 @@ func (s *Server) RevisionMetadata(ctx context.Context, q *application.RevisionMe // using the specific revisionId for _, h := range a.Status.History { if h.ID == versionId { + // The iteration values are assigned to the respective iteration variables as in an assignment statement. + // The iteration variables may be declared by the “range” clause using a form of short variable declaration (:=). + // In this case their types are set to the types of the respective iteration values and their scope is the block of the "for" statement; + // they are re-used in each iteration. If the iteration variables are declared outside the "for" statement, + // after execution their values will be those of the last iteration. + // https://golang.org/ref/spec#For_statements h := h if isRevisionMultiSource { source = &h.Sources[*q.SourceIndex]