github.com/oam-dev/kubevela@v1.9.11/references/cli/config.go (about) 1 /* 2 Copyright 2022 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 "bufio" 21 "bytes" 22 "context" 23 "errors" 24 "fmt" 25 "strings" 26 27 "github.com/spf13/cobra" 28 "helm.sh/helm/v3/pkg/strvals" 29 "k8s.io/kubectl/pkg/util/i18n" 30 "k8s.io/kubectl/pkg/util/templates" 31 "sigs.k8s.io/yaml" 32 33 workflowv1alpha1 "github.com/kubevela/workflow/api/v1alpha1" 34 35 "github.com/oam-dev/kubevela/apis/types" 36 velacmd "github.com/oam-dev/kubevela/pkg/cmd" 37 "github.com/oam-dev/kubevela/pkg/config" 38 pkgUtils "github.com/oam-dev/kubevela/pkg/utils" 39 "github.com/oam-dev/kubevela/pkg/utils/util" 40 "github.com/oam-dev/kubevela/references/docgen" 41 ) 42 43 // ConfigCommandGroup commands for the config 44 func ConfigCommandGroup(f velacmd.Factory, order string, streams util.IOStreams) *cobra.Command { 45 cmd := &cobra.Command{ 46 Use: "config", 47 Short: i18n.T("Manage the configs."), 48 Long: i18n.T("Manage the configs, such as the terraform provider, image registry, helm repository, etc."), 49 Annotations: map[string]string{ 50 types.TagCommandType: types.TypePlatform, 51 types.TagCommandOrder: order, 52 }, 53 } 54 cmd.AddCommand(NewListConfigCommand(f, streams)) 55 cmd.AddCommand(NewCreateConfigCommand(f, streams)) 56 cmd.AddCommand(NewDistributeConfigCommand(f, streams)) 57 cmd.AddCommand(NewDeleteConfigCommand(f, streams)) 58 return cmd 59 } 60 61 // TemplateCommandGroup commands for the template of the config 62 func TemplateCommandGroup(f velacmd.Factory, order string, streams util.IOStreams) *cobra.Command { 63 cmd := &cobra.Command{ 64 Use: "config-template", 65 Aliases: []string{"ct"}, 66 Short: i18n.T("Manage the template of config."), 67 Annotations: map[string]string{ 68 types.TagCommandType: types.TypePlatform, 69 types.TagCommandOrder: order, 70 }, 71 } 72 cmd.AddCommand(NewTemplateApplyCommand(f, streams)) 73 cmd.AddCommand(NewTemplateListCommand(f, streams)) 74 cmd.AddCommand(NewTemplateDeleteCommand(f, streams)) 75 cmd.AddCommand(NewTemplateShowCommand(f, streams)) 76 return cmd 77 } 78 79 // TemplateApplyCommandOptions the options of the command that apply the config template. 80 type TemplateApplyCommandOptions struct { 81 File string 82 Namespace string 83 Name string 84 } 85 86 // TemplateCommandOptions the options of the command that delete or show the config template. 87 type TemplateCommandOptions struct { 88 Namespace string 89 Name string 90 } 91 92 // TemplateListCommandOptions the options of the command that list the config templates. 93 type TemplateListCommandOptions struct { 94 Namespace string 95 AllNamespace bool 96 } 97 98 // NewTemplateApplyCommand command for creating and updating the config template 99 func NewTemplateApplyCommand(f velacmd.Factory, streams util.IOStreams) *cobra.Command { 100 var options TemplateApplyCommandOptions 101 cmd := &cobra.Command{ 102 Use: "apply", 103 Short: i18n.T("Apply a config template."), 104 Annotations: map[string]string{ 105 types.TagCommandType: types.TypeExtension, 106 }, 107 Args: cobra.ExactArgs(0), 108 RunE: func(cmd *cobra.Command, args []string) error { 109 body, err := pkgUtils.ReadRemoteOrLocalPath(options.File, false) 110 if err != nil { 111 return err 112 } 113 inf := config.NewConfigFactory(f.Client()) 114 template, err := inf.ParseTemplate(options.Name, body) 115 if err != nil { 116 return err 117 } 118 if err := inf.CreateOrUpdateConfigTemplate(context.Background(), options.Namespace, template); err != nil { 119 return err 120 } 121 streams.Infof("the config template %s applied successfully\n", template.Name) 122 return nil 123 }, 124 } 125 cmd.Flags().StringVarP(&options.File, "file", "f", "", "specify the template file name") 126 cmd.Flags().StringVarP(&options.Name, "name", "", "", "specify the config template name") 127 cmd.Flags().StringVarP(&options.Namespace, "namespace", "n", types.DefaultKubeVelaNS, "specify the namespace of the template") 128 return cmd 129 } 130 131 // NewTemplateListCommand command for listing the config templates 132 func NewTemplateListCommand(f velacmd.Factory, streams util.IOStreams) *cobra.Command { 133 var options TemplateListCommandOptions 134 cmd := &cobra.Command{ 135 Use: "list", 136 Short: i18n.T("List the config templates."), 137 Example: "vela config template list [-A]", 138 Annotations: map[string]string{ 139 types.TagCommandType: types.TypeExtension, 140 }, 141 Args: cobra.ExactArgs(0), 142 RunE: func(cmd *cobra.Command, args []string) error { 143 inf := config.NewConfigFactory(f.Client()) 144 if options.AllNamespace { 145 options.Namespace = "" 146 } 147 templateList, err := inf.ListTemplates(context.Background(), options.Namespace, "") 148 if err != nil { 149 return err 150 } 151 table := newUITable() 152 header := []interface{}{"NAME", "ALIAS", "SCOPE", "SENSITIVE", "CREATED-TIME"} 153 if options.AllNamespace { 154 header = append([]interface{}{"NAMESPACE"}, header...) 155 } 156 table.AddRow(header...) 157 for _, t := range templateList { 158 row := []interface{}{t.Name, t.Alias, t.Scope, t.Sensitive, t.CreateTime} 159 if options.AllNamespace { 160 row = append([]interface{}{t.Namespace}, row...) 161 } 162 table.AddRow(row...) 163 } 164 if _, err := streams.Out.Write(table.Bytes()); err != nil { 165 return err 166 } 167 if _, err := streams.Out.Write([]byte("\n")); err != nil { 168 return err 169 } 170 return nil 171 }, 172 } 173 cmd.Flags().StringVarP(&options.Namespace, "namespace", "n", types.DefaultKubeVelaNS, "specify the namespace of the template") 174 cmd.Flags().BoolVarP(&options.AllNamespace, "all-namespaces", "A", false, "If true, check the specified action in all namespaces.") 175 return cmd 176 } 177 178 // NewTemplateShowCommand command for show the properties document 179 func NewTemplateShowCommand(f velacmd.Factory, streams util.IOStreams) *cobra.Command { 180 var options TemplateCommandOptions 181 cmd := &cobra.Command{ 182 Use: "show", 183 Short: i18n.T("Show the documents of the template properties"), 184 Example: "vela config-template show <name> [-n]", 185 Annotations: map[string]string{ 186 types.TagCommandType: types.TypeExtension, 187 }, 188 Args: cobra.ExactArgs(1), 189 RunE: func(cmd *cobra.Command, args []string) error { 190 options.Name = args[0] 191 if options.Name == "" { 192 return fmt.Errorf("can not show the properties without the template") 193 } 194 inf := config.NewConfigFactory(f.Client()) 195 template, err := inf.LoadTemplate(context.Background(), options.Name, options.Namespace) 196 if err != nil { 197 return err 198 } 199 doc, err := docgen.GenerateConsoleDocument(template.Schema.Title, template.Schema) 200 if err != nil { 201 return err 202 } 203 if _, err := streams.Out.Write([]byte(doc)); err != nil { 204 return err 205 } 206 return nil 207 }, 208 } 209 cmd.Flags().StringVarP(&options.Namespace, "namespace", "n", types.DefaultKubeVelaNS, "specify the namespace of the template") 210 return cmd 211 } 212 213 // NewTemplateDeleteCommand command for deleting the config template 214 func NewTemplateDeleteCommand(f velacmd.Factory, streams util.IOStreams) *cobra.Command { 215 var options TemplateCommandOptions 216 cmd := &cobra.Command{ 217 Use: "delete", 218 Short: i18n.T("Delete a config template."), 219 Example: fmt.Sprintf("vela config template delete <name> [-n %s]", types.DefaultKubeVelaNS), 220 Annotations: map[string]string{ 221 types.TagCommandType: types.TypeCD, 222 }, 223 Args: cobra.ExactArgs(1), 224 RunE: func(cmd *cobra.Command, args []string) error { 225 if len(args) == 0 { 226 return fmt.Errorf("please must provides the template name") 227 } 228 options.Name = args[0] 229 userInput := &UserInput{ 230 Writer: streams.Out, 231 Reader: bufio.NewReader(streams.In), 232 } 233 if !assumeYes { 234 userConfirmation := userInput.AskBool("Do you want to delete this template", &UserInputOptions{assumeYes}) 235 if !userConfirmation { 236 return fmt.Errorf("stopping deleting") 237 } 238 } 239 inf := config.NewConfigFactory(f.Client()) 240 if err := inf.DeleteTemplate(context.Background(), options.Namespace, options.Name); err != nil { 241 return err 242 } 243 streams.Infof("the config template %s deleted successfully\n", options.Name) 244 return nil 245 }, 246 } 247 cmd.Flags().StringVarP(&options.Namespace, "namespace", "n", types.DefaultKubeVelaNS, "specify the namespace of the template") 248 return cmd 249 } 250 251 // DistributeConfigCommandOptions the options of the command that distribute the config. 252 type DistributeConfigCommandOptions struct { 253 Targets []string 254 Config string 255 Namespace string 256 Recalled bool 257 } 258 259 // CreateConfigCommandOptions the options of the command that create the config. 260 type CreateConfigCommandOptions struct { 261 Template string 262 Namespace string 263 Name string 264 File string 265 Properties map[string]interface{} 266 DryRun bool 267 Targets []string 268 Description string 269 Alias string 270 } 271 272 // Validate validate the options 273 func (i CreateConfigCommandOptions) Validate() error { 274 if i.Name == "" { 275 return fmt.Errorf("the config name must be specified") 276 } 277 if len(i.Targets) > 0 && i.DryRun { 278 return fmt.Errorf("can not set the distribution in dry-run mode") 279 } 280 return nil 281 } 282 283 func (i *CreateConfigCommandOptions) parseProperties(args []string) error { 284 if i.File != "" { 285 body, err := pkgUtils.ReadRemoteOrLocalPath(i.File, false) 286 if err != nil { 287 return err 288 } 289 var properties = map[string]interface{}{} 290 if err := yaml.Unmarshal(body, &properties); err != nil { 291 return err 292 } 293 i.Properties = properties 294 return nil 295 } 296 res := map[string]interface{}{} 297 for _, arg := range args { 298 if err := strvals.ParseInto(arg, res); err != nil { 299 return err 300 } 301 } 302 i.Properties = res 303 return nil 304 } 305 306 // ConfigListCommandOptions the options of the command that list configs. 307 type ConfigListCommandOptions struct { 308 Namespace string 309 Template string 310 AllNamespace bool 311 } 312 313 // NewListConfigCommand command for listing the config secrets 314 func NewListConfigCommand(f velacmd.Factory, streams util.IOStreams) *cobra.Command { 315 var options ConfigListCommandOptions 316 cmd := &cobra.Command{ 317 Use: "list", 318 Short: i18n.T("List the configs."), 319 Annotations: map[string]string{ 320 types.TagCommandType: types.TypeCD, 321 }, 322 Args: cobra.ExactArgs(0), 323 RunE: func(cmd *cobra.Command, args []string) error { 324 name := options.Template 325 if strings.Contains(options.Template, "/") { 326 namespacedName := strings.SplitN(options.Template, "/", 2) 327 name = namespacedName[1] 328 } 329 if options.AllNamespace { 330 options.Namespace = "" 331 } 332 inf := config.NewConfigFactory(f.Client()) 333 configs, err := inf.ListConfigs(context.Background(), options.Namespace, name, "", true) 334 if err != nil { 335 return err 336 } 337 table := newUITable() 338 header := []interface{}{"NAME", "ALIAS", "DISTRIBUTION", "TEMPLATE", "CREATED-TIME", "DESCRIPTION"} 339 if options.AllNamespace { 340 header = append([]interface{}{"NAMESPACE"}, header...) 341 } 342 table.AddRow(header...) 343 for _, t := range configs { 344 var targetShow = "" 345 for _, target := range t.Targets { 346 if targetShow != "" { 347 targetShow += " " 348 } 349 switch target.Status { 350 case string(workflowv1alpha1.WorkflowStepPhaseSucceeded): 351 targetShow += green.Sprintf("%s/%s", target.ClusterName, target.Namespace) 352 case string(workflowv1alpha1.WorkflowStepPhaseFailed): 353 targetShow += red.Sprintf("%s/%s", target.ClusterName, target.Namespace) 354 default: 355 targetShow += yellow.Sprintf("%s/%s", target.ClusterName, target.Namespace) 356 } 357 } 358 row := []interface{}{t.Name, t.Alias, targetShow, fmt.Sprintf("%s/%s", t.Template.Namespace, t.Template.Name), t.CreateTime, t.Description} 359 if options.AllNamespace { 360 row = append([]interface{}{t.Namespace}, row...) 361 } 362 table.AddRow(row...) 363 } 364 if _, err := streams.Out.Write(table.Bytes()); err != nil { 365 return err 366 } 367 if _, err := streams.Out.Write([]byte("\n")); err != nil { 368 return err 369 } 370 return nil 371 }, 372 } 373 cmd.Flags().StringVarP(&options.Namespace, "namespace", "n", types.DefaultKubeVelaNS, "specify the namespace of the config") 374 cmd.Flags().StringVarP(&options.Template, "template", "t", "", "specify the template of the config") 375 cmd.Flags().BoolVarP(&options.AllNamespace, "all-namespaces", "A", false, "If true, check the specified action in all namespaces.") 376 return cmd 377 } 378 379 // NewCreateConfigCommand command for creating the config 380 func NewCreateConfigCommand(f velacmd.Factory, streams util.IOStreams) *cobra.Command { 381 var options CreateConfigCommandOptions 382 createConfigExample := templates.Examples(i18n.T(` 383 # Generate a config with the args 384 vela config create test-registry --template=image-registry registry=index.docker.io auth.username=test auth.password=test 385 386 # Generate a config with the file 387 vela config create test-config --template=image-registry -f config.yaml 388 389 # Generate a config without the template 390 vela config create test-vela -f config.yaml 391 `)) 392 393 cmd := &cobra.Command{ 394 Use: "create", 395 Aliases: []string{"c"}, 396 Short: i18n.T("Create a config."), 397 Example: createConfigExample, 398 Annotations: map[string]string{ 399 types.TagCommandType: types.TypeCD, 400 }, 401 RunE: func(cmd *cobra.Command, args []string) error { 402 inf := config.NewConfigFactory(f.Client()) 403 options.Name = args[0] 404 if err := options.Validate(); err != nil { 405 return err 406 } 407 name := options.Template 408 namespace := types.DefaultKubeVelaNS 409 if strings.Contains(options.Template, "/") { 410 namespacedName := strings.SplitN(options.Template, "/", 2) 411 namespace = namespacedName[0] 412 name = namespacedName[1] 413 } 414 if err := options.parseProperties(args[1:]); err != nil { 415 return err 416 } 417 configItem, err := inf.ParseConfig(context.Background(), config.NamespacedName{ 418 Name: name, 419 Namespace: namespace, 420 }, config.Metadata{ 421 NamespacedName: config.NamespacedName{ 422 Name: options.Name, 423 Namespace: options.Namespace, 424 }, 425 Properties: options.Properties, 426 Alias: options.Alias, 427 Description: options.Description, 428 }) 429 if err != nil { 430 return err 431 } 432 if options.DryRun { 433 var outBuilder = bytes.NewBuffer(nil) 434 out, err := yaml.Marshal(configItem.Secret) 435 if err != nil { 436 return err 437 } 438 _, err = outBuilder.Write(out) 439 if err != nil { 440 return err 441 } 442 if configItem.OutputObjects != nil { 443 for k, object := range configItem.OutputObjects { 444 _, err = outBuilder.WriteString("# Object: \n ---" + k) 445 if err != nil { 446 return err 447 } 448 out, err := yaml.Marshal(object) 449 if err != nil { 450 return err 451 } 452 if _, err := outBuilder.Write(out); err != nil { 453 return err 454 } 455 } 456 } 457 _, err = streams.Out.Write(outBuilder.Bytes()) 458 return err 459 } 460 if err := inf.CreateOrUpdateConfig(context.Background(), configItem, options.Namespace); err != nil { 461 return err 462 } 463 if len(options.Targets) > 0 { 464 ads := &config.CreateDistributionSpec{ 465 Targets: []*config.ClusterTarget{}, 466 Configs: []*config.NamespacedName{ 467 &configItem.NamespacedName, 468 }, 469 } 470 for _, t := range options.Targets { 471 ti := strings.Split(t, "/") 472 if len(ti) == 2 { 473 ads.Targets = append(ads.Targets, &config.ClusterTarget{ 474 ClusterName: ti[0], 475 Namespace: ti[1], 476 }) 477 } else { 478 ads.Targets = append(ads.Targets, &config.ClusterTarget{ 479 ClusterName: types.ClusterLocalName, 480 Namespace: ti[0], 481 }) 482 } 483 } 484 name := config.DefaultDistributionName(options.Name) 485 if err := inf.CreateOrUpdateDistribution(context.Background(), options.Namespace, name, ads); err != nil { 486 return err 487 } 488 } 489 streams.Infof("the config %s applied successfully\n", options.Name) 490 return nil 491 }, 492 } 493 cmd.Flags().StringVarP(&options.Template, "template", "t", "", "specify the config template name and namespace") 494 cmd.Flags().StringVarP(&options.File, "file", "f", "", "specify the file name of the config properties") 495 cmd.Flags().StringArrayVarP(&options.Targets, "target", "", []string{}, "this config will be distributed if this flag is set") 496 cmd.Flags().BoolVarP(&options.DryRun, "dry-run", "", false, "Dry run to apply the config") 497 cmd.Flags().StringVarP(&options.Namespace, "namespace", "n", types.DefaultKubeVelaNS, "specify the namespace of the config") 498 cmd.Flags().StringVarP(&options.Description, "description", "", "", "specify the description of the config") 499 cmd.Flags().StringVarP(&options.Alias, "alias", "", "", "specify the alias of the config") 500 return cmd 501 } 502 503 // NewDistributeConfigCommand command for distributing the config 504 func NewDistributeConfigCommand(f velacmd.Factory, streams util.IOStreams) *cobra.Command { 505 var options DistributeConfigCommandOptions 506 507 distributionExample := templates.Examples(i18n.T(` 508 # distribute the config(test-registry) from the vela-system namespace to the default namespace in the local cluster. 509 vela config d test-registry -t default 510 511 # distribute the config(test-registry) from the vela-system namespace to the other clusters. 512 vela config d test-registry -t cluster1/default -t cluster2/default 513 514 # recall the config 515 vela config d test-registry --recall 516 `)) 517 518 cmd := &cobra.Command{ 519 Use: "distribute", 520 Aliases: []string{"d"}, 521 Short: i18n.T("Distribute a config"), 522 Example: distributionExample, 523 Annotations: map[string]string{ 524 types.TagCommandType: types.TypeCD, 525 }, 526 Args: cobra.ExactArgs(1), 527 RunE: func(cmd *cobra.Command, args []string) error { 528 inf := config.NewConfigFactory(f.Client()) 529 options.Config = args[0] 530 name := config.DefaultDistributionName(options.Config) 531 if options.Recalled { 532 userInput := &UserInput{ 533 Writer: streams.Out, 534 Reader: bufio.NewReader(streams.In), 535 } 536 if !assumeYes { 537 userConfirmation := userInput.AskBool("Do you want to recall this config", &UserInputOptions{assumeYes}) 538 if !userConfirmation { 539 return fmt.Errorf("recalling stopped") 540 } 541 } 542 if err := inf.DeleteDistribution(context.Background(), options.Namespace, name); err != nil { 543 return err 544 } 545 streams.Infof("the distribution %s deleted successfully\n", name) 546 return nil 547 } 548 549 ads := &config.CreateDistributionSpec{ 550 Targets: []*config.ClusterTarget{}, 551 Configs: []*config.NamespacedName{ 552 { 553 Name: options.Config, 554 Namespace: options.Namespace, 555 }, 556 }, 557 } 558 for _, t := range options.Targets { 559 ti := strings.Split(t, "/") 560 if len(ti) == 2 { 561 ads.Targets = append(ads.Targets, &config.ClusterTarget{ 562 ClusterName: ti[0], 563 Namespace: ti[1], 564 }) 565 } else { 566 ads.Targets = append(ads.Targets, &config.ClusterTarget{ 567 ClusterName: types.ClusterLocalName, 568 Namespace: ti[0], 569 }) 570 } 571 } 572 if err := inf.CreateOrUpdateDistribution(context.Background(), options.Namespace, name, ads); err != nil { 573 return err 574 } 575 streams.Infof("the distribution %s applied successfully\n", name) 576 return nil 577 }, 578 } 579 cmd.Flags().StringArrayVarP(&options.Targets, "target", "t", []string{}, "specify the targets that want to distribute,the format is: <clusterName>/<namespace>") 580 cmd.Flags().StringVarP(&options.Namespace, "namespace", "n", types.DefaultKubeVelaNS, "specify the namespace of the distribution") 581 cmd.Flags().BoolVarP(&options.Recalled, "recall", "r", false, "this field means recalling the configs from all targets.") 582 return cmd 583 } 584 585 // ConfigDeleteCommandOptions the options of the command that delete the config. 586 type ConfigDeleteCommandOptions struct { 587 Namespace string 588 Name string 589 NotRecall bool 590 } 591 592 // NewDeleteConfigCommand command for deleting the config secret 593 func NewDeleteConfigCommand(f velacmd.Factory, streams util.IOStreams) *cobra.Command { 594 var options ConfigDeleteCommandOptions 595 cmd := &cobra.Command{ 596 Use: "delete", 597 Short: i18n.T("Delete a config."), 598 Annotations: map[string]string{ 599 types.TagCommandType: types.TypeCD, 600 }, 601 Args: cobra.ExactArgs(1), 602 RunE: func(cmd *cobra.Command, args []string) error { 603 options.Name = args[0] 604 inf := config.NewConfigFactory(f.Client()) 605 userInput := &UserInput{ 606 Writer: streams.Out, 607 Reader: bufio.NewReader(streams.In), 608 } 609 if !assumeYes { 610 userConfirmation := userInput.AskBool("Do you want to delete this config", &UserInputOptions{assumeYes}) 611 if !userConfirmation { 612 return fmt.Errorf("deleting stopped") 613 } 614 } 615 616 if !options.NotRecall { 617 if err := inf.DeleteDistribution(context.Background(), options.Namespace, config.DefaultDistributionName(options.Name)); err != nil && !errors.Is(err, config.ErrNotFoundDistribution) { 618 return err 619 } 620 } 621 622 if err := inf.DeleteConfig(context.Background(), options.Namespace, options.Name); err != nil { 623 return err 624 } 625 626 streams.Infof("the config %s deleted successfully\n", options.Name) 627 return nil 628 }, 629 } 630 cmd.Flags().StringVarP(&options.Namespace, "namespace", "n", types.DefaultKubeVelaNS, "specify the namespace of the config") 631 cmd.Flags().BoolVarP(&options.NotRecall, "not-recall", "", false, "means only deleting the config from the local and do not recall from targets.") 632 return cmd 633 }