github.com/oam-dev/kubevela@v1.9.11/pkg/definition/gen_sdk/go.go (about)

     1  /*
     2  Copyright 2021 The KubeVela Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package gen_sdk
    18  
    19  import (
    20  	"bytes"
    21  	"fmt"
    22  	"go/format"
    23  	"os"
    24  	"os/exec"
    25  	"path"
    26  	"path/filepath"
    27  	"regexp"
    28  	"strings"
    29  
    30  	j "github.com/dave/jennifer/jen"
    31  	"github.com/ettle/strcase"
    32  	"github.com/pkg/errors"
    33  
    34  	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
    35  	pkgdef "github.com/oam-dev/kubevela/pkg/definition"
    36  )
    37  
    38  // GoProxyEnvKey with the environment variable name that defines the GOPROXY preferences.
    39  const GoProxyEnvKey = "GOPROXY"
    40  
    41  var (
    42  	mainModuleVersionKey langArgKey = "MainModuleVersion"
    43  	goProxyKey           langArgKey = "GoProxy"
    44  
    45  	mainModuleVersion = LangArg{
    46  		Name: mainModuleVersionKey,
    47  		Desc: "The version of main module, it will be used in go get command. For example, tag, commit id, branch name",
    48  		// default hash of main module. This is a commit hash of kubevela-contrib/kubvela-go-sdk. It will be used in go get command.
    49  		Default: "cd431bb25a9a",
    50  	}
    51  	goProxy = LangArg{
    52  		Name:    goProxyKey,
    53  		Desc:    "The proxy for go get/go mod tidy command",
    54  		Default: "",
    55  	}
    56  )
    57  
    58  func init() {
    59  	// Propagate the environment GOPROXY variable to the tests that are executed through external containers.
    60  	goProxy.Default = os.Getenv(GoProxyEnvKey)
    61  	registerLangArg("go", mainModuleVersion, goProxy)
    62  }
    63  
    64  const (
    65  	// PackagePlaceHolder is the package name placeholder
    66  	PackagePlaceHolder = "github.com/kubevela/vela-go-sdk"
    67  )
    68  
    69  var (
    70  	// DefinitionKindToPascal is the map of definition kind to pascal case
    71  	DefinitionKindToPascal = map[string]string{
    72  		v1beta1.ComponentDefinitionKind:    "Component",
    73  		v1beta1.TraitDefinitionKind:        "Trait",
    74  		v1beta1.WorkflowStepDefinitionKind: "WorkflowStep",
    75  		v1beta1.PolicyDefinitionKind:       "Policy",
    76  	}
    77  	// DefinitionKindToBaseType is the map of definition kind to base type
    78  	DefinitionKindToBaseType = map[string]string{
    79  		v1beta1.ComponentDefinitionKind:    "ComponentBase",
    80  		v1beta1.TraitDefinitionKind:        "TraitBase",
    81  		v1beta1.WorkflowStepDefinitionKind: "WorkflowStepBase",
    82  		v1beta1.PolicyDefinitionKind:       "PolicyBase",
    83  	}
    84  	// DefinitionKindToStatement is the map of definition kind to statement
    85  	DefinitionKindToStatement = map[string]*j.Statement{
    86  		v1beta1.ComponentDefinitionKind:    j.Qual("common", "ApplicationComponent"),
    87  		v1beta1.TraitDefinitionKind:        j.Qual("common", "ApplicationTrait"),
    88  		v1beta1.WorkflowStepDefinitionKind: j.Qual("v1beta1", "WorkflowStep"),
    89  		v1beta1.PolicyDefinitionKind:       j.Qual("v1beta1", "AppPolicy"),
    90  	}
    91  )
    92  
    93  // GoDefModifier is the Modifier for golang, modify code for each definition
    94  type GoDefModifier struct {
    95  	*GenMeta
    96  	*goArgs
    97  
    98  	defStructPointer *j.Statement
    99  }
   100  
   101  // GoModuleModifier is the Modifier for golang, modify code for each module which contains multiple definitions
   102  type GoModuleModifier struct {
   103  	*GenMeta
   104  	*goArgs
   105  }
   106  
   107  type goArgs struct {
   108  	apiDir   string
   109  	defDir   string
   110  	utilsDir string
   111  	// def name of different cases
   112  	nameInSnakeCase      string
   113  	nameInPascalCase     string
   114  	specNameInPascalCase string
   115  	typeVarName          string
   116  	defStructName        string
   117  	defFuncReceiver      string
   118  }
   119  
   120  func (a *goArgs) init(m *GenMeta) error {
   121  	var err error
   122  	a.apiDir, err = filepath.Abs(path.Join(m.Output, m.APIDirectory))
   123  	if err != nil {
   124  		return err
   125  	}
   126  	a.defDir = path.Join(a.apiDir, pkgdef.DefinitionKindToType[m.kind], m.name)
   127  	a.utilsDir = path.Join(m.Output, "pkg", "apis", "utils")
   128  	a.nameInSnakeCase = strcase.ToSnake(m.name)
   129  	a.nameInPascalCase = strcase.ToPascal(m.name)
   130  	a.typeVarName = a.nameInPascalCase + "Type"
   131  	a.specNameInPascalCase = a.nameInPascalCase + "Spec"
   132  	a.defStructName = strcase.ToGoPascal(m.name + "-" + pkgdef.DefinitionKindToType[m.kind])
   133  	a.defFuncReceiver = m.name[:1]
   134  	return nil
   135  }
   136  
   137  // Modify implements Modifier
   138  func (m *GoModuleModifier) Modify() error {
   139  	for _, fn := range []func() error{
   140  		m.init,
   141  		m.format,
   142  		m.addSubGoMod,
   143  		m.tidyMainMod,
   144  	} {
   145  		if err := fn(); err != nil {
   146  			return errors.Wrap(err, fnName(fn))
   147  		}
   148  	}
   149  	return nil
   150  }
   151  
   152  func (m *GoModuleModifier) init() error {
   153  	m.goArgs = &goArgs{}
   154  	return m.goArgs.init(m.GenMeta)
   155  }
   156  
   157  // Name the name of modifier
   158  func (m *GoModuleModifier) Name() string {
   159  	return "goModuleModifier"
   160  }
   161  
   162  // Name the name of modifier
   163  func (m *GoDefModifier) Name() string {
   164  	return "GoDefModifier"
   165  }
   166  
   167  // Modify the modification of generated code
   168  func (m *GoDefModifier) Modify() error {
   169  	for _, fn := range []func() error{
   170  		m.init,
   171  		m.clean,
   172  		m.moveUtils,
   173  		m.modifyDefs,
   174  		m.addDefAPI,
   175  		m.addValidateTraits,
   176  		m.exportMethods,
   177  	} {
   178  		if err := fn(); err != nil {
   179  			return errors.Wrap(err, fnName(fn))
   180  		}
   181  	}
   182  	return nil
   183  }
   184  
   185  func (m *GoDefModifier) init() error {
   186  	m.goArgs = &goArgs{}
   187  	err := m.goArgs.init(m.GenMeta)
   188  	if err != nil {
   189  		return err
   190  	}
   191  
   192  	m.defStructPointer = j.Op("*").Id(m.defStructName)
   193  
   194  	err = os.MkdirAll(m.utilsDir, 0750)
   195  	return err
   196  }
   197  
   198  func (m *GoDefModifier) clean() error {
   199  	err := os.RemoveAll(path.Join(m.defDir, ".openapi-generator"))
   200  	if err != nil {
   201  		return err
   202  	}
   203  	err = os.RemoveAll(path.Join(m.defDir, "api"))
   204  	if err != nil {
   205  		return err
   206  	}
   207  
   208  	files, _ := os.ReadDir(m.defDir)
   209  	for _, f := range files {
   210  		dst := strings.TrimPrefix(f.Name(), "model_")
   211  		if dst == m.nameInSnakeCase+"_spec.go" {
   212  			dst = m.nameInSnakeCase + ".go"
   213  		}
   214  		err = os.Rename(path.Join(m.defDir, f.Name()), path.Join(m.defDir, dst))
   215  		if err != nil {
   216  			return err
   217  		}
   218  	}
   219  	return nil
   220  
   221  }
   222  
   223  // addSubGoMod will add a go.mod and go.sum in the api directory if user mark that the api is a submodule
   224  func (m *GoModuleModifier) addSubGoMod() error {
   225  	if !m.IsSubModule {
   226  		return nil
   227  	}
   228  	files := map[string]string{
   229  		"go.mod_": "go.mod",
   230  		"go.sum":  "go.sum",
   231  	}
   232  	for src, dst := range files {
   233  		srcContent, err := Scaffold.ReadFile(path.Join(ScaffoldDir, "go", src))
   234  		if err != nil {
   235  			return errors.Wrap(err, "read "+src)
   236  		}
   237  		subModuleName := strings.TrimSuffix(fmt.Sprintf("%s/%s", m.Package, m.APIDirectory), "/")
   238  		srcContent = bytes.ReplaceAll(srcContent, []byte("module "+PackagePlaceHolder), []byte("module "+subModuleName))
   239  		srcContent = bytes.ReplaceAll(srcContent, []byte("// require "+PackagePlaceHolder), []byte("require "+m.Package))
   240  
   241  		err = os.WriteFile(path.Join(m.apiDir, dst), srcContent, 0600)
   242  		if err != nil {
   243  			return errors.Wrap(err, "write "+dst)
   244  		}
   245  	}
   246  
   247  	cmds := make([]*exec.Cmd, 0)
   248  	if m.LangArgs.Get(mainModuleVersionKey) != mainModuleVersion.Default {
   249  		// nolint:gosec
   250  		cmds = append(cmds, exec.Command("docker", "run",
   251  			"--rm",
   252  			"-v", m.apiDir+":/api",
   253  			"-w", "/api",
   254  			"golang:1.19-alpine",
   255  			"go", "get", fmt.Sprintf("%s@%s", m.Package, m.LangArgs.Get(mainModuleVersionKey)),
   256  		))
   257  	}
   258  	// nolint:gosec
   259  	cmds = append(cmds, exec.Command("docker", "run",
   260  		"--rm",
   261  		"-v", m.apiDir+":/api",
   262  		"-w", "/api",
   263  		"--env", "GOPROXY="+m.LangArgs.Get(goProxyKey),
   264  		"golang:1.19-alpine",
   265  		"go", "mod", "tidy",
   266  	))
   267  	for _, cmd := range cmds {
   268  		if m.Verbose {
   269  			fmt.Println(cmd.String())
   270  			cmd.Stdout = os.Stdout
   271  			cmd.Stderr = os.Stderr
   272  		}
   273  
   274  		err := cmd.Run()
   275  		if err != nil {
   276  			return errors.Wrapf(err, "fail to run command %s", cmd.String())
   277  		}
   278  	}
   279  	return nil
   280  }
   281  
   282  // tidyMainMod will run go mod tidy in the main module
   283  func (m *GoModuleModifier) tidyMainMod() error {
   284  	if !m.InitSDK {
   285  		return nil
   286  	}
   287  	outDir, err := filepath.Abs(m.GenMeta.Output)
   288  	if err != nil {
   289  		return err
   290  	}
   291  	// nolint:gosec
   292  	cmd := exec.Command("docker", "run",
   293  		"--rm",
   294  		"-v", outDir+":/api",
   295  		"-w", "/api",
   296  		"golang:1.19-alpine",
   297  		"go", "mod", "tidy",
   298  	)
   299  	if m.Verbose {
   300  		fmt.Println(cmd.String())
   301  		cmd.Stdout = os.Stdout
   302  		cmd.Stderr = os.Stderr
   303  	}
   304  	return cmd.Run()
   305  }
   306  
   307  // read all files in definition directory,
   308  // 1. replace the Nullable* Struct
   309  // 2. replace the package name
   310  func (m *GoDefModifier) modifyDefs() error {
   311  	changeNullableType := func(b []byte) []byte {
   312  		return regexp.MustCompile("Nullable(String|(Float|Int)(32|64)|Bool)").ReplaceAll(b, []byte("utils.Nullable$1"))
   313  	}
   314  
   315  	files, err := os.ReadDir(m.defDir)
   316  	defHandleFunc := []byteHandler{
   317  		m.packageFunc,
   318  		changeNullableType,
   319  	}
   320  	if err != nil {
   321  		return err
   322  	}
   323  	for _, f := range files {
   324  		loc := path.Join(m.defDir, f.Name())
   325  		// nolint:gosec
   326  		b, err := os.ReadFile(loc)
   327  		if err != nil {
   328  			return errors.Wrapf(err, "read file")
   329  		}
   330  		for _, h := range defHandleFunc {
   331  			b = h(b)
   332  		}
   333  
   334  		_ = os.WriteFile(loc, b, 0600)
   335  	}
   336  	return nil
   337  }
   338  
   339  func (m *GoDefModifier) moveUtils() error {
   340  	// Adjust the generated files and code
   341  	err := os.Rename(path.Join(m.defDir, "utils.go"), path.Join(m.utilsDir, "utils.go"))
   342  	if err != nil {
   343  		return err
   344  	}
   345  	utilsFile := path.Join(m.utilsDir, "utils.go")
   346  
   347  	// nolint:gosec
   348  	utilsBytes, err := os.ReadFile(utilsFile)
   349  	if err != nil {
   350  		return err
   351  	}
   352  	utilsBytes = bytes.Replace(utilsBytes, []byte(fmt.Sprintf("package %s", strcase.ToSnake(m.name))), []byte("package utils"), 1)
   353  	utilsBytes = bytes.ReplaceAll(utilsBytes, []byte("isNil"), []byte("IsNil"))
   354  	err = os.WriteFile(utilsFile, utilsBytes, 0600)
   355  	if err != nil {
   356  		return err
   357  	}
   358  	return nil
   359  }
   360  
   361  // addDefAPI will add component/trait/workflowstep/policy Object to the api
   362  func (m *GoDefModifier) addDefAPI() error {
   363  	file, err := os.OpenFile(path.Join(m.defDir, m.nameInSnakeCase+".go"), os.O_APPEND|os.O_WRONLY, 0600)
   364  	if err != nil {
   365  		return err
   366  	}
   367  	defer func() {
   368  		_ = file.Close()
   369  	}()
   370  	renderGroup := make([]*j.Statement, 0)
   371  	renderGroup = append(renderGroup, m.genCommonFunc()...)
   372  	renderGroup = append(renderGroup, m.genFromFunc()...)
   373  	renderGroup = append(renderGroup, m.genDedicatedFunc()...)
   374  	renderGroup = append(renderGroup, m.genNameTypeFunc()...)
   375  	renderGroup = append(renderGroup, m.genUnmarshalFunc()...)
   376  	renderGroup = append(renderGroup, m.genBaseSetterFunc()...)
   377  	renderGroup = append(renderGroup, m.genAddSubStepFunc())
   378  
   379  	buf := new(bytes.Buffer)
   380  	for _, r := range renderGroup {
   381  		// write content at the end of file
   382  		err := r.Render(buf)
   383  		buf.WriteString("\n\n")
   384  		if err != nil {
   385  			return errors.Wrap(err, "render code")
   386  		}
   387  	}
   388  	_, err = file.Write(buf.Bytes())
   389  	if err != nil {
   390  		return errors.Wrap(err, "append content to file")
   391  	}
   392  	return nil
   393  }
   394  
   395  func (m *GoDefModifier) genCommonFunc() []*j.Statement {
   396  	kind := m.kind
   397  	typeName := j.Id(m.nameInPascalCase + "Type")
   398  	typeConst := j.Const().Add(typeName).Op("=").Lit(m.name)
   399  	j.Op("=").Lit(m.name)
   400  	defStruct := j.Type().Id(m.defStructName).Struct(
   401  		j.Id("Base").Id("apis").Dot(DefinitionKindToBaseType[kind]),
   402  		j.Id("Properties").Id(m.specNameInPascalCase),
   403  	)
   404  
   405  	initFunc := j.Func().Id("init").Params().BlockFunc(func(g *j.Group) {
   406  		g.Add(j.Qual("sdkcommon", "Register"+DefinitionKindToPascal[kind]).Call(j.Add(typeName), j.Id("From"+DefinitionKindToPascal[kind])))
   407  		if kind == v1beta1.WorkflowStepDefinitionKind {
   408  			g.Add(j.Qual("sdkcommon", "RegisterWorkflowSubStep").Call(j.Add(typeName), j.Id("FromWorkflowSubStep")))
   409  		}
   410  	},
   411  	)
   412  
   413  	defStructConstructor := j.Func().Id(m.nameInPascalCase).Params(
   414  		j.Do(func(s *j.Statement) {
   415  			switch kind {
   416  			case v1beta1.ComponentDefinitionKind, v1beta1.PolicyDefinitionKind, v1beta1.WorkflowStepDefinitionKind:
   417  				s.Id("name").String()
   418  			}
   419  		}),
   420  	).Add(m.defStructPointer).Block(
   421  		j.Id(m.defFuncReceiver).Op(":=").Op("&").Id(m.defStructName).Values(j.Dict{
   422  			j.Id("Base"): j.Id("apis").Dot(DefinitionKindToBaseType[kind]).BlockFunc(
   423  				func(g *j.Group) {
   424  					switch kind {
   425  					case v1beta1.ComponentDefinitionKind, v1beta1.PolicyDefinitionKind, v1beta1.WorkflowStepDefinitionKind:
   426  						g.Id("Name").Op(":").Id("name").Op(",")
   427  						g.Id("Type").Op(":").Add(typeName).Op(",")
   428  					}
   429  				}),
   430  		}),
   431  		j.Return(j.Id(m.defFuncReceiver)),
   432  	)
   433  	traitType := DefinitionKindToStatement[v1beta1.TraitDefinitionKind]
   434  	stepType := DefinitionKindToStatement[v1beta1.WorkflowStepDefinitionKind]
   435  	builderDict := j.Dict{
   436  		// all definition have type and properties
   437  		j.Id("Type"):       j.Add(typeName),
   438  		j.Id("Properties"): j.Qual("util", "Object2RawExtension").Params(j.Id(m.defFuncReceiver).Dot("Properties")),
   439  	}
   440  	builderDictValues := map[string][]string{
   441  		v1beta1.PolicyDefinitionKind:       {"Name"},
   442  		v1beta1.ComponentDefinitionKind:    {"Name", "DependsOn", "Inputs", "Outputs"},
   443  		v1beta1.WorkflowStepDefinitionKind: {"Name", "DependsOn", "Inputs", "Outputs", "If", "Timeout", "Meta"},
   444  	}
   445  	for _, v := range builderDictValues[kind] {
   446  		builderDict[j.Id(v)] = j.Id(m.defFuncReceiver).Dot("Base").Dot(v)
   447  	}
   448  	switch kind {
   449  	case v1beta1.ComponentDefinitionKind:
   450  		builderDict[j.Id("Traits")] = j.Id("traits")
   451  	case v1beta1.WorkflowStepDefinitionKind:
   452  		builderDict[j.Id("SubSteps")] = j.Id("subSteps")
   453  	}
   454  	buildFunc := j.Func().
   455  		Params(j.Id(m.defFuncReceiver).Add(m.defStructPointer)).
   456  		Id("Build").Params().
   457  		Add(DefinitionKindToStatement[kind]).BlockFunc(func(g *j.Group) {
   458  		switch kind {
   459  		case v1beta1.ComponentDefinitionKind:
   460  			g.Add(j.Id("traits").Op(":=").Make(j.Index().Add(traitType), j.Lit(0)))
   461  			g.Add(j.For(j.List(j.Id("_"), j.Id("trait")).Op(":=").Range().Id(m.defFuncReceiver).Dot("Base").Dot("Traits")).Block(
   462  				j.Id("traits").Op("=").Append(j.Id("traits"), j.Id("trait").Dot("Build").Call()),
   463  			))
   464  		case v1beta1.WorkflowStepDefinitionKind:
   465  			g.Add(j.Id("_subSteps").Op(":=").Make(j.Index().Add(stepType), j.Lit(0)))
   466  			g.Add(j.For(j.List(j.Id("_"), j.Id("subStep")).Op(":=").Range().Id(m.defFuncReceiver).Dot("Base").Dot("SubSteps")).Block(
   467  				j.Id("_subSteps").Op("=").Append(j.Id("_subSteps"), j.Id("subStep").Dot("Build").Call()),
   468  			))
   469  			g.Add(j.Id("subSteps").Op(":=").Make(j.Index().Qual("common", "WorkflowSubStep"), j.Lit(0)))
   470  			g.Add(j.For(j.List(j.Id("_"), j.Id("_s").Op(":=").Range().Id("_subSteps"))).Block(
   471  				j.Id("subSteps").Op("=").Append(j.Id("subSteps"), j.Qual("common", "WorkflowSubStep").ValuesFunc(
   472  					func(_g *j.Group) {
   473  						for _, v := range []string{"Name", "DependsOn", "Inputs", "Outputs", "If", "Timeout", "Meta", "Properties", "Type"} {
   474  							_g.Add(j.Id(v).Op(":").Id("_s").Dot(v))
   475  						}
   476  					}),
   477  				)),
   478  			)
   479  		}
   480  		g.Add(j.Id("res").Op(":=").Add(DefinitionKindToStatement[kind]).Values(builderDict))
   481  		g.Add(j.Return(j.Id("res")))
   482  	})
   483  
   484  	return []*j.Statement{typeConst, initFunc, defStruct, defStructConstructor, buildFunc}
   485  }
   486  
   487  func (m *GoDefModifier) genFromFunc() []*j.Statement {
   488  	kind := m.kind
   489  	kindBaseProperties := map[string][]string{
   490  		v1beta1.ComponentDefinitionKind:    {"Name", "DependsOn", "Inputs", "Outputs"},
   491  		v1beta1.WorkflowStepDefinitionKind: {"Name", "DependsOn", "Inputs", "Outputs", "If", "Timeout", "Meta"},
   492  		v1beta1.PolicyDefinitionKind:       {"Name"},
   493  		v1beta1.TraitDefinitionKind:        {},
   494  	}
   495  
   496  	// fromFuncRsv means build from a part of K8s Object (e.g. v1beta1.Application.spec.component[*] to internal presentation (e.g. Component)
   497  	// fromFuncRsv will have a function receiver
   498  	getSubSteps := func(sub bool) func(g *j.Group) {
   499  		if m.kind != v1beta1.WorkflowStepDefinitionKind || sub {
   500  			return func(g *j.Group) {}
   501  		}
   502  		return func(g *j.Group) {
   503  			g.Add(j.Id("subSteps").Op(":=").Make(j.Index().Qual("apis", DefinitionKindToPascal[kind]), j.Lit(0)))
   504  			g.Add(
   505  				j.For(
   506  					j.List(j.Id("_"), j.Id("_s")).Op(":=").Range().Id("from").Dot("SubSteps")).Block(
   507  					j.List(j.Id("subStep"), j.Err()).Op(":=").Id(m.defFuncReceiver).Dot("FromWorkflowSubStep").Call(j.Id("_s")),
   508  					j.If(j.Err().Op("!=").Nil()).Block(
   509  						j.Return(j.Nil(), j.Err()),
   510  					),
   511  					j.Id("subSteps").Op("=").Append(j.Id("subSteps"), j.Id("subStep")),
   512  				),
   513  			)
   514  		}
   515  	}
   516  	assignSubSteps := func(sub bool) func(g *j.Group) {
   517  		if m.kind != v1beta1.WorkflowStepDefinitionKind || sub {
   518  			return func(g *j.Group) {}
   519  		}
   520  		return func(g *j.Group) {
   521  			g.Add(j.Id(m.defFuncReceiver).Dot("Base").Dot("SubSteps").Op("=").Id("subSteps"))
   522  		}
   523  	}
   524  	fromFuncRsv := func(sub bool) *j.Statement {
   525  		funcName := "From" + DefinitionKindToPascal[kind]
   526  		params := DefinitionKindToStatement[kind]
   527  		if sub {
   528  			funcName = "FromWorkflowSubStep"
   529  			params = j.Qual("common", "WorkflowSubStep")
   530  		}
   531  		return j.Func().
   532  			Params(j.Id(m.defFuncReceiver).Add(m.defStructPointer)).
   533  			Id(funcName).
   534  			Params(j.Id("from").Add(params)).Params(j.Add(m.defStructPointer), j.Error()).
   535  			BlockFunc(func(g *j.Group) {
   536  				if kind == v1beta1.ComponentDefinitionKind {
   537  					g.Add(j.For(j.List(j.Id("_"), j.Id("trait")).Op(":=").Range().Id("from").Dot("Traits")).Block(
   538  						j.List(j.Id("_t"), j.Err()).Op(":=").Qual("sdkcommon", "FromTrait").Call(j.Id("trait")),
   539  						j.If(j.Err().Op("!=").Nil()).Block(
   540  							j.Return(j.Nil(), j.Err()),
   541  						),
   542  						j.Id(m.defFuncReceiver).Dot("Base").Dot("Traits").Op("=").Append(j.Id(m.defFuncReceiver).Dot("Base").Dot("Traits"), j.Id("_t")),
   543  					))
   544  				}
   545  				g.Add(j.Var().Id("properties").Id(m.specNameInPascalCase))
   546  				g.Add(
   547  					j.If(j.Id("from").Dot("Properties").Op("!=").Nil()).Block(
   548  						j.Err().Op(":=").Qual("json", "Unmarshal").Call(j.Id("from").Dot("Properties").Dot("Raw"), j.Op("&").Id("properties")),
   549  						j.If(j.Err().Op("!=").Nil()).Block(
   550  							j.Return(j.Nil(), j.Err()),
   551  						),
   552  					),
   553  				)
   554  				getSubSteps(sub)(g)
   555  
   556  				for _, prop := range kindBaseProperties[kind] {
   557  					g.Add(j.Id(m.defFuncReceiver).Dot("Base").Dot(prop).Op("=").Id("from").Dot(prop))
   558  				}
   559  				g.Add(j.Id(m.defFuncReceiver).Dot("Base").Dot("Type").Op("=").Id(m.typeVarName))
   560  				g.Add(j.Id(m.defFuncReceiver).Dot("Properties").Op("=").Id("properties"))
   561  
   562  				assignSubSteps(sub)(g)
   563  				g.Add(j.Return(j.Id(m.defFuncReceiver), j.Nil()))
   564  			},
   565  			)
   566  	}
   567  
   568  	// fromFunc is like fromFuncRsv but not having function receiver, returning an internal presentation
   569  	fromFunc := j.Func().
   570  		Id("From"+DefinitionKindToPascal[kind]).
   571  		Params(j.Id("from").Add(DefinitionKindToStatement[kind])).Params(j.Qual("apis", DefinitionKindToPascal[kind]), j.Error()).
   572  		Block(
   573  			j.Id(m.defFuncReceiver).Op(":=").Op("&").Id(m.defStructName).Values(j.Dict{}),
   574  			j.Return(j.Id(m.defFuncReceiver).Dot("From"+DefinitionKindToPascal[kind]).Call(j.Id("from"))),
   575  		)
   576  	fromSubFunc := j.Func().Id("FromWorkflowSubStep").
   577  		Params(j.Id("from").Qual("common", "WorkflowSubStep")).Params(j.Qual("apis", DefinitionKindToPascal[kind]), j.Error()).
   578  		Block(
   579  			j.Id(m.defFuncReceiver).Op(":=").Op("&").Id(m.defStructName).Values(j.Dict{}),
   580  			j.Return(j.Id(m.defFuncReceiver).Dot("FromWorkflowSubStep").Call(j.Id("from"))),
   581  		)
   582  
   583  	res := []*j.Statement{fromFuncRsv(false), fromFunc}
   584  	if m.kind == v1beta1.WorkflowStepDefinitionKind {
   585  		res = append(res, fromFuncRsv(true), fromSubFunc)
   586  	}
   587  	return res
   588  }
   589  
   590  // genDedicatedFunc generate functions for definition kinds
   591  func (m *GoDefModifier) genDedicatedFunc() []*j.Statement {
   592  	switch m.kind {
   593  	case v1beta1.ComponentDefinitionKind:
   594  		setTraitFunc := j.Func().
   595  			Params(j.Id(m.defFuncReceiver).Add(m.defStructPointer)).
   596  			Id("SetTraits").
   597  			Params(j.Id("traits").Op("...").Qual("apis", "Trait")).
   598  			Add(m.defStructPointer).
   599  			Block(
   600  				j.For(j.List(j.Id("_"), j.Id("addTrait")).Op(":=").Range().Id("traits")).Block(
   601  					j.Id("found").Op(":=").False(),
   602  					j.For(j.List(j.Id("i"), j.Id("_t")).Op(":=").Range().Id(m.defFuncReceiver).Dot("Base").Dot("Traits")).Block(
   603  						j.If(j.Id("_t").Dot("DefType").Call().Op("==").Id("addTrait").Dot("DefType").Call()).Block(
   604  							j.Id(m.defFuncReceiver).Dot("Base").Dot("Traits").Index(j.Id("i")).Op("=").Id("addTrait"),
   605  							j.Id("found").Op("=").True(),
   606  							j.Break(),
   607  						),
   608  					),
   609  					j.If(j.Op("!").Id("found")).Block(
   610  						j.Id(m.defFuncReceiver).Dot("Base").Dot("Traits").Op("=").Append(j.Id(m.defFuncReceiver).Dot("Base").Dot("Traits"), j.Id("addTrait")),
   611  					),
   612  				),
   613  				j.Return(j.Id(m.defFuncReceiver)),
   614  			)
   615  		getTraitFunc := j.Func().
   616  			Params(j.Id(m.defFuncReceiver).Add(m.defStructPointer)).
   617  			Id("GetTrait").
   618  			Params(j.Id("typ").String()).
   619  			Params(j.Qual("apis", "Trait")).
   620  			Block(
   621  				j.For(j.List(j.Id("_"), j.Id("_t")).Op(":=").Range().Id(m.defFuncReceiver).Dot("Base").Dot("Traits")).Block(
   622  					j.If(j.Id("_t").Dot("DefType").Call().Op("==").Id("typ")).Block(
   623  						j.Return(j.Id("_t")),
   624  					),
   625  				),
   626  				j.Return(j.Nil()),
   627  			)
   628  		getAllTraitFunc := j.Func().
   629  			Params(j.Id(m.defFuncReceiver).Add(m.defStructPointer)).
   630  			Id("GetAllTraits").
   631  			Params().
   632  			Params(j.Index().Qual("apis", "Trait")).
   633  			Block(
   634  				j.Return(j.Id(m.defFuncReceiver).Dot("Base").Dot("Traits")),
   635  			)
   636  
   637  		return []*j.Statement{setTraitFunc, getTraitFunc, getAllTraitFunc}
   638  	case v1beta1.WorkflowStepDefinitionKind:
   639  	}
   640  	return nil
   641  }
   642  
   643  func (m *GoDefModifier) genNameTypeFunc() []*j.Statement {
   644  	nameFunc := j.Func().Params(j.Id(m.defFuncReceiver).Add(m.defStructPointer)).Id(DefinitionKindToPascal[m.kind] + "Name").Params().String().Block(
   645  		j.Return(j.Id(m.defFuncReceiver).Dot("Base").Dot("Name")),
   646  	)
   647  	typeFunc := j.Func().Params(j.Id(m.defFuncReceiver).Add(m.defStructPointer)).Id("DefType").Params().String().Block(
   648  		j.Return(j.Id(m.typeVarName)),
   649  	)
   650  	switch m.kind {
   651  	case v1beta1.ComponentDefinitionKind, v1beta1.WorkflowStepDefinitionKind, v1beta1.PolicyDefinitionKind:
   652  		return []*j.Statement{nameFunc, typeFunc}
   653  	case v1beta1.TraitDefinitionKind:
   654  		return []*j.Statement{typeFunc}
   655  	}
   656  	return nil
   657  }
   658  
   659  func (m *GoDefModifier) genUnmarshalFunc() []*j.Statement {
   660  	return []*j.Statement{j.Null()}
   661  }
   662  
   663  func (m *GoDefModifier) genBaseSetterFunc() []*j.Statement {
   664  	baseFuncArgs := map[string][]struct {
   665  		funcName string
   666  		argName  string
   667  		argType  *j.Statement
   668  		dst      *j.Statement
   669  		isAppend bool
   670  	}{
   671  		v1beta1.ComponentDefinitionKind: {
   672  			{funcName: "DependsOn", argName: "dependsOn", argType: j.Index().String()},
   673  			{funcName: "Inputs", argName: "input", argType: j.Qual("common", "StepInputs")},
   674  			{funcName: "Outputs", argName: "output", argType: j.Qual("common", "StepOutputs")},
   675  			{funcName: "AddDependsOn", argName: "dependsOn", argType: j.String(), isAppend: true, dst: j.Dot("DependsOn")},
   676  			// TODO: uncomment this after https://github.com/kubevela/workflow/pull/125 is released.
   677  			// {funcName: "AddInput", argName: "input", argType: Qual("common", "StepInputs"), isAppend: true, dst: "Inputs"},
   678  			// {funcName: "AddOutput", argName: "output", argType: Qual("common", "StepOutputs"), isAppend: true, dst: "Outputs"},
   679  		},
   680  		v1beta1.WorkflowStepDefinitionKind: {
   681  			{funcName: "If", argName: "_if", argType: j.String()},
   682  			{funcName: "Alias", argName: "alias", argType: j.String(), dst: j.Dot("Meta").Dot("Alias")},
   683  			{funcName: "Timeout", argName: "timeout", argType: j.String()},
   684  			{funcName: "DependsOn", argName: "dependsOn", argType: j.Index().String()},
   685  			{funcName: "Inputs", argName: "input", argType: j.Qual("common", "StepInputs")},
   686  			{funcName: "Outputs", argName: "output", argType: j.Qual("common", "StepOutputs")},
   687  			// {funcName: "AddInput", argName: "input", argType: Qual("common", "StepInputs"), isAppend: true, dst: "Inputs"},
   688  			// {funcName: "AddOutput", argName: "output", argType: Qual("common", "StepOutputs"), isAppend: true, dst: "Outputs"},
   689  		},
   690  	}
   691  	baseFuncs := make([]*j.Statement, 0)
   692  	for _, fn := range baseFuncArgs[m.kind] {
   693  		if fn.dst == nil {
   694  			fn.dst = j.Dot(fn.funcName)
   695  		}
   696  		f := j.Func().
   697  			Params(j.Id(m.defFuncReceiver).Add(m.defStructPointer)).
   698  			Id(fn.funcName).
   699  			Params(j.Id(fn.argName).Add(fn.argType)).
   700  			Add(m.defStructPointer).
   701  			BlockFunc(func(g *j.Group) {
   702  				field := j.Id(m.defFuncReceiver).Dot("Base").Add(fn.dst)
   703  				if fn.isAppend {
   704  					g.Add(field.Clone().Op("=").Append(field.Clone(), j.Id(fn.argName)))
   705  				} else {
   706  					g.Add(field.Clone().Op("=").Id(fn.argName))
   707  				}
   708  				g.Add(j.Return(j.Id(m.defFuncReceiver)))
   709  			})
   710  		baseFuncs = append(baseFuncs, f)
   711  	}
   712  	return baseFuncs
   713  }
   714  
   715  func (m *GoDefModifier) genAddSubStepFunc() *j.Statement {
   716  	if m.name != "step-group" || m.kind != v1beta1.WorkflowStepDefinitionKind {
   717  		return j.Null()
   718  	}
   719  	subList := j.Id(m.defFuncReceiver).Dot("Base").Dot("SubSteps")
   720  	return j.Func().
   721  		Params(j.Id(m.defFuncReceiver).Add(m.defStructPointer)).
   722  		Id("AddSubStep").
   723  		Params(j.Id("subStep").Qual("apis", "WorkflowStep")).
   724  		Add(m.defStructPointer).
   725  		Block(
   726  			subList.Clone().Op("=").Append(subList.Clone(), j.Id("subStep")),
   727  			j.Return(j.Id(m.defFuncReceiver)),
   728  		)
   729  }
   730  
   731  // exportMethods will export methods from definition spec struct to definition struct
   732  func (m *GoDefModifier) exportMethods() error {
   733  	fileLoc := path.Join(m.defDir, m.nameInSnakeCase+".go")
   734  	// nolint:gosec
   735  	file, err := os.ReadFile(fileLoc)
   736  	if err != nil {
   737  		return err
   738  	}
   739  	var fileStr = string(file)
   740  	from := fmt.Sprintf("*%sSpec", m.nameInPascalCase)
   741  	to := fmt.Sprintf("*%s", m.defStructName)
   742  	// replace all the function receiver but not below functions
   743  	// New{m.nameInPascalCase}SpecWith
   744  	// New{m.nameInPascalCase}Spec
   745  	fileStr = regexp.MustCompile(fmt.Sprintf(`func \(o \%s\) ([()\[\]{}\w ]+)\%s([ )])`, from, from)).ReplaceAllString(fileStr, fmt.Sprintf("func (o %s) $1%s$2", to, to))
   746  	fileStr = strings.ReplaceAll(fileStr, "func (o "+from, "func (o "+to)
   747  
   748  	// replace all function receiver in function body
   749  	// o.foo -> o.Properties.foo
   750  	// o.Base keeps the same
   751  	// seek the MarshalJSON function, replace functions before it
   752  	parts := strings.SplitN(fileStr, "MarshalJSON", 2)
   753  	if len(parts) != 2 {
   754  		return fmt.Errorf("can't find MarshalJSON function")
   755  	}
   756  	parts[0] = strings.ReplaceAll(parts[0], "o.", "o.Properties.")
   757  	parts[0] = strings.ReplaceAll(parts[0], "o.Properties.Base", "o.Base")
   758  	fileStr = parts[0] + "MarshalJSON" + parts[1]
   759  
   760  	return os.WriteFile(fileLoc, []byte(fileStr), 0600)
   761  }
   762  
   763  func (m *GoDefModifier) addValidateTraits() error {
   764  	if m.kind != v1beta1.ComponentDefinitionKind {
   765  		return nil
   766  	}
   767  	fileLoc := path.Join(m.defDir, m.nameInSnakeCase+".go")
   768  	// nolint:gosec
   769  	file, err := os.ReadFile(fileLoc)
   770  	if err != nil {
   771  		return err
   772  	}
   773  	var fileStr = string(file)
   774  	buf := bytes.Buffer{}
   775  
   776  	err = j.For(j.List(j.Id("i"), j.Id("v").Op(":=").Range().Id("o").Dot("Base").Dot("Traits"))).Block(
   777  		j.If(j.Id("err").Op(":=").Id("v").Dot("Validate").Call().Op(";").Id("err").Op("!=").Nil()).Block(
   778  			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"))),
   779  		),
   780  	).Render(&buf)
   781  	if err != nil {
   782  		return err
   783  	}
   784  	// add validate trait part in Validate function
   785  	exp := regexp.MustCompile(`Validate\(\)((.|\n)*?)(return nil)`)
   786  	s := buf.String()
   787  	fileStr = exp.ReplaceAllString(fileStr, fmt.Sprintf("Validate()$1\n%s\n$3", s))
   788  
   789  	return os.WriteFile(fileLoc, []byte(fileStr), 0600)
   790  }
   791  func (m *GoModuleModifier) format() error {
   792  	// check if gofmt is installed
   793  	// todo (chivalryq): support go mod tidy for sub-module
   794  
   795  	formatters := []string{"gofmt", "goimports"}
   796  	var formatterPaths []string
   797  	allFormattersInstalled := true
   798  	for _, formatter := range formatters {
   799  		p, err := exec.LookPath(formatter)
   800  		if err != nil {
   801  			allFormattersInstalled = false
   802  			break
   803  		}
   804  		formatterPaths = append(formatterPaths, p)
   805  	}
   806  	if allFormattersInstalled {
   807  		for _, fmter := range formatterPaths {
   808  			if m.Verbose {
   809  				fmt.Printf("Use %s to format code\n", fmter)
   810  			}
   811  			// nolint:gosec
   812  			cmd := exec.Command(fmter, "-w", m.apiDir)
   813  			output, err := cmd.CombinedOutput()
   814  			if err != nil {
   815  				return errors.Wrap(err, string(output))
   816  			}
   817  		}
   818  		return nil
   819  	}
   820  	// fallback to use go lib
   821  	if m.Verbose {
   822  		fmt.Println("At least one of linters is not installed, use go/format lib to format code")
   823  	}
   824  
   825  	// format all .go files
   826  	return filepath.Walk(m.apiDir, func(path string, info os.FileInfo, err error) error {
   827  		if err != nil {
   828  			return err
   829  		}
   830  		if !strings.HasSuffix(path, ".go") {
   831  			return nil
   832  		}
   833  		// nolint:gosec
   834  		content, err := os.ReadFile(path)
   835  		if err != nil {
   836  			return errors.Wrapf(err, "read file %s", path)
   837  		}
   838  		formatted, err := format.Source(content)
   839  		if err != nil {
   840  			return errors.Wrapf(err, "format file %s", path)
   841  		}
   842  		err = os.WriteFile(path, formatted, 0600)
   843  		if err != nil {
   844  			return errors.Wrapf(err, "write file %s", path)
   845  		}
   846  		return nil
   847  	})
   848  }