github.com/oam-dev/kubevela@v1.9.11/references/cli/init.go (about) 1 /* 2 Copyright 2021 The KubeVela Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package cli 18 19 import ( 20 "context" 21 "encoding/json" 22 "fmt" 23 "os" 24 "strconv" 25 "time" 26 27 "cuelang.org/go/cue" 28 "github.com/AlecAivazis/survey/v2" 29 "github.com/fatih/color" 30 "github.com/spf13/cobra" 31 "github.com/spf13/pflag" 32 v1 "k8s.io/api/core/v1" 33 apierrors "k8s.io/apimachinery/pkg/api/errors" 34 "sigs.k8s.io/controller-runtime/pkg/client" 35 "sigs.k8s.io/yaml" 36 37 "github.com/oam-dev/kubevela/apis/types" 38 common2 "github.com/oam-dev/kubevela/pkg/utils/common" 39 cmdutil "github.com/oam-dev/kubevela/pkg/utils/util" 40 "github.com/oam-dev/kubevela/references/appfile" 41 "github.com/oam-dev/kubevela/references/appfile/api" 42 "github.com/oam-dev/kubevela/references/common" 43 "github.com/oam-dev/kubevela/references/docgen" 44 ) 45 46 type appInitOptions struct { 47 client client.Client 48 cmdutil.IOStreams 49 Namespace string 50 c common2.Args 51 52 app *api.Application 53 appName string 54 workloadName string 55 workloadType string 56 renderOnly bool 57 } 58 59 // NewInitCommand creates `init` command 60 func NewInitCommand(c common2.Args, order string, ioStreams cmdutil.IOStreams) *cobra.Command { 61 o := &appInitOptions{IOStreams: ioStreams, c: c} 62 cmd := &cobra.Command{ 63 Use: "init", 64 DisableFlagsInUseLine: true, 65 Short: "Create scaffold for an application.", 66 Long: "Create scaffold for vela application.", 67 Example: "vela init", 68 RunE: func(cmd *cobra.Command, args []string) error { 69 var err error 70 o.Namespace, err = GetFlagNamespaceOrEnv(cmd, c) 71 if err != nil { 72 return err 73 } 74 75 newClient, err := c.GetClient() 76 if err != nil { 77 return err 78 } 79 o.client = newClient 80 o.IOStreams.Info("Welcome to use KubeVela CLI! Please describe your application.") 81 o.IOStreams.Info() 82 if err = o.CheckEnv(); err != nil { 83 return err 84 } 85 if err = o.Naming(); err != nil { 86 return err 87 } 88 if err = o.Workload(); err != nil { 89 return err 90 } 91 92 if err := appfile.Validate(o.app); err != nil { 93 return err 94 } 95 96 b, err := yaml.Marshal(o.app.AppFile) 97 if err != nil { 98 return err 99 } 100 err = os.WriteFile("./vela.yaml", b, 0600) 101 if err != nil { 102 return err 103 } 104 o.IOStreams.Info("\nDeployment config is rendered and written to " + color.New(color.FgCyan).Sprint("vela.yaml")) 105 106 if o.renderOnly { 107 return nil 108 } 109 110 ctx := context.Background() 111 err = appfile.BuildRun(ctx, o.app, o.client, o.Namespace, o.IOStreams) 112 if err != nil { 113 return err 114 } 115 deployStatus, err := printTrackingDeployStatus(c, o.IOStreams, o.appName, o.Namespace, 300*time.Second) 116 if err != nil { 117 return err 118 } 119 if deployStatus != appDeployedHealthy { 120 return nil 121 } 122 return printAppStatus(context.Background(), newClient, ioStreams, o.appName, o.Namespace, cmd, c, false) 123 }, 124 Annotations: map[string]string{ 125 types.TagCommandOrder: order, 126 types.TagCommandType: types.TypeStart, 127 }, 128 } 129 cmd.Flags().BoolVar(&o.renderOnly, "render-only", false, "Rendering vela.yaml in current dir and do not deploy") 130 addNamespaceAndEnvArg(cmd) 131 cmd.SetOut(ioStreams.Out) 132 return cmd 133 } 134 135 // Naming asks user to input app name 136 func (o *appInitOptions) Naming() error { 137 prompt := &survey.Input{ 138 Message: "What would you like to name your application (required): ", 139 } 140 err := survey.AskOne(prompt, &o.appName, survey.WithValidator(survey.Required)) 141 if err != nil { 142 return fmt.Errorf("read app name err %w", err) 143 } 144 return nil 145 } 146 147 // CheckEnv checks environment, e.g., domain and email. 148 func (o *appInitOptions) CheckEnv() error { 149 if o.Namespace == "" { 150 o.Namespace = "default" 151 } 152 var ns v1.Namespace 153 ctx := context.Background() 154 err := o.client.Get(ctx, client.ObjectKey{ 155 Name: o.Namespace, 156 }, &ns) 157 if apierrors.IsNotFound(err) { 158 ns.Name = o.Namespace 159 err = o.client.Create(ctx, &ns) 160 if err != nil { 161 return err 162 } 163 } else if err != nil { 164 return err 165 } 166 return nil 167 } 168 169 func formatAndGetUsage(p *types.Parameter) string { 170 usage := p.Usage 171 if usage == "" { 172 usage = "what would you configure for parameter '" + color.New(color.FgCyan).Sprintf("%s", p.Name) + "'" 173 } 174 if p.Required { 175 usage += " (required): " 176 } else { 177 defaultValue := fmt.Sprintf("%v", p.Default) 178 if defaultValue != "" { 179 usage += fmt.Sprintf(" (optional, default is %s): ", defaultValue) 180 } else { 181 usage += " (optional): " 182 } 183 if val, ok := p.Default.(json.Number); ok { 184 if p.Type == cue.NumberKind || p.Type == cue.FloatKind { 185 p.Default, _ = val.Float64() 186 } 187 if p.Type == cue.IntKind { 188 p.Default, _ = val.Int64() 189 } 190 } 191 } 192 return usage 193 } 194 195 // Workload asks user to choose workload type from installed workloads 196 func (o *appInitOptions) Workload() error { 197 workloads, err := docgen.LoadInstalledCapabilityWithType(o.Namespace, o.c, types.TypeComponentDefinition) 198 if err != nil { 199 return err 200 } 201 var workloadList []string 202 for _, w := range workloads { 203 workloadList = append(workloadList, w.Name) 204 } 205 prompt := &survey.Select{ 206 Message: "Choose the workload type for your application (required, e.g., webservice): ", 207 Options: workloadList, 208 } 209 err = survey.AskOne(prompt, &o.workloadType, survey.WithValidator(survey.Required)) 210 if err != nil { 211 return fmt.Errorf("read workload type err %w", err) 212 } 213 workload, err := GetCapabilityByName(o.workloadType, workloads) 214 if err != nil { 215 return err 216 } 217 namePrompt := &survey.Input{ 218 Message: fmt.Sprintf("What would you like to name this %s (required): ", o.workloadType), 219 } 220 err = survey.AskOne(namePrompt, &o.workloadName, survey.WithValidator(survey.Required)) 221 if err != nil { 222 return fmt.Errorf("read workload name err %w", err) 223 } 224 fs := pflag.NewFlagSet("workload", pflag.ContinueOnError) 225 for _, pp := range workload.Parameters { 226 p := pp 227 if p.Name == "name" { 228 continue 229 } 230 if p.Ignore { 231 continue 232 } 233 usage := formatAndGetUsage(&p) 234 // nolint:exhaustive 235 switch p.Type { 236 case cue.StringKind: 237 var data string 238 prompt := &survey.Input{ 239 Message: usage, 240 } 241 var opts []survey.AskOpt 242 if p.Required { 243 opts = append(opts, survey.WithValidator(survey.Required)) 244 } 245 err = survey.AskOne(prompt, &data, opts...) 246 if err != nil { 247 return fmt.Errorf("read param %s err %w", p.Name, err) 248 } 249 fs.String(p.Name, data, p.Usage) 250 case cue.NumberKind, cue.FloatKind: 251 var data string 252 prompt := &survey.Input{ 253 Message: usage, 254 } 255 var opts []survey.AskOpt 256 if p.Required { 257 opts = append(opts, survey.WithValidator(survey.Required)) 258 } 259 opts = append(opts, survey.WithValidator(func(ans interface{}) error { 260 data := ans.(string) 261 if data == "" && !p.Required { 262 return nil 263 } 264 _, err := strconv.ParseFloat(data, 64) 265 return err 266 })) 267 err = survey.AskOne(prompt, &data, opts...) 268 if err != nil { 269 return fmt.Errorf("read param %s err %w", p.Name, err) 270 } 271 if data == "" { 272 fs.Float64(p.Name, p.Default.(float64), p.Usage) 273 } else { 274 val, _ := strconv.ParseFloat(data, 64) 275 fs.Float64(p.Name, val, p.Usage) 276 } 277 case cue.IntKind: 278 var data string 279 prompt := &survey.Input{ 280 Message: usage, 281 } 282 var opts []survey.AskOpt 283 if p.Required { 284 opts = append(opts, survey.WithValidator(survey.Required)) 285 } 286 opts = append(opts, survey.WithValidator(func(ans interface{}) error { 287 data := ans.(string) 288 if data == "" && !p.Required { 289 return nil 290 } 291 _, err := strconv.ParseInt(data, 10, 64) 292 return err 293 })) 294 err = survey.AskOne(prompt, &data, opts...) 295 if err != nil { 296 return fmt.Errorf("read param %s err %w", p.Name, err) 297 } 298 if data == "" { 299 fs.Int64(p.Name, p.Default.(int64), p.Usage) 300 } else { 301 val, _ := strconv.ParseInt(data, 10, 64) 302 fs.Int64(p.Name, val, p.Usage) 303 } 304 case cue.BoolKind: 305 var data bool 306 prompt := &survey.Confirm{ 307 Message: usage, 308 } 309 if p.Required { 310 err = survey.AskOne(prompt, &data, survey.WithValidator(survey.Required)) 311 } else { 312 err = survey.AskOne(prompt, &data) 313 } 314 if err != nil { 315 return fmt.Errorf("read param %s err %w", p.Name, err) 316 } 317 fs.Bool(p.Name, data, p.Usage) 318 default: 319 // other type not supported 320 } 321 } 322 o.app, err = common.BaseComplete(o.Namespace, o.c, o.workloadName, o.appName, fs, o.workloadType) 323 return err 324 } 325 326 // GetCapabilityByName get eponymous types.Capability from workloads by name 327 func GetCapabilityByName(name string, workloads []types.Capability) (types.Capability, error) { 328 for _, v := range workloads { 329 if v.Name == name { 330 return v, nil 331 } 332 } 333 return types.Capability{}, fmt.Errorf("%s not found", name) 334 }