github.com/oam-dev/kubevela@v1.9.11/references/docgen/parser.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 docgen 18 19 import ( 20 "context" 21 "encoding/json" 22 "fmt" 23 "io/fs" 24 "os" 25 "path/filepath" 26 "sort" 27 "strconv" 28 "strings" 29 30 "cuelang.org/go/cue" 31 "cuelang.org/go/cue/ast" 32 "github.com/getkin/kin-openapi/openapi3" 33 "github.com/olekukonko/tablewriter" 34 "github.com/pkg/errors" 35 "github.com/rogpeppe/go-internal/modfile" 36 "k8s.io/apimachinery/pkg/api/meta" 37 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 38 "k8s.io/apimachinery/pkg/runtime/schema" 39 "k8s.io/klog/v2" 40 "sigs.k8s.io/controller-runtime/pkg/client" 41 "sigs.k8s.io/controller-runtime/pkg/client/fake" 42 "sigs.k8s.io/yaml" 43 44 "github.com/kubevela/workflow/pkg/cue/model/value" 45 "github.com/kubevela/workflow/pkg/cue/packages" 46 47 "github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1" 48 "github.com/oam-dev/kubevela/apis/types" 49 "github.com/oam-dev/kubevela/pkg/controller/utils" 50 velacue "github.com/oam-dev/kubevela/pkg/cue" 51 pkgdef "github.com/oam-dev/kubevela/pkg/definition" 52 pkgUtils "github.com/oam-dev/kubevela/pkg/utils" 53 "github.com/oam-dev/kubevela/pkg/utils/common" 54 "github.com/oam-dev/kubevela/pkg/utils/terraform" 55 "github.com/oam-dev/kubevela/references/docgen/fix" 56 57 gitssh "github.com/go-git/go-git/v5/plumbing/transport/ssh" 58 k8stypes "k8s.io/apimachinery/pkg/types" 59 ) 60 61 // ParseReference is used to include the common function `parseParameter` 62 type ParseReference struct { 63 Client client.Client 64 I18N *I18n `json:"i18n"` 65 Remote *FromCluster `json:"remote"` 66 Local *FromLocal `json:"local"` 67 DefinitionName string `json:"definitionName"` 68 DisplayFormat string 69 } 70 71 func (ref *ParseReference) getCapabilities(ctx context.Context, c common.Args) ([]types.Capability, error) { 72 var ( 73 caps []types.Capability 74 pd *packages.PackageDiscover 75 ) 76 switch { 77 case ref.Local != nil: 78 lcaps := make([]*types.Capability, 0) 79 for _, path := range ref.Local.Paths { 80 caps, err := ParseLocalFiles(path, c) 81 if err != nil { 82 return nil, fmt.Errorf("failed to get capability from local file %s: %w", path, err) 83 } 84 lcaps = append(lcaps, caps...) 85 } 86 for _, lcap := range lcaps { 87 caps = append(caps, *lcap) 88 } 89 case ref.Remote != nil: 90 config, err := c.GetConfig() 91 if err != nil { 92 return nil, err 93 } 94 pd, err = packages.NewPackageDiscover(config) 95 if err != nil { 96 return nil, err 97 } 98 ref.Remote.PD = pd 99 if ref.DefinitionName == "" { 100 caps, err = LoadAllInstalledCapability("default", c) 101 if err != nil { 102 return nil, fmt.Errorf("failed to get all capabilityes: %w", err) 103 } 104 } else { 105 var rcap *types.Capability 106 if ref.Remote.Rev == 0 { 107 rcap, err = GetCapabilityByName(ctx, c, ref.DefinitionName, ref.Remote.Namespace, pd) 108 if err != nil { 109 return nil, fmt.Errorf("failed to get capability %s: %w", ref.DefinitionName, err) 110 } 111 } else { 112 rcap, err = GetCapabilityFromDefinitionRevision(ctx, c, pd, ref.Remote.Namespace, ref.DefinitionName, ref.Remote.Rev) 113 if err != nil { 114 return nil, fmt.Errorf("failed to get revision %v of capability %s: %w", ref.Remote.Rev, ref.DefinitionName, err) 115 } 116 } 117 caps = []types.Capability{*rcap} 118 } 119 default: 120 return nil, fmt.Errorf("failed to get capability %s without namespace or local filepath", ref.DefinitionName) 121 } 122 return caps, nil 123 } 124 125 func (ref *ParseReference) prettySentence(s string) string { 126 if strings.TrimSpace(s) == "" { 127 return "" 128 } 129 return ref.I18N.Get(s) + ref.I18N.Get(".") 130 } 131 func (ref *ParseReference) formatTableString(s string) string { 132 return strings.ReplaceAll(s, "|", `|`) 133 } 134 135 // prepareConsoleParameter prepares the table content for each property 136 // nolint:staticcheck 137 func (ref *ParseReference) prepareConsoleParameter(tableName string, parameterList []ReferenceParameter, category types.CapabilityCategory) ConsoleReference { 138 table := tablewriter.NewWriter(os.Stdout) 139 table.SetColWidth(100) 140 table.SetHeader([]string{ref.I18N.Get("Name"), ref.I18N.Get("Description"), ref.I18N.Get("Type"), ref.I18N.Get("Required"), ref.I18N.Get("Default")}) 141 switch category { 142 case types.CUECategory: 143 for _, p := range parameterList { 144 if !p.Ignore { 145 printableDefaultValue := ref.getCUEPrintableDefaultValue(p.Default) 146 table.Append([]string{ref.I18N.Get(p.Name), ref.prettySentence(p.Usage), ref.I18N.Get(p.PrintableType), ref.I18N.Get(strconv.FormatBool(p.Required)), ref.I18N.Get(printableDefaultValue)}) 147 } 148 } 149 case types.TerraformCategory: 150 // Terraform doesn't have default value 151 for _, p := range parameterList { 152 table.Append([]string{ref.I18N.Get(p.Name), ref.prettySentence(p.Usage), ref.I18N.Get(p.PrintableType), ref.I18N.Get(strconv.FormatBool(p.Required)), ""}) 153 } 154 default: 155 } 156 157 return ConsoleReference{TableName: tableName, TableObject: table} 158 } 159 160 func cueValue2Ident(val cue.Value) *ast.Ident { 161 var ident *ast.Ident 162 if source, ok := val.Source().(*ast.Ident); ok { 163 ident = source 164 } 165 if source, ok := val.Source().(*ast.Field); ok { 166 if v, ok := source.Value.(*ast.Ident); ok { 167 ident = v 168 } 169 } 170 return ident 171 } 172 173 func getIndentName(val cue.Value) string { 174 ident := cueValue2Ident(val) 175 if ident != nil && len(ident.Name) != 0 { 176 return strings.TrimPrefix(ident.Name, "#") 177 } 178 return val.IncompleteKind().String() 179 } 180 181 func getConcreteOrValueType(val cue.Value) string { 182 op, elements := val.Expr() 183 if op != cue.OrOp { 184 return val.IncompleteKind().String() 185 } 186 var printTypes []string 187 for _, ev := range elements { 188 incompleteKind := ev.IncompleteKind().String() 189 if !ev.IsConcrete() { 190 return incompleteKind 191 } 192 ident := cueValue2Ident(ev) 193 if ident != nil && len(ident.Name) != 0 { 194 printTypes = append(printTypes, strings.TrimPrefix(ident.Name, "#")) 195 } else { 196 // only convert string in `or` operator for now 197 opName, err := ev.String() 198 if err != nil { 199 return incompleteKind 200 } 201 opName = `"` + opName + `"` 202 printTypes = append(printTypes, opName) 203 } 204 } 205 return strings.Join(printTypes, " or ") 206 } 207 208 func getSuffix(capName string, containSuffix bool) (string, string) { 209 var suffixTitle = " (" + capName + ")" 210 var suffixRef = "-" + strings.ToLower(capName) 211 if !containSuffix || capName == "" { 212 suffixTitle = "" 213 suffixRef = "" 214 } 215 return suffixTitle, suffixRef 216 } 217 218 // parseParameters parses every parameter to docs 219 // TODO(wonderflow): refactor the code to reduce the complexity 220 // nolint:staticcheck,gocyclo 221 func (ref *ParseReference) parseParameters(capName string, paraValue cue.Value, paramKey string, depth int, containSuffix bool) (string, []ConsoleReference, error) { 222 var doc string 223 var console []ConsoleReference 224 var params []ReferenceParameter 225 226 if !paraValue.Exists() { 227 return "", console, nil 228 } 229 suffixTitle, suffixRef := getSuffix(capName, containSuffix) 230 231 switch paraValue.Kind() { 232 case cue.StructKind: 233 arguments, err := paraValue.Struct() 234 if err != nil { 235 return "", nil, fmt.Errorf("field %s not defined as struct %w", paramKey, err) 236 } 237 238 if arguments.Len() == 0 { 239 var param ReferenceParameter 240 param.Name = "\\-" 241 param.Required = true 242 tl := paraValue.Template() 243 if tl != nil { // is map type 244 param.PrintableType = fmt.Sprintf("map[string]:%s", tl("").IncompleteKind().String()) 245 } else { 246 param.PrintableType = "{}" 247 } 248 params = append(params, param) 249 } 250 251 for i := 0; i < arguments.Len(); i++ { 252 var param ReferenceParameter 253 fi := arguments.Field(i) 254 if fi.IsDefinition { 255 continue 256 } 257 val := fi.Value 258 name := fi.Selector 259 param.Name = name 260 if def, ok := val.Default(); ok && def.IsConcrete() { 261 param.Default = velacue.GetDefault(def) 262 } 263 param.Required = !fi.IsOptional && (param.Default == nil) 264 param.Short, param.Usage, param.Alias, param.Ignore = velacue.RetrieveComments(val) 265 param.Type = val.IncompleteKind() 266 switch val.IncompleteKind() { 267 case cue.StructKind: 268 if subField, err := val.Struct(); err == nil && subField.Len() == 0 { // err cannot be not nil,so ignore it 269 if mapValue, ok := val.Elem(); ok { 270 indentName := getIndentName(mapValue) 271 _, err := mapValue.Fields() 272 if err == nil { 273 subDoc, subConsole, err := ref.parseParameters(capName, mapValue, indentName, depth+1, containSuffix) 274 if err != nil { 275 return "", nil, err 276 } 277 param.PrintableType = fmt.Sprintf("map[string]%s(#%s%s)", indentName, strings.ToLower(indentName), suffixRef) 278 doc += subDoc 279 console = append(console, subConsole...) 280 } else { 281 param.PrintableType = "map[string]" + mapValue.IncompleteKind().String() 282 } 283 } else { 284 param.PrintableType = val.IncompleteKind().String() 285 } 286 } else { 287 op, elements := val.Expr() 288 if op == cue.OrOp { 289 var printTypes []string 290 for idx, ev := range elements { 291 opName := getIndentName(ev) 292 if opName == "struct" { 293 opName = fmt.Sprintf("type-option-%d", idx+1) 294 } 295 subDoc, subConsole, err := ref.parseParameters(capName, ev, opName, depth+1, containSuffix) 296 if err != nil { 297 return "", nil, err 298 } 299 printTypes = append(printTypes, fmt.Sprintf("[%s](#%s%s)", opName, strings.ToLower(opName), suffixRef)) 300 doc += subDoc 301 console = append(console, subConsole...) 302 } 303 param.PrintableType = strings.Join(printTypes, " or ") 304 } else { 305 subDoc, subConsole, err := ref.parseParameters(capName, val, name, depth+1, containSuffix) 306 if err != nil { 307 return "", nil, err 308 } 309 param.PrintableType = fmt.Sprintf("[%s](#%s%s)", name, strings.ToLower(name), suffixRef) 310 doc += subDoc 311 console = append(console, subConsole...) 312 } 313 } 314 case cue.ListKind: 315 elem := val.LookupPath(cue.MakePath(cue.AnyIndex)) 316 if !elem.Exists() { 317 // fail to get elements, use the value of ListKind to be the type 318 param.Type = val.Kind() 319 param.PrintableType = val.IncompleteKind().String() 320 break 321 } 322 switch elem.Kind() { 323 case cue.StructKind: 324 param.PrintableType = fmt.Sprintf("[[]%s](#%s%s)", name, strings.ToLower(name), suffixRef) 325 subDoc, subConsole, err := ref.parseParameters(capName, elem, name, depth+1, containSuffix) 326 if err != nil { 327 return "", nil, err 328 } 329 doc += subDoc 330 console = append(console, subConsole...) 331 default: 332 param.Type = elem.Kind() 333 param.PrintableType = fmt.Sprintf("[]%s", elem.IncompleteKind().String()) 334 } 335 default: 336 param.PrintableType = getConcreteOrValueType(val) 337 } 338 params = append(params, param) 339 } 340 default: 341 var param ReferenceParameter 342 op, elements := paraValue.Expr() 343 if op == cue.OrOp { 344 var printTypes []string 345 for idx, ev := range elements { 346 opName := getIndentName(ev) 347 if opName == "struct" { 348 opName = fmt.Sprintf("type-option-%d", idx+1) 349 } 350 subDoc, subConsole, err := ref.parseParameters(capName, ev, opName, depth+1, containSuffix) 351 if err != nil { 352 return "", nil, err 353 } 354 printTypes = append(printTypes, fmt.Sprintf("[%s](#%s%s)", opName, strings.ToLower(opName), suffixRef)) 355 doc += subDoc 356 console = append(console, subConsole...) 357 } 358 param.PrintableType = strings.Join(printTypes, " or ") 359 } else { 360 // TODO more composition type to be handle here 361 param.Name = "--" 362 param.Usage = "Unsupported Composition Type" 363 param.PrintableType = extractTypeFromError(paraValue) 364 } 365 params = append(params, param) 366 } 367 368 switch ref.DisplayFormat { 369 case Markdown, "": 370 // markdown defines the contents that display in web 371 var tableName string 372 if paramKey != Specification { 373 length := depth + 3 374 if length >= 5 { 375 length = 5 376 } 377 tableName = fmt.Sprintf("%s %s%s", strings.Repeat("#", length), paramKey, suffixTitle) 378 } 379 mref := MarkdownReference{} 380 mref.I18N = ref.I18N 381 doc = mref.getParameterString(tableName, params, types.CUECategory) + doc 382 case Console: 383 length := depth + 1 384 if length >= 3 { 385 length = 3 386 } 387 cref := ConsoleReference{} 388 tableName := fmt.Sprintf("%s %s", strings.Repeat("#", length), paramKey) 389 console = append([]ConsoleReference{cref.prepareConsoleParameter(tableName, params, types.CUECategory)}, console...) 390 } 391 return doc, console, nil 392 } 393 394 func extractTypeFromError(paraValue cue.Value) string { 395 str, err := paraValue.String() 396 if err == nil { 397 return str 398 } 399 str = err.Error() 400 sll := strings.Split(str, "cannot use value (") 401 if len(sll) < 2 { 402 return str 403 } 404 str = sll[1] 405 sll = strings.Split(str, " (type") 406 return sll[0] 407 } 408 409 // getCUEPrintableDefaultValue converts the value in `interface{}` type to be printable 410 func (ref *ParseReference) getCUEPrintableDefaultValue(v interface{}) string { 411 if v == nil { 412 return "" 413 } 414 switch value := v.(type) { 415 case Int64Type: 416 return strconv.FormatInt(value, 10) 417 case StringType: 418 if v == "" { 419 return "empty" 420 } 421 return value 422 case BoolType: 423 return strconv.FormatBool(value) 424 } 425 return "" 426 } 427 428 // CommonReference contains parameters info of HelmCategory and KubuCategory type capability at present 429 type CommonReference struct { 430 Name string 431 Parameters []ReferenceParameter 432 Depth int 433 } 434 435 // CommonSchema is a struct contains *openapi3.Schema style parameter 436 type CommonSchema struct { 437 Name string 438 Schemas *openapi3.Schema 439 } 440 441 // GenerateTerraformCapabilityProperties generates Capability properties for Terraform ComponentDefinition 442 func (ref *ParseReference) parseTerraformCapabilityParameters(capability types.Capability) ([]ReferenceParameterTable, []ReferenceParameterTable, error) { 443 var ( 444 tables []ReferenceParameterTable 445 refParameterList []ReferenceParameter 446 writeConnectionSecretToRefReferenceParameter ReferenceParameter 447 configuration string 448 err error 449 outputsList []ReferenceParameter 450 outputsTables []ReferenceParameterTable 451 outputsTableName string 452 ) 453 outputsTableName = fmt.Sprintf("%s %s\n\n%s", strings.Repeat("#", 3), ref.I18N.Get("Outputs"), ref.I18N.Get("WriteConnectionSecretToRefIntroduction")) 454 455 writeConnectionSecretToRefReferenceParameter.Name = terraform.TerraformWriteConnectionSecretToRefName 456 writeConnectionSecretToRefReferenceParameter.PrintableType = terraform.TerraformWriteConnectionSecretToRefType 457 writeConnectionSecretToRefReferenceParameter.Required = false 458 writeConnectionSecretToRefReferenceParameter.Usage = terraform.TerraformWriteConnectionSecretToRefDescription 459 460 if capability.ConfigurationType == "remote" { 461 var publicKey *gitssh.PublicKeys 462 publicKey = nil 463 if ref.Client != nil { 464 compDefNamespacedName := k8stypes.NamespacedName{Name: capability.Name, Namespace: capability.Namespace} 465 compDef := &v1beta1.ComponentDefinition{} 466 ctx := context.Background() 467 if err := ref.Client.Get(ctx, compDefNamespacedName, compDef); err != nil { 468 return nil, nil, fmt.Errorf("failed to get git component definition: %w", err) 469 } 470 gitCredentialsSecretReference := compDef.Spec.Schematic.Terraform.GitCredentialsSecretReference 471 if gitCredentialsSecretReference != nil { 472 publicKey, err = utils.GetGitSSHPublicKey(ctx, ref.Client, gitCredentialsSecretReference) 473 if err != nil { 474 return nil, nil, fmt.Errorf("failed to get publickey git credentials secret: %w", err) 475 } 476 } 477 } 478 configuration, err = utils.GetTerraformConfigurationFromRemote(capability.Name, capability.TerraformConfiguration, capability.Path, publicKey) 479 if err != nil { 480 return nil, nil, fmt.Errorf("failed to retrieve Terraform configuration from %s: %w", capability.Name, err) 481 } 482 } else { 483 configuration = capability.TerraformConfiguration 484 } 485 486 variables, outputs, err := common.ParseTerraformVariables(configuration) 487 if err != nil { 488 return nil, nil, errors.Wrap(err, "failed to generate capability properties") 489 } 490 for _, v := range variables { 491 var refParam ReferenceParameter 492 refParam.Name = v.Name 493 refParam.PrintableType = strings.ReplaceAll(v.Type, "\n", `\n`) 494 refParam.Usage = strings.ReplaceAll(v.Description, "\n", `\n`) 495 refParam.Required = v.Required 496 refParameterList = append(refParameterList, refParam) 497 } 498 refParameterList = append(refParameterList, writeConnectionSecretToRefReferenceParameter) 499 sort.SliceStable(refParameterList, func(i, j int) bool { 500 return refParameterList[i].Name < refParameterList[j].Name 501 }) 502 503 tables = append(tables, ReferenceParameterTable{ 504 Name: "", 505 Parameters: refParameterList, 506 }) 507 508 var ( 509 writeSecretRefNameParam ReferenceParameter 510 writeSecretRefNameSpaceParam ReferenceParameter 511 ) 512 513 // prepare `## writeConnectionSecretToRef` 514 writeSecretRefNameParam.Name = "name" 515 writeSecretRefNameParam.PrintableType = "string" 516 writeSecretRefNameParam.Required = true 517 writeSecretRefNameParam.Usage = terraform.TerraformSecretNameDescription 518 519 writeSecretRefNameSpaceParam.Name = "namespace" 520 writeSecretRefNameSpaceParam.PrintableType = "string" 521 writeSecretRefNameSpaceParam.Required = false 522 writeSecretRefNameSpaceParam.Usage = terraform.TerraformSecretNamespaceDescription 523 524 writeSecretRefParameterList := []ReferenceParameter{writeSecretRefNameParam, writeSecretRefNameSpaceParam} 525 writeSecretTableName := fmt.Sprintf("%s %s", strings.Repeat("#", 4), terraform.TerraformWriteConnectionSecretToRefName) 526 527 sort.SliceStable(writeSecretRefParameterList, func(i, j int) bool { 528 return writeSecretRefParameterList[i].Name < writeSecretRefParameterList[j].Name 529 }) 530 tables = append(tables, ReferenceParameterTable{ 531 Name: writeSecretTableName, 532 Parameters: writeSecretRefParameterList, 533 }) 534 535 // outputs 536 for _, v := range outputs { 537 var refParam ReferenceParameter 538 refParam.Name = v.Name 539 refParam.Usage = v.Description 540 outputsList = append(outputsList, refParam) 541 } 542 543 sort.SliceStable(outputsList, func(i, j int) bool { 544 return outputsList[i].Name < outputsList[j].Name 545 }) 546 outputsTables = append(outputsTables, ReferenceParameterTable{ 547 Name: outputsTableName, 548 Parameters: outputsList, 549 }) 550 return tables, outputsTables, nil 551 } 552 553 // ParseLocalFiles parse the local files in directory and get name, configuration from local ComponentDefinition file 554 func ParseLocalFiles(localFilePath string, c common.Args) ([]*types.Capability, error) { 555 lcaps := make([]*types.Capability, 0) 556 if modfile.IsDirectoryPath(localFilePath) { 557 // walk the dir and get files 558 err := filepath.WalkDir(localFilePath, func(path string, info fs.DirEntry, err error) error { 559 if err != nil { 560 return err 561 } 562 if info.IsDir() { 563 return nil 564 } 565 if !strings.HasSuffix(info.Name(), ".yaml") && !strings.HasSuffix(info.Name(), ".cue") { 566 return nil 567 } 568 // FIXME: remove this temporary fix when https://github.com/cue-lang/cue/issues/2047 is fixed 569 if strings.Contains(path, "container-image") { 570 lcaps = append(lcaps, fix.CapContainerImage) 571 return nil 572 } 573 lcap, err := ParseLocalFile(path, c) 574 if err != nil { 575 return err 576 } 577 lcaps = append(lcaps, lcap) 578 return nil 579 }) 580 if err != nil { 581 return nil, err 582 } 583 } else { 584 lcap, err := ParseLocalFile(localFilePath, c) 585 if err != nil { 586 return nil, err 587 } 588 lcaps = append(lcaps, lcap) 589 } 590 return lcaps, nil 591 } 592 593 // ParseLocalFile parse the local file and get name, configuration from local ComponentDefinition file 594 func ParseLocalFile(localFilePath string, c common.Args) (*types.Capability, error) { 595 data, err := pkgUtils.ReadRemoteOrLocalPath(localFilePath, false) 596 if err != nil { 597 return nil, errors.Wrap(err, "failed to read local file or url") 598 } 599 600 if strings.HasSuffix(localFilePath, "yaml") { 601 jsonData, err := yaml.YAMLToJSON(data) 602 if err != nil { 603 return nil, errors.Wrap(err, "failed to convert yaml data into k8s valid json format") 604 } 605 var localDefinition v1beta1.ComponentDefinition 606 if err = json.Unmarshal(jsonData, &localDefinition); err != nil { 607 return nil, errors.Wrap(err, "failed to unmarshal data into componentDefinition") 608 } 609 desc := localDefinition.ObjectMeta.Annotations["definition.oam.dev/description"] 610 lcap := &types.Capability{ 611 Name: localDefinition.ObjectMeta.Name, 612 Description: desc, 613 TerraformConfiguration: localDefinition.Spec.Schematic.Terraform.Configuration, 614 ConfigurationType: localDefinition.Spec.Schematic.Terraform.Type, 615 Path: localDefinition.Spec.Schematic.Terraform.Path, 616 } 617 lcap.Type = types.TypeComponentDefinition 618 lcap.Category = types.TerraformCategory 619 return lcap, nil 620 } 621 622 // local definition for general definition in CUE format 623 def := pkgdef.Definition{Unstructured: unstructured.Unstructured{}} 624 config, err := c.GetConfig() 625 if err != nil { 626 klog.Infof("ignore kubernetes cluster, unable to get kubeconfig: %s", err.Error()) 627 } 628 629 if err = def.FromCUEString(string(data), config); err != nil { 630 return nil, errors.Wrapf(err, "failed to parse CUE for definition") 631 } 632 pd, err := c.GetPackageDiscover() 633 if err != nil { 634 klog.Warning("fail to build package discover, use local info instead", err) 635 } 636 cli, err := c.GetClient() 637 if err != nil { 638 klog.Warning("fail to build client, use local info instead", err) 639 } 640 mapper := fake.NewClientBuilder().Build().RESTMapper() 641 if cli != nil { 642 mapper = cli.RESTMapper() 643 } 644 lcap, err := ParseCapabilityFromUnstructured(mapper, pd, def.Unstructured) 645 if err != nil { 646 return nil, errors.Wrapf(err, "fail to parse definition to capability %s", def.GetName()) 647 } 648 return &lcap, nil 649 650 } 651 652 // WalkParameterSchema will extract properties from *openapi3.Schema 653 func WalkParameterSchema(parameters *openapi3.Schema, name string, depth int) { 654 if parameters == nil { 655 return 656 } 657 var schemas []CommonSchema 658 var commonParameters []ReferenceParameter 659 for k, v := range parameters.Properties { 660 p := ReferenceParameter{ 661 Parameter: types.Parameter{ 662 Name: k, 663 Default: v.Value.Default, 664 Usage: v.Value.Description, 665 JSONType: v.Value.Type, 666 }, 667 PrintableType: v.Value.Type, 668 } 669 required := false 670 for _, requiredType := range parameters.Required { 671 if k == requiredType { 672 required = true 673 break 674 } 675 } 676 p.Required = required 677 if v.Value.Type == "object" { 678 if v.Value.Properties != nil { 679 schemas = append(schemas, CommonSchema{ 680 Name: k, 681 Schemas: v.Value, 682 }) 683 } 684 p.PrintableType = fmt.Sprintf("[%s](#%s)", k, k) 685 } 686 commonParameters = append(commonParameters, p) 687 } 688 689 commonRefs = append(commonRefs, CommonReference{ 690 Name: fmt.Sprintf("%s %s", strings.Repeat("#", depth+1), name), 691 Parameters: commonParameters, 692 Depth: depth + 1, 693 }) 694 695 for _, schema := range schemas { 696 WalkParameterSchema(schema.Schemas, schema.Name, depth+1) 697 } 698 } 699 700 // GetBaseResourceKinds helps get resource.group string of components' base resource 701 func GetBaseResourceKinds(cueStr string, pd *packages.PackageDiscover, mapper meta.RESTMapper) (string, error) { 702 t, err := value.NewValue(cueStr+velacue.BaseTemplate, pd, "") 703 if err != nil { 704 return "", errors.Wrap(err, "failed to parse base template") 705 } 706 tmpl := t.CueValue() 707 708 kindValue := tmpl.LookupPath(cue.ParsePath("output.kind")) 709 kind, err := kindValue.String() 710 if err != nil { 711 return "", err 712 } 713 apiVersionValue := tmpl.LookupPath(cue.ParsePath("output.apiVersion")) 714 apiVersion, err := apiVersionValue.String() 715 if err != nil { 716 return "", err 717 } 718 GroupAndVersion := strings.Split(apiVersion, "/") 719 if len(GroupAndVersion) == 1 { 720 GroupAndVersion = append([]string{""}, GroupAndVersion...) 721 } 722 mapping, err := mapper.RESTMapping(schema.GroupKind{Group: GroupAndVersion[0], Kind: kind}, GroupAndVersion[1]) 723 gvr := mapping.Resource 724 if err != nil { 725 return "", err 726 } 727 return fmt.Sprintf("- %s.%s", gvr.Resource, gvr.Group), nil 728 }