diff --git a/cmd/ocm/create/cluster/cmd.go b/cmd/ocm/create/cluster/cmd.go index 21faffe8..c0c0ba3a 100644 --- a/cmd/ocm/create/cluster/cmd.go +++ b/cmd/ocm/create/cluster/cmd.go @@ -28,6 +28,7 @@ import ( "github.com/AlecAivazis/survey/v2" "github.com/openshift-online/ocm-cli/cmd/ocm/edit/ingress" "github.com/openshift-online/ocm-cli/pkg/arguments" + "github.com/openshift-online/ocm-cli/pkg/billing" c "github.com/openshift-online/ocm-cli/pkg/cluster" "github.com/openshift-online/ocm-cli/pkg/ocm" "github.com/openshift-online/ocm-cli/pkg/provider" @@ -68,6 +69,7 @@ var args struct { clusterWideProxy c.ClusterWideProxy gcpServiceAccountFile arguments.FilePath etcdEncryption bool + subscriptionType string // Scaling options computeMachineType string @@ -92,6 +94,8 @@ const clusterNameHelp = "will be used when generating a sub-domain for your clus const subnetTemplate = "%s (%s)" +const subscriptionTypeTemplate = "%s (%s)" + // Creates a subnet options using a predefined template. func setSubnetOption(subnet, zone string) string { return fmt.Sprintf(subnetTemplate, subnet, zone) @@ -102,6 +106,14 @@ func parseSubnet(subnetOption string) string { return strings.Split(subnetOption, " ")[0] } +func setSubscriptionTypeOption(id, description string) string { + return fmt.Sprintf(subscriptionTypeTemplate, id, description) +} + +func parseSubscriptionType(subscriptionTypeOption string) string { + return strings.Split(subscriptionTypeOption, " ")[0] +} + // Cmd Constant: var Cmd = &cobra.Command{ Use: "cluster [flags] NAME", @@ -301,6 +313,16 @@ func init() { fmt.Sprintf("Namespace Ownership Policy for ingress. Options are %s", strings.Join(ingress.ValidNamespaceOwnershipPolicies, ",")), ) + + fs.StringVar( + &args.subscriptionType, + "subscription-type", + billing.StandardSubscriptionType, + fmt.Sprintf("The subscription billing model for the cluster. Options are %s", + strings.Join(billing.ValidSubscriptionTypes, ",")), + ) + arguments.SetQuestion(fs, "subscription-type", "Subscription type:") + Cmd.RegisterFlagCompletionFunc("subscription-type", arguments.MakeCompleteFunc(getSubscriptionTypeOptions)) } func osdProviderOptions(_ *sdk.Connection) ([]arguments.Option, error) { @@ -377,6 +399,31 @@ func getVersionOptionsWithDefault(connection *sdk.Connection, channelGroup strin return } +func getSubscriptionTypeOptions(connection *sdk.Connection) ([]arguments.Option, error) { + options := []arguments.Option{} + billingModels, err := billing.GetBillingModels(connection) + if err != nil { + return options, err + } + for _, billingModel := range billingModels { + option := subscriptionTypeOption(billingModel.ID(), billingModel.Description()) + //Standard billing model should always be the first option + if billingModel.ID() == billing.StandardSubscriptionType { + options = append([]arguments.Option{option}, options...) + } else { + options = append(options, option) + } + } + return options, nil +} + +func subscriptionTypeOption(id string, description string) arguments.Option { + option := arguments.Option{ + Value: setSubscriptionTypeOption(id, description), + } + return option +} + func getMachineTypeOptions(connection *sdk.Connection) ([]arguments.Option, error) { return provider.GetMachineTypeOptions( connection.ClustersMgmt().V1(), @@ -421,12 +468,29 @@ func preRun(cmd *cobra.Command, argv []string) error { // Validate flags / ask for missing data. fs := cmd.Flags() + // Get options for subscription type + subscriptionTypeOptions, _ := getSubscriptionTypeOptions(connection) + err = arguments.PromptOneOf(fs, "subscription-type", subscriptionTypeOptions) + if err != nil { + return err + } + // Only offer the 2 providers known to support OSD now; // but don't validate if set, to not block `ocm` CLI from creating clusters on future providers. providers, _ := osdProviderOptions(connection) - err = arguments.PromptOneOf(fs, "provider", providers) - if err != nil { - return err + // If marketplace-gcp subscription type is used, provider can only be GCP + gcpBillingModel, _ := billing.GetBillingModel(connection, billing.MarketplaceGcpSubscriptionType) + gcpSubscriptionTypeTemplate := subscriptionTypeOption(gcpBillingModel.ID(), gcpBillingModel.Description()) + isGcpMarketplaceSubscriptionType := args.subscriptionType == gcpSubscriptionTypeTemplate.Value + + if isGcpMarketplaceSubscriptionType { + fmt.Println("setting provider to", c.ProviderGCP) + args.provider = c.ProviderGCP + } else { + err = arguments.PromptOneOf(fs, "provider", providers) + if err != nil { + return err + } } if wasClusterWideProxyReceived() { @@ -435,7 +499,12 @@ func preRun(cmd *cobra.Command, argv []string) error { args.clusterWideProxy.Enabled = true } - err = promptCCS(fs) + // If marketplace-gcp subscription type is used, ccs should by default be true + if isGcpMarketplaceSubscriptionType { + fmt.Println("setting ccs to 'true'") + args.ccs.Enabled = true + } + err = promptCCS(fs, args.ccs.Enabled) if err != nil { return err } @@ -530,6 +599,7 @@ func preRun(cmd *cobra.Command, argv []string) error { if err != nil { return err } + return nil } @@ -554,6 +624,10 @@ func run(cmd *cobra.Command, argv []string) error { return err } + if args.interactive { + args.subscriptionType = parseSubscriptionType(args.subscriptionType) + } + clusterConfig := c.Spec{ Name: args.clusterName, Region: args.region, @@ -577,6 +651,7 @@ func run(cmd *cobra.Command, argv []string) error { Private: &args.private, EtcdEncryption: args.etcdEncryption, DefaultIngress: defaultIngress, + SubscriptionType: args.subscriptionType, } cluster, err := c.CreateCluster(connection.ClustersMgmt().V1(), clusterConfig, args.dryRun) @@ -1036,8 +1111,11 @@ func promptExistingVPC(fs *pflag.FlagSet, connection *sdk.Connection) error { return err } -func promptCCS(fs *pflag.FlagSet) error { - err := arguments.PromptBool(fs, "ccs") +func promptCCS(fs *pflag.FlagSet, presetCCS bool) error { + var err error + if !presetCCS { + err = arguments.PromptBool(fs, "ccs") + } if err != nil { return err } diff --git a/go.mod b/go.mod index 91d732b9..bd359d29 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/nwidger/jsoncolor v0.3.2 github.com/onsi/ginkgo/v2 v2.11.0 github.com/onsi/gomega v1.27.8 - github.com/openshift-online/ocm-sdk-go v0.1.356 + github.com/openshift-online/ocm-sdk-go v0.1.368 github.com/openshift/rosa v1.2.24 github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 github.com/spf13/cobra v1.7.0 diff --git a/go.sum b/go.sum index 0f1ba27f..670df710 100644 --- a/go.sum +++ b/go.sum @@ -336,8 +336,8 @@ github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAl github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc= github.com/onsi/gomega v1.27.8/go.mod h1:2J8vzI/s+2shY9XHRApDkdgPo1TKT7P2u6fXeJKFnNQ= -github.com/openshift-online/ocm-sdk-go v0.1.356 h1:FFUAi+mR5pdBsp3pisXp7M4SiFdxKQwNwKTQNY7QDY0= -github.com/openshift-online/ocm-sdk-go v0.1.356/go.mod h1:KYOw8kAKAHyPrJcQoVR82CneQ4ofC02Na4cXXaTq4Nw= +github.com/openshift-online/ocm-sdk-go v0.1.368 h1:qP+gkChV8WDwwpkUw1xUyjTXKdvrwyd70Gff2GMUSeU= +github.com/openshift-online/ocm-sdk-go v0.1.368/go.mod h1:KYOw8kAKAHyPrJcQoVR82CneQ4ofC02Na4cXXaTq4Nw= github.com/openshift/rosa v1.2.24 h1:vv0yYnWHx6CCPEAau/0rS54P2ksaf+uWXb1TQPWxiYE= github.com/openshift/rosa v1.2.24/go.mod h1:MVXB27O3PF8WoOic23I03mmq6/9kVxpFx6FKyLMCyrQ= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= diff --git a/pkg/billing/billing.go b/pkg/billing/billing.go new file mode 100644 index 00000000..ef5371bf --- /dev/null +++ b/pkg/billing/billing.go @@ -0,0 +1,43 @@ +package billing + +import ( + sdk "github.com/openshift-online/ocm-sdk-go" + amv1 "github.com/openshift-online/ocm-sdk-go/accountsmgmt/v1" +) + +const ( + StandardSubscriptionType = "standard" + MarketplaceRhmSubscriptionType = "marketplace-rhm" + MarketplaceGcpSubscriptionType = "marketplace-gcp" +) + +var ValidSubscriptionTypes = []string{ + StandardSubscriptionType, + MarketplaceRhmSubscriptionType, + MarketplaceGcpSubscriptionType, +} + +func GetBillingModel(connection *sdk.Connection, billingModelID string) (*amv1.BillingModelItem, error) { + bilingModel, err := connection.AccountsMgmt().V1().BillingModels().BillingModel(billingModelID).Get().Send() + if err != nil { + return nil, err + } + return bilingModel.Body(), nil +} + +func GetBillingModels(connection *sdk.Connection) ([]*amv1.BillingModelItem, error) { + response, err := connection.AccountsMgmt().V1().BillingModels().List().Send() + if err != nil { + return nil, err + } + billingModels := response.Items().Slice() + var validBillingModel []*amv1.BillingModelItem + for _, billingModel := range billingModels { + for _, validSubscriptionTypeId := range ValidSubscriptionTypes { + if billingModel.ID() == validSubscriptionTypeId { + validBillingModel = append(validBillingModel, billingModel) + } + } + } + return validBillingModel, nil +} diff --git a/pkg/cluster/cluster.go b/pkg/cluster/cluster.go index 67ad48d0..dbdb08a1 100644 --- a/pkg/cluster/cluster.go +++ b/pkg/cluster/cluster.go @@ -27,7 +27,7 @@ import ( "time" sdk "github.com/openshift-online/ocm-sdk-go" - amsv1 "github.com/openshift-online/ocm-sdk-go/accountsmgmt/v1" + amv1 "github.com/openshift-online/ocm-sdk-go/accountsmgmt/v1" cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1" ) @@ -68,6 +68,7 @@ type Spec struct { ChannelGroup string Expiration time.Time EtcdEncryption bool + SubscriptionType string // Scaling config ComputeMachineType string @@ -312,6 +313,7 @@ func CreateCluster(cmv1Client *cmv1.Client, config Spec, dryRun bool) (*cmv1.Clu ID(config.Flavour), ). EtcdEncryption(config.EtcdEncryption). + BillingModel(cmv1.BillingModel(config.SubscriptionType)). Properties(clusterProperties) clusterBuilder = clusterBuilder.Version( @@ -703,7 +705,7 @@ func GetClusterAddOns(connection *sdk.Connection, clusterID string) ([]*AddOnIte } // Only display add-ons for which the org has quota - quotaCosts.Each(func(quotaCost *amsv1.QuotaCost) bool { + quotaCosts.Each(func(quotaCost *amv1.QuotaCost) bool { relatedResources := quotaCost.RelatedResources() for _, relatedResource := range relatedResources { if relatedResource.ResourceType() == "add-on" && diff --git a/pkg/cluster/describe.go b/pkg/cluster/describe.go index d0d26fd9..371cbaf7 100644 --- a/pkg/cluster/describe.go +++ b/pkg/cluster/describe.go @@ -196,6 +196,7 @@ func PrintClusterDescription(connection *sdk.Connection, cluster *cmv1.Cluster) "Infra: %d\n"+ "Computes: %s\n"+ "Product: %s\n"+ + "Subscription type: %s\n"+ "Provider: %s\n"+ "Version: %s\n"+ "Region: %s\n"+ @@ -221,6 +222,7 @@ func PrintClusterDescription(connection *sdk.Connection, cluster *cmv1.Cluster) cluster.Nodes().Infra(), computesStr, cluster.Product().ID(), + cluster.BillingModel(), cluster.CloudProvider().ID(), cluster.OpenshiftVersion(), cluster.Region().ID(),