github.com/oam-dev/kubevela@v1.9.11/references/cli/def.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 "bufio" 21 "bytes" 22 "context" 23 "fmt" 24 "io" 25 "os" 26 "os/exec" 27 "path" 28 "path/filepath" 29 "reflect" 30 "regexp" 31 "strconv" 32 "strings" 33 "time" 34 35 "cuelang.org/go/cue" 36 "cuelang.org/go/cue/cuecontext" 37 "cuelang.org/go/encoding/gocode/gocodec" 38 "github.com/kubevela/workflow/pkg/cue/model/sets" 39 crossplane "github.com/oam-dev/terraform-controller/api/types/crossplane-runtime" 40 "github.com/pkg/errors" 41 "github.com/spf13/cobra" 42 "gopkg.in/yaml.v3" 43 errors2 "k8s.io/apimachinery/pkg/api/errors" 44 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 45 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 46 "k8s.io/apimachinery/pkg/runtime/serializer/json" 47 types2 "k8s.io/apimachinery/pkg/types" 48 "k8s.io/klog/v2" 49 "sigs.k8s.io/controller-runtime/pkg/client" 50 51 commontype "github.com/oam-dev/kubevela/apis/core.oam.dev/common" 52 "github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1" 53 "github.com/oam-dev/kubevela/apis/types" 54 "github.com/oam-dev/kubevela/pkg/cue/process" 55 pkgdef "github.com/oam-dev/kubevela/pkg/definition" 56 "github.com/oam-dev/kubevela/pkg/definition/gen_sdk" 57 "github.com/oam-dev/kubevela/pkg/utils" 58 addonutil "github.com/oam-dev/kubevela/pkg/utils/addon" 59 "github.com/oam-dev/kubevela/pkg/utils/common" 60 "github.com/oam-dev/kubevela/pkg/utils/filters" 61 "github.com/oam-dev/kubevela/pkg/utils/util" 62 "github.com/oam-dev/kubevela/references/cuegen" 63 providergen "github.com/oam-dev/kubevela/references/cuegen/generators/provider" 64 "github.com/oam-dev/kubevela/references/docgen" 65 ) 66 67 const ( 68 // HelmChartNamespacePlaceholder is used as a placeholder for rendering definitions into helm chart format 69 HelmChartNamespacePlaceholder = "###HELM_NAMESPACE###" 70 // HelmChartFormatEnvName is the name of the environment variable to enable render helm chart format YAML 71 HelmChartFormatEnvName = "AS_HELM_CHART" 72 ) 73 74 // DefinitionCommandGroup create the command group for `vela def` command to manage definitions 75 func DefinitionCommandGroup(c common.Args, order string, ioStreams util.IOStreams) *cobra.Command { 76 cmd := &cobra.Command{ 77 Use: "def", 78 Short: "Manage definitions.", 79 Long: "Manage X-Definitions for extension.", 80 Annotations: map[string]string{ 81 types.TagCommandOrder: order, 82 types.TagCommandType: types.TypeExtension, 83 }, 84 } 85 cmd.SetOut(ioStreams.Out) 86 cmd.AddCommand( 87 NewDefinitionGetCommand(c), 88 NewDefinitionListCommand(c), 89 NewDefinitionEditCommand(c), 90 NewDefinitionRenderCommand(c), 91 NewDefinitionApplyCommand(c, ioStreams), 92 NewDefinitionDelCommand(c), 93 NewDefinitionInitCommand(c), 94 NewDefinitionValidateCommand(c), 95 NewDefinitionDocGenCommand(c, ioStreams), 96 NewCapabilityShowCommand(c, "", ioStreams), 97 NewDefinitionGenAPICommand(c), 98 NewDefinitionGenCUECommand(c, ioStreams), 99 NewDefinitionGenDocCommand(c, ioStreams), 100 ) 101 return cmd 102 } 103 104 func getPrompt(cmd *cobra.Command, reader *bufio.Reader, description string, prompt string, validate func(string) error) (string, error) { 105 cmd.Printf(description) 106 for { 107 cmd.Printf(prompt) 108 resp, err := reader.ReadString('\n') 109 resp = strings.TrimSpace(resp) 110 if err != nil { 111 return "", errors.Wrapf(err, "failed to read user response") 112 } 113 if validate == nil { 114 return resp, nil 115 } 116 err = validate(resp) 117 if err != nil { 118 cmd.Println(err) 119 } else { 120 return resp, nil 121 } 122 } 123 } 124 125 // nolint:staticcheck 126 func buildTemplateFromYAML(templateYAML string, def *pkgdef.Definition) error { 127 templateYAMLBytes, err := utils.ReadRemoteOrLocalPath(templateYAML, false) 128 if err != nil { 129 return errors.Wrapf(err, "failed to get template YAML file %s", templateYAML) 130 } 131 yamlStrings := regexp.MustCompile(`\n---[^\n]*\n`).Split(string(templateYAMLBytes), -1) 132 templateObject := map[string]interface{}{ 133 process.OutputFieldName: map[string]interface{}{}, 134 process.OutputsFieldName: map[string]interface{}{}, 135 process.ParameterFieldName: map[string]interface{}{}, 136 } 137 for index, yamlString := range yamlStrings { 138 var yamlObject map[string]interface{} 139 if err = yaml.Unmarshal([]byte(yamlString), &yamlObject); err != nil { 140 return errors.Wrapf(err, "failed to unmarshal template yaml file") 141 } 142 if index == 0 { 143 templateObject[process.OutputFieldName] = yamlObject 144 } else { 145 name, _, _ := unstructured.NestedString(yamlObject, "metadata", "name") 146 if name == "" { 147 name = fmt.Sprintf("output-%d", index) 148 } 149 templateObject[process.OutputsFieldName].(map[string]interface{})[name] = yamlObject 150 } 151 } 152 codec := gocodec.New((*cue.Runtime)(cuecontext.New()), &gocodec.Config{}) 153 val, err := codec.Decode(templateObject) 154 if err != nil { 155 return errors.Wrapf(err, "failed to decode template into cue") 156 } 157 templateString, err := sets.ToString(val) 158 if err != nil { 159 return errors.Wrapf(err, "failed to encode template cue string") 160 } 161 err = unstructured.SetNestedField(def.Object, templateString, pkgdef.DefinitionTemplateKeys...) 162 if err != nil { 163 return errors.Wrapf(err, "failed to merge template cue string") 164 } 165 return nil 166 } 167 168 // NewDefinitionInitCommand create the `vela def init` command to help user initialize a definition locally 169 func NewDefinitionInitCommand(_ common.Args) *cobra.Command { 170 cmd := &cobra.Command{ 171 Use: "init DEF_NAME", 172 Short: "Init a new definition", 173 Long: "Init a new definition with given arguments or interactively\n* We support parsing a single YAML file (like kubernetes objects) into the cue-style template. \n" + 174 "However, we do not support variables in YAML file currently, which prevents users from directly feeding files like helm chart directly. \n" + 175 "We may introduce such features in the future.", 176 Example: "# Command below initiate an empty TraitDefinition named my-ingress\n" + 177 "> vela def init my-ingress -t trait --desc \"My ingress trait definition.\" > ./my-ingress.cue\n" + 178 "# Command below initiate a definition named my-def interactively and save it to ./my-def.cue\n" + 179 "> vela def init my-def -i --output ./my-def.cue\n" + 180 "# Command below initiate a ComponentDefinition named my-webservice with the template parsed from ./template.yaml.\n" + 181 "> vela def init my-webservice -i --template-yaml ./template.yaml\n" + 182 "# Initiate a Terraform ComponentDefinition named vswitch from Github for Alibaba Cloud.\n" + 183 "> vela def init vswitch --type component --provider alibaba --desc xxx --git https://github.com/kubevela-contrib/terraform-modules.git --path alibaba/vswitch\n" + 184 "# Initiate a Terraform ComponentDefinition named redis from local file for AWS.\n" + 185 "> vela def init redis --type component --provider aws --desc \"Terraform configuration for AWS Redis\" --local redis.tf", 186 Args: cobra.ExactArgs(1), 187 RunE: func(cmd *cobra.Command, args []string) error { 188 var defStr string 189 definitionType, err := cmd.Flags().GetString(FlagType) 190 if err != nil { 191 return errors.Wrapf(err, "failed to get `%s`", FlagType) 192 } 193 alias, err := cmd.Flags().GetString(FlagAlias) 194 if err != nil { 195 return errors.Wrapf(err, "failed to get `%s`", FlagAlias) 196 } 197 desc, err := cmd.Flags().GetString(FlagDescription) 198 if err != nil { 199 return errors.Wrapf(err, "failed to get `%s`", FlagDescription) 200 } 201 templateYAML, err := cmd.Flags().GetString(FlagTemplateYAML) 202 if err != nil { 203 return errors.Wrapf(err, "failed to get `%s`", FlagTemplateYAML) 204 } 205 output, err := cmd.Flags().GetString(FlagOutput) 206 if err != nil { 207 return errors.Wrapf(err, "failed to get `%s`", FlagOutput) 208 } 209 interactive, err := cmd.Flags().GetBool(FlagInteractive) 210 if err != nil { 211 return errors.Wrapf(err, "failed to get `%s`", FlagInteractive) 212 } 213 214 if interactive { 215 reader := bufio.NewReader(cmd.InOrStdin()) 216 if definitionType == "" { 217 if definitionType, err = getPrompt(cmd, reader, "Please choose one definition type from the following values: "+strings.Join(pkgdef.ValidDefinitionTypes(), ", ")+"\n", "> Definition type: ", func(resp string) error { 218 if _, ok := pkgdef.DefinitionTypeToKind[resp]; !ok { 219 return errors.New("invalid definition type") 220 } 221 return nil 222 }); err != nil { 223 return err 224 } 225 } 226 if desc == "" { 227 if desc, err = getPrompt(cmd, reader, "", "> Definition description: ", nil); err != nil { 228 return err 229 } 230 } 231 if templateYAML == "" { 232 if templateYAML, err = getPrompt(cmd, reader, "Please enter the location the template YAML file to build definition. Leave it empty to generate default template.\n", "> Definition template filename: ", func(resp string) error { 233 if resp == "" { 234 return nil 235 } 236 _, err = os.Stat(resp) 237 return err 238 }); err != nil { 239 return err 240 } 241 } 242 if output == "" { 243 if output, err = getPrompt(cmd, reader, "Please enter the output location of the generated definition. Leave it empty to print definition to stdout.\n", "> Definition output filename: ", nil); err != nil { 244 return err 245 } 246 } 247 } 248 249 kind, ok := pkgdef.DefinitionTypeToKind[definitionType] 250 if !ok { 251 return errors.New("invalid definition type") 252 } 253 254 name := args[0] 255 provider, err := cmd.Flags().GetString(FlagProvider) 256 if err != nil { 257 return errors.Wrapf(err, "failed to get `%s`", FlagProvider) 258 } 259 if provider != "" { 260 defStr, err = generateTerraformTypedComponentDefinition(cmd, name, kind, provider, desc) 261 if err != nil { 262 return errors.Wrapf(err, "failed to generate Terraform typed component definition") 263 } 264 } else { 265 def := pkgdef.Definition{Unstructured: unstructured.Unstructured{}} 266 def.SetGVK(kind) 267 def.SetName(name) 268 def.SetAnnotations(map[string]string{ 269 pkgdef.DescriptionKey: desc, 270 pkgdef.AliasKey: alias, 271 }) 272 def.SetLabels(map[string]string{}) 273 def.Object["spec"] = pkgdef.GetDefinitionDefaultSpec(def.GetKind()) 274 if templateYAML != "" { 275 if err = buildTemplateFromYAML(templateYAML, &def); err != nil { 276 return err 277 } 278 } 279 defStr, err = def.ToCUEString() 280 if err != nil { 281 return errors.Wrapf(err, "failed to generate cue string") 282 } 283 } 284 if output != "" { 285 if err = os.WriteFile(path.Clean(output), []byte(defStr), 0600); err != nil { 286 return errors.Wrapf(err, "failed to write definition into %s", output) 287 } 288 cmd.Printf("Definition written to %s\n", output) 289 } else if _, err = cmd.OutOrStdout().Write([]byte(defStr + "\n")); err != nil { 290 return errors.Wrapf(err, "failed to write out cue string") 291 } 292 return nil 293 }, 294 } 295 cmd.Flags().StringP(FlagType, "t", "", "Specify the type of the new definition. Valid types: "+strings.Join(pkgdef.ValidDefinitionTypes(), ", ")) 296 cmd.Flags().StringP(FlagDescription, "d", "", "Specify the description of the new definition.") 297 cmd.Flags().StringP(FlagAlias, "a", "", "Specify the alias of the new definition.") 298 cmd.Flags().StringP(FlagTemplateYAML, "f", "", "Specify the template yaml file that definition will use to build the schema. If empty, a default template for the given definition type will be used.") 299 cmd.Flags().StringP(FlagOutput, "o", "", "Specify the output path of the generated definition. If empty, the definition will be printed in the console.") 300 cmd.Flags().BoolP(FlagInteractive, "i", false, "Specify whether use interactive process to help generate definitions.") 301 cmd.Flags().StringP(FlagProvider, "p", "", "Specify which provider the cloud resource definition belongs to. Only `alibaba`, `aws`, `azure`, `gcp`, `baidu`, `tencent`, `elastic`, `ucloud`, `vsphere` are supported.") 302 cmd.Flags().StringP(FlagGit, "", "", "Specify which git repository the configuration(HCL) is stored in. Valid when --provider/-p is set.") 303 cmd.Flags().StringP(FlagLocal, "", "", "Specify the local path of the configuration(HCL) file. Valid when --provider/-p is set.") 304 cmd.Flags().StringP(FlagPath, "", "", "Specify which path the configuration(HCL) is stored in the Git repository. Valid when --git is set.") 305 return cmd 306 } 307 308 func generateTerraformTypedComponentDefinition(cmd *cobra.Command, name, kind, provider, desc string) (string, error) { 309 if kind != v1beta1.ComponentDefinitionKind { 310 return "", errors.New("provider is only valid when the type of the definition is component") 311 } 312 313 switch provider { 314 case "aws", "azure", "alibaba", "tencent", "gcp", "baidu", "elastic", "ucloud", "vsphere", "huawei": 315 var terraform *commontype.Terraform 316 317 git, err := cmd.Flags().GetString(FlagGit) 318 if err != nil { 319 return "", errors.Wrapf(err, "failed to get `%s`", FlagGit) 320 } 321 local, err := cmd.Flags().GetString(FlagLocal) 322 if err != nil { 323 return "", errors.Wrapf(err, "failed to get `%s`", FlagLocal) 324 } 325 if git != "" && local != "" { 326 return "", errors.New("only one of --git and --local can be set") 327 } 328 gitPath, err := cmd.Flags().GetString(FlagPath) 329 if err != nil { 330 return "", errors.Wrapf(err, "failed to get `%s`", FlagPath) 331 } 332 if git != "" { 333 if !strings.HasPrefix(git, "https://") || !strings.HasSuffix(git, ".git") { 334 return "", errors.Errorf("invalid git url: %s", git) 335 } 336 terraform = &commontype.Terraform{ 337 Configuration: git, 338 Type: "remote", 339 Path: gitPath, 340 } 341 } else if local != "" { 342 hcl, err := os.ReadFile(filepath.Clean(local)) 343 if err != nil { 344 return "", errors.Wrapf(err, "failed to read Terraform configuration from file %s", local) 345 } 346 terraform = &commontype.Terraform{ 347 Configuration: string(hcl), 348 } 349 } 350 def := v1beta1.ComponentDefinition{ 351 TypeMeta: metav1.TypeMeta{ 352 APIVersion: "core.oam.dev/v1beta1", 353 Kind: "ComponentDefinition", 354 }, 355 ObjectMeta: metav1.ObjectMeta{ 356 Name: fmt.Sprintf("%s-%s", provider, name), 357 Namespace: types.DefaultKubeVelaNS, 358 Annotations: map[string]string{ 359 "definition.oam.dev/description": desc, 360 }, 361 Labels: map[string]string{ 362 "type": "terraform", 363 }, 364 }, 365 Spec: v1beta1.ComponentDefinitionSpec{ 366 Workload: commontype.WorkloadTypeDescriptor{ 367 Definition: commontype.WorkloadGVK{ 368 APIVersion: "terraform.core.oam.dev/v1beta2", 369 Kind: "Configuration", 370 }, 371 }, 372 Schematic: &commontype.Schematic{ 373 Terraform: terraform, 374 }, 375 }, 376 } 377 if provider != "alibaba" { 378 def.Spec.Schematic.Terraform.ProviderReference = &crossplane.Reference{ 379 Name: provider, 380 Namespace: "default", 381 } 382 } 383 var out bytes.Buffer 384 err = json.NewSerializerWithOptions(json.DefaultMetaFactory, nil, nil, json.SerializerOptions{Yaml: true}).Encode(&def, &out) 385 if err != nil { 386 return "", errors.Wrapf(err, "failed to marshal component definition") 387 } 388 return out.String(), nil 389 default: 390 return "", errors.Errorf("Provider `%s` is not supported. Only `alibaba`, `aws`, `azure`, `gcp`, `baidu`, `tencent`, `elastic`, `ucloud`, `vsphere` are supported.", provider) 391 } 392 } 393 394 func getSingleDefinition(cmd *cobra.Command, definitionName string, client client.Client, definitionType string, namespace string) (*pkgdef.Definition, error) { 395 definitions, err := pkgdef.SearchDefinition(client, definitionType, namespace, filters.ByName(definitionName)) 396 if err != nil { 397 return nil, err 398 } 399 if len(definitions) == 0 { 400 return nil, fmt.Errorf("definition not found") 401 } 402 if len(definitions) > 1 { 403 table := newUITable() 404 table.AddRow("NAME", "TYPE", "NAMESPACE", "DESCRIPTION") 405 for _, definition := range definitions { 406 desc := "" 407 if annotations := definition.GetAnnotations(); annotations != nil { 408 desc = annotations[pkgdef.DescriptionKey] 409 } 410 table.AddRow(definition.GetName(), definition.GetKind(), definition.GetNamespace(), desc) 411 } 412 cmd.Println(table) 413 return nil, fmt.Errorf("found %d definitions, please specify which one to select with more arguments", len(definitions)) 414 } 415 return &pkgdef.Definition{Unstructured: definitions[0]}, nil 416 } 417 418 // getDefRevs will search for DefinitionRevisions with specified conditions. 419 // Check SearchDefinitionRevisions for details. 420 func getDefRevs(ctx context.Context, client client.Client, ns, defTypeStr, defName string, rev int64) ([]v1beta1.DefinitionRevision, error) { 421 defType, ok := pkgdef.StringToDefinitionType[defTypeStr] 422 // Empty definition type is intentionally allowed, to allow the user to match all definition types 423 if defTypeStr != "" && !ok { 424 return nil, fmt.Errorf("%s is not a valid type. Valid types are %v", defTypeStr, reflect.ValueOf(pkgdef.StringToDefinitionType).MapKeys()) 425 } 426 427 return pkgdef.SearchDefinitionRevisions(ctx, client, ns, defName, defType, rev) 428 } 429 430 // printDefRevs will print DefinitionRevisions 431 func printDefRevs(ctx context.Context, cmd *cobra.Command, client client.Client, ns, defTypeStr, defName string) error { 432 revs, err := getDefRevs(ctx, client, ns, defTypeStr, defName, 0) 433 if err != nil { 434 return err 435 } 436 437 table := newUITable() 438 table.AddRow("NAME", "REVISION", "TYPE", "HASH") 439 for _, rev := range revs { 440 table.AddRow(defName, rev.Spec.Revision, rev.Spec.DefinitionType, rev.Spec.RevisionHash) 441 } 442 cmd.Println(table) 443 444 return nil 445 } 446 447 // NewDefinitionGetCommand create the `vela def get` command to get definition from k8s 448 func NewDefinitionGetCommand(c common.Args) *cobra.Command { 449 var listRevisions bool 450 var targetRevision string 451 cmd := &cobra.Command{ 452 Use: "get NAME", 453 Short: "Get definition", 454 Long: "Get definition from kubernetes cluster", 455 Example: "# Command below will get the ComponentDefinition(or other definitions if exists) of webservice in all namespaces\n" + 456 "> vela def get webservice\n" + 457 "# Command below will get the TraitDefinition of annotations in namespace vela-system\n" + 458 "> vela def get annotations --type trait --namespace vela-system", 459 Args: cobra.ExactArgs(1), 460 RunE: func(cmd *cobra.Command, args []string) error { 461 definitionType, err := cmd.Flags().GetString(FlagType) 462 if err != nil { 463 return errors.Wrapf(err, "failed to get `%s`", FlagType) 464 } 465 namespace, err := cmd.Flags().GetString(FlagNamespace) 466 if err != nil { 467 return errors.Wrapf(err, "failed to get `%s`", Namespace) 468 } 469 k8sClient, err := c.GetClient() 470 if err != nil { 471 return errors.Wrapf(err, "failed to get k8s client") 472 } 473 474 if listRevisions { 475 return printDefRevs(context.Background(), cmd, k8sClient, namespace, definitionType, args[0]) 476 } 477 478 var def *pkgdef.Definition 479 480 // Get history Definition from DefinitionRevisions 481 if targetRevision != "" { 482 // "v1", "1", both need to work 483 targetRevision = strings.TrimPrefix(targetRevision, "v") 484 ver, err := strconv.Atoi(targetRevision) 485 if err != nil { 486 return fmt.Errorf("invalid version: %w", err) 487 } 488 489 // Get the user-specified revision. 490 revs, err := getDefRevs(context.Background(), k8sClient, namespace, definitionType, args[0], int64(ver)) 491 if err != nil { 492 return err 493 } 494 if len(revs) == 0 { 495 return fmt.Errorf("no %s with revision %s found in namespace %s", args[0], targetRevision, namespace) 496 } 497 498 // Now we have at least one DefinitionRevision (typically it will only be one). 499 // They all fit user's conditions. We will use the first one. 500 // Extract Definition from DefinitionRevision that we just got. 501 def, err = pkgdef.GetDefinitionFromDefinitionRevision(&revs[0]) 502 if err != nil { 503 return err 504 } 505 } else { 506 def, err = getSingleDefinition(cmd, args[0], k8sClient, definitionType, namespace) 507 if err != nil { 508 return err 509 } 510 } 511 512 cueString, err := def.ToCUEString() 513 if err != nil { 514 return errors.Wrapf(err, "failed to get cue format definition") 515 } 516 if _, err = cmd.OutOrStdout().Write([]byte(cueString + "\n")); err != nil { 517 return errors.Wrapf(err, "failed to write out cue string") 518 } 519 return nil 520 }, 521 } 522 cmd.Flags().StringP(FlagType, "t", "", "Specify which definition type to get. If empty, all types will be searched. Valid types: "+strings.Join(pkgdef.ValidDefinitionTypes(), ", ")) 523 cmd.Flags().BoolVarP(&listRevisions, "revisions", "", false, "List revisions of the specified definition.") 524 cmd.Flags().StringVarP(&targetRevision, "revision", "r", "", "Get the specified version of a definition.") 525 cmd.Flags().StringP(Namespace, "n", types.DefaultKubeVelaNS, "Specify which namespace the definition locates.") 526 return cmd 527 } 528 529 // NewDefinitionDocGenCommand create the `vela def doc-gen` command to generate documentation of definitions 530 func NewDefinitionDocGenCommand(c common.Args, ioStreams util.IOStreams) *cobra.Command { 531 var docPath, location, i18nPath string 532 cmd := &cobra.Command{ 533 Use: "doc-gen NAME", 534 Short: "Generate documentation for definitions", 535 Long: "Generate documentation for definitions", 536 Example: "1. Generate documentation for ComponentDefinition webservice:\n" + 537 "> vela def doc-gen webservice -n vela-system\n" + 538 "2. Generate documentation for local CUE Definition file webservice.cue:\n" + 539 "> vela def doc-gen webservice.cue\n" + 540 "3. Generate documentation for local Cloud Resource Definition YAML alibaba-vpc.yaml:\n" + 541 "> vela def doc-gen alibaba-vpc.yaml\n", 542 Deprecated: "This command has been replaced by 'vela show' or 'vela def show'.", 543 RunE: func(cmd *cobra.Command, args []string) error { 544 if len(args) == 0 { 545 return fmt.Errorf("please specify definition name, cue file or a cloud resource definition yaml") 546 } 547 namespace, err := cmd.Flags().GetString(FlagNamespace) 548 if err != nil { 549 return errors.Wrapf(err, "failed to get `%s`", Namespace) 550 } 551 return ShowReferenceMarkdown(context.Background(), c, ioStreams, args[0], docPath, location, i18nPath, namespace, 0) 552 553 }, 554 } 555 cmd.Flags().StringVarP(&docPath, "path", "p", "", "Specify the path for of the doc generated from definition.") 556 cmd.Flags().StringVarP(&location, "location", "l", "", "specify the location for of the doc generated from definition, now supported options 'zh', 'en'. ") 557 cmd.Flags().StringP(Namespace, "n", types.DefaultKubeVelaNS, "Specify which namespace the definition locates.") 558 cmd.Flags().StringVarP(&i18nPath, "i18n", "", "https://kubevela.io/reference-i18n.json", "specify the location for of the doc generated from definition, now supported options 'zh', 'en'. ") 559 return cmd 560 } 561 562 // NewDefinitionListCommand create the `vela def list` command to list definition from k8s 563 func NewDefinitionListCommand(c common.Args) *cobra.Command { 564 cmd := &cobra.Command{ 565 Use: "list", 566 Short: "List definitions.", 567 Long: "List definitions in kubernetes cluster.", 568 Example: "# Command below will list all definitions in all namespaces\n" + 569 "> vela def list\n" + 570 "# Command below will list all definitions in the vela-system namespace\n" + 571 "> vela def get annotations --type trait --namespace vela-system", 572 Args: cobra.ExactArgs(0), 573 RunE: func(cmd *cobra.Command, args []string) error { 574 definitionType, err := cmd.Flags().GetString(FlagType) 575 if err != nil { 576 return errors.Wrapf(err, "failed to get `%s`", FlagType) 577 } 578 namespace, err := cmd.Flags().GetString(FlagNamespace) 579 if err != nil { 580 return errors.Wrapf(err, "failed to get `%s`", Namespace) 581 } 582 addonName, err := cmd.Flags().GetString("from") 583 if err != nil { 584 return errors.Wrapf(err, "failed to get `%s`", "from") 585 } 586 k8sClient, err := c.GetClient() 587 if err != nil { 588 return errors.Wrapf(err, "failed to get k8s client") 589 } 590 definitions, err := pkgdef.SearchDefinition(k8sClient, 591 definitionType, 592 namespace, 593 filters.ByOwnerAddon(addonName)) 594 if err != nil { 595 return err 596 } 597 if len(definitions) == 0 { 598 cmd.Println("No definition found.") 599 return nil 600 } 601 // Determine if there is a definition in the list from some addons 602 // This is used to tell if we want the SOURCE-ADDON column 603 showSourceAddon := false 604 for _, def := range definitions { 605 ownerRef := def.GetOwnerReferences() 606 if len(ownerRef) > 0 && strings.HasPrefix(ownerRef[0].Name, addonutil.AddonAppPrefix) { 607 showSourceAddon = true 608 break 609 } 610 } 611 table := newUITable() 612 613 // We only include SOURCE-ADDON if there is at least one definition from an addon 614 if showSourceAddon { 615 table.AddRow("NAME", "TYPE", "NAMESPACE", "SOURCE-ADDON", "DESCRIPTION") 616 } else { 617 table.AddRow("NAME", "TYPE", "NAMESPACE", "DESCRIPTION") 618 } 619 620 for _, definition := range definitions { 621 desc := "" 622 if annotations := definition.GetAnnotations(); annotations != nil { 623 desc = annotations[pkgdef.DescriptionKey] 624 } 625 626 // Do not show SOURCE-ADDON column 627 if !showSourceAddon { 628 table.AddRow(definition.GetName(), definition.GetKind(), definition.GetNamespace(), desc) 629 continue 630 } 631 632 sourceAddon := "" 633 if len(definition.GetOwnerReferences()) > 0 { 634 sourceAddon = strings.TrimPrefix(definition.GetOwnerReferences()[0].Name, "addon-") 635 } 636 table.AddRow(definition.GetName(), definition.GetKind(), definition.GetNamespace(), sourceAddon, desc) 637 } 638 cmd.Println(table) 639 return nil 640 }, 641 } 642 cmd.Flags().StringP(FlagType, "t", "", "Specify which definition type to list. If empty, all types will be searched. Valid types: "+strings.Join(pkgdef.ValidDefinitionTypes(), ", ")) 643 cmd.Flags().String("from", "", "Filter definitions by which addon installed them.") 644 cmd.Flags().StringP(Namespace, "n", types.DefaultKubeVelaNS, "Specify which namespace the definition locates.") 645 return cmd 646 } 647 648 // NewDefinitionEditCommand create the `vela def edit` command to help user edit remote definitions 649 func NewDefinitionEditCommand(c common.Args) *cobra.Command { 650 cmd := &cobra.Command{ 651 Use: "edit NAME", 652 Short: "Edit X-Definition.", 653 Long: "Edit X-Definition in kubernetes. If type and namespace are not specified, the command will automatically search all possible results.\n" + 654 "By default, this command will use the vi editor and can be altered by setting EDITOR environment variable.", 655 Example: "# Command below will edit the ComponentDefinition (and other definitions if exists) of webservice in kubernetes\n" + 656 "> vela def edit webservice\n" + 657 "# Command below will edit the TraitDefinition of ingress in vela-system namespace\n" + 658 "> vela def edit ingress --type trait --namespace vela-system", 659 Args: cobra.ExactArgs(1), 660 RunE: func(cmd *cobra.Command, args []string) error { 661 definitionType, err := cmd.Flags().GetString(FlagType) 662 if err != nil { 663 return errors.Wrapf(err, "failed to get `%s`", FlagType) 664 } 665 namespace, err := cmd.Flags().GetString(FlagNamespace) 666 if err != nil { 667 return errors.Wrapf(err, "failed to get `%s`", Namespace) 668 } 669 config, err := c.GetConfig() 670 if err != nil { 671 return err 672 } 673 k8sClient, err := c.GetClient() 674 if err != nil { 675 return errors.Wrapf(err, "failed to get k8s client") 676 } 677 def, err := getSingleDefinition(cmd, args[0], k8sClient, definitionType, namespace) 678 if err != nil { 679 return err 680 } 681 cueString, err := def.ToCUEString() 682 if err != nil { 683 return errors.Wrapf(err, "failed to get cue format definition") 684 } 685 cleanup := func(filePath string) { 686 if err := os.Remove(filePath); err != nil { 687 cmd.PrintErrf("failed to remove file %s: %v", filePath, err) 688 } 689 } 690 filename := fmt.Sprintf("vela-def-%d", time.Now().UnixNano()) 691 tempFilePath := filepath.Join(os.TempDir(), filename+CUEExtension) 692 if err := os.WriteFile(tempFilePath, []byte(cueString), 0600); err != nil { 693 return errors.Wrapf(err, "failed to write temporary file") 694 } 695 defer cleanup(tempFilePath) 696 editor := os.Getenv("EDITOR") 697 if editor == "" { 698 editor = "vi" 699 } 700 scriptFilePath := filepath.Join(os.TempDir(), filename+".sh") 701 if err := os.WriteFile(scriptFilePath, []byte(editor+" "+tempFilePath), 0600); err != nil { 702 return errors.Wrapf(err, "failed to write temporary script file") 703 } 704 defer cleanup(scriptFilePath) 705 706 editCmd := exec.Command("sh", path.Clean(scriptFilePath)) //nolint:gosec 707 editCmd.Stdin = os.Stdin 708 editCmd.Stdout = os.Stdout 709 editCmd.Stderr = os.Stderr 710 if err = editCmd.Run(); err != nil { 711 return errors.Wrapf(err, "failed to run editor %s at path %s", editor, scriptFilePath) 712 } 713 newBuf, err := os.ReadFile(path.Clean(tempFilePath)) 714 if err != nil { 715 return errors.Wrapf(err, "failed to read temporary file %s", tempFilePath) 716 } 717 if cueString == string(newBuf) { 718 cmd.Printf("definition unchanged\n") 719 return nil 720 } 721 if err := def.FromCUEString(string(newBuf), config); err != nil { 722 return errors.Wrapf(err, "failed to load edited cue string") 723 } 724 if err := k8sClient.Update(context.Background(), def); err != nil { 725 return errors.Wrapf(err, "failed to apply changes to kubernetes") 726 } 727 cmd.Printf("Definition edited successfully.\n") 728 return nil 729 }, 730 } 731 cmd.Flags().StringP(FlagType, "t", "", "Specify which definition type to get. If empty, all types will be searched. Valid types: "+strings.Join(pkgdef.ValidDefinitionTypes(), ", ")) 732 cmd.Flags().StringP(Namespace, "n", types.DefaultKubeVelaNS, "Specify which namespace the definition locates.") 733 return cmd 734 } 735 736 func prettyYAMLMarshal(obj map[string]interface{}) (string, error) { 737 var b bytes.Buffer 738 encoder := yaml.NewEncoder(&b) 739 encoder.SetIndent(2) 740 err := encoder.Encode(&obj) 741 if err != nil { 742 return "", err 743 } 744 return b.String(), nil 745 } 746 747 // NewDefinitionRenderCommand create the `vela def render` command to help user render definition cue file into k8s YAML file, if used without kubernetes environment, set IGNORE_KUBE_CONFIG=true 748 func NewDefinitionRenderCommand(c common.Args) *cobra.Command { 749 cmd := &cobra.Command{ 750 Use: "render DEFINITION.cue", 751 Short: "Render X-Definition.", 752 Long: "Render X-Definition with cue format into kubernetes YAML format. Could be used to check whether the cue format definition is working as expected. If a directory is used as input, all cue definitions in the directory will be rendered.", 753 Example: "# Command below will render my-webservice.cue into YAML format and print it out.\n" + 754 "> vela def render my-webservice.cue\n" + 755 "# Command below will render my-webservice.cue and save it in my-webservice.yaml.\n" + 756 "> vela def render my-webservice.cue -o my-webservice.yaml" + 757 "# Command below will render all cue format definitions in the ./defs/cue/ directory and save the YAML objects in ./defs/yaml/.\n" + 758 "> vela def render ./defs/cue/ -o ./defs/yaml/", 759 Args: cobra.ExactArgs(1), 760 RunE: func(cmd *cobra.Command, args []string) error { 761 output, err := cmd.Flags().GetString(FlagOutput) 762 if err != nil { 763 return errors.Wrapf(err, "failed to get `%s`", FlagOutput) 764 } 765 message, err := cmd.Flags().GetString(FlagMessage) 766 if err != nil { 767 return errors.Wrapf(err, "failed to get `%s`", FlagMessage) 768 } 769 770 render := func(inputFilename, outputFilename string) error { 771 cueBytes, err := utils.ReadRemoteOrLocalPath(inputFilename, false) 772 if err != nil { 773 return errors.Wrapf(err, "failed to get %s", args[0]) 774 } 775 config, err := c.GetConfig() 776 if err != nil { 777 klog.Infof("ignore kubernetes cluster, unable to get kubeconfig: %s", err.Error()) 778 } 779 def := pkgdef.Definition{Unstructured: unstructured.Unstructured{}} 780 if err := def.FromCUEString(string(cueBytes), config); err != nil { 781 return errors.Wrapf(err, "failed to parse CUE") 782 } 783 784 helmChartFormatEnv := strings.ToLower(os.Getenv(HelmChartFormatEnvName)) 785 if helmChartFormatEnv == "true" { 786 def.SetNamespace(HelmChartNamespacePlaceholder) 787 } else if helmChartFormatEnv == "system" { 788 def.SetNamespace(types.DefaultKubeVelaNS) 789 } 790 if len(def.GetLabels()) == 0 { 791 def.SetLabels(nil) 792 } 793 s, err := prettyYAMLMarshal(def.Object) 794 if err != nil { 795 return errors.Wrapf(err, "failed to marshal CRD into YAML") 796 } 797 s = strings.ReplaceAll(s, "'"+HelmChartNamespacePlaceholder+"'", "{{ include \"systemDefinitionNamespace\" . }}") + "\n" 798 if outputFilename == "" { 799 s = fmt.Sprintf("--- %s ---\n%s", filepath.Base(inputFilename), s) 800 cmd.Print(s) 801 } else { 802 if message != "" { 803 s = "# " + strings.ReplaceAll(message, "{{INPUT_FILENAME}}", filepath.Base(inputFilename)) + "\n" + s 804 } 805 s = "# Code generated by KubeVela templates. DO NOT EDIT. Please edit the original cue file.\n" + s 806 if err := os.WriteFile(outputFilename, []byte(s), 0600); err != nil { 807 return errors.Wrapf(err, "failed to write YAML format definition to file %s", outputFilename) 808 } 809 } 810 return nil 811 } 812 inputFilenames := []string{args[0]} 813 outputFilenames := []string{output} 814 fi, err := os.Stat(args[0]) 815 if err != nil { 816 return errors.Wrapf(err, "failed to get input %s", args[0]) 817 } 818 if fi.IsDir() { 819 inputFilenames = []string{} 820 outputFilenames = []string{} 821 err := filepath.Walk(args[0], func(path string, info os.FileInfo, err error) error { 822 filename := filepath.Base(path) 823 fileSuffix := filepath.Ext(path) 824 if fileSuffix != CUEExtension { 825 return nil 826 } 827 inputFilenames = append(inputFilenames, path) 828 if output != "" { 829 outputFilenames = append(outputFilenames, filepath.Join(output, strings.ReplaceAll(filename, CUEExtension, YAMLExtension))) 830 } else { 831 outputFilenames = append(outputFilenames, "") 832 } 833 return nil 834 }) 835 if err != nil { 836 return errors.Wrapf(err, "failed to read directory %s", args[0]) 837 } 838 } 839 for i, inputFilename := range inputFilenames { 840 if err = render(inputFilename, outputFilenames[i]); err != nil { 841 if _, err = fmt.Fprintf(cmd.ErrOrStderr(), "failed to render %s, reason: %v", inputFilename, err); err != nil { 842 return errors.Wrapf(err, "failed to write err") 843 } 844 } 845 } 846 return nil 847 }, 848 } 849 cmd.Flags().StringP(FlagOutput, "o", "", "Specify the output path of the rendered definition YAML. If empty, the definition will be printed in the console. If input is a directory, the output path is expected to be a directory as well.") 850 cmd.Flags().StringP(FlagMessage, "", "", "Specify the header message of the generated YAML file. For example, declaring author information.") 851 return cmd 852 } 853 854 // NewDefinitionApplyCommand create the `vela def apply` command to help user apply local definitions to k8s 855 func NewDefinitionApplyCommand(c common.Args, streams util.IOStreams) *cobra.Command { 856 cmd := &cobra.Command{ 857 Use: "apply DEFINITION.cue", 858 Short: "Apply X-Definition.", 859 Long: "Apply X-Definition from local storage to kubernetes cluster. It will apply file to vela-system namespace by default.", 860 Example: "# Command below will apply the local my-webservice.cue file to kubernetes vela-system namespace\n" + 861 "> vela def apply my-webservice.cue\n" + 862 "# Apply the local directory including all files(YAML and CUE definition) to kubernetes vela-system namespace\n" + 863 "> vela def apply def/\n" + 864 "# Command below will apply the ./defs/my-trait.cue file to kubernetes default namespace\n" + 865 "> vela def apply ./defs/my-trait.cue --namespace default" + 866 "# Command below will convert the ./defs/my-trait.cue file to kubernetes CRD object and print it without applying it to kubernetes\n" + 867 "> vela def apply ./defs/my-trait.cue --dry-run" + 868 "# Apply a CUE from URL \n" + 869 "> vela def apply https://my-host-to-def/my-trait.cue --dry-run" + 870 "# Apply a CUE from stdin \n" + 871 "> vela def apply -", 872 Args: cobra.ExactArgs(1), 873 RunE: func(cmd *cobra.Command, args []string) error { 874 ctx := context.Background() 875 dryRun, err := cmd.Flags().GetBool(FlagDryRun) 876 if err != nil { 877 return errors.Wrapf(err, "failed to get `%s`", FlagDryRun) 878 } 879 namespace, err := cmd.Flags().GetString(FlagNamespace) 880 if err != nil { 881 return errors.Wrapf(err, "failed to get `%s`", Namespace) 882 } 883 if len(args) < 1 { 884 return errors.New("you must specify the definition path, directory or URL") 885 } 886 return defApplyAll(ctx, c, streams, namespace, args[0], dryRun) 887 }, 888 } 889 890 cmd.Flags().BoolP(FlagDryRun, "", false, "only build definition from CUE into CRB object without applying it to kubernetes clusters") 891 cmd.Flags().StringP(Namespace, "n", types.DefaultKubeVelaNS, "Specify which namespace the definition locates.") 892 return cmd 893 } 894 895 func defApplyAll(ctx context.Context, c common.Args, io util.IOStreams, namespace, path string, dryRun bool) error { 896 files, err := utils.LoadDataFromPath(ctx, path, utils.IsJSONYAMLorCUEFile) 897 if err != nil { 898 return errors.Wrapf(err, "failed to get from %s", path) 899 } 900 for _, f := range files { 901 result, err := defApplyOne(ctx, c, namespace, f.Path, f.Data, dryRun) 902 if err != nil { 903 return err 904 } 905 io.Infonln(result) 906 } 907 return nil 908 } 909 910 func defApplyOne(ctx context.Context, c common.Args, namespace, defpath string, defBytes []byte, dryRun bool) (string, error) { 911 config, err := c.GetConfig() 912 if err != nil { 913 return "", err 914 } 915 k8sClient, err := c.GetClient() 916 if err != nil { 917 return "", errors.Wrapf(err, "failed to get k8s client") 918 } 919 920 def := pkgdef.Definition{Unstructured: unstructured.Unstructured{}} 921 922 switch { 923 case strings.HasSuffix(defpath, YAMLExtension) || strings.HasSuffix(defpath, YMLExtension): 924 // In this case, it's not in cue format, it's a yaml 925 if err = def.FromYAML(defBytes); err != nil { 926 return "", errors.Wrapf(err, "failed to parse YAML to definition") 927 } 928 if dryRun { 929 return "", errors.New("dry-run will render CUE to YAML, while the input is already in yaml") 930 } 931 // YAML won't validate or format CUE schematic 932 op, err := utils.CreateOrUpdate(ctx, k8sClient, &def) 933 if err != nil { 934 return "", err 935 } 936 return fmt.Sprintf("%s %s in namespace %s %s.\n", def.GetKind(), def.GetName(), def.GetNamespace(), op), nil 937 default: 938 if err := def.FromCUEString(string(defBytes), config); err != nil { 939 return "", errors.Wrapf(err, "failed to parse CUE for definition") 940 } 941 def.SetNamespace(namespace) 942 } 943 944 if dryRun { 945 s, err := prettyYAMLMarshal(def.Object) 946 if err != nil { 947 return "", errors.Wrapf(err, "failed to marshal CRD into YAML") 948 } 949 return s, nil 950 } 951 952 oldDef := pkgdef.Definition{Unstructured: unstructured.Unstructured{}} 953 oldDef.SetGroupVersionKind(def.GroupVersionKind()) 954 err = k8sClient.Get(ctx, types2.NamespacedName{ 955 Namespace: def.GetNamespace(), 956 Name: def.GetName(), 957 }, &oldDef) 958 if err != nil { 959 if errors2.IsNotFound(err) { 960 kind := def.GetKind() 961 if err = k8sClient.Create(ctx, &def); err != nil { 962 return "", errors.Wrapf(err, "failed to create new definition in kubernetes") 963 } 964 return fmt.Sprintf("%s %s created in namespace %s.\n", kind, def.GetName(), def.GetNamespace()), nil 965 } 966 return "", errors.Wrapf(err, "failed to check existence of target definition in kubernetes") 967 } 968 if err := oldDef.FromCUEString(string(defBytes), config); err != nil { 969 return "", errors.Wrapf(err, "failed to merge with existing definition") 970 } 971 if err = k8sClient.Update(ctx, &oldDef); err != nil { 972 return "", errors.Wrapf(err, "failed to update existing definition in kubernetes") 973 } 974 return fmt.Sprintf("%s %s in namespace %s updated.\n", oldDef.GetKind(), oldDef.GetName(), oldDef.GetNamespace()), nil 975 } 976 977 // NewDefinitionDelCommand create the `vela def del` command to help user delete existing definitions conveniently 978 func NewDefinitionDelCommand(c common.Args) *cobra.Command { 979 cmd := &cobra.Command{ 980 Use: "del DEFINITION_NAME", 981 Short: "Delete X-Definition.", 982 Long: "Delete X-Definition in kubernetes cluster.", 983 Example: "# Command below will delete TraitDefinition of annotations in default namespace\n" + 984 "> vela def del annotations -t trait -n default", 985 Args: cobra.ExactArgs(1), 986 RunE: func(cmd *cobra.Command, args []string) error { 987 definitionType, err := cmd.Flags().GetString(FlagType) 988 if err != nil { 989 return errors.Wrapf(err, "failed to get `%s`", FlagType) 990 } 991 namespace, err := cmd.Flags().GetString(FlagNamespace) 992 if err != nil { 993 return errors.Wrapf(err, "failed to get `%s`", Namespace) 994 } 995 k8sClient, err := c.GetClient() 996 if err != nil { 997 return errors.Wrapf(err, "failed to get k8s client") 998 } 999 def, err := getSingleDefinition(cmd, args[0], k8sClient, definitionType, namespace) 1000 if err != nil { 1001 return err 1002 } 1003 desc := def.GetAnnotations()[pkgdef.DescriptionKey] 1004 toDelete := false 1005 _, err = getPrompt(cmd, bufio.NewReader(cmd.InOrStdin()), 1006 fmt.Sprintf("Are you sure to delete the following definition in namespace %s?\n", def.GetNamespace())+ 1007 fmt.Sprintf("%s %s: %s\n", def.GetKind(), def.GetName(), desc), 1008 "[yes|no] > ", 1009 func(resp string) error { 1010 switch strings.ToLower(resp) { 1011 case "yes": 1012 toDelete = true 1013 case "y": 1014 toDelete = true 1015 case "no": 1016 toDelete = false 1017 case "n": 1018 toDelete = false 1019 default: 1020 return errors.New("invalid input") 1021 } 1022 return nil 1023 }) 1024 if err != nil { 1025 return err 1026 } 1027 if !toDelete { 1028 return nil 1029 } 1030 if err := k8sClient.Delete(context.Background(), def); err != nil { 1031 return errors.Wrapf(err, "failed to delete %s %s in namespace %s", def.GetKind(), def.GetName(), def.GetNamespace()) 1032 } 1033 cmd.Printf("%s %s in namespace %s deleted.\n", def.GetKind(), def.GetName(), def.GetNamespace()) 1034 return nil 1035 }, 1036 } 1037 cmd.Flags().StringP(FlagType, "t", "", "Specify the definition type of target. Valid types: "+strings.Join(pkgdef.ValidDefinitionTypes(), ", ")) 1038 cmd.Flags().StringP(Namespace, "n", types.DefaultKubeVelaNS, "Specify which namespace the definition locates.") 1039 return cmd 1040 } 1041 1042 // NewDefinitionValidateCommand create the `vela def vet` command to help user validate the definition 1043 func NewDefinitionValidateCommand(c common.Args) *cobra.Command { 1044 cmd := &cobra.Command{ 1045 Use: "vet DEFINITION.cue", 1046 Short: "Validate X-Definition.", 1047 Long: "Validate definition file by checking whether it has the valid cue format with fields set correctly\n" + 1048 "* Currently, this command only checks the cue format. This function is still working in progress and we will support more functional validation mechanism in the future.", 1049 Example: "# Command below will validate the my-def.cue file.\n" + 1050 "> vela def vet my-def.cue\n" + 1051 "# Validate every cue file provided\n" + 1052 "> vela def vet my-def1.cue my-def2.cue my-def3.cue\n" + 1053 "# Validate every cue file in the specified directories" + 1054 "> vela def vet ./test1/ ./test2/", 1055 Args: cobra.MinimumNArgs(1), 1056 RunE: func(cmd *cobra.Command, args []string) error { 1057 for _, arg := range args { 1058 files, err := utils.LoadDataFromPath(cmd.Context(), arg, utils.IsCUEFile) 1059 if err != nil { 1060 return errors.Wrapf(err, "failed to get file from %s", arg) 1061 } 1062 for _, file := range files { 1063 validateRes, err := validateSingleCueFile(file.Path, file.Data, c) 1064 if err != nil { 1065 return err 1066 } 1067 fmt.Fprintf(cmd.OutOrStdout(), "%s", validateRes) 1068 } 1069 } 1070 return nil 1071 }, 1072 } 1073 return cmd 1074 } 1075 1076 func validateSingleCueFile(fileName string, fileData []byte, c common.Args) (string, error) { 1077 def := pkgdef.Definition{Unstructured: unstructured.Unstructured{}} 1078 config, err := c.GetConfig() 1079 if err != nil { 1080 klog.Infof("ignore kubernetes cluster, unable to get kubeconfig: %s", err.Error()) 1081 } 1082 if err := def.FromCUEString(string(fileData), config); err != nil { 1083 return "", errors.Wrapf(err, "failed to parse CUE: %s", fileName) 1084 } 1085 return fmt.Sprintf("Validation %s succeed.\n", fileName), nil 1086 } 1087 1088 // NewDefinitionGenAPICommand create the `vela def gen-api` command to help user generate Go code from the definition 1089 func NewDefinitionGenAPICommand(c common.Args) *cobra.Command { 1090 meta := gen_sdk.GenMeta{} 1091 var languageArgs []string 1092 1093 cmd := &cobra.Command{ 1094 Use: "gen-api DEFINITION.cue", 1095 Short: "Generate SDK from X-Definition.", 1096 Long: "Generate SDK from X-definition file.\n" + 1097 "* This command leverage openapi-generator project. Therefore demands \"docker\" exist in PATH\n" + 1098 "* Currently, this function is still working in progress and not all formats of parameter in X-definition are supported yet.", 1099 Example: "# Generate SDK for golang with scaffold initialized\n" + 1100 "> vela def gen-api --init --language go -f /path/to/def -o /path/to/sdk\n" + 1101 "# Generate incremental definition files to existing sdk directory\n" + 1102 "> vela def gen-api --language go -f /path/to/def -o /path/to/sdk\n" + 1103 "# Generate definitions to a sub-module\n" + 1104 "> vela def gen-api --language go -f /path/to/def -o /path/to/sdk --submodule --api-dir path/relative/to/output --language-args arg1=val1,arg2=val2\n", 1105 RunE: func(cmd *cobra.Command, args []string) error { 1106 err := meta.Init(c, languageArgs) 1107 if err != nil { 1108 return err 1109 } 1110 err = meta.CreateScaffold() 1111 if err != nil { 1112 return err 1113 } 1114 err = meta.PrepareGeneratorAndTemplate() 1115 if err != nil { 1116 return err 1117 } 1118 err = meta.Run() 1119 if err != nil { 1120 return err 1121 } 1122 1123 return nil 1124 }, 1125 } 1126 1127 cmd.Flags().StringVarP(&meta.Output, "output", "o", "./apis", "Output directory path") 1128 cmd.Flags().StringVar(&meta.APIDirectory, "api-dir", "", "API directory path to put definition API files, relative to output directory. Default value: go: pkg/apis") 1129 cmd.Flags().BoolVar(&meta.IsSubModule, "submodule", false, "Whether the generated code is a submodule of the project. If set, the directory specified by `api-dir` will be treated as a submodule of the project") 1130 cmd.Flags().StringVarP(&meta.Package, "package", "p", gen_sdk.PackagePlaceHolder, "Package name of generated code") 1131 cmd.Flags().StringVarP(&meta.Lang, "language", "g", "go", "Language to generate code. Valid languages: go") 1132 cmd.Flags().StringVarP(&meta.Template, "template", "t", "", "Template file path, if not specified, the default template will be used") 1133 cmd.Flags().StringSliceVarP(&meta.File, "file", "f", nil, "File name of definitions, can be specified multiple times, or use comma to separate multiple files. If directory specified, all files found recursively in the directory will be used") 1134 cmd.Flags().BoolVar(&meta.InitSDK, "init", false, "Init the whole SDK project, if not set, only the API file will be generated") 1135 cmd.Flags().BoolVarP(&meta.Verbose, "verbose", "v", false, "Print verbose logs") 1136 var langArgsDescStr string 1137 for lang, args := range gen_sdk.LangArgsRegistry { 1138 langArgsDescStr += lang + ": \n" 1139 for key, arg := range args { 1140 langArgsDescStr += fmt.Sprintf("\t%s: %s(default: %s)\n", key, arg.Name, arg.Default) 1141 } 1142 } 1143 cmd.Flags().StringSliceVar(&languageArgs, "language-args", []string{}, 1144 fmt.Sprintf("language-specific arguments to pass to the go generator, available options: \n"+langArgsDescStr), 1145 ) 1146 1147 return cmd 1148 } 1149 1150 const ( 1151 genTypeProvider = "provider" 1152 ) 1153 1154 // NewDefinitionGenCUECommand create the `vela def gen-cue` command to help user generate CUE schema from the go code 1155 func NewDefinitionGenCUECommand(_ common.Args, streams util.IOStreams) *cobra.Command { 1156 var ( 1157 typ string 1158 typeMap map[string]string 1159 nullable bool 1160 ) 1161 1162 cmd := &cobra.Command{ 1163 Use: "gen-cue [flags] SOURCE.go", 1164 Args: cobra.ExactArgs(1), 1165 Short: "Generate CUE schema from Go code.", 1166 Long: "Generate CUE schema from Go code.\n" + 1167 "* This command provide a way to generate CUE schema from Go code,\n" + 1168 "* Which can be used to keep consistency between Go code and CUE schema automatically.\n", 1169 Example: "# Generate CUE schema for provider type\n" + 1170 "> vela def gen-cue -t provider /path/to/myprovider.go > /path/to/myprovider.cue\n" + 1171 "# Generate CUE schema for provider type with custom types\n" + 1172 "> vela def gen-cue -t provider --types *k8s.io/apimachinery/pkg/apis/meta/v1/unstructured.Unstructured=ellipsis /path/to/myprovider.go > /path/to/myprovider.cue", 1173 RunE: func(cmd *cobra.Command, args []string) (rerr error) { 1174 // convert map[string]string to map[string]cuegen.Type 1175 newTypeMap := make(map[string]cuegen.Type, len(typeMap)) 1176 for k, v := range typeMap { 1177 newTypeMap[k] = cuegen.Type(v) 1178 } 1179 1180 file := args[0] 1181 if !strings.HasSuffix(file, ".go") { 1182 return fmt.Errorf("invalid file %s, must be a go file", file) 1183 } 1184 1185 switch typ { 1186 case genTypeProvider: 1187 return providergen.Generate(providergen.Options{ 1188 File: file, 1189 Writer: streams.Out, 1190 Types: newTypeMap, 1191 Nullable: nullable, 1192 }) 1193 default: 1194 return fmt.Errorf("invalid type %s", typ) 1195 } 1196 }, 1197 } 1198 1199 cmd.Flags().StringVarP(&typ, "type", "t", "", "Type of the definition to generate. Valid types: [provider]") 1200 cmd.Flags().BoolVar(&nullable, "nullable", false, "Whether to generate null enum for pointer type") 1201 cmd.Flags().StringToStringVar(&typeMap, "types", map[string]string{}, "Special types to generate, format: <package+struct>=[any|ellipsis]. e.g. --types=*k8s.io/apimachinery/pkg/apis/meta/v1/unstructured.Unstructured=ellipsis") 1202 1203 return cmd 1204 } 1205 1206 // NewDefinitionGenDocCommand create the `vela def gen-doc` command to generate documentation of definitions 1207 func NewDefinitionGenDocCommand(_ common.Args, streams util.IOStreams) *cobra.Command { 1208 var typ string 1209 1210 cmd := &cobra.Command{ 1211 Use: "gen-doc [flags] SOURCE.cue...", 1212 Args: cobra.MinimumNArgs(1), 1213 Short: "Generate documentation for non component, trait, policy and workflow definitions", 1214 Long: "Generate documentation for non component, trait, policy and workflow definitions", 1215 Example: "1. Generate documentation for provider definitions\n" + 1216 "> vela def gen-doc -t provider provider1.cue provider2.cue > provider.md", 1217 RunE: func(cmd *cobra.Command, args []string) error { 1218 readers := make([]io.Reader, 0, len(args)) 1219 1220 for _, arg := range args { 1221 if !strings.HasSuffix(arg, CUEExtension) { 1222 return fmt.Errorf("invalid file %s, must be a cue file", arg) 1223 } 1224 1225 f, err := os.ReadFile(filepath.Clean(arg)) 1226 if err != nil { 1227 return fmt.Errorf("read file %s: %w", arg, err) 1228 } 1229 1230 readers = append(readers, bytes.NewReader(f)) 1231 } 1232 1233 switch typ { 1234 case genTypeProvider: 1235 return docgen.GenerateProvidersMarkdown(cmd.Context(), readers, streams.Out) 1236 default: 1237 return fmt.Errorf("invalid type %s", typ) 1238 } 1239 }, 1240 } 1241 1242 cmd.Flags().StringVarP(&typ, "type", "t", "", "Type of the definition to generate. Valid types: [provider]") 1243 1244 return cmd 1245 }