github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/cli/cmd/cluster/create_subcmds.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 "context" 24 "fmt" 25 "os" 26 27 "github.com/spf13/cobra" 28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 30 "k8s.io/apimachinery/pkg/runtime/schema" 31 cmdutil "k8s.io/kubectl/pkg/cmd/util" 32 33 "github.com/1aal/kubeblocks/pkg/cli/cluster" 34 "github.com/1aal/kubeblocks/pkg/cli/create" 35 "github.com/1aal/kubeblocks/pkg/cli/edit" 36 "github.com/1aal/kubeblocks/pkg/cli/types" 37 "github.com/1aal/kubeblocks/pkg/cli/util" 38 ) 39 40 type objectInfo struct { 41 gvr schema.GroupVersionResource 42 obj *unstructured.Unstructured 43 } 44 45 type createSubCmdsOptions struct { 46 // clusterType is the type of the cluster to create. 47 clusterType cluster.ClusterType 48 49 // values is used to render the cluster helm chartInfo. 50 values map[string]interface{} 51 52 // chartInfo is the cluster chart information, used to render the command flag 53 // and validate the values. 54 chartInfo *cluster.ChartInfo 55 56 *create.CreateOptions 57 } 58 59 func newSubCmdsOptions(createOptions *create.CreateOptions, t cluster.ClusterType) (*createSubCmdsOptions, error) { 60 var err error 61 o := &createSubCmdsOptions{ 62 CreateOptions: createOptions, 63 clusterType: t, 64 } 65 66 if o.chartInfo, err = cluster.BuildChartInfo(t); err != nil { 67 return nil, err 68 } 69 return o, nil 70 } 71 72 func buildCreateSubCmds(createOptions *create.CreateOptions) []*cobra.Command { 73 var cmds []*cobra.Command 74 75 for _, t := range cluster.SupportedTypes() { 76 o, err := newSubCmdsOptions(createOptions, t) 77 if err != nil { 78 fmt.Fprintf(os.Stdout, "Failed add '%s' to 'create' sub command due to %s\n", t.String(), err.Error()) 79 cluster.ClearCharts(t) 80 continue 81 } 82 83 cmd := &cobra.Command{ 84 Use: t.String() + " NAME", 85 Short: fmt.Sprintf("Create a %s cluster.", t), 86 Example: buildCreateSubCmdsExamples(t), 87 Run: func(cmd *cobra.Command, args []string) { 88 o.Args = args 89 cmdutil.CheckErr(o.CreateOptions.Complete()) 90 cmdutil.CheckErr(o.complete(cmd)) 91 cmdutil.CheckErr(o.validate()) 92 cmdutil.CheckErr(o.run()) 93 }, 94 } 95 96 if o.chartInfo.Alias != "" { 97 cmd.Aliases = []string{o.chartInfo.Alias} 98 } 99 100 util.CheckErr(addCreateFlags(cmd, o.Factory, o.chartInfo)) 101 cmds = append(cmds, cmd) 102 } 103 return cmds 104 } 105 106 func (o *createSubCmdsOptions) complete(cmd *cobra.Command) error { 107 var err error 108 109 // if name is not specified, generate a random cluster name 110 if o.Name == "" { 111 o.Name, err = generateClusterName(o.Dynamic, o.Namespace) 112 if err != nil { 113 return err 114 } 115 } 116 117 // get values from flags 118 o.values = getValuesFromFlags(cmd.LocalNonPersistentFlags()) 119 120 // get all the rendered objects 121 objs, err := o.getObjectsInfo() 122 if err != nil { 123 return err 124 } 125 126 // find the cluster object 127 clusterObj, err := o.getClusterObj(objs) 128 if err != nil { 129 return err 130 } 131 132 // get clusterDef name 133 spec, ok := clusterObj.Object["spec"].(map[string]interface{}) 134 if !ok { 135 return fmt.Errorf("cannot find spec in cluster object") 136 } 137 clusterDef, ok := spec["clusterDefinitionRef"].(string) 138 if !ok { 139 return fmt.Errorf("cannot find clusterDefinitionRef in cluster spec") 140 } 141 o.chartInfo.ClusterDef = clusterDef 142 143 return nil 144 } 145 146 func (o *createSubCmdsOptions) validate() error { 147 if err := o.validateVersion(); err != nil { 148 return err 149 } 150 return cluster.ValidateValues(o.chartInfo, o.values) 151 } 152 153 func (o *createSubCmdsOptions) run() error { 154 155 objs, err := o.getObjectsInfo() 156 if err != nil { 157 return err 158 } 159 160 getClusterObj := func() (*unstructured.Unstructured, error) { 161 for _, obj := range objs { 162 if obj.gvr == types.ClusterGVR() { 163 return obj.obj, nil 164 } 165 } 166 return nil, fmt.Errorf("failed to find cluster object from manifests rendered from %s chart", o.clusterType) 167 } 168 169 // only edits the cluster object, other dependency objects are created directly 170 if o.EditBeforeCreate { 171 clusterObj, err := getClusterObj() 172 if err != nil { 173 return err 174 } 175 customEdit := edit.NewCustomEditOptions(o.Factory, o.IOStreams, "create") 176 if err = customEdit.Run(clusterObj); err != nil { 177 return err 178 } 179 } 180 181 dryRun, err := o.GetDryRunStrategy() 182 if err != nil { 183 return err 184 } 185 186 // create cluster and dependency resources 187 for _, obj := range objs { 188 isCluster := obj.gvr == types.ClusterGVR() 189 resObj := obj.obj 190 191 if dryRun != create.DryRunClient { 192 createOptions := metav1.CreateOptions{} 193 if dryRun == create.DryRunServer { 194 createOptions.DryRun = []string{metav1.DryRunAll} 195 } 196 197 // create resource 198 resObj, err = o.Dynamic.Resource(obj.gvr).Namespace(o.Namespace).Create(context.TODO(), resObj, createOptions) 199 if err != nil { 200 return err 201 } 202 203 // only output cluster resource 204 if dryRun != create.DryRunServer && isCluster { 205 if o.Quiet { 206 continue 207 } 208 if o.CustomOutPut != nil { 209 o.CustomOutPut(o.CreateOptions) 210 } 211 fmt.Fprintf(o.Out, "%s %s created\n", resObj.GetKind(), resObj.GetName()) 212 continue 213 } 214 } 215 216 if len(objs) > 1 { 217 fmt.Fprintf(o.Out, "---\n") 218 } 219 220 p, err := o.ToPrinter(nil, false) 221 if err != nil { 222 return err 223 } 224 225 if err = p.PrintObj(resObj, o.Out); err != nil { 226 return err 227 } 228 } 229 return nil 230 } 231 232 func (o *createSubCmdsOptions) validateVersion() error { 233 var err error 234 cv, ok := o.values[cluster.VersionSchemaProp.String()].(string) 235 if ok && cv != "" { 236 if err = cluster.ValidateClusterVersion(o.Dynamic, o.chartInfo.ClusterDef, cv); err != nil { 237 return fmt.Errorf("cluster version \"%s\" does not exist, run following command to get the available cluster versions\n\tkbcli cv list --cluster-definition=%s", 238 cv, o.chartInfo.ClusterDef) 239 } 240 return nil 241 } 242 243 cv, err = cluster.GetDefaultVersion(o.Dynamic, o.chartInfo.ClusterDef) 244 if err != nil { 245 return err 246 } 247 // set cluster version 248 o.values[cluster.VersionSchemaProp.String()] = cv 249 250 dryRun, err := o.GetDryRunStrategy() 251 if err != nil { 252 return err 253 } 254 // if dryRun is set, run in quiet mode, avoid to output yaml file with the info 255 if dryRun != create.DryRunNone { 256 return nil 257 } 258 259 fmt.Fprintf(o.Out, "Info: --version is not specified, %s is applied by default.\n", cv) 260 return nil 261 } 262 263 func (o *createSubCmdsOptions) getObjectsInfo() ([]*objectInfo, error) { 264 // move values that belong to sub chart to sub map 265 values := buildHelmValues(o.chartInfo, o.values) 266 267 // get Kubernetes version 268 kubeVersion, err := util.GetK8sVersion(o.Client.Discovery()) 269 if err != nil || kubeVersion == "" { 270 return nil, fmt.Errorf("failed to get Kubernetes version %v", err) 271 } 272 273 // get cluster manifests 274 manifests, err := cluster.GetManifests(o.chartInfo.Chart, o.Namespace, o.Name, kubeVersion, values) 275 if err != nil { 276 return nil, err 277 } 278 279 // get objects to be created from manifests 280 return getObjectsInfo(o.Factory, manifests) 281 } 282 283 func (o *createSubCmdsOptions) getClusterObj(objs []*objectInfo) (*unstructured.Unstructured, error) { 284 for _, obj := range objs { 285 if obj.gvr == types.ClusterGVR() { 286 return obj.obj, nil 287 } 288 } 289 return nil, fmt.Errorf("failed to find cluster object from manifests rendered from %s chart", o.clusterType) 290 }