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
39 changes: 29 additions & 10 deletions cli/cmd/customer_archive.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,30 +7,49 @@ import (

func (r *runners) InitCustomersArchiveCommand(parent *cobra.Command) *cobra.Command {
cmd := &cobra.Command{
Use: "archive",
Short: "archive a customer",
Long: `archive a customer`,
Use: "archive [flags]",
Short: "Archive a customer",
Long: `The archive command allows you to archive a specific customer.

This command will mark a customer as archived in the system. Archived customers
are typically those who are no longer active or have terminated their service.
Archiving a customer does not delete their data but changes their status in the system.

You must specify the customer using either their name or ID with the --customer flag.`,
Example: ` # Archive a customer by ID
replicated customer archive --customer cus_abcdef123456

# Archive a customer by name
replicated customer archive --customer "Acme Inc"

# Archive a customer for a specific app (if you have multiple apps)
replicated customer archive --app myapp --customer "Acme Inc"`,
RunE: r.archiveCustomer,
SilenceUsage: true,
}
parent.AddCommand(cmd)
cmd.Flags().StringVar(&r.args.customerArchiveNameOrId, "customer", "", "The Customer Name or ID")
cmd.Flags().String("customer", "", "The Customer Name or ID")
cmd.MarkFlagRequired("customer")

return cmd
}

func (r *runners) archiveCustomer(_ *cobra.Command, _ []string) error {
if r.args.customerArchiveNameOrId == "" {
return errors.Errorf("missing or invalid parameters: customer")
func (r *runners) archiveCustomer(cmd *cobra.Command, _ []string) error {
customerNameOrId, err := cmd.Flags().GetString("customer")
if err != nil {
return errors.Wrap(err, "get customer flag")
}
if customerNameOrId == "" {
return errors.New("missing or invalid parameters: customer")
}

customer, err := r.api.GetCustomerByNameOrId(r.appType, r.appID, r.args.customerArchiveNameOrId)
customer, err := r.api.GetCustomerByNameOrId(r.appType, r.appID, customerNameOrId)
if err != nil {
return errors.Wrapf(err, "find customer %q", r.args.customerArchiveNameOrId)
return errors.Wrapf(err, "find customer %q", customerNameOrId)
}

if err := r.api.ArchiveCustomer(r.appType, customer.ID); err != nil {
return errors.Wrapf(err, "archive customer %q", r.args.customerArchiveNameOrId)
return errors.Wrapf(err, "archive customer %q", customerNameOrId)
}

return nil
Expand Down
156 changes: 103 additions & 53 deletions cli/cmd/customer_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,68 +12,118 @@ import (
"github.com/spf13/cobra"
)

type createCustomerOpts struct {
Name string
CustomID string
ChannelNames []string
DefaultChannel string
ExpiryDuration time.Duration
EnsureChannel bool
IsAirgapEnabled bool
IsGitopsSupported bool
IsSnapshotSupported bool
IsKotsInstallEnabled bool
IsEmbeddedClusterEnabled bool
IsGeoaxisSupported bool
IsHelmVMDownloadEnabled bool
IsIdentityServiceSupported bool
IsInstallerSupportEnabled bool
IsSupportBundleUploadEnabled bool
Email string
CustomerType string
}

func (r *runners) InitCustomersCreateCommand(parent *cobra.Command) *cobra.Command {
opts := createCustomerOpts{}
var outputFormat string

cmd := &cobra.Command{
Use: "create",
Short: "create a customer",
Long: `create a customer`,
RunE: r.createCustomer,
Use: "create",
Short: "Create a new customer for the current application",
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.

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

# Create a trial customer with an expiration date
replicated customer create --app myapp --name "Trial User" --type trial --channel stable --expires-in 720h

# 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 \
--airgap --snapshot --kots-install --embedded-cluster-download \
--support-bundle-upload --ensure-channel`,
RunE: func(cmd *cobra.Command, args []string) error {
return r.createCustomer(cmd, opts, outputFormat)
},
SilenceUsage: false,
SilenceErrors: true, // this command uses custom error printing
SilenceErrors: true,
}

parent.AddCommand(cmd)
cmd.Flags().StringVar(&r.args.customerCreateName, "name", "", "Name of the customer")
cmd.Flags().StringVar(&r.args.customerCreateCustomID, "custom-id", "", "Set a custom customer ID to more easily tie this customer record to your external data systems")
cmd.Flags().StringArrayVar(&r.args.customerCreateChannel, "channel", []string{}, "Release channel to which the customer should be assigned (can be specified multiple times)")
cmd.Flags().StringVar(&r.args.customerCreateDefaultChannel, "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().DurationVar(&r.args.customerCreateExpiryDuration, "expires-in", 0, "If set, an expiration date will be set on the license. Supports Go durations like '72h' or '3600m'")
cmd.Flags().BoolVar(&r.args.customerCreateEnsureChannel, "ensure-channel", false, "If set, channel will be created if it does not exist.")
cmd.Flags().BoolVar(&r.args.customerCreateIsAirgapEnabled, "airgap", false, "If set, the license will allow airgap installs.")
cmd.Flags().BoolVar(&r.args.customerCreateIsGitopsSupported, "gitops", false, "If set, the license will allow the GitOps usage.")
cmd.Flags().BoolVar(&r.args.customerCreateIsSnapshotSupported, "snapshot", false, "If set, the license will allow Snapshots.")
cmd.Flags().BoolVar(&r.args.customerCreateIsKotsInstallEnabled, "kots-install", true, "If set, the license will allow KOTS install. Otherwise license will allow Helm CLI installs only.")
cmd.Flags().BoolVar(&r.args.customerCreateIsEmbeddedClusterDownloadEnabled, "embedded-cluster-download", false, "If set, the license will allow embedded cluster downloads.")
cmd.Flags().BoolVar(&r.args.customerCreateIsGeoaxisSupported, "geo-axis", false, "If set, the license will allow Geo Axis usage.")
cmd.Flags().BoolVar(&r.args.customerCreateIsHelmVMDownloadEnabled, "helmvm-cluster-download", false, "If set, the license will allow helmvm cluster downloads.")
cmd.Flags().BoolVar(&r.args.customerCreateIsIdentityServiceSupported, "identity-service", false, "If set, the license will allow Identity Service usage.")
cmd.Flags().BoolVar(&r.args.customerCreateIsInstallerSupportEnabled, "installer-support", false, "If set, the license will allow installer support.")
cmd.Flags().BoolVar(&r.args.customerCreateIsSupportBundleUploadEnabled, "support-bundle-upload", false, "If set, the license will allow uploading support bundles.")
cmd.Flags().StringVar(&r.args.customerCreateEmail, "email", "", "Email address of the customer that is to be created.")
cmd.Flags().StringVar(&r.args.customerCreateType, "type", "dev", "The license type to create. One of: dev|trial|paid|community|test (default: dev)")
cmd.Flags().StringVar(&r.outputFormat, "output", "table", "The output format to use. One of: json|table (default: table)")
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().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.")
cmd.Flags().BoolVar(&opts.IsGitopsSupported, "gitops", false, "If set, the license will allow the GitOps usage.")
cmd.Flags().BoolVar(&opts.IsSnapshotSupported, "snapshot", false, "If set, the license will allow Snapshots.")
cmd.Flags().BoolVar(&opts.IsKotsInstallEnabled, "kots-install", true, "If set, the license will allow KOTS install. Otherwise license will allow Helm CLI installs only.")
cmd.Flags().BoolVar(&opts.IsEmbeddedClusterEnabled, "embedded-cluster-download", false, "If set, the license will allow embedded cluster downloads.")
cmd.Flags().BoolVar(&opts.IsGeoaxisSupported, "geo-axis", false, "If set, the license will allow Geo Axis usage.")
cmd.Flags().BoolVar(&opts.IsHelmVMDownloadEnabled, "helmvm-cluster-download", false, "If set, the license will allow helmvm cluster downloads.")
cmd.Flags().BoolVar(&opts.IsIdentityServiceSupported, "identity-service", false, "If set, the license will allow Identity Service usage.")
cmd.Flags().BoolVar(&opts.IsInstallerSupportEnabled, "installer-support", false, "If set, the license will allow installer support.")
cmd.Flags().BoolVar(&opts.IsSupportBundleUploadEnabled, "support-bundle-upload", false, "If set, the license will allow uploading support bundles.")
cmd.Flags().StringVar(&opts.Email, "email", "", "Email address of the customer that is to be created.")
cmd.Flags().StringVar(&opts.CustomerType, "type", "dev", "The license type to create. One of: dev|trial|paid|community|test (default: dev)")
cmd.Flags().StringVar(&outputFormat, "output", "table", "The output format to use. One of: json|table (default: table)")

cmd.MarkFlagRequired("channel")

return cmd
}

func (r *runners) createCustomer(cmd *cobra.Command, _ []string) (err error) {
func (r *runners) createCustomer(cmd *cobra.Command, opts createCustomerOpts, outputFormat string) (err error) {
defer func() {
printIfError(cmd, err)
}()

// all of the following validation occurs in the API also, but
// we want to fail fast if the user has provided invalid input
if err := validateCustomerType(r.args.customerCreateType); err != nil {
// Validation
if err := validateCustomerType(opts.CustomerType); err != nil {
return errors.Wrap(err, "validate customer type")
}
if r.args.customerCreateType == "test" && r.args.customerCreateExpiryDuration > time.Hour*48 {
if opts.CustomerType == "test" && opts.ExpiryDuration > time.Hour*48 {
return errors.New("test licenses cannot be created with an expiration date greater than 48 hours")
}
if r.args.customerCreateType == "paid" {
r.args.customerCreateType = "prod"
if opts.CustomerType == "paid" {
opts.CustomerType = "prod"
}

channels := []kotsclient.CustomerChannel{}

foundDefaultChannel := false
for _, requestedChannel := range r.args.customerCreateChannel {
for _, requestedChannel := range opts.ChannelNames {
getOrCreateChannelOptions := client.GetOrCreateChannelOptions{
AppID: r.appID,
AppType: r.appType,
NameOrID: requestedChannel,
Description: "",
CreateIfAbsent: r.args.customerCreateEnsureChannel,
CreateIfAbsent: opts.EnsureChannel,
}

channel, err := r.api.GetOrCreateChannelByName(getOrCreateChannelOptions)
Expand All @@ -85,7 +135,7 @@ func (r *runners) createCustomer(cmd *cobra.Command, _ []string) (err error) {
ID: channel.ID,
}

if r.args.customerCreateDefaultChannel == requestedChannel {
if opts.DefaultChannel == requestedChannel {
customerChannel.IsDefault = true
foundDefaultChannel = true
}
Expand All @@ -97,7 +147,7 @@ func (r *runners) createCustomer(cmd *cobra.Command, _ []string) (err error) {
return errors.New("no channels found")
}

if r.args.customerUpdateDefaultChannel != "" && !foundDefaultChannel {
if opts.DefaultChannel != "" && !foundDefaultChannel {
return errors.New("default channel not found in specified channels")
}

Expand All @@ -110,32 +160,32 @@ func (r *runners) createCustomer(cmd *cobra.Command, _ []string) (err error) {
channels[0] = firstChannel
}

opts := kotsclient.CreateCustomerOpts{
Name: r.args.customerCreateName,
CustomID: r.args.customerCreateCustomID,
createOpts := kotsclient.CreateCustomerOpts{
Name: opts.Name,
CustomID: opts.CustomID,
Channels: channels,
AppID: r.appID,
ExpiresAtDuration: r.args.customerCreateExpiryDuration,
IsAirgapEnabled: r.args.customerCreateIsAirgapEnabled,
IsGitopsSupported: r.args.customerCreateIsGitopsSupported,
IsSnapshotSupported: r.args.customerCreateIsSnapshotSupported,
IsKotsInstallEnabled: r.args.customerCreateIsKotsInstallEnabled,
IsEmbeddedClusterDownloadEnabled: r.args.customerCreateIsEmbeddedClusterDownloadEnabled,
IsGeoaxisSupported: r.args.customerCreateIsGeoaxisSupported,
IsHelmVMDownloadEnabled: r.args.customerCreateIsHelmVMDownloadEnabled,
IsIdentityServiceSupported: r.args.customerCreateIsIdentityServiceSupported,
IsInstallerSupportEnabled: r.args.customerCreateIsInstallerSupportEnabled,
IsSupportBundleUploadEnabled: r.args.customerCreateIsSupportBundleUploadEnabled,
LicenseType: r.args.customerCreateType,
Email: r.args.customerCreateEmail,
ExpiresAtDuration: opts.ExpiryDuration,
IsAirgapEnabled: opts.IsAirgapEnabled,
IsGitopsSupported: opts.IsGitopsSupported,
IsSnapshotSupported: opts.IsSnapshotSupported,
IsKotsInstallEnabled: opts.IsKotsInstallEnabled,
IsEmbeddedClusterDownloadEnabled: opts.IsEmbeddedClusterEnabled,
IsGeoaxisSupported: opts.IsGeoaxisSupported,
IsHelmVMDownloadEnabled: opts.IsHelmVMDownloadEnabled,
IsIdentityServiceSupported: opts.IsIdentityServiceSupported,
IsInstallerSupportEnabled: opts.IsInstallerSupportEnabled,
IsSupportBundleUploadEnabled: opts.IsSupportBundleUploadEnabled,
LicenseType: opts.CustomerType,
Email: opts.Email,
}

customer, err := r.api.CreateCustomer(r.appType, opts)
customer, err := r.api.CreateCustomer(r.appType, createOpts)
if err != nil {
return errors.Wrap(err, "create customer")
}

err = print.Customer(r.outputFormat, r.w, customer)
err = print.Customer(outputFormat, r.w, customer)
if err != nil {
return errors.Wrap(err, "print customer")
}
Expand Down
57 changes: 40 additions & 17 deletions cli/cmd/customer_download_license.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,43 +9,66 @@ import (
)

func (r *runners) InitCustomersDownloadLicenseCommand(parent *cobra.Command) *cobra.Command {
var (
customer string
output string
)

cmd := &cobra.Command{
Use: "download-license",
Short: "download a customer license",
Long: `download a customer license`,
RunE: r.downloadCustomerLicense,
Use: "download-license [flags]",
Short: "Download a customer's license",
Long: `The download-license command allows you to retrieve and save a customer's license.

This command fetches the license for a specified customer and either outputs it
to stdout or saves it to a file. The license contains crucial information about
the customer's subscription and usage rights.

You must specify the customer using either their name or ID with the --customer flag.`,
Example: ` # Download license for a customer by ID and output to stdout
replicated customer download-license --customer cus_abcdef123456

# Download license for a customer by name and save to a file
replicated customer download-license --customer "Acme Inc" --output license.yaml

# Download license for a customer in a specific app (if you have multiple apps)
replicated customer download-license --app myapp --customer "Acme Inc" --output license.yaml`,
RunE: func(cmd *cobra.Command, args []string) error {
return r.downloadCustomerLicense(cmd, customer, output)
},
SilenceUsage: true,
}
parent.AddCommand(cmd)
cmd.Flags().StringVar(&r.args.customerLicenseInspectCustomer, "customer", "", "The Customer Name or ID")
cmd.Flags().StringVarP(&r.args.customerLicenseInspectOutput, "output", "o", "-", "Path to output license to. Defaults to stdout")
cmd.Flags().String("customer", "", "The Customer Name or ID")
cmd.Flags().StringP("output", "o", "-", "Path to output license to. Defaults to stdout")
cmd.MarkFlagRequired("customer")

return cmd
}

func (r *runners) downloadCustomerLicense(_ *cobra.Command, _ []string) error {
if r.args.customerLicenseInspectCustomer == "" {
return errors.Errorf("missing or invalid parameters: customer")
func (r *runners) downloadCustomerLicense(cmd *cobra.Command, customer string, output string) error {
customerNameOrId, err := cmd.Flags().GetString("customer")
if err != nil {
return errors.Wrap(err, "get customer flag")
}
if r.args.customerLicenseInspectOutput == "" {
return errors.Errorf("missing or invalid parameters: output")
if customerNameOrId == "" {
return errors.New("missing or invalid parameters: customer")
}

customer, err := r.api.GetCustomerByNameOrId(r.appType, r.appID, r.args.customerLicenseInspectCustomer)
c, err := r.api.GetCustomerByNameOrId(r.appType, r.appID, customer)
if err != nil {
return errors.Wrapf(err, "find customer %q", r.args.customerLicenseInspectCustomer)
return errors.Wrapf(err, "find customer %q", customerNameOrId)
}

license, err := r.api.DownloadLicense(r.appType, r.appID, customer.ID)
license, err := r.api.DownloadLicense(r.appType, r.appID, c.ID)
if err != nil {
return errors.Wrapf(err, "download license for customer %q", customer.Name)
return errors.Wrapf(err, "download license for customer %q", c.Name)
}

defer r.w.Flush()
if r.args.customerLicenseInspectOutput == "-" {
if output == "-" {
_, err = fmt.Fprintln(r.w, string(license))
return err
}

return ioutil.WriteFile(r.args.customerLicenseInspectOutput, license, 0644)
return ioutil.WriteFile(output, license, 0644)
}
Loading
Loading