Skip to content

Commit b927287

Browse files
JGAntunessgalsaleh
andauthored
feat(updates): hide/show demoted/un-demoted releases when updated upstream (#5505)
* feat(updates): hide/show demoted/un-demoted releases when updated upstream * fix: add missing column * chore: cleanup unused logic * chore: feedback * fix: edge cases around app demotion * Update pkg/updatechecker/updatechecker.go Co-authored-by: Salah Al Saleh <[email protected]> --------- Co-authored-by: Salah Al Saleh <[email protected]>
1 parent 49e6a88 commit b927287

File tree

7 files changed

+464
-5
lines changed

7 files changed

+464
-5
lines changed

migrations/tables/app_version.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ spec:
3939
constraints:
4040
notNull: true
4141
default: 0
42+
- name: is_demoted
43+
type: integer
44+
constraints:
45+
notNull: true
46+
default: 0
4247
- name: release_notes
4348
type: text
4449
- name: supportbundle_spec

pkg/store/kotsstore/downstream_store.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -379,7 +379,8 @@ func (s *KOTSStore) GetDownstreamVersions(appID string, clusterID string, downlo
379379
adv.app_id = ado.app_id AND adv.cluster_id = ado.cluster_id AND adv.sequence = ado.downstream_sequence
380380
WHERE
381381
adv.app_id = ? AND
382-
adv.cluster_id = ?`
382+
adv.cluster_id = ? AND
383+
av.is_demoted = false`
383384

384385
if downloadedOnly {
385386
query += fmt.Sprintf(` AND adv.status != '%s'`, types.VersionPendingDownload)

pkg/store/kotsstore/version_store.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -975,6 +975,25 @@ func (s *KOTSStore) UpdateAppVersionMetadata(appID string, update upstreamtypes.
975975
return nil
976976
}
977977

978+
// UpdateAppVersionDemotion updates the is_demoted field for a specific app version.
979+
func (s *KOTSStore) UpdateAppVersionDemotion(appID, channelID, cursor string, isDemoted bool) error {
980+
db := persistence.MustGetDBSession()
981+
982+
query := `UPDATE app_version
983+
SET is_demoted = ?
984+
WHERE app_id = ? AND channel_id = ? AND update_cursor = ?`
985+
986+
_, err := db.WriteOneParameterized(gorqlite.ParameterizedStatement{
987+
Query: query,
988+
Arguments: []interface{}{isDemoted, appID, channelID, cursor},
989+
})
990+
if err != nil {
991+
return fmt.Errorf("update app version is demoted: %v", err)
992+
}
993+
994+
return nil
995+
}
996+
978997
func (s *KOTSStore) HasStrictPreflights(appID string, sequence int64) (bool, error) {
979998
var preflightSpecStr gorqlite.NullString
980999
db := persistence.MustGetDBSession()

pkg/store/mock/mock.go

Lines changed: 28 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/store/store_interface.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ type VersionStore interface {
191191
GetNextAppSequence(appID string) (int64, error)
192192
GetCurrentUpdateCursor(appID string, channelID string) (string, error)
193193
UpdateAppVersionMetadata(appID string, update upstreamtypes.Update) error
194+
UpdateAppVersionDemotion(appID, channelID, cursor string, isDemoted bool) error
194195
HasStrictPreflights(appID string, sequence int64) (bool, error)
195196
GetEmbeddedClusterConfigForVersion(appID string, sequence int64) (*embeddedclusterv1beta1.Config, error)
196197
}

pkg/updatechecker/updatechecker.go

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ var jobs = make(map[string]*cron.Cron)
3737
var mtx sync.Mutex
3838
var store storepkg.Store
3939

40+
// getUpdates is a package-level variable that can be replaced in tests for mocking
41+
var getUpdates = kotspull.GetUpdates
42+
4043
// Start will start the update checker
4144
// the frequency of those update checks are app specific and can be modified by the user
4245
func Start() error {
@@ -251,7 +254,7 @@ func checkForKotsAppUpdates(opts types.CheckForUpdatesOpts, finishedChan chan<-
251254
}
252255

253256
// get updates
254-
updates, err := kotspull.GetUpdates(fmt.Sprintf("replicated://%s", latestLicense.Spec.AppSlug), getUpdatesOptions)
257+
updates, err := getUpdates(fmt.Sprintf("replicated://%s", latestLicense.Spec.AppSlug), getUpdatesOptions)
255258
if err != nil {
256259
return nil, errors.Wrap(err, "failed to get updates")
257260
}
@@ -274,7 +277,7 @@ func checkForKotsAppUpdates(opts types.CheckForUpdatesOpts, finishedChan chan<-
274277
return nil, errors.Errorf("no app versions found for app %s in downstream %s", opts.AppID, d.ClusterID)
275278
}
276279

277-
if err := maybeUpdatePendingVersionsMetadata(a.ID, getUpdatesOptions, appVersions.CurrentVersion); err != nil {
280+
if err := maybeUpdatePendingVersionsMetadata(a.ID, getUpdatesOptions, appVersions.CurrentVersion, appVersions.PendingVersions); err != nil {
278281
logger.Error(errors.Wrap(err, "failed to update app version metadata"))
279282
}
280283

@@ -337,13 +340,18 @@ func checkForKotsAppUpdates(opts types.CheckForUpdatesOpts, finishedChan chan<-
337340
return &ucr, nil
338341
}
339342

343+
// getVersionKey builds a string given a chhanel ID and cursor to be used as a key for version lookup maps with the format <channelID>-<cursor>
344+
func getVersionKey(channelID, cursor string) string {
345+
return fmt.Sprintf("%s-%s", channelID, cursor)
346+
}
347+
340348
// maybeUpdatePendingVersionsMetadata updates metadata for pending versions since the currently deployed version.
341349
//
342350
// Limitations:
343351
// - Only gets pending releases for the channel of the currently deployed version, even if channel changed in later versions
344352
// - Does not rerender the application archive, so the Installation object in the archive can become out of sync
345353
// - This is not in the critical path - errors are logged but don't fail the overall update check
346-
func maybeUpdatePendingVersionsMetadata(appID string, getUpdatesOptions kotspull.GetUpdatesOptions, currentVersion *downstreamtypes.DownstreamVersion) error {
354+
func maybeUpdatePendingVersionsMetadata(appID string, getUpdatesOptions kotspull.GetUpdatesOptions, currentVersion *downstreamtypes.DownstreamVersion, pendingVersions []*downstreamtypes.DownstreamVersion) error {
347355
if currentVersion == nil {
348356
return nil
349357
}
@@ -358,16 +366,51 @@ func maybeUpdatePendingVersionsMetadata(appID string, getUpdatesOptions kotspull
358366
getUpdatesOptions.CurrentChannelName = ""
359367
}
360368

361-
updates, err := kotspull.GetUpdates(fmt.Sprintf("replicated://%s", getUpdatesOptions.License.Spec.AppSlug), getUpdatesOptions)
369+
updates, err := getUpdates(fmt.Sprintf("replicated://%s", getUpdatesOptions.License.Spec.AppSlug), getUpdatesOptions)
362370
if err != nil {
363371
return errors.Wrap(err, "get updates for metadata refresh")
364372
}
365373

374+
// build a version map of the pending versions for fast lookup
375+
pendingVersionsMap := make(map[string]*downstreamtypes.DownstreamVersion)
376+
for _, pending := range pendingVersions {
377+
// We want to prevent mistakenly updating app versions by:
378+
// - only considering pending versions from the same channel as the currently deployed version
379+
// - making sure the cursor is different than the currently deployed version cursor, which can be the same when changing channels.
380+
if pending.ChannelID == currentVersion.ChannelID && pending.UpdateCursor != currentVersion.UpdateCursor {
381+
key := getVersionKey(pending.ChannelID, pending.UpdateCursor)
382+
pendingVersionsMap[key] = pending
383+
}
384+
}
385+
386+
// build a version map of the updates for fast lookup
387+
updateVersionsMap := make(map[string]*upstreamtypes.Update)
366388
for _, update := range updates.Updates {
389+
// update the app version metadata with the upstream info
367390
if err := store.UpdateAppVersionMetadata(appID, update); err != nil {
368391
logger.Error(errors.Wrapf(err, "failed to update app version metadata for %s", update.VersionLabel))
369392
}
393+
key := getVersionKey(update.ChannelID, update.Cursor)
394+
updateVersionsMap[key] = &update
395+
}
396+
397+
// Determine versions to demote: pending but not in updates
398+
for key, pending := range pendingVersionsMap {
399+
if _, exists := updateVersionsMap[key]; !exists {
400+
if err := store.UpdateAppVersionDemotion(appID, pending.ChannelID, pending.UpdateCursor, true); err != nil {
401+
logger.Error(errors.Wrapf(err, "failed to update app version demotion state for %s", pending.VersionLabel))
402+
}
403+
}
370404
}
405+
// Determine versions to un-demote: updates but not in pending
406+
for key, update := range updateVersionsMap {
407+
if _, exists := pendingVersionsMap[key]; !exists {
408+
if err := store.UpdateAppVersionDemotion(appID, update.ChannelID, update.Cursor, false); err != nil {
409+
logger.Error(errors.Wrapf(err, "failed to update app version demotion state for %s", update.VersionLabel))
410+
}
411+
}
412+
}
413+
371414
return nil
372415
}
373416

0 commit comments

Comments
 (0)