Feat: generate docs for reference automatically (#4377)

Signed-off-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>

Feat: refactor hardcode example to embd.FS

Signed-off-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>

Fix: refactor doc gen for general types

Signed-off-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>

Fix: update generate format

Signed-off-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>

Fix: generate terraform reference docs

Signed-off-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>

Feat: add definition reference generate script

Signed-off-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>

Fix: refine output format

Signed-off-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>

Fix: remove dup annotation

Signed-off-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>

Fix: update doc

Signed-off-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>

Fix: add i18n support

Signed-off-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>

Feat: add translation

Signed-off-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>

Feat: add policy definition gen

Signed-off-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>

Fix: add compatibility for lable Annotation change

Signed-off-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>

Fix: add more tests

Signed-off-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>

Feat: allow mark example doc url on annotation

Signed-off-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>

Fix: align vela show with vela def doc-gen, add vela def show equals with vela show

Signed-off-by: Jianbo Sun <jianbo.sjb@alibaba-inc.com>
This commit is contained in:
Jianbo Sun
2022-07-18 19:22:55 +08:00
committed by GitHub
parent a519a6c89d
commit b24e7523d8
94 changed files with 3755 additions and 1943 deletions

1
.gitignore vendored
View File

@@ -51,3 +51,4 @@ git-page/
# e2e rollout runtime image build
runtime/rollout/e2e/tmp
vela.json

View File

@@ -14,7 +14,7 @@ test: vet lint staticcheck unit-test-core test-cli-gen
test-cli-gen:
mkdir -p ./bin/doc
go run ./hack/docgen/gen.go ./bin/doc
go run ./hack/docgen/cli/gen.go ./bin/doc
unit-test-core:
go test -coverprofile=coverage.txt $(shell go list ./pkg/... ./cmd/... ./apis/... | grep -v apiserver | grep -v applicationconfiguration)
go test $(shell go list ./references/... | grep -v apiserver)

View File

@@ -165,6 +165,7 @@ type Capability struct {
Center string `json:"center,omitempty"`
Status string `json:"status,omitempty"`
Description string `json:"description,omitempty"`
Example string `json:"example,omitempty"`
Labels map[string]string `json:"labels,omitempty"`
Category CapabilityCategory `json:"category,omitempty"`

View File

@@ -48,6 +48,8 @@ var DefaultKubeVelaNS = "vela-system"
const (
// AnnoDefinitionDescription is the annotation which describe what is the capability used for in a WorkloadDefinition/TraitDefinition Object
AnnoDefinitionDescription = "definition.oam.dev/description"
// AnnoDefinitionExampleURL is the annotation which describe url of usage examples of the capability, it will be loaded in documentation generate.
AnnoDefinitionExampleURL = "definition.oam.dev/example-url"
// AnnoDefinitionAlias is the annotation for definition alias
AnnoDefinitionAlias = "definition.oam.dev/alias"
// AnnoDefinitionIcon is the annotation which describe the icon url

View File

@@ -4,13 +4,13 @@ apiVersion: core.oam.dev/v1beta1
kind: ComponentDefinition
metadata:
annotations:
custom.definition.oam.dev/alias.config.oam.dev: Image Registry
alias.config.oam.dev: Image Registry
definition.oam.dev/description: Config information to authenticate image registry
labels:
custom.definition.oam.dev/catalog.config.oam.dev: velacore-config
custom.definition.oam.dev/multi-cluster.config.oam.dev: "true"
custom.definition.oam.dev/type.config.oam.dev: image-registry
catalog.config.oam.dev: velacore-config
custom.definition.oam.dev/ui-hidden: "true"
multi-cluster.config.oam.dev: "true"
type.config.oam.dev: image-registry
name: config-image-registry
namespace: {{ include "systemDefinitionNamespace" . }}
spec:

View File

@@ -4,13 +4,13 @@ apiVersion: core.oam.dev/v1beta1
kind: ComponentDefinition
metadata:
annotations:
custom.definition.oam.dev/alias.config.oam.dev: Image Registry
alias.config.oam.dev: Image Registry
definition.oam.dev/description: Config information to authenticate image registry
labels:
custom.definition.oam.dev/catalog.config.oam.dev: velacore-config
custom.definition.oam.dev/multi-cluster.config.oam.dev: "true"
custom.definition.oam.dev/type.config.oam.dev: image-registry
catalog.config.oam.dev: velacore-config
custom.definition.oam.dev/ui-hidden: "true"
multi-cluster.config.oam.dev: "true"
type.config.oam.dev: image-registry
name: config-image-registry
namespace: {{ include "systemDefinitionNamespace" . }}
spec:

View File

@@ -2,9 +2,9 @@
## General
- list all configuration types
- list all configuration types. Note before vela v1.5, the key is "custom.definition.oam.dev/catalog.config.oam.dev"
```shell
$ vela components --label custom.definition.oam.dev/catalog.config.oam.dev=velacore-config
$ vela components --label catalog.config.oam.dev=velacore-config
NAME DEFINITION
config-dex-connector autodetects.core.oam.dev
config-helm-repository autodetects.core.oam.dev

View File

@@ -93,7 +93,7 @@ var _ = Describe("Test Kubectl Plugin", func() {
var tempApp v1beta1.Application
_ = k8sClient.Get(ctx, client.ObjectKey{Namespace: "default", Name: app.Name}, &tempApp)
return tempApp.Status.LatestRevision != nil
}, 20*time.Second).Should(BeTrue())
}, 20*time.Second, time.Second).Should(BeTrue())
By("live-diff application")
err := os.WriteFile("live-diff-app.yaml", []byte(newApplication), 0644)
@@ -108,7 +108,7 @@ var _ = Describe("Test Kubectl Plugin", func() {
var tempApp v1beta1.Application
_ = k8sClient.Get(ctx, client.ObjectKey{Namespace: "default", Name: app.Name}, &tempApp)
return tempApp.Status.LatestRevision != nil
}, 20*time.Second).Should(BeTrue())
}, 20*time.Second, time.Second).Should(BeTrue())
output, err := e2e.Exec("kubectl-vela live-diff -f live-diff-app.yaml -d definitions")
Expect(err).NotTo(HaveOccurred())
@@ -134,7 +134,7 @@ var _ = Describe("Test Kubectl Plugin", func() {
cdName := "test-webapp-chart"
output, _ := e2e.Exec(fmt.Sprintf("kubectl-vela show %s -n default", cdName))
return output
}, 20*time.Second).Should(ContainSubstring("Properties"))
}, 20*time.Second, time.Second).Should(ContainSubstring("Specification"))
})
It("Test show componentDefinition def with raw Kube mode", func() {
cdName := "kube-worker"
@@ -978,20 +978,20 @@ spec:
}
`
var showCdResult = `# Properties
var showCdResult = `# Specification
+---------+--------------------------------------------------------------------------------------------------+----------+----------+---------+
| NAME | DESCRIPTION | TYPE | REQUIRED | DEFAULT |
+---------+--------------------------------------------------------------------------------------------------+----------+----------+---------+
| cmd | Commands to run in the container | []string | false | |
| count | specify number of tasks to run in parallel | int | true | 1 |
| cmd | Commands to run in the container. | []string | false | |
| count | specify number of tasks to run in parallel. | int | true | 1 |
| restart | Define the job restart policy, the value can only be Never or OnFailure. By default, it's Never. | string | true | Never |
| image | Which image would you like to use for your service | string | true | |
| image | Which image would you like to use for your service. | string | true | |
+---------+--------------------------------------------------------------------------------------------------+----------+----------+---------+
`
var showTdResult = `# Properties
var showTdResult = `# Specification
+---------+-------------+----------+----------+---------+
| NAME | DESCRIPTION | TYPE | REQUIRED | DEFAULT |
+---------+-------------+----------+----------+---------+

View File

@@ -1,17 +1,17 @@
/*
Copyright 2021 The KubeVela Authors.
Copyright 2022 The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main

View File

@@ -0,0 +1,150 @@
/*
Copyright 2022 The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"encoding/json"
"flag"
"fmt"
"io/ioutil"
"log"
"strings"
)
var i18nDoc = map[string]map[string]string{}
const cnComp = "../kubevela.io/i18n/zh/docusaurus-plugin-content-docs/current/end-user/components/references.md"
const enComp = "../kubevela.io/docs/end-user/components/references.md"
/*
const enTrait = "../kubevela.io/docs/end-user/traits/references.md"
const cnTrait = "../kubevela.io/i18n/zh/docusaurus-plugin-content-docs/current/end-user/traits/references.md"
const enPolicy = "../kubevela.io/docs/end-user/policies/references.md"
const cnPolicy = "../kubevela.io/i18n/zh/docusaurus-plugin-content-docs/current/end-user/policies/references.md"
const cnWorkflow = "../kubevela.io/i18n/zh/docusaurus-plugin-content-docs/current/end-user/workflow/built-in-workflow-defs.md"
const enWorkflow = "../kubevela.io/docs/end-user/workflow/built-in-workflow-defs.md"
*/
func main() {
pathCN := flag.String("path-cn", cnComp, "specify the path of chinese reference doc.")
pathEN := flag.String("path-en", enComp, "specify the path of english reference doc.")
path := flag.String("path", "", "path of existing i18n json data, if specified, it will read the file and keep the old data with append only.")
flag.Parse()
if *path != "" {
data, err := ioutil.ReadFile(*path)
if err == nil {
err = json.Unmarshal(data, &i18nDoc)
if err != nil {
log.Fatalln(err)
}
}
}
paths := strings.Split(*pathEN, ";")
var enbuff string
for _, v := range paths {
if strings.TrimSpace(v) == "" {
continue
}
data, err := ioutil.ReadFile(v)
if err != nil {
log.Fatalln(err)
}
enbuff += string(data) + "\n"
}
cnpaths := strings.Split(*pathCN, ";")
var cnbuff string
for _, v := range cnpaths {
if strings.TrimSpace(v) == "" {
continue
}
data, err := ioutil.ReadFile(v)
if err != nil {
log.Fatalln(err)
}
cnbuff += string(data) + "\n"
}
var entable, cntable = map[string]string{}, map[string]string{}
ens := strings.Split(enbuff, "\n")
for _, v := range ens {
values := strings.Split(v, "|")
if len(values) < 4 {
continue
}
var a, b = 0, 1
if values[0] == "" {
a, b = 1, 2
}
key := strings.TrimSpace(values[a])
desc := strings.Trim(strings.TrimSpace(values[b]), ".")
if strings.Contains(key, "----") {
continue
}
if strings.TrimSpace(desc) == "" {
continue
}
if len(entable[key]) > len(desc) {
continue
}
entable[key] = desc
}
cns := strings.Split(cnbuff, "\n")
for _, v := range cns {
values := strings.Split(v, "|")
if len(values) < 5 {
continue
}
var a, b = 0, 1
if values[0] == "" {
a, b = 1, 2
}
key := strings.TrimSpace(values[a])
desc := strings.Trim(strings.TrimSpace(values[b]), ".")
if strings.Contains(key, "----") {
continue
}
if strings.TrimSpace(desc) == "" {
continue
}
if len(cntable[key]) > len(desc) {
continue
}
cntable[key] = desc
}
for k, v := range entable {
trans := i18nDoc[v]
if trans == nil {
trans = map[string]string{}
}
trans["Chinese"] = cntable[k]
// fmt.Println("Key=", k, " | ", v, " | ", cntable[k])
i18nDoc[v] = trans
}
output, err := json.MarshalIndent(i18nDoc, "", "\t")
if err != nil {
log.Fatalln(err)
}
fmt.Println(string(output))
}

72
hack/docgen/def/gen.go Normal file
View File

@@ -0,0 +1,72 @@
/*
Copyright 2022 The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"context"
"flag"
"fmt"
"os"
"github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/hack/docgen/def/mods"
"github.com/oam-dev/kubevela/pkg/utils/common"
"github.com/oam-dev/kubevela/references/plugins"
)
func main() {
ctx := context.Background()
c, err := common.InitBaseRestConfig()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
path := flag.String("path", "", "specify the path of output")
location := flag.String("location", "", "path of output")
defdir := flag.String("def-dir", "", "path of definition dir")
tp := flag.String("type", "", "choose one of the definition to print")
i18nfile := flag.String("i18n", "../kubevela.io/static/reference-i18n.json", "file path of i18n data")
flag.Parse()
if *i18nfile != "" {
plugins.LoadI18nData(*i18nfile)
}
if *tp == "" && (*defdir != "" || *path != "") {
fmt.Println("you must specify a type with definition ref path specified ")
os.Exit(1)
}
fmt.Printf("creating docs with args path=%s, location=%s, defdir=%s, type=%s.\n", *path, *location, *defdir, *tp)
switch types.CapType(*tp) {
case types.TypeComponentDefinition, "component":
mods.ComponentDef(ctx, c, path, location, *defdir)
case types.TypeTrait:
mods.TraitDef(ctx, c, path, location, *defdir)
case types.TypePolicy:
mods.PolicyDef(ctx, c, path, location, *defdir)
case types.TypeWorkflowStep:
mods.WorkflowDef(ctx, c, path, location, *defdir)
default:
mods.ComponentDef(ctx, c, path, location, *defdir)
mods.TraitDef(ctx, c, path, location, *defdir)
mods.PolicyDef(ctx, c, path, location, *defdir)
mods.WorkflowDef(ctx, c, path, location, *defdir)
}
}

View File

@@ -0,0 +1,120 @@
/*
Copyright 2022 The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package mods
import (
"context"
"fmt"
"io/ioutil"
"os"
"strings"
"time"
"github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/utils/common"
"github.com/oam-dev/kubevela/references/plugins"
)
const (
// ComponentDefRefPath is the target path for kubevela.io component ref docs
ComponentDefRefPath = "../kubevela.io/docs/end-user/components/references.md"
// ComponentDefRefPathZh is the target path for kubevela.io component ref docs in Chinese
ComponentDefRefPathZh = "../kubevela.io/i18n/zh/docusaurus-plugin-content-docs/current/end-user/components/references.md"
// ComponentDefDir store inner CUE definition
ComponentDefDir = "./vela-templates/definitions/internal/component/"
)
// CustomComponentHeaderEN .
var CustomComponentHeaderEN = `---
title: Built-in Component Type
---
This documentation will walk through all the built-in component types sorted alphabetically.
` + fmt.Sprintf("> It was generated automatically by [scripts](../../contributor/cli-ref-doc), please don't update manually, last updated at %s.\n\n", time.Now().Format(time.RFC3339))
// CustomComponentHeaderZH .
var CustomComponentHeaderZH = `---
title: 内置组件列表
---
本文档将**按字典序**展示所有内置组件的参数列表。
` + fmt.Sprintf("> 本文档由[脚本](../../contributor/cli-ref-doc)自动生成,请勿手动修改,上次更新于 %s。\n\n", time.Now().Format(time.RFC3339))
// ComponentDef generate component def reference doc
func ComponentDef(ctx context.Context, c common.Args, path, location *string, defdir string) {
if defdir == "" {
defdir = ComponentDefDir
}
ref := &plugins.MarkdownReference{
AllInOne: true,
Filter: func(capability types.Capability) bool {
if capability.Type != types.TypeComponentDefinition || capability.Category != types.CUECategory {
return false
}
if capability.Labels != nil && (capability.Labels[types.LabelDefinitionHidden] == "true" || capability.Labels[types.LabelDefinitionDeprecated] == "true") {
return false
}
// only print capability which contained in cue def
files, err := ioutil.ReadDir(defdir)
if err != nil {
fmt.Println("read dir err", defdir, err)
return false
}
for _, f := range files {
if strings.Contains(f.Name(), capability.Name) {
return true
}
}
return false
},
CustomDocHeader: CustomComponentHeaderEN,
}
ref.Remote = &plugins.FromCluster{Namespace: types.DefaultKubeVelaNS}
if *path != "" {
ref.I18N = &plugins.En
if strings.Contains(*location, "zh") || strings.Contains(*location, "chinese") {
ref.I18N = &plugins.Zh
ref.CustomDocHeader = CustomComponentHeaderZH
}
if err := ref.GenerateReferenceDocs(ctx, c, *path); err != nil {
fmt.Println(err)
os.Exit(1)
}
fmt.Printf("component reference docs (%s) successfully generated in %s \n", ref.I18N.Language(), *path)
}
if *location == "" || *location == "en" {
ref.I18N = &plugins.En
if err := ref.GenerateReferenceDocs(ctx, c, ComponentDefRefPath); err != nil {
fmt.Println(err)
os.Exit(1)
}
fmt.Printf("component reference docs (%s) successfully generated in %s \n", ref.I18N.Language(), ComponentDefRefPath)
}
if *location == "" || *location == "zh" {
ref.I18N = &plugins.Zh
ref.CustomDocHeader = CustomComponentHeaderZH
if err := ref.GenerateReferenceDocs(ctx, c, ComponentDefRefPathZh); err != nil {
fmt.Println(err)
os.Exit(1)
}
fmt.Printf("component reference docs (%s) successfully generated in %s \n", ref.I18N.Language(), ComponentDefRefPathZh)
}
}

View File

@@ -0,0 +1,119 @@
/*
Copyright 2022 The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package mods
import (
"context"
"fmt"
"io/ioutil"
"os"
"strings"
"time"
"github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/utils/common"
"github.com/oam-dev/kubevela/references/plugins"
)
const (
// PolicyDefRefPath is the target path for kubevela.io policy ref docs
PolicyDefRefPath = "../kubevela.io/docs/end-user/policies/references.md"
// PolicyDefRefPathZh is the target path for kubevela.io policy ref docs in Chinese
PolicyDefRefPathZh = "../kubevela.io/i18n/zh/docusaurus-plugin-content-docs/current/end-user/policies/references.md"
// PolicyDefDir store inner CUE definition
PolicyDefDir = "./vela-templates/definitions/internal/policy/"
)
// CustomPolicyHeaderEN .
var CustomPolicyHeaderEN = `---
title: Built-in Policy Type
---
This documentation will walk through all the built-in policy types sorted alphabetically.
` + fmt.Sprintf("> It was generated automatically by [scripts](../../contributor/cli-ref-doc), please don't update manually, last updated at %s.\n\n", time.Now().Format(time.RFC3339))
// CustomPolicyHeaderZH .
var CustomPolicyHeaderZH = `---
title: 内置策略列表
---
本文档将**按字典序**展示所有内置策略的参数列表。
` + fmt.Sprintf("> 本文档由[脚本](../../contributor/cli-ref-doc)自动生成,请勿手动修改,上次更新于 %s。\n\n", time.Now().Format(time.RFC3339))
// PolicyDef generate policy def reference doc
func PolicyDef(ctx context.Context, c common.Args, path, location *string, defdir string) {
if defdir == "" {
defdir = PolicyDefDir
}
ref := &plugins.MarkdownReference{
AllInOne: true,
Filter: func(capability types.Capability) bool {
if capability.Type != types.TypePolicy || capability.Category != types.CUECategory {
return false
}
if capability.Labels != nil && (capability.Labels[types.LabelDefinitionHidden] == "true" || capability.Labels[types.LabelDefinitionDeprecated] == "true") {
return false
}
// only print capability which contained in cue def
files, err := ioutil.ReadDir(defdir)
if err != nil {
fmt.Println("read dir err", defdir, err)
return false
}
for _, f := range files {
if strings.Contains(f.Name(), capability.Name) {
return true
}
}
return false
},
CustomDocHeader: CustomPolicyHeaderEN,
}
ref.Remote = &plugins.FromCluster{Namespace: types.DefaultKubeVelaNS}
if *path != "" {
ref.I18N = &plugins.En
if strings.Contains(*location, "zh") || strings.Contains(*location, "chinese") {
ref.I18N = &plugins.Zh
ref.CustomDocHeader = CustomPolicyHeaderZH
}
if err := ref.GenerateReferenceDocs(ctx, c, *path); err != nil {
fmt.Println(err)
os.Exit(1)
}
fmt.Printf("policy reference docs (%s) successfully generated in %s \n", ref.I18N.Language(), *path)
}
if *location == "" || *location == "en" {
ref.I18N = &plugins.En
if err := ref.GenerateReferenceDocs(ctx, c, PolicyDefRefPath); err != nil {
fmt.Println(err)
os.Exit(1)
}
fmt.Printf("policy reference docs (%s) successfully generated in %s \n", ref.I18N.Language(), PolicyDefRefPath)
}
if *location == "" || *location == "zh" {
ref.I18N = &plugins.Zh
ref.CustomDocHeader = CustomPolicyHeaderZH
if err := ref.GenerateReferenceDocs(ctx, c, PolicyDefRefPathZh); err != nil {
fmt.Println(err)
os.Exit(1)
}
fmt.Printf("policy reference docs (%s) successfully generated in %s \n", ref.I18N.Language(), PolicyDefRefPathZh)
}
}

View File

@@ -0,0 +1,120 @@
/*
Copyright 2022 The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package mods
import (
"context"
"fmt"
"io/ioutil"
"os"
"strings"
"time"
"github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/utils/common"
"github.com/oam-dev/kubevela/references/plugins"
)
const (
// TraitDefRefPath is the target path for kubevela.io trait ref docs
TraitDefRefPath = "../kubevela.io/docs/end-user/traits/references.md"
// TraitDefRefPathZh is the target path for kubevela.io trait ref docs in Chinese
TraitDefRefPathZh = "../kubevela.io/i18n/zh/docusaurus-plugin-content-docs/current/end-user/traits/references.md"
// TraitDefDir store inner CUE definition
TraitDefDir = "./vela-templates/definitions/internal/trait/"
)
// CustomTraitHeaderEN .
var CustomTraitHeaderEN = `---
title: Built-in Trait Type
---
This documentation will walk through all the built-in trait types sorted alphabetically.
` + fmt.Sprintf("> It was generated automatically by [scripts](../../contributor/cli-ref-doc), please don't update manually, last updated at %s.\n\n", time.Now().Format(time.RFC3339))
// CustomTraitHeaderZH .
var CustomTraitHeaderZH = `---
title: 内置运维特征列表
---
本文档将**按字典序**展示所有内置运维特征的参数列表。
` + fmt.Sprintf("> 本文档由[脚本](../../contributor/cli-ref-doc)自动生成,请勿手动修改,上次更新于 %s。\n\n", time.Now().Format(time.RFC3339))
// TraitDef generate trait def reference doc
func TraitDef(ctx context.Context, c common.Args, path, location *string, defdir string) {
if defdir == "" {
defdir = TraitDefDir
}
ref := &plugins.MarkdownReference{
AllInOne: true,
Filter: func(capability types.Capability) bool {
if capability.Type != types.TypeTrait || capability.Category != types.CUECategory {
return false
}
if capability.Labels != nil && (capability.Labels[types.LabelDefinitionDeprecated] == "true") {
return false
}
// only print capability which contained in cue def
files, err := ioutil.ReadDir(defdir)
if err != nil {
fmt.Println("read dir err", defdir, err)
return false
}
for _, f := range files {
if strings.Contains(f.Name(), capability.Name) {
return true
}
}
return false
},
CustomDocHeader: CustomTraitHeaderEN,
}
ref.Remote = &plugins.FromCluster{Namespace: types.DefaultKubeVelaNS}
if *path != "" {
ref.I18N = &plugins.En
if strings.Contains(*location, "zh") || strings.Contains(*location, "chinese") {
ref.I18N = &plugins.Zh
ref.CustomDocHeader = CustomTraitHeaderZH
}
if err := ref.GenerateReferenceDocs(ctx, c, *path); err != nil {
fmt.Println(err)
os.Exit(1)
}
fmt.Printf("trait reference docs (%s) successfully generated in %s \n", ref.I18N.Language(), *path)
}
if *location == "" || *location == "en" {
ref.I18N = &plugins.En
if err := ref.GenerateReferenceDocs(ctx, c, TraitDefRefPath); err != nil {
fmt.Println(err)
os.Exit(1)
}
fmt.Printf("trait reference docs (%s) successfully generated in %s \n", ref.I18N.Language(), TraitDefRefPath)
}
if *location == "" || *location == "zh" {
ref.I18N = &plugins.Zh
ref.CustomDocHeader = CustomTraitHeaderZH
if err := ref.GenerateReferenceDocs(ctx, c, TraitDefRefPathZh); err != nil {
fmt.Println(err)
os.Exit(1)
}
fmt.Printf("trait reference docs (%s) successfully generated in %s \n", ref.I18N.Language(), TraitDefRefPathZh)
}
}

View File

@@ -0,0 +1,122 @@
/*
Copyright 2022 The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package mods
import (
"context"
"fmt"
"io/ioutil"
"os"
"strings"
"time"
"github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/utils/common"
"github.com/oam-dev/kubevela/references/plugins"
)
const (
// WorkflowDefRefPath is the target path for kubevela.io workflow ref docs
WorkflowDefRefPath = "../kubevela.io/docs/end-user/workflow/built-in-workflow-defs.md"
// WorkflowDefRefPathZh is the target path for kubevela.io workflow ref docs in Chinese
WorkflowDefRefPathZh = "../kubevela.io/i18n/zh/docusaurus-plugin-content-docs/current/end-user/workflow/built-in-workflow-defs.md"
// WorkflowDefDir store inner CUE definition
WorkflowDefDir = "./vela-templates/definitions/internal/workflowstep/"
)
// CustomWorkflowHeaderEN .
var CustomWorkflowHeaderEN = `---
title: Built-in WorkflowStep Type
---
This documentation will walk through all the built-in workflow step types sorted alphabetically.
` + fmt.Sprintf("> It was generated automatically by [scripts](../../contributor/cli-ref-doc), please don't update manually, last updated at %s.\n\n", time.Now().Format(time.RFC3339))
// CustomWorkflowHeaderZH .
var CustomWorkflowHeaderZH = `---
title: 内置工作流步骤列表
---
本文档将**按字典序**展示所有内置工作流步骤的参数列表。
` + fmt.Sprintf("> 本文档由[脚本](../../contributor/cli-ref-doc)自动生成,请勿手动修改,上次更新于 %s。\n\n", time.Now().Format(time.RFC3339))
// WorkflowDef generate workflow def reference doc
func WorkflowDef(ctx context.Context, c common.Args, path, location *string, defdir string) {
if defdir == "" {
defdir = WorkflowDefDir
}
ref := &plugins.MarkdownReference{
AllInOne: true,
Filter: func(capability types.Capability) bool {
if capability.Type != types.TypeWorkflowStep || capability.Category != types.CUECategory {
return false
}
if capability.Labels != nil && (capability.Labels[types.LabelDefinitionHidden] == "true" || capability.Labels[types.LabelDefinitionDeprecated] == "true") {
return false
}
// only print capability which contained in cue def
files, err := ioutil.ReadDir(defdir)
if err != nil {
fmt.Println("read dir err", defdir, err)
return false
}
for _, f := range files {
if strings.Contains(f.Name(), capability.Name) {
return true
}
}
return false
},
CustomDocHeader: CustomWorkflowHeaderEN,
}
ref.Remote = &plugins.FromCluster{Namespace: types.DefaultKubeVelaNS}
if *path != "" {
ref.I18N = &plugins.En
if strings.Contains(*location, "zh") || strings.Contains(*location, "chinese") {
ref.I18N = &plugins.Zh
ref.CustomDocHeader = CustomWorkflowHeaderZH
}
if err := ref.GenerateReferenceDocs(ctx, c, *path); err != nil {
fmt.Println(err)
os.Exit(1)
}
fmt.Printf("workflow reference docs (%s) successfully generated in %s \n", ref.I18N.Language(), *path)
}
if *location == "" || *location == "en" {
ref.I18N = &plugins.En
if err := ref.GenerateReferenceDocs(ctx, c, WorkflowDefRefPath); err != nil {
fmt.Println(err)
os.Exit(1)
}
fmt.Printf("workflow reference docs (%s) successfully generated in %s \n", ref.I18N.Language(), WorkflowDefRefPath)
}
if *location == "" || *location == "zh" {
ref.I18N = &plugins.Zh
ref.CustomDocHeader = CustomWorkflowHeaderZH
if err := ref.GenerateReferenceDocs(ctx, c, WorkflowDefRefPathZh); err != nil {
fmt.Println(err)
os.Exit(1)
}
fmt.Printf("workflow reference docs (%s) successfully generated in %s \n", ref.I18N.Language(), WorkflowDefRefPathZh)
}
}

View File

@@ -0,0 +1,87 @@
/*
Copyright 2021 The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"context"
"flag"
"fmt"
"os"
"strings"
"github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/utils/common"
"github.com/oam-dev/kubevela/references/plugins"
)
const (
// KubeVelaIOTerraformPath is the target path for kubevela.io terraform docs
KubeVelaIOTerraformPath = "../kubevela.io/docs/end-user/components/cloud-services/terraform"
// KubeVelaIOTerraformPathZh is the target path for kubevela.io terraform docs in Chinese
KubeVelaIOTerraformPathZh = "../kubevela.io/i18n/zh/docusaurus-plugin-content-docs/current/end-user/components/cloud-services/terraform"
)
func main() {
ref := &plugins.MarkdownReference{}
ctx := context.Background()
c, err := common.InitBaseRestConfig()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
ref.Remote = &plugins.FromCluster{Namespace: types.DefaultKubeVelaNS}
ref.Filter = func(capability types.Capability) bool {
if capability.Labels != nil && capability.Labels[types.LabelDefinitionHidden] == "true" {
return false
}
return capability.Type == types.TypeComponentDefinition && capability.Category == types.TerraformCategory
}
path := flag.String("path", "", "path of output")
location := flag.String("location", "", "path of output")
i18nfile := flag.String("i18n", "../kubevela.io/static/reference-i18n.json", "file path of i18n data")
flag.Parse()
if *i18nfile != "" {
plugins.LoadI18nData(*i18nfile)
}
if *path != "" {
ref.I18N = &plugins.En
if strings.Contains(*location, "zh") || strings.Contains(*location, "chinese") {
ref.I18N = &plugins.Zh
}
if err := ref.GenerateReferenceDocs(ctx, c, *path); err != nil {
fmt.Println(err)
os.Exit(1)
}
fmt.Printf("terraform reference docs (%s) successfully generated in %s \n", ref.I18N.Language(), *path)
}
ref.I18N = &plugins.En
if err := ref.GenerateReferenceDocs(ctx, c, KubeVelaIOTerraformPath); err != nil {
fmt.Println(err)
os.Exit(1)
}
fmt.Printf("terraform reference docs (%s) successfully generated in %s \n", ref.I18N.Language(), KubeVelaIOTerraformPath)
ref.I18N = &plugins.Zh
if err := ref.GenerateReferenceDocs(ctx, c, KubeVelaIOTerraformPathZh); err != nil {
fmt.Println(err)
os.Exit(1)
}
fmt.Printf("terraform reference docs (%s) successfully generated in %s \n", ref.I18N.Language(), KubeVelaIOTerraformPathZh)
}

View File

@@ -1,25 +0,0 @@
## Conflicts With
### `Autoscale`
When `Rollout` and `Autoscle` traits are attached to the same service, they two will fight over the number of instances during rollout. Thus, it's by design that `Rollout` will take over replicas control (specified by `.replicas` field) during rollout.
> Note: in up coming releases, KubeVela will introduce a separate section in Appfile to define release phase configurations such as `Rollout`.
## How `Rollout` works?
`Rollout` trait implements progressive release process to rollout your app following [Canary strategy](https://martinfowler.com/bliki/CanaryRelease.html).
In detail, `Rollout` controller will create a canary of your app , and then gradually shift traffic to the canary while measuring key performance indicators like HTTP requests success rate at the same time.
![alt](https://raw.githubusercontent.com/oam-dev/kubevela.io/main/docs/resources/traffic-shifting-analysis.png)
In this sample, for every `10s`, `5%` traffic will be shifted to canary from the primary, until the traffic on canary reached `50%`. At the mean time, the instance number of canary will automatically scale to `replicas: 2` per configured in Appfile.
Based on analysis result of the KPIs during this traffic shifting, a canary will be promoted or aborted if analysis is failed. If promoting, the primary will be upgraded from v1 to v2, and traffic will be fully shifted back to the primary instances. So as result, canary instances will be deleted after the promotion finished.
![alt](https://raw.githubusercontent.com/oam-dev/kubevela.io/main/docs/resources/promotion.png)
> Note: KubeVela's `Rollout` trait is implemented with [Weaveworks Flagger](https://flagger.app/) operator.

View File

@@ -1,50 +0,0 @@
/*
Copyright 2021 The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"context"
"fmt"
"os"
"github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/utils/common"
"github.com/oam-dev/kubevela/references/plugins"
)
func main() {
ref := &plugins.MarkdownReference{}
ctx := context.Background()
path := plugins.BaseRefPath
if len(os.Args) == 2 {
ref.DefinitionName = os.Args[1]
path = plugins.KubeVelaIOTerraformPath
}
c, err := common.InitBaseRestConfig()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
ref.Remote = &plugins.Remote{Namespace: types.DefaultKubeVelaNS}
if err := ref.GenerateReferenceDocs(ctx, c, path); err != nil {
fmt.Println(err)
os.Exit(1)
}
}

View File

@@ -36,7 +36,7 @@ import (
"github.com/oam-dev/kubevela/pkg/apiserver/utils/bcode"
"github.com/oam-dev/kubevela/pkg/apiserver/utils/log"
"github.com/oam-dev/kubevela/pkg/oam/util"
utils2 "github.com/oam-dev/kubevela/pkg/utils"
pkgUtils "github.com/oam-dev/kubevela/pkg/utils"
"github.com/oam-dev/kubevela/pkg/workflow/step"
)
@@ -286,7 +286,7 @@ func compareWorkflowSteps(old, new steps) steps {
}
}
_, needDeleted, needAdded := utils2.ThreeWaySliceCompare(oldTargets, newTargets)
_, needDeleted, needAdded := pkgUtils.ThreeWaySliceCompare(oldTargets, newTargets)
var workflowSteps []*workflowStep
var deployCloudResourcePolicyExist = false
@@ -295,7 +295,7 @@ func compareWorkflowSteps(old, new steps) steps {
var deletedPolicyCount = 0
for i := range oldStep.policies {
p := oldStep.policies[i]
if utils2.SliceIncludeSlice(needDeleted, cacheTarget(oldStep.stepType, p.targets)) {
if pkgUtils.SliceIncludeSlice(needDeleted, cacheTarget(oldStep.stepType, p.targets)) {
p.state = deleteState
deletedPolicyCount++
}
@@ -314,7 +314,7 @@ func compareWorkflowSteps(old, new steps) steps {
newStep := new[j]
for i := range newStep.policies {
p := newStep.policies[i]
if utils2.SliceIncludeSlice(needAdded, cacheTarget(newStep.stepType, p.targets)) {
if pkgUtils.SliceIncludeSlice(needAdded, cacheTarget(newStep.stepType, p.targets)) {
if p.policyType == v1alpha1.EnvBindingPolicyType && deployCloudResourcePolicyExist {
p.state = updateState
} else {
@@ -418,7 +418,7 @@ func UpdateAppEnvWorkflow(ctx context.Context, kubeClient client.Client, ds data
log.Logger.Errorf("fail to update the env workflow %s", envs[i].PrimaryKey())
}
}
log.Logger.Infof("The env workflows of app %s updated successfully", utils2.Sanitize(app.PrimaryKey()))
log.Logger.Infof("The env workflows of app %s updated successfully", pkgUtils.Sanitize(app.PrimaryKey()))
return nil
}
@@ -607,7 +607,7 @@ func GenEnvWorkflowStepsAndPolicies(ctx context.Context, kubeClient client.Clien
if step.Properties != nil {
properties, err := model.NewJSONStruct(step.Properties)
if err != nil {
log.Logger.Errorf("workflow %s step %s properties is invalid %s", utils2.Sanitize(app.Name), utils2.Sanitize(step.Name), err.Error())
log.Logger.Errorf("workflow %s step %s properties is invalid %s", pkgUtils.Sanitize(app.Name), pkgUtils.Sanitize(step.Name), err.Error())
continue
}
s.Properties = properties

View File

@@ -34,7 +34,7 @@ import (
"github.com/oam-dev/kubevela/pkg/apiserver/domain/model"
"github.com/oam-dev/kubevela/pkg/apiserver/infrastructure/datastore"
"github.com/oam-dev/kubevela/pkg/apiserver/utils/log"
utils2 "github.com/oam-dev/kubevela/pkg/utils"
pkgUtils "github.com/oam-dev/kubevela/pkg/utils"
)
func TestCompareWorkflowSteps(t *testing.T) {
@@ -543,7 +543,7 @@ func CreateEnvWorkflow(ctx context.Context, store datastore.DataStore, kubeClien
EnvName: env.Name,
AppPrimaryKey: app.PrimaryKey(),
}
log.Logger.Infof("create workflow %s for app %s", utils2.Sanitize(workflow.Name), utils2.Sanitize(app.PrimaryKey()))
log.Logger.Infof("create workflow %s for app %s", pkgUtils.Sanitize(workflow.Name), pkgUtils.Sanitize(app.PrimaryKey()))
if err := store.Add(ctx, workflow); err != nil {
return err
}

View File

@@ -53,9 +53,9 @@ import (
"github.com/oam-dev/kubevela/pkg/appfile/dryrun"
"github.com/oam-dev/kubevela/pkg/oam"
"github.com/oam-dev/kubevela/pkg/oam/discoverymapper"
utils2 "github.com/oam-dev/kubevela/pkg/utils"
pkgUtils "github.com/oam-dev/kubevela/pkg/utils"
"github.com/oam-dev/kubevela/pkg/utils/apply"
common2 "github.com/oam-dev/kubevela/pkg/utils/common"
commonutil "github.com/oam-dev/kubevela/pkg/utils/common"
)
// PolicyType build-in policy type
@@ -128,7 +128,7 @@ func listApp(ctx context.Context, ds datastore.DataStore, listOptions apisv1.Lis
if listOptions.Env != "" || listOptions.TargetName != "" {
envBinding, err = repository.ListFullEnvBinding(ctx, ds, repository.EnvListOption{})
if err != nil {
log.Logger.Errorf("list envbinding for list application in env %s err %v", utils2.Sanitize(listOptions.Env), err)
log.Logger.Errorf("list envbinding for list application in env %s err %v", pkgUtils.Sanitize(listOptions.Env), err)
return nil, err
}
}
@@ -197,7 +197,7 @@ func (c *applicationServiceImpl) ListApplications(ctx context.Context, listOptio
return []*apisv1.ApplicationBase{}, nil
}
if len(listOptions.Projects) > 0 {
if !utils2.SliceIncludeSlice(availableProjectNames, listOptions.Projects) {
if !pkgUtils.SliceIncludeSlice(availableProjectNames, listOptions.Projects) {
return []*apisv1.ApplicationBase{}, nil
}
}
@@ -595,7 +595,7 @@ func (c *applicationServiceImpl) DetailComponent(ctx context.Context, app *model
}
var cd v1beta1.ComponentDefinition
if err := c.KubeClient.Get(ctx, types.NamespacedName{Name: component.Type, Namespace: velatypes.DefaultKubeVelaNS}, &cd); err != nil {
log.Logger.Warnf("component definition %s get failure. %s", utils2.Sanitize(component.Type), err.Error())
log.Logger.Warnf("component definition %s get failure. %s", pkgUtils.Sanitize(component.Type), err.Error())
}
return &apisv1.DetailComponentResponse{
@@ -1065,7 +1065,7 @@ func (c *applicationServiceImpl) UpdateComponent(ctx context.Context, app *model
func (c *applicationServiceImpl) createComponent(ctx context.Context, app *model.Application, com apisv1.CreateComponentRequest, main bool) (*apisv1.ComponentBase, error) {
var cd v1beta1.ComponentDefinition
if err := c.KubeClient.Get(ctx, types.NamespacedName{Name: com.ComponentType, Namespace: velatypes.DefaultKubeVelaNS}, &cd); err != nil {
log.Logger.Warnf("component definition %s get failure. %s", utils2.Sanitize(com.ComponentType), err.Error())
log.Logger.Warnf("component definition %s get failure. %s", pkgUtils.Sanitize(com.ComponentType), err.Error())
return nil, bcode.ErrComponentTypeNotSupport
}
userName, _ := ctx.Value(&apisv1.CtxKeyUser).(string)
@@ -1120,7 +1120,7 @@ func (c *applicationServiceImpl) createComponent(ctx context.Context, app *model
if errors.Is(err, datastore.ErrRecordExist) {
return nil, bcode.ErrApplicationComponentExist
}
log.Logger.Warnf("add component for app %s failure %s", utils2.Sanitize(app.PrimaryKey()), err.Error())
log.Logger.Warnf("add component for app %s failure %s", pkgUtils.Sanitize(app.PrimaryKey()), err.Error())
return nil, err
}
// update the env workflow, the automatically generated workflow is determined by the component type.
@@ -1491,8 +1491,8 @@ func (c *applicationServiceImpl) CompareApp(ctx context.Context, appModel *model
return compareResponse, nil
}
args := common2.Args{
Schema: common2.Scheme,
args := commonutil.Args{
Schema: commonutil.Scheme,
}
_ = args.SetConfig(c.KubeConfig)
args.SetClient(c.KubeClient)
@@ -1534,8 +1534,8 @@ func (c *applicationServiceImpl) DryRunAppOrRevision(ctx context.Context, appMod
default:
return nil, bcode.ErrApplicationDryRunFailed.SetMessage("The dry run type is not supported")
}
args := common2.Args{
Schema: common2.Scheme,
args := commonutil.Args{
Schema: commonutil.Scheme,
}
_ = args.SetConfig(c.KubeConfig)
args.SetClient(c.KubeClient)
@@ -1594,7 +1594,7 @@ func (c *applicationServiceImpl) resetApp(ctx context.Context, targetApp *v1beta
targetCompNames = append(targetCompNames, comp.Name)
}
readyToUpdate, readyToDelete, readyToAdd := utils2.ThreeWaySliceCompare(originCompNames, targetCompNames)
readyToUpdate, readyToDelete, readyToAdd := pkgUtils.ThreeWaySliceCompare(originCompNames, targetCompNames)
// delete new app's components
for _, compName := range readyToDelete {
@@ -1626,11 +1626,11 @@ func (c *applicationServiceImpl) resetApp(ctx context.Context, targetApp *v1beta
if errors.Is(err, datastore.ErrRecordExist) {
err := c.Store.Put(ctx, &compModel)
if err != nil {
log.Logger.Warnf("update comp %s for app %s failure %s", comp.Name, utils2.Sanitize(appPrimaryKey), err.Error())
log.Logger.Warnf("update comp %s for app %s failure %s", comp.Name, pkgUtils.Sanitize(appPrimaryKey), err.Error())
}
return &apisv1.AppResetResponse{IsReset: true}, err
}
log.Logger.Warnf("add comp %s for app %s failure %s", comp.Name, utils2.Sanitize(appPrimaryKey), err.Error())
log.Logger.Warnf("add comp %s for app %s failure %s", comp.Name, pkgUtils.Sanitize(appPrimaryKey), err.Error())
return &apisv1.AppResetResponse{}, err
}
}
@@ -1638,7 +1638,7 @@ func (c *applicationServiceImpl) resetApp(ctx context.Context, targetApp *v1beta
return &apisv1.AppResetResponse{IsReset: true}, nil
}
func dryRunApplication(ctx context.Context, c common2.Args, app *v1beta1.Application) (bytes.Buffer, error) {
func dryRunApplication(ctx context.Context, c commonutil.Args, app *v1beta1.Application) (bytes.Buffer, error) {
var buff = bytes.Buffer{}
if _, err := fmt.Fprintf(&buff, "---\n# Application(%s) \n---\n\n", app.Name); err != nil {
return buff, fmt.Errorf("fail to write to buff %w", err)
@@ -1696,7 +1696,7 @@ func ignoreSomeParams(o *v1beta1.Application) {
*o = defaultApplication
}
func compare(ctx context.Context, c common2.Args, targetApp *v1beta1.Application, baseApp *v1beta1.Application) (*dryrun.DiffEntry, bytes.Buffer, error) {
func compare(ctx context.Context, c commonutil.Args, targetApp *v1beta1.Application, baseApp *v1beta1.Application) (*dryrun.DiffEntry, bytes.Buffer, error) {
var buff = bytes.Buffer{}
_, err := c.GetClient()
if err != nil {

View File

@@ -42,9 +42,6 @@ import (
)
const (
definitionAlias = definition.UserPrefix + "alias.config.oam.dev"
definitionType = definition.UserPrefix + "type.config.oam.dev"
configIsReady = "Ready"
configIsNotReady = "Not ready"
terraformProviderAlias = "Terraform Cloud Provider"
@@ -74,20 +71,36 @@ type configServiceImpl struct {
func (u *configServiceImpl) ListConfigTypes(ctx context.Context, query string) ([]*apis.ConfigType, error) {
defs := &v1beta1.ComponentDefinitionList{}
if err := u.KubeClient.List(ctx, defs, client.InNamespace(types.DefaultKubeVelaNS),
client.MatchingLabels{definition.UserPrefix + "catalog.config.oam.dev": types.VelaCoreConfig}); err != nil {
client.MatchingLabels{
configCatalog: types.VelaCoreConfig,
}); err != nil {
return nil, err
}
var items []v1beta1.ComponentDefinition
items = append(items, defs.Items...)
// for compatibility of the config catalog key
defsLegacy := &v1beta1.ComponentDefinitionList{}
if err := u.KubeClient.List(ctx, defsLegacy, client.InNamespace(types.DefaultKubeVelaNS),
client.MatchingLabels{
// leave here as the legacy format to test the compatibility
definition.UserPrefix + configCatalog: types.VelaCoreConfig,
}); err != nil {
return nil, err
}
items = append(items, defsLegacy.Items...)
var tfDefs []v1beta1.ComponentDefinition
var configTypes []*apis.ConfigType
for _, d := range defs.Items {
if d.Labels[definitionType] == types.TerraformProvider {
for _, d := range items {
if DefinitionType(d.Labels) == types.TerraformProvider {
tfDefs = append(tfDefs, d)
continue
}
configTypes = append(configTypes, &apis.ConfigType{
Alias: d.Annotations[definitionAlias],
Alias: DefinitionAlias(d.Annotations),
Name: d.Name,
Definitions: []string{d.Name},
Description: d.Annotations[types.AnnoDefinitionDescription],
@@ -119,7 +132,7 @@ func (u *configServiceImpl) GetConfigType(ctx context.Context, configType string
}
t := &apis.ConfigType{
Alias: d.Annotations[definitionAlias],
Alias: DefinitionAlias(d.Annotations),
Name: configType,
Description: d.Annotations[types.AnnoDefinitionDescription],
}

View File

@@ -57,7 +57,7 @@ func TestListConfigTypes(t *testing.T) {
Name: "def1",
Namespace: types.DefaultKubeVelaNS,
Labels: map[string]string{
definition.UserPrefix + "catalog.config.oam.dev": types.VelaCoreConfig,
configCatalog: types.VelaCoreConfig,
definitionType: types.TerraformProvider,
},
},
@@ -74,7 +74,7 @@ func TestListConfigTypes(t *testing.T) {
definitionAlias: "Def2",
},
Labels: map[string]string{
definition.UserPrefix + "catalog.config.oam.dev": types.VelaCoreConfig,
configCatalog: types.VelaCoreConfig,
},
},
}
@@ -156,7 +156,7 @@ func TestGetConfigType(t *testing.T) {
definitionAlias: "Def2",
},
Labels: map[string]string{
definition.UserPrefix + "catalog.config.oam.dev": types.VelaCoreConfig,
definition.UserPrefix + configCatalog: types.VelaCoreConfig,
},
},
}

View File

@@ -34,7 +34,7 @@ import (
"github.com/oam-dev/kubevela/pkg/apiserver/utils"
"github.com/oam-dev/kubevela/pkg/apiserver/utils/bcode"
"github.com/oam-dev/kubevela/pkg/apiserver/utils/log"
utils2 "github.com/oam-dev/kubevela/pkg/utils"
pkgUtils "github.com/oam-dev/kubevela/pkg/utils"
)
// EnvBindingService envbinding service
@@ -129,7 +129,7 @@ func (e *envBindingServiceImpl) BatchCreateEnvBinding(ctx context.Context, app *
continue
}
if err := e.Store.Add(ctx, envBindingModel); err != nil {
log.Logger.Errorf("add envbinding %s failure %s", utils2.Sanitize(envBindingModel.Name), err.Error())
log.Logger.Errorf("add envbinding %s failure %s", pkgUtils.Sanitize(envBindingModel.Name), err.Error())
continue
}
err = e.createEnvWorkflow(ctx, app, env, i == 0)
@@ -237,7 +237,7 @@ func (e *envBindingServiceImpl) createEnvWorkflow(ctx context.Context, app *mode
EnvName: env.Name,
AppPrimaryKey: app.PrimaryKey(),
}
log.Logger.Infof("create workflow %s for app %s", utils2.Sanitize(workflow.Name), utils2.Sanitize(app.PrimaryKey()))
log.Logger.Infof("create workflow %s for app %s", pkgUtils.Sanitize(workflow.Name), pkgUtils.Sanitize(app.PrimaryKey()))
if err := e.Store.Add(ctx, workflow); err != nil {
return err
}

View File

@@ -568,7 +568,7 @@ func (p *projectServiceImpl) GetConfigs(ctx context.Context, projectName, config
if err != nil {
klog.InfoS("failed to get component definition", "ComponentDefinition", configType, "err", err)
} else {
configs[i].ConfigTypeAlias = d.Annotations[definitionAlias]
configs[i].ConfigTypeAlias = DefinitionAlias(d.Annotations)
}
}
}

View File

@@ -0,0 +1,61 @@
/*
Copyright 2022 The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package service
import "github.com/oam-dev/kubevela/pkg/definition"
const (
definitionAlias = "alias.config.oam.dev"
definitionType = "type.config.oam.dev"
configCatalog = "catalog.config.oam.dev"
)
// DefinitionAlias will get definitionAlias value from tags
func DefinitionAlias(tags map[string]string) string {
if tags == nil {
return ""
}
val := tags[definitionAlias]
if val != "" {
return val
}
return tags[definition.UserPrefix+definitionAlias]
}
// DefinitionType will get definitionType value from tags
func DefinitionType(tags map[string]string) string {
if tags == nil {
return ""
}
val := tags[definitionType]
if val != "" {
return val
}
return tags[definition.UserPrefix+definitionType]
}
// ConfigCatalog will get configCatalog value from tags
func ConfigCatalog(tags map[string]string) string {
if tags == nil {
return ""
}
val := tags[configCatalog]
if val != "" {
return val
}
return tags[definition.UserPrefix+configCatalog]
}

View File

@@ -0,0 +1,50 @@
/*
Copyright 2022 The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package service
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestCompatiblleTag(t *testing.T) {
tg := map[string]string{
"alias.config.oam.dev": "abc",
"type.config.oam.dev": "image-registry",
"catalog.config.oam.dev": "cata",
}
tgOld := map[string]string{
"custom.definition.oam.dev/alias.config.oam.dev": "abc-2",
"custom.definition.oam.dev/type.config.oam.dev": "image-registry-2",
"custom.definition.oam.dev/catalog.config.oam.dev": "cata-2",
}
assert.Equal(t, DefinitionAlias(nil), "")
assert.Equal(t, DefinitionAlias(tg), "abc")
assert.Equal(t, DefinitionAlias(tgOld), "abc-2")
assert.Equal(t, DefinitionType(nil), "")
assert.Equal(t, DefinitionType(tg), "image-registry")
assert.Equal(t, DefinitionType(tgOld), "image-registry-2")
assert.Equal(t, ConfigCatalog(nil), "")
assert.Equal(t, ConfigCatalog(tg), "cata")
assert.Equal(t, ConfigCatalog(tgOld), "cata-2")
}

View File

@@ -29,7 +29,7 @@ import (
apisv1 "github.com/oam-dev/kubevela/pkg/apiserver/interfaces/api/dto/v1"
"github.com/oam-dev/kubevela/pkg/apiserver/utils/bcode"
"github.com/oam-dev/kubevela/pkg/apiserver/utils/log"
utils2 "github.com/oam-dev/kubevela/pkg/utils"
pkgUtils "github.com/oam-dev/kubevela/pkg/utils"
)
const (
@@ -151,7 +151,7 @@ func (u *userServiceImpl) DeleteUser(ctx context.Context, username string) error
}
}
if err := u.Store.Delete(ctx, &model.User{Name: username}); err != nil {
log.Logger.Errorf("failed to delete user %s %v", utils2.Sanitize(username), err.Error())
log.Logger.Errorf("failed to delete user %s %v", pkgUtils.Sanitize(username), err.Error())
return err
}
return nil

View File

@@ -41,7 +41,7 @@ import (
"github.com/oam-dev/kubevela/pkg/apiserver/utils/log"
"github.com/oam-dev/kubevela/pkg/oam"
"github.com/oam-dev/kubevela/pkg/oam/util"
utils2 "github.com/oam-dev/kubevela/pkg/utils"
pkgUtils "github.com/oam-dev/kubevela/pkg/utils"
"github.com/oam-dev/kubevela/pkg/utils/apply"
wfTypes "github.com/oam-dev/kubevela/pkg/workflow/types"
)
@@ -91,7 +91,7 @@ func (w *workflowServiceImpl) DeleteWorkflow(ctx context.Context, app *model.App
}
records, err := w.Store.List(ctx, &record, &datastore.ListOptions{})
if err != nil {
log.Logger.Errorf("list workflow %s record failure %s", utils2.Sanitize(workflow.PrimaryKey()), err.Error())
log.Logger.Errorf("list workflow %s record failure %s", pkgUtils.Sanitize(workflow.PrimaryKey()), err.Error())
}
for _, record := range records {
if err := w.Store.Delete(ctx, record); err != nil {
@@ -186,7 +186,7 @@ func (w *workflowServiceImpl) CreateOrUpdateWorkflow(ctx context.Context, app *m
EnvName: req.EnvName,
AppPrimaryKey: app.PrimaryKey(),
}
log.Logger.Infof("create workflow %s for app %s", utils2.Sanitize(req.Name), utils2.Sanitize(app.PrimaryKey()))
log.Logger.Infof("create workflow %s for app %s", pkgUtils.Sanitize(req.Name), pkgUtils.Sanitize(app.PrimaryKey()))
if err := w.Store.Add(ctx, workflow); err != nil {
return nil, err
}

View File

@@ -284,7 +284,11 @@ func (def *Definition) FromCUE(val *cue.Value, templateString string) error {
return err
}
for _k, _v := range _annotations {
annotations[UserPrefix+_k] = _v
if strings.Contains(_k, "oam.dev") {
annotations[_k] = _v
} else {
annotations[UserPrefix+_k] = _v
}
}
case "labels":
var _labels map[string]string
@@ -292,7 +296,11 @@ func (def *Definition) FromCUE(val *cue.Value, templateString string) error {
return err
}
for _k, _v := range _labels {
labels[UserPrefix+_k] = _v
if strings.Contains(_k, "oam.dev") {
labels[_k] = _v
} else {
labels[UserPrefix+_k] = _v
}
}
case "attributes":
if err := codec.Encode(_value, &spec); err != nil {

View File

@@ -27,6 +27,7 @@ import (
"os/exec"
"path/filepath"
"testing"
"time"
"cuelang.org/go/cue"
"cuelang.org/go/cue/load"
@@ -192,6 +193,7 @@ func TestHttpGetCaFile(t *testing.T) {
err := testServer.ListenAndServeTLS("./testdata/server.crt", "./testdata/server.key")
assert.NoError(t, err)
}()
time.Sleep(time.Millisecond)
caFile, err := ioutil.ReadFile("./testdata/server.crt")
assert.NoError(t, err)

75
pkg/utils/load.go Normal file
View File

@@ -0,0 +1,75 @@
/*
Copyright 2022 The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package utils
import (
"context"
j "encoding/json"
"io/ioutil"
"os"
"path/filepath"
"github.com/oam-dev/kubevela/pkg/utils/common"
)
// ReadRemoteOrLocalPath will read a path remote or locally
func ReadRemoteOrLocalPath(pathOrURL string, saveLocal bool) ([]byte, error) {
var data []byte
var err error
switch {
case pathOrURL == "-":
data, err = ioutil.ReadAll(os.Stdin)
if err != nil {
return nil, err
}
case IsValidURL(pathOrURL):
data, err = common.HTTPGetWithOption(context.Background(), pathOrURL, nil)
if err != nil {
return nil, err
}
default:
data, err = os.ReadFile(filepath.Clean(pathOrURL))
if err != nil {
return nil, err
}
}
if saveLocal {
if err = localSave(pathOrURL, data); err != nil {
return nil, err
}
}
return data, nil
}
func localSave(url string, body []byte) error {
var name string
ext := filepath.Ext(url)
switch ext {
case ".json":
name = "vela.json"
case ".yaml", ".yml":
name = "vela.yaml"
default:
if j.Valid(body) {
name = "vela.json"
} else {
name = "vela.yaml"
}
}
//nolint:gosec
return os.WriteFile(name, body, 0644)
}

49
pkg/utils/load_test.go Normal file
View File

@@ -0,0 +1,49 @@
/*
Copyright 2022 The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package utils
import (
"fmt"
"net/http"
"os"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestReadRemoteOrLocalPath(t *testing.T) {
go func() {
svr := http.NewServeMux()
svr.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, `{"Outputs":{"Chinese":"输出"}}`)
})
http.ListenAndServe(":65503", svr)
}()
time.Sleep(time.Millisecond)
_, err := ReadRemoteOrLocalPath("http://127.0.0.1:65503", false)
assert.NoError(t, err)
_, err = ReadRemoteOrLocalPath("http://127.0.0.1:65503", true)
assert.NoError(t, err)
_, err = ReadRemoteOrLocalPath("vela.json", false)
assert.NoError(t, err)
err = os.Remove("vela.json")
assert.NoError(t, err)
}

View File

@@ -24,7 +24,7 @@ import (
"github.com/pkg/errors"
"github.com/oam-dev/kubevela/pkg/cue/model/value"
"github.com/oam-dev/kubevela/references/common"
"github.com/oam-dev/kubevela/pkg/utils"
)
// QueryView contains query data
@@ -98,7 +98,7 @@ func ParseVelaQL(ql string) (QueryView, error) {
// ParseVelaQLFromPath will parse a velaQL file path to QueryView
func ParseVelaQLFromPath(velaQLViewPath string) (*QueryView, error) {
body, err := common.ReadRemoteOrLocalPath(velaQLViewPath)
body, err := utils.ReadRemoteOrLocalPath(velaQLViewPath, false)
if err != nil {
return nil, errors.Errorf("read view file from %s: %v", velaQLViewPath, err)
}

View File

@@ -45,7 +45,6 @@ import (
"github.com/oam-dev/kubevela/pkg/workflow/tasks"
"github.com/oam-dev/kubevela/pkg/workflow/tasks/template"
wfTypes "github.com/oam-dev/kubevela/pkg/workflow/types"
refcommon "github.com/oam-dev/kubevela/references/common"
)
const (
@@ -197,7 +196,7 @@ func ParseViewIntoConfigMap(viewStr, name string) (*v1.ConfigMap, error) {
//
// By saying file, it can actually be a file, URL, or stdin (-).
func StoreViewFromFile(ctx context.Context, c client.Client, path, viewName string) error {
content, err := refcommon.ReadRemoteOrLocalPath(path)
content, err := utils.ReadRemoteOrLocalPath(path, false)
if err != nil {
return errors.Errorf("cannot load cue file: %v", err)
}

View File

@@ -27,6 +27,8 @@ import (
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
"github.com/oam-dev/kubevela/pkg/oam"
"github.com/oam-dev/kubevela/pkg/utils/util"
"github.com/oam-dev/kubevela/references/appfile/api"
)
// Run will deploy OAM objects and other assistant K8s Objects including ConfigMap, OAM Scope Custom Resource.
@@ -85,3 +87,12 @@ func CreateOrUpdateApplication(ctx context.Context, client client.Client, app *v
app.ResourceVersion = geta.ResourceVersion
return client.Update(ctx, app)
}
// BuildRun will build application and deploy from Appfile
func BuildRun(ctx context.Context, app *api.Application, client client.Client, namespace string, io util.IOStreams) error {
o, err := app.ConvertToApplication(namespace, io, app.Tm, true)
if err != nil {
return err
}
return Run(ctx, client, o, nil)
}

View File

@@ -205,7 +205,6 @@ func NewAddonEnableCommand(c common.Args, ioStream cmdutil.IOStreams) *cobra.Com
// AdditionalEndpointPrinter will print endpoints
func AdditionalEndpointPrinter(ctx context.Context, c common.Args, k8sClient client.Client, name string, isUpgrade bool) {
fmt.Printf("Please access %s from the following endpoints:\n", name)
err := printAppEndpoints(ctx, addonutil.Addon2AppName(name), types.DefaultKubeVelaNS, Filter{}, c, true)
if err != nil {
fmt.Println("Get application endpoints error:", err)

View File

@@ -107,7 +107,7 @@ func NewCommandWithIOStreams(ioStream util.IOStreams) *cobra.Command {
// Extension
NewAddonCommand(commandArgs, "9", ioStream),
NewUISchemaCommand(commandArgs, "8", ioStream),
DefinitionCommandGroup(commandArgs, "7"),
DefinitionCommandGroup(commandArgs, "7", ioStream),
NewRegistryCommand(ioStream, "6"),
NewTraitCommand(commandArgs, ioStream),
NewComponentsCommand(commandArgs, ioStream),

View File

@@ -56,8 +56,7 @@ import (
addonutil "github.com/oam-dev/kubevela/pkg/utils/addon"
"github.com/oam-dev/kubevela/pkg/utils/common"
"github.com/oam-dev/kubevela/pkg/utils/filters"
clicom "github.com/oam-dev/kubevela/references/common"
"github.com/oam-dev/kubevela/references/plugins"
cmdutil "github.com/oam-dev/kubevela/pkg/utils/util"
)
const (
@@ -68,7 +67,7 @@ const (
)
// DefinitionCommandGroup create the command group for `vela def` command to manage definitions
func DefinitionCommandGroup(c common.Args, order string) *cobra.Command {
func DefinitionCommandGroup(c common.Args, order string, ioStreams cmdutil.IOStreams) *cobra.Command {
cmd := &cobra.Command{
Use: "def",
Short: "Manage Definitions",
@@ -87,7 +86,8 @@ func DefinitionCommandGroup(c common.Args, order string) *cobra.Command {
NewDefinitionDelCommand(c),
NewDefinitionInitCommand(c),
NewDefinitionValidateCommand(c),
NewDefinitionGenDocCommand(c),
NewDefinitionGenDocCommand(c, ioStreams),
NewCapabilityShowCommand(c, ioStreams),
NewDefinitionGenAPICommand(c),
)
return cmd
@@ -115,7 +115,7 @@ func getPrompt(cmd *cobra.Command, reader *bufio.Reader, description string, pro
}
func buildTemplateFromYAML(templateYAML string, def *pkgdef.Definition) error {
templateYAMLBytes, err := clicom.ReadRemoteOrLocalPath(templateYAML)
templateYAMLBytes, err := utils.ReadRemoteOrLocalPath(templateYAML, false)
if err != nil {
return errors.Wrapf(err, "failed to get template YAML file %s", templateYAML)
}
@@ -511,65 +511,42 @@ func NewDefinitionGetCommand(c common.Args) *cobra.Command {
},
}
cmd.Flags().StringP(FlagType, "t", "", "Specify which definition type to get. If empty, all types will be searched. Valid types: "+strings.Join(pkgdef.ValidDefinitionTypes(), ", "))
cmd.Flags().StringP(Namespace, "n", "", "Specify which namespace to get. If empty, all namespaces will be searched.")
cmd.Flags().BoolVarP(&listRevisions, "revisions", "", false, "List revisions of the specified definition.")
cmd.Flags().StringVarP(&targetRevision, "revision", "r", "", "Get the specified version of a definition.")
cmd.Flags().StringP(Namespace, "n", types.DefaultKubeVelaNS, "Specify which namespace the definition locates.")
return cmd
}
// NewDefinitionGenDocCommand create the `vela def doc-gen` command to generate documentation of definitions
func NewDefinitionGenDocCommand(c common.Args) *cobra.Command {
func NewDefinitionGenDocCommand(c common.Args, ioStreams cmdutil.IOStreams) *cobra.Command {
var path, location, i18nPath string
cmd := &cobra.Command{
Use: "doc-gen NAME",
Short: "Generate documentation of definitions (Only Terraform typed definitions are supported)",
Long: "Generate documentation of definitions",
Example: "1. Generate documentation for ComponentDefinition alibaba-vpc:\n" +
"> vela def doc-gen alibaba-vpc -n vela-system\n" +
"2. Generate documentation for local ComponentDefinition file alibaba-vpc.yaml:\n" +
Short: "Generate documentation for definitions",
Long: "Generate documentation for definitions",
Example: "1. Generate documentation for ComponentDefinition webservice:\n" +
"> vela def doc-gen webservice -n vela-system\n" +
"2. Generate documentation for local CUE Definition file webservice.cue:\n" +
"> vela def doc-gen webservice.cue\n" +
"3. Generate documentation for local Cloud Resource Definition YAML alibaba-vpc.yaml:\n" +
"> vela def doc-gen alibaba-vpc.yaml\n",
Deprecated: "This command has been replaced by 'vela show' or 'vela def show'.",
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return fmt.Errorf("please specify definition name or a definition file")
return fmt.Errorf("please specify definition name, cue file or a cloud resource definition yaml")
}
ref := &plugins.MarkdownReference{}
if strings.HasSuffix(args[0], ".yaml") {
// read from local file
localFilePath := args[0]
fileName := filepath.Base(localFilePath)
if !strings.HasSuffix(fileName, ".yaml") {
return fmt.Errorf("invalid local file path `%s`", localFilePath)
}
ref.DefinitionName = strings.TrimSuffix(fileName, ".yaml")
ref.Local = &plugins.Local{Path: localFilePath}
} else {
namespace, err := cmd.Flags().GetString(FlagNamespace)
if err != nil {
return errors.Wrapf(err, "failed to get `%s`", Namespace)
}
ref.DefinitionName = args[0]
ref.Remote = &plugins.Remote{Namespace: namespace}
namespace, err := cmd.Flags().GetString(FlagNamespace)
if err != nil {
return errors.Wrapf(err, "failed to get `%s`", Namespace)
}
return ShowReferenceMarkdown(context.Background(), c, ioStreams, args[0], path, location, i18nPath, namespace, 0)
ctx := context.Background()
pathEn := plugins.KubeVelaIOTerraformPath
ref.I18N = plugins.En
if err := ref.GenerateReferenceDocs(ctx, c, pathEn); err != nil {
return errors.Wrap(err, "failed to generate reference docs")
}
cmd.Printf("Generated docs in English for %s in %s/%s.md\n", args[0], pathEn, ref.DefinitionName)
pathZh := plugins.KubeVelaIOTerraformPathZh
ref.I18N = plugins.Zh
if err := ref.GenerateReferenceDocs(ctx, c, pathZh); err != nil {
return errors.Wrap(err, "failed to generate reference docs")
}
cmd.Printf("Generated docs in Chinese for %s in %s/%s.md\n", args[0], pathZh, ref.DefinitionName)
return nil
},
}
cmd.Flags().StringP(Namespace, "n", "", "Specify the namespace of the definition.")
cmd.Flags().StringVarP(&path, "path", "p", "", "Specify the path for of the doc generated from definition.")
cmd.Flags().StringVarP(&location, "location", "l", "", "specify the location for of the doc generated from definition, now supported options 'zh', 'en'. ")
cmd.Flags().StringP(Namespace, "n", types.DefaultKubeVelaNS, "Specify which namespace the definition locates.")
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'. ")
return cmd
}
@@ -654,8 +631,8 @@ func NewDefinitionListCommand(c common.Args) *cobra.Command {
},
}
cmd.Flags().StringP(FlagType, "t", "", "Specify which definition type to list. If empty, all types will be searched. Valid types: "+strings.Join(pkgdef.ValidDefinitionTypes(), ", "))
cmd.Flags().StringP(Namespace, "n", "", "Specify which namespace to list. If empty, all namespaces will be searched.")
cmd.Flags().String("from", "", "Filter definitions by which addon installed them.")
cmd.Flags().StringP(Namespace, "n", types.DefaultKubeVelaNS, "Specify which namespace the definition locates.")
return cmd
}
@@ -743,7 +720,7 @@ func NewDefinitionEditCommand(c common.Args) *cobra.Command {
},
}
cmd.Flags().StringP(FlagType, "t", "", "Specify which definition type to get. If empty, all types will be searched. Valid types: "+strings.Join(pkgdef.ValidDefinitionTypes(), ", "))
cmd.Flags().StringP(Namespace, "n", "", "Specify which namespace to get. If empty, all namespaces will be searched.")
cmd.Flags().StringP(Namespace, "n", types.DefaultKubeVelaNS, "Specify which namespace the definition locates.")
return cmd
}
@@ -782,7 +759,7 @@ func NewDefinitionRenderCommand(c common.Args) *cobra.Command {
}
render := func(inputFilename, outputFilename string) error {
cueBytes, err := clicom.ReadRemoteOrLocalPath(inputFilename)
cueBytes, err := utils.ReadRemoteOrLocalPath(inputFilename, false)
if err != nil {
return errors.Wrapf(err, "failed to get %s", args[0])
}
@@ -901,7 +878,7 @@ func NewDefinitionApplyCommand(c common.Args) *cobra.Command {
return errors.Wrapf(err, "failed to get k8s client")
}
defpath := args[0]
defBytes, err := clicom.ReadRemoteOrLocalPath(defpath)
defBytes, err := utils.ReadRemoteOrLocalPath(defpath, false)
if err != nil {
return errors.Wrapf(err, "failed to get from %s", defpath)
}
@@ -967,7 +944,7 @@ func NewDefinitionApplyCommand(c common.Args) *cobra.Command {
},
}
cmd.Flags().BoolP(FlagDryRun, "", false, "only build definition from CUE into CRB object without applying it to kubernetes clusters")
cmd.Flags().StringP(Namespace, "n", "vela-system", "Specify which namespace to apply.")
cmd.Flags().StringP(Namespace, "n", types.DefaultKubeVelaNS, "Specify which namespace the definition locates.")
return cmd
}
@@ -1032,7 +1009,7 @@ func NewDefinitionDelCommand(c common.Args) *cobra.Command {
},
}
cmd.Flags().StringP(FlagType, "t", "", "Specify the definition type of target. Valid types: "+strings.Join(pkgdef.ValidDefinitionTypes(), ", "))
cmd.Flags().StringP(Namespace, "n", "", "Specify which namespace the definition locates.")
cmd.Flags().StringP(Namespace, "n", types.DefaultKubeVelaNS, "Specify which namespace the definition locates.")
return cmd
}

View File

@@ -40,6 +40,7 @@ import (
pkgdef "github.com/oam-dev/kubevela/pkg/definition"
addonutil "github.com/oam-dev/kubevela/pkg/utils/addon"
common2 "github.com/oam-dev/kubevela/pkg/utils/common"
cmdutil "github.com/oam-dev/kubevela/pkg/utils/util"
)
const (
@@ -179,7 +180,7 @@ func removeDir(dirname string, t *testing.T) {
}
func TestNewDefinitionCommandGroup(t *testing.T) {
cmd := DefinitionCommandGroup(common2.Args{}, "")
cmd := DefinitionCommandGroup(common2.Args{}, "", cmdutil.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr})
initCommand(cmd)
cmd.SetArgs([]string{"-h"})
if err := cmd.Execute(); err != nil {
@@ -396,7 +397,7 @@ func TestNewDefinitionGetCommand(t *testing.T) {
cmd := NewDefinitionGetCommand(c)
initCommand(cmd)
traitName := createTrait(c, t)
cmd.SetArgs([]string{traitName})
cmd.SetArgs([]string{traitName, "-n" + VelaTestNamespace})
if err := cmd.Execute(); err != nil {
t.Fatalf("unexpeced error when executing get command: %v", err)
}
@@ -459,7 +460,7 @@ func TestNewDefinitionGetCommand(t *testing.T) {
func TestNewDefinitionGenDocCommand(t *testing.T) {
c := initArgs()
cmd := NewDefinitionGenDocCommand(c)
cmd := NewDefinitionGenDocCommand(c, cmdutil.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr})
assert.NotNil(t, cmd.Execute())
cmd.SetArgs([]string{"alibaba-xxxxxxx"})
@@ -501,7 +502,7 @@ func TestNewDefinitionEditCommand(t *testing.T) {
if err := os.Setenv("EDITOR", "sed -i -e 's/test-trait/TestTrait/g'"); err != nil {
t.Fatalf("failed to set editor env: %v", err)
}
cmd.SetArgs([]string{traitName})
cmd.SetArgs([]string{traitName, "-n", VelaTestNamespace})
if err := cmd.Execute(); err != nil {
t.Fatalf("unexpeced error when executing edit command: %v", err)
}
@@ -585,7 +586,7 @@ func TestNewDefinitionDelCommand(t *testing.T) {
traitName := createTrait(c, t)
reader := strings.NewReader("yes\n")
cmd.SetIn(reader)
cmd.SetArgs([]string{traitName})
cmd.SetArgs([]string{traitName, "-n", VelaTestNamespace})
if err := cmd.Execute(); err != nil {
t.Fatalf("unexpeced error when executing del command: %v", err)
}

View File

@@ -35,9 +35,9 @@ import (
"github.com/oam-dev/kubevela/pkg/oam"
"github.com/oam-dev/kubevela/pkg/oam/discoverymapper"
oamutil "github.com/oam-dev/kubevela/pkg/oam/util"
"github.com/oam-dev/kubevela/pkg/utils"
"github.com/oam-dev/kubevela/pkg/utils/common"
cmdutil "github.com/oam-dev/kubevela/pkg/utils/util"
clicom "github.com/oam-dev/kubevela/references/common"
)
// DryRunCmdOptions contains dry-run cmd options
@@ -200,7 +200,7 @@ func ReadObjectsFromFile(path string) ([]oam.Object, error) {
}
func readApplicationFromFile(filename string) (*corev1beta1.Application, error) {
fileContent, err := clicom.ReadRemoteOrLocalPath(filename)
fileContent, err := utils.ReadRemoteOrLocalPath(filename, true)
if err != nil {
return nil, err
}

View File

@@ -107,7 +107,7 @@ func NewInitCommand(c common2.Args, order string, ioStreams cmdutil.IOStreams) *
}
ctx := context.Background()
err = common.BuildRun(ctx, o.app, o.client, o.Namespace, o.IOStreams)
err = appfile.BuildRun(ctx, o.app, o.client, o.Namespace, o.IOStreams)
if err != nil {
return err
}

View File

@@ -36,10 +36,8 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"sigs.k8s.io/yaml"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
"github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/oam/discoverymapper"
"github.com/oam-dev/kubevela/pkg/oam/util"
"github.com/oam-dev/kubevela/pkg/utils/common"
"github.com/oam-dev/kubevela/pkg/utils/system"
cmdutil "github.com/oam-dev/kubevela/pkg/utils/util"
@@ -743,33 +741,5 @@ func ParseCapability(mapper discoverymapper.DiscoveryMapper, data []byte) (types
if err != nil {
return types.Capability{}, err
}
switch obj.GetKind() {
case "ComponentDefinition":
var cd v1beta1.ComponentDefinition
err = yaml.Unmarshal(data, &cd)
if err != nil {
return types.Capability{}, err
}
var workloadDefinitionRef string
if cd.Spec.Workload.Type != "" {
workloadDefinitionRef = cd.Spec.Workload.Type
} else {
ref, err := util.ConvertWorkloadGVK2Definition(mapper, cd.Spec.Workload.Definition)
if err != nil {
return types.Capability{}, err
}
workloadDefinitionRef = ref.Name
}
return plugins.HandleDefinition(cd.Name, workloadDefinitionRef, cd.Annotations, cd.Labels, cd.Spec.Extension, types.TypeComponentDefinition, nil, cd.Spec.Schematic, nil)
case "TraitDefinition":
var td v1beta1.TraitDefinition
err = yaml.Unmarshal(data, &td)
if err != nil {
return types.Capability{}, err
}
return plugins.HandleDefinition(td.Name, td.Spec.Reference.Name, td.Annotations, td.Labels, td.Spec.Extension, types.TypeTrait, td.Spec.AppliesToWorkloads, td.Spec.Schematic, nil)
case "ScopeDefinition":
// TODO(wonderflow): support scope definition here.
}
return types.Capability{}, fmt.Errorf("unknown definition Type %s", obj.GetKind())
return plugins.ParseCapabilityFromUnstructured(mapper, obj)
}

View File

@@ -18,7 +18,6 @@ package cli
import (
"context"
"errors"
"fmt"
"net/http"
"os"
@@ -31,6 +30,7 @@ import (
"syscall"
"time"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/oam-dev/kubevela/apis/types"
@@ -60,51 +60,71 @@ const (
)
var webSite bool
var showFormat string
// NewCapabilityShowCommand shows the reference doc for a component type or trait
func NewCapabilityShowCommand(c common.Args, ioStreams cmdutil.IOStreams) *cobra.Command {
var revision string
var revision, path, location, i18nPath string
cmd := &cobra.Command{
Use: "show",
Short: "Show the reference doc for a component, trait or workflow.",
Long: "Show the reference doc for component, trait or workflow types.",
Example: `show webservice`,
Use: "show",
Short: "Show the reference doc for a component, trait, policy or workflow.",
Long: "Show the reference doc for component, trait, policy or workflow types. 'vela show' equals with 'vela def show'. ",
Example: `0. Run 'vela show' directly to start a web server for all reference docs.
1. Generate documentation for ComponentDefinition webservice:
> vela show webservice -n vela-system
2. Generate documentation for local CUE Definition file webservice.cue:
> vela show webservice.cue
3. Generate documentation for local Cloud Resource Definition YAML alibaba-vpc.yaml:
> vela show alibaba-vpc.yaml
4. Specify output format, markdown supported:
> vela show webservice --format markdown
5. Specify a language for output, by default, it's english. You can also load your own translation script:
> vela show webservice --location zh
> vela show webservice --location zh --i18n https://kubevela.io/reference-i18n.json
6. Show doc for a specified revision, it must exist in control plane cluster:
> vela show webservice --revision v1
`,
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return fmt.Errorf("please specify a component type or trait")
}
ctx := context.Background()
capabilityName := args[0]
var capabilityName string
if len(args) > 0 {
capabilityName = args[0]
} else {
cmd.Println("opening a browser for all capability docs...")
webSite = true
}
namespace, err := GetFlagNamespaceOrEnv(cmd, c)
if err != nil {
return err
}
if revision == "" {
if webSite {
return startReferenceDocsSite(ctx, namespace, c, ioStreams, capabilityName)
var ver int
if revision != "" {
// v1, 1, both need to work
version := strings.TrimPrefix(revision, "v")
ver, err = strconv.Atoi(version)
if err != nil {
return fmt.Errorf("invalid revision: %w", err)
}
return ShowReferenceConsole(ctx, c, ioStreams, capabilityName, namespace, 0)
}
// v1, 1, both need to work
version := strings.TrimPrefix(revision, "v")
ver, err := strconv.Atoi(version)
if err != nil {
return fmt.Errorf("invalid revision: %w", err)
}
if webSite {
return startReferenceDocsSite(ctx, namespace, c, ioStreams, capabilityName)
}
return ShowReferenceConsole(ctx, c, ioStreams, capabilityName, namespace, int64(ver))
if path != "" || showFormat == "md" || showFormat == "markdown" {
return ShowReferenceMarkdown(ctx, c, ioStreams, capabilityName, path, location, i18nPath, namespace, int64(ver))
}
return ShowReferenceConsole(ctx, c, ioStreams, capabilityName, namespace, location, i18nPath, int64(ver))
},
Annotations: map[string]string{
types.TagCommandType: types.TypeStart,
},
}
cmd.Flags().BoolVarP(&webSite, "web", "", false, " start web doc site")
cmd.Flags().BoolVarP(&webSite, "web", "", false, "start web doc site")
cmd.Flags().StringVarP(&showFormat, "format", "", "", "specify format of output data, by default it's a pretty human readable format, you can specify markdown(md)")
cmd.Flags().StringVarP(&revision, "revision", "r", "", "Get the specified revision of a definition. Use def get to list revisions.")
cmd.Flags().StringVarP(&path, "path", "p", "", "Specify the path for of the doc generated from definition.")
cmd.Flags().StringVarP(&location, "location", "l", "", "specify the location for of the doc generated from definition, now supported options 'zh', 'en'. ")
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'. ")
addNamespaceAndEnvArg(cmd)
cmd.SetOut(ioStreams.Out)
@@ -132,6 +152,10 @@ func startReferenceDocsSite(ctx context.Context, ns string, c common.Args, ioStr
var capabilityIsValid bool
var capabilityType types.CapType
for _, c := range capabilities {
if capabilityName == "" {
capabilityIsValid = true
break
}
if c.Name == capabilityName {
capabilityIsValid = true
capabilityType = c.Type
@@ -139,7 +163,7 @@ func startReferenceDocsSite(ctx context.Context, ns string, c common.Args, ioStr
}
}
if !capabilityIsValid {
return fmt.Errorf("%s is not a valid component, trait or workflow", capabilityName)
return fmt.Errorf("%s is not a valid component, trait, policy or workflow", capabilityName)
}
cli, err := c.GetClient()
@@ -149,6 +173,7 @@ func startReferenceDocsSite(ctx context.Context, ns string, c common.Args, ioStr
ref := &plugins.MarkdownReference{
ParseReference: plugins.ParseReference{
Client: cli,
I18N: &plugins.En,
},
}
@@ -160,7 +185,7 @@ func startReferenceDocsSite(ctx context.Context, ns string, c common.Args, ioStr
if err != nil {
return err
}
if err := ref.CreateMarkdown(ctx, capabilities, docsPath, plugins.ReferenceSourcePath, pd); err != nil {
if err := ref.CreateMarkdown(ctx, capabilities, docsPath, true, pd); err != nil {
return err
}
@@ -184,11 +209,14 @@ func startReferenceDocsSite(ctx context.Context, ns string, c common.Args, ioStr
}
if capabilityType != types.TypeWorkload && capabilityType != types.TypeTrait && capabilityType != types.TypeScope &&
capabilityType != types.TypeComponentDefinition && capabilityType != types.TypeWorkflowStep {
capabilityType != types.TypeComponentDefinition && capabilityType != types.TypeWorkflowStep && capabilityType != "" {
return fmt.Errorf("unsupported type: %v", capabilityType)
}
url := fmt.Sprintf("http://127.0.0.1%s/#/%s/%s", Port, capabilityType, capabilityName)
var suffix = capabilityName
if suffix != "" {
suffix = "/" + suffix
}
url := fmt.Sprintf("http://127.0.0.1%s/#/%s%s", Port, capabilityType, suffix)
server := &http.Server{
Addr: Port,
Handler: http.FileServer(http.Dir(docsPath)),
@@ -298,7 +326,7 @@ func generateIndexHTML(docsPath string) error {
<div id="app"></div>
<script>
window.$docsify = {
name: 'KubeVela Reference Docs',
name: 'KubeVela Customized Reference Docs',
loadSidebar: true,
loadNavbar: true,
subMaxLevel: 1,
@@ -402,73 +430,64 @@ func getDefinitions(capabilities []types.Capability) ([]string, []string, []stri
}
// ShowReferenceConsole will show capability reference in console
func ShowReferenceConsole(ctx context.Context, c common.Args, ioStreams cmdutil.IOStreams, capabilityName string, ns string, rev int64) error {
config, err := c.GetConfig()
if err != nil {
return err
}
pd, err := packages.NewPackageDiscover(config)
if err != nil {
return err
}
var capability *types.Capability
if rev == 0 {
capability, err = plugins.GetCapabilityByName(ctx, c, capabilityName, ns, pd)
if err != nil {
return err
}
} else {
capability, err = plugins.GetCapabilityFromDefinitionRevision(ctx, c, pd, ns, capabilityName, rev)
if err != nil {
return err
}
}
func ShowReferenceConsole(ctx context.Context, c common.Args, ioStreams cmdutil.IOStreams, capabilityName string, ns, location, i18nPath string, rev int64) error {
cli, err := c.GetClient()
if err != nil {
return err
}
ref := &plugins.ConsoleReference{
ParseReference: plugins.ParseReference{
Client: cli,
},
ref := &plugins.ConsoleReference{}
paserRef, err := genRefParser(capabilityName, ns, location, i18nPath, rev)
if err != nil {
return err
}
paserRef.Client = cli
ref.ParseReference = paserRef
return ref.Show(ctx, c, ioStreams, capabilityName, ns, rev)
}
var propertyConsole []plugins.ConsoleReference
switch capability.Category {
case types.HelmCategory:
_, propertyConsole, err = ref.GenerateHelmAndKubeProperties(ctx, capability)
if err != nil {
return err
}
case types.KubeCategory:
_, propertyConsole, err = ref.GenerateHelmAndKubeProperties(ctx, capability)
if err != nil {
return err
}
case types.CUECategory:
propertyConsole, err = ref.GenerateCUETemplateProperties(capability, pd)
if err != nil {
return err
}
case types.TerraformCategory:
propertyConsole, err = ref.GenerateTerraformCapabilityProperties(*capability)
if err != nil {
return err
}
default:
return fmt.Errorf("unsupport capability category %s", capability.Category)
// ShowReferenceMarkdown will show capability in "markdown" format
func ShowReferenceMarkdown(ctx context.Context, c common.Args, ioStreams cmdutil.IOStreams, capabilityNameOrPath, outputPath, location, i18nPath, ns string, rev int64) error {
ref := &plugins.MarkdownReference{}
paserRef, err := genRefParser(capabilityNameOrPath, ns, location, i18nPath, rev)
if err != nil {
return err
}
for _, p := range propertyConsole {
ioStreams.Info(p.TableName)
p.TableObject.Render()
ioStreams.Info("\n")
ref.ParseReference = paserRef
if err := ref.GenerateReferenceDocs(ctx, c, outputPath); err != nil {
return errors.Wrap(err, "failed to generate reference docs")
}
if outputPath != "" {
ioStreams.Infof("Generated docs in %s for %s in %s/%s.md\n", ref.I18N, capabilityNameOrPath, outputPath, ref.DefinitionName)
}
return nil
}
func genRefParser(capabilityNameOrPath, ns, location, i18nPath string, rev int64) (plugins.ParseReference, error) {
ref := plugins.ParseReference{}
if location != "" {
plugins.LoadI18nData(i18nPath)
}
if strings.HasSuffix(capabilityNameOrPath, ".yaml") || strings.HasSuffix(capabilityNameOrPath, ".cue") {
// read from local file
localFilePath := capabilityNameOrPath
fileName := filepath.Base(localFilePath)
ref.DefinitionName = strings.TrimSuffix(strings.TrimSuffix(fileName, ".yaml"), ".cue")
ref.Local = &plugins.FromLocal{Path: localFilePath}
} else {
ref.DefinitionName = capabilityNameOrPath
ref.Remote = &plugins.FromCluster{Namespace: ns, Rev: rev}
}
switch strings.ToLower(location) {
case "zh", "cn", "chinese":
ref.I18N = &plugins.Zh
case "", "en", "english":
ref.I18N = &plugins.En
default:
return ref, fmt.Errorf("unknown location %s for i18n translation", location)
}
return ref, nil
}
// OpenBrowser will open browser by url in different OS system
// nolint:gosec
func OpenBrowser(url string) error {

View File

@@ -178,6 +178,7 @@ func printAppEndpoints(ctx context.Context, appName string, namespace string, f
if skipEmptyTable && len(endpoints) == 0 {
return nil
}
fmt.Printf("Please access %s from the following endpoints:\n", appName)
table := tablewriter.NewWriter(os.Stdout)
table.SetColWidth(100)
table.SetHeader([]string{"Cluster", "Component", "Ref(Kind/Namespace/Name)", "Endpoint", "Inner"})

View File

@@ -39,6 +39,7 @@ import (
"github.com/oam-dev/kubevela/pkg/controller/core.oam.dev/v1alpha2/application"
"github.com/oam-dev/kubevela/pkg/controller/utils"
"github.com/oam-dev/kubevela/pkg/oam"
pkgUtils "github.com/oam-dev/kubevela/pkg/utils"
utilcommon "github.com/oam-dev/kubevela/pkg/utils/common"
"github.com/oam-dev/kubevela/pkg/utils/util"
"github.com/oam-dev/kubevela/references/common"
@@ -199,7 +200,7 @@ func addDebugPolicy(app *v1beta1.Application) {
func (opt *UpCommandOptions) deployApplicationFromFile(f velacmd.Factory, cmd *cobra.Command) error {
cli := f.Client()
body, err := common.ReadRemoteOrLocalPath(opt.File)
body, err := pkgUtils.ReadRemoteOrLocalPath(opt.File, true)
if err != nil {
return err
}

View File

@@ -21,9 +21,6 @@ import (
"context"
j "encoding/json"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"time"
"github.com/crossplane/crossplane-runtime/pkg/meta"
@@ -284,37 +281,13 @@ func (o *DeleteOptions) DeleteComponent(io cmdutil.IOStreams) error {
// LoadAppFile will load vela appfile from remote URL or local file system.
func LoadAppFile(pathOrURL string) (*api.AppFile, error) {
body, err := ReadRemoteOrLocalPath(pathOrURL)
body, err := utils.ReadRemoteOrLocalPath(pathOrURL, false)
if err != nil {
return nil, err
}
return api.LoadFromBytes(body)
}
// ReadRemoteOrLocalPath will read a path remote or locally
func ReadRemoteOrLocalPath(pathOrURL string) ([]byte, error) {
if pathOrURL == "-" {
return ioutil.ReadAll(os.Stdin)
}
var body []byte
var err error
if utils.IsValidURL(pathOrURL) {
body, err = common.HTTPGetWithOption(context.Background(), pathOrURL, nil)
if err != nil {
return nil, err
}
if err = localSave(pathOrURL, body); err != nil {
return nil, err
}
} else {
body, err = os.ReadFile(filepath.Clean(pathOrURL))
if err != nil {
return nil, err
}
}
return body, nil
}
// IsAppfile check if a file is Appfile format or application format, return true if it's appfile, false means application object
func IsAppfile(body []byte) bool {
if j.Valid(body) {
@@ -333,25 +306,6 @@ func IsAppfile(body []byte) bool {
return true
}
func localSave(url string, body []byte) error {
var name string
ext := filepath.Ext(url)
switch ext {
case ".json":
name = "vela.json"
case ".yaml", ".yml":
name = "vela.yaml"
default:
if j.Valid(body) {
name = "vela.json"
} else {
name = "vela.yaml"
}
}
//nolint:gosec
return os.WriteFile(name, body, 0644)
}
// ExportFromAppFile exports Application from appfile object
func (o *AppfileOptions) ExportFromAppFile(app *api.AppFile, namespace string, quiet bool, c common.Args) (*BuildResult, []byte, error) {
tm, err := template.Load(namespace, c)

View File

@@ -15,23 +15,3 @@ limitations under the License.
*/
package common
import (
"context"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/oam-dev/kubevela/pkg/utils/util"
"github.com/oam-dev/kubevela/references/appfile"
"github.com/oam-dev/kubevela/references/appfile/api"
)
// BuildRun will build application and deploy from Appfile
func BuildRun(ctx context.Context, app *api.Application, client client.Client, namespace string, io util.IOStreams) error {
o, err := app.ConvertToApplication(namespace, io, app.Tm, true)
if err != nil {
return err
}
return appfile.Run(ctx, client, o, nil)
}

View File

@@ -22,14 +22,12 @@ import (
"os"
"strings"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/oam-dev/kubevela/pkg/definition"
"github.com/pkg/errors"
kerrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/klog/v2"
"sigs.k8s.io/controller-runtime/pkg/client"
commontypes "github.com/oam-dev/kubevela/apis/core.oam.dev/common"
@@ -38,8 +36,10 @@ import (
"github.com/oam-dev/kubevela/pkg/appfile"
"github.com/oam-dev/kubevela/pkg/cue"
"github.com/oam-dev/kubevela/pkg/cue/packages"
"github.com/oam-dev/kubevela/pkg/definition"
"github.com/oam-dev/kubevela/pkg/oam/discoverymapper"
"github.com/oam-dev/kubevela/pkg/oam/util"
"github.com/oam-dev/kubevela/pkg/utils"
"github.com/oam-dev/kubevela/pkg/utils/common"
"github.com/oam-dev/kubevela/pkg/utils/helm"
util2 "github.com/oam-dev/kubevela/pkg/utils/util"
@@ -50,16 +50,42 @@ const DescriptionUndefined = "description not defined"
// GetCapabilitiesFromCluster will get capability from K8s cluster
func GetCapabilitiesFromCluster(ctx context.Context, namespace string, c common.Args, selector labels.Selector) ([]types.Capability, error) {
workloads, _, err := GetComponentsFromCluster(ctx, namespace, c, selector)
caps, erl, err := GetComponentsFromCluster(ctx, namespace, c, selector)
for _, er := range erl {
klog.Infof("get component capability %v", er)
}
if err != nil {
return nil, err
}
traits, _, err := GetTraitsFromCluster(ctx, namespace, c, selector)
traits, erl, err := GetTraitsFromCluster(ctx, namespace, c, selector)
if err != nil {
return nil, err
}
workloads = append(workloads, traits...)
return workloads, nil
for _, er := range erl {
klog.Infof("get trait capability %v", er)
}
caps = append(caps, traits...)
plcs, erl, err := GetPolicies(ctx, namespace, c)
if err != nil {
return nil, err
}
for _, er := range erl {
klog.Infof("get policy capability %v", er)
}
caps = append(caps, plcs...)
wfs, erl, err := GetWorkflowSteps(ctx, namespace, c)
if err != nil {
return nil, err
}
for _, er := range erl {
klog.Infof("get workflow step capability %v", er)
}
caps = append(caps, wfs...)
return caps, nil
}
// GetNamespacedCapabilitiesFromCluster will get capability from K8s cluster in the specified namespace and default namespace
@@ -297,6 +323,7 @@ func HandleDefinition(name, crdName string, annotation, labels map[string]string
}
tmp.CrdName = crdName
tmp.Description = GetDescription(annotation)
tmp.Example = GetExample(annotation)
tmp.Labels = labels
return tmp, nil
}
@@ -314,6 +341,28 @@ func GetDescription(annotation map[string]string) string {
return desc
}
// GetExample get example markdown from annotation specified url
func GetExample(annotation map[string]string) string {
if annotation == nil {
return ""
}
examplePath, ok := annotation[types.AnnoDefinitionExampleURL]
if !ok {
return ""
}
fmt.Println("XXXXX", examplePath)
if !utils.IsValidURL(examplePath) {
fmt.Println("XXyyy", examplePath)
return ""
}
data, err := common.HTTPGetWithOption(context.Background(), examplePath, nil)
if err != nil {
fmt.Println("XXyyy", err)
return ""
}
return string(data)
}
// HandleTemplate will handle definition template to capability
func HandleTemplate(in *runtime.RawExtension, schematic *commontypes.Schematic, name string, pd *packages.PackageDiscover) (types.Capability, error) {
tmp, err := appfile.ConvertTemplateJSON2Object(name, in, schematic)

View File

@@ -0,0 +1,167 @@
/*
Copyright 2021 The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package plugins
import (
"context"
"fmt"
"github.com/olekukonko/tablewriter"
"github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/cue/packages"
"github.com/oam-dev/kubevela/pkg/utils/common"
cmdutil "github.com/oam-dev/kubevela/pkg/utils/util"
)
// Int64Type is int64 type
type Int64Type = int64
// StringType is string type
type StringType = string
// BoolType is bool type
type BoolType = bool
// Reference is the struct for capability information
type Reference interface {
prepareParameter(tableName string, parameterList []ReferenceParameter) string
}
// FromCluster is the struct for input Namespace
type FromCluster struct {
Namespace string `json:"namespace"`
Rev int64 `json:"revision"`
PD *packages.PackageDiscover
}
// FromLocal is the struct for input Definition Path
type FromLocal struct {
Path string `json:"path"`
}
// ConsoleReference is the struct for capability information in console
type ConsoleReference struct {
ParseReference
TableName string `json:"tableName"`
TableObject *tablewriter.Table `json:"tableObject"`
}
// 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"`
// PrintableType is same to `parameter.Type` which could be printable
PrintableType string `json:"printableType"`
}
// ReferenceParameterTable stores the information of a bunch of ReferenceParameter in a table style
type ReferenceParameterTable struct {
Name string
Parameters []ReferenceParameter
Depth *int
}
var commonRefs []CommonReference
// GenerateCUETemplateProperties get all properties of a capability
func (ref *ConsoleReference) GenerateCUETemplateProperties(capability *types.Capability, pd *packages.PackageDiscover) (string, []ConsoleReference, error) {
ref.DisplayFormat = "console"
capName := capability.Name
cueValue, err := common.GetCUEParameterValue(capability.CueTemplate, pd)
if err != nil {
return "", nil, fmt.Errorf("failed to retrieve `parameters` value from %s with err: %w", capName, err)
}
var defaultDepth = 0
doc, console, err := ref.parseParameters(capName, cueValue, Specification, defaultDepth, false)
if err != nil {
return "", nil, err
}
return doc, console, nil
}
// GenerateTerraformCapabilityProperties generates Capability properties for Terraform ComponentDefinition in Cli console
func (ref *ConsoleReference) GenerateTerraformCapabilityProperties(capability types.Capability) ([]ConsoleReference, error) {
var references []ConsoleReference
variableTables, _, err := ref.parseTerraformCapabilityParameters(capability)
if err != nil {
return nil, err
}
for _, t := range variableTables {
references = append(references, ref.prepareConsoleParameter(t.Name, t.Parameters, types.TerraformCategory))
}
return references, nil
}
// Show will show capability reference in console
func (ref *ConsoleReference) Show(ctx context.Context, c common.Args, ioStreams cmdutil.IOStreams, capabilityName string, ns string, rev int64) error {
caps, err := ref.getCapabilities(ctx, c)
if err != nil {
return err
}
if len(caps) < 1 {
return fmt.Errorf("no capability found with name %s namespace %s", capabilityName, ns)
}
capability := &caps[0]
var propertyConsole []ConsoleReference
switch capability.Category {
case types.CUECategory:
var pd *packages.PackageDiscover
if ref.Remote != nil {
pd = ref.Remote.PD
}
_, propertyConsole, err = ref.GenerateCUETemplateProperties(capability, pd)
if err != nil {
return err
}
case types.TerraformCategory:
propertyConsole, err = ref.GenerateTerraformCapabilityProperties(*capability)
if err != nil {
return err
}
case types.HelmCategory, types.KubeCategory:
_, propertyConsole, err = ref.GenerateHelmAndKubeProperties(ctx, 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()
ioStreams.Info("\n")
}
return nil
}

View File

@@ -0,0 +1,75 @@
/*
Copyright 2022 The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package plugins
import (
"fmt"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
"github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/oam/discoverymapper"
"github.com/oam-dev/kubevela/pkg/oam/util"
)
// ParseCapabilityFromUnstructured will convert Unstructured to Capability
func ParseCapabilityFromUnstructured(mapper discoverymapper.DiscoveryMapper, obj unstructured.Unstructured) (types.Capability, error) {
var err error
switch obj.GetKind() {
case "ComponentDefinition":
var cd v1beta1.ComponentDefinition
err = runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, &cd)
if err != nil {
return types.Capability{}, err
}
var workloadDefinitionRef string
if cd.Spec.Workload.Type != "" {
workloadDefinitionRef = cd.Spec.Workload.Type
} else if mapper != nil {
ref, err := util.ConvertWorkloadGVK2Definition(mapper, cd.Spec.Workload.Definition)
if err != nil {
return types.Capability{}, err
}
workloadDefinitionRef = ref.Name
}
return HandleDefinition(cd.Name, workloadDefinitionRef, cd.Annotations, cd.Labels, cd.Spec.Extension, types.TypeComponentDefinition, nil, cd.Spec.Schematic, nil)
case "TraitDefinition":
var td v1beta1.TraitDefinition
err = runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, &td)
if err != nil {
return types.Capability{}, err
}
return HandleDefinition(td.Name, td.Spec.Reference.Name, td.Annotations, td.Labels, td.Spec.Extension, types.TypeTrait, td.Spec.AppliesToWorkloads, td.Spec.Schematic, nil)
case "PolicyDefinition":
var pd v1beta1.PolicyDefinition
err = runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, &pd)
if err != nil {
return types.Capability{}, err
}
return HandleDefinition(pd.Name, pd.Spec.Reference.Name, pd.Annotations, pd.Labels, nil, types.TypePolicy, nil, pd.Spec.Schematic, nil)
case "WorkflowStepDefinition":
var pd v1beta1.WorkflowStepDefinition
err = runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, &pd)
if err != nil {
return types.Capability{}, err
}
return HandleDefinition(pd.Name, pd.Spec.Reference.Name, pd.Annotations, pd.Labels, nil, types.TypeWorkflowStep, nil, pd.Spec.Schematic, nil)
}
return types.Capability{}, fmt.Errorf("unknown definition Type %s", obj.GetKind())
}

View File

@@ -0,0 +1,14 @@
```yaml
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: ack-cloud-source
spec:
components:
- name: ack-cluster
type: alibaba-ack
properties:
writeConnectionSecretToRef:
name: ack-conn
namespace: vela-system
```

View File

@@ -0,0 +1,13 @@
```yaml
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: provision-cloud-resource-eip
spec:
components:
- name: sample-eip
type: alibaba-eip
properties:
writeConnectionSecretToRef:
name: eip-conn
```

View File

@@ -0,0 +1,15 @@
```yaml
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: oss-cloud-source
spec:
components:
- name: sample-oss
type: alibaba-oss
properties:
bucket: vela-website
acl: private
writeConnectionSecretToRef:
name: oss-conn
```

View File

@@ -0,0 +1,16 @@
```yaml
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: rds-cloud-source
spec:
components:
- name: sample-db
type: alibaba-rds
properties:
instance_name: sample-db
account_name: oamtest
password: U34rfwefwefffaked
writeConnectionSecretToRef:
name: db-conn
```

View File

@@ -0,0 +1,16 @@
```yaml
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: redis-cloud-source
spec:
components:
- name: sample-redis
type: alibaba-redis
properties:
instance_name: oam-redis
account_name: oam
password: Xyfff83jfewGGfaked
writeConnectionSecretToRef:
name: redis-conn
```

View File

@@ -0,0 +1,16 @@
```yaml
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: app-sls-project-sample
spec:
components:
- name: sample-sls-project
type: alibaba-sls-project
properties:
name: kubevela-1112
description: "Managed by KubeVela"
writeConnectionSecretToRef:
name: sls-project-conn
```

View File

@@ -0,0 +1,18 @@
```yaml
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: app-sls-store-sample
spec:
components:
- name: sample-sls-store
type: alibaba-sls-store
properties:
store_name: kubevela-1111
store_retention_period: 30
store_shard_count: 2
store_max_split_shard_count: 2
writeConnectionSecretToRef:
name: sls-store-conn
```

View File

@@ -0,0 +1,14 @@
```yaml
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: app-vpc-sample
spec:
components:
- name: sample-vpc
type: alibaba-vpc
properties:
vpc_cidr: "172.16.0.0/12"
writeConnectionSecretToRef:
name: vpc-conn
```

View File

@@ -0,0 +1,16 @@
```yaml
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: s3-cloud-source
spec:
components:
- name: sample-s3
type: aws-s3
properties:
bucket: vela-website-20211019
acl: private
writeConnectionSecretToRef:
name: s3-conn
```

View File

@@ -0,0 +1,20 @@
```yaml
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: mariadb-backend
spec:
components:
- name: mariadb-backend
type: azure-database-mariadb
properties:
resource_group: "kubevela-group"
location: "West Europe"
server_name: "kubevela"
db_name: "backend"
username: "acctestun"
password: "H@Sh1CoR3!Faked"
writeConnectionSecretToRef:
name: azure-db-conn
namespace: vela-system
```

View File

@@ -0,0 +1,29 @@
```yaml
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: storage-account-dev
spec:
components:
- name: storage-account-dev
type: azure-storage-account
properties:
create_rsg: false
resource_group_name: "weursgappdev01"
location: "West Europe"
name: "appdev01"
tags: |
{
ApplicationName = "Application01"
Terraform = "Yes"
}
static_website: |
[{
index_document = "index.html"
error_404_document = "index.html"
}]
writeConnectionSecretToRef:
name: storage-account-dev
namespace: vela-system
```

View File

@@ -0,0 +1,22 @@
```yaml
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: image-dev
namespace: vela-system
labels:
"app.oam.dev/source-of-truth": "from-inner-system"
"config.oam.dev/catalog": "velacore-config"
"config.oam.dev/type": "config-image-registry"
project: abc
spec:
components:
- name: image-dev
type: config-image-registry
properties:
registry: "registry.cn-beijing.aliyuncs.com"
auth:
username: "my-username"
password: "my-password"
email: "a@gmail.com"
```

View File

@@ -0,0 +1,15 @@
```yaml
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: cron-worker
spec:
components:
- name: mytask
type: cron-task
properties:
image: perl
count: 10
cmd: ["perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"]
schedule: "*/1 * * * *"
```

View File

@@ -0,0 +1,48 @@
```yaml
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: addon-node-exporter
namespace: vela-system
spec:
components:
- name: node-exporter
type: daemon
properties:
image: prom/node-exporter
imagePullPolicy: IfNotPresent
volumeMounts:
hostPath:
- mountPath: /host/sys
mountPropagation: HostToContainer
name: sys
path: /sys
readOnly: true
- mountPath: /host/root
mountPropagation: HostToContainer
name: root
path: /
readOnly: true
traits:
- properties:
args:
- --path.sysfs=/host/sys
- --path.rootfs=/host/root
- --no-collector.wifi
- --no-collector.hwmon
- --collector.filesystem.ignored-mount-points=^/(dev|proc|sys|var/lib/docker/.+|var/lib/kubelet/pods/.+)($|/)
- --collector.netclass.ignored-devices=^(veth.*)$
type: command
- properties:
annotations:
prometheus.io/path: /metrics
prometheus.io/port: "8080"
prometheus.io/scrape: "true"
port:
- 9100
type: expose
- properties:
cpu: 0.1
memory: 250Mi
type: resource
```

View File

@@ -0,0 +1,25 @@
```yaml
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: app-raw
spec:
components:
- name: myjob
type: k8s-objects
properties:
objects:
- apiVersion: batch/v1
kind: Job
metadata:
name: pi
spec:
template:
spec:
containers:
- name: pi
image: perl
command: ["perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"]
restartPolicy: Never
backoffLimit: 4
```

View File

@@ -0,0 +1,3 @@
| NAME | DESCRIPTION | TYPE | REQUIRED | DEFAULT |
|---------|-------------|-----------------------|----------|---------|
| objects | A slice of Kubernetes resource manifests | [][Kubernetes-Objects](https://kubernetes.io/docs/concepts/overview/working-with-objects/kubernetes-objects/) | true | |

View File

@@ -0,0 +1,23 @@
```yaml
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: ref-objects-example
namespace: examples
spec:
components:
- name: image-pull-secrets
type: ref-objects
properties:
objects:
- resource: secret
name: image-credential-to-copy
policies:
- name: topology-hangzhou-clusters
type: topology
properties:
clusterLabelSelector:
region: hangzhou
```
You can refer to the [detail docs](https://kubevela.io/docs/end-user/components/ref-objects) about ref-objects.

View File

@@ -0,0 +1,14 @@
```yaml
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: app-worker
spec:
components:
- name: mytask
type: task
properties:
image: perl
count: 10
cmd: ["perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"]
```

View File

@@ -0,0 +1,25 @@
```yaml
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: website
spec:
components:
- name: frontend
type: webservice
properties:
image: oamdev/testapp:v1
cmd: ["node", "server.js"]
ports:
- port: 8080
expose: true
cpu: "0.1"
env:
- name: FOO
value: bar
- name: FOO
valueFrom:
secretKeyRef:
name: bar
key: bar
```

View File

@@ -0,0 +1,15 @@
```yaml
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: app-worker
spec:
components:
- name: myworker
type: worker
properties:
image: "busybox"
cmd:
- sleep
- "1000"
```

View File

@@ -0,0 +1,21 @@
```yaml
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: apply-once-app
spec:
components:
- name: hello-world
type: webservice
properties:
image: oamdev/hello-world
traits:
- type: scaler
properties:
replicas: 1
policies:
- name: apply-once
type: apply-once
properties:
enable: true
```

View File

@@ -0,0 +1,50 @@
```yaml
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: first-vela-app
spec:
components:
- name: express-server
type: webservice
properties:
image: oamdev/hello-world
port: 8000
traits:
- type: ingress-1-20
properties:
domain: testsvc.example.com
http:
"/": 8000
policies:
- name: keep-legacy-resource
type: garbage-collect
properties:
keepLegacyResource: true
```
```yaml
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: garbage-collect-app
spec:
components:
- name: hello-world-new
type: webservice
properties:
image: oamdev/hello-world
traits:
- type: expose
properties:
port: [8000]
policies:
- name: garbage-collect
type: garbage-collect
properties:
rules:
- selector:
traitTypes:
- expose
strategy: onAppDelete
```

View File

@@ -0,0 +1,102 @@
```yaml
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: deploy-with-override
namespace: examples
spec:
components:
- name: nginx-with-override
type: webservice
properties:
image: nginx
policies:
- name: topology-hangzhou-clusters
type: topology
properties:
clusterLabelSelector:
region: hangzhou
- name: topology-local
type: topology
properties:
clusters: ["local"]
namespace: examples-alternative
- name: override-nginx-legacy-image
type: override
properties:
components:
- name: nginx-with-override
properties:
image: nginx:1.20
- name: override-high-availability
type: override
properties:
components:
- type: webservice
traits:
- type: scaler
properties:
replicas: 3
workflow:
steps:
- type: deploy
name: deploy-local
properties:
policies: ["topology-local"]
- type: deploy
name: deploy-hangzhou
properties:
policies: ["topology-hangzhou-clusters", "override-nginx-legacy-image", "override-high-availability"]
```
```yaml
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: advance-override
namespace: examples
spec:
components:
- name: nginx-advance-override-legacy
type: webservice
properties:
image: nginx:1.20
- name: nginx-advance-override-latest
type: webservice
properties:
image: nginx
policies:
- name: topology-hangzhou-clusters
type: topology
properties:
clusterLabelSelector:
region: hangzhou
- name: topology-local
type: topology
properties:
clusters: ["local"]
namespace: examples-alternative
- name: override-nginx-legacy
type: override
properties:
selector: ["nginx-advance-override-legacy"]
- name: override-nginx-latest
type: override
properties:
selector: ["nginx-advance-override-latest", "nginx-advance-override-stable"]
components:
- name: nginx-advance-override-stable
type: webservice
properties:
image: nginx:stable
workflow:
steps:
- type: deploy
name: deploy-local
properties:
policies: ["topology-local", "override-nginx-legacy"]
- type: deploy
name: deploy-hangzhou
properties:
policies: ["topology-hangzhou-clusters", "override-nginx-latest"]
```

View File

@@ -0,0 +1,58 @@
```yaml
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: basic-topology
namespace: examples
spec:
components:
- name: nginx-basic
type: webservice
properties:
image: nginx
policies:
- name: topology-hangzhou-clusters
type: topology
properties:
clusters: ["hangzhou-1", "hangzhou-2"]
```
```yaml
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: label-selector-topology
namespace: examples
spec:
components:
- name: nginx-label-selector
type: webservice
properties:
image: nginx
policies:
- name: topology-hangzhou-clusters
type: topology
properties:
clusterLabelSelector:
region: hangzhou
```
```yaml
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: local-ns-topology
namespace: examples
spec:
components:
- name: nginx-local-ns
type: webservice
properties:
image: nginx
policies:
- name: topology-local
type: topology
properties:
clusters: ["local"]
namespace: examples-alternative
```

View File

@@ -0,0 +1,20 @@
```yaml
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: myapp
spec:
components:
- name: express-server
type: webservice
properties:
image: crccheck/hello-world
port: 8000
traits:
- type: labels
properties:
"release": "stable"
- type: annotations
properties:
"description": "web application"
```

View File

@@ -0,0 +1,18 @@
```yaml
kind: Application
metadata:
name: first-vela-app
spec:
components:
- name: express-server
type: webservice
properties:
image: crccheck/hello-world
port: 8000
traits:
- type: ingress
properties:
domain: testsvc.example.com
http:
"/": 8000
```

View File

@@ -0,0 +1,20 @@
```yaml
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: myapp
spec:
components:
- name: express-server
type: webservice
properties:
image: crccheck/hello-world
port: 8000
traits:
- type: labels
properties:
"release": "stable"
- type: annotations
properties:
"description": "web application"
```

View File

@@ -0,0 +1,27 @@
```yaml
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: website
spec:
components:
- name: frontend
type: webservice
properties:
image: nginx
traits:
- type: scaler
properties:
replicas: 2
- type: sidecar
properties:
name: "sidecar-test"
image: "fluentd"
- name: backend
type: worker
properties:
image: busybox
cmd:
- sleep
- '1000'
```

View File

@@ -0,0 +1,36 @@
```yaml
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: vela-app-with-sidecar
spec:
components:
- name: log-gen-worker
type: worker
properties:
image: busybox
cmd:
- /bin/sh
- -c
- >
i=0;
while true;
do
echo "$i: $(date)" >> /var/log/date.log;
i=$((i+1));
sleep 1;
done
volumes:
- name: varlog
mountPath: /var/log
type: emptyDir
traits:
- type: sidecar
properties:
name: count-log
image: busybox
cmd: [ /bin/sh, -c, 'tail -n+1 -f /var/log/date.log']
volumes:
- name: varlog
path: /var/log
```

View File

@@ -0,0 +1,79 @@
/*
Copyright 2022 The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package plugins
import (
"embed"
"io/fs"
"strings"
"k8s.io/klog/v2"
)
//go:embed def-doc
var defdoc embed.FS
// DefinitionDocSamples stores the configuration yaml sample for capabilities
var DefinitionDocSamples = map[string]string{}
// DefinitionDocDescription stores the description for capabilities
var DefinitionDocDescription = map[string]string{}
// DefinitionDocParameters stores the parameters for capabilities, it will override the generated one
var DefinitionDocParameters = map[string]string{}
const (
suffixSample = ".eg.md"
suffixParameter = ".param.md"
suffixDescription = ".desc.md"
)
func init() {
err := fs.WalkDir(defdoc, "def-doc", func(path string, d fs.DirEntry, err error) error {
if d.IsDir() {
return nil
}
switch {
case strings.HasSuffix(d.Name(), suffixSample):
data, err := defdoc.ReadFile(path)
if err != nil {
klog.ErrorS(err, "ignore this embed built-in definition sample", "path", path)
return nil
}
DefinitionDocSamples[strings.TrimSuffix(d.Name(), suffixSample)] = strings.TrimSpace(string(data))
case strings.HasSuffix(d.Name(), suffixDescription):
data, err := defdoc.ReadFile(path)
if err != nil {
klog.ErrorS(err, "ignore this embed built-in definition description", "path", path)
return nil
}
DefinitionDocDescription[strings.TrimSuffix(d.Name(), suffixDescription)] = strings.TrimSpace(string(data))
case strings.HasSuffix(d.Name(), suffixParameter):
data, err := defdoc.ReadFile(path)
if err != nil {
klog.ErrorS(err, "ignore this embed built-in definition parameter", "path", path)
return nil
}
DefinitionDocParameters[strings.TrimSuffix(d.Name(), suffixParameter)] = strings.TrimSpace(string(data))
}
return nil
})
if err != nil {
klog.ErrorS(err, "unable to read embed built-in definition documentation")
return
}
}

View File

@@ -0,0 +1,27 @@
/*
Copyright 2022 The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package plugins
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestDcoInit(t *testing.T) {
assert.True(t, len(DefinitionDocSamples) > 0)
}

View File

@@ -16,120 +16,172 @@ limitations under the License.
package plugins
import (
"encoding/json"
"log"
"strings"
"github.com/oam-dev/kubevela/pkg/utils"
)
// Language is used to define the language
type Language string
const (
// En is English, the default language
En Language = "English"
var (
// En is english, the default language
En = I18n{lang: LangEn}
// Zh is Chinese
Zh Language = "Chinese"
Zh = I18n{lang: LangZh}
)
const (
// LangEn is english, the default language
LangEn Language = "English"
// LangZh is Chinese
LangZh Language = "Chinese"
)
// I18n will automatically get translated data
type I18n struct {
lang Language
}
// LoadI18nData will load i18n data for the package
func LoadI18nData(path string) {
log.Printf("loading i18n data from %s", path)
data, err := utils.ReadRemoteOrLocalPath(path, false)
if err != nil {
log.Println("ignore using the i18n data", err)
return
}
var dat = map[string]map[Language]string{}
err = json.Unmarshal(data, &dat)
if err != nil {
log.Println("ignore using the i18n data", err)
return
}
for k, v := range dat {
if _, ok := v[LangEn]; !ok {
v[LangEn] = k
}
k = strings.ToLower(k)
ed, ok := i18nDoc[k]
if !ok {
ed = map[Language]string{}
}
for sk, sv := range v {
sv = strings.TrimSpace(sv)
if sv == "" {
continue
}
ed[sk] = sv
}
i18nDoc[k] = ed
}
}
// Language return the language used in i18n instance
func (i *I18n) Language() Language {
if i == nil || i.lang == "" {
return En.Language()
}
return i.lang
}
func (i *I18n) trans(str string) (string, bool) {
dd, ok := i18nDoc[str]
if !ok {
return str, false
}
data := dd[i.lang]
if data == "" {
return str, true
}
return data, true
}
// Get translate for the string
func (i *I18n) Get(str string) string {
if i == nil || i.lang == "" {
return En.Get(str)
}
if data, ok := i.trans(str); ok {
return data
}
str = strings.TrimSpace(str)
if data, ok := i.trans(str); ok {
return data
}
str = strings.TrimSuffix(str, ".")
if data, ok := i.trans(str); ok {
return data
}
str = strings.TrimSuffix(str, "。")
if data, ok := i.trans(str); ok {
return data
}
raw := str
str = strings.TrimSpace(str)
if data, ok := i.trans(str); ok {
return data
}
str = strings.ToLower(str)
if data, ok := i.trans(str); ok {
return data
}
return raw
}
// Definitions are all the words and phrases for internationalization in cli and docs
var Definitions = map[string]map[Language]string{
"Description": {
Zh: "描述",
En: "Description",
var i18nDoc = map[string]map[Language]string{
".": {
LangZh: "",
LangEn: ".",
},
"Samples": {
Zh: "示例",
En: "Samples",
"Description": {
LangZh: "描述",
LangEn: "Description",
},
"Examples": {
LangZh: "示例",
LangEn: "Examples",
},
"Specification": {
Zh: "参数说明",
En: "Specification",
LangZh: "参数说明",
LangEn: "Specification",
},
"AlibabaCloud": {
Zh: "阿里云",
En: "Alibaba Cloud",
LangZh: "阿里云",
LangEn: "Alibaba Cloud",
},
"AWS": {
Zh: "AWS",
En: "AWS",
LangZh: "AWS",
LangEn: "AWS",
},
"Azure": {
Zh: "Azure",
En: "Azure",
LangZh: "Azure",
LangEn: "Azure",
},
"Name": {
Zh: "名称",
En: "Name",
LangZh: "名称",
LangEn: "Name",
},
"Type": {
Zh: "类型",
En: "Type",
LangZh: "类型",
LangEn: "Type",
},
"Required": {
Zh: "是否必须",
En: "Required",
LangZh: "是否必须",
LangEn: "Required",
},
"Default": {
Zh: "默认值",
En: "Default",
LangZh: "默认值",
LangEn: "Default",
},
"WriteConnectionSecretToRefIntroduction": {
Zh: "如果设置了 `writeConnectionSecretToRef`,一个 Kubernetes Secret 将会被创建并且它的数据里有这些键key",
En: "If `writeConnectionSecretToRef` is set, a secret will be generated with these keys as below:",
},
"Outputs": {
Zh: "输出",
En: "Outputs",
},
"Properties": {
Zh: "属性",
En: "Properties",
},
"Terraform_configuration_for_Alibaba_Cloud_ACK_cluster": {
Zh: "用于部署阿里云 ACK 集群的组件说明",
En: "Terraform configuration for Alibaba Cloud ACK cluster",
},
"Terraform_configuration_for_Alibaba_Cloud_Serverless_Kubernetes_(ASK)": {
Zh: "用于部署阿里云 Serverless Kubernetes (ASK) 的组件说明",
En: "Terraform configuration for Alibaba Cloud Serverless Kubernetes (ASK)",
},
"Terraform_configuration_for_Alibaba_Cloud_Elastic_IP": {
Zh: "用于部署阿里云弹性 IP 的组件说明",
En: "Terraform configuration for Alibaba Cloud Elastic IP",
},
"Terraform_configuration_for_Alibaba_Cloud_OSS_object": {
Zh: "用于部署阿里云 OSS 的组件说明",
En: "Terraform configuration for Alibaba Cloud OSS",
},
"Terraform_configuration_for_Alibaba_Cloud_RDS_object": {
Zh: "用于部署阿里云 RDS 的组件说明",
En: "Terraform configuration for Alibaba Cloud RDS",
},
"Terraform_configuration_for_Alibaba_Cloud_Redis": {
Zh: "用于部署阿里云 Redis 的组件说明",
En: "Terraform configuration for Alibaba Cloud Redis",
},
"Terraform_configuration_for_Alibaba_Cloud_SLS_Project": {
Zh: "用于部署阿里云 SLS Project 的组件说明",
En: "Terraform configuration for Alibaba Cloud SLS Project",
},
"Terraform_configuration_for_Alibaba_Cloud_SLS_Store": {
Zh: "用于部署阿里云 SLS Store 的组件说明",
En: "Terraform configuration for Alibaba Cloud SLS Store",
},
"Terraform_configuration_for_Alibaba_Cloud_VPC": {
Zh: "用于部署阿里云 VPC 的组件说明",
En: "Terraform configuration for Alibaba Cloud VPC",
},
"Terraform_configuration_for_Alibaba_Cloud_VSwitch": {
Zh: "用于部署阿里云 VSwitch 的组件说明",
En: "Terraform configuration for Alibaba Cloud VSwitch",
},
"Terraform_configuration_for_AWS_S3": {
Zh: "用于部署 AWS S3 的组件说明",
En: "Terraform configuration for AWS S3",
},
"Terraform_configuration_for_Azure_Database_Mariadb": {
Zh: "用于部署 Azure mariadb 数据库的组件说明",
En: "Terraform configuration for Azure Database Mariadb",
},
"Terraform_configuration_for_Azure_Blob_Storage_Account": {
Zh: "用于部署 Azure Blob Storage 账号的的组件说明",
En: "Terraform configuration for Azure Blob Storage Account",
"Apply To Component Types": {
LangZh: "适用于组件类型",
LangEn: "Apply To Component Types",
},
}

View File

@@ -0,0 +1,57 @@
/*
Copyright 2022 The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package plugins
import (
"fmt"
"net/http"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestLoad(t *testing.T) {
go func() {
svr := http.NewServeMux()
svr.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, `{"Outputs":{"Chinese":"输出"}}`)
})
http.ListenAndServe(":65502", svr)
}()
time.Sleep(time.Millisecond)
assert.Equal(t, En.Language(), Language("English"))
assert.Equal(t, En.Get("nihaoha"), "nihaoha")
assert.Equal(t, En.Get("AlibabaCloud"), "Alibaba Cloud")
var ni *I18n
assert.Equal(t, ni.Get("AlibabaCloud"), "Alibaba Cloud")
assert.Equal(t, ni.Get("AlibabaCloud."), "Alibaba Cloud")
assert.Equal(t, ni.Get("AlibabaCloud。"), "Alibaba Cloud")
assert.Equal(t, ni.Get("AlibabaCloud。 "), "Alibaba Cloud")
assert.Equal(t, ni.Get("AlibabaCloud 。 "), "Alibaba Cloud")
assert.Equal(t, ni.Get("AlibabaCloud \n "), "Alibaba Cloud")
assert.Equal(t, ni.Get(" A\n "), "A")
assert.Equal(t, ni.Get(" \n "), "")
assert.Equal(t, Zh.Language(), Language("Chinese"))
assert.Equal(t, Zh.Get("nihaoha"), "nihaoha")
assert.Equal(t, Zh.Get("AlibabaCloud"), "阿里云")
LoadI18nData("http://127.0.0.1:65502")
assert.Equal(t, Zh.Get("Outputs"), "输出")
}

View File

@@ -1,57 +0,0 @@
/*
Copyright 2021 The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package plugins
import (
"github.com/oam-dev/kubevela/apis/types"
)
var (
deployment = types.Capability{
Name: "deployment",
Type: types.TypeComponentDefinition,
Parameters: []types.Parameter{
{
Name: "image",
Short: "i",
Required: true,
},
},
}
statefulset = types.Capability{
Name: "statefulset",
Type: types.TypeComponentDefinition,
Parameters: []types.Parameter{
{
Name: "image",
Short: "i",
Required: true,
},
},
}
route = types.Capability{
Name: "route",
Type: types.TypeTrait,
Parameters: []types.Parameter{
{
Name: "domain",
Short: "d",
Required: true,
},
},
}
)

View File

@@ -0,0 +1,313 @@
/*
Copyright 2022 The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package plugins
import (
"context"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"sort"
"strings"
"github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/cue/packages"
"github.com/oam-dev/kubevela/pkg/utils/common"
)
// MarkdownReference is the struct for capability information in
type MarkdownReference struct {
Filter func(types.Capability) bool
AllInOne bool
CustomDocHeader string
ParseReference
}
// GenerateReferenceDocs generates reference docs
func (ref *MarkdownReference) GenerateReferenceDocs(ctx context.Context, c common.Args, baseRefPath string) error {
caps, err := ref.getCapabilities(ctx, c)
if err != nil {
return err
}
var pd *packages.PackageDiscover
if ref.Remote != nil {
pd = ref.Remote.PD
}
return ref.CreateMarkdown(ctx, caps, baseRefPath, false, pd)
}
// CreateMarkdown creates markdown based on capabilities
func (ref *MarkdownReference) CreateMarkdown(ctx context.Context, caps []types.Capability, baseRefPath string, catalog bool, pd *packages.PackageDiscover) error {
sort.Slice(caps, func(i, j int) bool {
return caps[i].Name < caps[j].Name
})
var all string
ref.DisplayFormat = Markdown
for _, c := range caps {
if ref.Filter != nil && !ref.Filter(c) {
continue
}
capDoc, err := ref.GenerateMarkdownForCap(ctx, c, pd, ref.AllInOne)
if err != nil {
return err
}
if baseRefPath == "" {
fmt.Println(capDoc)
continue
}
if ref.AllInOne {
all += capDoc + "\n\n"
continue
}
refPath := baseRefPath
if catalog {
// catalog by capability type with folder
refPath = filepath.Join(baseRefPath, string(c.Type))
}
if _, err := os.Stat(refPath); err != nil && os.IsNotExist(err) {
if err := os.MkdirAll(refPath, 0750); err != nil {
return err
}
}
refPath = strings.TrimSuffix(refPath, "/")
fileName := fmt.Sprintf("%s.md", c.Name)
markdownFile := filepath.Join(refPath, fileName)
f, err := os.OpenFile(filepath.Clean(markdownFile), os.O_WRONLY|os.O_CREATE, 0600)
if err != nil {
return fmt.Errorf("failed to open file %s: %w", markdownFile, err)
}
if err = os.Truncate(markdownFile, 0); err != nil {
return fmt.Errorf("failed to truncate file %s: %w", markdownFile, err)
}
if _, err := f.WriteString(capDoc); err != nil {
return err
}
if err := f.Close(); err != nil {
return err
}
}
if !ref.AllInOne {
return nil
}
all = ref.CustomDocHeader + all
if baseRefPath != "" {
return ioutil.WriteFile(baseRefPath, []byte(all), 0600)
}
fmt.Println(all)
return nil
}
// GenerateMarkdownForCap will generate markdown for one capability
func (ref *MarkdownReference) GenerateMarkdownForCap(ctx context.Context, c types.Capability, pd *packages.PackageDiscover, containSuffix bool) (string, error) {
var (
description string
sample string
specification string
generatedDoc string
err error
)
if c.Type != types.TypeWorkload && c.Type != types.TypeComponentDefinition && c.Type != types.TypeTrait &&
c.Type != types.TypeWorkflowStep && c.Type != types.TypePolicy {
return "", fmt.Errorf("type(%s) of the capability(%s) is not supported for now", c.Type, c.Name)
}
capName := c.Name
lang := ref.I18N
capNameInTitle := ref.makeReadableTitle(capName)
switch c.Category {
case types.CUECategory:
cueValue, err := common.GetCUEParameterValue(c.CueTemplate, pd)
if err != nil {
return "", fmt.Errorf("failed to retrieve `parameters` value from %s with err: %w", c.Name, err)
}
var defaultDepth = 0
generatedDoc, _, err = ref.parseParameters(capName, cueValue, Specification, defaultDepth, containSuffix)
if err != nil {
return "", err
}
case types.HelmCategory, types.KubeCategory:
properties, _, err := ref.GenerateHelmAndKubeProperties(ctx, &c)
if err != nil {
return "", fmt.Errorf("failed to retrieve `parameters` value from %s with err: %w", c.Name, err)
}
for _, property := range properties {
generatedDoc += ref.getParameterString("###"+property.Name, property.Parameters, types.HelmCategory)
}
case types.TerraformCategory:
generatedDoc, err = ref.GenerateTerraformCapabilityPropertiesAndOutputs(c)
if err != nil {
return "", err
}
default:
return "", fmt.Errorf("unsupport category %s from capability %s", c.Category, capName)
}
title := fmt.Sprintf("---\ntitle: %s\n---", capNameInTitle)
if ref.AllInOne {
title = fmt.Sprintf("## %s", capNameInTitle)
}
sampleContent := c.Example
if sampleContent == "" {
sampleContent = DefinitionDocSamples[capName]
}
descriptionI18N := DefinitionDocDescription[capName]
if descriptionI18N == "" {
descriptionI18N = c.Description
}
parameterDoc := DefinitionDocParameters[capName]
if parameterDoc == "" {
parameterDoc = generatedDoc
}
var sharp = "##"
exampleTitle := lang.Get(Examples)
specificationTitle := lang.Get(Specification)
if ref.AllInOne {
sharp = "###"
exampleTitle += " (" + capName + ")"
specificationTitle += " (" + capName + ")"
}
description = fmt.Sprintf("\n\n%s %s\n\n%s", sharp, lang.Get(Description), strings.TrimSpace(lang.Get(descriptionI18N)))
if !strings.HasSuffix(description, lang.Get(".")) {
description += lang.Get(".")
}
if c.Type == types.TypeTrait {
description += "\n\n### " + lang.Get("Apply To Component Types") + "\n\n"
var applyto string
for _, ap := range c.AppliesTo {
applyto += "- " + ap + "\n"
}
if applyto == "" {
applyto = "- All/*"
}
description += applyto + "\n"
}
if sampleContent != "" {
sample = fmt.Sprintf("\n\n%s %s\n\n%s", sharp, exampleTitle, sampleContent)
}
specification = fmt.Sprintf("\n\n%s %s\n%s", sharp, specificationTitle, parameterDoc)
return title + description + sample + specification, nil
}
func (ref *MarkdownReference) makeReadableTitle(title string) string {
if !strings.Contains(title, "-") {
return strings.Title(title)
}
var name string
provider := strings.Split(title, "-")[0]
switch provider {
case "alibaba":
name = "AlibabaCloud"
case "aws":
name = "AWS"
case "azure":
name = "Azure"
default:
return strings.Title(title)
}
cloudResource := strings.Replace(title, provider+"-", "", 1)
return fmt.Sprintf("%s %s", ref.I18N.Get(name), strings.ToUpper(cloudResource))
}
// getParameterString prepares the table content for each property
func (ref *MarkdownReference) getParameterString(tableName string, parameterList []ReferenceParameter, category types.CapabilityCategory) string {
tab := fmt.Sprintf("\n\n%s\n\n", tableName)
if tableName == "" || tableName == Specification {
tab = "\n\n"
}
tab += fmt.Sprintf(" %s | %s | %s | %s | %s \n", ref.I18N.Get("Name"), ref.I18N.Get("Description"), ref.I18N.Get("Type"), ref.I18N.Get("Required"), ref.I18N.Get("Default"))
tab += fmt.Sprintf(" %s | %s | %s | %s | %s \n",
strings.Repeat("-", len(ref.I18N.Get("Name"))),
strings.Repeat("-", len(ref.I18N.Get("Description"))),
strings.Repeat("-", len(ref.I18N.Get("Type"))),
strings.Repeat("-", len(ref.I18N.Get("Required"))),
strings.Repeat("-", len(ref.I18N.Get("Default"))))
switch category {
case types.CUECategory:
for _, p := range parameterList {
if !p.Ignore {
printableDefaultValue := ref.getCUEPrintableDefaultValue(p.Default)
tab += fmt.Sprintf(" %s | %s | %s | %t | %s \n", p.Name, ref.prettySentence(p.Usage), p.PrintableType, p.Required, printableDefaultValue)
}
}
case types.HelmCategory:
for _, p := range parameterList {
printableDefaultValue := ref.getJSONPrintableDefaultValue(p.JSONType, p.Default)
tab += fmt.Sprintf(" %s | %s | %s | %t | %s \n", p.Name, ref.prettySentence(p.Usage), p.PrintableType, p.Required, printableDefaultValue)
}
case types.KubeCategory:
for _, p := range parameterList {
// Kube parameter doesn't have default value
tab += fmt.Sprintf(" %s | %s | %s | %t | %s \n", p.Name, ref.prettySentence(p.Usage), p.PrintableType, p.Required, "")
}
case types.TerraformCategory:
// Terraform doesn't have default value
for _, p := range parameterList {
tab += fmt.Sprintf(" %s | %s | %s | %t | %s \n", p.Name, ref.prettySentence(p.Usage), p.PrintableType, p.Required, "")
}
default:
}
return tab
}
// GenerateTerraformCapabilityPropertiesAndOutputs generates Capability properties and outputs for Terraform ComponentDefinition
func (ref *MarkdownReference) GenerateTerraformCapabilityPropertiesAndOutputs(capability types.Capability) (string, error) {
var references string
variableTables, outputsTable, err := ref.parseTerraformCapabilityParameters(capability)
if err != nil {
return "", err
}
for _, t := range variableTables {
references += ref.getParameterString(t.Name, t.Parameters, types.CUECategory)
}
for _, t := range outputsTable {
references += ref.prepareTerraformOutputs(t.Name, t.Parameters)
}
return references, nil
}
// getParameterString prepares the table content for each property
func (ref *MarkdownReference) prepareTerraformOutputs(tableName string, parameterList []ReferenceParameter) string {
if len(parameterList) == 0 {
return ""
}
tfdoc := fmt.Sprintf("\n\n%s\n\n", tableName)
if tableName == "" {
tfdoc = "\n\n"
}
tfdoc += fmt.Sprintf(" %s | %s \n", ref.I18N.Get("Name"), ref.I18N.Get("Description"))
tfdoc += " ------------ | ------------- \n"
for _, p := range parameterList {
tfdoc += fmt.Sprintf(" %s | %s\n", p.Name, p.Usage)
}
return tfdoc
}

View File

@@ -0,0 +1,183 @@
/*
Copyright 2022 The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package plugins
import (
"context"
"fmt"
"net/http"
"testing"
"time"
"github.com/crossplane/crossplane-runtime/pkg/test"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/utils/common"
)
func TestCreateMarkdownForCUE(t *testing.T) {
go func() {
svr := http.NewServeMux()
svr.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, %s!", r.URL.Path[1:])
})
http.ListenAndServe(":65501", svr)
}()
time.Sleep(time.Millisecond)
mr := MarkdownReference{}
mr.Local = &FromLocal{Path: "./testdata/testdef.cue"}
capp, err := ParseLocalFile(mr.Local.Path, common.Args{})
assert.NoError(t, err)
got, err := mr.GenerateMarkdownForCap(context.Background(), *capp, nil, false)
assert.NoError(t, err)
fmt.Println(got)
assert.Contains(t, got, "A test key")
assert.Contains(t, got, "title: Testdef")
assert.Contains(t, got, "K8s-objects allow users to specify raw K8s objects in properties")
assert.Contains(t, got, "Examples")
assert.Contains(t, got, "Hello, examples/applications/create-namespace.yaml!")
mr.Local = &FromLocal{Path: "./testdata/testdeftrait.cue"}
capp, err = ParseLocalFile(mr.Local.Path, common.Args{})
assert.NoError(t, err)
got, err = mr.GenerateMarkdownForCap(context.Background(), *capp, nil, false)
assert.NoError(t, err)
assert.Contains(t, got, "Specify the hostAliases to add")
assert.Contains(t, got, "title: Testdeftrait")
assert.Contains(t, got, "Add host aliases on K8s pod for your workload which follows the pod spec in path 'spec.template'.")
}
func TestCreateMarkdown(t *testing.T) {
ctx := context.Background()
ref := &MarkdownReference{}
ref.I18N = &En
refZh := &MarkdownReference{}
refZh.I18N = &Zh
workloadName := "workload1"
traitName := "trait1"
scopeName := "scope1"
workloadName2 := "workload2"
workloadCueTemplate := `
parameter: {
// +usage=Which image would you like to use for your service
// +short=i
image: string
}
`
traitCueTemplate := `
parameter: {
replicas: int
}
`
configuration := `
resource "alicloud_oss_bucket" "bucket-acl" {
bucket = var.bucket
acl = var.acl
}
output "BUCKET_NAME" {
value = "${alicloud_oss_bucket.bucket-acl.bucket}.${alicloud_oss_bucket.bucket-acl.extranet_endpoint}"
}
variable "bucket" {
description = "OSS bucket name"
default = "vela-website"
type = string
}
variable "acl" {
description = "OSS bucket ACL, supported 'private', 'public-read', 'public-read-write'"
default = "private"
type = string
}
`
cases := map[string]struct {
reason string
ref *MarkdownReference
capabilities []types.Capability
want error
}{
"WorkloadTypeAndTraitCapability": {
reason: "valid capabilities",
ref: ref,
capabilities: []types.Capability{
{
Name: workloadName,
Type: types.TypeWorkload,
CueTemplate: workloadCueTemplate,
Category: types.CUECategory,
},
{
Name: traitName,
Type: types.TypeTrait,
CueTemplate: traitCueTemplate,
Category: types.CUECategory,
},
{
Name: workloadName2,
TerraformConfiguration: configuration,
Type: types.TypeWorkload,
Category: types.TerraformCategory,
},
},
want: nil,
},
"ScopeTypeCapability": {
reason: "invalid capabilities",
ref: ref,
capabilities: []types.Capability{
{
Name: scopeName,
Type: types.TypeScope,
},
},
want: fmt.Errorf("type(scope) of the capability(scope1) is not supported for now"),
},
"TerraformCapabilityInChinese": {
reason: "terraform capability",
ref: refZh,
capabilities: []types.Capability{
{
Name: workloadName2,
TerraformConfiguration: configuration,
Type: types.TypeWorkload,
Category: types.TerraformCategory,
},
},
want: nil,
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
got := tc.ref.CreateMarkdown(ctx, tc.capabilities, RefTestDir, false, nil)
if diff := cmp.Diff(tc.want, got, test.EquateErrors()); diff != "" {
t.Errorf("\n%s\nCreateMakrdown(...): -want error, +got error:\n%s", tc.reason, diff)
}
})
}
}

View File

@@ -0,0 +1,536 @@
/*
Copyright 2022 The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package plugins
import (
"context"
"encoding/json"
"fmt"
"os"
"sort"
"strconv"
"strings"
"cuelang.org/go/cue"
"cuelang.org/go/cue/ast"
"github.com/getkin/kin-openapi/openapi3"
"github.com/olekukonko/tablewriter"
"github.com/pkg/errors"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/yaml"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
"github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/controller/utils"
velacue "github.com/oam-dev/kubevela/pkg/cue"
"github.com/oam-dev/kubevela/pkg/cue/model"
"github.com/oam-dev/kubevela/pkg/cue/packages"
pkgdef "github.com/oam-dev/kubevela/pkg/definition"
pkgUtils "github.com/oam-dev/kubevela/pkg/utils"
"github.com/oam-dev/kubevela/pkg/utils/common"
"github.com/oam-dev/kubevela/pkg/utils/terraform"
)
// ParseReference is used to include the common function `parseParameter`
type ParseReference struct {
Client client.Client
I18N *I18n `json:"i18n"`
Remote *FromCluster `json:"remote"`
Local *FromLocal `json:"local"`
DefinitionName string `json:"definitionName"`
DisplayFormat string
}
func (ref *ParseReference) getCapabilities(ctx context.Context, c common.Args) ([]types.Capability, error) {
var (
caps []types.Capability
pd *packages.PackageDiscover
)
switch {
case ref.Local != nil:
lcap, err := ParseLocalFile(ref.Local.Path, c)
if err != nil {
return nil, fmt.Errorf("failed to get capability from local file %s: %w", ref.DefinitionName, err)
}
caps = append(caps, *lcap)
case ref.Remote != nil:
config, err := c.GetConfig()
if err != nil {
return nil, err
}
pd, err = packages.NewPackageDiscover(config)
if err != nil {
return nil, err
}
ref.Remote.PD = pd
if ref.DefinitionName == "" {
caps, err = LoadAllInstalledCapability("default", c)
if err != nil {
return nil, fmt.Errorf("failed to get all capabilityes: %w", err)
}
} else {
var rcap *types.Capability
if ref.Remote.Rev == 0 {
rcap, err = GetCapabilityByName(ctx, c, ref.DefinitionName, ref.Remote.Namespace, pd)
if err != nil {
return nil, fmt.Errorf("failed to get capability %s: %w", ref.DefinitionName, err)
}
} else {
rcap, err = GetCapabilityFromDefinitionRevision(ctx, c, pd, ref.Remote.Namespace, ref.DefinitionName, ref.Remote.Rev)
if err != nil {
return nil, fmt.Errorf("failed to get revision %v of capability %s: %w", ref.Remote.Rev, ref.DefinitionName, err)
}
}
caps = []types.Capability{*rcap}
}
default:
return nil, fmt.Errorf("failed to get capability %s without namespace or local filepath", ref.DefinitionName)
}
return caps, nil
}
func (ref *ParseReference) prettySentence(s string) string {
if strings.TrimSpace(s) == "" {
return ""
}
return ref.I18N.Get(s) + ref.I18N.Get(".")
}
// prepareConsoleParameter prepares the table content for each property
func (ref *ParseReference) prepareConsoleParameter(tableName string, parameterList []ReferenceParameter, category types.CapabilityCategory) ConsoleReference {
table := tablewriter.NewWriter(os.Stdout)
table.SetColWidth(100)
table.SetHeader([]string{ref.I18N.Get("Name"), ref.I18N.Get("Description"), ref.I18N.Get("Type"), ref.I18N.Get("Required"), ref.I18N.Get("Default")})
switch category {
case types.CUECategory:
for _, p := range parameterList {
if !p.Ignore {
printableDefaultValue := ref.getCUEPrintableDefaultValue(p.Default)
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)})
}
}
case types.HelmCategory, types.KubeCategory:
for _, p := range parameterList {
printableDefaultValue := ref.getJSONPrintableDefaultValue(p.JSONType, p.Default)
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)})
}
case types.TerraformCategory:
// Terraform doesn't have default value
for _, p := range parameterList {
table.Append([]string{ref.I18N.Get(p.Name), ref.prettySentence(p.Usage), ref.I18N.Get(p.PrintableType), ref.I18N.Get(strconv.FormatBool(p.Required)), ""})
}
default:
}
return ConsoleReference{TableName: tableName, TableObject: table}
}
// parseParameters parses every parameter
func (ref *ParseReference) parseParameters(capName string, paraValue cue.Value, paramKey string, depth int, containSuffix bool) (string, []ConsoleReference, error) {
var doc string
var console []ConsoleReference
var params []ReferenceParameter
var suffixTitle = " (" + capName + ")"
var suffixRef = "-" + strings.ToLower(capName)
if !containSuffix || capName == "" {
suffixTitle = ""
suffixRef = ""
}
switch paraValue.Kind() {
case cue.StructKind:
arguments, err := paraValue.Struct()
if err != nil {
return "", nil, fmt.Errorf("arguments not defined as struct %w", err)
}
if arguments.Len() == 0 {
var param ReferenceParameter
param.Name = "\\-"
param.Required = true
tl := paraValue.Template()
if tl != nil { // is map type
param.PrintableType = fmt.Sprintf("map[string]%s", tl("").IncompleteKind().String())
} else {
param.PrintableType = "{}"
}
params = append(params, param)
}
for i := 0; i < arguments.Len(); i++ {
var param ReferenceParameter
fi := arguments.Field(i)
if fi.IsDefinition {
continue
}
val := fi.Value
name := fi.Name
param.Name = name
param.Required = !fi.IsOptional
if def, ok := val.Default(); ok && def.IsConcrete() {
param.Default = velacue.GetDefault(def)
}
param.Short, param.Usage, param.Alias, param.Ignore = velacue.RetrieveComments(val)
param.Type = val.IncompleteKind()
switch val.IncompleteKind() {
case cue.StructKind:
if subField, err := val.Struct(); err == nil && subField.Len() == 0 { // err cannot be not nil,so ignore it
if mapValue, ok := val.Elem(); ok {
// In the future we could recursively call to support complex map-value(struct or list)
source, converted := mapValue.Source().(*ast.Ident)
if converted && len(source.Name) != 0 {
param.PrintableType = fmt.Sprintf("map[string]%s", source.Name)
} else {
param.PrintableType = fmt.Sprintf("map[string]%s", mapValue.IncompleteKind().String())
}
} else {
return "", nil, fmt.Errorf("failed to got Map kind from %s", param.Name)
}
} else {
subDoc, subConsole, err := ref.parseParameters(capName, val, name, depth+1, containSuffix)
if err != nil {
return "", nil, err
}
param.PrintableType = fmt.Sprintf("[%s](#%s%s)", name, strings.ToLower(name), suffixRef)
doc += subDoc
console = append(console, subConsole...)
}
case cue.ListKind:
elem, success := val.Elem()
if !success {
// fail to get elements, use the value of ListKind to be the type
param.Type = val.Kind()
param.PrintableType = val.IncompleteKind().String()
break
}
switch elem.Kind() {
case cue.StructKind:
param.PrintableType = fmt.Sprintf("[[]%s](#%s%s)", name, strings.ToLower(name), suffixRef)
subDoc, subConsole, err := ref.parseParameters(capName, elem, name, depth+1, containSuffix)
if err != nil {
return "", nil, err
}
doc += subDoc
console = append(console, subConsole...)
default:
param.Type = elem.Kind()
param.PrintableType = fmt.Sprintf("[]%s", elem.IncompleteKind().String())
}
default:
param.PrintableType = param.Type.String()
}
params = append(params, param)
}
default:
//
}
switch ref.DisplayFormat {
case Markdown, "":
// markdown defines the contents that display in web
var tableName string
if paramKey != Specification {
length := depth + 3
if length >= 5 {
length = 5
}
tableName = fmt.Sprintf("%s %s%s", strings.Repeat("#", length), paramKey, suffixTitle)
}
mref := MarkdownReference{}
mref.I18N = ref.I18N
doc = mref.getParameterString(tableName, params, types.CUECategory) + doc
case Console:
length := depth + 1
if length >= 3 {
length = 3
}
cref := ConsoleReference{}
tableName := fmt.Sprintf("%s %s", strings.Repeat("#", length), paramKey)
console = append([]ConsoleReference{cref.prepareConsoleParameter(tableName, params, types.CUECategory)}, console...)
}
return doc, console, nil
}
// getCUEPrintableDefaultValue converts the value in `interface{}` type to be printable
func (ref *ParseReference) getCUEPrintableDefaultValue(v interface{}) string {
if v == nil {
return ""
}
switch value := v.(type) {
case Int64Type:
return strconv.FormatInt(value, 10)
case StringType:
if v == "" {
return "empty"
}
return value
case BoolType:
return strconv.FormatBool(value)
}
return ""
}
func (ref *ParseReference) getJSONPrintableDefaultValue(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]
}
// CommonReference contains parameters info of HelmCategory and KubuCategory type capability at present
type CommonReference struct {
Name string
Parameters []ReferenceParameter
Depth int
}
// CommonSchema is a struct contains *openapi3.Schema style parameter
type CommonSchema struct {
Name string
Schemas *openapi3.Schema
}
// GenerateHelmAndKubeProperties get all properties of a Helm/Kube Category type capability
func (ref *ParseReference) GenerateHelmAndKubeProperties(ctx context.Context, capability *types.Capability) ([]CommonReference, []ConsoleReference, error) {
cmName := fmt.Sprintf("%s%s", types.CapabilityConfigMapNamePrefix, capability.Name)
switch capability.Type {
case types.TypeComponentDefinition:
cmName = fmt.Sprintf("component-%s", cmName)
case types.TypeTrait:
cmName = fmt.Sprintf("trait-%s", cmName)
default:
}
var cm v1.ConfigMap
commonRefs = make([]CommonReference, 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.NewLoader().LoadFromData(json.RawMessage(parameterJSON))
if err != nil {
return nil, nil, err
}
parameters := swagger.Components.Schemas[model.ParameterFieldName].Value
WalkParameterSchema(parameters, Specification, 0)
var consoleRefs []ConsoleReference
for _, item := range commonRefs {
consoleRefs = append(consoleRefs, ref.prepareConsoleParameter(item.Name, item.Parameters, types.HelmCategory))
}
return commonRefs, consoleRefs, err
}
// GenerateTerraformCapabilityProperties generates Capability properties for Terraform ComponentDefinition
func (ref *ParseReference) parseTerraformCapabilityParameters(capability types.Capability) ([]ReferenceParameterTable, []ReferenceParameterTable, error) {
var (
tables []ReferenceParameterTable
refParameterList []ReferenceParameter
writeConnectionSecretToRefReferenceParameter ReferenceParameter
configuration string
err error
outputsList []ReferenceParameter
outputsTables []ReferenceParameterTable
outputsTableName string
)
outputsTableName = fmt.Sprintf("%s %s\n\n%s", strings.Repeat("#", 3), ref.I18N.Get("Outputs"), ref.I18N.Get("WriteConnectionSecretToRefIntroduction"))
writeConnectionSecretToRefReferenceParameter.Name = terraform.TerraformWriteConnectionSecretToRefName
writeConnectionSecretToRefReferenceParameter.PrintableType = terraform.TerraformWriteConnectionSecretToRefType
writeConnectionSecretToRefReferenceParameter.Required = false
writeConnectionSecretToRefReferenceParameter.Usage = terraform.TerraformWriteConnectionSecretToRefDescription
if capability.ConfigurationType == "remote" {
configuration, err = utils.GetTerraformConfigurationFromRemote(capability.Name, capability.TerraformConfiguration, capability.Path)
if err != nil {
return nil, nil, fmt.Errorf("failed to retrieve Terraform configuration from %s: %w", capability.Name, err)
}
} else {
configuration = capability.TerraformConfiguration
}
variables, outputs, err := common.ParseTerraformVariables(configuration)
if err != nil {
return nil, nil, errors.Wrap(err, "failed to generate capability properties")
}
for _, v := range variables {
var refParam ReferenceParameter
refParam.Name = v.Name
refParam.PrintableType = strings.ReplaceAll(v.Type, "\n", `\n`)
refParam.Usage = strings.ReplaceAll(v.Description, "\n", `\n`)
refParam.Required = v.Required
refParameterList = append(refParameterList, refParam)
}
refParameterList = append(refParameterList, writeConnectionSecretToRefReferenceParameter)
sort.SliceStable(refParameterList, func(i, j int) bool {
return refParameterList[i].Name < refParameterList[j].Name
})
tables = append(tables, ReferenceParameterTable{
Name: "",
Parameters: refParameterList,
})
var (
writeSecretRefNameParam ReferenceParameter
writeSecretRefNameSpaceParam ReferenceParameter
)
// prepare `## writeConnectionSecretToRef`
writeSecretRefNameParam.Name = "name"
writeSecretRefNameParam.PrintableType = "string"
writeSecretRefNameParam.Required = true
writeSecretRefNameParam.Usage = terraform.TerraformSecretNameDescription
writeSecretRefNameSpaceParam.Name = "namespace"
writeSecretRefNameSpaceParam.PrintableType = "string"
writeSecretRefNameSpaceParam.Required = false
writeSecretRefNameSpaceParam.Usage = terraform.TerraformSecretNamespaceDescription
writeSecretRefParameterList := []ReferenceParameter{writeSecretRefNameParam, writeSecretRefNameSpaceParam}
writeSecretTableName := fmt.Sprintf("%s %s", strings.Repeat("#", 4), terraform.TerraformWriteConnectionSecretToRefName)
sort.SliceStable(writeSecretRefParameterList, func(i, j int) bool {
return writeSecretRefParameterList[i].Name < writeSecretRefParameterList[j].Name
})
tables = append(tables, ReferenceParameterTable{
Name: writeSecretTableName,
Parameters: writeSecretRefParameterList,
})
// outputs
for _, v := range outputs {
var refParam ReferenceParameter
refParam.Name = v.Name
refParam.Usage = v.Description
outputsList = append(outputsList, refParam)
}
sort.SliceStable(outputsList, func(i, j int) bool {
return outputsList[i].Name < outputsList[j].Name
})
outputsTables = append(outputsTables, ReferenceParameterTable{
Name: outputsTableName,
Parameters: outputsList,
})
return tables, outputsTables, nil
}
// ParseLocalFile parse the local file and get name, configuration from local ComponentDefinition file
func ParseLocalFile(localFilePath string, c common.Args) (*types.Capability, error) {
data, err := pkgUtils.ReadRemoteOrLocalPath(localFilePath, false)
if err != nil {
return nil, errors.Wrap(err, "failed to read local file or url")
}
if strings.HasSuffix(localFilePath, "yaml") {
jsonData, err := yaml.YAMLToJSON(data)
if err != nil {
return nil, errors.Wrap(err, "failed to convert yaml data into k8s valid json format")
}
var localDefinition v1beta1.ComponentDefinition
if err = json.Unmarshal(jsonData, &localDefinition); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal data into componentDefinition")
}
desc := localDefinition.ObjectMeta.Annotations["definition.oam.dev/description"]
lcap := &types.Capability{
Name: localDefinition.ObjectMeta.Name,
Description: desc,
TerraformConfiguration: localDefinition.Spec.Schematic.Terraform.Configuration,
ConfigurationType: localDefinition.Spec.Schematic.Terraform.Type,
Path: localDefinition.Spec.Schematic.Terraform.Path,
}
lcap.Type = types.TypeComponentDefinition
lcap.Category = types.TerraformCategory
return lcap, nil
}
// local definition for general definition in CUE format
def := pkgdef.Definition{Unstructured: unstructured.Unstructured{}}
config, err := c.GetConfig()
if err != nil {
return nil, errors.Wrap(err, "get kubeconfig")
}
if err = def.FromCUEString(string(data), config); err != nil {
return nil, errors.Wrapf(err, "failed to parse CUE for definition")
}
lcap, err := ParseCapabilityFromUnstructured(nil, def.Unstructured)
if err != nil {
return nil, errors.Wrapf(err, "fail to parse definition to capability")
}
return &lcap, nil
}
// WalkParameterSchema will extract properties from *openapi3.Schema
func WalkParameterSchema(parameters *openapi3.Schema, name string, depth int) {
if parameters == nil {
return
}
var schemas []CommonSchema
var commonParameters []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, CommonSchema{
Name: k,
Schemas: v.Value,
})
}
p.PrintableType = fmt.Sprintf("[%s](#%s)", k, k)
}
commonParameters = append(commonParameters, p)
}
commonRefs = append(commonRefs, CommonReference{
Name: fmt.Sprintf("%s %s", strings.Repeat("#", depth+1), name),
Parameters: commonParameters,
Depth: depth + 1,
})
for _, schema := range schemas {
WalkParameterSchema(schema.Schemas, schema.Name, depth+1)
}
}

View File

@@ -17,7 +17,6 @@ limitations under the License.
package plugins
import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
@@ -27,14 +26,13 @@ import (
"strings"
"testing"
"github.com/oam-dev/kubevela/pkg/utils/common"
"github.com/crossplane/crossplane-runtime/pkg/test"
"github.com/getkin/kin-openapi/openapi3"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/utils/common"
)
var RefTestDir = filepath.Join(TestDir, "ref")
@@ -46,124 +44,9 @@ func TestCreateRefTestDir(t *testing.T) {
}
}
func TestCreateMarkdown(t *testing.T) {
ctx := context.Background()
ref := &MarkdownReference{}
refZh := &MarkdownReference{}
refZh.I18N = Zh
workloadName := "workload1"
traitName := "trait1"
scopeName := "scope1"
workloadName2 := "workload2"
workloadCueTemplate := `
parameter: {
// +usage=Which image would you like to use for your service
// +short=i
image: string
}
`
traitCueTemplate := `
parameter: {
replicas: int
}
`
configuration := `
resource "alicloud_oss_bucket" "bucket-acl" {
bucket = var.bucket
acl = var.acl
}
output "BUCKET_NAME" {
value = "${alicloud_oss_bucket.bucket-acl.bucket}.${alicloud_oss_bucket.bucket-acl.extranet_endpoint}"
}
variable "bucket" {
description = "OSS bucket name"
default = "vela-website"
type = string
}
variable "acl" {
description = "OSS bucket ACL, supported 'private', 'public-read', 'public-read-write'"
default = "private"
type = string
}
`
cases := map[string]struct {
reason string
ref *MarkdownReference
capabilities []types.Capability
want error
}{
"WorkloadTypeAndTraitCapability": {
reason: "valid capabilities",
ref: ref,
capabilities: []types.Capability{
{
Name: workloadName,
Type: types.TypeWorkload,
CueTemplate: workloadCueTemplate,
Category: types.CUECategory,
},
{
Name: traitName,
Type: types.TypeTrait,
CueTemplate: traitCueTemplate,
Category: types.CUECategory,
},
{
Name: workloadName2,
TerraformConfiguration: configuration,
Type: types.TypeWorkload,
Category: types.TerraformCategory,
},
},
want: nil,
},
"ScopeTypeCapability": {
reason: "invalid capabilities",
ref: ref,
capabilities: []types.Capability{
{
Name: scopeName,
Type: types.TypeScope,
},
},
want: fmt.Errorf("the type of the capability is not right"),
},
"TerraformCapabilityInChinese": {
reason: "terraform capability",
ref: refZh,
capabilities: []types.Capability{
{
Name: workloadName2,
TerraformConfiguration: configuration,
Type: types.TypeWorkload,
Category: types.TerraformCategory,
},
},
want: nil,
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
got := tc.ref.CreateMarkdown(ctx, tc.capabilities, RefTestDir, ReferenceSourcePath, nil)
if diff := cmp.Diff(tc.want, got, test.EquateErrors()); diff != "" {
t.Errorf("\n%s\nCreateMakrdown(...): -want error, +got error:\n%s", tc.reason, diff)
}
})
}
}
func TestPrepareParameterTable(t *testing.T) {
ref := MarkdownReference{}
ref.I18N = &En
tableName := "hello"
parameterList := []ReferenceParameter{
{
@@ -173,7 +56,7 @@ func TestPrepareParameterTable(t *testing.T) {
parameterName := "cpu"
parameterList[0].Name = parameterName
parameterList[0].Required = true
refContent := ref.prepareParameter(tableName, parameterList, types.CUECategory)
refContent := ref.getParameterString(tableName, parameterList, types.CUECategory)
assert.Contains(t, refContent, parameterName)
assert.Contains(t, refContent, "cpu")
}
@@ -213,7 +96,7 @@ func TestWalkParameterSchema(t *testing.T) {
"type": "object"
}`,
ExpectRefs: map[string]map[string]ReferenceParameter{
"# Properties": {
"# Specification": {
"cmd": ReferenceParameter{
Parameter: types.Parameter{
Name: "cmd",
@@ -258,7 +141,7 @@ func TestWalkParameterSchema(t *testing.T) {
"type": "object"
}`,
ExpectRefs: map[string]map[string]ReferenceParameter{
"# Properties": {
"# Specification": {
"obj": ReferenceParameter{
Parameter: types.Parameter{
Name: "obj",
@@ -321,7 +204,7 @@ func TestWalkParameterSchema(t *testing.T) {
"type": "object"
}`,
ExpectRefs: map[string]map[string]ReferenceParameter{
"# Properties": {
"# Specification": {
"obj": ReferenceParameter{
Parameter: types.Parameter{
Name: "obj",
@@ -367,7 +250,7 @@ func TestWalkParameterSchema(t *testing.T) {
swagger, err := openapi3.NewLoader().LoadFromData(json.RawMessage(parameterJSON))
assert.Equal(t, nil, err)
parameters := swagger.Components.Schemas["parameter"].Value
WalkParameterSchema(parameters, "Properties", 0)
WalkParameterSchema(parameters, Specification, 0)
refs := make(map[string]map[string]ReferenceParameter)
for _, items := range commonRefs {
refs[items.Name] = make(map[string]ReferenceParameter)
@@ -423,7 +306,7 @@ variable "acl" {
},
want: want{
errMsg: "",
tableName1: "### Properties",
tableName1: "",
tableName2: "#### writeConnectionSecretToRef",
},
},
@@ -437,7 +320,7 @@ variable "acl" {
},
want: want{
errMsg: "",
tableName1: "### Properties",
tableName1: "",
tableName2: "#### writeConnectionSecretToRef",
},
},
@@ -525,7 +408,7 @@ func TestMakeReadableTitle(t *testing.T) {
ref := &MarkdownReference{}
refZh := &MarkdownReference{}
refZh.I18N = Zh
refZh.I18N = &Zh
testcases := []struct {
args args
@@ -652,7 +535,7 @@ func TestParseLocalFile(t *testing.T) {
}
for _, tc := range testcases {
t.Run("", func(t *testing.T) {
lc, err := ParseLocalFile(tc.localFilePath)
lc, err := ParseLocalFile(tc.localFilePath, common.Args{})
if err != nil {
t.Errorf("ParseLocalFile(...): -want: %v, got error: %s\n", tc.want, err)
}
@@ -701,11 +584,13 @@ parameter: {
cueValue, _ := common.GetCUEParameterValue(cueTemplate, nil)
defaultDepth := 0
defaultDisplay := "console"
displayFormat = &defaultDisplay
ref.parseParameters(cueValue, "Properties", defaultDepth)
assert.Equal(t, 1, len(propertyConsole))
propertyConsole[0].TableObject.Render()
w.Close()
ref.DisplayFormat = defaultDisplay
_, console, err := ref.parseParameters("", cueValue, Specification, defaultDepth, false)
assert.NoError(t, err)
assert.Equal(t, 1, len(console))
console[0].TableObject.Render()
err = w.Close()
assert.NoError(t, err)
out, _ := ioutil.ReadAll(r)
assert.True(t, strings.Contains(string(out), "map[string]#KeySecret"))
}

File diff suppressed because it is too large Load Diff

24
references/plugins/testdata/testdef.cue vendored Normal file
View File

@@ -0,0 +1,24 @@
"testdef": {
type: "component"
annotations: {
"definition.oam.dev/example-url": "http://127.0.0.1:65501/examples/applications/create-namespace.yaml"
}
labels: {}
description: "K8s-objects allow users to specify raw K8s objects in properties"
attributes: workload: type: "autodetects.core.oam.dev"
}
template: {
output: parameter.objects[0]
outputs: {
for i, v in parameter.objects {
if i > 0 {
"objects-\(i)": v
}
}
}
parameter: {
// +usage=A test key
objects: [...{}]
}
}

View File

@@ -0,0 +1,23 @@
testdeftrait: {
type: "trait"
annotations: {}
labels: {}
description: "Add host aliases on K8s pod for your workload which follows the pod spec in path 'spec.template'."
attributes: {
podDisruptive: false
appliesToWorkloads: ["*"]
}
}
template: {
patch: {
// +patchKey=ip
spec: template: spec: hostAliases: parameter.hostAliases
}
parameter: {
// +usage=Specify the hostAliases to add
hostAliases: [...{
ip: string
hostnames: [...string]
}]
}
}

View File

@@ -0,0 +1,33 @@
/*
Copyright 2022 The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package plugins
const (
// Markdown marks the format name of docs
Markdown = "markdown"
// Console marks the format name of docs
Console = "console"
)
const (
// Specification marks the title of parameter in reference docs
Specification = "Specification"
// Description marks the title of description in reference docs
Description = "Description"
// Examples marks the title of example in reference doc
Examples = "Examples"
)