diff --git a/client/channel.go b/client/channel.go index f352f4d1e..bb929cba6 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, kotsChannel.ChannelSequence) 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, kotsChannel.ChannelSequence) + if currentRelease == nil { + return nil, "", errors.New("no active releases found in channel") } proxyDomain := currentRelease.ProxyRegistryDomain @@ -300,6 +294,49 @@ func (c *Client) GetCurrentChannelRelease(appID string, appType string, channelI return nil, "", errors.Errorf("unknown app type %q", appType) } +// 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 { + continue + } + if currentRelease == nil || releases[i].ChannelSequence > currentRelease.ChannelSequence { + currentRelease = &releases[i] + } + } + return currentRelease +} + +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 { + 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..f00c2f301 --- /dev/null +++ b/client/channel_test.go @@ -0,0 +1,80 @@ +package client + +import ( + "testing" + + "github.com/replicatedhq/replicated/pkg/types" + "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, 0) + + 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, 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, 0) + + 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, 0) + + require.Nil(t, currentRelease) +}