From ace1225f2eee9adf65840caeb97bfdc7b031a1a5 Mon Sep 17 00:00:00 2001 From: Justiceleeg Date: Wed, 28 Jan 2026 12:02:25 -0600 Subject: [PATCH 1/3] 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/3] 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 }}` From b418e0960b28b33dd9629c13353debf10d91d578 Mon Sep 17 00:00:00 2001 From: Justiceleeg Date: Thu, 29 Jan 2026 15:48:48 -0600 Subject: [PATCH 3/3] Update help text to reflect single channel assignment --- cli/cmd/customer_create.go | 4 ++-- cli/cmd/customer_update.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cli/cmd/customer_create.go b/cli/cmd/customer_create.go index d1e7702e1..6e3949339 100644 --- a/cli/cmd/customer_create.go +++ b/cli/cmd/customer_create.go @@ -44,8 +44,8 @@ func (r *runners) InitCustomersCreateCommand(parent *cobra.Command) *cobra.Comma Long: `Create a new customer for the current application with specified attributes. This command allows you to create a customer record with various properties such as name, -custom ID, channels, license type, and feature flags. You can set expiration dates, -enable or disable specific features, and assign the customer to one or more channels. +custom ID, channel, license type, and feature flags. You can set expiration dates, +enable or disable specific features, and assign the customer to a channel. The --app flag must be set to specify the target application.`, Example: `# Create a basic customer with a name and assigned to a channel diff --git a/cli/cmd/customer_update.go b/cli/cmd/customer_update.go index c0e2c1002..bdf9afb1e 100644 --- a/cli/cmd/customer_update.go +++ b/cli/cmd/customer_update.go @@ -43,8 +43,8 @@ func (r *runners) InitCustomerUpdateCommand(parent *cobra.Command) *cobra.Comman Long: `Update an existing customer's information and settings. This command allows you to modify various attributes of a customer, including their name, - custom ID, assigned channels, license type, and feature flags. You can update expiration dates, - enable or disable specific features, and change channel assignments. + custom ID, assigned channel, license type, and feature flags. You can update expiration dates, + enable or disable specific features, and change the channel assignment. The --customer flag is required to specify which customer to update.`, Example: `# Update a customer's name