From 63855abed9274e45d89c4b896040e87fb08e745d Mon Sep 17 00:00:00 2001 From: yangsoon Date: Fri, 23 Apr 2021 16:50:44 +0800 Subject: [PATCH] fix cli: vela show support list the parameter of ComponentDefinition which use helm charts (#1543) * vela show support helm * show helm parameter in web * add test * support link jump * add test --- apis/types/capability.go | 20 +-- e2e/plugin/plugin_suit_test.go | 6 + e2e/plugin/plugin_test.go | 30 ++++ hack/references/generate.go | 4 +- references/cli/show.go | 38 ++++- references/plugins/cluster.go | 10 +- references/plugins/cluster_test.go | 2 + references/plugins/reference_test.go | 205 ++++++++++++++++++++++++- references/plugins/references.go | 214 +++++++++++++++++++++++---- 9 files changed, 480 insertions(+), 49 deletions(-) diff --git a/apis/types/capability.go b/apis/types/capability.go index a43f0798f..ba7f507b0 100644 --- a/apis/types/capability.go +++ b/apis/types/capability.go @@ -38,15 +38,16 @@ type CRDInfo struct { // Capability defines the content of a capability type Capability struct { - Name string `json:"name"` - Type CapType `json:"type"` - CueTemplate string `json:"template,omitempty"` - CueTemplateURI string `json:"templateURI,omitempty"` - Parameters []Parameter `json:"parameters,omitempty"` - CrdName string `json:"crdName,omitempty"` - Center string `json:"center,omitempty"` - Status string `json:"status,omitempty"` - Description string `json:"description,omitempty"` + Name string `json:"name"` + Type CapType `json:"type"` + CueTemplate string `json:"template,omitempty"` + CueTemplateURI string `json:"templateURI,omitempty"` + Parameters []Parameter `json:"parameters,omitempty"` + CrdName string `json:"crdName,omitempty"` + Center string `json:"center,omitempty"` + Status string `json:"status,omitempty"` + Description string `json:"description,omitempty"` + Category CapabilityCategory `json:"category,omitempty"` // trait only AppliesTo []string `json:"appliesTo,omitempty"` @@ -121,6 +122,7 @@ type Parameter struct { Usage string `json:"usage,omitempty"` Type cue.Kind `json:"type,omitempty"` Alias string `json:"alias,omitempty"` + JSONType string `json:"jsonType,omitempty"` } // SetFlagBy set cli flag from Parameter diff --git a/e2e/plugin/plugin_suit_test.go b/e2e/plugin/plugin_suit_test.go index 1d5213cd6..cfc3b811f 100644 --- a/e2e/plugin/plugin_suit_test.go +++ b/e2e/plugin/plugin_suit_test.go @@ -43,6 +43,7 @@ var app v1beta1.Application var testShowCdDef v1beta1.ComponentDefinition var testShowTdDef v1beta1.TraitDefinition var testCdDef v1beta1.ComponentDefinition +var testCdDefWithHelm v1beta1.ComponentDefinition var testTdDef v1beta1.TraitDefinition func TestKubectlPlugin(t *testing.T) { @@ -75,6 +76,10 @@ var _ = BeforeSuite(func(done Done) { err = k8sClient.Create(ctx, &testCdDef) Expect(err).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{})) + Expect(yaml.Unmarshal([]byte(componentDefWithHelm), &testCdDefWithHelm)).Should(BeNil()) + err = k8sClient.Create(ctx, &testCdDefWithHelm) + Expect(err).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{})) + Expect(yaml.Unmarshal([]byte(traitDef), &testTdDef)).Should(BeNil()) err = k8sClient.Create(ctx, &testTdDef) Expect(err).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{})) @@ -99,6 +104,7 @@ var _ = AfterSuite(func() { By("delete application and definitions") Expect(k8sClient.Delete(ctx, &app)).Should(BeNil()) Expect(k8sClient.Delete(ctx, &testCdDef)).Should(BeNil()) + Expect(k8sClient.Delete(ctx, &testCdDefWithHelm)).Should(BeNil()) Expect(k8sClient.Delete(ctx, &testTdDef)).Should(BeNil()) Expect(k8sClient.Delete(ctx, &testShowCdDef)).Should(BeNil()) Expect(k8sClient.Delete(ctx, &testShowTdDef)).Should(BeNil()) diff --git a/e2e/plugin/plugin_test.go b/e2e/plugin/plugin_test.go index baf8c5e86..7e5c31395 100644 --- a/e2e/plugin/plugin_test.go +++ b/e2e/plugin/plugin_test.go @@ -117,6 +117,12 @@ var _ = Describe("Test Kubectl Plugin", func() { Expect(err).NotTo(HaveOccurred()) Expect(output).Should(Equal(showTdResult)) }) + It("Test show componentDefinition use Helm Charts as Workload", func() { + cdName := "test-webapp-chart" + output, err := e2e.Exec(fmt.Sprintf("kubectl-vela show %s", cdName)) + Expect(err).NotTo(HaveOccurred()) + Expect(output).Should(ContainSubstring("Properties")) + }) }) }) @@ -344,6 +350,30 @@ spec: ` +var componentDefWithHelm = ` +apiVersion: core.oam.dev/v1beta1 +kind: ComponentDefinition +metadata: + name: test-webapp-chart + namespace: default + annotations: + definition.oam.dev/description: helm chart for webapp +spec: + workload: + definition: + apiVersion: apps/v1 + kind: Deployment + schematic: + helm: + release: + chart: + spec: + chart: "podinfo" + version: "5.1.4" + repository: + url: "http://oam.dev/catalog/" +` + var traitDef = ` apiVersion: core.oam.dev/v1beta1 kind: TraitDefinition diff --git a/hack/references/generate.go b/hack/references/generate.go index dab404817..72b26cfe0 100644 --- a/hack/references/generate.go +++ b/hack/references/generate.go @@ -17,6 +17,7 @@ limitations under the License. package main import ( + "context" "fmt" "os" @@ -25,7 +26,8 @@ import ( func main() { ref := &plugins.MarkdownReference{} - if err := ref.GenerateReferenceDocs(plugins.BaseRefPath); err != nil { + ctx := context.Background() + if err := ref.GenerateReferenceDocs(ctx, plugins.BaseRefPath); err != nil { fmt.Println(err) os.Exit(1) } diff --git a/references/cli/show.go b/references/cli/show.go index f078664bb..6d0bb8c96 100644 --- a/references/cli/show.go +++ b/references/cli/show.go @@ -132,8 +132,18 @@ func startReferenceDocsSite(ctx context.Context, c common.Args, ioStreams cmduti if !capabilityIsValid { return fmt.Errorf("%s is not a valid component type or trait", capabilityName) } - ref := &plugins.MarkdownReference{} - if err := ref.CreateMarkdown(capabilities, docsPath, plugins.ReferenceSourcePath); err != nil { + + cli, err := c.GetClient() + if err != nil { + return err + } + ref := &plugins.MarkdownReference{ + ParseReference: plugins.ParseReference{ + Client: cli, + }, + } + + if err := ref.CreateMarkdown(ctx, capabilities, docsPath, plugins.ReferenceSourcePath); err != nil { return err } @@ -345,11 +355,31 @@ func ShowReferenceConsole(ctx context.Context, c common.Args, ioStreams cmdutil. return err } - ref := &plugins.ConsoleReference{} - propertyConsole, err := ref.GenerateCapabilityProperties(capability) + cli, err := c.GetClient() if err != nil { return err } + ref := &plugins.ConsoleReference{ + ParseReference: plugins.ParseReference{ + Client: cli, + }, + } + + var propertyConsole []plugins.ConsoleReference + switch capability.Category { + case types.HelmCategory: + _, propertyConsole, err = ref.GenerateHELMProperties(ctx, capability) + if err != nil { + return err + } + case types.CUECategory: + propertyConsole, err = ref.GenerateCUETemplateProperties(capability) + if err != nil { + return err + } + default: + return fmt.Errorf("unsupport capability category %s", capability.Category) + } for _, p := range propertyConsole { ioStreams.Info(p.TableName) p.TableObject.Render() diff --git a/references/plugins/cluster.go b/references/plugins/cluster.go index 0a3de45f9..391f476d6 100644 --- a/references/plugins/cluster.go +++ b/references/plugins/cluster.go @@ -205,15 +205,17 @@ func HandleTemplate(in *runtime.RawExtension, schematic *commontypes.Schematic, tmp.CueTemplate = string(b) } if tmp.CueTemplate == "" { + if schematic != nil && schematic.HELM != nil { + tmp.Category = types.HelmCategory + return tmp, nil + } return types.Capability{}, errors.New("template not exist in definition") } - if err != nil { - return types.Capability{}, err - } tmp.Parameters, err = cue.GetParameters(tmp.CueTemplate) if err != nil { return types.Capability{}, err } + tmp.Category = types.CUECategory return tmp, nil } @@ -278,6 +280,7 @@ func SyncDefinitionToLocal(ctx context.Context, c common.Args, capabilityName st template, err := HandleDefinition(capabilityName, ref.Name, componentDef.Annotations, componentDef.Spec.Extension, types.TypeComponentDefinition, nil, componentDef.Spec.Schematic) if err == nil { + template.Namespace = componentDef.Namespace return &template, nil } } @@ -297,6 +300,7 @@ func SyncDefinitionToLocal(ctx context.Context, c common.Args, capabilityName st template, err := HandleDefinition(capabilityName, traitDef.Spec.Reference.Name, traitDef.Annotations, traitDef.Spec.Extension, types.TypeTrait, nil, traitDef.Spec.Schematic) if err == nil { + template.Namespace = traitDef.Namespace return &template, nil } } diff --git a/references/plugins/cluster_test.go b/references/plugins/cluster_test.go index fff00c670..8fd1e174f 100644 --- a/references/plugins/cluster_test.go +++ b/references/plugins/cluster_test.go @@ -48,6 +48,7 @@ var _ = Describe("DefinitionFiles", func() { Type: types.TypeComponentDefinition, CrdName: "deployments.apps", Description: "description not defined", + Category: types.CUECategory, Parameters: []types.Parameter{ { Type: cue.ListKind, @@ -80,6 +81,7 @@ var _ = Describe("DefinitionFiles", func() { Name: WebserviceName, Type: types.TypeComponentDefinition, Description: "description not defined", + Category: types.CUECategory, Parameters: []types.Parameter{{ Name: "env", Type: cue.ListKind, }, { diff --git a/references/plugins/reference_test.go b/references/plugins/reference_test.go index d33557fcb..c84f65d48 100644 --- a/references/plugins/reference_test.go +++ b/references/plugins/reference_test.go @@ -17,12 +17,16 @@ limitations under the License. package plugins import ( + "context" + "encoding/json" "fmt" "os" "path/filepath" + "reflect" "testing" "github.com/crossplane/crossplane-runtime/pkg/test" + "github.com/getkin/kin-openapi/openapi3" "github.com/google/go-cmp/cmp" "github.com/stretchr/testify/assert" @@ -68,11 +72,13 @@ parameter: { Name: workloadName, Type: types.TypeWorkload, CueTemplate: workloadCueTemplate, + Category: types.CUECategory, }, { Name: traitName, Type: types.TypeTrait, CueTemplate: traitCueTemplate, + Category: types.CUECategory, }, }, want: nil, @@ -89,9 +95,10 @@ parameter: { }, } ref := &MarkdownReference{} + ctx := context.Background() for name, tc := range cases { t.Run(name, func(t *testing.T) { - got := ref.CreateMarkdown(tc.capabilities, RefTestDir, ReferenceSourcePath) + got := ref.CreateMarkdown(ctx, tc.capabilities, RefTestDir, ReferenceSourcePath) if diff := cmp.Diff(tc.want, got, test.EquateErrors()); diff != "" { t.Errorf("\n%s\nCreateMakrdown(...): -want error, +got error:\n%s", tc.reason, diff) } @@ -113,7 +120,7 @@ func TestPrepareParameterTable(t *testing.T) { parameterName := "cpu" parameterList[0].Name = parameterName parameterList[0].Required = true - refContent := ref.prepareParameter(tableName, parameterList) + refContent := ref.prepareParameter(tableName, parameterList, types.CUECategory) assert.Contains(t, refContent, parameterName) assert.Contains(t, refContent, "cpu") } @@ -124,3 +131,197 @@ func TestDeleteRefTestDir(t *testing.T) { assert.NoError(t, err) } } + +func TestWalkParameterSchema(t *testing.T) { + testcases := []struct { + data string + ExpectRefs map[string]map[string]ReferenceParameter + }{ + { + data: `{ + "properties": { + "cmd": { + "description": "Commands to run in the container", + "items": { + "type": "string" + }, + "title": "cmd", + "type": "array" + }, + "image": { + "description": "Which image would you like to use for your service", + "title": "image", + "type": "string" + } + }, + "required": [ + "image" + ], + "type": "object" +}`, + ExpectRefs: map[string]map[string]ReferenceParameter{ + "# Properties": { + "cmd": ReferenceParameter{ + Parameter: types.Parameter{ + Name: "cmd", + Usage: "Commands to run in the container", + JSONType: "array", + }, + PrintableType: "array", + }, + "image": ReferenceParameter{ + Parameter: types.Parameter{ + Name: "image", + Required: true, + Usage: "Which image would you like to use for your service", + JSONType: "string", + }, + PrintableType: "string", + }, + }, + }, + }, + { + data: `{ + "properties": { + "obj": { + "properties": { + "f0": { + "default": "v0", + "type": "string" + }, + "f1": { + "default": "v1", + "type": "string" + }, + "f2": { + "default": "v2", + "type": "string" + } + }, + "type": "object" + }, + }, + "type": "object" +}`, + ExpectRefs: map[string]map[string]ReferenceParameter{ + "# Properties": { + "obj": ReferenceParameter{ + Parameter: types.Parameter{ + Name: "obj", + JSONType: "object", + }, + PrintableType: "[obj](#obj)", + }, + }, + "## obj": { + "f0": ReferenceParameter{ + Parameter: types.Parameter{ + Name: "f0", + Default: "v0", + JSONType: "string", + }, + PrintableType: "string", + }, + "f1": ReferenceParameter{ + Parameter: types.Parameter{ + Name: "f1", + Default: "v1", + JSONType: "string", + }, + PrintableType: "string", + }, + "f2": ReferenceParameter{ + Parameter: types.Parameter{ + Name: "f2", + Default: "v2", + JSONType: "string", + }, + PrintableType: "string", + }, + }, + }, + }, + { + data: `{ + "properties": { + "obj": { + "properties": { + "f0": { + "default": "v0", + "type": "string" + }, + "f1": { + "default": "v1", + "type": "object", + "properties": { + "g0": { + "default": "v2", + "type": "string" + } + } + } + }, + "type": "object" + } + }, + "type": "object" +}`, + ExpectRefs: map[string]map[string]ReferenceParameter{ + "# Properties": { + "obj": ReferenceParameter{ + Parameter: types.Parameter{ + Name: "obj", + JSONType: "object", + }, + PrintableType: "[obj](#obj)", + }, + }, + "## obj": { + "f0": ReferenceParameter{ + Parameter: types.Parameter{ + Name: "f0", + Default: "v0", + JSONType: "string", + }, + PrintableType: "string", + }, + "f1": ReferenceParameter{ + Parameter: types.Parameter{ + Name: "f1", + Default: "v1", + JSONType: "object", + }, + PrintableType: "[f1](#f1)", + }, + }, + "### f1": { + "g0": ReferenceParameter{ + Parameter: types.Parameter{ + Name: "g0", + Default: "v2", + JSONType: "string", + }, + PrintableType: "string", + }, + }, + }, + }, + } + for _, cases := range testcases { + helmRefs = make([]HELMReference, 0) + parameterJSON := fmt.Sprintf(BaseOpenAPIV3Template, cases.data) + swagger, err := openapi3.NewSwaggerLoader().LoadSwaggerFromData(json.RawMessage(parameterJSON)) + assert.Equal(t, nil, err) + parameters := swagger.Components.Schemas["parameter"].Value + WalkParameterSchema(parameters, "Properties", 0) + refs := make(map[string]map[string]ReferenceParameter) + for _, items := range helmRefs { + refs[items.Name] = make(map[string]ReferenceParameter) + for _, item := range items.Parameters { + refs[items.Name][item.Name] = item + } + } + assert.Equal(t, true, reflect.DeepEqual(cases.ExpectRefs, refs)) + } +} diff --git a/references/plugins/references.go b/references/plugins/references.go index 05a1b3af4..fc62449eb 100644 --- a/references/plugins/references.go +++ b/references/plugins/references.go @@ -17,6 +17,8 @@ limitations under the License. package plugins import ( + "context" + "encoding/json" "fmt" "io/ioutil" "os" @@ -25,7 +27,11 @@ import ( "strings" "cuelang.org/go/cue" + "github.com/getkin/kin-openapi/openapi3" "github.com/olekukonko/tablewriter" + "github.com/pkg/errors" + v1 "k8s.io/api/core/v1" + "sigs.k8s.io/controller-runtime/pkg/client" "github.com/oam-dev/kubevela/apis/types" mycue "github.com/oam-dev/kubevela/pkg/cue" @@ -62,6 +68,7 @@ type Reference interface { // ParseReference is used to include the common function `parseParameter` type ParseReference struct { + Client client.Client } // MarkdownReference is the struct for capability information in @@ -181,6 +188,21 @@ services: `, } +// BaseOpenAPIV3Template is Standard OpenAPIV3 Template +var BaseOpenAPIV3Template = `{ + "openapi": "3.0.0", + "info": { + "title": "definition-parameter", + "version": "1.0" + }, + "paths": {}, + "components": { + "schemas": { + "parameter": %s + } + } +}` + // ReferenceParameter is the parameter section of CUE template type ReferenceParameter struct { types.Parameter `json:",inline,omitempty"` @@ -194,13 +216,14 @@ var refContent string var recurseDepth *int var propertyConsole []ConsoleReference var displayFormat *string +var helmRefs []HELMReference func setDisplayFormat(format string) { displayFormat = &format } // GenerateReferenceDocs generates reference docs -func (ref *MarkdownReference) GenerateReferenceDocs(baseRefPath string) error { +func (ref *MarkdownReference) GenerateReferenceDocs(ctx context.Context, baseRefPath string) error { c, err := common.InitBaseRestConfig() if err != nil { return err @@ -212,15 +235,15 @@ func (ref *MarkdownReference) GenerateReferenceDocs(baseRefPath string) error { if baseRefPath == "" { baseRefPath = BaseRefPath } - return ref.CreateMarkdown(caps, baseRefPath, ReferenceSourcePath) + return ref.CreateMarkdown(ctx, caps, baseRefPath, ReferenceSourcePath) } // CreateMarkdown creates markdown based on capabilities -func (ref *MarkdownReference) CreateMarkdown(caps []types.Capability, baseRefPath, referenceSourcePath string) error { +func (ref *MarkdownReference) CreateMarkdown(ctx context.Context, caps []types.Capability, baseRefPath, referenceSourcePath string) error { setDisplayFormat("markdown") var capabilityType string var specificationType string - for _, c := range caps { + for i, c := range caps { switch c.Type { case types.TypeWorkload: capabilityType = WorkloadTypePath @@ -253,25 +276,35 @@ func (ref *MarkdownReference) CreateMarkdown(caps []types.Capability, baseRefPat return fmt.Errorf("failed to truncate file %s: %w", markdownFile, err) } capName := c.Name - - cueValue, err := common.GetCUEParameterValue(c.CueTemplate) - if err != nil { - return fmt.Errorf("failed to retrieve `parameters` value from %s with err: %w", c.Name, err) - } refContent = "" - var defaultDepth = 0 - recurseDepth = &defaultDepth capNameInTitle := strings.Title(capName) - if err := ref.parseParameters(cueValue, "Properties", defaultDepth); err != nil { - return err + switch c.Category { + case types.CUECategory: + cueValue, err := common.GetCUEParameterValue(c.CueTemplate) + if err != nil { + return fmt.Errorf("failed to retrieve `parameters` value from %s with err: %w", c.Name, err) + } + var defaultDepth = 0 + recurseDepth = &defaultDepth + if err := ref.parseParameters(cueValue, "Properties", defaultDepth); err != nil { + return err + } + case types.HelmCategory: + properties, _, err := ref.GenerateHELMProperties(ctx, &caps[i]) + if err != nil { + return fmt.Errorf("failed to retrieve `parameters` value from %s with err: %w", c.Name, err) + } + for _, property := range properties { + refContent += ref.prepareParameter("#"+property.Name, property.Parameters, types.HelmCategory) + } + default: + return fmt.Errorf("unsupport capability category %s", c.Category) } + title := fmt.Sprintf("# %s", capNameInTitle) description := fmt.Sprintf("\n\n## Description\n\n%s", c.Description) specificationIntro := fmt.Sprintf("List of all configuration options for a `%s` %s.", capNameInTitle, specificationType) specificationContent := ref.generateSpecification(capName) - if err != nil { - return err - } specification := fmt.Sprintf("\n\n## Specification\n\n%s\n\n%s", specificationIntro, specificationContent) // it's fine if the conflict info files not found @@ -289,26 +322,45 @@ func (ref *MarkdownReference) CreateMarkdown(caps []types.Capability, baseRefPat } // prepareParameter prepares the table content for each property -func (ref *MarkdownReference) prepareParameter(tableName string, parameterList []ReferenceParameter) string { +func (ref *MarkdownReference) prepareParameter(tableName string, parameterList []ReferenceParameter, category types.CapabilityCategory) string { refContent := fmt.Sprintf("\n\n%s\n\n", tableName) refContent += "Name | Description | Type | Required | Default \n" refContent += "------------ | ------------- | ------------- | ------------- | ------------- \n" - for _, p := range parameterList { - printableDefaultValue := ref.getPrintableDefaultValue(p.Default) - refContent += fmt.Sprintf(" %s | %s | %s | %t | %s \n", p.Name, p.Usage, p.PrintableType, p.Required, printableDefaultValue) + switch category { + case types.CUECategory: + for _, p := range parameterList { + printableDefaultValue := ref.getCUEPrintableDefaultValue(p.Default) + refContent += fmt.Sprintf(" %s | %s | %s | %t | %s \n", p.Name, p.Usage, p.PrintableType, p.Required, printableDefaultValue) + } + case types.HelmCategory: + for _, p := range parameterList { + printableDefaultValue := ref.getHELMPrintableDefaultValue(p.JSONType, p.Default) + refContent += fmt.Sprintf(" %s | %s | %s | %t | %s \n", p.Name, strings.ReplaceAll(p.Usage, "\n", ""), p.PrintableType, p.Required, printableDefaultValue) + } + default: } return refContent } // prepareParameter prepares the table content for each property -func (ref *ConsoleReference) prepareParameter(tableName string, parameterList []ReferenceParameter) ConsoleReference { +func (ref *ParseReference) prepareParameter(tableName string, parameterList []ReferenceParameter, category types.CapabilityCategory) ConsoleReference { table := tablewriter.NewWriter(os.Stdout) table.SetColWidth(100) table.SetHeader([]string{"Name", "Description", "Type", "Required", "Default"}) - for _, p := range parameterList { - printableDefaultValue := ref.getPrintableDefaultValue(p.Default) - table.Append([]string{p.Name, p.Usage, p.PrintableType, strconv.FormatBool(p.Required), printableDefaultValue}) + switch category { + case types.CUECategory: + for _, p := range parameterList { + printableDefaultValue := ref.getCUEPrintableDefaultValue(p.Default) + table.Append([]string{p.Name, p.Usage, p.PrintableType, strconv.FormatBool(p.Required), printableDefaultValue}) + } + case types.HelmCategory: + for _, p := range parameterList { + printableDefaultValue := ref.getHELMPrintableDefaultValue(p.JSONType, p.Default) + table.Append([]string{p.Name, p.Usage, p.PrintableType, strconv.FormatBool(p.Required), printableDefaultValue}) + } + default: } + return ConsoleReference{TableName: tableName, TableObject: table} } @@ -382,18 +434,18 @@ func (ref *ParseReference) parseParameters(paraValue cue.Value, paramKey string, case "markdown": tableName := fmt.Sprintf("%s %s", strings.Repeat("#", depth+2), paramKey) ref := MarkdownReference{} - refContent = ref.prepareParameter(tableName, params) + refContent + refContent = ref.prepareParameter(tableName, params, types.CUECategory) + refContent case "console": ref := ConsoleReference{} tableName := fmt.Sprintf("%s %s", strings.Repeat("#", depth+1), paramKey) - console := ref.prepareParameter(tableName, params) + console := ref.prepareParameter(tableName, params, types.CUECategory) propertyConsole = append([]ConsoleReference{console}, propertyConsole...) } return nil } -// getPrintableDefaultValue converts the value in `interface{}` type to be printable -func (ref *ParseReference) getPrintableDefaultValue(v interface{}) string { +// getCUEPrintableDefaultValue converts the value in `interface{}` type to be printable +func (ref *ParseReference) getCUEPrintableDefaultValue(v interface{}) string { if v == nil { return "" } @@ -411,6 +463,20 @@ func (ref *ParseReference) getPrintableDefaultValue(v interface{}) string { return "" } +func (ref *ParseReference) getHELMPrintableDefaultValue(dataType string, value interface{}) string { + if value != nil { + return strings.TrimSpace(fmt.Sprintf("%v", value)) + } + defaultValueMap := map[string]string{ + "number": "0", + "boolean": "false", + "string": "\"\"", + "object": "{}", + "array": "[]", + } + return defaultValueMap[dataType] +} + // generateSpecification generates Specification part for reference docs func (ref *MarkdownReference) generateSpecification(capabilityName string) string { return fmt.Sprintf("```yaml%s```", ConfigurationYamlSample[capabilityName]) @@ -429,8 +495,8 @@ func (ref *MarkdownReference) generateConflictWithAndMore(capabilityName string, return "\n" + string(data), nil } -// GenerateCapabilityProperties get all properties of a capability -func (ref *ConsoleReference) GenerateCapabilityProperties(capability *types.Capability) ([]ConsoleReference, error) { +// GenerateCUETemplateProperties get all properties of a capability +func (ref *ConsoleReference) GenerateCUETemplateProperties(capability *types.Capability) ([]ConsoleReference, error) { setDisplayFormat("console") capName := capability.Name @@ -446,3 +512,91 @@ func (ref *ConsoleReference) GenerateCapabilityProperties(capability *types.Capa return propertyConsole, nil } + +// HELMReference contains parameters info of HelmCategory type capability +type HELMReference struct { + Name string + Parameters []ReferenceParameter + Depth int +} + +// HELMSchema is a struct contains *openapi3.Schema style parameter +type HELMSchema struct { + Name string + Schemas *openapi3.Schema +} + +// GenerateHELMProperties get all properties of a HelmCategory type capability +func (ref *ParseReference) GenerateHELMProperties(ctx context.Context, capability *types.Capability) ([]HELMReference, []ConsoleReference, error) { + cmName := fmt.Sprintf("%s%s", types.CapabilityConfigMapNamePrefix, capability.Name) + var cm v1.ConfigMap + helmRefs = make([]HELMReference, 0) + if err := ref.Client.Get(ctx, client.ObjectKey{Namespace: capability.Namespace, Name: cmName}, &cm); err != nil { + return nil, nil, err + } + data, ok := cm.Data[types.OpenapiV3JSONSchema] + if !ok { + return nil, nil, errors.Errorf("configMap doesn't have openapi-v3-json-schema data") + } + parameterJSON := fmt.Sprintf(BaseOpenAPIV3Template, data) + swagger, err := openapi3.NewSwaggerLoader().LoadSwaggerFromData(json.RawMessage(parameterJSON)) + if err != nil { + return nil, nil, err + } + parameters := swagger.Components.Schemas["parameter"].Value + WalkParameterSchema(parameters, "Properties", 0) + + var consoleRefs []ConsoleReference + for _, item := range helmRefs { + consoleRefs = append(consoleRefs, ref.prepareParameter(item.Name, item.Parameters, types.HelmCategory)) + } + return helmRefs, consoleRefs, err +} + +// WalkParameterSchema will extract properties from *openapi3.Schema +func WalkParameterSchema(parameters *openapi3.Schema, name string, depth int) { + if parameters == nil { + return + } + var schemas []HELMSchema + var helmParameters []ReferenceParameter + for k, v := range parameters.Properties { + p := ReferenceParameter{ + Parameter: types.Parameter{ + Name: k, + Default: v.Value.Default, + Usage: v.Value.Description, + JSONType: v.Value.Type, + }, + PrintableType: v.Value.Type, + } + required := false + for _, requiredType := range parameters.Required { + if k == requiredType { + required = true + break + } + } + p.Required = required + if v.Value.Type == "object" { + if v.Value.Properties != nil { + schemas = append(schemas, HELMSchema{ + Name: k, + Schemas: v.Value, + }) + } + p.PrintableType = fmt.Sprintf("[%s](#%s)", k, k) + } + helmParameters = append(helmParameters, p) + } + + helmRefs = append(helmRefs, HELMReference{ + Name: fmt.Sprintf("%s %s", strings.Repeat("#", depth+1), name), + Parameters: helmParameters, + Depth: depth + 1, + }) + + for _, schema := range schemas { + WalkParameterSchema(schema.Schemas, schema.Name, depth+1) + } +}