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 }