Files
kubevela/pkg/serverlib/definition.go
2021-01-08 15:46:30 +08:00

205 lines
5.5 KiB
Go

package serverlib
import (
"bufio"
"encoding/json"
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
"github.com/getkin/kin-openapi/openapi3"
mycue "github.com/oam-dev/kubevela/pkg/cue"
"github.com/oam-dev/kubevela/pkg/utils/common"
"github.com/oam-dev/kubevela/pkg/utils/system"
)
const (
// OpenAPISchemaDir is the folder name under ~/.vela/capabilities
OpenAPISchemaDir = "openapi"
// UsageTag is usage comment annotation
UsageTag = "+usage="
// ShortTag is the short alias annotation
ShortTag = "+short"
)
// OpenAPISchema is the struct for OpenAPI Schema generated by Cue OpenAPI
type OpenAPISchema struct {
OpenAPI string `json:"openapi"`
Components Components `json:"components"`
}
// Components is the struct filed of OpenAPISchema
type Components struct {
Schemas Schemas `json:"schemas"`
}
// Schemas is the struct filed of Components
type Schemas struct {
Parameter map[string]interface{} `json:"parameter"`
}
// GetDefinition is the main function for GetDefinition API
func GetDefinition(name string) ([]byte, error) {
openAPISchema, err := generateOpenAPISchemaFromCapabilityParameter(name)
if err != nil {
return nil, err
}
swagger, err := openapi3.NewSwaggerLoader().LoadSwaggerFromData(openAPISchema)
if err != nil {
return nil, err
}
schemaRef := swagger.Components.Schemas["parameter"]
schema := schemaRef.Value
fixOpenAPISchema("", schema)
parameter, err := schema.MarshalJSON()
if err != nil {
return nil, err
}
return parameter, nil
}
// generateOpenAPISchemaFromCapabilityParameter returns the parameter of a definition in cue.Value format
func generateOpenAPISchemaFromCapabilityParameter(name string) ([]byte, error) {
dir, err := system.GetCapabilityDir()
if err != nil {
return nil, err
}
definitionCueName := fmt.Sprintf("%s.cue", name)
schemaDir := filepath.Join(dir, OpenAPISchemaDir)
if err = prepareParameterCue(dir, definitionCueName, schemaDir); err != nil {
return nil, err
}
if err = appendCueReference(filepath.Join(dir, OpenAPISchemaDir, definitionCueName)); err != nil {
return nil, err
}
filename := filepath.FromSlash(definitionCueName)
return common.GenOpenAPIFromFile(filepath.Join(dir, OpenAPISchemaDir), filename)
}
// prepareParameterCue cuts `parameter` section form definition .cue file
func prepareParameterCue(fileDir, fileName string, targetSchemaDir string) error {
if _, err := os.Stat(targetSchemaDir); err != nil && os.IsNotExist(err) {
if err := os.Mkdir(targetSchemaDir, 0750); err != nil {
return err
}
}
cueFile := filepath.Join(fileDir, fileName)
f, err := os.Open(filepath.Clean(cueFile))
if err != nil {
return err
}
//nolint
defer f.Close()
schemaFile := filepath.Join(targetSchemaDir, fileName)
_, err = os.Stat(schemaFile)
if err == nil {
if err = os.Truncate(schemaFile, 0); err != nil {
return err
}
}
targetFile, err := os.OpenFile(filepath.Clean(schemaFile), os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
if err != nil {
return err
}
//nolint
defer targetFile.Close()
scanner := bufio.NewScanner(f)
var withParameterFlag bool
r := regexp.MustCompile("[[:space:]]*parameter:[[:space:]]*{.*")
for scanner.Scan() {
text := scanner.Text()
if r.MatchString(text) {
// a variable has to be refined as a definition which starts with "#"
text = fmt.Sprintf("parameter: #parameter\n#%s", text)
withParameterFlag = true
}
if _, err := targetFile.WriteString(fmt.Sprintf("%s\n", text)); err != nil {
return err
}
}
if !withParameterFlag {
return fmt.Errorf("cue file %s doesn't contain section `parmeter`", cueFile)
}
return nil
}
// appendCueReference appends `context` filed to parameter .cue file
func appendCueReference(cueFile string) error {
f, err := os.OpenFile(filepath.Clean(cueFile), os.O_APPEND|os.O_WRONLY, 0600)
if err != nil {
return err
}
//nolint
defer f.Close()
if _, err := f.WriteString(mycue.BaseTemplate); err != nil {
return err
}
return nil
}
// fixOpenAPISchema fixes tainted `description` filed, missing of title `field`.
func fixOpenAPISchema(name string, schema *openapi3.Schema) {
t := schema.Type
switch t {
case "object":
for k, v := range schema.Properties {
s := v.Value
fixOpenAPISchema(k, s)
}
case "array":
fixOpenAPISchema("", schema.Items.Value)
}
if name != "" {
schema.Title = name
}
description := schema.Description
if strings.Contains(description, UsageTag) {
description = strings.Split(description, UsageTag)[1]
}
if strings.Contains(description, ShortTag) {
description = strings.Split(description, ShortTag)[0]
description = strings.TrimSpace(description)
}
schema.Description = description
}
// getParameterItemName gets the name of a parameter item
func getParameterItemName(previousLine string) (string, error) {
// parse property name, like from `"cmd": {\n`
tempPropertyName := strings.Split(previousLine, "\"")
if len(tempPropertyName) <= 1 {
return "", fmt.Errorf("could not get property name from: %s", previousLine)
}
propertyName := strings.Split(tempPropertyName[1], "\"")[0]
return propertyName, nil
}
// getParameterFromOpenAPISchema retrieves needs section from OpenAPI schema for front-end requirements
func getParameterFromOpenAPISchema(openAPISchema []byte) ([]byte, error) {
var schema OpenAPISchema
var parameterJSON []byte
var err error
if err = json.Unmarshal(openAPISchema, &schema); err != nil {
return nil, err
}
parameter := schema.Components.Schemas.Parameter
if parameterJSON, err = json.Marshal(parameter); err != nil {
return nil, err
}
return parameterJSON, nil
}