From 4082892f72b05f0adf508987b12c5ebddd183b3d Mon Sep 17 00:00:00 2001 From: Deepti Kumar Date: Fri, 8 Mar 2024 05:44:26 +0000 Subject: [PATCH] [PLAT-10499][PLAT-10777][PLAT-11248]Support CRUD operations for Providers - Create kubernetes provider Summary: ``` Create a Kubernetes provider in YugabyteDB Anywhere Usage: yba provider create kubernetes [flags] Aliases: kubernetes, k8s Flags: --type string [Required] Kubernetes cloud type. Allowed values: aks, eks, gke, custom. --image-registry string [Optional] Kubernetes Image Registry. (default "quay.io/yugabyte/yugabyte") --pull-secret-file string [Required] Kuberenetes Pull Secret File Path. --kubeconfig-file string [Optional] Kuberenetes Config File Path. --storage-class string [Optional] Kubernetes Storage Class. --region stringArray [Required] Region associated with the Kubernetes provider. Minimum number of required regions = 1. Provide the following comma separated fields as key-value pairs:"region-name=,config-name=,config-file-path=,storage-class=,cert-manager-cluster-issuer=,cert-manager-issuer=,domain=,namespace=,pod-address-template=,overrirdes-file-path=". Region name is a required key-value. Config name (and Config File Path provided together), Storage Class, Cert Manager Cluster Issuer, Cert Manager Issuer, Domain, Namespace, Pod Address Template and Overrides File Path are optional. Each region needs to be added using a separate --region flag. --zone stringArray [Required] Zone associated to the Kubernetes Region defined. Provide the following comma separated fields as key-value pairs:"zone-name=,region-name=,config-name=,config-file-path=,storage-class=,cert-manager-cluster-issuer=,cert-manager-issuer=,domain=,namespace=,pod-address-template=,overrirdes-file-path=". Zone name and Region name are required values. Config name (and Config File Path provided together), Storage Class, Cert Manager Cluster Issuer, Cert Manager Issuer, Domain, Namespace, Pod Address Template and Overrides File Path are optional. Each --region definition must have atleast one corresponding --zone definition. Multiple --zone definitions can be provided per region.Each zone needs to be added using a separate --zone flag. --airgap-install [Optional] Do YugabyteDB nodes have access to public internet to download packages. -h, --help help for kubernetes Global Flags: -a, --apiToken string YugabyteDB Anywhere api token. --config string Config file, defaults to $HOME/.yba-cli.yaml --debug Use debug mode, same as --logLevel debug. -H, --host string YugabyteDB Anywhere Host (default "http://localhost:9000") -l, --logLevel string Select the desired log level format. (default "info") --no-color Disable colors in output , defaults to false. -o, --output string Select the desired output format. Allowed values: table, json, pretty. (default "table") -n, --provider-name string [Required] The name of the provider to be created. --timeout duration Wait command timeout, example: 5m, 1h. (default 168h0m0s) --wait Wait until the task is completed, otherwise it will exit immediately. (default true) ``` Test Plan: Creating a provider with overrides in one zone: ``` ./yba provider create kubernetes -n dkumar --type gke \ --pull-secret-file \ --region region-name=us-west1 \ --zone zone-name=us-west1-b,region-name=us-west1,storage-class=yb-standard,overrirdes-file-path= \ --zone zone-name=us-west1-a,region-name=us-west1,storage-class=yb-standard \ --zone zone-name=us-west1-c,region-name=us-west1,storage-class=yb-standard --debug ``` Reviewers: sneelakantan, vpatibandla Reviewed By: sneelakantan Subscribers: yugaware Differential Revision: https://phorge.dev.yugabyte.com/D31006 --- .../cmd/provider/create/aws_provider.go | 2 +- .../cmd/provider/create/azu_provider.go | 2 +- .../cmd/provider/create/create_provider.go | 2 +- .../cmd/provider/create/createproviderutil.go | 7 + .../cmd/provider/create/gcp_provider.go | 2 +- .../provider/create/kubernetes_provider.go | 458 ++++++++++++++++++ .../cmd/provider/create/onprem_provider.go | 2 +- .../yba-cli/cmd/universe/build_universe.go | 141 +++++- .../yba-cli/cmd/universe/create_universe.go | 23 + managed/yba-cli/cmd/util/util.go | 32 ++ .../formatter/kubernetes/kubernetes.go | 4 +- .../internal/formatter/provider/region.go | 11 + .../internal/formatter/provider/zone.go | 11 + 13 files changed, 673 insertions(+), 24 deletions(-) create mode 100644 managed/yba-cli/cmd/provider/create/kubernetes_provider.go diff --git a/managed/yba-cli/cmd/provider/create/aws_provider.go b/managed/yba-cli/cmd/provider/create/aws_provider.go index 2100aefbff2b..7bb4034a7f1c 100644 --- a/managed/yba-cli/cmd/provider/create/aws_provider.go +++ b/managed/yba-cli/cmd/provider/create/aws_provider.go @@ -188,7 +188,7 @@ func init() { "custom-ssh-keypair-file-path") createAWSProviderCmd.Flags().Bool("airgap-install", false, - "[Optional] Are YugabyteDB nodes installed in an air-gapped environment, "+ + "[Optional] Are YugabyteDB nodes installed in an air-gapped environment,"+ " lacking access to the public internet for package downloads, "+ "defaults to false.") diff --git a/managed/yba-cli/cmd/provider/create/azu_provider.go b/managed/yba-cli/cmd/provider/create/azu_provider.go index 9cd430d45540..f2d3bd7e0243 100644 --- a/managed/yba-cli/cmd/provider/create/azu_provider.go +++ b/managed/yba-cli/cmd/provider/create/azu_provider.go @@ -185,7 +185,7 @@ func init() { "custom-ssh-keypair-file-path") createAzureProviderCmd.Flags().Bool("airgap-install", false, - "[Optional] Are YugabyteDB nodes installed in an air-gapped environment, "+ + "[Optional] Are YugabyteDB nodes installed in an air-gapped environment,"+ " lacking access to the public internet for package downloads, "+ "defaults to false.") } diff --git a/managed/yba-cli/cmd/provider/create/create_provider.go b/managed/yba-cli/cmd/provider/create/create_provider.go index 2a2b0fa999a3..f3e54d14e0f6 100644 --- a/managed/yba-cli/cmd/provider/create/create_provider.go +++ b/managed/yba-cli/cmd/provider/create/create_provider.go @@ -23,7 +23,7 @@ func init() { CreateProviderCmd.AddCommand(createAWSProviderCmd) CreateProviderCmd.AddCommand(createGCPProviderCmd) CreateProviderCmd.AddCommand(createAzureProviderCmd) - // CreateProviderCmd.AddCommand(createK8sProviderCmd) + CreateProviderCmd.AddCommand(createK8sProviderCmd) CreateProviderCmd.AddCommand(createOnpremProviderCmd) CreateProviderCmd.PersistentFlags().StringP("provider-name", "n", "", diff --git a/managed/yba-cli/cmd/provider/create/createproviderutil.go b/managed/yba-cli/cmd/provider/create/createproviderutil.go index a4ff91841e5f..a43a74f04172 100644 --- a/managed/yba-cli/cmd/provider/create/createproviderutil.go +++ b/managed/yba-cli/cmd/provider/create/createproviderutil.go @@ -57,3 +57,10 @@ func waitForCreateProviderTask( } } + +func valueNotFoundForKeyError(key string) { + logrus.Fatalln( + formatter.Colorize( + fmt.Sprintf("Key \"%s\" specified but value is empty\n", key), + formatter.RedColor)) +} diff --git a/managed/yba-cli/cmd/provider/create/gcp_provider.go b/managed/yba-cli/cmd/provider/create/gcp_provider.go index 490d19d1a100..44e96b9829a1 100644 --- a/managed/yba-cli/cmd/provider/create/gcp_provider.go +++ b/managed/yba-cli/cmd/provider/create/gcp_provider.go @@ -173,7 +173,7 @@ func init() { "custom-ssh-keypair-file-path") createGCPProviderCmd.Flags().Bool("airgap-install", false, - "[Optional] Are YugabyteDB nodes installed in an air-gapped environment, "+ + "[Optional] Are YugabyteDB nodes installed in an air-gapped environment,"+ " lacking access to the public internet for package downloads, "+ "defaults to false.") } diff --git a/managed/yba-cli/cmd/provider/create/kubernetes_provider.go b/managed/yba-cli/cmd/provider/create/kubernetes_provider.go new file mode 100644 index 000000000000..71686e3d0387 --- /dev/null +++ b/managed/yba-cli/cmd/provider/create/kubernetes_provider.go @@ -0,0 +1,458 @@ +/* + * Copyright (c) YugaByte, Inc. + */ + +package create + +import ( + "strings" + + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + ybaclient "github.com/yugabyte/platform-go-client" + "github.com/yugabyte/yugabyte-db/managed/yba-cli/cmd/util" + ybaAuthClient "github.com/yugabyte/yugabyte-db/managed/yba-cli/internal/client" + "github.com/yugabyte/yugabyte-db/managed/yba-cli/internal/formatter" +) + +// createK8sProviderCmd represents the provider command +var createK8sProviderCmd = &cobra.Command{ + Use: "kubernetes", + Aliases: []string{"k8s"}, + SuggestFor: []string{"gke", "eks", "aks"}, + Short: "Create a Kubernetes YugabyteDB Anywhere provider", + Long: "Create a Kubernetes provider in YugabyteDB Anywhere", + PreRun: func(cmd *cobra.Command, args []string) { + providerNameFlag, err := cmd.Flags().GetString("provider-name") + if err != nil { + logrus.Fatalf(formatter.Colorize(err.Error()+"\n", formatter.RedColor)) + } + if len(providerNameFlag) == 0 { + cmd.Help() + logrus.Fatalln( + formatter.Colorize("No provider name found to create\n", formatter.RedColor)) + } + }, + Run: func(cmd *cobra.Command, args []string) { + authAPI, err := ybaAuthClient.NewAuthAPIClient() + if err != nil { + logrus.Fatalf(formatter.Colorize(err.Error()+"\n", formatter.RedColor)) + } + authAPI.GetCustomerUUID() + providerName, err := cmd.Flags().GetString("provider-name") + if err != nil { + logrus.Fatalf(formatter.Colorize(err.Error()+"\n", formatter.RedColor)) + } + providerCode := "kubernetes" + + airgapInstall, err := cmd.Flags().GetBool("airgap-install") + if err != nil { + logrus.Fatalf(formatter.Colorize(err.Error()+"\n", formatter.RedColor)) + } + + providerType, err := cmd.Flags().GetString("type") + if err != nil { + logrus.Fatalf(formatter.Colorize(err.Error()+"\n", formatter.RedColor)) + } + + imageRegistry, err := cmd.Flags().GetString("image-registry") + if err != nil { + logrus.Fatalf(formatter.Colorize(err.Error()+"\n", formatter.RedColor)) + } + + pullSecretFilePath, err := cmd.Flags().GetString("pull-secret-file") + if err != nil { + logrus.Fatalf(formatter.Colorize(err.Error()+"\n", formatter.RedColor)) + } + + logrus.Debug("Reading Kubernetes Pull Secret") + pullSecretContent := util.YAMLtoString(pullSecretFilePath) + + configFilePath, err := cmd.Flags().GetString("kubeconfig-file") + if err != nil { + logrus.Fatalf(formatter.Colorize(err.Error()+"\n", formatter.RedColor)) + } + + var configContent string + if len(configFilePath) > 0 { + logrus.Debug("Reading Kube Config") + configContent = util.YAMLtoString(configFilePath) + } + + storageClass, err := cmd.Flags().GetString("storage-class") + if err != nil { + logrus.Fatalf(formatter.Colorize(err.Error()+"\n", formatter.RedColor)) + } + + regions, err := cmd.Flags().GetStringArray("region") + if err != nil { + logrus.Fatalf(formatter.Colorize(err.Error()+"\n", formatter.RedColor)) + } + + zones, err := cmd.Flags().GetStringArray("zone") + if err != nil { + logrus.Fatalf(formatter.Colorize(err.Error()+"\n", formatter.RedColor)) + } + + requestBody := ybaclient.Provider{ + Code: util.GetStringPointer(providerCode), + Name: util.GetStringPointer(providerName), + Regions: buildK8sRegions(regions, zones), + Details: &ybaclient.ProviderDetails{ + AirGapInstall: util.GetBoolPointer(airgapInstall), + CloudInfo: &ybaclient.CloudInfo{ + Kubernetes: &ybaclient.KubernetesInfo{ + KubernetesImageRegistry: util.GetStringPointer(imageRegistry), + KubernetesProvider: util.GetStringPointer(providerType), + KubernetesPullSecretContent: util.GetStringPointer(pullSecretContent), + KubernetesStorageClass: util.GetStringPointer(storageClass), + KubeConfigContent: util.GetStringPointer(configContent), + }, + }, + }, + } + + rCreate, response, err := authAPI.CreateProvider(). + CreateProviderRequest(requestBody).Execute() + if err != nil { + errMessage := util.ErrorFromHTTPResponse(response, err, "Provider", "Create K8s") + logrus.Fatalf(formatter.Colorize(errMessage.Error()+"\n", formatter.RedColor)) + } + + providerUUID := rCreate.GetResourceUUID() + taskUUID := rCreate.GetTaskUUID() + + waitForCreateProviderTask(authAPI, providerName, providerUUID, taskUUID) + }, +} + +func init() { + createK8sProviderCmd.Flags().SortFlags = false + + createK8sProviderCmd.Flags().String("type", "", + "[Required] Kubernetes cloud type. Allowed values: aks, eks, gke, custom.") + createK8sProviderCmd.MarkFlagRequired("kubernetes") + createK8sProviderCmd.Flags().String("image-registry", "quay.io/yugabyte/yugabyte", + "[Optional] Kubernetes Image Registry.") + + createK8sProviderCmd.Flags().String("pull-secret-file", "", + "[Required] Kuberenetes Pull Secret File Path.") + createK8sProviderCmd.MarkFlagRequired("pull-secret-file") + + createK8sProviderCmd.Flags().String("kubeconfig-file", "", + "[Optional] Kuberenetes Config File Path.") + + createK8sProviderCmd.Flags().String("storage-class", "", + "[Optional] Kubernetes Storage Class.") + + createK8sProviderCmd.Flags().StringArray("region", []string{}, + "[Required] Region associated with the Kubernetes provider. Minimum number of required "+ + "regions = 1. Provide the following comma separated fields as key-value pairs:"+ + "\"region-name=,config-name=,"+ + "config-file-path=,"+ + "storage-class=,"+ + "cert-manager-cluster-issuer=,"+ + "cert-manager-issuer=,domain=,namespace=,"+ + "pod-address-template=,"+ + "overrirdes-file-path=\". "+ + formatter.Colorize("Region name is a required key-value.", + formatter.GreenColor)+ + " Config name (and Config File Path provided together), Storage Class, Cert Manager"+ + " Cluster Issuer, Cert Manager Issuer, Domain, Namespace, Pod Address Template and"+ + " Overrides File Path are optional. "+ + "Each region needs to be added using a separate --region flag.") + + createK8sProviderCmd.Flags().StringArray("zone", []string{}, + "[Required] Zone associated to the Kubernetes Region defined. "+ + "Provide the following comma separated fields as key-value pairs:"+ + "\"zone-name=,region-name=,config-name=,"+ + "config-file-path=,"+ + "storage-class=,"+ + "cert-manager-cluster-issuer=,"+ + "cert-manager-issuer=,domain=,namespace=,"+ + "pod-address-template=,"+ + "overrirdes-file-path=\". "+ + formatter.Colorize("Zone name and Region name are required values. ", + formatter.GreenColor)+ + " Config name (and Config File Path provided together), Storage Class, Cert Manager"+ + " Cluster Issuer, Cert Manager Issuer, Domain, Namespace, Pod Address Template and"+ + " Overrides File Path are optional. "+ + "Each --region definition "+ + "must have atleast one corresponding --zone definition. Multiple --zone definitions "+ + "can be provided per region."+ + "Each zone needs to be added using a separate --zone flag.") + createK8sProviderCmd.Flags().Bool("airgap-install", false, + "[Optional] Do YugabyteDB nodes have access to public internet to download packages.") +} + +func buildK8sRegions( + regionStrings, + zoneStrings []string) (res []ybaclient.Region) { + if len(regionStrings) == 0 { + logrus.Fatalln( + formatter.Colorize("Atleast one region is required per provider.", + formatter.RedColor)) + } + for _, regionString := range regionStrings { + region := map[string]string{} + for _, regionInfo := range strings.Split(regionString, ",") { + kvp := strings.Split(regionInfo, "=") + if len(kvp) != 2 { + logrus.Fatalln( + formatter.Colorize("Incorrect format in region description.", + formatter.RedColor)) + } + key := kvp[0] + val := kvp[1] + switch key { + case "region-name": + if len(strings.TrimSpace(val)) != 0 { + region["name"] = val + } else { + valueNotFoundForKeyError(key) + } + case "config-name": + if len(strings.TrimSpace(val)) != 0 { + region["config-name"] = val + } else { + valueNotFoundForKeyError(key) + } + case "config-file-path": + if len(strings.TrimSpace(val)) != 0 { + region["config-file-path"] = val + } else { + valueNotFoundForKeyError(key) + } + case "storage-class": + if len(strings.TrimSpace(val)) != 0 { + region["storage-class"] = val + } else { + valueNotFoundForKeyError(key) + } + case "cert-manager-cluster-issuer": + if len(strings.TrimSpace(val)) != 0 { + region["cert-manager-cluster-issuer"] = val + } else { + valueNotFoundForKeyError(key) + } + case "cert-manager-issuer": + if len(strings.TrimSpace(val)) != 0 { + region["cert-manager-issuer"] = val + } else { + valueNotFoundForKeyError(key) + } + case "domain": + if len(strings.TrimSpace(val)) != 0 { + region["domain"] = val + } else { + valueNotFoundForKeyError(key) + } + case "namespace": + if len(strings.TrimSpace(val)) != 0 { + region["namespace"] = val + } else { + valueNotFoundForKeyError(key) + } + case "overrirdes-file-path": + if len(strings.TrimSpace(val)) != 0 { + region["overrirdes-file-path"] = val + } else { + valueNotFoundForKeyError(key) + } + case "pod-address-template": + if len(strings.TrimSpace(val)) != 0 { + region["pod-address-template"] = val + } else { + valueNotFoundForKeyError(key) + } + } + } + if _, ok := region["name"]; !ok { + logrus.Fatalln( + formatter.Colorize("Name not specified in region.", + formatter.RedColor)) + } + var configContent string + if _, ok := region["config-name"]; ok { + if _, ok := region["config-file-path"]; !ok { + logrus.Fatalln( + formatter.Colorize("Config file path not specified in region.", + formatter.RedColor)) + } else { + logrus.Debug("Reading Region Kube Config") + configContent = util.YAMLtoString(region["config-file-path"]) + } + } + + var overrides string + if _, ok := region["overrirdes-file-path"]; ok { + logrus.Debug("Reading Region Kubernetes Overrides") + overrides = util.YAMLtoString(region["overrirdes-file-path"]) + } + + zones := buildK8sZones(zoneStrings, region["name"]) + r := ybaclient.Region{ + Code: util.GetStringPointer(region["name"]), + Name: util.GetStringPointer(region["name"]), + Details: &ybaclient.RegionDetails{ + CloudInfo: &ybaclient.RegionCloudInfo{ + Kubernetes: &ybaclient.KubernetesRegionInfo{ + CertManagerClusterIssuer: util.GetStringPointer(region["cert-manager-cluster-issuer"]), + CertManagerIssuer: util.GetStringPointer(region["cert-manager-issuer"]), + KubeConfigContent: util.GetStringPointer(configContent), + KubeDomain: util.GetStringPointer(region["domain"]), + KubeNamespace: util.GetStringPointer(region["namespace"]), + KubePodAddressTemplate: util.GetStringPointer(region["pod-address-template"]), + KubernetesStorageClass: util.GetStringPointer(region["storage-class"]), + Overrides: util.GetStringPointer(overrides), + }, + }, + }, + Zones: zones, + } + res = append(res, r) + } + return res +} + +func buildK8sZones( + zoneStrings []string, + regionName string) (res []ybaclient.AvailabilityZone) { + for _, zoneString := range zoneStrings { + zone := map[string]string{} + for _, zoneInfo := range strings.Split(zoneString, ",") { + kvp := strings.Split(zoneInfo, "=") + if len(kvp) != 2 { + logrus.Fatalln( + formatter.Colorize("Incorrect format in zone description", + formatter.RedColor)) + } + key := kvp[0] + val := kvp[1] + switch key { + case "zone-name": + if len(strings.TrimSpace(val)) != 0 { + zone["name"] = val + } else { + valueNotFoundForKeyError(key) + } + case "region-name": + if len(strings.TrimSpace(val)) != 0 { + zone["region-name"] = val + } else { + valueNotFoundForKeyError(key) + } + case "config-name": + if len(strings.TrimSpace(val)) != 0 { + zone["config-name"] = val + } else { + valueNotFoundForKeyError(key) + } + case "config-file-path": + if len(strings.TrimSpace(val)) != 0 { + zone["config-file-path"] = val + } else { + valueNotFoundForKeyError(key) + } + case "storage-class": + if len(strings.TrimSpace(val)) != 0 { + zone["storage-class"] = val + } else { + valueNotFoundForKeyError(key) + } + case "cert-manager-cluster-issuer": + if len(strings.TrimSpace(val)) != 0 { + zone["cert-manager-cluster-issuer"] = val + } else { + valueNotFoundForKeyError(key) + } + case "cert-manager-issuer": + if len(strings.TrimSpace(val)) != 0 { + zone["cert-manager-issuer"] = val + } else { + valueNotFoundForKeyError(key) + } + case "domain": + if len(strings.TrimSpace(val)) != 0 { + zone["domain"] = val + } else { + valueNotFoundForKeyError(key) + } + case "namespace": + if len(strings.TrimSpace(val)) != 0 { + zone["namespace"] = val + } else { + valueNotFoundForKeyError(key) + } + case "overrirdes-file-path": + if len(strings.TrimSpace(val)) != 0 { + zone["overrirdes-file-path"] = val + } else { + valueNotFoundForKeyError(key) + } + case "pod-address-template": + if len(strings.TrimSpace(val)) != 0 { + zone["pod-address-template"] = val + } else { + valueNotFoundForKeyError(key) + } + } + } + if _, ok := zone["name"]; !ok { + logrus.Fatalln( + formatter.Colorize("Name not specified in zone.", + formatter.RedColor)) + } + if _, ok := zone["region-name"]; !ok { + logrus.Fatalln( + formatter.Colorize("Region name not specified in zone.", + formatter.RedColor)) + } + + var configContent string + if _, ok := zone["config-name"]; ok { + if _, ok := zone["config-file-path"]; !ok { + logrus.Fatalln( + formatter.Colorize("Config file path not specified in zone.", + formatter.RedColor)) + } else { + logrus.Debug("Reading Zone Kube Config") + configContent = util.YAMLtoString(zone["config-file-path"]) + } + } + + var overrides string + if _, ok := zone["overrirdes-file-path"]; ok { + logrus.Debug("Reading Zone Kubernetes Overrides") + overrides = util.YAMLtoString(zone["overrirdes-file-path"]) + } + + if strings.Compare(zone["region-name"], regionName) == 0 { + z := ybaclient.AvailabilityZone{ + Code: util.GetStringPointer(zone["name"]), + Name: zone["name"], + Details: &ybaclient.AvailabilityZoneDetails{ + CloudInfo: &ybaclient.AZCloudInfo{ + Kubernetes: &ybaclient.KubernetesRegionInfo{ + CertManagerClusterIssuer: util.GetStringPointer(zone["cert-manager-cluster-issuer"]), + CertManagerIssuer: util.GetStringPointer(zone["cert-manager-issuer"]), + KubeConfigContent: util.GetStringPointer(configContent), + KubeDomain: util.GetStringPointer(zone["domain"]), + KubeNamespace: util.GetStringPointer(zone["namespace"]), + KubePodAddressTemplate: util.GetStringPointer(zone["pod-address-template"]), + KubernetesStorageClass: util.GetStringPointer(zone["storage-class"]), + Overrides: util.GetStringPointer(overrides), + }, + }, + }, + } + res = append(res, z) + } + } + if len(res) == 0 { + logrus.Fatalln( + formatter.Colorize("Atleast one zone is required per region.", + formatter.RedColor)) + } + return res +} diff --git a/managed/yba-cli/cmd/provider/create/onprem_provider.go b/managed/yba-cli/cmd/provider/create/onprem_provider.go index a0f6ceb8e34f..660306657bca 100644 --- a/managed/yba-cli/cmd/provider/create/onprem_provider.go +++ b/managed/yba-cli/cmd/provider/create/onprem_provider.go @@ -225,7 +225,7 @@ func init() { " manually, set to false to provision during universe creation, defaults to false.") createOnpremProviderCmd.Flags().Bool("airgap-install", false, - "[Optional] Are YugabyteDB nodes installed in an air-gapped environment, "+ + "[Optional] Are YugabyteDB nodes installed in an air-gapped environment,"+ " lacking access to the public internet for package downloads, "+ "defaults to false.") createOnpremProviderCmd.Flags().Bool("install-node-exporter", true, diff --git a/managed/yba-cli/cmd/universe/build_universe.go b/managed/yba-cli/cmd/universe/build_universe.go index 94b2eae3d3fb..cb5c7b576821 100644 --- a/managed/yba-cli/cmd/universe/build_universe.go +++ b/managed/yba-cli/cmd/universe/build_universe.go @@ -113,9 +113,10 @@ func buildClusters( if len(providerListResponse) < 1 { return nil, fmt.Errorf("no provider found") } - providerUUID := providerListResponse[0].GetUuid() + providerUsed := providerListResponse[0] + providerUUID := providerUsed.GetUuid() if len(providerName) == 0 { - providerName = providerListResponse[0].GetName() + providerName = providerUsed.GetName() } logrus.Info("Using provider: ", fmt.Sprintf("%s %s", @@ -148,8 +149,29 @@ func buildClusters( if err != nil { return nil, err } + + var k8sTserverMemSize, k8sMasterMemSize, k8sTserverCPUCoreCount, k8sMasterCPUCoreCount []float64 + if providerType == "kubernetes" { dedicatedNodes = true + + k8sTserverMemSize, err = cmd.Flags().GetFloat64Slice("k8s-tserver-mem-size") + if err != nil { + return nil, err + } + k8sTserverCPUCoreCount, err = cmd.Flags().GetFloat64Slice("k8s-tserver-cpu-core-count") + if err != nil { + return nil, err + } + + k8sMasterMemSize, err = cmd.Flags().GetFloat64Slice("k8s-master-mem-size") + if err != nil { + return nil, err + } + k8sMasterCPUCoreCount, err = cmd.Flags().GetFloat64Slice("k8s-master-cpu-core-count") + if err != nil { + return nil, err + } } var masterInstanceType string @@ -214,7 +236,7 @@ func buildClusters( if err != nil { return nil, err } - regionsInProvider := providerListResponse[0].GetRegions() + regionsInProvider := providerUsed.GetRegions() if len(regionsInProvider) == 0 { return nil, fmt.Errorf("no regions found for provider %s", providerName) } @@ -375,10 +397,9 @@ func buildClusters( } idKey := r[0].GetIdKey() accessKeyCode = idKey.GetKeyCode() - + logrus.Info("Using access key: ", + formatter.Colorize(accessKeyCode, formatter.GreenColor), "\n") } - logrus.Info("Using access key: ", - formatter.Colorize(accessKeyCode, formatter.GreenColor), "\n") awsARNString, err := cmd.Flags().GetString("aws-arn-string") if err != nil { @@ -398,6 +419,52 @@ func buildClusters( enableIPV6 = false } + k8sUniverseOverridesFilePath, err := cmd.Flags().GetString( + "kubernetes-universe-overrides-file-path") + if err != nil { + return nil, err + } + if providerType != "kubernetes" && len(k8sUniverseOverridesFilePath) > 0 { + logrus.Debug( + "kubernetest-universe-overrides-file-path can only be set for" + + " Kubernetes universes, ignoring value\n") + k8sUniverseOverridesFilePath = "" + } + var k8sUniverseOverrides string + if len(k8sUniverseOverridesFilePath) > 0 { + logrus.Debug("Reading Helm Universe Overrides") + k8sUniverseOverrides = util.YAMLtoString(k8sUniverseOverridesFilePath) + } + + k8sAZOverridesFilePaths, err := cmd.Flags().GetStringArray( + "kubernetes-az-overrides-file-path") + if err != nil { + return nil, err + } + if providerType != "kubernetes" && len(k8sAZOverridesFilePaths) > 0 { + logrus.Debug( + "kubernetest-az-overrides-file-path can only be set for" + + " Kubernetes universes, ignoring value\n") + k8sAZOverridesFilePaths = make([]string, 0) + } + + k8sAZOverridesMap := make(map[string]string, 0) + for _, k := range k8sAZOverridesFilePaths { + var k8sAZOverrides string + if len(k) > 0 { + logrus.Debug("Reading Helm AZ Overrides") + k8sAZOverrides = util.YAMLtoString(k) + } + regionIndex := strings.Index(k8sAZOverrides, "\n") + + region := strings.TrimSpace(strings.ReplaceAll(k8sAZOverrides[:regionIndex], ":", "")) + regionOverride := k8sAZOverrides[regionIndex+1:] + + if len(region) > 0 && len(regionOverride) > 0 { + k8sAZOverridesMap[region] = regionOverride + } + } + userTagsMap, err := cmd.Flags().GetStringToString("user-tags") if err != nil { return nil, err @@ -459,6 +526,7 @@ func buildClusters( } c := ybaclient.Cluster{ ClusterType: clusterType, + Index: util.GetInt32Pointer(int32(i)), UserIntent: ybaclient.UserIntent{ UniverseName: util.GetStringPointer(universeName), ProviderType: util.GetStringPointer(providerType), @@ -496,10 +564,44 @@ func buildClusters( PreferredRegion: util.GetStringPointer(preferredRegions[i]), AwsArnString: util.GetStringPointer(awsARNString), - MasterGFlags: util.StringMap(masterGFlags), - TserverGFlags: util.StringMap(tserverGFlagsList[i]), + MasterGFlags: util.StringMap(masterGFlags), + TserverGFlags: util.StringMap(tserverGFlagsList[i]), + UniverseOverrides: util.GetStringPointer(k8sUniverseOverrides), + AzOverrides: util.StringtoStringMap(k8sAZOverridesMap), }, } + if providerType == "kubernetes" { + k8sTserverMemSizeLen := len(k8sTserverMemSize) + k8sMasterMemSizeLen := len(k8sMasterMemSize) + k8sTserverCPUCoreCountLen := len(k8sTserverCPUCoreCount) + k8sMasterCPUCoreCountLen := len(k8sMasterCPUCoreCount) + if i == k8sTserverMemSizeLen { + k8sTserverMemSize = append(k8sTserverMemSize, 4) + k8sTserverMemSizeLen = k8sTserverMemSizeLen + 1 + } + if i == k8sMasterMemSizeLen { + k8sMasterMemSize = append(k8sMasterMemSize, 4) + k8sMasterMemSizeLen = k8sMasterMemSizeLen + 1 + } + if i == k8sTserverCPUCoreCountLen { + k8sTserverCPUCoreCount = append(k8sTserverCPUCoreCount, 2) + k8sTserverCPUCoreCountLen = k8sTserverCPUCoreCountLen + 1 + } + if i == k8sMasterCPUCoreCountLen { + k8sMasterCPUCoreCount = append(k8sTserverCPUCoreCount, 2) + k8sMasterCPUCoreCountLen = k8sMasterCPUCoreCountLen + 1 + } + userIntent := c.GetUserIntent() + userIntent.SetTserverK8SNodeResourceSpec(ybaclient.K8SNodeResourceSpec{ + MemoryGib: k8sTserverMemSize[i], + CpuCoreCount: k8sTserverCPUCoreCount[i], + }) + userIntent.SetMasterK8SNodeResourceSpec(ybaclient.K8SNodeResourceSpec{ + MemoryGib: k8sMasterMemSize[i], + CpuCoreCount: k8sMasterCPUCoreCount[i], + }) + c.SetUserIntent(userIntent) + } res = append(res, c) } @@ -600,6 +702,19 @@ func buildDeviceInfo( mountPoints[i] = "" } } + if providerType == "kubernetes" { + if i == storageClassLen { + storageClass = append(storageClass, "standard") + storageClassLen = storageClassLen + 1 + } + } else { + if i == storageClassLen { + storageClass = append(storageClass, "") + storageClassLen = storageClassLen + 1 + } else { + storageClass[i] = "" + } + } if i == numVolumeLen { numVolumes = append(numVolumes, 1) numVolumeLen = numVolumeLen + 1 @@ -617,16 +732,6 @@ func buildDeviceInfo( storageType = append(storageType, storageTypeDefault) storageTypeLen = storageTypeLen + 1 } - if i == storageClassLen { - defaultStorageClass := "" - if providerType == "kubernetes" { - defaultStorageClass = "standard" - } else { - defaultStorageClass = "" - } - storageClass = append(storageClass, defaultStorageClass) - storageClassLen = storageClassLen + 1 - } deviceInfo := &ybaclient.DeviceInfo{ DiskIops: util.GetInt32Pointer(int32(diskIops[i])), MountPoints: util.GetStringPointer(mountPoints[i]), diff --git a/managed/yba-cli/cmd/universe/create_universe.go b/managed/yba-cli/cmd/universe/create_universe.go index 89ca357f52f4..aac51df0f6a5 100644 --- a/managed/yba-cli/cmd/universe/create_universe.go +++ b/managed/yba-cli/cmd/universe/create_universe.go @@ -296,6 +296,12 @@ func init() { "[Optional] Desired throughput for the volumes mounted on this instance in MB/s, "+ "supported only for AWS. Provide throughput "+ "for each cluster as a separate flag or as comma separated values.") + createUniverseCmd.Flags().Float64Slice("k8s-tserver-mem-size", []float64{4, 4}, + "[Optional] Memory size of the kubernetes tserver node in GB. Provide k8s-tserver-mem-size "+ + "for each cluster as a separate flag or as comma separated values.") + createUniverseCmd.Flags().Float64Slice("k8s-tserver-cpu-core-count", []float64{2, 2}, + "[Optional] CPU core count of the kubernetes tserver node. Provide k8s-tserver-cpu-core-count "+ + "for each cluster as a separate flag or as comma separated values.") // if dedicated nodes is set to true createUniverseCmd.Flags().String("dedicated-master-instance-type", "", @@ -327,6 +333,12 @@ func init() { createUniverseCmd.Flags().Int("dedicated-master-throughput", 125, "[Optional] Desired throughput for the volumes mounted on this instance in MB/s, "+ "supported only for AWS.") + createUniverseCmd.Flags().Float64Slice("k8s-master-mem-size", []float64{4, 4}, + "[Optional] Memory size of the kubernetes master node in GB. Provide k8s-tserver-mem-size "+ + "for each cluster as a separate flag or as comma separated values.") + createUniverseCmd.Flags().Float64Slice("k8s-master-cpu-core-count", []float64{2, 2}, + "[Optional] CPU core count of the kubernetes master node. Provide k8s-tserver-cpu-core-count "+ + "for each cluster as a separate flag or as comma separated values.") // Advanced configuratopn // taken only for Primary cluster createUniverseCmd.Flags().Bool("assign-public-ip", true, @@ -381,6 +393,17 @@ func init() { "as key=value pairs per flag. Example \"--user-tags "+ "name=test --user-tags owner=development\" OR "+ "\"--user-tags name=test,owner=development\".") + createUniverseCmd.Flags().String("kubernetes-universe-overrides-file-path", "", + "[Optional] Helm Overrides file path for the universe, supported for Kubernetes."+ + " For examples on universe overrides file contents, please refer to: "+ + "\"https://docs.yugabyte.com/stable/yugabyte-platform/"+ + "create-deployments/create-universe-multi-zone-kubernetes/#configure-helm-overrides\"") + createUniverseCmd.Flags().StringArray("kubernetes-az-overrides-file-path", []string{}, + "[Optional] Helm Overrides file paths for the availabilty zone, supported for Kubernetes."+ + " Provide file paths for overrides of each Availabilty zone as a separate flag."+ + " For examples on availabilty zone overrides file contents, please refer to: "+ + "\"https://docs.yugabyte.com/stable/yugabyte-platform/"+ + "create-deployments/create-universe-multi-zone-kubernetes/#configure-helm-overrides\"") // Inputs for communication ports diff --git a/managed/yba-cli/cmd/util/util.go b/managed/yba-cli/cmd/util/util.go index 95b760a02dac..9697e41af675 100644 --- a/managed/yba-cli/cmd/util/util.go +++ b/managed/yba-cli/cmd/util/util.go @@ -10,6 +10,7 @@ import ( "fmt" "io" "net/http" + "os" "reflect" "regexp" "strconv" @@ -17,6 +18,8 @@ import ( "github.com/AlecAivazis/survey/v2" "github.com/sirupsen/logrus" + "github.com/yugabyte/yugabyte-db/managed/yba-cli/internal/formatter" + "gopkg.in/yaml.v2" ) // StringSlice accepts array of interface and returns a pointer to slice of string @@ -297,3 +300,32 @@ func IsYBVersion(v string) (bool, error) { } return true, nil } + +// YAMLtoString reads yaml file and converts the data into a string +func YAMLtoString(filePath string) string { + logrus.Debug("YAML File Path: ", filePath) + yamlContent, err := os.ReadFile(filePath) + if err != nil { + logrus.Fatalf( + formatter.Colorize("Error reading YAML file: "+err.Error()+"\n", + formatter.RedColor)) + } + var data map[string]interface{} + + // Unmarshal the YAML content into the map + err = yaml.Unmarshal(yamlContent, &data) + if err != nil { + logrus.Fatalf( + formatter.Colorize("Error unmarshalling YAML file: "+err.Error()+"\n", + formatter.RedColor)) + } + + contentBytes, err := yaml.Marshal(data) + if err != nil { + logrus.Fatalf( + formatter.Colorize("Error marshalling YAML file: "+err.Error()+"\n", + formatter.RedColor)) + } + return string(contentBytes) + +} diff --git a/managed/yba-cli/internal/formatter/kubernetes/kubernetes.go b/managed/yba-cli/internal/formatter/kubernetes/kubernetes.go index fdb6b7153868..d5f25e3c9b0b 100644 --- a/managed/yba-cli/internal/formatter/kubernetes/kubernetes.go +++ b/managed/yba-cli/internal/formatter/kubernetes/kubernetes.go @@ -29,7 +29,9 @@ const ( // Region4 provides header for K8S Region Cloud Info Region4 = "table {{.KubernetesImageRegistry}}\t{{.KubernetesProvider}}\t{{.KubernetesPullSecret}}" // Region5 provides header for K8S Region Cloud Info - Region5 = "table {{.KubernetesPullSecretName}}\t{{.KubernetesStorageClass}}\t{{.Overrides}}" + Region5 = "table {{.KubernetesPullSecretName}}\t{{.KubernetesStorageClass}}" + // Region6 provides header for K8S Region Cloud Info + Region6 = "table {{.Overrides}}" certManagerClusterIssuerHeader = "Certificate Manager Cluster Issuer" certManagerIssuerHeader = "Certificate Manager Issuer" diff --git a/managed/yba-cli/internal/formatter/provider/region.go b/managed/yba-cli/internal/formatter/provider/region.go index 1362278cb69a..0e4b4504adce 100644 --- a/managed/yba-cli/internal/formatter/provider/region.go +++ b/managed/yba-cli/internal/formatter/provider/region.go @@ -196,6 +196,17 @@ func (r *RegionContext) Write(providerCode string, index int) error { return err } r.PostFormat(tmpl, kubernetes.NewRegionContext()) + + tmpl, err = r.startSubsection(kubernetes.Region6) + if err != nil { + logrus.Errorf("%s", err.Error()) + return err + } + if err := r.ContextFormat(tmpl, rc.KubeRegion); err != nil { + logrus.Errorf("%s", err.Error()) + return err + } + r.PostFormat(tmpl, kubernetes.NewRegionContext()) } // Zones Subsection diff --git a/managed/yba-cli/internal/formatter/provider/zone.go b/managed/yba-cli/internal/formatter/provider/zone.go index 42c29c782185..022bd2a1322a 100644 --- a/managed/yba-cli/internal/formatter/provider/zone.go +++ b/managed/yba-cli/internal/formatter/provider/zone.go @@ -139,6 +139,17 @@ func (z *ZoneContext) Write(providerCode string, index int) error { } z.PostFormat(tmpl, kubernetes.NewZoneContext()) + tmpl, err = z.startSubsection(kubernetes.Region6) + if err != nil { + logrus.Errorf("%s", err.Error()) + return err + } + if err := z.ContextFormat(tmpl, zc.KubeZone); err != nil { + logrus.Errorf("%s", err.Error()) + return err + } + z.PostFormat(tmpl, kubernetes.NewZoneContext()) + } z.Output.Write([]byte("\n")) return nil