From ace1225f2eee9adf65840caeb97bfdc7b031a1a5 Mon Sep 17 00:00:00 2001 From: Justiceleeg Date: Wed, 28 Jan 2026 12:02:25 -0600 Subject: [PATCH 1/2] Hide multi-channel license support from CLI Multi-channel licenses are gated by a feature flag that's not enabled. Remove CLI exposure to prevent user confusion and API errors. - Change --channel flag from array to single value - Remove --default-channel flag - Simplify channel handling to single-channel only --- cli/cmd/customer_create.go | 68 ++++++++++---------------------------- cli/cmd/customer_update.go | 66 ++++++++++-------------------------- 2 files changed, 35 insertions(+), 99 deletions(-) diff --git a/cli/cmd/customer_create.go b/cli/cmd/customer_create.go index 3eda8e444..d1e7702e1 100644 --- a/cli/cmd/customer_create.go +++ b/cli/cmd/customer_create.go @@ -1,8 +1,6 @@ package cmd import ( - "fmt" - "os" "time" "github.com/pkg/errors" @@ -15,8 +13,7 @@ import ( type createCustomerOpts struct { Name string CustomID string - ChannelNames []string - DefaultChannel string + ChannelName string ExpiryDuration time.Duration EnsureChannel bool IsAirgapEnabled bool @@ -54,9 +51,6 @@ The --app flag must be set to specify the target application.`, Example: `# Create a basic customer with a name and assigned to a channel replicated customer create --app myapp --name "Acme Inc" --channel stable -# Create a customer with multiple channels and a custom ID -replicated customer create --app myapp --name "Beta Corp" --custom-id "BETA123" --channel beta --channel stable - # Create a paid customer with specific features enabled replicated customer create --app myapp --name "Enterprise Ltd" --type paid --channel enterprise --airgap --snapshot @@ -65,8 +59,7 @@ replicated customer create --app myapp --name "Trial User" --type trial --channe # Create a customer with all available options replicated customer create --app myapp --name "Full Options Inc" --custom-id "FULL001" \ - --channel stable --channel beta --default-channel stable --type paid \ - --email "contact@fulloptions.com" --expires-in 8760h \ + --channel stable --type paid --email "contact@fulloptions.com" --expires-in 8760h \ --airgap --snapshot --kots-install --embedded-cluster-download \ --support-bundle-upload --ensure-channel`, RunE: func(cmd *cobra.Command, args []string) error { @@ -79,8 +72,7 @@ replicated customer create --app myapp --name "Full Options Inc" --custom-id "FU parent.AddCommand(cmd) cmd.Flags().StringVar(&opts.Name, "name", "", "Name of the customer") cmd.Flags().StringVar(&opts.CustomID, "custom-id", "", "Set a custom customer ID to more easily tie this customer record to your external data systems") - cmd.Flags().StringArrayVar(&opts.ChannelNames, "channel", []string{}, "Release channel to which the customer should be assigned (can be specified multiple times)") - cmd.Flags().StringVar(&opts.DefaultChannel, "default-channel", "", "Which of the specified channels should be the default channel. if not set, the first channel specified will be the default channel.") + cmd.Flags().StringVar(&opts.ChannelName, "channel", "", "Release channel to which the customer should be assigned") cmd.Flags().DurationVar(&opts.ExpiryDuration, "expires-in", 0, "If set, an expiration date will be set on the license. Supports Go durations like '72h' or '3600m'") cmd.Flags().BoolVar(&opts.EnsureChannel, "ensure-channel", false, "If set, channel will be created if it does not exist.") cmd.Flags().BoolVar(&opts.IsAirgapEnabled, "airgap", false, "If set, the license will allow airgap installs.") @@ -126,50 +118,24 @@ func (r *runners) createCustomer(cmd *cobra.Command, opts createCustomerOpts, ou opts.CustomerType = "prod" } - channels := []kotsclient.CustomerChannel{} - - foundDefaultChannel := false - for _, requestedChannel := range opts.ChannelNames { - getOrCreateChannelOptions := client.GetOrCreateChannelOptions{ - AppID: r.appID, - AppType: r.appType, - NameOrID: requestedChannel, - Description: "", - CreateIfAbsent: opts.EnsureChannel, - } - - channel, err := r.api.GetOrCreateChannelByName(getOrCreateChannelOptions) - if err != nil { - return errors.Wrap(err, "get channel") - } - - customerChannel := kotsclient.CustomerChannel{ - ID: channel.ID, - } - - if opts.DefaultChannel == requestedChannel { - customerChannel.IsDefault = true - foundDefaultChannel = true - } - - channels = append(channels, customerChannel) - } - - if len(channels) == 0 { - return errors.New("no channels found") + getOrCreateChannelOptions := client.GetOrCreateChannelOptions{ + AppID: r.appID, + AppType: r.appType, + NameOrID: opts.ChannelName, + Description: "", + CreateIfAbsent: opts.EnsureChannel, } - if opts.DefaultChannel != "" && !foundDefaultChannel { - return errors.New("default channel not found in specified channels") + channel, err := r.api.GetOrCreateChannelByName(getOrCreateChannelOptions) + if err != nil { + return errors.Wrap(err, "get channel") } - if !foundDefaultChannel { - if len(channels) > 1 { - fmt.Fprintln(os.Stderr, "No default channel specified, defaulting to the first channel specified.") - } - firstChannel := channels[0] - firstChannel.IsDefault = true - channels[0] = firstChannel + channels := []kotsclient.CustomerChannel{ + { + ID: channel.ID, + IsDefault: true, + }, } createOpts := kotsclient.CreateCustomerOpts{ diff --git a/cli/cmd/customer_update.go b/cli/cmd/customer_update.go index 271c183bb..c0e2c1002 100644 --- a/cli/cmd/customer_update.go +++ b/cli/cmd/customer_update.go @@ -1,8 +1,6 @@ package cmd import ( - "fmt" - "os" "time" "github.com/pkg/errors" @@ -16,8 +14,7 @@ type updateCustomerOpts struct { CustomerID string Name string CustomID string - Channels []string - DefaultChannel string + Channel string ExpiryDuration time.Duration EnsureChannel bool IsAirgapEnabled bool @@ -53,8 +50,8 @@ func (r *runners) InitCustomerUpdateCommand(parent *cobra.Command) *cobra.Comman Example: `# Update a customer's name replicated customer update --customer cus_abcdef123456 --name "New Company Name" -# Change a customer's channel and make it the default -replicated customer update --customer cus_abcdef123456 --channel stable --default-channel stable +# Change a customer's channel +replicated customer update --customer cus_abcdef123456 --channel stable # Enable airgap installations for a customer replicated customer update --customer cus_abcdef123456 --airgap @@ -79,8 +76,7 @@ replicated customer update --customer cus_abcdef123456 --name "JSON Corp" --outp cmd.Flags().StringVar(&opts.CustomerID, "customer", "", "The ID of the customer to update") cmd.Flags().StringVar(&opts.Name, "name", "", "Name of the customer") cmd.Flags().StringVar(&opts.CustomID, "custom-id", "", "Set a custom customer ID to more easily tie this customer record to your external data systems") - cmd.Flags().StringArrayVar(&opts.Channels, "channel", []string{}, "Release channel to which the customer should be assigned (can be specified multiple times)") - cmd.Flags().StringVar(&opts.DefaultChannel, "default-channel", "", "Which of the specified channels should be the default channel. if not set, the first channel specified will be the default channel.") + cmd.Flags().StringVar(&opts.Channel, "channel", "", "Release channel to which the customer should be assigned") cmd.Flags().DurationVar(&opts.ExpiryDuration, "expires-in", 0, "If set, an expiration date will be set on the license. Supports Go durations like '72h' or '3600m'") cmd.Flags().BoolVar(&opts.EnsureChannel, "ensure-channel", false, "If set, channel will be created if it does not exist.") cmd.Flags().BoolVar(&opts.IsAirgapEnabled, "airgap", false, "If set, the license will allow airgap installs.") @@ -132,50 +128,24 @@ func (r *runners) updateCustomer(cmd *cobra.Command, opts updateCustomerOpts) (e opts.Type = "prod" } - channels := []kotsclient.CustomerChannel{} - - foundDefaultChannel := false - for _, requestedChannel := range opts.Channels { - getOrCreateChannelOptions := client.GetOrCreateChannelOptions{ - AppID: r.appID, - AppType: r.appType, - NameOrID: requestedChannel, - Description: "", - CreateIfAbsent: opts.EnsureChannel, - } - - channel, err := r.api.GetOrCreateChannelByName(getOrCreateChannelOptions) - if err != nil { - return errors.Wrap(err, "get channel") - } - - customerChannel := kotsclient.CustomerChannel{ - ID: channel.ID, - } - - if opts.DefaultChannel == requestedChannel { - customerChannel.IsDefault = true - foundDefaultChannel = true - } - - channels = append(channels, customerChannel) + getOrCreateChannelOptions := client.GetOrCreateChannelOptions{ + AppID: r.appID, + AppType: r.appType, + NameOrID: opts.Channel, + Description: "", + CreateIfAbsent: opts.EnsureChannel, } - if len(channels) == 0 { - return errors.New("no channels found") - } - - if opts.DefaultChannel != "" && !foundDefaultChannel { - return errors.New("default channel not found in specified channels") + channel, err := r.api.GetOrCreateChannelByName(getOrCreateChannelOptions) + if err != nil { + return errors.Wrap(err, "get channel") } - if !foundDefaultChannel { - if len(channels) > 1 { - fmt.Fprintln(os.Stderr, "No default channel specified, defaulting to the first channel specified.") - } - firstChannel := channels[0] - firstChannel.IsDefault = true - channels[0] = firstChannel + channels := []kotsclient.CustomerChannel{ + { + ID: channel.ID, + IsDefault: true, + }, } updateOpts := kotsclient.UpdateCustomerOpts{ From 7f885344c2c2407cffe5c14c0775bb4057991d3e Mon Sep 17 00:00:00 2001 From: Justiceleeg Date: Thu, 29 Jan 2026 11:20:30 -0600 Subject: [PATCH 2/2] Change CHANNELS column header to singular CHANNEL --- cli/print/customers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/print/customers.go b/cli/print/customers.go index 5c72fb50d..2ee8c0679 100644 --- a/cli/print/customers.go +++ b/cli/print/customers.go @@ -10,7 +10,7 @@ import ( "github.com/replicatedhq/replicated/pkg/types" ) -var customersTmplSrc = `ID NAME CHANNELS EXPIRES TYPE CUSTOM_ID +var customersTmplSrc = `ID NAME CHANNEL EXPIRES TYPE CUSTOM_ID {{ range . -}} {{ .ID }} {{ .Name }} {{range .Channels}} {{.Name}}{{end}} {{if not .Expires}}Never{{else}}{{.Expires}}{{end}} {{.Type}} {{if not .CustomID}}Not Set{{else}}{{.CustomID}}{{end}} {{ end }}`