From 0bc1da2151df54d1a1101169b70977b15fe69ebd Mon Sep 17 00:00:00 2001 From: Marc Campbell Date: Sun, 17 May 2026 09:48:36 -0600 Subject: [PATCH 1/3] get latest release should not include demoted --- client/channel.go | 42 +++++++++++++++++++++++--------- client/channel_test.go | 54 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 11 deletions(-) create mode 100644 client/channel_test.go diff --git a/client/channel.go b/client/channel.go index f352f4d1e..459f450fd 100644 --- a/client/channel.go +++ b/client/channel.go @@ -230,12 +230,7 @@ func (c *Client) GetCurrentChannelRelease(appID string, appType string, channelI // If the channel has releases data, find the current one if len(kotsChannel.Releases) > 0 { - var currentRelease *types.ChannelRelease - for _, release := range kotsChannel.Releases { - if currentRelease == nil || release.ChannelSequence > currentRelease.ChannelSequence { - currentRelease = &release - } - } + currentRelease := currentChannelRelease(kotsChannel.Releases) if currentRelease != nil { proxyDomain := currentRelease.ProxyRegistryDomain if proxyDomain == "" && kotsChannel.CustomHostNameOverrides.Proxy.Hostname != "" { @@ -257,6 +252,7 @@ func (c *Client) GetCurrentChannelRelease(appID string, appType string, channelI } return currentRelease, proxyDomain, nil } + return nil, "", errors.New("no active releases found in channel") } // Fallback to the existing approach if releases aren't included @@ -269,11 +265,9 @@ func (c *Client) GetCurrentChannelRelease(appID string, appType string, channelI return nil, "", errors.New("no releases found in channel") } - var currentRelease *types.ChannelRelease - for _, release := range releases { - if currentRelease == nil || release.ChannelSequence > currentRelease.ChannelSequence { - currentRelease = release - } + currentRelease := currentChannelReleasePtrs(releases) + if currentRelease == nil { + return nil, "", errors.New("no active releases found in channel") } proxyDomain := currentRelease.ProxyRegistryDomain @@ -300,6 +294,32 @@ func (c *Client) GetCurrentChannelRelease(appID string, appType string, channelI return nil, "", errors.Errorf("unknown app type %q", appType) } +func currentChannelRelease(releases []types.ChannelRelease) *types.ChannelRelease { + var currentRelease *types.ChannelRelease + for i := range releases { + if releases[i].IsDemoted { + continue + } + if currentRelease == nil || releases[i].ChannelSequence > currentRelease.ChannelSequence { + currentRelease = &releases[i] + } + } + return currentRelease +} + +func currentChannelReleasePtrs(releases []*types.ChannelRelease) *types.ChannelRelease { + var currentRelease *types.ChannelRelease + for _, release := range releases { + if release == nil || release.IsDemoted { + continue + } + if currentRelease == nil || release.ChannelSequence > currentRelease.ChannelSequence { + currentRelease = release + } + } + return currentRelease +} + // GetDefaultProxyHostname gets the default proxy hostname from the app's custom hostnames func (c *Client) GetDefaultProxyHostname(appID string) (string, error) { customHostnames, err := c.KotsClient.ListCustomHostnames(appID) diff --git a/client/channel_test.go b/client/channel_test.go new file mode 100644 index 000000000..6a51f9516 --- /dev/null +++ b/client/channel_test.go @@ -0,0 +1,54 @@ +package client + +import ( + "testing" + + "github.com/replicatedhq/replicated/pkg/types" + "github.com/stretchr/testify/require" +) + +func TestCurrentChannelReleaseSkipsDemotedReleases(t *testing.T) { + releases := []types.ChannelRelease{ + {ChannelSequence: 1}, + {ChannelSequence: 2, IsDemoted: true}, + } + + currentRelease := currentChannelRelease(releases) + + require.NotNil(t, currentRelease) + require.EqualValues(t, 1, currentRelease.ChannelSequence) +} + +func TestCurrentChannelReleaseReturnsNilWhenAllReleasesAreDemoted(t *testing.T) { + releases := []types.ChannelRelease{ + {ChannelSequence: 1, IsDemoted: true}, + {ChannelSequence: 2, IsDemoted: true}, + } + + currentRelease := currentChannelRelease(releases) + + require.Nil(t, currentRelease) +} + +func TestCurrentChannelReleasePtrsSkipsDemotedReleases(t *testing.T) { + releases := []*types.ChannelRelease{ + {ChannelSequence: 1}, + {ChannelSequence: 2, IsDemoted: true}, + } + + currentRelease := currentChannelReleasePtrs(releases) + + require.NotNil(t, currentRelease) + require.EqualValues(t, 1, currentRelease.ChannelSequence) +} + +func TestCurrentChannelReleasePtrsReturnsNilWhenAllReleasesAreDemoted(t *testing.T) { + releases := []*types.ChannelRelease{ + {ChannelSequence: 1, IsDemoted: true}, + {ChannelSequence: 2, IsDemoted: true}, + } + + currentRelease := currentChannelReleasePtrs(releases) + + require.Nil(t, currentRelease) +} From ec732e0f084a721709e20e8e1796713d4053f87a Mon Sep 17 00:00:00 2001 From: Marc Campbell Date: Mon, 18 May 2026 14:00:02 -0700 Subject: [PATCH 2/3] fix: use channel's ChannelSequence to identify current release Co-Authored-By: Claude Sonnet 4.6 (1M context) --- client/channel.go | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/client/channel.go b/client/channel.go index 459f450fd..bb929cba6 100644 --- a/client/channel.go +++ b/client/channel.go @@ -230,7 +230,7 @@ func (c *Client) GetCurrentChannelRelease(appID string, appType string, channelI // If the channel has releases data, find the current one if len(kotsChannel.Releases) > 0 { - currentRelease := currentChannelRelease(kotsChannel.Releases) + currentRelease := currentChannelRelease(kotsChannel.Releases, kotsChannel.ChannelSequence) if currentRelease != nil { proxyDomain := currentRelease.ProxyRegistryDomain if proxyDomain == "" && kotsChannel.CustomHostNameOverrides.Proxy.Hostname != "" { @@ -265,7 +265,7 @@ func (c *Client) GetCurrentChannelRelease(appID string, appType string, channelI return nil, "", errors.New("no releases found in channel") } - currentRelease := currentChannelReleasePtrs(releases) + currentRelease := currentChannelReleasePtrs(releases, kotsChannel.ChannelSequence) if currentRelease == nil { return nil, "", errors.New("no active releases found in channel") } @@ -294,7 +294,17 @@ func (c *Client) GetCurrentChannelRelease(appID string, appType string, channelI return nil, "", errors.Errorf("unknown app type %q", appType) } -func currentChannelRelease(releases []types.ChannelRelease) *types.ChannelRelease { +// currentChannelRelease returns the release matching channelSequence (the server's +// authoritative current sequence). Falls back to the highest non-demoted sequence +// when channelSequence is 0 (not returned by the API). +func currentChannelRelease(releases []types.ChannelRelease, channelSequence int32) *types.ChannelRelease { + if channelSequence > 0 { + for i := range releases { + if releases[i].ChannelSequence == channelSequence { + return &releases[i] + } + } + } var currentRelease *types.ChannelRelease for i := range releases { if releases[i].IsDemoted { @@ -307,7 +317,14 @@ func currentChannelRelease(releases []types.ChannelRelease) *types.ChannelReleas return currentRelease } -func currentChannelReleasePtrs(releases []*types.ChannelRelease) *types.ChannelRelease { +func currentChannelReleasePtrs(releases []*types.ChannelRelease, channelSequence int32) *types.ChannelRelease { + if channelSequence > 0 { + for _, release := range releases { + if release != nil && release.ChannelSequence == channelSequence { + return release + } + } + } var currentRelease *types.ChannelRelease for _, release := range releases { if release == nil || release.IsDemoted { From 9e6211fe385016a33a96982649bb621b46ca6abf Mon Sep 17 00:00:00 2001 From: Marc Campbell Date: Mon, 18 May 2026 14:12:29 -0700 Subject: [PATCH 3/3] fix: update tests for currentChannelRelease signature change Co-Authored-By: Claude Sonnet 4.6 (1M context) --- client/channel_test.go | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/client/channel_test.go b/client/channel_test.go index 6a51f9516..f00c2f301 100644 --- a/client/channel_test.go +++ b/client/channel_test.go @@ -7,13 +7,26 @@ import ( "github.com/stretchr/testify/require" ) +func TestCurrentChannelReleaseUsesChannelSequence(t *testing.T) { + releases := []types.ChannelRelease{ + {ChannelSequence: 1}, + {ChannelSequence: 2}, + {ChannelSequence: 3, IsDemoted: true}, + } + + currentRelease := currentChannelRelease(releases, 2) + + require.NotNil(t, currentRelease) + require.EqualValues(t, 2, currentRelease.ChannelSequence) +} + func TestCurrentChannelReleaseSkipsDemotedReleases(t *testing.T) { releases := []types.ChannelRelease{ {ChannelSequence: 1}, {ChannelSequence: 2, IsDemoted: true}, } - currentRelease := currentChannelRelease(releases) + currentRelease := currentChannelRelease(releases, 0) require.NotNil(t, currentRelease) require.EqualValues(t, 1, currentRelease.ChannelSequence) @@ -25,18 +38,31 @@ func TestCurrentChannelReleaseReturnsNilWhenAllReleasesAreDemoted(t *testing.T) {ChannelSequence: 2, IsDemoted: true}, } - currentRelease := currentChannelRelease(releases) + currentRelease := currentChannelRelease(releases, 0) require.Nil(t, currentRelease) } +func TestCurrentChannelReleasePtrsUsesChannelSequence(t *testing.T) { + releases := []*types.ChannelRelease{ + {ChannelSequence: 1}, + {ChannelSequence: 2}, + {ChannelSequence: 3, IsDemoted: true}, + } + + currentRelease := currentChannelReleasePtrs(releases, 2) + + require.NotNil(t, currentRelease) + require.EqualValues(t, 2, currentRelease.ChannelSequence) +} + func TestCurrentChannelReleasePtrsSkipsDemotedReleases(t *testing.T) { releases := []*types.ChannelRelease{ {ChannelSequence: 1}, {ChannelSequence: 2, IsDemoted: true}, } - currentRelease := currentChannelReleasePtrs(releases) + currentRelease := currentChannelReleasePtrs(releases, 0) require.NotNil(t, currentRelease) require.EqualValues(t, 1, currentRelease.ChannelSequence) @@ -48,7 +74,7 @@ func TestCurrentChannelReleasePtrsReturnsNilWhenAllReleasesAreDemoted(t *testing {ChannelSequence: 2, IsDemoted: true}, } - currentRelease := currentChannelReleasePtrs(releases) + currentRelease := currentChannelReleasePtrs(releases, 0) require.Nil(t, currentRelease) }