1
0
mirror of https://github.com/rancher/norman.git synced 2025-06-21 21:17:13 +00:00
norman/generator/generator.go

386 lines
9.1 KiB
Go
Raw Normal View History

2017-11-11 04:44:02 +00:00
package generator
import (
2017-11-28 21:28:25 +00:00
"fmt"
"io"
2017-11-11 05:07:22 +00:00
"io/ioutil"
"net/http"
2017-11-11 04:44:02 +00:00
"os"
2017-11-11 05:07:22 +00:00
"os/exec"
2017-11-11 04:44:02 +00:00
"path"
"regexp"
"strings"
"text/template"
2017-11-11 05:07:22 +00:00
"github.com/pkg/errors"
2017-11-11 04:44:02 +00:00
"github.com/rancher/norman/types"
"github.com/rancher/norman/types/convert"
2017-11-11 05:07:22 +00:00
"k8s.io/gengo/args"
"k8s.io/gengo/examples/deepcopy-gen/generators"
2017-11-12 00:07:09 +00:00
"k8s.io/gengo/generator"
gengotypes "k8s.io/gengo/types"
2017-11-11 04:44:02 +00:00
)
var (
blackListTypes = map[string]bool{
"schema": true,
"resource": true,
"collection": true,
}
underscoreRegexp = regexp.MustCompile(`([a-z])([A-Z])`)
)
2017-11-21 20:46:30 +00:00
type fieldInfo struct {
Name string
Type string
}
2017-11-11 04:44:02 +00:00
func getGoType(field types.Field, schema *types.Schema, schemas *types.Schemas) string {
return getTypeString(field.Nullable, field.Type, schema, schemas)
}
func getTypeString(nullable bool, typeName string, schema *types.Schema, schemas *types.Schemas) string {
switch {
case strings.HasPrefix(typeName, "reference["):
return "string"
case strings.HasPrefix(typeName, "map["):
return "map[string]" + getTypeString(false, typeName[len("map["):len(typeName)-1], schema, schemas)
case strings.HasPrefix(typeName, "array["):
return "[]" + getTypeString(false, typeName[len("array["):len(typeName)-1], schema, schemas)
}
name := ""
switch typeName {
case "json":
return "interface{}"
case "boolean":
name = "bool"
case "float":
name = "float64"
case "int":
name = "int64"
2017-11-15 04:33:57 +00:00
case "multiline":
return "string"
case "masked":
return "string"
2017-11-11 04:44:02 +00:00
case "password":
return "string"
case "date":
return "string"
case "string":
return "string"
case "enum":
return "string"
default:
if schema != nil && schemas != nil {
otherSchema := schemas.Schema(&schema.Version, typeName)
if otherSchema != nil {
name = otherSchema.CodeName
}
}
if name == "" {
name = convert.Capitalize(typeName)
}
}
if nullable {
return "*" + name
}
return name
}
2017-11-21 20:46:30 +00:00
func getTypeMap(schema *types.Schema, schemas *types.Schemas) map[string]fieldInfo {
result := map[string]fieldInfo{}
for name, field := range schema.ResourceFields {
result[field.CodeName] = fieldInfo{
Name: name,
Type: getGoType(field, schema, schemas),
}
2017-11-11 04:44:02 +00:00
}
return result
}
func getResourceActions(schema *types.Schema, schemas *types.Schemas) map[string]types.Action {
result := map[string]types.Action{}
for name, action := range schema.ResourceActions {
if schemas.Schema(&schema.Version, action.Output) != nil {
result[name] = action
}
}
return result
}
func generateType(outputDir string, schema *types.Schema, schemas *types.Schemas) error {
filePath := strings.ToLower("zz_generated_" + addUnderscore(schema.ID) + ".go")
output, err := os.Create(path.Join(outputDir, filePath))
if err != nil {
return err
}
defer output.Close()
typeTemplate, err := template.New("type.template").
Funcs(funcs()).
Parse(strings.Replace(typeTemplate, "%BACK%", "`", -1))
if err != nil {
return err
}
return typeTemplate.Execute(output, map[string]interface{}{
"schema": schema,
"structFields": getTypeMap(schema, schemas),
"resourceActions": getResourceActions(schema, schemas),
})
}
2017-11-28 21:28:25 +00:00
func generateController(external bool, outputDir string, schema *types.Schema, schemas *types.Schemas) error {
2017-11-11 04:44:02 +00:00
filePath := strings.ToLower("zz_generated_" + addUnderscore(schema.ID) + "_controller.go")
output, err := os.Create(path.Join(outputDir, filePath))
if err != nil {
return err
}
defer output.Close()
typeTemplate, err := template.New("controller.template").
Funcs(funcs()).
Parse(strings.Replace(controllerTemplate, "%BACK%", "`", -1))
if err != nil {
return err
}
2017-11-28 21:28:25 +00:00
importPackage := ""
prefix := ""
if external {
parts := strings.Split(schema.PkgName, "/vendor/")
importPackage = fmt.Sprintf("\"%s\"", parts[len(parts)-1])
prefix = schema.Version.Version + "."
}
2017-11-11 04:44:02 +00:00
return typeTemplate.Execute(output, map[string]interface{}{
2017-11-28 21:28:25 +00:00
"schema": schema,
"importPackage": importPackage,
"prefix": prefix,
2017-11-11 04:44:02 +00:00
})
}
2017-11-13 19:50:25 +00:00
func generateK8sClient(outputDir string, version *types.APIVersion, schemas []*types.Schema) error {
filePath := strings.ToLower("zz_generated_k8s_client.go")
output, err := os.Create(path.Join(outputDir, filePath))
if err != nil {
return err
}
defer output.Close()
typeTemplate, err := template.New("k8sClient.template").
Funcs(funcs()).
Parse(strings.Replace(k8sClientTemplate, "%BACK%", "`", -1))
if err != nil {
return err
}
return typeTemplate.Execute(output, map[string]interface{}{
"version": version,
"schemas": schemas,
})
}
2017-11-11 04:44:02 +00:00
func generateClient(outputDir string, schemas []*types.Schema) error {
template, err := template.New("client.template").
Funcs(funcs()).
Parse(clientTemplate)
if err != nil {
return err
}
output, err := os.Create(path.Join(outputDir, "zz_generated_client.go"))
if err != nil {
return err
}
defer output.Close()
return template.Execute(output, map[string]interface{}{
"schemas": schemas,
})
}
2017-11-28 21:28:25 +00:00
func GenerateControllerForTypes(version *types.APIVersion, k8sOutputPackage string, objs ...interface{}) error {
baseDir := args.DefaultSourceTree()
k8sDir := path.Join(baseDir, k8sOutputPackage)
if err := prepareDirs(k8sDir); err != nil {
return err
}
schemas := types.NewSchemas()
var controllers []*types.Schema
for _, obj := range objs {
schema, err := schemas.Import(version, obj)
if err != nil {
return err
}
controllers = append(controllers, schema)
if err := generateController(true, k8sDir, schema, schemas); err != nil {
return err
}
}
if err := deepCopyGen(baseDir, k8sOutputPackage); err != nil {
return err
}
return generateK8sClient(k8sDir, version, controllers)
}
2017-11-11 04:44:02 +00:00
func Generate(schemas *types.Schemas, cattleOutputPackage, k8sOutputPackage string) error {
baseDir := args.DefaultSourceTree()
cattleDir := path.Join(baseDir, cattleOutputPackage)
k8sDir := path.Join(baseDir, k8sOutputPackage)
if err := prepareDirs(cattleDir, k8sDir); err != nil {
return err
}
2017-11-13 19:50:25 +00:00
controllers := []*types.Schema{}
2017-11-12 00:07:09 +00:00
2017-11-11 04:44:02 +00:00
generated := []*types.Schema{}
for _, schema := range schemas.Schemas() {
if blackListTypes[schema.ID] {
continue
}
if err := generateType(cattleDir, schema, schemas); err != nil {
return err
}
2017-11-12 00:07:09 +00:00
if contains(schema.CollectionMethods, http.MethodGet) &&
!strings.HasPrefix(schema.PkgName, "k8s.io") &&
!strings.Contains(schema.PkgName, "/vendor/") {
2017-11-13 19:50:25 +00:00
controllers = append(controllers, schema)
2017-11-28 21:28:25 +00:00
if err := generateController(false, k8sDir, schema, schemas); err != nil {
2017-11-11 04:44:02 +00:00
return err
}
}
generated = append(generated, schema)
}
if err := generateClient(cattleDir, generated); err != nil {
return err
}
2017-11-13 19:50:25 +00:00
if len(controllers) > 0 {
2017-11-12 00:07:09 +00:00
if err := deepCopyGen(baseDir, k8sOutputPackage); err != nil {
return err
}
2017-11-13 19:50:25 +00:00
generateK8sClient(k8sDir, &controllers[0].Version, controllers)
2017-11-11 04:44:02 +00:00
}
if err := gofmt(baseDir, k8sOutputPackage); err != nil {
return err
}
return gofmt(baseDir, cattleOutputPackage)
}
func prepareDirs(dirs ...string) error {
for _, dir := range dirs {
if err := os.MkdirAll(dir, 0755); err != nil {
return err
}
files, err := ioutil.ReadDir(dir)
if err != nil {
return err
}
for _, file := range files {
if strings.HasPrefix(file.Name(), "zz_generated") {
2017-11-11 05:07:22 +00:00
if err := os.Remove(path.Join(dir, file.Name())); err != nil {
return errors.Wrapf(err, "failed to delete %s", path.Join(dir, file.Name()))
}
2017-11-11 04:44:02 +00:00
}
}
}
return nil
}
func gofmt(workDir, pkg string) error {
2017-11-11 05:07:22 +00:00
cmd := exec.Command("goimports", "-w", "-l", "./"+pkg)
2017-11-11 04:44:02 +00:00
cmd.Dir = workDir
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
func deepCopyGen(workDir, pkg string) error {
arguments := &args.GeneratorArgs{
InputDirs: []string{pkg},
OutputBase: workDir,
OutputPackagePath: pkg,
OutputFileBaseName: "zz_generated_deepcopy",
GoHeaderFilePath: "/dev/null",
GeneratedBuildTag: "ignore_autogenerated",
}
return arguments.Execute(
generators.NameSystems(),
generators.DefaultNameSystem(),
2017-11-12 00:07:09 +00:00
func(context *generator.Context, arguments *args.GeneratorArgs) generator.Packages {
packageParts := strings.Split(pkg, "/")
return generator.Packages{
&generator.DefaultPackage{
PackageName: packageParts[len(packageParts)-1],
PackagePath: pkg,
HeaderText: []byte{},
GeneratorFunc: func(c *generator.Context) []generator.Generator {
return []generator.Generator{
&noInitGenerator{
generators.NewGenDeepCopy(arguments.OutputFileBaseName, pkg, nil, true, true),
},
}
},
FilterFunc: func(c *generator.Context, t *gengotypes.Type) bool {
if t.Name.Package != pkg {
return false
}
if isObjectOrList(t) {
t.SecondClosestCommentLines = append(t.SecondClosestCommentLines,
"+k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object")
}
return true
},
},
}
})
}
type noInitGenerator struct {
generator.Generator
}
func (n *noInitGenerator) Init(*generator.Context, io.Writer) error {
return nil
}
func isObjectOrList(t *gengotypes.Type) bool {
for _, member := range t.Members {
if member.Embedded && (member.Name == "ObjectMeta" || member.Name == "ListMeta") {
return true
}
if member.Embedded && isObjectOrList(member.Type) {
return true
}
}
return false
2017-11-11 04:44:02 +00:00
}