package build import ( "errors" "fmt" "go/ast" "go/parser" "go/token" "log/slog" "os" "os/exec" "path" "regexp" "strings" ) type version struct { verString string major int minor int } type Builder struct { rootDir string SkipClone bool SkipCleanup bool PackagesOnly bool Verbose bool Debug bool } func (b *Builder) Build() error { slog.Info("Starting Builder") if b.PackagesOnly { slog.Info(" >>> only generating pkg_gen <<<") } rootErr := b.determineRoot() if rootErr != nil { return rootErr } if !b.PackagesOnly { if b.Verbose { slog.Info(" ... Checking needed commands available") } chkErr := checkCommands([]string{}) if chkErr != nil { return chkErr } } //if !b.SkipCleanup { // slog.Info("Cleaning up old packages directory") // err := os.RemoveAll(path.Join(b.rootDir, "pkg_gen")) // if err != nil { // return err // } //} // //if !b.SkipCleanup && !b.PackagesOnly { // slog.Info("Cleaning up old packages directory") // err := os.RemoveAll(path.Join(b.rootDir, "pkg_gen")) // if err != nil { // return err // } //} //slog.Info("Creating generator dir", "dir", fmt.Sprintf("%s/%s", *root, GEN_REPO_NAME)) //genDir := path.Join(*root, GEN_REPO_NAME) //if !b.SkipClone { // err = createGeneratorDir(GEN_REPO, genDir, b.SkipClone) // if err != nil { // return err // } //} oasHandlerErr := b.oasHandler(path.Join(b.rootDir, "service_specs")) if oasHandlerErr != nil { return oasHandlerErr } //if !b.PackagesOnly { // slog.Info("Generating service boilerplate") // err = generateServiceFiles(*root, path.Join(*root, GEN_REPO_NAME)) // if err != nil { // return err // } // // slog.Info("Copying all service files") // err = CopyDirectory( // path.Join(*root, "generated", "internal", "services"), // path.Join(*root, "stackit", "internal", "services"), // ) // if err != nil { // return err // } // // err = createBoilerplate(*root, path.Join(*root, "stackit", "internal", "services")) // if err != nil { // return err // } //} slog.Info("Done") return nil } type templateData struct { PackageName string PackageNameCamel string PackageNamePascal string NameCamel string NamePascal string NameSnake string Fields []string } func createBoilerplate(rootFolder, folder string) error { services, err := os.ReadDir(folder) if err != nil { return err } for _, svc := range services { if !svc.IsDir() { continue } resources, err := os.ReadDir(path.Join(folder, svc.Name())) if err != nil { return err } var handleDS bool var handleRes bool var foundDS bool var foundRes bool for _, res := range resources { if !res.IsDir() { continue } resourceName := res.Name() dsFile := path.Join( folder, svc.Name(), res.Name(), "datasources_gen", fmt.Sprintf("%s_data_source_gen.go", res.Name()), ) handleDS = FileExists(dsFile) resFile := path.Join( folder, svc.Name(), res.Name(), "resources_gen", fmt.Sprintf("%s_resource_gen.go", res.Name()), ) handleRes = FileExists(resFile) dsGoFile := path.Join(folder, svc.Name(), res.Name(), "datasource.go") foundDS = FileExists(dsGoFile) resGoFile := path.Join(folder, svc.Name(), res.Name(), "resource.go") foundRes = FileExists(resGoFile) if handleDS && !foundDS { slog.Info(" creating missing datasource.go", "service", svc.Name(), "resource", resourceName) if !ValidateSnakeCase(resourceName) { return errors.New("resource name is invalid") } fields, tokenErr := getTokens(dsFile) if tokenErr != nil { return fmt.Errorf("error reading tokens: %w", tokenErr) } tplName := "data_source_scaffold.gotmpl" err = writeTemplateToFile( tplName, path.Join(rootFolder, "cmd", "cmd", "build", "templates", tplName), dsGoFile, &templateData{ PackageName: svc.Name(), PackageNameCamel: ToCamelCase(svc.Name()), PackageNamePascal: ToPascalCase(svc.Name()), NameCamel: ToCamelCase(resourceName), NamePascal: ToPascalCase(resourceName), NameSnake: resourceName, Fields: fields, }, ) if err != nil { panic(err) } } if handleRes && !foundRes { slog.Info(" creating missing resource.go", "service", svc.Name(), "resource", resourceName) if !ValidateSnakeCase(resourceName) { return errors.New("resource name is invalid") } fields, tokenErr := getTokens(resFile) if tokenErr != nil { return fmt.Errorf("error reading tokens: %w", tokenErr) } tplName := "resource_scaffold.gotmpl" err = writeTemplateToFile( tplName, path.Join(rootFolder, "cmd", "cmd", "build", "templates", tplName), resGoFile, &templateData{ PackageName: svc.Name(), PackageNameCamel: ToCamelCase(svc.Name()), PackageNamePascal: ToPascalCase(svc.Name()), NameCamel: ToCamelCase(resourceName), NamePascal: ToPascalCase(resourceName), NameSnake: resourceName, Fields: fields, }, ) if err != nil { return err } if !FileExists(path.Join(folder, svc.Name(), res.Name(), "functions.go")) { slog.Info(" creating missing functions.go", "service", svc.Name(), "resource", resourceName) if !ValidateSnakeCase(resourceName) { return errors.New("resource name is invalid") } fncTplName := "functions_scaffold.gotmpl" err = writeTemplateToFile( fncTplName, path.Join(rootFolder, "cmd", "cmd", "build", "templates", fncTplName), path.Join(folder, svc.Name(), res.Name(), "functions.go"), &templateData{ PackageName: svc.Name(), PackageNameCamel: ToCamelCase(svc.Name()), PackageNamePascal: ToPascalCase(svc.Name()), NameCamel: ToCamelCase(resourceName), NamePascal: ToPascalCase(resourceName), NameSnake: resourceName, }, ) if err != nil { return err } } } } } return nil } func handleLine(line string) (string, error) { schemaRegex := regexp.MustCompile(`(\s+")(id)(": schema.[a-zA-Z0-9]+Attribute{)`) schemaMatches := schemaRegex.FindAllStringSubmatch(line, -1) if schemaMatches != nil { return fmt.Sprintf("%stf_original_api_id%s", schemaMatches[0][1], schemaMatches[0][3]), nil } modelRegex := regexp.MustCompile(`(\s+Id\s+types.[a-zA-Z0-9]+\s+.tfsdk:")(id)(".)`) modelMatches := modelRegex.FindAllStringSubmatch(line, -1) if modelMatches != nil { return fmt.Sprintf("%stf_original_api_id%s", modelMatches[0][1], modelMatches[0][3]), nil } return line, nil } func (b *Builder) determineRoot() error { cmd := exec.Command("git", "rev-parse", "--show-toplevel") out, err := cmd.Output() if err != nil { return err } lines := strings.Split(string(out), "\n") if lines[0] == "" { return fmt.Errorf("unable to determine root directory from git") } b.rootDir = lines[0] if b.Verbose { slog.Info(" ... using root", "dir", b.rootDir) } return nil } //func createGeneratorDir(repoUrl, targetDir string, skipClone bool) error { // if !skipClone { // if FileExists(targetDir) { // remErr := os.RemoveAll(targetDir) // if remErr != nil { // return remErr // } // } // _, cloneErr := git.Clone( // clone.Repository(repoUrl), // clone.Directory(targetDir), // ) // if cloneErr != nil { // return cloneErr // } // } // return nil //} func getTokens(fileName string) ([]string, error) { fset := token.NewFileSet() var result []string node, err := parser.ParseFile(fset, fileName, nil, parser.ParseComments) if err != nil { return nil, err } ast.Inspect( node, func(n ast.Node) bool { // Suche nach Typ-Deklarationen (structs) ts, ok := n.(*ast.TypeSpec) if ok { if strings.Contains(ts.Name.Name, "Model") { ast.Inspect( ts, func(sn ast.Node) bool { tts, tok := sn.(*ast.Field) if tok { result = append(result, tts.Names[0].String()) } return true }, ) } } return true }, ) return result, nil }