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  }