components-contrib/.build-tools/pkg/metadataanalyzer/utils.go

205 lines
4.9 KiB
Go

package metadataanalyzer
import (
"errors"
"fmt"
"go/ast"
"go/parser"
"go/token"
"io/fs"
"log"
"os"
"path/filepath"
"strings"
"text/template"
// Import the embed package.
_ "embed"
)
//go:embed analyzer.template
var tmpl string
type PkgInfo struct {
Method string
ComponentType string
}
func GenerateMetadataAnalyzer(contribRoot string, componentFolders []string, outputfile string) {
fset := token.NewFileSet()
pkgs := make(map[string]PkgInfo)
err := filepath.WalkDir(contribRoot, func(path string, file fs.DirEntry, err error) error {
if err != nil {
return err
}
if file.IsDir() {
if file.Name() == "vendor" || file.Name() == "tests" || file.Name() == "internal" {
return fs.SkipDir
}
return nil
}
if filepath.Ext(path) != ".go" {
return nil
}
componentTypeFolder := ""
packageName := ""
skip := true
dir := filepath.Dir(path)
for dir != "." && !strings.HasSuffix(dir, "components-contrib") {
if !skip {
packageName = filepath.Base(dir) + "/" + packageName
} else {
packageName = filepath.Base(dir)
}
skip = false
dir = filepath.Dir(dir)
curFolder := filepath.Base(dir)
for _, val := range componentFolders {
if curFolder == val {
componentTypeFolder = curFolder
}
}
}
parsedFile, err := parser.ParseFile(fset, path, nil, parser.ParseComments)
if err != nil {
log.Printf("could not parse %s: %v", path, err)
return nil
}
var method string
var methodFinderErr error
methodFound := false
switch componentTypeFolder {
// Only the component types listed here implement the GetComponentMetadata method today
// Note: these are folder names not the type of components
case "secretstores":
method, methodFinderErr = getConstructorMethod("secretstores.SecretStore", parsedFile)
if methodFinderErr == nil {
methodFound = true
}
case "state":
method, methodFinderErr = getConstructorMethod("state.Store", parsedFile)
if methodFinderErr == nil {
methodFound = true
}
case "bindings":
method, methodFinderErr = getConstructorMethod("bindings.InputOutputBinding", parsedFile)
if methodFinderErr == nil {
methodFound = true
} else {
method, methodFinderErr = getConstructorMethod("bindings.OutputBinding", parsedFile)
if methodFinderErr == nil {
methodFound = true
} else {
method, methodFinderErr = getConstructorMethod("bindings.InputBinding", parsedFile)
if methodFinderErr == nil {
methodFound = true
}
}
}
case "lock":
method, methodFinderErr = getConstructorMethod("lock.Store", parsedFile)
if methodFinderErr == nil {
methodFound = true
}
case "workflows":
method, methodFinderErr = getConstructorMethod("workflows.Workflow", parsedFile)
if methodFinderErr == nil {
methodFound = true
}
case "configuration":
method, methodFinderErr = getConstructorMethod("configuration.Store", parsedFile)
if methodFinderErr == nil {
methodFound = true
}
case "crypto":
method, methodFinderErr = getConstructorMethod("contribCrypto.SubtleCrypto", parsedFile)
if methodFinderErr == nil {
methodFound = true
}
case "middleware":
method, methodFinderErr = getConstructorMethod("middleware.Middleware", parsedFile)
if methodFinderErr == nil {
methodFound = true
}
case "pubsub":
method, methodFinderErr = getConstructorMethod("pubsub.PubSub", parsedFile)
if methodFinderErr == nil {
methodFound = true
}
}
if methodFound {
pkgs[packageName] = PkgInfo{
Method: method,
ComponentType: componentTypeFolder,
}
}
return nil
})
if err != nil {
log.Fatal(err)
}
data := make(map[string][]string)
for fullpkg, info := range pkgs {
sanitizedPkg := strings.ReplaceAll(strings.ReplaceAll(fullpkg, "/", "_"), "-", "_")
data[fullpkg] = []string{sanitizedPkg, info.Method, info.ComponentType}
}
templateData := struct {
Pkgs map[string][]string
}{
Pkgs: data,
}
// let's try loading the template
f, err := os.Create(outputfile)
if err != nil {
log.Fatal(err)
}
defer f.Close()
t := template.Must(template.New("tmpl").Parse(tmpl))
err = t.Execute(f, templateData)
if err != nil {
log.Fatal(err)
}
}
func getConstructorMethod(componentType string, file *ast.File) (string, error) {
typeSplit := strings.Split(componentType, ".")
if len(typeSplit) != 2 {
return "", fmt.Errorf("invalid component type: %s", componentType)
}
for _, d := range file.Decls {
if f, ok := d.(*ast.FuncDecl); ok {
if f.Type.Results != nil && len(f.Type.Results.List) > 0 {
if selExpr, ok := f.Type.Results.List[0].Type.(*ast.SelectorExpr); ok {
xIdent, ok := selExpr.X.(*ast.Ident)
if !ok || xIdent.Name != typeSplit[0] {
continue
}
if selExpr.Sel.Name == typeSplit[1] {
if len(f.Name.Name) > 3 && f.Name.Name[:3] == "New" {
return f.Name.Name, nil
}
}
}
}
}
}
return "", errors.New("could not find constructor method")
}