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 }