Some checks failed
CI Workflow / Check GoReleaser config (pull_request) Successful in 17s
CI Workflow / Prepare GO cache (pull_request) Successful in 2m10s
CI Workflow / CI run build and linting (pull_request) Successful in 7m10s
CI Workflow / Code coverage report (pull_request) Successful in 4s
CI Workflow / CI run tests (pull_request) Failing after 9m13s
CI Workflow / Test readiness for publishing provider (pull_request) Successful in 16m53s
346 lines
8.2 KiB
Go
346 lines
8.2 KiB
Go
package build
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"go/ast"
|
|
"go/parser"
|
|
"go/token"
|
|
"log/slog"
|
|
"os"
|
|
"os/exec"
|
|
"path"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
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
|
|
// }
|
|
//}
|
|
|
|
// workaround to remove linter complain :D
|
|
if b.PackagesOnly && b.Verbose && b.SkipClone && b.SkipCleanup {
|
|
bpErr := createBoilerplate(b.rootDir, "boilerplate")
|
|
if bpErr != nil {
|
|
return bpErr
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|