mirror of
https://github.com/kubevela/kubevela.git
synced 2026-08-18 12:06:38 +00:00
Generate a local site to host all reference docs
By Cli `vela reference` to generate reference docs for all workload types and traits, and host them in a local website. To fix #880
This commit is contained in:
+2
-224
@@ -2,236 +2,14 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"cuelang.org/go/cue"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
mycue "github.com/oam-dev/kubevela/pkg/cue"
|
||||
"github.com/oam-dev/kubevela/pkg/plugins"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
)
|
||||
|
||||
const (
|
||||
BaseRefPath = "docs/en/developers/references"
|
||||
ReferencePath = "hack/references"
|
||||
)
|
||||
|
||||
type ReferenceMarkdown struct {
|
||||
CapabilityName string `json:"capabilityName"`
|
||||
CapabilityType string `json:"capabilityType"`
|
||||
}
|
||||
type Parameter 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"`
|
||||
}
|
||||
|
||||
var refContent string
|
||||
var recurseDepth *int
|
||||
|
||||
func main() {
|
||||
var capabilityType string
|
||||
var specificationType string
|
||||
caps, err := plugins.LoadAllInstalledCapability()
|
||||
if err != nil {
|
||||
fmt.Printf("failed to generate reference docs for all capabilities: %s", err)
|
||||
if err := plugins.GenerateReferenceDocs(); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
for _, c := range caps {
|
||||
switch c.Type {
|
||||
case "workload":
|
||||
capabilityType = "workload-types"
|
||||
specificationType = "workload type"
|
||||
case "trait":
|
||||
capabilityType = "traits"
|
||||
specificationType = "trait"
|
||||
default:
|
||||
fmt.Printf("the type of the capability is not right")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fileName := fmt.Sprintf("%s.md", c.Name)
|
||||
filePath := filepath.Join(BaseRefPath, capabilityType, fileName)
|
||||
f, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE, 0644)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to open file %s: %s", filePath, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if err = os.Truncate(filePath, 0); err != nil {
|
||||
fmt.Printf("failed to truncate file %s: %s", filePath, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
capName := c.Name
|
||||
ref := ReferenceMarkdown{
|
||||
CapabilityName: capName,
|
||||
CapabilityType: capabilityType,
|
||||
}
|
||||
|
||||
cueValue, err := common.GetCUEParameterValue(c.CueTemplate)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to retrieve `parameters` value from %s with err: %s", c.Name, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
refContent = ""
|
||||
var defaultDepth = 0
|
||||
recurseDepth = &defaultDepth
|
||||
capNameInTitle := strings.Title(capName)
|
||||
if err := ref.parseParameters(cueValue, "Properties", defaultDepth); err != nil {
|
||||
fmt.Printf(err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
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, err := generateSpecification(capName)
|
||||
if err != nil {
|
||||
fmt.Printf(err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
specification := fmt.Sprintf("\n\n## Specification\n\n%s\n\n%s", specificationIntro, specificationContent)
|
||||
|
||||
conflictWithAndMoreSection, err := generateConflictWithAndMore(capName)
|
||||
if err != nil {
|
||||
fmt.Printf(err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
refContent = title + description + specification + refContent + conflictWithAndMoreSection
|
||||
f.WriteString(refContent)
|
||||
f.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// prepareParameterTable prepares the table content for each property
|
||||
func (ref *ReferenceMarkdown) prepareParameterTable(tableName string, parameterList []Parameter) string {
|
||||
refContent := fmt.Sprintf("\n\n%s\n\n", tableName)
|
||||
refContent += "Name | Description | Type | Required | Default \n"
|
||||
refContent += "------------ | ------------- | ------------- | ------------- | ------------- \n"
|
||||
for _, p := range parameterList {
|
||||
//defaultValue := p.Default
|
||||
//if defaultValue == nil {
|
||||
// defaultValue = ""
|
||||
//}
|
||||
printableDefaultValue := getPrintableDefaultValue(p.Default)
|
||||
refContent += fmt.Sprintf(" %s | %s | %s | %t | %s \n", p.Name, p.Usage, p.PrintableType, p.Required, printableDefaultValue)
|
||||
}
|
||||
return refContent
|
||||
}
|
||||
|
||||
// parseParameters parses every parameter
|
||||
func (ref *ReferenceMarkdown) parseParameters(paraValue cue.Value, paramKey string, depth int) error {
|
||||
var params []Parameter
|
||||
*recurseDepth++
|
||||
switch paraValue.Kind() {
|
||||
case cue.StructKind:
|
||||
arguments, err := paraValue.Struct()
|
||||
if err != nil {
|
||||
return fmt.Errorf("arguments not defined as struct %w", err)
|
||||
}
|
||||
for i := 0; i < arguments.Len(); i++ {
|
||||
var param Parameter
|
||||
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 = mycue.GetDefault(def)
|
||||
}
|
||||
param.Short, param.Usage, param.Alias = mycue.RetrieveComments(val)
|
||||
param.Type = val.IncompleteKind()
|
||||
switch val.IncompleteKind() {
|
||||
case cue.StructKind:
|
||||
depth := *recurseDepth
|
||||
// TODO(zzxwill) this case not processed `selector?: [string]: string`
|
||||
if name == "selector" {
|
||||
param.PrintableType = "map[string]string"
|
||||
} else {
|
||||
if err := ref.parseParameters(val, name, depth); err != nil {
|
||||
return err
|
||||
}
|
||||
param.PrintableType = fmt.Sprintf("[%s](#%s)", name, name)
|
||||
}
|
||||
case cue.ListKind:
|
||||
elem, success := val.Elem()
|
||||
if !success {
|
||||
return fmt.Errorf("failed to get elements from %s", val)
|
||||
}
|
||||
switch elem.Kind() {
|
||||
case cue.StructKind:
|
||||
param.PrintableType = fmt.Sprintf("[[]%s](#%s)", name, name)
|
||||
depth := *recurseDepth
|
||||
if err := ref.parseParameters(elem, name, depth); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
param.Type = elem.Kind()
|
||||
param.PrintableType = fmt.Sprintf("[]%s", elem.IncompleteKind().String())
|
||||
}
|
||||
default:
|
||||
param.PrintableType = param.Type.String()
|
||||
}
|
||||
params = append(params, param)
|
||||
}
|
||||
}
|
||||
|
||||
tableName := fmt.Sprintf("%s %s", strings.Repeat("#", depth+2), paramKey)
|
||||
refContent = ref.prepareParameterTable(tableName, params) + refContent
|
||||
return nil
|
||||
}
|
||||
|
||||
// getPrintableDefaultValue converts the value in `interface{}` type to be printable
|
||||
func getPrintableDefaultValue(v interface{}) string {
|
||||
if v == nil {
|
||||
return ""
|
||||
}
|
||||
switch v.(type) {
|
||||
case int64:
|
||||
return strconv.FormatInt(v.(int64), 10)
|
||||
case string:
|
||||
if v == "" {
|
||||
return "empty"
|
||||
}
|
||||
return v.(string)
|
||||
case bool:
|
||||
return strconv.FormatBool(v.(bool))
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// generateSpecification generates Specification part for reference docs
|
||||
func generateSpecification(capability string) (string, error) {
|
||||
configurationPath, err := filepath.Abs(filepath.Join(ReferencePath, "configurations", fmt.Sprintf("%s.yaml", capability)))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get configuration path: %w", err)
|
||||
}
|
||||
|
||||
spec, err := ioutil.ReadFile(configurationPath)
|
||||
// skip if Configuration usage of a capability doesn't exist.
|
||||
if err != nil {
|
||||
spec = nil
|
||||
}
|
||||
return fmt.Sprintf("```yaml\n%s```", string(spec)), nil
|
||||
}
|
||||
|
||||
// generateConflictWithAndMore generates Section `Conflicts With` and more like `How xxx works` in reference docs
|
||||
func generateConflictWithAndMore(capabilityName string) (string, error) {
|
||||
conflictWithFile, err := filepath.Abs(filepath.Join(ReferencePath, "conflictsWithAndMore", fmt.Sprintf("%s.md", capabilityName)))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to locate conflictWith file: %w", err)
|
||||
}
|
||||
data, err := ioutil.ReadFile(conflictWithFile)
|
||||
if err != nil {
|
||||
return "", nil
|
||||
}
|
||||
return "\n" + string(data), nil
|
||||
}
|
||||
|
||||
@@ -58,6 +58,7 @@ func NewCommand() *cobra.Command {
|
||||
NewInitCommand(commandArgs, ioStream),
|
||||
NewUpCommand(commandArgs, ioStream),
|
||||
NewExportCommand(commandArgs, ioStream),
|
||||
NewReferencesCommand(commandArgs, ioStream),
|
||||
|
||||
// Apps
|
||||
NewListCommand(commandArgs, ioStream),
|
||||
|
||||
@@ -0,0 +1,271 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
cmdutil "github.com/oam-dev/kubevela/pkg/commands/util"
|
||||
"github.com/oam-dev/kubevela/pkg/plugins"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/system"
|
||||
)
|
||||
|
||||
const (
|
||||
// SideBar file name for docsify
|
||||
SideBar = "_sidebar.md"
|
||||
// NavBar file name for docsify
|
||||
NavBar = "_navbar.md"
|
||||
// IndexHTML file name for docsify
|
||||
IndexHTML = "index.html"
|
||||
// README file name for docsify
|
||||
README = "README.md"
|
||||
)
|
||||
|
||||
// Port is the port for reference docs website
|
||||
const Port = ":18081"
|
||||
|
||||
// NewReferencesCommand shows the website which hosts the reference docs for workload types and trait types
|
||||
func NewReferencesCommand(c types.Args, ioStreams cmdutil.IOStreams) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "reference",
|
||||
Aliases: []string{"ref", "ref-docs"},
|
||||
Short: "Opens up the reference docs website for workload types and trait types",
|
||||
Long: "Opens up the reference docs website for workload types and trait types",
|
||||
Example: `vela reference`,
|
||||
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
return c.SetConfig()
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
return startReferenceDocsSite(ctx, c, ioStreams)
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
types.TagCommandType: types.TypeStart,
|
||||
},
|
||||
}
|
||||
cmd.Flags().StringP("svc", "s", "", "service name")
|
||||
cmd.SetOut(ioStreams.Out)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func startReferenceDocsSite(ctx context.Context, c types.Args, ioStreams cmdutil.IOStreams) error {
|
||||
home, err := system.GetVelaHomeDir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
referenceHome := filepath.Join(home, "reference")
|
||||
|
||||
definitionPath := filepath.Join(referenceHome, "capabilities")
|
||||
if _, err := os.Stat(definitionPath); err != nil && os.IsNotExist(err) {
|
||||
if err := os.MkdirAll(definitionPath, 0750); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
docsPath := filepath.Join(referenceHome, "docs")
|
||||
if _, err := os.Stat(docsPath); err != nil && os.IsNotExist(err) {
|
||||
if err := os.MkdirAll(docsPath, 0750); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
capabilities, _, err := plugins.SyncDefinitionToLocal(ctx, c, definitionPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := plugins.CreateMarkdown(capabilities, docsPath, plugins.ReferenceSourcePath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := generateSideBar(capabilities, docsPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := generateNavBar(docsPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := generateIndexHTML(docsPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := generateREADME(capabilities, docsPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
server := &http.Server{
|
||||
Addr: Port,
|
||||
Handler: http.FileServer(http.Dir(docsPath)),
|
||||
ReadTimeout: 5 * time.Second,
|
||||
WriteTimeout: 10 * time.Second,
|
||||
}
|
||||
server.SetKeepAlivesEnabled(true)
|
||||
errCh := make(chan error, 1)
|
||||
|
||||
launch(server, errCh)
|
||||
|
||||
select {
|
||||
case err = <-errCh:
|
||||
return err
|
||||
case <-time.After(time.Second):
|
||||
var url = "http://127.0.0.1" + Port
|
||||
if err := OpenBrowser(url); err != nil {
|
||||
ioStreams.Infof("automatically invoking browser failed: %v\nPlease visit %s for reference docs", err, url)
|
||||
}
|
||||
}
|
||||
|
||||
// handle signal: SIGTERM(15)
|
||||
sc := make(chan os.Signal, 1)
|
||||
signal.Notify(sc, syscall.SIGTERM)
|
||||
|
||||
<-sc
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||
defer cancel()
|
||||
return server.Shutdown(ctx)
|
||||
}
|
||||
|
||||
func launch(server *http.Server, errChan chan<- error) {
|
||||
go func() {
|
||||
// http.Handle("/", http.FileServer(http.Dir(docsPath)))
|
||||
err := server.ListenAndServe()
|
||||
if err != nil && errors.Is(err, http.ErrServerClosed) {
|
||||
errChan <- err
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func generateSideBar(capabilities []types.Capability, docsPath string) error {
|
||||
sideBar := filepath.Join(docsPath, SideBar)
|
||||
workloads, traits := getWorkloadsAndTraits(capabilities)
|
||||
f, err := os.Create(sideBar)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := f.WriteString("- Workload Types\n"); err != nil {
|
||||
return nil
|
||||
}
|
||||
workloadFolderName := "workload-types"
|
||||
for _, w := range workloads {
|
||||
if _, err := f.WriteString(fmt.Sprintf(" - [%s](%s/%s.md)\n", w, workloadFolderName, w)); err != nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if _, err := f.WriteString("- Traits\n"); err != nil {
|
||||
return nil
|
||||
}
|
||||
traitFolderName := "traits"
|
||||
for _, t := range traits {
|
||||
if _, err := f.WriteString(fmt.Sprintf(" - [%s](%s/%s.md)\n", t, traitFolderName, t)); err != nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func generateNavBar(docsPath string) error {
|
||||
sideBar := filepath.Join(docsPath, NavBar)
|
||||
_, err := os.Create(sideBar)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func generateIndexHTML(docsPath string) error {
|
||||
indexHTML := `
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>KubeVela Reference Docs</title>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
||||
<meta name="description" content="Description">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0">
|
||||
<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/docsify@4/lib/themes/vue.css">
|
||||
<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/docsify-sidebar-collapse/dist/sidebar.min.css" />
|
||||
<link rel="stylesheet" href="./resources/css/custom.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script>
|
||||
window.$docsify = {
|
||||
name: 'KubeVela Reference Docs',
|
||||
loadSidebar: true,
|
||||
loadNavbar: true,
|
||||
subMaxLevel: 1,
|
||||
alias: {
|
||||
'/_sidebar.md': '/_sidebar.md',
|
||||
'/_navbar.md': '/_navbar.md'
|
||||
},
|
||||
formatUpdated: '{MM}/{DD}/{YYYY} {HH}:{mm}:{ss}',
|
||||
}
|
||||
</script>
|
||||
<!-- Docsify v4 -->
|
||||
<script src="//cdn.jsdelivr.net/npm/docsify@4"></script>
|
||||
<script src="//cdn.jsdelivr.net/npm/docsify/lib/docsify.min.js"></script>
|
||||
<!-- plugins -->
|
||||
<script src="//cdn.jsdelivr.net/npm/docsify-sidebar-collapse/dist/docsify-sidebar-collapse.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
`
|
||||
return ioutil.WriteFile(filepath.Join(docsPath, IndexHTML), []byte(indexHTML), 0600)
|
||||
}
|
||||
|
||||
func generateREADME(capabilities []types.Capability, docsPath string) error {
|
||||
readmeMD := filepath.Join(docsPath, README)
|
||||
f, err := os.Create(readmeMD)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := f.WriteString("# KubeVela Reference Docs for Workload Types and Traits\n" +
|
||||
"Click the navigation bar on the left or the links below to look into the detailed referennce of a Workload type or a Trait.\n"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
workloads, traits := getWorkloadsAndTraits(capabilities)
|
||||
|
||||
if _, err := f.WriteString("## Workload Types\n"); err != nil {
|
||||
return err
|
||||
}
|
||||
workloadFolderName := "workload-types"
|
||||
for _, w := range workloads {
|
||||
if _, err := f.WriteString(fmt.Sprintf(" - [%s](%s/%s.md)\n", w, workloadFolderName, w)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if _, err := f.WriteString("## Traits\n"); err != nil {
|
||||
return err
|
||||
}
|
||||
traitFolderName := "traits"
|
||||
for _, t := range traits {
|
||||
if _, err := f.WriteString(fmt.Sprintf(" - [%s](%s/%s.md)\n", t, traitFolderName, t)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getWorkloadsAndTraits(capabilities []types.Capability) ([]string, []string) {
|
||||
var workloads, traits []string
|
||||
for _, c := range capabilities {
|
||||
switch c.Type {
|
||||
case types.TypeWorkload:
|
||||
workloads = append(workloads, c.Name)
|
||||
case types.TypeTrait:
|
||||
traits = append(traits, c.Name)
|
||||
case types.TypeScope:
|
||||
|
||||
}
|
||||
}
|
||||
return workloads, traits
|
||||
}
|
||||
+3
-22
@@ -49,33 +49,14 @@ func RefreshDefinitions(ctx context.Context, c types.Args, ioStreams cmdutil.IOS
|
||||
return nil
|
||||
}
|
||||
|
||||
var syncedTemplates []types.Capability
|
||||
|
||||
templates, templateErrors, err := plugins.GetWorkloadsFromCluster(ctx, types.DefaultKubeVelaNS, c, dir, nil)
|
||||
syncedTemplates, warnings, err := plugins.SyncDefinitionToLocal(ctx, c, dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(templateErrors) > 0 {
|
||||
for _, e := range templateErrors {
|
||||
ioStreams.Infof("WARN: %v, you will unable to use this workload capability\n", e)
|
||||
}
|
||||
for _, w := range warnings {
|
||||
ioStreams.Infof(w)
|
||||
}
|
||||
syncedTemplates = append(syncedTemplates, templates...)
|
||||
plugins.SinkTemp2Local(templates, dir)
|
||||
|
||||
templates, templateErrors, err = plugins.GetTraitsFromCluster(ctx, types.DefaultKubeVelaNS, c, dir, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(templateErrors) > 0 {
|
||||
for _, e := range templateErrors {
|
||||
ioStreams.Infof("WARN: %v, you will unable to use this trait capability\n", e)
|
||||
}
|
||||
}
|
||||
syncedTemplates = append(syncedTemplates, templates...)
|
||||
plugins.SinkTemp2Local(templates, dir)
|
||||
plugins.RemoveLegacyTemps(syncedTemplates, dir)
|
||||
|
||||
printRefreshReport(syncedTemplates, oldCaps, ioStreams, silentOutput, false)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -198,3 +198,34 @@ func HandleTemplate(in *runtime.RawExtension, name, syncDir string) (types.Capab
|
||||
}
|
||||
return tmp, nil
|
||||
}
|
||||
|
||||
// SyncDefinitionToLocal sync definitions to local
|
||||
func SyncDefinitionToLocal(ctx context.Context, c types.Args, localDefinitionDir string) ([]types.Capability, []string, error) {
|
||||
var syncedTemplates []types.Capability
|
||||
var warnings []string
|
||||
|
||||
templates, templateErrors, err := GetWorkloadsFromCluster(ctx, types.DefaultKubeVelaNS, c, localDefinitionDir, 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 workload capability\n", e))
|
||||
}
|
||||
}
|
||||
syncedTemplates = append(syncedTemplates, templates...)
|
||||
SinkTemp2Local(templates, localDefinitionDir)
|
||||
|
||||
templates, templateErrors, err = GetTraitsFromCluster(ctx, types.DefaultKubeVelaNS, c, localDefinitionDir, 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
|
||||
}
|
||||
|
||||
@@ -0,0 +1,262 @@
|
||||
package plugins
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"cuelang.org/go/cue"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
mycue "github.com/oam-dev/kubevela/pkg/cue"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
)
|
||||
|
||||
const (
|
||||
// BaseRefPath is the target path for reference docs
|
||||
BaseRefPath = "docs/en/developers/references"
|
||||
// ReferenceSourcePath is the location for source reference
|
||||
ReferenceSourcePath = "hack/references"
|
||||
)
|
||||
|
||||
// Int64Type is int64 type
|
||||
type Int64Type int64
|
||||
|
||||
// StringType is string type
|
||||
type StringType string
|
||||
|
||||
// BoolType is bool type
|
||||
type BoolType bool
|
||||
|
||||
// ReferenceMarkdown is the struct for capability information
|
||||
type ReferenceMarkdown struct {
|
||||
// CapabilityName is the name of a capability
|
||||
CapabilityName string `json:"capabilityName"`
|
||||
// CapabilityType is the type of a capability
|
||||
CapabilityType string `json:"capabilityType"`
|
||||
}
|
||||
|
||||
// Parameter is the parameter section of CUE template
|
||||
type Parameter 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"`
|
||||
}
|
||||
|
||||
var refContent string
|
||||
var recurseDepth *int
|
||||
|
||||
// GenerateReferenceDocs generates reference docs
|
||||
func GenerateReferenceDocs() error {
|
||||
|
||||
caps, err := LoadAllInstalledCapability()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate reference docs for all capabilities: %s", err)
|
||||
}
|
||||
|
||||
return CreateMarkdown(caps, BaseRefPath, ReferenceSourcePath)
|
||||
}
|
||||
|
||||
// CreateMarkdown creates markdown based on capabilities
|
||||
func CreateMarkdown(caps []types.Capability, baseRefPath, referenceSourcePath string) error {
|
||||
var capabilityType string
|
||||
var specificationType string
|
||||
for _, c := range caps {
|
||||
switch c.Type {
|
||||
case types.TypeWorkload:
|
||||
capabilityType = "workload-types"
|
||||
specificationType = "workload type"
|
||||
case types.TypeTrait:
|
||||
capabilityType = "traits"
|
||||
specificationType = "trait"
|
||||
default:
|
||||
return fmt.Errorf("the type of the capability is not right")
|
||||
}
|
||||
|
||||
fileName := fmt.Sprintf("%s.md", c.Name)
|
||||
filePath := filepath.Join(baseRefPath, capabilityType)
|
||||
if _, err := os.Stat(filePath); err != nil && os.IsNotExist(err) {
|
||||
if err := os.MkdirAll(filePath, 0750); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
markdownFile := filepath.Join(baseRefPath, capabilityType, 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: %s", markdownFile, err)
|
||||
}
|
||||
if err = os.Truncate(markdownFile, 0); err != nil {
|
||||
return fmt.Errorf("failed to truncate file %s: %s", markdownFile, err)
|
||||
}
|
||||
capName := c.Name
|
||||
ref := ReferenceMarkdown{
|
||||
CapabilityName: capName,
|
||||
CapabilityType: capabilityType,
|
||||
}
|
||||
|
||||
cueValue, err := common.GetCUEParameterValue(c.CueTemplate)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to retrieve `parameters` value from %s with err: %s", c.Name, err)
|
||||
}
|
||||
refContent = ""
|
||||
var defaultDepth = 0
|
||||
recurseDepth = &defaultDepth
|
||||
capNameInTitle := strings.Title(capName)
|
||||
if err := ref.parseParameters(cueValue, "Properties", defaultDepth); err != nil {
|
||||
return err
|
||||
}
|
||||
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, err := generateSpecification(capName, referenceSourcePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
specification := fmt.Sprintf("\n\n## Specification\n\n%s\n\n%s", specificationIntro, specificationContent)
|
||||
|
||||
conflictWithAndMoreSection, err := generateConflictWithAndMore(capName, referenceSourcePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
refContent = title + description + specification + refContent + conflictWithAndMoreSection
|
||||
if _, err := f.WriteString(refContent); err != nil {
|
||||
return nil
|
||||
}
|
||||
if err := f.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// prepareParameterTable prepares the table content for each property
|
||||
func (ref *ReferenceMarkdown) prepareParameterTable(tableName string, parameterList []Parameter) string {
|
||||
refContent := fmt.Sprintf("\n\n%s\n\n", tableName)
|
||||
refContent += "Name | Description | Type | Required | Default \n"
|
||||
refContent += "------------ | ------------- | ------------- | ------------- | ------------- \n"
|
||||
for _, p := range parameterList {
|
||||
printableDefaultValue := getPrintableDefaultValue(p.Default)
|
||||
refContent += fmt.Sprintf(" %s | %s | %s | %t | %s \n", p.Name, p.Usage, p.PrintableType, p.Required, printableDefaultValue)
|
||||
}
|
||||
return refContent
|
||||
}
|
||||
|
||||
// parseParameters parses every parameter
|
||||
func (ref *ReferenceMarkdown) parseParameters(paraValue cue.Value, paramKey string, depth int) error {
|
||||
var params []Parameter
|
||||
*recurseDepth++
|
||||
switch paraValue.Kind() {
|
||||
case cue.StructKind:
|
||||
arguments, err := paraValue.Struct()
|
||||
if err != nil {
|
||||
return fmt.Errorf("arguments not defined as struct %w", err)
|
||||
}
|
||||
for i := 0; i < arguments.Len(); i++ {
|
||||
var param Parameter
|
||||
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 = mycue.GetDefault(def)
|
||||
}
|
||||
param.Short, param.Usage, param.Alias = mycue.RetrieveComments(val)
|
||||
param.Type = val.IncompleteKind()
|
||||
switch val.IncompleteKind() {
|
||||
case cue.StructKind:
|
||||
depth := *recurseDepth
|
||||
// TODO(zzxwill) this case not processed `selector?: [string]: string`
|
||||
if name == "selector" {
|
||||
param.PrintableType = "map[string]string"
|
||||
} else {
|
||||
if err := ref.parseParameters(val, name, depth); err != nil {
|
||||
return err
|
||||
}
|
||||
param.PrintableType = fmt.Sprintf("[%s](#%s)", name, name)
|
||||
}
|
||||
case cue.ListKind:
|
||||
elem, success := val.Elem()
|
||||
if !success {
|
||||
return fmt.Errorf("failed to get elements from %s", val)
|
||||
}
|
||||
switch elem.Kind() {
|
||||
case cue.StructKind:
|
||||
param.PrintableType = fmt.Sprintf("[[]%s](#%s)", name, name)
|
||||
depth := *recurseDepth
|
||||
if err := ref.parseParameters(elem, name, depth); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
param.Type = elem.Kind()
|
||||
param.PrintableType = fmt.Sprintf("[]%s", elem.IncompleteKind().String())
|
||||
}
|
||||
default:
|
||||
param.PrintableType = param.Type.String()
|
||||
}
|
||||
params = append(params, param)
|
||||
}
|
||||
default:
|
||||
//
|
||||
}
|
||||
|
||||
tableName := fmt.Sprintf("%s %s", strings.Repeat("#", depth+2), paramKey)
|
||||
refContent = ref.prepareParameterTable(tableName, params) + refContent
|
||||
return nil
|
||||
}
|
||||
|
||||
// getPrintableDefaultValue converts the value in `interface{}` type to be printable
|
||||
func getPrintableDefaultValue(v interface{}) string {
|
||||
if v == nil {
|
||||
return ""
|
||||
}
|
||||
switch v.(type) {
|
||||
case Int64Type:
|
||||
return strconv.FormatInt(v.(int64), 10)
|
||||
case StringType:
|
||||
if v == "" {
|
||||
return "empty"
|
||||
}
|
||||
return v.(string)
|
||||
case BoolType:
|
||||
return strconv.FormatBool(v.(bool))
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// generateSpecification generates Specification part for reference docs
|
||||
func generateSpecification(capability string, referenceSourcePath string) (string, error) {
|
||||
configurationPath, err := filepath.Abs(filepath.Join(referenceSourcePath, "configurations", fmt.Sprintf("%s.yaml", capability)))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get configuration path: %w", err)
|
||||
}
|
||||
|
||||
spec, err := ioutil.ReadFile(filepath.Clean(configurationPath))
|
||||
// skip if Configuration usage of a capability doesn't exist.
|
||||
if err != nil {
|
||||
spec = nil
|
||||
}
|
||||
return fmt.Sprintf("```yaml\n%s```", string(spec)), nil
|
||||
}
|
||||
|
||||
// generateConflictWithAndMore generates Section `Conflicts With` and more like `How xxx works` in reference docs
|
||||
func generateConflictWithAndMore(capabilityName string, referenceSourcePath string) (string, error) {
|
||||
conflictWithFile, err := filepath.Abs(filepath.Join(referenceSourcePath, "conflictsWithAndMore", fmt.Sprintf("%s.md", capabilityName)))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to locate conflictWith file: %w", err)
|
||||
}
|
||||
data, err := ioutil.ReadFile(filepath.Clean(conflictWithFile))
|
||||
if err != nil {
|
||||
return "", nil
|
||||
}
|
||||
return "\n" + string(data), nil
|
||||
}
|
||||
Reference in New Issue
Block a user