diff --git a/Makefile b/Makefile index 56a47a967..6e3ef2482 100644 --- a/Makefile +++ b/Makefile @@ -65,7 +65,6 @@ dashboard-build: doc-gen: rm -r docs/en/cli/* go run hack/docgen/gen.go - go run hack/references/generate.go docs-build: ifneq ($(wildcard git-page),) diff --git a/docs/en/end-user/traits/labels.md b/docs/en/end-user/traits/annotations-and-labels.md similarity index 100% rename from docs/en/end-user/traits/labels.md rename to docs/en/end-user/traits/annotations-and-labels.md diff --git a/docs/en/end-user/traits/autoscale.md b/docs/en/end-user/traits/autoscale.md deleted file mode 100644 index 5a10d1834..000000000 --- a/docs/en/end-user/traits/autoscale.md +++ /dev/null @@ -1,44 +0,0 @@ ---- -title: Autoscale ---- - -## Description - -Automatically scales workloads by resource utilization metrics or cron triggers. - -## Specification - -List of all configuration options for a `Autoscale` trait. - -```yaml -... - min: 1 - max: 4 - cron: - startAt: "14:00" - duration: "2h" - days: "Monday, Thursday" - replicas: 2 - timezone: "America/Los_Angeles" - cpuPercent: 10 -``` - -## Properties - -Name | Description | Type | Required | Default ------------- | ------------- | ------------- | ------------- | ------------- - min | Minimal replicas of the workload | int | true | - max | Maximal replicas of the workload | int | true | - cpuPercent | Specify the value for CPU utilization, like 80, which means 80% | int | false | - cron | Cron type auto-scaling. Just for `appfile`, not available for Cli usage | [cron](#cron) | false | - - -### cron - -Name | Description | Type | Required | Default ------------- | ------------- | ------------- | ------------- | ------------- - startAt | The time to start scaling, like `08:00` | string | true | - duration | For how long the scaling will last | string | true | - days | Several workdays or weekends, like "Monday, Tuesday" | string | true | - replicas | The target replicas to be scaled to | int | true | - timezone | Timezone, like "America/Los_Angeles" | string | true | diff --git a/docs/sidebars.js b/docs/sidebars.js index 8d73e2911..c3b9733d4 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -37,7 +37,7 @@ module.exports = { 'Traits': [ 'end-user/traits/ingress', 'end-user/traits/scaler', - 'end-user/traits/labels', + 'end-user/traits/annotations-and-labels', 'end-user/traits/sidecar', 'end-user/traits/volumes', 'end-user/traits/more', diff --git a/references/cli/show.go b/references/cli/show.go index bb37fa8b9..f6c6060e8 100644 --- a/references/cli/show.go +++ b/references/cli/show.go @@ -80,7 +80,7 @@ func NewCapabilityShowCommand(c common.Args, ioStreams cmdutil.IOStreams) *cobra return err } if webSite { - return startReferenceDocsSite(ctx, c, ioStreams, capabilityName) + return startReferenceDocsSite(ctx, velaEnv.Namespace, c, ioStreams, capabilityName) } return ShowReferenceConsole(ctx, c, ioStreams, capabilityName, velaEnv.Namespace) }, @@ -94,7 +94,7 @@ func NewCapabilityShowCommand(c common.Args, ioStreams cmdutil.IOStreams) *cobra return cmd } -func startReferenceDocsSite(ctx context.Context, c common.Args, ioStreams cmdutil.IOStreams, capabilityName string) error { +func startReferenceDocsSite(ctx context.Context, ns string, c common.Args, ioStreams cmdutil.IOStreams, capabilityName string) error { home, err := system.GetVelaHomeDir() if err != nil { return err @@ -113,13 +113,11 @@ func startReferenceDocsSite(ctx context.Context, c common.Args, ioStreams cmduti return err } } - - capabilities, _, err := plugins.SyncDefinitionsToLocal(ctx, c, definitionPath) + capabilities, err := plugins.GetNamespacedCapabilitiesFromCluster(ctx, ns, c, nil) if err != nil { return err } - - // check input capability is valid + // check whether input capability is valid var capabilityIsValid bool var capabilityType types.CapType for _, c := range capabilities { @@ -378,7 +376,7 @@ func ShowReferenceConsole(ctx context.Context, c common.Args, ioStreams cmdutil. return err } case types.TerraformCategory: - propertyConsole, err = ref.GenerateTerraformCapabilityProperties(capability) + propertyConsole, err = ref.GenerateTerraformCapabilityProperties(*capability) if err != nil { return err } diff --git a/references/plugins/cluster.go b/references/plugins/cluster.go index 43e315c86..77fe42ce2 100644 --- a/references/plugins/cluster.go +++ b/references/plugins/cluster.go @@ -57,16 +57,46 @@ func GetCapabilitiesFromCluster(ctx context.Context, namespace string, c common. return workloads, nil } +// GetNamespacedCapabilitiesFromCluster will get capability from K8s cluster in the specified namespace and default namespace +// If the definition could be found from `namespace`, try to find in namespace `types.DefaultKubeVelaNS` +func GetNamespacedCapabilitiesFromCluster(ctx context.Context, namespace string, c common.Args, selector labels.Selector) ([]types.Capability, error) { + var capabilities []types.Capability + + if workloads, _, err := GetComponentsFromClusterWithValidateOption(ctx, namespace, c, selector, false); err == nil { + capabilities = append(capabilities, workloads...) + } + + if traits, _, err := GetTraitsFromClusterWithValidateOption(ctx, namespace, c, selector, false); err == nil { + capabilities = append(capabilities, traits...) + } + + // get components from default namespace + if workloads, _, err := GetComponentsFromClusterWithValidateOption(ctx, types.DefaultKubeVelaNS, c, selector, false); err == nil { + capabilities = append(capabilities, workloads...) + } + + // get traits from default namespace + if traits, _, err := GetTraitsFromClusterWithValidateOption(ctx, types.DefaultKubeVelaNS, c, selector, false); err == nil { + capabilities = append(capabilities, traits...) + } + + if len(capabilities) > 0 { + return capabilities, nil + } + return nil, fmt.Errorf("could not find any components or traits from namespace %s and %s", namespace, types.DefaultKubeVelaNS) +} + // GetComponentsFromCluster will get capability from K8s cluster func GetComponentsFromCluster(ctx context.Context, namespace string, c common.Args, selector labels.Selector) ([]types.Capability, []error, error) { + return GetComponentsFromClusterWithValidateOption(ctx, namespace, c, selector, true) +} + +// GetComponentsFromClusterWithValidateOption will get capability from K8s cluster with an option whether to valid Components +func GetComponentsFromClusterWithValidateOption(ctx context.Context, namespace string, c common.Args, selector labels.Selector, validateFlag bool) ([]types.Capability, []error, error) { newClient, err := c.GetClient() if err != nil { return nil, nil, err } - dm, err := discoverymapper.New(c.Config) - if err != nil { - return nil, nil, err - } var templates []types.Capability var componentsDefs v1beta1.ComponentDefinitionList @@ -77,27 +107,37 @@ func GetComponentsFromCluster(ctx context.Context, namespace string, c common.Ar var templateErrors []error for _, cd := range componentsDefs.Items { - ref, err := util.ConvertWorkloadGVK2Definition(dm, cd.Spec.Workload.Definition) + dm, err := c.GetDiscoveryMapper() if err != nil { - templateErrors = append(templateErrors, errors.Wrapf(err, "convert workload definition `%s` failed", cd.Name)) - continue - } - tmp, err := HandleDefinition(cd.Name, ref.Name, cd.Annotations, cd.Spec.Extension, types.TypeComponentDefinition, nil, cd.Spec.Schematic) - if err != nil { - templateErrors = append(templateErrors, errors.Wrapf(err, "handle workload template `%s` failed", cd.Name)) - continue - } - tmp.Namespace = namespace - if tmp, err = validateCapabilities(tmp, dm, cd.Name, ref); err != nil { return nil, nil, err } - templates = append(templates, tmp) + ref, err := util.ConvertWorkloadGVK2Definition(dm, cd.Spec.Workload.Definition) + if err != nil { + return nil, nil, err + } + + tmp, err := GetCapabilityByComponentDefinitionObject(cd, ref.Name) + if err != nil { + templateErrors = append(templateErrors, err) + continue + } + if validateFlag { + if err = validateCapabilities(tmp, dm, cd.Name, ref); err != nil { + return nil, nil, err + } + } + templates = append(templates, *tmp) } return templates, templateErrors, nil } // GetTraitsFromCluster will get capability from K8s cluster func GetTraitsFromCluster(ctx context.Context, namespace string, c common.Args, selector labels.Selector) ([]types.Capability, []error, error) { + return GetTraitsFromClusterWithValidateOption(ctx, namespace, c, selector, true) +} + +// GetTraitsFromClusterWithValidateOption will get capability from K8s cluster with an option whether to valid Traits +func GetTraitsFromClusterWithValidateOption(ctx context.Context, namespace string, c common.Args, selector labels.Selector, validateFlag bool) ([]types.Capability, []error, error) { newClient, err := c.GetClient() if err != nil { return nil, nil, err @@ -115,28 +155,30 @@ func GetTraitsFromCluster(ctx context.Context, namespace string, c common.Args, var templateErrors []error for _, td := range traitDefs.Items { - tmp, err := HandleDefinition(td.Name, td.Spec.Reference.Name, td.Annotations, td.Spec.Extension, types.TypeTrait, td.Spec.AppliesToWorkloads, td.Spec.Schematic) + tmp, err := GetCapabilityByTraitDefinitionObject(td) if err != nil { templateErrors = append(templateErrors, errors.Wrapf(err, "handle trait template `%s` failed", td.Name)) continue } tmp.Namespace = namespace - if tmp, err = validateCapabilities(tmp, dm, td.Name, td.Spec.Reference); err != nil { - return nil, nil, err + if validateFlag { + if err = validateCapabilities(tmp, dm, td.Name, td.Spec.Reference); err != nil { + return nil, nil, err + } } - templates = append(templates, tmp) + templates = append(templates, *tmp) } return templates, templateErrors, nil } // validateCapabilities validates whether helm charts are successful installed, GVK are successfully retrieved. -func validateCapabilities(tmp types.Capability, dm discoverymapper.DiscoveryMapper, definitionName string, reference commontypes.DefinitionReference) (types.Capability, error) { +func validateCapabilities(tmp *types.Capability, dm discoverymapper.DiscoveryMapper, definitionName string, reference commontypes.DefinitionReference) error { var err error if tmp.Install != nil { tmp.Source = &types.Source{ChartName: tmp.Install.Helm.Name} ioStream := util2.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr} if err = helm.InstallHelmChart(ioStream, tmp.Install.Helm); err != nil { - return tmp, fmt.Errorf("unable to install helm chart dependency %s(%s from %s) for this trait '%s': %w ", tmp.Install.Helm.Name, tmp.Install.Helm.Version, tmp.Install.Helm.URL, definitionName, err) + return fmt.Errorf("unable to install helm chart dependency %s(%s from %s) for this trait '%s': %w ", tmp.Install.Helm.Name, tmp.Install.Helm.Version, tmp.Install.Helm.URL, definitionName, err) } } gvk, err := util.GetGVKFromDefinition(dm, reference) @@ -146,14 +188,14 @@ func validateCapabilities(tmp types.Capability, dm discoverymapper.DiscoveryMapp if strings.Contains(errMsg, substr) { err = fmt.Errorf("expected provider: %s", strings.Split(errMsg, substr)[1]) } - return tmp, fmt.Errorf("installing capability '%s'... %w", definitionName, err) + return fmt.Errorf("installing capability '%s'... %w", definitionName, err) } tmp.CrdInfo = &types.CRDInfo{ APIVersion: gvk.GroupVersion().String(), Kind: gvk.Kind, } - return tmp, nil + return nil } // HandleDefinition will handle definition to capability @@ -226,42 +268,11 @@ func HandleTemplate(in *runtime.RawExtension, schematic *commontypes.Schematic, return tmp, nil } -// SyncDefinitionsToLocal sync definitions to local -func SyncDefinitionsToLocal(ctx context.Context, c common.Args, localDefinitionDir string) ([]types.Capability, []string, error) { - var syncedTemplates []types.Capability - var warnings []string - - templates, templateErrors, err := GetComponentsFromCluster(ctx, types.DefaultKubeVelaNS, c, nil) - if err != nil { - return nil, nil, err - } - if len(templateErrors) > 0 { - for _, e := range templateErrors { - warnings = append(warnings, fmt.Sprintf("WARN: %v, you will unable to use this component capability\n", e)) - } - } - syncedTemplates = append(syncedTemplates, templates...) - SinkTemp2Local(templates, localDefinitionDir) - - templates, templateErrors, err = GetTraitsFromCluster(ctx, types.DefaultKubeVelaNS, c, nil) - if err != nil { - return nil, warnings, err - } - if len(templateErrors) > 0 { - for _, e := range templateErrors { - warnings = append(warnings, fmt.Sprintf("WARN: %v, you will unable to use this trait capability\n", e)) - } - } - syncedTemplates = append(syncedTemplates, templates...) - SinkTemp2Local(templates, localDefinitionDir) - return syncedTemplates, warnings, nil -} - // GetCapabilityByName gets capability by definition name func GetCapabilityByName(ctx context.Context, c common.Args, capabilityName string, ns string) (*types.Capability, error) { var ( foundCapability bool - template types.Capability + capability *types.Capability err error ) @@ -289,13 +300,11 @@ func GetCapabilityByName(ctx context.Context, c common.Args, capabilityName stri if err != nil { return nil, err } - template, err = HandleDefinition(capabilityName, ref.Name, - componentDef.Annotations, componentDef.Spec.Extension, types.TypeComponentDefinition, nil, componentDef.Spec.Schematic) + capability, err = GetCapabilityByComponentDefinitionObject(componentDef, ref.Name) if err != nil { - return nil, errors.Wrap(err, "failed to handle ComponentDefinition") + return nil, err } - template.Namespace = componentDef.Namespace - return &template, nil + return capability, nil } foundCapability = false @@ -310,13 +319,37 @@ func GetCapabilityByName(ctx context.Context, c common.Args, capabilityName stri } } if foundCapability { - template, err = HandleDefinition(capabilityName, traitDef.Spec.Reference.Name, - traitDef.Annotations, traitDef.Spec.Extension, types.TypeTrait, nil, traitDef.Spec.Schematic) + capability, err = GetCapabilityByTraitDefinitionObject(traitDef) if err != nil { - return nil, errors.Wrap(err, "failed to handle TraitDefinition") + return nil, err } - template.Namespace = traitDef.Namespace - return &template, nil + return capability, nil } return nil, fmt.Errorf("cloud not find %s is namespace %s, or %s", capabilityName, ns, types.DefaultKubeVelaNS) } + +// GetCapabilityByComponentDefinitionObject gets capability by ComponentDefinition object +func GetCapabilityByComponentDefinitionObject(componentDef v1beta1.ComponentDefinition, referenceName string) (*types.Capability, error) { + capability, err := HandleDefinition(componentDef.Name, referenceName, + componentDef.Annotations, componentDef.Spec.Extension, types.TypeComponentDefinition, nil, componentDef.Spec.Schematic) + if err != nil { + return nil, errors.Wrap(err, "failed to handle ComponentDefinition") + } + capability.Namespace = componentDef.Namespace + return &capability, nil +} + +// GetCapabilityByTraitDefinitionObject gets capability by TraitDefinition object +func GetCapabilityByTraitDefinitionObject(traitDef v1beta1.TraitDefinition) (*types.Capability, error) { + var ( + capability types.Capability + err error + ) + capability, err = HandleDefinition(traitDef.Name, traitDef.Spec.Reference.Name, + traitDef.Annotations, traitDef.Spec.Extension, types.TypeTrait, nil, traitDef.Spec.Schematic) + if err != nil { + return nil, errors.Wrap(err, "failed to handle TraitDefinition") + } + capability.Namespace = traitDef.Namespace + return &capability, nil +} diff --git a/references/plugins/cluster_test.go b/references/plugins/cluster_test.go index 8fd1e174f..0c137dce4 100644 --- a/references/plugins/cluster_test.go +++ b/references/plugins/cluster_test.go @@ -19,23 +19,27 @@ package plugins import ( "context" "fmt" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" + "io/ioutil" "cuelang.org/go/cue" + "github.com/ghodss/yaml" "github.com/google/go-cmp/cmp" + "github.com/oam-dev/kubevela/pkg/oam/util" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/selection" logf "sigs.k8s.io/controller-runtime/pkg/log" + corev1beta1 "github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1" "github.com/oam-dev/kubevela/apis/types" "github.com/oam-dev/kubevela/pkg/utils/common" ) const ( TestDir = "testdata" - RouteName = "routes.test" DeployName = "deployments.testapps" WebserviceName = "webservice.testapps" ) @@ -132,3 +136,163 @@ var _ = Describe("DefinitionFiles", func() { Expect(cmp.Diff(alldef, []types.Capability{deployment, websvc})).Should(BeEquivalentTo("")) }) }) + +var _ = Describe("test GetCapabilityByName", func() { + var ( + ctx context.Context + c common.Args + ns string + defaultNS string + cd1 corev1beta1.ComponentDefinition + cd2 corev1beta1.ComponentDefinition + td1 corev1beta1.TraitDefinition + td2 corev1beta1.TraitDefinition + component1 string + component2 string + trait1 string + trait2 string + ) + BeforeEach(func() { + c = common.Args{ + Client: k8sClient, + Config: cfg, + Schema: scheme, + } + ctx = context.Background() + ns = "cluster-test-ns" + defaultNS = types.DefaultKubeVelaNS + component1 = "cd1" + component2 = "cd2" + trait1 = "td1" + trait2 = "td2" + + By("create namespace") + Expect(k8sClient.Create(ctx, &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: ns}})).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{})) + Expect(k8sClient.Create(ctx, &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: defaultNS}})).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{})) + + By("create ComponentDefinition") + data, _ := ioutil.ReadFile("testdata/componentDef.yaml") + yaml.Unmarshal(data, &cd1) + yaml.Unmarshal(data, &cd2) + cd1.Namespace = ns + cd1.Name = component1 + Expect(k8sClient.Create(ctx, &cd1)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{})) + + cd2.Namespace = defaultNS + cd2.Name = component2 + Expect(k8sClient.Create(ctx, &cd2)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{})) + + By("create TraitDefinition") + data, _ = ioutil.ReadFile("testdata/manualscalars.yaml") + yaml.Unmarshal(data, &td1) + yaml.Unmarshal(data, &td2) + td1.Namespace = ns + td1.Name = trait1 + Expect(k8sClient.Create(ctx, &td1)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{})) + + td2.Namespace = defaultNS + td2.Name = trait2 + Expect(k8sClient.Create(ctx, &td2)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{})) + + }) + + It("get capability", func() { + Context("ComponentDefinition is in the current namespace", func() { + _, err := GetCapabilityByName(ctx, c, component1, ns) + Expect(err).Should(BeNil()) + }) + Context("ComponentDefinition is in the default namespace", func() { + _, err := GetCapabilityByName(ctx, c, component2, ns) + Expect(err).Should(BeNil()) + }) + + Context("TraitDefinition is in the current namespace", func() { + _, err := GetCapabilityByName(ctx, c, trait1, ns) + Expect(err).Should(BeNil()) + }) + Context("TraitDefinitionDefinition is in the default namespace", func() { + _, err := GetCapabilityByName(ctx, c, trait2, ns) + Expect(err).Should(BeNil()) + }) + + Context("capability cloud not be found", func() { + _, err := GetCapabilityByName(ctx, c, "a-component-definition-not-existed", ns) + Expect(err).Should(HaveOccurred()) + }) + }) +}) + +var _ = Describe("test GetNamespacedCapabilitiesFromCluster", func() { + var ( + ctx context.Context + c common.Args + ns string + defaultNS string + cd1 corev1beta1.ComponentDefinition + cd2 corev1beta1.ComponentDefinition + td1 corev1beta1.TraitDefinition + td2 corev1beta1.TraitDefinition + component1 string + component2 string + trait1 string + trait2 string + ) + BeforeEach(func() { + c = common.Args{ + Client: k8sClient, + Config: cfg, + Schema: scheme, + } + ctx = context.Background() + ns = "cluster-test-ns" + defaultNS = types.DefaultKubeVelaNS + component1 = "cd1" + component2 = "cd2" + trait1 = "td1" + trait2 = "td2" + + By("create namespace") + Expect(k8sClient.Create(ctx, &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: ns}})).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{})) + Expect(k8sClient.Create(ctx, &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: defaultNS}})).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{})) + + By("create ComponentDefinition") + data, _ := ioutil.ReadFile("testdata/componentDef.yaml") + yaml.Unmarshal(data, &cd1) + yaml.Unmarshal(data, &cd2) + cd1.Namespace = ns + cd1.Name = component1 + Expect(k8sClient.Create(ctx, &cd1)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{})) + + cd2.Namespace = defaultNS + cd2.Name = component2 + Expect(k8sClient.Create(ctx, &cd2)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{})) + + By("create TraitDefinition") + data, _ = ioutil.ReadFile("testdata/manualscalars.yaml") + yaml.Unmarshal(data, &td1) + yaml.Unmarshal(data, &td2) + td1.Namespace = ns + td1.Name = trait1 + Expect(k8sClient.Create(ctx, &td1)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{})) + + td2.Namespace = defaultNS + td2.Name = trait2 + Expect(k8sClient.Create(ctx, &td2)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{})) + + }) + + It("get namespaced capabilities", func() { + Context("found all capabilities", func() { + capabilities, err := GetNamespacedCapabilitiesFromCluster(ctx, ns, c, nil) + Expect(len(capabilities)).Should(Equal(4)) + Expect(err).Should(BeNil()) + }) + + Context("found two capabilities with a bad namespace", func() { + capabilities, err := GetNamespacedCapabilitiesFromCluster(ctx, "a-bad-ns", c, nil) + Expect(len(capabilities)).Should(Equal(2)) + Expect(err).Should(BeNil()) + }) + + }) +}) diff --git a/references/plugins/reference_test.go b/references/plugins/reference_test.go index 9463355ad..5f1ecbee6 100644 --- a/references/plugins/reference_test.go +++ b/references/plugins/reference_test.go @@ -46,6 +46,7 @@ func TestCreateMarkdown(t *testing.T) { workloadName := "workload1" traitName := "trait1" scopeName := "scope1" + workloadName2 := "workload2" workloadCueTemplate := ` parameter: { @@ -58,6 +59,29 @@ parameter: { 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 { @@ -80,6 +104,12 @@ parameter: { CueTemplate: traitCueTemplate, Category: types.CUECategory, }, + { + Name: workloadName2, + TerraformConfiguration: configuration, + Type: types.TypeWorkload, + Category: types.TerraformCategory, + }, }, want: nil, }, @@ -110,11 +140,9 @@ parameter: { func TestPrepareParameterTable(t *testing.T) { ref := MarkdownReference{} tableName := "hello" - var depth int = 1 parameterList := []ReferenceParameter{ { PrintableType: "string", - Depth: &depth, }, } parameterName := "cpu" @@ -329,7 +357,7 @@ func TestWalkParameterSchema(t *testing.T) { func TestGenerateTerraformCapabilityProperties(t *testing.T) { ref := &ConsoleReference{} type args struct { - cap *types.Capability + cap types.Capability } type want struct { @@ -343,7 +371,7 @@ func TestGenerateTerraformCapabilityProperties(t *testing.T) { }{ "normal": { args: args{ - cap: &types.Capability{ + cap: types.Capability{ TerraformConfiguration: ` resource "alicloud_oss_bucket" "bucket-acl" { bucket = var.bucket @@ -376,7 +404,7 @@ variable "acl" { }, "configuration is not valid": { args: args{ - cap: &types.Capability{ + cap: types.Capability{ TerraformConfiguration: `abc`, }, }, diff --git a/references/plugins/references.go b/references/plugins/references.go index dce7dbcc3..dc7723c2d 100644 --- a/references/plugins/references.go +++ b/references/plugins/references.go @@ -42,11 +42,11 @@ import ( const ( // BaseRefPath is the target path for reference docs - BaseRefPath = "docs/en/developers/references" + BaseRefPath = "docs/en/end-user" // ReferenceSourcePath is the location for source reference ReferenceSourcePath = "hack/references" // ComponentDefinitionTypePath is the URL path for component typed capability - ComponentDefinitionTypePath = "component-types" + ComponentDefinitionTypePath = "components" // WorkloadTypePath is the URL path for workload typed capability WorkloadTypePath = "workload-types" // TraitPath is the URL path for trait typed capability @@ -54,12 +54,8 @@ const ( ) const ( - // TerraformVariableName is the name for Terraform Variable - TerraformVariableName = "variable" // TerraformWriteConnectionSecretToRefName is the name for Terraform WriteConnectionSecretToRef TerraformWriteConnectionSecretToRefName = "writeConnectionSecretToRef" - // TerraformVariableType is the type for Terraform Variable - TerraformVariableType = "[variable](#variable)" // TerraformWriteConnectionSecretToRefType is the type for Terraform WriteConnectionSecretToRef TerraformWriteConnectionSecretToRefType = "[writeConnectionSecretToRef](#writeConnectionSecretToRef)" ) @@ -97,106 +93,224 @@ type ConsoleReference struct { // ConfigurationYamlSample stores the configuration yaml sample for capabilities var ConfigurationYamlSample = map[string]string{ - "autoscale": ` -name: testapp - -services: - express-server: - ... - - autoscale: - min: 1 - max: 4 - cron: - startAt: "14:00" - duration: "2h" - days: "Monday, Thursday" - replicas: 2 - timezone: "America/Los_Angeles" - cpuPercent: 10 + "annotations": ` +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" `, + + "ingress": ` +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 +`, + + "labels": ` +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" +`, + "metrics": ` -name: my-app-name - -services: - my-service-name: - ... - metrics: - format: "prometheus" - port: 8080 - path: "/metrics" - scheme: "http" - enabled: true -`, - "rollout": ` -servcies: - express-server: - ... - - rollout: - replicas: 2 - stepWeight: 50 - interval: "10s" -`, - "route": ` -name: my-app-name - -services: - my-service-name: - ... - route: - domain: example.com - issuer: tls - rules: - - path: /testapp - rewriteTarget: / -`, - "scaler": ` -name: my-app-name - -services: - my-service-name: - ... - scaler: - replicas: 100 -`, - "task": ` -name: my-app-name - -services: - my-service-name: - type: task - image: perl - count: 10 - cmd: ["perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"] -`, - "webservice": ` -name: my-app-name - -services: - my-service-name: - type: webservice # could be skipped - image: oamdev/testapp:v1 - cmd: ["node", "server.js"] +... + format: "prometheus" port: 8080 - cpu: "0.1" - env: - - name: FOO - value: bar - - name: FOO - valueFrom: - secretKeyRef: - name: bar - key: bar + path: "/metrics" + scheme: "http" + enabled: true +`, + + "route": ` +... + domain: example.com + issuer: tls + rules: + - path: /testapp + rewriteTarget: / +`, + + "scaler": ` +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' +`, + + "sidecar": ` +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 +`, + + "task": ` +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)"] +`, + + "volumes": ` +apiVersion: core.oam.dev/v1beta1 +kind: Application +metadata: + name: app-worker +spec: + components: + - name: myworker + type: worker + properties: + image: "busybox" + cmd: + - sleep + - "1000" + traits: + - type: aws-ebs-volume + properties: + name: "my-ebs" + mountPath: "/myebs" + volumeID: "my-ebs-id" +`, + + "webservice": ` +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"] + port: 8080 + cpu: "0.1" + env: + - name: FOO + value: bar + - name: FOO + valueFrom: + secretKeyRef: + name: bar + key: bar `, "worker": ` -name: my-app-name - -services: - my-service-name: - type: worker - image: oamdev/testapp:v1 - cmd: ["node", "server.js"] +apiVersion: core.oam.dev/v1beta1 +kind: Application +metadata: + name: app-worker +spec: + components: + - name: myworker + type: worker + properties: + image: "busybox" + cmd: + - sleep + - "1000" `, } @@ -220,8 +334,13 @@ type ReferenceParameter struct { types.Parameter `json:",inline,omitempty"` // PrintableType is same to `parameter.Type` which could be printable PrintableType string `json:"printableType"` - // Depth marks the depth for calling of function `parseParameters` - Depth *int `json:"depth"` +} + +// ReferenceParameterTable stores the information of a bunch of ReferenceParameter in a table style +type ReferenceParameterTable struct { + Name string + Parameters []ReferenceParameter + Depth *int } var refContent string @@ -254,19 +373,14 @@ func (ref *MarkdownReference) GenerateReferenceDocs(ctx context.Context, baseRef func (ref *MarkdownReference) CreateMarkdown(ctx context.Context, caps []types.Capability, baseRefPath, referenceSourcePath string) error { setDisplayFormat("markdown") var capabilityType string - var specificationType string for i, c := range caps { switch c.Type { case types.TypeWorkload: capabilityType = WorkloadTypePath - specificationType = "workload type" case types.TypeComponentDefinition: capabilityType = ComponentDefinitionTypePath - specificationType = "component type" case types.TypeTrait: capabilityType = TraitPath - specificationType = "trait" - default: return fmt.Errorf("the type of the capability is not right") } @@ -309,20 +423,28 @@ func (ref *MarkdownReference) CreateMarkdown(ctx context.Context, caps []types.C for _, property := range properties { refContent += ref.prepareParameter("#"+property.Name, property.Parameters, types.HelmCategory) } + case types.TerraformCategory: + refContent, err = ref.GenerateTerraformCapabilityProperties(c) + if err != nil { + return err + } default: return fmt.Errorf("unsupport capability category %s", c.Category) } + title := fmt.Sprintf("%s\n===============", capNameInTitle) - title := fmt.Sprintf("# %s", capNameInTitle) - description := fmt.Sprintf("\n\n## Description\n\n%s", c.Description) - specificationIntro := fmt.Sprintf("List of all configuration options for a `%s` %s.", capNameInTitle, specificationType) - specificationContent := ref.generateSpecification(capName) - specification := fmt.Sprintf("\n\n## Specification\n\n%s\n\n%s", specificationIntro, specificationContent) + description := fmt.Sprintf("\n\n# Description\n\n%s", c.Description) + var sample string + sampleContent := ref.generateSample(capName) + if sampleContent != "" { + sample = fmt.Sprintf("\n\n# Samples\n\n%s", sampleContent) + } + specification := fmt.Sprintf("\n\n# Specification\n%s", refContent) // it's fine if the conflict info files not found conflictWithAndMoreSection, _ := ref.generateConflictWithAndMore(capName, referenceSourcePath) - refContent = title + description + specification + refContent + conflictWithAndMoreSection + refContent = title + description + sample + conflictWithAndMoreSection + specification if _, err := f.WriteString(refContent); err != nil { return err } @@ -349,6 +471,11 @@ func (ref *MarkdownReference) prepareParameter(tableName string, parameterList [ printableDefaultValue := ref.getHELMPrintableDefaultValue(p.JSONType, p.Default) refContent += fmt.Sprintf(" %s | %s | %s | %t | %s \n", p.Name, strings.ReplaceAll(p.Usage, "\n", ""), p.PrintableType, p.Required, printableDefaultValue) } + case types.TerraformCategory: + // Terraform doesn't have default value + for _, p := range parameterList { + refContent += fmt.Sprintf(" %s | %s | %s | %t | %s \n", p.Name, strings.ReplaceAll(p.Usage, "\n", ""), p.PrintableType, p.Required, "") + } default: } return refContent @@ -375,7 +502,6 @@ func (ref *ParseReference) prepareParameter(tableName string, parameterList []Re for _, p := range parameterList { table.Append([]string{p.Name, p.Usage, p.PrintableType, strconv.FormatBool(p.Required), ""}) } - default: } @@ -510,9 +636,13 @@ func (ref *ParseReference) getHELMPrintableDefaultValue(dataType string, value i return defaultValueMap[dataType] } -// generateSpecification generates Specification part for reference docs -func (ref *MarkdownReference) generateSpecification(capabilityName string) string { - return fmt.Sprintf("```yaml%s```", ConfigurationYamlSample[capabilityName]) +// generateSample generates Specification part for reference docs +func (ref *MarkdownReference) generateSample(capabilityName string) string { + // TODO(zzxwill): we should generate the sample automatically instead of maintain hardcode example. + if _, ok := ConfigurationYamlSample[capabilityName]; ok { + return fmt.Sprintf("```yaml%s```", ConfigurationYamlSample[capabilityName]) + } + return "" } // generateConflictWithAndMore generates Section `Conflicts With` and more like `How xxx works` in reference docs @@ -586,6 +716,66 @@ func (ref *ParseReference) GenerateHELMProperties(ctx context.Context, capabilit return helmRefs, consoleRefs, err } +// GenerateTerraformCapabilityProperties generates Capability properties for Terraform ComponentDefinition +func (ref *ParseReference) parseTerraformCapabilityParameters(capability types.Capability) ([]ReferenceParameterTable, error) { + var ( + tables []ReferenceParameterTable + refParameterList []ReferenceParameter + writeConnectionSecretToRefReferenceParameter ReferenceParameter + ) + + writeConnectionSecretToRefReferenceParameter.Name = TerraformWriteConnectionSecretToRefName + writeConnectionSecretToRefReferenceParameter.PrintableType = TerraformWriteConnectionSecretToRefType + writeConnectionSecretToRefReferenceParameter.Required = false + writeConnectionSecretToRefReferenceParameter.Usage = "The secret which the cloud resource connection will be written to" + + variables, err := ref.parseTerraformVariables(capability.TerraformConfiguration) + if err != nil { + return nil, errors.Wrap(err, "failed to generate capability properties") + } + for _, v := range variables { + var refParam ReferenceParameter + refParam.Name = v.Name + refParam.PrintableType = v.Type + refParam.Usage = v.Description + refParam.Required = true + refParameterList = append(refParameterList, refParam) + } + refParameterList = append(refParameterList, writeConnectionSecretToRefReferenceParameter) + + propertiesTableName := fmt.Sprintf("%s %s", strings.Repeat("#", 1), "Properties") + tables = append(tables, ReferenceParameterTable{ + Name: propertiesTableName, + Parameters: refParameterList, + }) + + var ( + writeSecretRefNameParam ReferenceParameter + writeSecretRefNameSpaceParam ReferenceParameter + ) + + // prepare `## writeConnectionSecretToRef` + writeSecretRefNameParam.Name = "name" + writeSecretRefNameParam.PrintableType = "string" + writeSecretRefNameParam.Required = true + writeSecretRefNameParam.Usage = "The secret name which the cloud resource connection will be written to" + + writeSecretRefNameSpaceParam.Name = "namespace" + writeSecretRefNameSpaceParam.PrintableType = "string" + writeSecretRefNameSpaceParam.Required = false + writeSecretRefNameSpaceParam.Usage = "The secret namespace which the cloud resource connection will be written to" + + writeSecretRefParameterList := []ReferenceParameter{writeSecretRefNameParam, writeSecretRefNameSpaceParam} + writeSecretTableName := fmt.Sprintf("%s %s", strings.Repeat("#", 2), TerraformWriteConnectionSecretToRefName) + + tables = append(tables, ReferenceParameterTable{ + Name: writeSecretTableName, + Parameters: writeSecretRefParameterList, + }) + + return tables, nil +} + // WalkParameterSchema will extract properties from *openapi3.Schema func WalkParameterSchema(parameters *openapi3.Schema, name string, depth int) { if parameters == nil { @@ -634,57 +824,30 @@ func WalkParameterSchema(parameters *openapi3.Schema, name string, depth int) { } } -// GenerateTerraformCapabilityProperties generates Capability properties for Terraform ComponentDefinition -// application developers need to set two sections `variable` and `writeConnectionSecretToRef` in an application -func (ref *ConsoleReference) GenerateTerraformCapabilityProperties(capability *types.Capability) ([]ConsoleReference, error) { - var ( - writeConnectionSecretToRefReferenceParameter ReferenceParameter - propertiesConsoleReference ConsoleReference - writeSecretConsoleReference ConsoleReference - ) +// GenerateTerraformCapabilityProperties generates Capability properties for Terraform ComponentDefinition in Cli console +func (ref *ConsoleReference) GenerateTerraformCapabilityProperties(capability types.Capability) ([]ConsoleReference, error) { + var references []ConsoleReference - writeConnectionSecretToRefReferenceParameter.Name = TerraformWriteConnectionSecretToRefName - writeConnectionSecretToRefReferenceParameter.PrintableType = TerraformWriteConnectionSecretToRefType - writeConnectionSecretToRefReferenceParameter.Required = false - writeConnectionSecretToRefReferenceParameter.Usage = "The secret which the cloud resource connection will be written to" - - variables, err := ref.parseTerraformVariables(capability.TerraformConfiguration) + tables, err := ref.parseTerraformCapabilityParameters(capability) if err != nil { - return nil, errors.Wrap(err, "failed to generate capability properties") + return nil, err } - var refParameterList []ReferenceParameter - for _, v := range variables { - var refParam ReferenceParameter - refParam.Name = v.Name - refParam.PrintableType = v.Type - refParam.Usage = v.Description - refParam.Required = true - refParameterList = append(refParameterList, refParam) + for _, t := range tables { + references = append(references, ref.prepareParameter(t.Name, t.Parameters, types.TerraformCategory)) } - refParameterList = append(refParameterList, writeConnectionSecretToRefReferenceParameter) - - propertiesTableName := fmt.Sprintf("%s %s", strings.Repeat("#", 1), "Properties") - propertiesConsoleReference = ref.prepareParameter(propertiesTableName, refParameterList, types.CUECategory) - - var ( - writeSecretRefNameParam ReferenceParameter - writeSecretRefNameSpaceParam ReferenceParameter - ) - - // prepare `## writeConnectionSecretToRef` - writeSecretRefNameParam.Name = "name" - writeSecretRefNameParam.PrintableType = "string" - writeSecretRefNameParam.Required = true - writeSecretRefNameParam.Usage = "The secret name which the cloud resource connection will be written to" - - writeSecretRefNameSpaceParam.Name = "namespace" - writeSecretRefNameSpaceParam.PrintableType = "string" - writeSecretRefNameSpaceParam.Required = false - writeSecretRefNameSpaceParam.Usage = "The secret namespace which the cloud resource connection will be written to" - - writeSecretRefParameterList := []ReferenceParameter{writeSecretRefNameParam, writeSecretRefNameSpaceParam} - writeSecretTableName := fmt.Sprintf("%s %s", strings.Repeat("#", 2), TerraformWriteConnectionSecretToRefName) - writeSecretConsoleReference = ref.prepareParameter(writeSecretTableName, writeSecretRefParameterList, types.CUECategory) - - return []ConsoleReference{propertiesConsoleReference, writeSecretConsoleReference}, nil + return references, nil +} + +// GenerateTerraformCapabilityProperties generates Capability properties for Terraform ComponentDefinition in a local website +func (ref *MarkdownReference) GenerateTerraformCapabilityProperties(capability types.Capability) (string, error) { + var references string + + tables, err := ref.parseTerraformCapabilityParameters(capability) + if err != nil { + return "", err + } + for _, t := range tables { + references += ref.prepareParameter(t.Name, t.Parameters, types.CUECategory) + } + return references, nil } diff --git a/references/plugins/suit_test.go b/references/plugins/suit_test.go index 91a24daee..ff9c06725 100644 --- a/references/plugins/suit_test.go +++ b/references/plugins/suit_test.go @@ -57,7 +57,7 @@ var td corev1beta1.TraitDefinition var wd, websvcWD corev1beta1.WorkloadDefinition var cd, websvcCD corev1beta1.ComponentDefinition -func TestAPIs(t *testing.T) { +func TestReferencePlugins(t *testing.T) { RegisterFailHandler(Fail) RunSpecsWithDefaultAndCustomReporters(t,