github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/cli/cmd/cluster/create_util.go (about)

     1  /*
     2  Copyright (C) 2022-2023 ApeCloud Co., Ltd
     3  
     4  This file is part of KubeBlocks project
     5  
     6  This program is free software: you can redistribute it and/or modify
     7  it under the terms of the GNU Affero General Public License as published by
     8  the Free Software Foundation, either version 3 of the License, or
     9  (at your option) any later version.
    10  
    11  This program is distributed in the hope that it will be useful
    12  but WITHOUT ANY WARRANTY; without even the implied warranty of
    13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    14  GNU Affero General Public License for more details.
    15  
    16  You should have received a copy of the GNU Affero General Public License
    17  along with this program.  If not, see <http://www.gnu.org/licenses/>.
    18  */
    19  
    20  package cluster
    21  
    22  import (
    23  	"fmt"
    24  	"strings"
    25  
    26  	"github.com/spf13/cobra"
    27  	flag "github.com/spf13/pflag"
    28  	"github.com/stoewer/go-strcase"
    29  	"golang.org/x/exp/maps"
    30  	"golang.org/x/exp/slices"
    31  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    32  	cmdutil "k8s.io/kubectl/pkg/cmd/util"
    33  	utilcomp "k8s.io/kubectl/pkg/util/completion"
    34  	"k8s.io/kubectl/pkg/util/templates"
    35  	"sigs.k8s.io/yaml"
    36  
    37  	"github.com/1aal/kubeblocks/pkg/cli/cluster"
    38  	"github.com/1aal/kubeblocks/pkg/cli/types"
    39  	"github.com/1aal/kubeblocks/pkg/cli/util"
    40  	"github.com/1aal/kubeblocks/pkg/cli/util/flags"
    41  	"github.com/1aal/kubeblocks/pkg/constant"
    42  )
    43  
    44  var (
    45  	resetValFlagNames = []string{
    46  		cluster.VersionSchemaProp.String(),
    47  	}
    48  )
    49  
    50  // addCreateFlags adds the flags for creating a cluster, these flags are built by the cluster schema.
    51  func addCreateFlags(cmd *cobra.Command, f cmdutil.Factory, c *cluster.ChartInfo) error {
    52  	if c == nil {
    53  		return nil
    54  	}
    55  
    56  	// add the flags for the cluster schema
    57  	if err := flags.BuildFlagsBySchema(cmd, c.Schema); err != nil {
    58  		return err
    59  	}
    60  
    61  	// add the flags for sub helm chart
    62  	if err := flags.BuildFlagsBySchema(cmd, c.SubSchema); err != nil {
    63  		return err
    64  	}
    65  
    66  	// reset some flags default value, such as version, a suitable version will be chosen
    67  	// by cli if user doesn't specify the version
    68  	resetFlagsValue(cmd.Flags())
    69  
    70  	// register completion function for some generic flag
    71  	registerFlagCompFunc(cmd, f, c)
    72  	return nil
    73  }
    74  
    75  // getValuesFromFlags gets the values from the flags, these values are used to render a cluster.
    76  func getValuesFromFlags(fs *flag.FlagSet) map[string]interface{} {
    77  	values := make(map[string]interface{}, 0)
    78  	fs.VisitAll(func(f *flag.Flag) {
    79  		if f.Name == "help" {
    80  			return
    81  		}
    82  		var val interface{}
    83  		switch f.Value.Type() {
    84  		case flags.CobraBool:
    85  			val, _ = fs.GetBool(f.Name)
    86  		case flags.CobraInt:
    87  			val, _ = fs.GetInt(f.Name)
    88  		case flags.CobraFloat64:
    89  			val, _ = fs.GetFloat64(f.Name)
    90  		case flags.CobraStringArray:
    91  			val, _ = fs.GetStringArray(f.Name)
    92  		case flags.CobraIntSlice:
    93  			val, _ = fs.GetIntSlice(f.Name)
    94  		case flags.CobraFloat64Slice:
    95  			val, _ = fs.GetFloat64Slice(f.Name)
    96  		case flags.CobraBoolSlice:
    97  			val, _ = fs.GetBoolSlice(f.Name)
    98  		default:
    99  			val, _ = fs.GetString(f.Name)
   100  		}
   101  		values[strcase.LowerCamelCase(f.Name)] = val
   102  	})
   103  	return values
   104  }
   105  
   106  // resetFlagsValue reset the default value of some flags
   107  func resetFlagsValue(fs *flag.FlagSet) {
   108  	fs.VisitAll(func(f *flag.Flag) {
   109  		for _, n := range resetValFlagNames {
   110  			if n == f.Name {
   111  				f.DefValue = ""
   112  				_ = f.Value.Set("")
   113  			}
   114  		}
   115  	})
   116  }
   117  
   118  func registerFlagCompFunc(cmd *cobra.Command, f cmdutil.Factory, c *cluster.ChartInfo) {
   119  	_ = cmd.RegisterFlagCompletionFunc(string(cluster.VersionSchemaProp),
   120  		func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
   121  			var versions []string
   122  			if c != nil && c.ClusterDef != "" {
   123  				label := fmt.Sprintf("%s=%s", constant.ClusterDefLabelKey, c.ClusterDef)
   124  				versions = util.CompGetResourceWithLabels(f, cmd, util.GVRToString(types.ClusterVersionGVR()), []string{label}, toComplete)
   125  			} else {
   126  				versions = utilcomp.CompGetResource(f, util.GVRToString(types.ClusterVersionGVR()), toComplete)
   127  			}
   128  			return versions, cobra.ShellCompDirectiveNoFileComp
   129  		})
   130  }
   131  
   132  // buildCreateSubCmdsExamples builds the creation examples for the specified clusterType type.
   133  func buildCreateSubCmdsExamples(t cluster.ClusterType) string {
   134  	exampleTpl := `
   135  	# Create a cluster with the default values
   136  	kbcli cluster create {{ .ClusterType }}
   137  
   138  	# Create a cluster with the specified cpu, memory and storage
   139  	kbcli cluster create {{ .ClusterType }} --cpu 1 --memory 2 --storage 10
   140  `
   141  
   142  	var builder strings.Builder
   143  	_ = util.PrintGoTemplate(&builder, exampleTpl, map[string]interface{}{
   144  		"ClusterType": t.String(),
   145  	})
   146  	return templates.Examples(builder.String())
   147  }
   148  
   149  // getObjectsInfo gets the objects info from the manifests.
   150  func getObjectsInfo(f cmdutil.Factory, manifests map[string]string) ([]*objectInfo, error) {
   151  	mapper, err := f.ToRESTMapper()
   152  	if err != nil {
   153  		return nil, err
   154  	}
   155  
   156  	var objects []*objectInfo
   157  	for _, manifest := range manifests {
   158  		objInfo := &objectInfo{}
   159  
   160  		// convert yaml to json
   161  		jsonData, err := yaml.YAMLToJSON([]byte(manifest))
   162  		if err != nil {
   163  			return nil, err
   164  		}
   165  
   166  		// get resource gvk
   167  		obj, gvk, err := unstructured.UnstructuredJSONScheme.Decode(jsonData, nil, nil)
   168  		if err != nil {
   169  			return nil, err
   170  		}
   171  
   172  		// convert gvk to gvr
   173  		m, err := mapper.RESTMapping(gvk.GroupKind(), gvk.Version)
   174  		if err != nil {
   175  			return nil, err
   176  		}
   177  
   178  		objInfo.obj = obj.(*unstructured.Unstructured)
   179  		objInfo.gvr = m.Resource
   180  		objects = append(objects, objInfo)
   181  	}
   182  	return objects, nil
   183  }
   184  
   185  // buildHelmValues builds the helm values from the cluster schema and the values from the flags.
   186  // For helm, the sub chart values should be in the sub map of the values.
   187  func buildHelmValues(c *cluster.ChartInfo, values map[string]interface{}) map[string]interface{} {
   188  	if c.SubSchema == nil {
   189  		return values
   190  	}
   191  	// todo: for key like `etcd.cluster` should adjust it to a map like
   192  	subSchemaKeys := maps.Keys(c.SubSchema.Properties)
   193  	newValues := map[string]interface{}{
   194  		c.SubChartName: map[string]interface{}{},
   195  	}
   196  	var build func(key []string, v interface{}, values *map[string]interface{})
   197  	build = func(key []string, v interface{}, values *map[string]interface{}) {
   198  		if len(key) == 1 {
   199  			(*values)[key[0]] = v
   200  			return
   201  		}
   202  		if (*values)[key[0]] == nil {
   203  			(*values)[key[0]] = make(map[string]interface{})
   204  		}
   205  		nextMap := (*values)[key[0]].(map[string]interface{})
   206  		build(key[1:], v, &nextMap)
   207  	}
   208  
   209  	for k, v := range values {
   210  		if slices.Contains(subSchemaKeys, k) {
   211  			newValues[c.SubChartName].(map[string]interface{})[k] = v
   212  		} else {
   213  			// todo: fix "."
   214  			build(strings.Split(k, "."), v, &newValues)
   215  		}
   216  	}
   217  
   218  	return newValues
   219  }