mirror of
https://github.com/kubevela/kubevela.git
synced 2026-08-18 20:17:04 +00:00
[Backport release-1.8] Feat: add sub-module to Golang SDK (#5731)
* wait to deal with go.mod Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com> (cherry picked from commit91f9e49d21) * seperate def and module modifier Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com> (cherry picked from commit8f4ef2f62a) * fix module import Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com> (cherry picked from commitfa02a0f8cd) * refine code Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com> (cherry picked from commit3a56d8c829) * remove the pointer reference in loop generalize the language-specific argument parsing amend tests Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com> (cherry picked from commit38b593d6f9) * remove focused test Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com> (cherry picked from commit8aa74df69f) * fix test Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com> (cherry picked from commit6d40d257e1) * update command usage Signed-off-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com> (cherry picked from commit78bb040039) --------- Co-authored-by: Qiaozp <qiaozhongpei.qzp@alibaba-inc.com>
This commit is contained in:
co-authored by
Qiaozp
parent
3975fbcda6
commit
75f8209a4c
@@ -42,10 +42,10 @@ jobs:
|
||||
run: make vela-cli
|
||||
|
||||
- name: Build SDK
|
||||
run: bin/vela def gen-api -f vela-templates/definitions/internal/ -o ./kubevela-go-sdk --package=github.com/kubevela-contrib/kubevela-go-sdk
|
||||
run: bin/vela def gen-api -f vela-templates/definitions/internal/ -o ./kubevela-go-sdk --package=github.com/kubevela-contrib/kubevela-go-sdk --init
|
||||
|
||||
- name: Validate SDK
|
||||
run: |
|
||||
cd kubevela-go-sdk
|
||||
go mod tidy
|
||||
golangci-lint run --timeout 5m ./...
|
||||
golangci-lint run --timeout 5m -e "exported:" -e "dot-imports" ./...
|
||||
|
||||
@@ -96,10 +96,10 @@ This command requires `docker` in PATH as we'll run openapi-generator in docker.
|
||||
|
||||
```shell
|
||||
# Initialize the SDK, generate API from all definitions,
|
||||
vela def gen-api --init -f /path/to/def/dir -o /path/to/sdk --lang go
|
||||
vela def gen-api --init -f /path/to/def/dir -o /path/to/sdk --language go
|
||||
|
||||
# Incrementally generate API from definitions
|
||||
vela def gen-api -f /path/to/def/dir -o /path/to/sdk --lang go
|
||||
vela def gen-api -f /path/to/def/dir -o /path/to/sdk --language go
|
||||
```
|
||||
|
||||
## Future work
|
||||
|
||||
+9
-4
@@ -33,7 +33,7 @@ config() {
|
||||
git config --global user.name "kubevela-bot"
|
||||
}
|
||||
|
||||
cloneAndClearRepo() {
|
||||
cloneAndClearCoreAPI() {
|
||||
echo "git clone"
|
||||
|
||||
if [[ -n "$SSH_DEPLOY_KEY" ]]; then
|
||||
@@ -42,8 +42,13 @@ cloneAndClearRepo() {
|
||||
git clone --single-branch --depth 1 https://github.com/$VELA_GO_SDK.git kubevela-go-sdk
|
||||
fi
|
||||
|
||||
echo "Clear kubevela-go-sdk pkg/*"
|
||||
rm -r kubevela-go-sdk/pkg/*
|
||||
echo "Clear kubevela-go-sdk pkg/apis/common, pkg/apis/component, pkg/apis/policy, pkg/apis/trait, pkg/apis/workflow-step, pkg/apis/utils, pkg/apis/types.go "
|
||||
rm -rf kubevela-go-sdk/pkg/apis/common
|
||||
rm -rf kubevela-go-sdk/pkg/apis/component
|
||||
rm -rf kubevela-go-sdk/pkg/apis/policy
|
||||
rm -rf kubevela-go-sdk/pkg/apis/trait
|
||||
rm -rf kubevela-go-sdk/pkg/apis/workflow-step
|
||||
rm -rf kubevela-go-sdk/pkg/apis/utils
|
||||
}
|
||||
|
||||
updateRepo() {
|
||||
@@ -79,7 +84,7 @@ syncRepo() {
|
||||
|
||||
main() {
|
||||
config
|
||||
cloneAndClearRepo
|
||||
cloneAndClearCoreAPI
|
||||
updateRepo
|
||||
syncRepo
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ go 1.19
|
||||
|
||||
require (
|
||||
github.com/oam-dev/kubevela-core-api v1.5.8
|
||||
|
||||
// for main module
|
||||
github.com/pkg/errors v0.9.1
|
||||
k8s.io/apimachinery v0.23.6
|
||||
k8s.io/client-go v0.23.6
|
||||
@@ -11,6 +13,9 @@ require (
|
||||
sigs.k8s.io/yaml v1.3.0
|
||||
)
|
||||
|
||||
// for sub-module
|
||||
// require github.com/kubevela/vela-go-sdk v0.0.0-20230309022604-cd431bb25a9a
|
||||
|
||||
require (
|
||||
cuelang.org/go v0.5.0-alpha.1 // indirect
|
||||
github.com/NYTimes/gziphandler v1.1.1 // indirect
|
||||
|
||||
@@ -285,14 +285,14 @@ func (a *ApplicationBuilder) Validate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func FromK8sObject(app *v1beta1.Application) (TypedApplication, error) {
|
||||
func FromK8sObject(app v1beta1.Application) (TypedApplication, error) {
|
||||
a := &ApplicationBuilder{}
|
||||
a.Name(app.Name)
|
||||
a.Namespace(app.Namespace)
|
||||
a.resourceVersion = app.ResourceVersion
|
||||
|
||||
for _, comp := range app.Spec.Components {
|
||||
c, err := FromComponent(&comp)
|
||||
c, err := FromComponent(comp)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "convert component from k8s object")
|
||||
}
|
||||
@@ -300,7 +300,7 @@ func FromK8sObject(app *v1beta1.Application) (TypedApplication, error) {
|
||||
}
|
||||
if app.Spec.Workflow != nil {
|
||||
for _, step := range app.Spec.Workflow.Steps {
|
||||
s, err := FromWorkflowStep(&step)
|
||||
s, err := FromWorkflowStep(step)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "convert workflow step from k8s object")
|
||||
}
|
||||
@@ -308,7 +308,7 @@ func FromK8sObject(app *v1beta1.Application) (TypedApplication, error) {
|
||||
}
|
||||
}
|
||||
for _, policy := range app.Spec.Policies {
|
||||
p, err := FromPolicy(&policy)
|
||||
p, err := FromPolicy(policy)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "convert policy from k8s object")
|
||||
}
|
||||
@@ -317,34 +317,34 @@ func FromK8sObject(app *v1beta1.Application) (TypedApplication, error) {
|
||||
return a, nil
|
||||
}
|
||||
|
||||
func FromComponent(component *common.ApplicationComponent) (Component, error) {
|
||||
func FromComponent(component common.ApplicationComponent) (Component, error) {
|
||||
build, ok := ComponentsBuilders[component.Type]
|
||||
if !ok {
|
||||
return nil, errors.Errorf("no component type %s registered", component.Type)
|
||||
}
|
||||
return build(*component)
|
||||
return build(component)
|
||||
}
|
||||
|
||||
func FromWorkflowStep(step *v1beta1.WorkflowStep) (WorkflowStep, error) {
|
||||
func FromWorkflowStep(step v1beta1.WorkflowStep) (WorkflowStep, error) {
|
||||
build, ok := WorkflowStepsBuilders[step.Type]
|
||||
if !ok {
|
||||
return nil, errors.Errorf("no workflow step type %s registered", step.Type)
|
||||
}
|
||||
return build(*step)
|
||||
return build(step)
|
||||
}
|
||||
|
||||
func FromPolicy(policy *v1beta1.AppPolicy) (Policy, error) {
|
||||
func FromPolicy(policy v1beta1.AppPolicy) (Policy, error) {
|
||||
build, ok := PoliciesBuilders[policy.Type]
|
||||
if !ok {
|
||||
return nil, errors.Errorf("no policy type %s registered", policy.Type)
|
||||
}
|
||||
return build(*policy)
|
||||
return build(policy)
|
||||
}
|
||||
|
||||
func FromTrait(trait *common.ApplicationTrait) (Trait, error) {
|
||||
func FromTrait(trait common.ApplicationTrait) (Trait, error) {
|
||||
build, ok := TraitBuilders[trait.Type]
|
||||
if !ok {
|
||||
return nil, errors.Errorf("no trait type %s registered", trait.Type)
|
||||
}
|
||||
return build(*trait)
|
||||
return build(trait)
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ func (c clientImpl) Get(ctx context.Context, key client.ObjectKey) (apis.TypedAp
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return sdkcommon.FromK8sObject(&_app)
|
||||
return sdkcommon.FromK8sObject(_app)
|
||||
}
|
||||
|
||||
func (c clientImpl) List(ctx context.Context, opts client.ListOption) ([]apis.TypedApplication, error) {
|
||||
@@ -108,7 +108,7 @@ func (c clientImpl) List(ctx context.Context, opts client.ListOption) ([]apis.Ty
|
||||
}
|
||||
var apps []apis.TypedApplication
|
||||
for _, app := range _appList.Items {
|
||||
_app, err := sdkcommon.FromK8sObject(&app)
|
||||
_app, err := sdkcommon.FromK8sObject(app)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -25,6 +25,8 @@ import (
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
|
||||
@@ -47,17 +49,31 @@ import (
|
||||
|
||||
type byteHandler func([]byte) []byte
|
||||
|
||||
var (
|
||||
defaultAPIDir = map[string]string{
|
||||
"go": "pkg/apis",
|
||||
}
|
||||
// LangArgsRegistry is used to store the argument info
|
||||
LangArgsRegistry = map[string]map[langArgKey]LangArg{}
|
||||
)
|
||||
|
||||
// GenMeta stores the metadata for generator.
|
||||
type GenMeta struct {
|
||||
config *rest.Config
|
||||
name string
|
||||
kind string
|
||||
|
||||
Output string
|
||||
Lang string
|
||||
Package string
|
||||
Template string
|
||||
File []string
|
||||
InitSDK bool
|
||||
Verbose bool
|
||||
Output string
|
||||
APIDirectory string
|
||||
IsSubModule bool
|
||||
Lang string
|
||||
Package string
|
||||
Template string
|
||||
File []string
|
||||
InitSDK bool
|
||||
Verbose bool
|
||||
|
||||
LangArgs LanguageArgs
|
||||
|
||||
cuePaths []string
|
||||
templatePath string
|
||||
@@ -67,11 +83,70 @@ type GenMeta struct {
|
||||
// Generator is used to generate SDK code from CUE template for one language.
|
||||
type Generator struct {
|
||||
meta *GenMeta
|
||||
name string
|
||||
kind string
|
||||
def definition.Definition
|
||||
openapiSchema []byte
|
||||
modifiers []Modifier
|
||||
// defModifiers are the modifiers for each definition.
|
||||
defModifiers []Modifier
|
||||
// moduleModifiers are the modifiers for the whole module. It will be executed after generating all definitions.
|
||||
moduleModifiers []Modifier
|
||||
}
|
||||
|
||||
// LanguageArgs is used to store the arguments for the language.
|
||||
type LanguageArgs interface {
|
||||
Get(key langArgKey) string
|
||||
Set(key langArgKey, value string)
|
||||
}
|
||||
|
||||
// langArgKey is language argument key.
|
||||
type langArgKey string
|
||||
|
||||
// LangArg is language-specific argument.
|
||||
type LangArg struct {
|
||||
Name langArgKey
|
||||
Desc string
|
||||
Default string
|
||||
}
|
||||
|
||||
// registerLangArg should be called in init() function of each language.
|
||||
func registerLangArg(lang string, arg ...LangArg) {
|
||||
if _, ok := LangArgsRegistry[lang]; !ok {
|
||||
LangArgsRegistry[lang] = map[langArgKey]LangArg{}
|
||||
}
|
||||
for _, a := range arg {
|
||||
LangArgsRegistry[lang][a.Name] = a
|
||||
}
|
||||
}
|
||||
|
||||
// NewLanguageArgs parses the language arguments and returns a LanguageArgs.
|
||||
func NewLanguageArgs(lang string, langArgs []string) (LanguageArgs, error) {
|
||||
availableArgs := LangArgsRegistry[lang]
|
||||
res := languageArgs{}
|
||||
for _, arg := range langArgs {
|
||||
parts := strings.Split(arg, "=")
|
||||
if len(parts) != 2 {
|
||||
return nil, errors.Errorf("argument %s is not in the format of key=value", arg)
|
||||
}
|
||||
if _, ok := availableArgs[langArgKey(parts[0])]; !ok {
|
||||
return nil, errors.Errorf("argument %s is not supported for language %s", parts[0], lang)
|
||||
}
|
||||
res.Set(langArgKey(parts[0]), parts[1])
|
||||
}
|
||||
for k, v := range availableArgs {
|
||||
if res.Get(k) == "" {
|
||||
res.Set(k, v.Default)
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
type languageArgs map[string]string
|
||||
|
||||
func (l languageArgs) Get(key langArgKey) string {
|
||||
return l[string(key)]
|
||||
}
|
||||
|
||||
func (l languageArgs) Set(key langArgKey, value string) {
|
||||
l[string(key)] = value
|
||||
}
|
||||
|
||||
// Modifier is used to modify the generated code.
|
||||
@@ -82,7 +157,7 @@ type Modifier interface {
|
||||
|
||||
// Init initializes the generator.
|
||||
// It will validate the param, analyze the CUE files, read them to memory, mkdir for output.
|
||||
func (meta *GenMeta) Init(c common.Args) (err error) {
|
||||
func (meta *GenMeta) Init(c common.Args, langArgs []string) (err error) {
|
||||
meta.config, err = c.GetConfig()
|
||||
if err != nil {
|
||||
klog.Info("No kubeconfig found, skipping")
|
||||
@@ -95,9 +170,18 @@ func (meta *GenMeta) Init(c common.Args) (err error) {
|
||||
return fmt.Errorf("language %s is not supported", meta.Lang)
|
||||
}
|
||||
|
||||
// Init arguments
|
||||
if meta.APIDirectory == "" {
|
||||
meta.APIDirectory = defaultAPIDir[meta.Lang]
|
||||
}
|
||||
|
||||
meta.LangArgs, err = NewLanguageArgs(meta.Lang, langArgs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
packageFuncs := map[string]byteHandler{
|
||||
"go": func(b []byte) []byte {
|
||||
return bytes.ReplaceAll(b, []byte("github.com/kubevela/vela-go-sdk"), []byte(meta.Package))
|
||||
return bytes.ReplaceAll(b, []byte(PackagePlaceHolder), []byte(meta.Package))
|
||||
},
|
||||
}
|
||||
|
||||
@@ -137,7 +221,7 @@ func (meta *GenMeta) CreateScaffold() error {
|
||||
if !meta.InitSDK {
|
||||
return nil
|
||||
}
|
||||
fmt.Println("Flag --init is set, creating scaffold...")
|
||||
klog.Info("Flag --init is set, creating scaffold...")
|
||||
langDirPrefix := fmt.Sprintf("%s/%s", ScaffoldDir, meta.Lang)
|
||||
err := fs.WalkDir(Scaffold, ScaffoldDir, func(_path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
@@ -155,7 +239,7 @@ func (meta *GenMeta) CreateScaffold() error {
|
||||
}
|
||||
fileContent = meta.packageFunc(fileContent)
|
||||
fileName := path.Join(meta.Output, strings.TrimPrefix(_path, langDirPrefix))
|
||||
// go.mod_ is a special file name, it will be renamed to go.mod. Go will exclude directory go.mod located from the build process.
|
||||
// go.mod_ is a special file name, it will be renamed to go.mod. Go will ignore directory containing go.mod during the build process.
|
||||
fileName = strings.ReplaceAll(fileName, "go.mod_", "go.mod")
|
||||
fileDir := path.Dir(fileName)
|
||||
if err = os.MkdirAll(fileDir, 0750); err != nil {
|
||||
@@ -236,18 +320,24 @@ func (meta *GenMeta) PrepareGeneratorAndTemplate() error {
|
||||
// 1. Generate OpenAPI schema from cue files
|
||||
// 2. Generate code from OpenAPI schema
|
||||
func (meta *GenMeta) Run() error {
|
||||
g := NewModifiableGenerator(meta)
|
||||
if len(meta.cuePaths) == 0 {
|
||||
return nil
|
||||
}
|
||||
APIGenerated := false
|
||||
for _, cuePath := range meta.cuePaths {
|
||||
klog.Infof("Generating SDK for %s", cuePath)
|
||||
g := NewModifiableGenerator(meta)
|
||||
klog.Infof("Generating API for %s", cuePath)
|
||||
// nolint:gosec
|
||||
cueBytes, err := os.ReadFile(cuePath)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to read %s", cuePath)
|
||||
}
|
||||
template, err := g.GetDefinitionValue(cueBytes)
|
||||
template, defName, defKind, err := g.GetDefinitionValue(cueBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
g.meta.SetDefinition(defName, defKind)
|
||||
|
||||
err = g.GenOpenAPISchema(template)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "unsupported node string (*ast.Ident)") {
|
||||
@@ -257,32 +347,51 @@ func (meta *GenMeta) Run() error {
|
||||
}
|
||||
return errors.Wrapf(err, "generate OpenAPI schema")
|
||||
}
|
||||
|
||||
err = g.GenerateCode()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
APIGenerated = true
|
||||
}
|
||||
if !APIGenerated {
|
||||
return nil
|
||||
}
|
||||
for _, m := range g.moduleModifiers {
|
||||
err := m.Modify()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetDefinitionValue returns a value.Value from cue bytes
|
||||
func (g *Generator) GetDefinitionValue(cueBytes []byte) (*value.Value, error) {
|
||||
// SetDefinition sets definition name and kind
|
||||
func (meta *GenMeta) SetDefinition(defName, defKind string) {
|
||||
meta.name = defName
|
||||
meta.kind = defKind
|
||||
}
|
||||
|
||||
// GetDefinitionValue returns a value.Value definition name, definition kind from cue bytes
|
||||
func (g *Generator) GetDefinitionValue(cueBytes []byte) (*value.Value, string, string, error) {
|
||||
g.def = definition.Definition{Unstructured: unstructured.Unstructured{}}
|
||||
if err := g.def.FromCUEString(string(cueBytes), g.meta.config); err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to parse CUE")
|
||||
return nil, "", "", errors.Wrapf(err, "failed to parse CUE")
|
||||
}
|
||||
g.name = g.def.GetName()
|
||||
g.kind = g.def.GetKind()
|
||||
|
||||
templateString, _, err := unstructured.NestedString(g.def.Object, definition.DefinitionTemplateKeys...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, "", "", err
|
||||
}
|
||||
if templateString == "" {
|
||||
return nil, "", "", errors.New("definition doesn't include cue schematic")
|
||||
}
|
||||
template, err := value.NewValue(templateString+velacue.BaseTemplate, nil, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, "", "", err
|
||||
}
|
||||
return template, nil
|
||||
return template, g.def.GetName(), g.def.GetKind(), nil
|
||||
}
|
||||
|
||||
// GenOpenAPISchema generates OpenAPI json schema from cue.Instance
|
||||
@@ -327,8 +436,8 @@ func (g *Generator) GenOpenAPISchema(val *value.Value) error {
|
||||
openapiSchema, err := doc.MarshalJSON()
|
||||
g.openapiSchema = openapiSchema
|
||||
if g.meta.Verbose {
|
||||
fmt.Println("OpenAPI schema:")
|
||||
fmt.Println(string(g.openapiSchema))
|
||||
klog.Info("OpenAPI schema:")
|
||||
klog.Info(string(g.openapiSchema))
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -337,13 +446,13 @@ func (g *Generator) completeOpenAPISchema(doc *openapi3.T) {
|
||||
for key, schema := range doc.Components.Schemas {
|
||||
switch key {
|
||||
case "parameter":
|
||||
spec := g.name + "-spec"
|
||||
spec := g.meta.name + "-spec"
|
||||
schema.Value.Title = spec
|
||||
completeFreeFormSchema(schema)
|
||||
completeSchema(key, schema)
|
||||
doc.Components.Schemas[spec] = schema
|
||||
delete(doc.Components.Schemas, key)
|
||||
case g.name + "-spec":
|
||||
case g.meta.name + "-spec":
|
||||
continue
|
||||
default:
|
||||
completeSchema(key, schema)
|
||||
@@ -353,7 +462,7 @@ func (g *Generator) completeOpenAPISchema(doc *openapi3.T) {
|
||||
|
||||
// GenerateCode will call openapi-generator to generate code and modify it
|
||||
func (g *Generator) GenerateCode() (err error) {
|
||||
tmpFile, err := os.CreateTemp("", g.name+"-*.json")
|
||||
tmpFile, err := os.CreateTemp("", g.meta.name+"-*.json")
|
||||
_, err = tmpFile.Write(g.openapiSchema)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "write openapi schema to temporary file")
|
||||
@@ -364,11 +473,11 @@ func (g *Generator) GenerateCode() (err error) {
|
||||
_ = os.Remove(tmpFile.Name())
|
||||
}
|
||||
}()
|
||||
apiDir, err := filepath.Abs(path.Join(g.meta.Output, "pkg", "apis"))
|
||||
apiDir, err := filepath.Abs(path.Join(g.meta.Output, g.meta.APIDirectory))
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "get absolute path of %s", apiDir)
|
||||
}
|
||||
err = os.MkdirAll(path.Join(apiDir, definition.DefinitionKindToType[g.kind]), 0750)
|
||||
err = os.MkdirAll(path.Join(apiDir, definition.DefinitionKindToType[g.meta.kind]), 0750)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "create directory %s", apiDir)
|
||||
}
|
||||
@@ -384,28 +493,28 @@ func (g *Generator) GenerateCode() (err error) {
|
||||
"generate",
|
||||
"-i", "/local/input/"+filepath.Base(tmpFile.Name()),
|
||||
"-g", g.meta.Lang,
|
||||
"-o", fmt.Sprintf("/local/output/%s/%s", definition.DefinitionKindToType[g.kind], g.name),
|
||||
"-o", fmt.Sprintf("/local/output/%s/%s", definition.DefinitionKindToType[g.meta.kind], g.meta.name),
|
||||
"-t", "/local/template",
|
||||
"--skip-validate-spec",
|
||||
"--enable-post-process-file",
|
||||
"--generate-alias-as-model",
|
||||
"--inline-schema-name-defaults", "arrayItemSuffix=,mapItemSuffix=",
|
||||
"--additional-properties", fmt.Sprintf("isGoSubmodule=true,packageName=%s", strings.ReplaceAll(g.name, "-", "_")),
|
||||
"--additional-properties", fmt.Sprintf("packageName=%s", strings.ReplaceAll(g.meta.name, "-", "_")),
|
||||
"--global-property", "modelDocs=false,models,supportingFiles=utils.go",
|
||||
)
|
||||
if g.meta.Verbose {
|
||||
fmt.Println(cmd.String())
|
||||
klog.Info(cmd.String())
|
||||
}
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, string(output))
|
||||
}
|
||||
if g.meta.Verbose {
|
||||
fmt.Println(string(output))
|
||||
klog.Info(string(output))
|
||||
}
|
||||
|
||||
// Adjust the generated files and code
|
||||
for _, m := range g.modifiers {
|
||||
for _, m := range g.defModifiers {
|
||||
err := m.Modify()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "modify fail by %s", m.Name())
|
||||
@@ -531,20 +640,21 @@ func completeSchemas(schemas openapi3.Schemas) {
|
||||
// NewModifiableGenerator returns a new Generator with modifiers
|
||||
func NewModifiableGenerator(meta *GenMeta) *Generator {
|
||||
g := &Generator{
|
||||
meta: meta,
|
||||
modifiers: []Modifier{},
|
||||
meta: meta,
|
||||
defModifiers: []Modifier{},
|
||||
moduleModifiers: []Modifier{},
|
||||
}
|
||||
mo := newModifierOnLanguage(meta.Lang, g)
|
||||
g.modifiers = append(g.modifiers, mo)
|
||||
appendModifiersByLanguage(g, meta)
|
||||
return g
|
||||
}
|
||||
|
||||
func newModifierOnLanguage(lang string, generator *Generator) Modifier {
|
||||
switch lang {
|
||||
func appendModifiersByLanguage(g *Generator, meta *GenMeta) {
|
||||
switch meta.Lang {
|
||||
case "go":
|
||||
return &GoModifier{g: generator}
|
||||
g.defModifiers = append(g.defModifiers, &GoDefModifier{GenMeta: meta})
|
||||
g.moduleModifiers = append(g.moduleModifiers, &GoModuleModifier{GenMeta: meta})
|
||||
default:
|
||||
panic("unsupported language: " + lang)
|
||||
panic(fmt.Sprintf("unsupported language: %s", meta.Lang))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -607,3 +717,7 @@ func defaultValueMatchOneOfItem(item *openapi3.Schema, defaultValue interface{})
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func fnName(fn interface{}) string {
|
||||
return runtime.FuncForPC(reflect.ValueOf(fn).Pointer()).Name()
|
||||
}
|
||||
|
||||
@@ -30,19 +30,28 @@ import (
|
||||
var _ = Describe("Test Generating SDK", func() {
|
||||
var err error
|
||||
outputDir := filepath.Join("testdata", "output")
|
||||
lang := "go"
|
||||
meta := GenMeta{
|
||||
Output: outputDir,
|
||||
Lang: "go",
|
||||
Package: "github.com/kubevela/test-gen-sdk",
|
||||
File: []string{filepath.Join("testdata", "cron-task.cue")},
|
||||
InitSDK: true,
|
||||
Output: outputDir,
|
||||
Lang: lang,
|
||||
Package: "github.com/kubevela-contrib/kubevela-go-sdk",
|
||||
APIDirectory: defaultAPIDir[lang],
|
||||
Verbose: true,
|
||||
}
|
||||
var langArgs []string
|
||||
|
||||
BeforeEach(func() {
|
||||
meta.InitSDK = false
|
||||
meta.File = []string{filepath.Join("testdata", "cron-task.cue")}
|
||||
meta.cuePaths = []string{}
|
||||
})
|
||||
|
||||
checkDirNotEmpty := func(dir string) {
|
||||
_, err = os.Stat(dir)
|
||||
Expect(err).Should(BeNil())
|
||||
}
|
||||
genWithMeta := func(meta GenMeta) {
|
||||
err = meta.Init(common.Args{})
|
||||
genWithMeta := func() {
|
||||
err = meta.Init(common.Args{}, langArgs)
|
||||
Expect(err).Should(BeNil())
|
||||
err = meta.CreateScaffold()
|
||||
Expect(err).Should(BeNil())
|
||||
@@ -52,43 +61,40 @@ var _ = Describe("Test Generating SDK", func() {
|
||||
Expect(err).Should(BeNil())
|
||||
}
|
||||
It("Test generating SDK and init the scaffold", func() {
|
||||
genWithMeta(meta)
|
||||
meta.InitSDK = true
|
||||
genWithMeta()
|
||||
checkDirNotEmpty(filepath.Join(outputDir, "pkg", "apis"))
|
||||
checkDirNotEmpty(filepath.Join(outputDir, "pkg", "apis", "component", "cron-task"))
|
||||
})
|
||||
|
||||
It("Test generating SDK, append apis", func() {
|
||||
meta.InitSDK = false
|
||||
meta.File = append(meta.File, "testdata/shared-resource.cue")
|
||||
|
||||
genWithMeta(meta)
|
||||
genWithMeta()
|
||||
checkDirNotEmpty(filepath.Join(outputDir, "pkg", "apis", "policy", "shared-resource"))
|
||||
})
|
||||
|
||||
It("Test free form parameter {...}", func() {
|
||||
meta.InitSDK = false
|
||||
meta.File = []string{"testdata/json-merge-patch.cue"}
|
||||
meta.Verbose = true
|
||||
|
||||
genWithMeta(meta)
|
||||
genWithMeta()
|
||||
checkDirNotEmpty(filepath.Join(outputDir, "pkg", "apis", "trait", "json-merge-patch"))
|
||||
})
|
||||
|
||||
It("Test workflow step", func() {
|
||||
meta.InitSDK = false
|
||||
meta.File = []string{"testdata/deploy.cue"}
|
||||
meta.Verbose = true
|
||||
|
||||
genWithMeta(meta)
|
||||
genWithMeta()
|
||||
checkDirNotEmpty(filepath.Join(outputDir, "pkg", "apis", "workflow-step", "deploy"))
|
||||
})
|
||||
|
||||
It("Test step-group", func() {
|
||||
meta.InitSDK = false
|
||||
meta.File = []string{"testdata/step-group.cue"}
|
||||
meta.Verbose = true
|
||||
|
||||
genWithMeta(meta)
|
||||
genWithMeta()
|
||||
checkDirNotEmpty(filepath.Join(outputDir, "pkg", "apis", "workflow-step", "step-group"))
|
||||
By("check if AddSubStep is generated")
|
||||
content, err := os.ReadFile(filepath.Join(outputDir, "pkg", "apis", "workflow-step", "step-group", "step_group.go"))
|
||||
@@ -97,20 +103,27 @@ var _ = Describe("Test Generating SDK", func() {
|
||||
})
|
||||
|
||||
It("Test oneOf", func() {
|
||||
meta.InitSDK = false
|
||||
meta.File = []string{"testdata/one_of.cue"}
|
||||
meta.Verbose = true
|
||||
|
||||
genWithMeta(meta)
|
||||
genWithMeta()
|
||||
checkDirNotEmpty(filepath.Join(outputDir, "pkg", "apis", "workflow-step", "one_of"))
|
||||
By("check if ")
|
||||
})
|
||||
|
||||
It("Test known issue: apply-terraform-provider", func() {
|
||||
meta.InitSDK = false
|
||||
meta.Verbose = true
|
||||
meta.File = []string{"testdata/apply-terraform-provider.cue"}
|
||||
genWithMeta(meta)
|
||||
genWithMeta()
|
||||
})
|
||||
|
||||
It("Test generate sub-module", func() {
|
||||
meta.APIDirectory = "pkg/apis/addons/test_addon"
|
||||
langArgs = []string{
|
||||
string(mainModuleVersionKey) + "=" + mainModuleVersion.Default,
|
||||
}
|
||||
meta.IsSubModule = true
|
||||
genWithMeta()
|
||||
checkDirNotEmpty(filepath.Join(outputDir, "pkg", "apis", "addons", "test_addon", "component", "cron-task"))
|
||||
})
|
||||
|
||||
AfterSuite(func() {
|
||||
@@ -207,3 +220,117 @@ var _ = Describe("FixSchemaWithOneAnyAllOf", func() {
|
||||
Expect(schema.Value.OneOf[0].Value.Enum).To(Equal([]interface{}{"go", "java", "python", "node", "ruby"}))
|
||||
})
|
||||
})
|
||||
|
||||
var _ = Describe("TestNewLanguageArgs", func() {
|
||||
type args struct {
|
||||
lang string
|
||||
langArgs []string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want map[string]string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "should create a languageArgs struct with the correct values",
|
||||
args: args{
|
||||
lang: "go",
|
||||
langArgs: []string{"flag1=value1", "flag2=value2"},
|
||||
},
|
||||
want: map[string]string{"flag1": "value1", "flag2": "value2"},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "should not set a value for an unknown flag",
|
||||
args: args{
|
||||
lang: "go",
|
||||
langArgs: []string{"unknownFlag=value"},
|
||||
},
|
||||
want: map[string]string{},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "should warn if an argument is not in the key=value format",
|
||||
args: args{
|
||||
lang: "go",
|
||||
langArgs: []string{"invalidArgument"},
|
||||
},
|
||||
want: map[string]string{},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
It(tt.name, func() {
|
||||
got, err := NewLanguageArgs(tt.args.lang, tt.args.langArgs)
|
||||
if tt.wantErr {
|
||||
Expect(err).To(HaveOccurred())
|
||||
return
|
||||
}
|
||||
for k, v := range tt.want {
|
||||
Expect(got.Get(langArgKey(k))).To(Equal(v))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
var _ = Describe("getValueType", func() {
|
||||
|
||||
type valueTypeTest struct {
|
||||
input interface{}
|
||||
expected CUEType
|
||||
}
|
||||
|
||||
tests := []valueTypeTest{
|
||||
{nil, ""},
|
||||
{"hello", "string"},
|
||||
{42, "integer"},
|
||||
{float32(3.14), "number"},
|
||||
{3.14159265358979323846, "number"},
|
||||
{true, "boolean"},
|
||||
{map[string]interface{}{"key": "value"}, "object"},
|
||||
{[]interface{}{1, 2, 3}, "array"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt // capture range variable
|
||||
It("should return the correct CUEType for the input", func() {
|
||||
Expect(getValueType(tt.input)).To(Equal(tt.expected))
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
var _ = Describe("type fit", func() {
|
||||
|
||||
var schema *openapi3.Schema
|
||||
|
||||
BeforeEach(func() {
|
||||
schema = &openapi3.Schema{Type: "string"}
|
||||
})
|
||||
|
||||
var testCases = []struct {
|
||||
name string
|
||||
cueType CUEType
|
||||
expectedFit bool
|
||||
schemaType string
|
||||
}{
|
||||
{"string can be oas string", CUEType("string"), true, "string"},
|
||||
{"string not oas integer", CUEType("string"), false, "integer"},
|
||||
{"integer can be oas integer", CUEType("integer"), true, "integer"},
|
||||
{"integer can be oas number", CUEType("integer"), true, "number"},
|
||||
{"number can be oas number", CUEType("number"), true, "number"},
|
||||
{"number not oas integer", CUEType("number"), false, "integer"},
|
||||
{"boolean can be oas boolean", CUEType("boolean"), true, "boolean"},
|
||||
{"array can be oas array", CUEType("array"), true, "array"},
|
||||
{"invalid type and any schema", CUEType(""), false, "anyschema"},
|
||||
}
|
||||
|
||||
It("should return whether the CUEType fits the schema type or not", func() {
|
||||
for _, tc := range testCases {
|
||||
schema.Type = tc.schemaType
|
||||
result := tc.cueType.fit(schema)
|
||||
Expect(result).To(Equal(tc.expectedFit), tc.name)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
+231
-79
@@ -23,11 +23,10 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
// we need dot import here to make the complex go code generating simpler
|
||||
// nolint:revive
|
||||
j "github.com/dave/jennifer/jen"
|
||||
"github.com/ettle/strcase"
|
||||
"github.com/pkg/errors"
|
||||
@@ -36,6 +35,32 @@ import (
|
||||
pkgdef "github.com/oam-dev/kubevela/pkg/definition"
|
||||
)
|
||||
|
||||
var (
|
||||
mainModuleVersionKey langArgKey = "MainModuleVersion"
|
||||
goProxyKey langArgKey = "GoProxy"
|
||||
|
||||
mainModuleVersion = LangArg{
|
||||
Name: mainModuleVersionKey,
|
||||
Desc: "The version of main module, it will be used in go get command. For example, tag, commit id, branch name",
|
||||
// default hash of main module. This is a commit hash of kubevela-contrib/kubvela-go-sdk. It will be used in go get command.
|
||||
Default: "cd431bb25a9a",
|
||||
}
|
||||
goProxy = LangArg{
|
||||
Name: goProxyKey,
|
||||
Desc: "The proxy for go get/go mod tidy command",
|
||||
Default: "https://goproxy.cn,direct",
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
registerLangArg("go", mainModuleVersion, goProxy)
|
||||
}
|
||||
|
||||
const (
|
||||
// PackagePlaceHolder is the package name placeholder
|
||||
PackagePlaceHolder = "github.com/kubevela/vela-go-sdk"
|
||||
)
|
||||
|
||||
var (
|
||||
// DefinitionKindToPascal is the map of definition kind to pascal case
|
||||
DefinitionKindToPascal = map[string]string{
|
||||
@@ -60,14 +85,22 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
// GoModifier is the Modifier for golang
|
||||
type GoModifier struct {
|
||||
g *Generator
|
||||
// GoDefModifier is the Modifier for golang, modify code for each definition
|
||||
type GoDefModifier struct {
|
||||
*GenMeta
|
||||
*goArgs
|
||||
|
||||
defName string
|
||||
defKind string
|
||||
verbose bool
|
||||
defStructPointer *j.Statement
|
||||
}
|
||||
|
||||
// GoModuleModifier is the Modifier for golang, modify code for each module which contains multiple definitions
|
||||
type GoModuleModifier struct {
|
||||
*GenMeta
|
||||
*goArgs
|
||||
}
|
||||
|
||||
type goArgs struct {
|
||||
apiDir string
|
||||
defDir string
|
||||
utilsDir string
|
||||
// def name of different cases
|
||||
@@ -77,16 +110,57 @@ type GoModifier struct {
|
||||
typeVarName string
|
||||
defStructName string
|
||||
defFuncReceiver string
|
||||
defStructPointer *j.Statement
|
||||
}
|
||||
|
||||
func (a *goArgs) init(m *GenMeta) error {
|
||||
var err error
|
||||
a.apiDir, err = filepath.Abs(path.Join(m.Output, m.APIDirectory))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.defDir = path.Join(a.apiDir, pkgdef.DefinitionKindToType[m.kind], m.name)
|
||||
a.utilsDir = path.Join(m.Output, "pkg", "apis", "utils")
|
||||
a.nameInSnakeCase = strcase.ToSnake(m.name)
|
||||
a.nameInPascalCase = strcase.ToPascal(m.name)
|
||||
a.typeVarName = a.nameInPascalCase + "Type"
|
||||
a.specNameInPascalCase = a.nameInPascalCase + "Spec"
|
||||
a.defStructName = strcase.ToGoPascal(m.name + "-" + pkgdef.DefinitionKindToType[m.kind])
|
||||
a.defFuncReceiver = m.name[:1]
|
||||
return nil
|
||||
}
|
||||
|
||||
// Modify implements Modifier
|
||||
func (m *GoModuleModifier) Modify() error {
|
||||
for _, fn := range []func() error{
|
||||
m.init,
|
||||
m.format,
|
||||
m.addSubGoMod,
|
||||
m.tidyMainMod,
|
||||
} {
|
||||
if err := fn(); err != nil {
|
||||
return errors.Wrap(err, fnName(fn))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *GoModuleModifier) init() error {
|
||||
m.goArgs = &goArgs{}
|
||||
return m.goArgs.init(m.GenMeta)
|
||||
}
|
||||
|
||||
// Name the name of modifier
|
||||
func (m *GoModifier) Name() string {
|
||||
return "GoModifier"
|
||||
func (m *GoModuleModifier) Name() string {
|
||||
return "goModuleModifier"
|
||||
}
|
||||
|
||||
// Name the name of modifier
|
||||
func (m *GoDefModifier) Name() string {
|
||||
return "GoDefModifier"
|
||||
}
|
||||
|
||||
// Modify the modification of generated code
|
||||
func (m *GoModifier) Modify() error {
|
||||
func (m *GoDefModifier) Modify() error {
|
||||
for _, fn := range []func() error{
|
||||
m.init,
|
||||
m.clean,
|
||||
@@ -95,35 +169,28 @@ func (m *GoModifier) Modify() error {
|
||||
m.addDefAPI,
|
||||
m.addValidateTraits,
|
||||
m.exportMethods,
|
||||
m.format,
|
||||
} {
|
||||
if err := fn(); err != nil {
|
||||
return err
|
||||
return errors.Wrap(err, fnName(fn))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *GoModifier) init() error {
|
||||
m.defName = m.g.name
|
||||
m.defKind = m.g.kind
|
||||
m.verbose = m.g.meta.Verbose
|
||||
func (m *GoDefModifier) init() error {
|
||||
m.goArgs = &goArgs{}
|
||||
err := m.goArgs.init(m.GenMeta)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pkgAPIDir := path.Join(m.g.meta.Output, "pkg", "apis")
|
||||
m.defDir = path.Join(pkgAPIDir, pkgdef.DefinitionKindToType[m.defKind], m.defName)
|
||||
m.utilsDir = path.Join(pkgAPIDir, "utils")
|
||||
m.nameInSnakeCase = strcase.ToSnake(m.defName)
|
||||
m.nameInPascalCase = strcase.ToPascal(m.defName)
|
||||
m.typeVarName = m.nameInPascalCase + "Type"
|
||||
m.specNameInPascalCase = m.nameInPascalCase + "Spec"
|
||||
m.defStructName = strcase.ToGoPascal(m.defName + "-" + pkgdef.DefinitionKindToType[m.defKind])
|
||||
m.defStructPointer = j.Op("*").Id(m.defStructName)
|
||||
m.defFuncReceiver = m.defName[:1]
|
||||
err := os.MkdirAll(m.utilsDir, 0750)
|
||||
|
||||
err = os.MkdirAll(m.utilsDir, 0750)
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *GoModifier) clean() error {
|
||||
func (m *GoDefModifier) clean() error {
|
||||
err := os.RemoveAll(path.Join(m.defDir, ".openapi-generator"))
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -148,17 +215,101 @@ func (m *GoModifier) clean() error {
|
||||
|
||||
}
|
||||
|
||||
// addSubGoMod will add a go.mod and go.sum in the api directory if user mark that the api is a submodule
|
||||
func (m *GoModuleModifier) addSubGoMod() error {
|
||||
if !m.IsSubModule {
|
||||
return nil
|
||||
}
|
||||
files := map[string]string{
|
||||
"go.mod_": "go.mod",
|
||||
"go.sum": "go.sum",
|
||||
}
|
||||
for src, dst := range files {
|
||||
srcContent, err := Scaffold.ReadFile(path.Join(ScaffoldDir, "go", src))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "read "+src)
|
||||
}
|
||||
subModuleName := strings.TrimSuffix(fmt.Sprintf("%s/%s", m.Package, m.APIDirectory), "/")
|
||||
srcContent = bytes.ReplaceAll(srcContent, []byte("module "+PackagePlaceHolder), []byte("module "+subModuleName))
|
||||
srcContent = bytes.ReplaceAll(srcContent, []byte("// require "+PackagePlaceHolder), []byte("require "+m.Package))
|
||||
|
||||
err = os.WriteFile(path.Join(m.apiDir, dst), srcContent, 0600)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "write "+dst)
|
||||
}
|
||||
}
|
||||
|
||||
cmds := make([]*exec.Cmd, 0)
|
||||
if m.LangArgs.Get(mainModuleVersionKey) != mainModuleVersion.Default {
|
||||
// nolint:gosec
|
||||
cmds = append(cmds, exec.Command("docker", "run",
|
||||
"--rm",
|
||||
"-v", m.apiDir+":/api",
|
||||
"-w", "/api",
|
||||
"golang:1.19-alpine",
|
||||
"go", "get", fmt.Sprintf("%s@%s", m.Package, m.LangArgs.Get(mainModuleVersionKey)),
|
||||
))
|
||||
}
|
||||
// nolint:gosec
|
||||
cmds = append(cmds, exec.Command("docker", "run",
|
||||
"--rm",
|
||||
"-v", m.apiDir+":/api",
|
||||
"-w", "/api",
|
||||
"--env", "GOPROXY="+m.LangArgs.Get(goProxyKey),
|
||||
"golang:1.19-alpine",
|
||||
"go", "mod", "tidy",
|
||||
))
|
||||
for _, cmd := range cmds {
|
||||
if m.Verbose {
|
||||
fmt.Println(cmd.String())
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
}
|
||||
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "fail to run command %s", cmd.String())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// tidyMainMod will run go mod tidy in the main module
|
||||
func (m *GoModuleModifier) tidyMainMod() error {
|
||||
if !m.InitSDK {
|
||||
return nil
|
||||
}
|
||||
outDir, err := filepath.Abs(m.GenMeta.Output)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// nolint:gosec
|
||||
cmd := exec.Command("docker", "run",
|
||||
"--rm",
|
||||
"-v", outDir+":/api",
|
||||
"-w", "/api",
|
||||
"golang:1.19-alpine",
|
||||
"go", "mod", "tidy",
|
||||
)
|
||||
if m.Verbose {
|
||||
fmt.Println(cmd.String())
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
}
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
// read all files in definition directory,
|
||||
// 1. replace the Nullable* Struct
|
||||
// 2. replace the package name
|
||||
func (m *GoModifier) modifyDefs() error {
|
||||
func (m *GoDefModifier) modifyDefs() error {
|
||||
changeNullableType := func(b []byte) []byte {
|
||||
return regexp.MustCompile("Nullable(String|(Float|Int)(32|64)|Bool)").ReplaceAll(b, []byte("utils.Nullable$1"))
|
||||
}
|
||||
|
||||
files, err := os.ReadDir(m.defDir)
|
||||
defHandleFunc := []byteHandler{
|
||||
m.g.meta.packageFunc,
|
||||
m.packageFunc,
|
||||
changeNullableType,
|
||||
}
|
||||
if err != nil {
|
||||
@@ -180,7 +331,7 @@ func (m *GoModifier) modifyDefs() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *GoModifier) moveUtils() error {
|
||||
func (m *GoDefModifier) moveUtils() error {
|
||||
// Adjust the generated files and code
|
||||
err := os.Rename(path.Join(m.defDir, "utils.go"), path.Join(m.utilsDir, "utils.go"))
|
||||
if err != nil {
|
||||
@@ -193,7 +344,7 @@ func (m *GoModifier) moveUtils() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
utilsBytes = bytes.Replace(utilsBytes, []byte(fmt.Sprintf("package %s", strcase.ToSnake(m.defName))), []byte("package utils"), 1)
|
||||
utilsBytes = bytes.Replace(utilsBytes, []byte(fmt.Sprintf("package %s", strcase.ToSnake(m.name))), []byte("package utils"), 1)
|
||||
utilsBytes = bytes.ReplaceAll(utilsBytes, []byte("isNil"), []byte("IsNil"))
|
||||
err = os.WriteFile(utilsFile, utilsBytes, 0600)
|
||||
if err != nil {
|
||||
@@ -203,7 +354,7 @@ func (m *GoModifier) moveUtils() error {
|
||||
}
|
||||
|
||||
// addDefAPI will add component/trait/workflowstep/policy Object to the api
|
||||
func (m *GoModifier) addDefAPI() error {
|
||||
func (m *GoDefModifier) addDefAPI() error {
|
||||
file, err := os.OpenFile(path.Join(m.defDir, m.nameInSnakeCase+".go"), os.O_APPEND|os.O_WRONLY, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -236,11 +387,11 @@ func (m *GoModifier) addDefAPI() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *GoModifier) genCommonFunc() []*j.Statement {
|
||||
kind := m.defKind
|
||||
func (m *GoDefModifier) genCommonFunc() []*j.Statement {
|
||||
kind := m.kind
|
||||
typeName := j.Id(m.nameInPascalCase + "Type")
|
||||
typeConst := j.Const().Add(typeName).Op("=").Lit(m.defName)
|
||||
j.Op("=").Lit(m.defName)
|
||||
typeConst := j.Const().Add(typeName).Op("=").Lit(m.name)
|
||||
j.Op("=").Lit(m.name)
|
||||
defStruct := j.Type().Id(m.defStructName).Struct(
|
||||
j.Id("Base").Id("apis").Dot(DefinitionKindToBaseType[kind]),
|
||||
j.Id("Properties").Id(m.specNameInPascalCase),
|
||||
@@ -328,8 +479,8 @@ func (m *GoModifier) genCommonFunc() []*j.Statement {
|
||||
return []*j.Statement{typeConst, initFunc, defStruct, defStructConstructor, buildFunc}
|
||||
}
|
||||
|
||||
func (m *GoModifier) genFromFunc() []*j.Statement {
|
||||
kind := m.g.kind
|
||||
func (m *GoDefModifier) genFromFunc() []*j.Statement {
|
||||
kind := m.kind
|
||||
kindBaseProperties := map[string][]string{
|
||||
v1beta1.ComponentDefinitionKind: {"Name", "DependsOn", "Inputs", "Outputs"},
|
||||
v1beta1.WorkflowStepDefinitionKind: {"Name", "DependsOn", "Inputs", "Outputs", "If", "Timeout", "Meta"},
|
||||
@@ -340,7 +491,7 @@ func (m *GoModifier) genFromFunc() []*j.Statement {
|
||||
// fromFuncRsv means build from a part of K8s Object (e.g. v1beta1.Application.spec.component[*] to internal presentation (e.g. Component)
|
||||
// fromFuncRsv will have a function receiver
|
||||
getSubSteps := func(sub bool) func(g *j.Group) {
|
||||
if m.defKind != v1beta1.WorkflowStepDefinitionKind || sub {
|
||||
if m.kind != v1beta1.WorkflowStepDefinitionKind || sub {
|
||||
return func(g *j.Group) {}
|
||||
}
|
||||
return func(g *j.Group) {
|
||||
@@ -358,7 +509,7 @@ func (m *GoModifier) genFromFunc() []*j.Statement {
|
||||
}
|
||||
}
|
||||
assignSubSteps := func(sub bool) func(g *j.Group) {
|
||||
if m.defKind != v1beta1.WorkflowStepDefinitionKind || sub {
|
||||
if m.kind != v1beta1.WorkflowStepDefinitionKind || sub {
|
||||
return func(g *j.Group) {}
|
||||
}
|
||||
return func(g *j.Group) {
|
||||
@@ -379,7 +530,7 @@ func (m *GoModifier) genFromFunc() []*j.Statement {
|
||||
BlockFunc(func(g *j.Group) {
|
||||
if kind == v1beta1.ComponentDefinitionKind {
|
||||
g.Add(j.For(j.List(j.Id("_"), j.Id("trait")).Op(":=").Range().Id("from").Dot("Traits")).Block(
|
||||
j.List(j.Id("_t"), j.Err()).Op(":=").Qual("sdkcommon", "FromTrait").Call(j.Op("&").Id("trait")),
|
||||
j.List(j.Id("_t"), j.Err()).Op(":=").Qual("sdkcommon", "FromTrait").Call(j.Id("trait")),
|
||||
j.If(j.Err().Op("!=").Nil()).Block(
|
||||
j.Return(j.Nil(), j.Err()),
|
||||
),
|
||||
@@ -425,15 +576,15 @@ func (m *GoModifier) genFromFunc() []*j.Statement {
|
||||
)
|
||||
|
||||
res := []*j.Statement{fromFuncRsv(false), fromFunc}
|
||||
if m.defKind == v1beta1.WorkflowStepDefinitionKind {
|
||||
if m.kind == v1beta1.WorkflowStepDefinitionKind {
|
||||
res = append(res, fromFuncRsv(true), fromSubFunc)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// genDedicatedFunc generate functions for definition kinds
|
||||
func (m *GoModifier) genDedicatedFunc() []*j.Statement {
|
||||
switch m.defKind {
|
||||
func (m *GoDefModifier) genDedicatedFunc() []*j.Statement {
|
||||
switch m.kind {
|
||||
case v1beta1.ComponentDefinitionKind:
|
||||
setTraitFunc := j.Func().
|
||||
Params(j.Id(m.defFuncReceiver).Add(m.defStructPointer)).
|
||||
@@ -484,14 +635,14 @@ func (m *GoModifier) genDedicatedFunc() []*j.Statement {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *GoModifier) genNameTypeFunc() []*j.Statement {
|
||||
nameFunc := j.Func().Params(j.Id(m.defFuncReceiver).Add(m.defStructPointer)).Id(DefinitionKindToPascal[m.defKind] + "Name").Params().String().Block(
|
||||
func (m *GoDefModifier) genNameTypeFunc() []*j.Statement {
|
||||
nameFunc := j.Func().Params(j.Id(m.defFuncReceiver).Add(m.defStructPointer)).Id(DefinitionKindToPascal[m.kind] + "Name").Params().String().Block(
|
||||
j.Return(j.Id(m.defFuncReceiver).Dot("Base").Dot("Name")),
|
||||
)
|
||||
typeFunc := j.Func().Params(j.Id(m.defFuncReceiver).Add(m.defStructPointer)).Id("DefType").Params().String().Block(
|
||||
j.Return(j.Id(m.typeVarName)),
|
||||
)
|
||||
switch m.defKind {
|
||||
switch m.kind {
|
||||
case v1beta1.ComponentDefinitionKind, v1beta1.WorkflowStepDefinitionKind, v1beta1.PolicyDefinitionKind:
|
||||
return []*j.Statement{nameFunc, typeFunc}
|
||||
case v1beta1.TraitDefinitionKind:
|
||||
@@ -500,11 +651,11 @@ func (m *GoModifier) genNameTypeFunc() []*j.Statement {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *GoModifier) genUnmarshalFunc() []*j.Statement {
|
||||
func (m *GoDefModifier) genUnmarshalFunc() []*j.Statement {
|
||||
return []*j.Statement{j.Null()}
|
||||
}
|
||||
|
||||
func (m *GoModifier) genBaseSetterFunc() []*j.Statement {
|
||||
func (m *GoDefModifier) genBaseSetterFunc() []*j.Statement {
|
||||
baseFuncArgs := map[string][]struct {
|
||||
funcName string
|
||||
argName string
|
||||
@@ -533,7 +684,7 @@ func (m *GoModifier) genBaseSetterFunc() []*j.Statement {
|
||||
},
|
||||
}
|
||||
baseFuncs := make([]*j.Statement, 0)
|
||||
for _, fn := range baseFuncArgs[m.defKind] {
|
||||
for _, fn := range baseFuncArgs[m.kind] {
|
||||
if fn.dst == nil {
|
||||
fn.dst = j.Dot(fn.funcName)
|
||||
}
|
||||
@@ -556,8 +707,8 @@ func (m *GoModifier) genBaseSetterFunc() []*j.Statement {
|
||||
return baseFuncs
|
||||
}
|
||||
|
||||
func (m *GoModifier) genAddSubStepFunc() *j.Statement {
|
||||
if m.defName != "step-group" || m.defKind != v1beta1.WorkflowStepDefinitionKind {
|
||||
func (m *GoDefModifier) genAddSubStepFunc() *j.Statement {
|
||||
if m.name != "step-group" || m.kind != v1beta1.WorkflowStepDefinitionKind {
|
||||
return j.Null()
|
||||
}
|
||||
subList := j.Id(m.defFuncReceiver).Dot("Base").Dot("SubSteps")
|
||||
@@ -573,7 +724,7 @@ func (m *GoModifier) genAddSubStepFunc() *j.Statement {
|
||||
}
|
||||
|
||||
// exportMethods will export methods from definition spec struct to definition struct
|
||||
func (m *GoModifier) exportMethods() error {
|
||||
func (m *GoDefModifier) exportMethods() error {
|
||||
fileLoc := path.Join(m.defDir, m.nameInSnakeCase+".go")
|
||||
// nolint:gosec
|
||||
file, err := os.ReadFile(fileLoc)
|
||||
@@ -604,8 +755,8 @@ func (m *GoModifier) exportMethods() error {
|
||||
return os.WriteFile(fileLoc, []byte(fileStr), 0600)
|
||||
}
|
||||
|
||||
func (m *GoModifier) addValidateTraits() error {
|
||||
if m.defKind != v1beta1.ComponentDefinitionKind {
|
||||
func (m *GoDefModifier) addValidateTraits() error {
|
||||
if m.kind != v1beta1.ComponentDefinitionKind {
|
||||
return nil
|
||||
}
|
||||
fileLoc := path.Join(m.defDir, m.nameInSnakeCase+".go")
|
||||
@@ -632,11 +783,12 @@ func (m *GoModifier) addValidateTraits() error {
|
||||
|
||||
return os.WriteFile(fileLoc, []byte(fileStr), 0600)
|
||||
}
|
||||
func (m *GoModifier) format() error {
|
||||
func (m *GoModuleModifier) format() error {
|
||||
// check if gofmt is installed
|
||||
// todo (chivalryq): support go mod tidy for sub-module
|
||||
|
||||
formatters := []string{"gofmt", "goimports"}
|
||||
formatterPaths := []string{}
|
||||
var formatterPaths []string
|
||||
allFormattersInstalled := true
|
||||
for _, formatter := range formatters {
|
||||
p, err := exec.LookPath(formatter)
|
||||
@@ -648,11 +800,11 @@ func (m *GoModifier) format() error {
|
||||
}
|
||||
if allFormattersInstalled {
|
||||
for _, fmter := range formatterPaths {
|
||||
if m.verbose {
|
||||
if m.Verbose {
|
||||
fmt.Printf("Use %s to format code\n", fmter)
|
||||
}
|
||||
// nolint:gosec
|
||||
cmd := exec.Command(fmter, "-w", m.defDir)
|
||||
cmd := exec.Command(fmter, "-w", m.apiDir)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, string(output))
|
||||
@@ -661,31 +813,31 @@ func (m *GoModifier) format() error {
|
||||
return nil
|
||||
}
|
||||
// fallback to use go lib
|
||||
if m.verbose {
|
||||
if m.Verbose {
|
||||
fmt.Println("At least one of linters is not installed, use go/format lib to format code")
|
||||
}
|
||||
files, err := os.ReadDir(m.defDir)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "read dir")
|
||||
}
|
||||
for _, f := range files {
|
||||
if !strings.HasSuffix(f.Name(), ".go") {
|
||||
continue
|
||||
}
|
||||
filePath := path.Join(m.defDir, f.Name())
|
||||
// nolint:gosec
|
||||
content, err := os.ReadFile(filePath)
|
||||
|
||||
// format all .go files
|
||||
return filepath.Walk(m.apiDir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "read file %s", filePath)
|
||||
return err
|
||||
}
|
||||
if !strings.HasSuffix(path, ".go") {
|
||||
return nil
|
||||
}
|
||||
// nolint:gosec
|
||||
content, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "read file %s", path)
|
||||
}
|
||||
formatted, err := format.Source(content)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "format file %s", filePath)
|
||||
return errors.Wrapf(err, "format file %s", path)
|
||||
}
|
||||
err = os.WriteFile(filePath, formatted, 0600)
|
||||
err = os.WriteFile(path, formatted, 0600)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "write file %s", filePath)
|
||||
return errors.Wrapf(err, "write file %s", path)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
+22
-6
@@ -1065,19 +1065,22 @@ func NewDefinitionValidateCommand(c common.Args) *cobra.Command {
|
||||
// NewDefinitionGenAPICommand create the `vela def gen-api` command to help user generate Go code from the definition
|
||||
func NewDefinitionGenAPICommand(c common.Args) *cobra.Command {
|
||||
meta := gen_sdk.GenMeta{}
|
||||
var languageArgs []string
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "gen-api DEFINITION.cue",
|
||||
Short: "Generate SDK from X-Definition.",
|
||||
Long: "Generate SDK from X-definition file.\n" +
|
||||
"* This command leverage openapi-generator project. Therefore demands \"docker\" exist in PATH" +
|
||||
"* This command leverage openapi-generator project. Therefore demands \"docker\" exist in PATH\n" +
|
||||
"* Currently, this function is still working in progress and not all formats of parameter in X-definition are supported yet.",
|
||||
Example: "# Generate SDK for golang with scaffold initialized\n" +
|
||||
"> vela def gen-api --init --lang go -f /path/to/def -o /path/to/sdk\n" +
|
||||
"> vela def gen-api --init --language go -f /path/to/def -o /path/to/sdk\n" +
|
||||
"# Generate incremental definition files to existing sdk directory\n" +
|
||||
"> vela def gen-api --lang go -f /path/to/def -o /path/to/sdk",
|
||||
"> vela def gen-api --language go -f /path/to/def -o /path/to/sdk\n" +
|
||||
"# Generate definitions to a sub-module\n" +
|
||||
"> vela def gen-api --language go -f /path/to/def -o /path/to/sdk --submodule --api-dir path/relative/to/output --language-args arg1=val1,arg2=val2\n",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
err := meta.Init(c)
|
||||
err := meta.Init(c, languageArgs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1097,13 +1100,26 @@ func NewDefinitionGenAPICommand(c common.Args) *cobra.Command {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().StringVarP(&meta.Output, "output", "o", "./apis", "Output directory path")
|
||||
cmd.Flags().StringVarP(&meta.Package, "package", "p", "github.com/kubevela/vela-go-sdk", "Package name of generated code")
|
||||
cmd.Flags().StringVarP(&meta.Lang, "lang", "g", "go", "Language to generate code. Valid languages: go")
|
||||
cmd.Flags().StringVar(&meta.APIDirectory, "api-dir", "", "API directory path to put definition API files, relative to output directory. Default value: go: pkg/apis")
|
||||
cmd.Flags().BoolVar(&meta.IsSubModule, "submodule", false, "Whether the generated code is a submodule of the project. If set, the directory specified by `api-dir` will be treated as a submodule of the project")
|
||||
cmd.Flags().StringVarP(&meta.Package, "package", "p", gen_sdk.PackagePlaceHolder, "Package name of generated code")
|
||||
cmd.Flags().StringVarP(&meta.Lang, "language", "g", "go", "Language to generate code. Valid languages: go")
|
||||
cmd.Flags().StringVarP(&meta.Template, "template", "t", "", "Template file path, if not specified, the default template will be used")
|
||||
cmd.Flags().StringSliceVarP(&meta.File, "file", "f", nil, "File name of definitions, can be specified multiple times, or use comma to separate multiple files. If directory specified, all files found recursively in the directory will be used")
|
||||
cmd.Flags().BoolVar(&meta.InitSDK, "init", false, "Init the whole SDK project, if not set, only the API file will be generated")
|
||||
cmd.Flags().BoolVarP(&meta.Verbose, "verbose", "v", false, "Print verbose logs")
|
||||
var langArgsDescStr string
|
||||
for lang, args := range gen_sdk.LangArgsRegistry {
|
||||
langArgsDescStr += lang + ": \n"
|
||||
for key, arg := range args {
|
||||
langArgsDescStr += fmt.Sprintf("\t%s: %s(default: %s)\n", key, arg.Name, arg.Default)
|
||||
}
|
||||
}
|
||||
cmd.Flags().StringSliceVar(&languageArgs, "language-args", []string{},
|
||||
fmt.Sprintf("language-specific arguments to pass to the go generator, available options: \n"+langArgsDescStr),
|
||||
)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user