Skip to content

Commit

Permalink
fix(server): could not find source for metadata revision (#18744) (#1…
Browse files Browse the repository at this point in the history
…8763) (#18782)

* fix(server): could not find source for metadata revision (#18744)



* lint



* the linter behaves poorly



* fix test



* share logic with chart endpoint



* more intuitive check



* remove debug line



---------

Signed-off-by: Michael Crenshaw <[email protected]>
Co-authored-by: Michael Crenshaw <[email protected]>
  • Loading branch information
gcp-cherry-pick-bot[bot] and crenshaw-dev authored Jun 24, 2024
1 parent 2a9a62e commit 16c20a8
Show file tree
Hide file tree
Showing 6 changed files with 373 additions and 101 deletions.
157 changes: 76 additions & 81 deletions server/application/application.go
Original file line number Diff line number Diff line change
Expand Up @@ -1495,71 +1495,9 @@ 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
// 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 == 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 {
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 == 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]
} else {
source = &h.Source
}
}
}
}
if source == nil {
return nil, fmt.Errorf("revision not found: %w", err)
source, err := getAppSourceBySourceIndexAndVersionId(a, q.SourceIndex, q.VersionId)
if err != nil {
return nil, fmt.Errorf("error getting app source by source index and version ID: %w", err)
}

repo, err := s.db.GetRepository(ctx, source.RepoURL, proj.Name)
Expand All @@ -1585,22 +1523,9 @@ func (s *Server) RevisionChartDetails(ctx context.Context, q *application.Revisi
return nil, err
}

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
source, err := getAppSourceBySourceIndexAndVersionId(a, q.SourceIndex, q.VersionId)
if err != nil {
return nil, fmt.Errorf("error getting app source by source index and version ID: %w", err)
}

if source.Chart == "" {
Expand All @@ -1622,6 +1547,76 @@ func (s *Server) RevisionChartDetails(ctx context.Context, q *application.Revisi
})
}

// getAppSourceBySourceIndexAndVersionId returns the source for a specific source index and version ID. Source index and
// version ID are optional. If the source index is not specified, it defaults to 0. If the version ID is not specified,
// we use the source(s) currently configured for the app. If the version ID is specified, we find the source for that
// version ID. If the version ID is not found, we return an error. If the source index is out of bounds for whichever
// source we choose (configured sources or sources for a specific version), we return an error.
func getAppSourceBySourceIndexAndVersionId(a *appv1.Application, sourceIndexMaybe *int32, versionIdMaybe *int32) (appv1.ApplicationSource, error) {
// Start with all the app's configured sources.
sources := a.Spec.GetSources()

// If the user specified a version, get the sources for that version. If the version is not found, return an error.
if versionIdMaybe != nil {
versionId := int64(*versionIdMaybe)
var err error
sources, err = getSourcesByVersionId(a, versionId)
if err != nil {
return appv1.ApplicationSource{}, fmt.Errorf("error getting source by version ID: %w", err)
}
}

// Start by assuming we want the first source.
sourceIndex := 0

// If the user specified a source index, use that instead.
if sourceIndexMaybe != nil {
sourceIndex = int(*sourceIndexMaybe)
if sourceIndex >= len(sources) {
if len(sources) == 1 {
return appv1.ApplicationSource{}, fmt.Errorf("source index %d not found because there is only 1 source", sourceIndex)
}
return appv1.ApplicationSource{}, fmt.Errorf("source index %d not found because there are only %d sources", sourceIndex, len(sources))
}
}

source := sources[sourceIndex]

return source, nil
}

// getRevisionHistoryByVersionId returns the revision history for a specific version ID.
// If the version ID is not found, it returns an empty revision history and false.
func getRevisionHistoryByVersionId(histories v1alpha1.RevisionHistories, versionId int64) (appv1.RevisionHistory, bool) {
for _, h := range histories {
if h.ID == versionId {
return h, true
}
}
return appv1.RevisionHistory{}, false
}

// getSourcesByVersionId returns the sources for a specific version ID. If there is no history, it returns an error.
// If the version ID is not found, it returns an error. If the version ID is found, and there are multiple sources,
// it returns the sources for that version ID. If the version ID is found, and there is only one source, it returns
// a slice with just the single source.
func getSourcesByVersionId(a *appv1.Application, versionId int64) ([]appv1.ApplicationSource, error) {
if len(a.Status.History) == 0 {
return nil, fmt.Errorf("version ID %d not found because the app has no history", versionId)
}

h, ok := getRevisionHistoryByVersionId(a.Status.History, versionId)
if !ok {
return nil, fmt.Errorf("revision history not found for version ID %d", versionId)
}

if len(h.Sources) > 0 {
return h.Sources, nil
}

return []v1alpha1.ApplicationSource{h.Source}, nil
}

func isMatchingResource(q *application.ResourcesQuery, key kube.ResourceKey) bool {
return (q.GetName() == "" || q.GetName() == key.Name) &&
(q.GetNamespace() == "" || q.GetNamespace() == key.Namespace) &&
Expand Down
Loading

0 comments on commit 16c20a8

Please sign in to comment.