pkg/handlers/template.go (157 lines of code) (raw):
package handlers
import (
"bytes"
"fmt"
"io/fs"
"path/filepath"
"strings"
tmpl "text/template"
"github.com/Azure/draft/pkg/config"
"github.com/Azure/draft/pkg/handlers/variableextractors/defaults"
"github.com/Azure/draft/pkg/reporeader"
"github.com/Azure/draft/pkg/templatewriter"
log "github.com/sirupsen/logrus"
)
type Template struct {
Config *config.DraftConfig
templateFiles fs.FS
templateWriter templatewriter.TemplateWriter
src string
dest string
version string
}
// GetTemplate returns a template by name, version, and destination
func GetTemplate(name, version, dest string, templateWriter templatewriter.TemplateWriter) (*Template, error) {
template, ok := templateConfigs[strings.ToLower(name)]
if !ok {
return nil, fmt.Errorf("template not found: %s", name)
}
template = template.DeepCopy()
if version == "" {
version = template.Config.DefaultVersion
log.Println("version not provided, using default version: ", version)
}
if !IsValidVersion(template.Config.Versions, version) {
return nil, fmt.Errorf("invalid version: %s", version)
}
if dest == "" {
dest = "."
log.Println("destination not provided, using current directory")
}
if _, err := filepath.Abs(dest); err != nil {
return nil, fmt.Errorf("invalid destination: %s", dest)
}
template.dest = dest
template.version = version
template.templateWriter = templateWriter
return template, nil
}
func (t *Template) Generate() error {
if err := t.validate(); err != nil {
log.Printf("template validation failed: %s", err.Error())
return fmt.Errorf("generating template: %w", err)
}
if err := t.Config.ApplyDefaultVariablesForVersion(t.version); err != nil {
return fmt.Errorf("create workflow files: %w", err)
}
return generateTemplate(t)
}
func (t *Template) validate() error {
if t == nil {
return fmt.Errorf("template is nil")
}
if t.Config == nil {
return fmt.Errorf("template draft config is nil")
}
if t.src == "" {
return fmt.Errorf("template source is empty")
}
if t.dest == "" {
return fmt.Errorf("template destination is empty")
}
if t.templateFiles == nil {
return fmt.Errorf("template files is nil")
}
if t.version == "" {
return fmt.Errorf("template version is empty")
}
return nil
}
func (t *Template) DeepCopy() *Template {
return &Template{
Config: t.Config.DeepCopy(),
templateFiles: t.templateFiles,
templateWriter: t.templateWriter,
src: t.src,
dest: t.dest,
version: t.version,
}
}
func (l *Template) ExtractDefaults(lowerLang string, r reporeader.RepoReader) (map[string]string, error) {
extractors := []reporeader.VariableExtractor{
&defaults.PythonExtractor{},
&defaults.GradleExtractor{},
}
extractedValues := make(map[string]string)
if r == nil {
log.Debugf("no repo reader provided, returning empty list of defaults")
return extractedValues, nil
}
for _, extractor := range extractors {
if extractor.MatchesLanguage(lowerLang) {
newDefaults, err := extractor.ReadDefaults(r)
if err != nil {
return nil, fmt.Errorf("error reading defaults for language %s: %v", lowerLang, err)
}
for k, v := range newDefaults {
if _, ok := extractedValues[k]; ok {
log.Debugf("duplicate default %s for language %s with extractor %s", k, lowerLang, extractor.GetName())
}
extractedValues[k] = v
log.Debugf("extracted default %s=%s with extractor:%s", k, v, extractor.GetName())
}
}
}
return extractedValues, nil
}
func generateTemplate(template *Template) error {
err := fs.WalkDir(template.templateFiles, template.src, func(path string, d fs.DirEntry, err error) error {
if d.IsDir() {
return template.templateWriter.EnsureDirectory(strings.Replace(path, template.src, template.dest, 1))
}
if strings.EqualFold(d.Name(), "draft.yaml") {
return nil
}
if err := writeTemplate(template, path); err != nil {
return fmt.Errorf("failed to write template %s: %w", path, err)
}
return nil
})
return err
}
func writeTemplate(draftTemplate *Template, inputFile string) error {
file, err := fs.ReadFile(draftTemplate.templateFiles, inputFile)
if err != nil {
return err
}
// Parse the template file, missingkey=error ensures an error will be returned if any variable is missing during template execution.
tmpl, err := tmpl.New("template").Option("missingkey=error").Parse(string(file))
if err != nil {
return err
}
// Execute the template with variableMap
var buf bytes.Buffer
err = tmpl.Execute(&buf, draftTemplate)
if err != nil {
return err
}
if err = draftTemplate.templateWriter.WriteFile(getOutputFileName(draftTemplate, inputFile), buf.Bytes()); err != nil {
return err
}
return nil
}
func getOutputFileName(draftTemplate *Template, inputFile string) string {
outputName := filepath.Clean(strings.Replace(inputFile, draftTemplate.src, draftTemplate.dest, 1))
fileName := filepath.Base(inputFile)
if overrideName, ok := draftTemplate.Config.FileNameOverrideMap[fileName]; ok {
return strings.Replace(outputName, fileName, overrideName, 1)
}
return outputName
}