Skip to content

Commit

Permalink
add support for switching between single and multi
Browse files Browse the repository at this point in the history
Signed-off-by: Jorge Turrado <jorge_turrado@hotmail.es>
  • Loading branch information
JorTurFer committed Apr 14, 2024
1 parent aec91ff commit 7c752b6
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 41 deletions.
15 changes: 9 additions & 6 deletions controller/sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,14 +108,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)
Expand All @@ -137,7 +140,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
Expand All @@ -148,7 +151,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
}
Expand All @@ -171,13 +174,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()
Expand Down Expand Up @@ -393,7 +396,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)
Expand Down
2 changes: 1 addition & 1 deletion reposerver/repository/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -2007,7 +2007,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
}
Expand Down
64 changes: 47 additions & 17 deletions server/application/application.go
Original file line number Diff line number Diff line change
Expand Up @@ -1484,29 +1484,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)
Expand Down
70 changes: 53 additions & 17 deletions server/repository/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -343,13 +343,19 @@ 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,
Repos: helmRepos,
KustomizeOptions: kustomizeOptions,
HelmOptions: helmOptions,
AppName: q.AppName,
RefSources: refSources,
})
}

Expand Down Expand Up @@ -560,18 +566,31 @@ 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 {
if source.Equals(&s) {
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
Expand All @@ -581,24 +600,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
}

0 comments on commit 7c752b6

Please sign in to comment.