/* 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("v1beta1", "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{ // all definition have type and properties j.Id("Type"): j.Add(typeName), 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"}, v1beta1.WorkflowStepDefinitionKind: {"Name", "DependsOn", "Inputs", "Outputs", "If", "Timeout", "Meta"}, } for _, v := range builderDictValues[kind] { builderDict[j.Id(v)] = j.Id(m.defFuncReceiver).Dot("Base").Dot(v) } switch kind { case v1beta1.ComponentDefinitionKind: builderDict[j.Id("Traits")] = j.Id("traits") case v1beta1.WorkflowStepDefinitionKind: builderDict[j.Id("SubSteps")] = j.Id("subSteps") } 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("common", "WorkflowSubStep"), 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.Qual("common", "WorkflowSubStep").ValuesFunc( func(_g *j.Group) { for _, v := range []string{"Name", "DependsOn", "Inputs", "Outputs", "If", "Timeout", "Meta", "Properties", "Type"} { _g.Add(j.Id(v).Op(":").Id("_s").Dot(v)) } }), )), ) } 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("common", "WorkflowSubStep") } 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("common", "WorkflowSubStep")).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("common", "StepInputs")}, {funcName: "Outputs", argName: "output", argType: j.Qual("common", "StepOutputs")}, {funcName: "AddDependsOn", argName: "dependsOn", argType: j.String(), isAppend: true, dst: j.Dot("DependsOn")}, // TODO: uncomment this after https://github.com/kubevela/workflow/pull/125 is released. // {funcName: "AddInput", argName: "input", argType: Qual("common", "StepInputs"), isAppend: true, dst: "Inputs"}, // {funcName: "AddOutput", argName: "output", argType: Qual("common", "StepOutputs"), isAppend: true, dst: "Outputs"}, }, 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("common", "StepInputs")}, {funcName: "Outputs", argName: "output", argType: j.Qual("common", "StepOutputs")}, // {funcName: "AddInput", argName: "input", argType: Qual("common", "StepInputs"), isAppend: true, dst: "Inputs"}, // {funcName: "AddOutput", argName: "output", argType: Qual("common", "StepOutputs"), isAppend: true, dst: "Outputs"}, }, } 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 }) }