Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(application-controller): Add support for rollback multi-source applications #14124

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions assets/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down Expand Up @@ -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": {
Expand Down Expand Up @@ -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)"
}
}
},
Expand Down
2 changes: 1 addition & 1 deletion cmd/argocd/commands/admin/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
4 changes: 2 additions & 2 deletions cmd/argocd/commands/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down
36 changes: 36 additions & 0 deletions cmd/argocd/commands/app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}

}

Expand Down
2 changes: 1 addition & 1 deletion controller/appcontroller.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion controller/hook.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
15 changes: 8 additions & 7 deletions controller/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -178,7 +178,9 @@ 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(), sources, app.Spec.Project, m.db.GetRepository, revisions, rollback)
if err != nil {
return nil, nil, fmt.Errorf("failed to get ref sources: %v", err)
}
Expand Down Expand Up @@ -264,7 +266,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)
}

Expand Down Expand Up @@ -395,7 +396,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()

Expand Down Expand Up @@ -453,7 +454,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())
Expand Down
Loading