Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions cli/cmd/channel_release_demote.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package cmd

import (
"errors"
"fmt"

"github.com/replicatedhq/replicated/client"
"github.com/spf13/cobra"
)

func (r *runners) InitChannelReleaseDemote(parent *cobra.Command) {
cmd := &cobra.Command{
Use: "demote CHANNEL_ID_OR_NAME",
Short: "Demote a release from a channel",
Long: "Demote a channel release from a channel using a channel sequence or release sequence.",
Example: ` # Demote a release from a channel by channel sequence
replicated channel release demote Beta --channel-sequence 15

# Demote a release from a channel by release sequence
replicated channel release demote Beta --release-sequence 12`,
Args: cobra.ExactArgs(1),
}

cmd.Flags().Int64Var(&r.args.demoteChannelSequence, "channel-sequence", 0, "The channel sequence to demote")
cmd.Flags().Int64Var(&r.args.demoteReleaseSequence, "release-sequence", 0, "The release sequence to demote")

parent.AddCommand(cmd)
cmd.RunE = r.channelReleaseDemote
}

func (r *runners) channelReleaseDemote(cmd *cobra.Command, args []string) error {
if !r.hasApp() {
return errors.New("no app specified")
}

if r.args.demoteChannelSequence == 0 && r.args.demoteReleaseSequence == 0 {
return errors.New("one of --channel-sequence or --release-sequence is required")
}

if r.args.demoteChannelSequence != 0 && r.args.demoteReleaseSequence != 0 {
fmt.Fprintf(r.w, "Warning: Both --channel-sequence and --release-sequence provided. Using --channel-sequence %d\n", r.args.demoteChannelSequence)
}

chanID := args[0]

opts := client.GetOrCreateChannelOptions{
AppID: r.appID,
AppType: r.appType,
NameOrID: chanID,
CreateIfAbsent: false,
}
foundChannel, err := r.api.GetOrCreateChannelByName(opts)
if err != nil {
return err
}

channelSequence := r.args.demoteChannelSequence
if r.args.demoteReleaseSequence != 0 {
kotsChannel, err := r.api.KotsClient.GetKotsChannel(r.appID, foundChannel.ID)
if err != nil {
return err
}

for _, channelRelease := range kotsChannel.Releases {
if int64(channelRelease.Sequence) == r.args.demoteReleaseSequence {
channelSequence = int64(channelRelease.ChannelSequence)
break
}
}

if channelSequence == 0 {
return fmt.Errorf("release sequence %d not found in channel %s", r.args.demoteReleaseSequence, foundChannel.ID)
}
}

demotedRelease, err := r.api.ChannelReleaseDemote(r.appID, r.appType, foundChannel.ID, channelSequence)
if err != nil {
return err
}

fmt.Fprintf(r.w, "Channel sequence %d (version %s, release %d) demoted in channel %s\n", channelSequence, demotedRelease.Semver, demotedRelease.Sequence, chanID)
r.w.Flush()

return nil
}
85 changes: 85 additions & 0 deletions cli/cmd/channel_release_undemote.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package cmd

import (
"errors"
"fmt"

"github.com/replicatedhq/replicated/client"
"github.com/spf13/cobra"
)

func (r *runners) InitChannelReleaseUnDemote(parent *cobra.Command) {
cmd := &cobra.Command{
Use: "un-demote CHANNEL_ID_OR_NAME",
Short: "Un-demote a release from a channel",
Long: "Un-demote a channel release from a channel using a channel sequence or release sequence.",
Example: ` # Un-demote a release from a channel by channel sequence
replicated channel release un-demote Beta --channel-sequence 15

# Un-demote a release from a channel by release sequence
replicated channel release un-demote Beta --release-sequence 12`,
Args: cobra.ExactArgs(1),
}

cmd.Flags().Int64Var(&r.args.unDemoteChannelSequence, "channel-sequence", 0, "The channel sequence to un-demote")
cmd.Flags().Int64Var(&r.args.unDemoteReleaseSequence, "release-sequence", 0, "The release sequence to un-demote")

parent.AddCommand(cmd)
cmd.RunE = r.channelReleaseUnDemote
}

func (r *runners) channelReleaseUnDemote(cmd *cobra.Command, args []string) error {
if !r.hasApp() {
return errors.New("no app specified")
}

if r.args.unDemoteChannelSequence == 0 && r.args.unDemoteReleaseSequence == 0 {
return errors.New("one of --channel-sequence or --release-sequence is required")
}

if r.args.unDemoteChannelSequence != 0 && r.args.unDemoteReleaseSequence != 0 {
fmt.Fprintf(r.w, "Warning: Both --channel-sequence and --release-sequence provided. Using --channel-sequence %d\n", r.args.unDemoteChannelSequence)
}

chanID := args[0]

opts := client.GetOrCreateChannelOptions{
AppID: r.appID,
AppType: r.appType,
NameOrID: chanID,
CreateIfAbsent: false,
}
foundChannel, err := r.api.GetOrCreateChannelByName(opts)
if err != nil {
return err
}

channelSequence := r.args.unDemoteChannelSequence
if r.args.unDemoteReleaseSequence != 0 {
kotsChannel, err := r.api.KotsClient.GetKotsChannel(r.appID, foundChannel.ID)
if err != nil {
return err
}

for _, channelRelease := range kotsChannel.Releases {
if int64(channelRelease.Sequence) == r.args.unDemoteReleaseSequence {
channelSequence = int64(channelRelease.ChannelSequence)
break
}
}

if channelSequence == 0 {
return fmt.Errorf("release sequence %d not found in channel %s", r.args.unDemoteReleaseSequence, foundChannel.ID)
}
}

unDemotedRelease, err := r.api.ChannelReleaseUnDemote(r.appID, r.appType, foundChannel.ID, channelSequence)
if err != nil {
return err
}

fmt.Fprintf(r.w, "Channel sequence %d (version %s, release %d) un-demoted in channel %s\n", channelSequence, unDemotedRelease.Semver, unDemotedRelease.Sequence, chanID)
r.w.Flush()

return nil
}
9 changes: 2 additions & 7 deletions cli/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,8 @@ func Execute(rootCmd *cobra.Command, stdin io.Reader, stdout io.Writer, stderr i
runCmds.InitChannelRemove(channelCmd)
runCmds.InitChannelEnableSemanticVersioning(channelCmd)
runCmds.InitChannelDisableSemanticVersioning(channelCmd)
runCmds.InitChannelReleaseDemote(channelCmd)
runCmds.InitChannelReleaseUnDemote(channelCmd)

runCmds.rootCmd.AddCommand(releaseCmd)
err := runCmds.InitReleaseCreate(releaseCmd)
Expand Down Expand Up @@ -430,10 +432,3 @@ func parseTags(tags []string) ([]types.Tag, error) {
}
return parsedTags, nil
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unused

func homeDir() string {
if h := os.Getenv("HOME"); h != "" {
return h
}
return os.Getenv("USERPROFILE")
}
32 changes: 14 additions & 18 deletions cli/cmd/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,15 @@ import (
// Runner holds the I/O dependencies and configurations used by individual
// commands, which are defined as methods on this type.
type runners struct {
appID string
appSlug string
appType string
isFoundationApp bool
Copy link
Contributor Author

@pandemicsyn pandemicsyn Jan 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isFoundationApp and dir was unused

api client.Client
platformAPI *platformclient.HTTPClient
kotsAPI *kotsclient.VendorV3Client
stdin io.Reader
dir string
outputFormat string
w *tabwriter.Writer
appID string
appSlug string
appType string
api client.Client
platformAPI *platformclient.HTTPClient
kotsAPI *kotsclient.VendorV3Client
stdin io.Reader
outputFormat string
w *tabwriter.Writer

rootCmd *cobra.Command
args runnerArgs
Expand Down Expand Up @@ -53,13 +51,7 @@ type runnerArgs struct {
createReleaseYamlFile string
createReleaseYamlDir string
createReleaseChart string
createReleaseConfigYaml string
createReleaseDeploymentYaml string
createReleaseServiceYaml string
createReleasePreflightYaml string
createReleaseSupportBundleYaml string
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all these where also unused

createReleasePromote string
createReleasePromoteDir string
createReleasePromoteRequired bool
createReleasePromoteNotes string
createReleasePromoteVersion string
Expand Down Expand Up @@ -177,7 +169,6 @@ type runnerArgs struct {
modelCollectionRmModelName string
modelCollectionRmModelCollectionID string

lsAppVersion string
lsVersionsDistribution string

lsClusterShowTerminated bool
Expand Down Expand Up @@ -254,4 +245,9 @@ type runnerArgs struct {
clusterAddonCreateObjectStoreDuration time.Duration
clusterAddonCreateObjectStoreDryRun bool
clusterAddonCreateObjectStoreOutput string

demoteReleaseSequence int64
demoteChannelSequence int64
unDemoteReleaseSequence int64
unDemoteChannelSequence int64
}
1 change: 1 addition & 0 deletions cli/print/channel_attributes.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ var channelAttrsTmplSrc = `ID: {{ .Chan.ID }}
NAME: {{ .Chan.Name }}
DESCRIPTION: {{ .Chan.Description }}
RELEASE: {{ if ge .Chan.ReleaseSequence 1 }}{{ .Chan.ReleaseSequence }}{{ else }} {{ end }}
CHANNEL SEQUENCE: {{ if ge .Chan.ChannelSequence 1 }}{{ .Chan.ChannelSequence }}{{ else }} {{ end }}
VERSION: {{ .Chan.ReleaseLabel }}
{{ if not .Chan.IsHelmOnly -}}
{{ with .Existing -}}
Expand Down
18 changes: 18 additions & 0 deletions client/channel.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,3 +164,21 @@ func (c *Client) UpdateSemanticVersioningForChannel(appType string, appID string

return errors.Errorf("unknown app type %q", appType)
}

func (c *Client) ChannelReleaseDemote(appID string, appType string, channelID string, channelSequence int64) (*types.ChannelRelease, error) {
if appType == "platform" {
return nil, errors.New("This feature is not currently supported for Platform applications.")
} else if appType == "kots" {
return c.KotsClient.DemoteChannelRelease(appID, channelID, channelSequence)
}
return nil, errors.Errorf("unknown app type %q", appType)
}

func (c *Client) ChannelReleaseUnDemote(appID string, appType string, channelID string, channelSequence int64) (*types.ChannelRelease, error) {
if appType == "platform" {
return nil, errors.New("This feature is not currently supported for Platform applications.")
} else if appType == "kots" {
return c.KotsClient.UnDemoteChannelRelease(appID, channelID, channelSequence)
}
return nil, errors.Errorf("unknown app type %q", appType)
}
30 changes: 30 additions & 0 deletions pkg/kotsclient/channel.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,3 +119,33 @@ func (c *VendorV3Client) UpdateSemanticVersioning(appID string, channel *types.C

return nil
}

func (c *VendorV3Client) DemoteChannelRelease(appID string, channelID string, channelSequence int64) (*types.ChannelRelease, error) {
url := fmt.Sprintf("/v3/app/%s/channel/%s/release/%d/demote", appID, url.QueryEscape(channelID), channelSequence)

response := struct {
Release types.ChannelRelease `json:"release"`
}{}

err := c.DoJSON(context.TODO(), "POST", url, http.StatusOK, nil, &response)
if err != nil {
return nil, errors.Wrap(err, "demote channel release")
}

return &response.Release, nil
}

func (c *VendorV3Client) UnDemoteChannelRelease(appID string, channelID string, channelSequence int64) (*types.ChannelRelease, error) {
url := fmt.Sprintf("/v3/app/%s/channel/%s/release/%d/undemote", appID, url.QueryEscape(channelID), channelSequence)

response := struct {
Release types.ChannelRelease `json:"release"`
}{}

err := c.DoJSON(context.TODO(), "POST", url, http.StatusOK, nil, &response)
if err != nil {
return nil, errors.Wrap(err, "un-demote channel release")
}

return &response.Release, nil
}
3 changes: 3 additions & 0 deletions pkg/types/channel.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ func (c *KotsChannel) ToChannel() *Channel {
Description: c.Description,
Slug: c.ChannelSlug,
ReleaseSequence: int64(c.ReleaseSequence),
ChannelSequence: int64(c.ChannelSequence),
ReleaseLabel: c.CurrentVersion,
IsArchived: c.IsArchived,
IsHelmOnly: c.IsHelmOnly,
Expand Down Expand Up @@ -96,6 +97,8 @@ type Channel struct {
ReleaseSequence int64 `json:"releaseSequence"`
ReleaseLabel string `json:"releaseLabel"`

ChannelSequence int64 `json:"channelSequence"`

IsArchived bool `json:"isArchived"`
IsHelmOnly bool `json:"isHelmOnly"`
}
Expand Down