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 }