Files
4010da6765 Chore: fix trait definition translation discrepancies (#7044)
* Fix: Enhance CUE generation for optional fields in collection operations

Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com>

* Feat: Add tests for optional fields and conditional logic in trait definitions

Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com>

* Fix: Add namespace field to podAffinityTerm in affinity trait definition

Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com>

* Fix: Add support for ForEachMap operation in CUE generation

Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com>

* Fix: Add tests for ForEachMap let bindings and custom rendering in trait definitions

Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com>

* Fix: Add optional description fields for PatchContainer and enhance parameter generation logic

Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com>

* Fix: Update parameter access to bracket notation in CUE generation and tests

Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com>

* Fix: Implement PatchStrategyAnnotation for CUE generation and enhance condition hoisting logic

Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com>

* Fix: Add SpreadAll operation for array patches and enhance IntParam constraints handling

Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com>

* Fix: Refactor writeSpreadAllOp to improve condition handling and element processing

Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com>

* Fix: Correct nodeSelector spelling and references in affinity trait definition

Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com>

* Fix: Add optional namespace field to podAffinityTerm in affinity trait definition

Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com>

* test: re-trigger test pipelines

Signed-off-by: Chaitanya Reddy Onteddu <chaitanyareddy0702@gmail.com>
Co-authored-by: Chaitanya Reddy Onteddu <chaitanyareddy0702@gmail.com>

* ci: retrigger checks

Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com>
Co-authored-by: Chaitanya Reddy Onteddu <chaitanyareddy0702@gmail.com>

* Fix: Update workflow API import paths to use the correct package location

Signed-off-by: Chaitanya Reddy Onteddu <chaitanyareddy0702@gmail.com>
Co-authored-by: Ayush Kumar <ayushshyamkumar888@gmail.com>

* Fix: Update dependency versions in go.mod for improved compatibility

Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com>
Co-authored-by: Chaitanya Reddy Onteddu <chaitanyareddy0702@gmail.com>

* ci: retrigger checks

Signed-off-by: Chaitanya Reddy Onteddu <chaitanyareddy0702@gmail.com>
Co-authored-by: Ayush Kumar <ayushshyamkumar888@gmail.com>

* Fix: Add tests for SpreadAll operation and conditional handling in trait definitions

Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com>

* Fix: Enhance optional field handling for non-string conditions in PatchContainer

Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com>

* Fix: Implement ForceOptional parameter handling to retain optionality with defaults

Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com>

* Fix: Add PatchStrategy field to PatchContainerConfig and update related logic in trait definitions

Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com>

* Fix: Implement PatchFieldBuilder for fluent API construction of PatchContainerField

Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com>

* Fix: Update test descriptions and enhance CUE generation for PatchFields with NotEmpty and IsSet conditions

Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com>

---------

Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com>
Signed-off-by: Chaitanya Reddy Onteddu <chaitanyareddy0702@gmail.com>
Co-authored-by: Chaitanya Reddy Onteddu <chaitanyareddy0702@gmail.com>
2026-02-20 14:09:11 -08:00

851 lines
28 KiB
Go

/*
Copyright 2021 The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package gen_sdk
import (
"bytes"
"fmt"
"go/format"
"os"
"os/exec"
"path"
"path/filepath"
"regexp"
"strings"
j "github.com/dave/jennifer/jen"
"github.com/ettle/strcase"
"github.com/pkg/errors"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
pkgdef "github.com/oam-dev/kubevela/pkg/definition"
)
// GoProxyEnvKey with the environment variable name that defines the GOPROXY preferences.
const GoProxyEnvKey = "GOPROXY"
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: "",
}
)
func init() {
// Propagate the environment GOPROXY variable to the tests that are executed through external containers.
goProxy.Default = os.Getenv(GoProxyEnvKey)
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{
v1beta1.ComponentDefinitionKind: "Component",
v1beta1.TraitDefinitionKind: "Trait",
v1beta1.WorkflowStepDefinitionKind: "WorkflowStep",
v1beta1.PolicyDefinitionKind: "Policy",
}
// DefinitionKindToBaseType is the map of definition kind to base type
DefinitionKindToBaseType = map[string]string{
v1beta1.ComponentDefinitionKind: "ComponentBase",
v1beta1.TraitDefinitionKind: "TraitBase",
v1beta1.WorkflowStepDefinitionKind: "WorkflowStepBase",
v1beta1.PolicyDefinitionKind: "PolicyBase",
}
// DefinitionKindToStatement is the map of definition kind to statement
DefinitionKindToStatement = map[string]*j.Statement{
v1beta1.ComponentDefinitionKind: j.Qual("common", "ApplicationComponent"),
v1beta1.TraitDefinitionKind: j.Qual("common", "ApplicationTrait"),
v1beta1.WorkflowStepDefinitionKind: j.Qual("github.com/kubevela/pkg/apis/oam/v1alpha1", "WorkflowStep"),
v1beta1.PolicyDefinitionKind: j.Qual("v1beta1", "AppPolicy"),
}
)
// GoDefModifier is the Modifier for golang, modify code for each definition
type GoDefModifier struct {
*GenMeta
*goArgs
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
nameInSnakeCase string
nameInPascalCase string
specNameInPascalCase string
typeVarName string
defStructName string
defFuncReceiver string
}
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 *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 *GoDefModifier) Modify() error {
for _, fn := range []func() error{
m.init,
m.clean,
m.moveUtils,
m.modifyDefs,
m.addDefAPI,
m.addValidateTraits,
m.exportMethods,
} {
if err := fn(); err != nil {
return errors.Wrap(err, fnName(fn))
}
}
return nil
}
func (m *GoDefModifier) init() error {
m.goArgs = &goArgs{}
err := m.goArgs.init(m.GenMeta)
if err != nil {
return err
}
m.defStructPointer = j.Op("*").Id(m.defStructName)
err = os.MkdirAll(m.utilsDir, 0750)
return err
}
func (m *GoDefModifier) clean() error {
err := os.RemoveAll(path.Join(m.defDir, ".openapi-generator"))
if err != nil {
return err
}
err = os.RemoveAll(path.Join(m.defDir, "api"))
if err != nil {
return err
}
files, _ := os.ReadDir(m.defDir)
for _, f := range files {
dst := strings.TrimPrefix(f.Name(), "model_")
if dst == m.nameInSnakeCase+"_spec.go" {
dst = m.nameInSnakeCase + ".go"
}
err = os.Rename(path.Join(m.defDir, f.Name()), path.Join(m.defDir, dst))
if err != nil {
return err
}
}
return nil
}
// 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.23.8-alpine@sha256:b7486658b87d34ecf95125e5b97e8dfe86c21f712aa36fc0c702e5dc41dc63e1",
"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.23.8-alpine@sha256:b7486658b87d34ecf95125e5b97e8dfe86c21f712aa36fc0c702e5dc41dc63e1",
"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.23.8-alpine@sha256:b7486658b87d34ecf95125e5b97e8dfe86c21f712aa36fc0c702e5dc41dc63e1",
"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 *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.packageFunc,
changeNullableType,
}
if err != nil {
return err
}
for _, f := range files {
loc := path.Join(m.defDir, f.Name())
// nolint:gosec
b, err := os.ReadFile(loc)
if err != nil {
return errors.Wrapf(err, "read file")
}
for _, h := range defHandleFunc {
b = h(b)
}
_ = os.WriteFile(loc, b, 0600)
}
return nil
}
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 {
return err
}
utilsFile := path.Join(m.utilsDir, "utils.go")
// nolint:gosec
utilsBytes, err := os.ReadFile(utilsFile)
if err != nil {
return err
}
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 {
return err
}
return nil
}
// addDefAPI will add component/trait/workflowstep/policy Object to the api
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
}
defer func() {
_ = file.Close()
}()
renderGroup := make([]*j.Statement, 0)
renderGroup = append(renderGroup, m.genCommonFunc()...)
renderGroup = append(renderGroup, m.genFromFunc()...)
renderGroup = append(renderGroup, m.genDedicatedFunc()...)
renderGroup = append(renderGroup, m.genNameTypeFunc()...)
renderGroup = append(renderGroup, m.genUnmarshalFunc()...)
renderGroup = append(renderGroup, m.genBaseSetterFunc()...)
renderGroup = append(renderGroup, m.genAddSubStepFunc())
buf := new(bytes.Buffer)
for _, r := range renderGroup {
// write content at the end of file
err := r.Render(buf)
buf.WriteString("\n\n")
if err != nil {
return errors.Wrap(err, "render code")
}
}
_, err = file.Write(buf.Bytes())
if err != nil {
return errors.Wrap(err, "append content to file")
}
return nil
}
func (m *GoDefModifier) genCommonFunc() []*j.Statement {
kind := m.kind
typeName := j.Id(m.nameInPascalCase + "Type")
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),
)
initFunc := j.Func().Id("init").Params().BlockFunc(func(g *j.Group) {
g.Add(j.Qual("sdkcommon", "Register"+DefinitionKindToPascal[kind]).Call(j.Add(typeName), j.Id("From"+DefinitionKindToPascal[kind])))
if kind == v1beta1.WorkflowStepDefinitionKind {
g.Add(j.Qual("sdkcommon", "RegisterWorkflowSubStep").Call(j.Add(typeName), j.Id("FromWorkflowSubStep")))
}
},
)
defStructConstructor := j.Func().Id(m.nameInPascalCase).Params(
j.Do(func(s *j.Statement) {
switch kind {
case v1beta1.ComponentDefinitionKind, v1beta1.PolicyDefinitionKind, v1beta1.WorkflowStepDefinitionKind:
s.Id("name").String()
}
}),
).Add(m.defStructPointer).Block(
j.Id(m.defFuncReceiver).Op(":=").Op("&").Id(m.defStructName).Values(j.Dict{
j.Id("Base"): j.Id("apis").Dot(DefinitionKindToBaseType[kind]).BlockFunc(
func(g *j.Group) {
switch kind {
case v1beta1.ComponentDefinitionKind, v1beta1.PolicyDefinitionKind, v1beta1.WorkflowStepDefinitionKind:
g.Id("Name").Op(":").Id("name").Op(",")
g.Id("Type").Op(":").Add(typeName).Op(",")
}
}),
}),
j.Return(j.Id(m.defFuncReceiver)),
)
traitType := DefinitionKindToStatement[v1beta1.TraitDefinitionKind]
stepType := DefinitionKindToStatement[v1beta1.WorkflowStepDefinitionKind]
builderDict := j.Dict{}
// For WorkflowStep, we need to nest properties under WorkflowStepBase
if kind == v1beta1.WorkflowStepDefinitionKind {
workflowStepBaseDict := j.Dict{
j.Id("Type"): j.Add(typeName),
j.Id("Properties"): j.Qual("util", "Object2RawExtension").Params(j.Id(m.defFuncReceiver).Dot("Properties")),
j.Id("Name"): j.Id(m.defFuncReceiver).Dot("Base").Dot("Name"),
j.Id("DependsOn"): j.Id(m.defFuncReceiver).Dot("Base").Dot("DependsOn"),
j.Id("Inputs"): j.Id(m.defFuncReceiver).Dot("Base").Dot("Inputs"),
j.Id("Outputs"): j.Id(m.defFuncReceiver).Dot("Base").Dot("Outputs"),
j.Id("If"): j.Id(m.defFuncReceiver).Dot("Base").Dot("If"),
j.Id("Timeout"): j.Id(m.defFuncReceiver).Dot("Base").Dot("Timeout"),
j.Id("Meta"): j.Id(m.defFuncReceiver).Dot("Base").Dot("Meta"),
}
builderDict[j.Id("WorkflowStepBase")] = j.Qual("github.com/kubevela/pkg/apis/oam/v1alpha1", "WorkflowStepBase").Values(workflowStepBaseDict)
builderDict[j.Id("SubSteps")] = j.Id("subSteps")
} else {
builderDict[j.Id("Type")] = j.Add(typeName)
builderDict[j.Id("Properties")] = j.Qual("util", "Object2RawExtension").Params(j.Id(m.defFuncReceiver).Dot("Properties"))
builderDictValues := map[string][]string{
v1beta1.PolicyDefinitionKind: {"Name"},
v1beta1.ComponentDefinitionKind: {"Name", "DependsOn", "Inputs", "Outputs"},
}
for _, v := range builderDictValues[kind] {
builderDict[j.Id(v)] = j.Id(m.defFuncReceiver).Dot("Base").Dot(v)
}
if kind == v1beta1.ComponentDefinitionKind {
builderDict[j.Id("Traits")] = j.Id("traits")
}
}
buildFunc := j.Func().
Params(j.Id(m.defFuncReceiver).Add(m.defStructPointer)).
Id("Build").Params().
Add(DefinitionKindToStatement[kind]).BlockFunc(func(g *j.Group) {
switch kind {
case v1beta1.ComponentDefinitionKind:
g.Add(j.Id("traits").Op(":=").Make(j.Index().Add(traitType), j.Lit(0)))
g.Add(j.For(j.List(j.Id("_"), j.Id("trait")).Op(":=").Range().Id(m.defFuncReceiver).Dot("Base").Dot("Traits")).Block(
j.Id("traits").Op("=").Append(j.Id("traits"), j.Id("trait").Dot("Build").Call()),
))
case v1beta1.WorkflowStepDefinitionKind:
g.Add(j.Id("_subSteps").Op(":=").Make(j.Index().Add(stepType), j.Lit(0)))
g.Add(j.For(j.List(j.Id("_"), j.Id("subStep")).Op(":=").Range().Id(m.defFuncReceiver).Dot("Base").Dot("SubSteps")).Block(
j.Id("_subSteps").Op("=").Append(j.Id("_subSteps"), j.Id("subStep").Dot("Build").Call()),
))
g.Add(j.Id("subSteps").Op(":=").Make(j.Index().Qual("github.com/kubevela/pkg/apis/oam/v1alpha1", "WorkflowStepBase"), j.Lit(0)))
g.Add(j.For(j.List(j.Id("_"), j.Id("_s").Op(":=").Range().Id("_subSteps"))).Block(
j.Id("subSteps").Op("=").Append(j.Id("subSteps"), j.Id("_s").Dot("WorkflowStepBase")),
))
}
g.Add(j.Id("res").Op(":=").Add(DefinitionKindToStatement[kind]).Values(builderDict))
g.Add(j.Return(j.Id("res")))
})
return []*j.Statement{typeConst, initFunc, defStruct, defStructConstructor, buildFunc}
}
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"},
v1beta1.PolicyDefinitionKind: {"Name"},
v1beta1.TraitDefinitionKind: {},
}
// 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.kind != v1beta1.WorkflowStepDefinitionKind || sub {
return func(g *j.Group) {}
}
return func(g *j.Group) {
g.Add(j.Id("subSteps").Op(":=").Make(j.Index().Qual("apis", DefinitionKindToPascal[kind]), j.Lit(0)))
g.Add(
j.For(
j.List(j.Id("_"), j.Id("_s")).Op(":=").Range().Id("from").Dot("SubSteps")).Block(
j.List(j.Id("subStep"), j.Err()).Op(":=").Id(m.defFuncReceiver).Dot("FromWorkflowSubStep").Call(j.Id("_s")),
j.If(j.Err().Op("!=").Nil()).Block(
j.Return(j.Nil(), j.Err()),
),
j.Id("subSteps").Op("=").Append(j.Id("subSteps"), j.Id("subStep")),
),
)
}
}
assignSubSteps := func(sub bool) func(g *j.Group) {
if m.kind != v1beta1.WorkflowStepDefinitionKind || sub {
return func(g *j.Group) {}
}
return func(g *j.Group) {
g.Add(j.Id(m.defFuncReceiver).Dot("Base").Dot("SubSteps").Op("=").Id("subSteps"))
}
}
fromFuncRsv := func(sub bool) *j.Statement {
funcName := "From" + DefinitionKindToPascal[kind]
params := DefinitionKindToStatement[kind]
if sub {
funcName = "FromWorkflowSubStep"
params = j.Qual("github.com/kubevela/pkg/apis/oam/v1alpha1", "WorkflowStepBase")
}
return j.Func().
Params(j.Id(m.defFuncReceiver).Add(m.defStructPointer)).
Id(funcName).
Params(j.Id("from").Add(params)).Params(j.Add(m.defStructPointer), j.Error()).
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.Id("trait")),
j.If(j.Err().Op("!=").Nil()).Block(
j.Return(j.Nil(), j.Err()),
),
j.Id(m.defFuncReceiver).Dot("Base").Dot("Traits").Op("=").Append(j.Id(m.defFuncReceiver).Dot("Base").Dot("Traits"), j.Id("_t")),
))
}
g.Add(j.Var().Id("properties").Id(m.specNameInPascalCase))
g.Add(
j.If(j.Id("from").Dot("Properties").Op("!=").Nil()).Block(
j.Err().Op(":=").Qual("json", "Unmarshal").Call(j.Id("from").Dot("Properties").Dot("Raw"), j.Op("&").Id("properties")),
j.If(j.Err().Op("!=").Nil()).Block(
j.Return(j.Nil(), j.Err()),
),
),
)
getSubSteps(sub)(g)
for _, prop := range kindBaseProperties[kind] {
g.Add(j.Id(m.defFuncReceiver).Dot("Base").Dot(prop).Op("=").Id("from").Dot(prop))
}
g.Add(j.Id(m.defFuncReceiver).Dot("Base").Dot("Type").Op("=").Id(m.typeVarName))
g.Add(j.Id(m.defFuncReceiver).Dot("Properties").Op("=").Id("properties"))
assignSubSteps(sub)(g)
g.Add(j.Return(j.Id(m.defFuncReceiver), j.Nil()))
},
)
}
// fromFunc is like fromFuncRsv but not having function receiver, returning an internal presentation
fromFunc := j.Func().
Id("From"+DefinitionKindToPascal[kind]).
Params(j.Id("from").Add(DefinitionKindToStatement[kind])).Params(j.Qual("apis", DefinitionKindToPascal[kind]), j.Error()).
Block(
j.Id(m.defFuncReceiver).Op(":=").Op("&").Id(m.defStructName).Values(j.Dict{}),
j.Return(j.Id(m.defFuncReceiver).Dot("From"+DefinitionKindToPascal[kind]).Call(j.Id("from"))),
)
fromSubFunc := j.Func().Id("FromWorkflowSubStep").
Params(j.Id("from").Qual("github.com/kubevela/pkg/apis/oam/v1alpha1", "WorkflowStepBase")).Params(j.Qual("apis", DefinitionKindToPascal[kind]), j.Error()).
Block(
j.Id(m.defFuncReceiver).Op(":=").Op("&").Id(m.defStructName).Values(j.Dict{}),
j.Return(j.Id(m.defFuncReceiver).Dot("FromWorkflowSubStep").Call(j.Id("from"))),
)
res := []*j.Statement{fromFuncRsv(false), fromFunc}
if m.kind == v1beta1.WorkflowStepDefinitionKind {
res = append(res, fromFuncRsv(true), fromSubFunc)
}
return res
}
// genDedicatedFunc generate functions for definition kinds
func (m *GoDefModifier) genDedicatedFunc() []*j.Statement {
switch m.kind {
case v1beta1.ComponentDefinitionKind:
setTraitFunc := j.Func().
Params(j.Id(m.defFuncReceiver).Add(m.defStructPointer)).
Id("SetTraits").
Params(j.Id("traits").Op("...").Qual("apis", "Trait")).
Add(m.defStructPointer).
Block(
j.For(j.List(j.Id("_"), j.Id("addTrait")).Op(":=").Range().Id("traits")).Block(
j.Id("found").Op(":=").False(),
j.For(j.List(j.Id("i"), j.Id("_t")).Op(":=").Range().Id(m.defFuncReceiver).Dot("Base").Dot("Traits")).Block(
j.If(j.Id("_t").Dot("DefType").Call().Op("==").Id("addTrait").Dot("DefType").Call()).Block(
j.Id(m.defFuncReceiver).Dot("Base").Dot("Traits").Index(j.Id("i")).Op("=").Id("addTrait"),
j.Id("found").Op("=").True(),
j.Break(),
),
),
j.If(j.Op("!").Id("found")).Block(
j.Id(m.defFuncReceiver).Dot("Base").Dot("Traits").Op("=").Append(j.Id(m.defFuncReceiver).Dot("Base").Dot("Traits"), j.Id("addTrait")),
),
),
j.Return(j.Id(m.defFuncReceiver)),
)
getTraitFunc := j.Func().
Params(j.Id(m.defFuncReceiver).Add(m.defStructPointer)).
Id("GetTrait").
Params(j.Id("typ").String()).
Params(j.Qual("apis", "Trait")).
Block(
j.For(j.List(j.Id("_"), j.Id("_t")).Op(":=").Range().Id(m.defFuncReceiver).Dot("Base").Dot("Traits")).Block(
j.If(j.Id("_t").Dot("DefType").Call().Op("==").Id("typ")).Block(
j.Return(j.Id("_t")),
),
),
j.Return(j.Nil()),
)
getAllTraitFunc := j.Func().
Params(j.Id(m.defFuncReceiver).Add(m.defStructPointer)).
Id("GetAllTraits").
Params().
Params(j.Index().Qual("apis", "Trait")).
Block(
j.Return(j.Id(m.defFuncReceiver).Dot("Base").Dot("Traits")),
)
return []*j.Statement{setTraitFunc, getTraitFunc, getAllTraitFunc}
case v1beta1.WorkflowStepDefinitionKind:
}
return nil
}
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.kind {
case v1beta1.ComponentDefinitionKind, v1beta1.WorkflowStepDefinitionKind, v1beta1.PolicyDefinitionKind:
return []*j.Statement{nameFunc, typeFunc}
case v1beta1.TraitDefinitionKind:
return []*j.Statement{typeFunc}
}
return nil
}
func (m *GoDefModifier) genUnmarshalFunc() []*j.Statement {
return []*j.Statement{j.Null()}
}
func (m *GoDefModifier) genBaseSetterFunc() []*j.Statement {
baseFuncArgs := map[string][]struct {
funcName string
argName string
argType *j.Statement
dst *j.Statement
isAppend bool
}{
v1beta1.ComponentDefinitionKind: {
{funcName: "DependsOn", argName: "dependsOn", argType: j.Index().String()},
{funcName: "Inputs", argName: "input", argType: j.Qual("github.com/kubevela/pkg/apis/oam/v1alpha1", "StepInputs")},
{funcName: "Outputs", argName: "output", argType: j.Qual("github.com/kubevela/pkg/apis/oam/v1alpha1", "StepOutputs")},
{funcName: "AddDependsOn", argName: "dependsOn", argType: j.String(), isAppend: true, dst: j.Dot("DependsOn")},
},
v1beta1.WorkflowStepDefinitionKind: {
{funcName: "If", argName: "_if", argType: j.String()},
{funcName: "Alias", argName: "alias", argType: j.String(), dst: j.Dot("Meta").Dot("Alias")},
{funcName: "Timeout", argName: "timeout", argType: j.String()},
{funcName: "DependsOn", argName: "dependsOn", argType: j.Index().String()},
{funcName: "Inputs", argName: "input", argType: j.Qual("github.com/kubevela/pkg/apis/oam/v1alpha1", "StepInputs")},
{funcName: "Outputs", argName: "output", argType: j.Qual("github.com/kubevela/pkg/apis/oam/v1alpha1", "StepOutputs")},
},
}
baseFuncs := make([]*j.Statement, 0)
for _, fn := range baseFuncArgs[m.kind] {
if fn.dst == nil {
fn.dst = j.Dot(fn.funcName)
}
f := j.Func().
Params(j.Id(m.defFuncReceiver).Add(m.defStructPointer)).
Id(fn.funcName).
Params(j.Id(fn.argName).Add(fn.argType)).
Add(m.defStructPointer).
BlockFunc(func(g *j.Group) {
field := j.Id(m.defFuncReceiver).Dot("Base").Add(fn.dst)
if fn.isAppend {
g.Add(field.Clone().Op("=").Append(field.Clone(), j.Id(fn.argName)))
} else {
g.Add(field.Clone().Op("=").Id(fn.argName))
}
g.Add(j.Return(j.Id(m.defFuncReceiver)))
})
baseFuncs = append(baseFuncs, f)
}
return baseFuncs
}
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")
return j.Func().
Params(j.Id(m.defFuncReceiver).Add(m.defStructPointer)).
Id("AddSubStep").
Params(j.Id("subStep").Qual("apis", "WorkflowStep")).
Add(m.defStructPointer).
Block(
subList.Clone().Op("=").Append(subList.Clone(), j.Id("subStep")),
j.Return(j.Id(m.defFuncReceiver)),
)
}
// exportMethods will export methods from definition spec struct to definition struct
func (m *GoDefModifier) exportMethods() error {
fileLoc := path.Join(m.defDir, m.nameInSnakeCase+".go")
// nolint:gosec
file, err := os.ReadFile(fileLoc)
if err != nil {
return err
}
var fileStr = string(file)
from := fmt.Sprintf("*%sSpec", m.nameInPascalCase)
to := fmt.Sprintf("*%s", m.defStructName)
// replace all the function receiver but not below functions
// New{m.nameInPascalCase}SpecWith
// New{m.nameInPascalCase}Spec
fileStr = regexp.MustCompile(fmt.Sprintf(`func \(o \%s\) ([()\[\]{}\w ]+)\%s([ )])`, from, from)).ReplaceAllString(fileStr, fmt.Sprintf("func (o %s) $1%s$2", to, to))
fileStr = strings.ReplaceAll(fileStr, "func (o "+from, "func (o "+to)
// replace all function receiver in function body
// o.foo -> o.Properties.foo
// o.Base keeps the same
// seek the MarshalJSON function, replace functions before it
parts := strings.SplitN(fileStr, "MarshalJSON", 2)
if len(parts) != 2 {
return fmt.Errorf("can't find MarshalJSON function")
}
parts[0] = strings.ReplaceAll(parts[0], "o.", "o.Properties.")
parts[0] = strings.ReplaceAll(parts[0], "o.Properties.Base", "o.Base")
fileStr = parts[0] + "MarshalJSON" + parts[1]
return os.WriteFile(fileLoc, []byte(fileStr), 0600)
}
func (m *GoDefModifier) addValidateTraits() error {
if m.kind != v1beta1.ComponentDefinitionKind {
return nil
}
fileLoc := path.Join(m.defDir, m.nameInSnakeCase+".go")
// nolint:gosec
file, err := os.ReadFile(fileLoc)
if err != nil {
return err
}
var fileStr = string(file)
buf := bytes.Buffer{}
err = j.For(j.List(j.Id("i"), j.Id("v").Op(":=").Range().Id("o").Dot("Base").Dot("Traits"))).Block(
j.If(j.Id("err").Op(":=").Id("v").Dot("Validate").Call().Op(";").Id("err").Op("!=").Nil()).Block(
j.Return(j.Qual("fmt", "Errorf").Call(j.Lit("traits[%d] %s in %s component is invalid: %w"), j.Id("i"), j.Id("v").Dot("DefType").Call(), j.Id(m.typeVarName), j.Id("err"))),
),
).Render(&buf)
if err != nil {
return err
}
// add validate trait part in Validate function
exp := regexp.MustCompile(`Validate\(\)((.|\n)*?)(return nil)`)
s := buf.String()
fileStr = exp.ReplaceAllString(fileStr, fmt.Sprintf("Validate()$1\n%s\n$3", s))
return os.WriteFile(fileLoc, []byte(fileStr), 0600)
}
func (m *GoModuleModifier) format() error {
// check if gofmt is installed
// todo (chivalryq): support go mod tidy for sub-module
formatters := []string{"gofmt", "goimports"}
var formatterPaths []string
allFormattersInstalled := true
for _, formatter := range formatters {
p, err := exec.LookPath(formatter)
if err != nil {
allFormattersInstalled = false
break
}
formatterPaths = append(formatterPaths, p)
}
if allFormattersInstalled {
for _, fmter := range formatterPaths {
if m.Verbose {
fmt.Printf("Use %s to format code\n", fmter)
}
// nolint:gosec
cmd := exec.Command(fmter, "-w", m.apiDir)
output, err := cmd.CombinedOutput()
if err != nil {
return errors.Wrap(err, string(output))
}
}
return nil
}
// fallback to use go lib
if m.Verbose {
fmt.Println("At least one of linters is not installed, use go/format lib to format code")
}
// format all .go files
return filepath.Walk(m.apiDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
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", path)
}
err = os.WriteFile(path, formatted, 0600)
if err != nil {
return errors.Wrapf(err, "write file %s", path)
}
return nil
})
}