Skip to content

Commit

Permalink
Migrate Dockerfile to generic handler (#399)
Browse files Browse the repository at this point in the history
  • Loading branch information
bfoley13 authored Oct 11, 2024
1 parent 073e184 commit df5053b
Show file tree
Hide file tree
Showing 89 changed files with 812 additions and 446 deletions.
60 changes: 30 additions & 30 deletions cmd/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,10 @@ import (
"github.com/Azure/draft/pkg/config"
dryrunpkg "github.com/Azure/draft/pkg/dryrun"
"github.com/Azure/draft/pkg/filematches"
"github.com/Azure/draft/pkg/languages"
"github.com/Azure/draft/pkg/linguist"
"github.com/Azure/draft/pkg/prompts"
"github.com/Azure/draft/pkg/templatewriter"
"github.com/Azure/draft/pkg/templatewriter/writers"
"github.com/Azure/draft/template"
)

// ErrNoLanguageDetected is raised when `draft create` does not detect source
Expand Down Expand Up @@ -52,8 +50,6 @@ type createCmd struct {
createConfigPath string
createConfig *CreateConfig

supportedLangs *languages.Languages

templateWriter templatewriter.TemplateWriter
templateVariableRecorder config.TemplateVariableRecorder
repoReader reporeader.RepoReader
Expand Down Expand Up @@ -151,7 +147,7 @@ func (cc *createCmd) run() error {

// detectLanguage detects the language used in a project destination directory
// It returns the DraftConfig for that language and the name of the language
func (cc *createCmd) detectLanguage() (*config.DraftConfig, string, error) {
func (cc *createCmd) detectLanguage() (*handlers.Template, string, error) {
hasGo := false
hasGoMod := false
var langs []*linguist.Language
Expand Down Expand Up @@ -213,60 +209,64 @@ func (cc *createCmd) detectLanguage() (*config.DraftConfig, string, error) {
}
}

cc.supportedLangs = languages.CreateLanguagesFromEmbedFS(template.Dockerfiles, cc.dest)

if cc.createConfig.LanguageType != "" {
log.Debug("using configuration language")
lowerLang := strings.ToLower(cc.createConfig.LanguageType)
langConfig := cc.supportedLangs.GetConfig(lowerLang)
if langConfig == nil {
return nil, "", ErrNoLanguageDetected
langDockerfileTemplate, err := handlers.GetTemplate(fmt.Sprintf("dockerfile-%s", lowerLang), "", cc.dest, cc.templateWriter)
if err != nil {
return nil, "", err
}
if langDockerfileTemplate == nil {
return nil, "", fmt.Errorf("could not find a template for %s", cc.createConfig.LanguageType)
}

return langConfig, lowerLang, nil
return langDockerfileTemplate, lowerLang, nil
}

for _, lang := range langs {
detectedLang := linguist.Alias(lang)
log.Infof("--> Draft detected %s (%f%%)\n", detectedLang.Language, detectedLang.Percent)
lowerLang := strings.ToLower(detectedLang.Language)
if cc.supportedLangs.ContainsLanguage(lowerLang) {
if handlers.IsValidTemplate(fmt.Sprintf("dockerfile-%s", lowerLang)) {
if lowerLang == "go" && hasGo && hasGoMod {
log.Debug("detected go and go module")
lowerLang = "gomodule"
}
langConfig := cc.supportedLangs.GetConfig(lowerLang)
return langConfig, lowerLang, nil
langDockerfileTemplate, err := handlers.GetTemplate(fmt.Sprintf("dockerfile-%s", lowerLang), "", cc.dest, cc.templateWriter)
if err != nil {
return nil, "", err
}
if langDockerfileTemplate == nil {
return nil, "", fmt.Errorf("could not find a template for detected language %s", detectedLang.Language)
}
return langDockerfileTemplate, lowerLang, nil
}
log.Infof("--> Could not find a pack for %s. Trying to find the next likely language match...", detectedLang.Language)
}
return nil, "", ErrNoLanguageDetected
}

func (cc *createCmd) generateDockerfile(langConfig *config.DraftConfig, lowerLang string) error {
func (cc *createCmd) generateDockerfile(dockerfileTemplate *handlers.Template, lowerLang string) error {
log.Info("--- Dockerfile Creation ---")
if cc.supportedLangs == nil {
return errors.New("supported languages were loaded incorrectly")
}

// Extract language-specific defaults from repo
extractedValues, err := cc.supportedLangs.ExtractDefaults(lowerLang, cc.repoReader)
extractedValues, err := dockerfileTemplate.ExtractDefaults(lowerLang, cc.repoReader)
if err != nil {
return err
}

// Check for existing duplicate defaults
for k, v := range extractedValues {
variableExists := false
for i, variable := range langConfig.Variables {
for i, variable := range dockerfileTemplate.Config.Variables {
if k == variable.Name {
variableExists = true
langConfig.Variables[i].Default.Value = v
dockerfileTemplate.Config.Variables[i].Default.Value = v
break
}
}
if !variableExists {
langConfig.Variables = append(langConfig.Variables, &config.BuilderVar{
dockerfileTemplate.Config.Variables = append(dockerfileTemplate.Config.Variables, &config.BuilderVar{
Name: k,
Default: config.BuilderVarDefault{
Value: v,
Expand All @@ -276,25 +276,25 @@ func (cc *createCmd) generateDockerfile(langConfig *config.DraftConfig, lowerLan
}

if cc.createConfig.LanguageVariables == nil {
langConfig.VariableMapToDraftConfig(flagVariablesMap)
dockerfileTemplate.Config.VariableMapToDraftConfig(flagVariablesMap)

if err = prompts.RunPromptsFromConfigWithSkips(langConfig); err != nil {
if err = prompts.RunPromptsFromConfigWithSkips(dockerfileTemplate.Config); err != nil {
return err
}
} else {
err = validateConfigInputsToPrompts(langConfig, cc.createConfig.LanguageVariables)
err = validateConfigInputsToPrompts(dockerfileTemplate.Config, cc.createConfig.LanguageVariables)
if err != nil {
return err
}
}

if cc.templateVariableRecorder != nil {
for _, variable := range langConfig.Variables {
for _, variable := range dockerfileTemplate.Config.Variables {
cc.templateVariableRecorder.Record(variable.Name, variable.Value)
}
}

if err = cc.supportedLangs.CreateDockerfileForLanguage(lowerLang, langConfig, cc.templateWriter); err != nil {
if err = dockerfileTemplate.Generate(); err != nil {
return fmt.Errorf("there was an error when creating the Dockerfile for language %s: %w", cc.createConfig.LanguageType, err)
}

Expand Down Expand Up @@ -363,7 +363,7 @@ func (cc *createCmd) createDeployment() error {
return deployTemplate.Generate()
}

func (cc *createCmd) createFiles(detectedLang *config.DraftConfig, lowerLang string) error {
func (cc *createCmd) createFiles(detectedLangTempalte *handlers.Template, lowerLang string) error {
// does no further checks without file detection

if cc.dockerfileOnly && cc.deploymentOnly {
Expand All @@ -372,7 +372,7 @@ func (cc *createCmd) createFiles(detectedLang *config.DraftConfig, lowerLang str

if cc.skipFileDetection {
if !cc.deploymentOnly {
err := cc.generateDockerfile(detectedLang, lowerLang)
err := cc.generateDockerfile(detectedLangTempalte, lowerLang)
if err != nil {
return err
}
Expand Down Expand Up @@ -412,7 +412,7 @@ func (cc *createCmd) createFiles(detectedLang *config.DraftConfig, lowerLang str
} else if hasDockerFile {
log.Info("--> Found Dockerfile in local directory, skipping Dockerfile creation...")
} else if !cc.deploymentOnly {
err := cc.generateDockerfile(detectedLang, lowerLang)
err := cc.generateDockerfile(detectedLangTempalte, lowerLang)
if err != nil {
return err
}
Expand Down
26 changes: 16 additions & 10 deletions cmd/create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,15 @@ import (
"github.com/stretchr/testify/assert"

"github.com/Azure/draft/pkg/config"
"github.com/Azure/draft/pkg/languages"
"github.com/Azure/draft/pkg/handlers"
"github.com/Azure/draft/pkg/linguist"
"github.com/Azure/draft/pkg/reporeader"
"github.com/Azure/draft/pkg/templatewriter/writers"
"github.com/Azure/draft/template"
)

func TestRun(t *testing.T) {
testCreateConfig := CreateConfig{LanguageVariables: []UserInputs{{Name: "PORT", Value: "8080"}}, DeployVariables: []UserInputs{{Name: "PORT", Value: "8080"}, {Name: "APPNAME", Value: "testingCreateCommand"}}}
flagVariablesMap = map[string]string{"PORT": "8080", "APPNAME": "testingCreateCommand", "VERSION": "1.18", "SERVICEPORT": "8080", "NAMESPACE": "testNamespace", "IMAGENAME": "testImage", "IMAGETAG": "latest"}
testCreateConfig := CreateConfig{LanguageVariables: []UserInputs{{Name: "PORT", Value: "8080"}}, DeployVariables: []UserInputs{{Name: "PORT", Value: "8080"}, {Name: "APPNAME", Value: "testingCreateCommand"}, {Name: "DOCKERFILENAME", Value: "Dockerfile"}}}
flagVariablesMap = map[string]string{"PORT": "8080", "APPNAME": "testingCreateCommand", "VERSION": "1.18", "SERVICEPORT": "8080", "NAMESPACE": "testNamespace", "IMAGENAME": "testImage", "IMAGETAG": "latest", "DOCKERFILENAME": "test.Dockerfile"}
mockCC := createCmd{
dest: "./..",
createConfig: &testCreateConfig,
Expand Down Expand Up @@ -199,7 +198,7 @@ func TestValidateConfigInputsToPromptsMissing(t *testing.T) {
assert.NotNil(t, err)
}

func (mcc *createCmd) mockDetectLanguage() (*config.DraftConfig, string, error) {
func (mcc *createCmd) mockDetectLanguage() (*handlers.Template, string, error) {
hasGo := false
hasGoMod := false
var langs []*linguist.Language
Expand Down Expand Up @@ -227,12 +226,13 @@ func (mcc *createCmd) mockDetectLanguage() (*config.DraftConfig, string, error)
}
}

mcc.supportedLangs = languages.CreateLanguagesFromEmbedFS(template.Dockerfiles, mcc.dest)

if mcc.createConfig.LanguageType != "" {
log.Debug("using configuration language")
lowerLang := strings.ToLower(mcc.createConfig.LanguageType)
langConfig := mcc.supportedLangs.GetConfig(lowerLang)
langConfig, err := handlers.GetTemplate(fmt.Sprintf("dockerfile-%s", lowerLang), "", mcc.dest, mcc.templateWriter)
if err != nil {
return nil, "", err
}
if langConfig == nil {
return nil, "", ErrNoLanguageDetected
}
Expand All @@ -245,13 +245,19 @@ func (mcc *createCmd) mockDetectLanguage() (*config.DraftConfig, string, error)
log.Infof("--> Draft detected %s (%f%%)\n", detectedLang.Language, detectedLang.Percent)
lowerLang := strings.ToLower(detectedLang.Language)

if mcc.supportedLangs.ContainsLanguage(lowerLang) {
if handlers.IsValidTemplate(fmt.Sprintf("dockerfile-%s", lowerLang)) {
if lowerLang == "go" && hasGo && hasGoMod {
log.Debug("detected go and go module")
lowerLang = "gomodule"
}

langConfig := mcc.supportedLangs.GetConfig(lowerLang)
langConfig, err := handlers.GetTemplate(fmt.Sprintf("dockerfile-%s", lowerLang), "", mcc.dest, mcc.templateWriter)
if err != nil {
return nil, "", err
}
if langConfig == nil {
return nil, "", ErrNoLanguageDetected
}
return langConfig, lowerLang, nil
}
log.Infof("--> Could not find a pack for %s. Trying to find the next likely language match...\n", detectedLang.Language)
Expand Down
14 changes: 6 additions & 8 deletions cmd/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ import (
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"

"github.com/Azure/draft/pkg/languages"
"github.com/Azure/draft/template"
"github.com/Azure/draft/pkg/handlers"
)

type Format string
Expand Down Expand Up @@ -61,15 +60,14 @@ func newInfoCmd() *cobra.Command {

func (ic *infoCmd) run() error {
log.Debugf("getting supported languages")
l := languages.CreateLanguagesFromEmbedFS(template.Dockerfiles, "")
supportedDockerfileTemplates := handlers.GetTemplatesByType(handlers.TemplateTypeDockerfile)

languagesInfo := make([]draftConfigInfo, 0)
for _, lang := range l.Names() {
langConfig := l.GetConfig(lang)
for _, template := range supportedDockerfileTemplates {
newConfig := draftConfigInfo{
Name: lang,
DisplayName: langConfig.DisplayName,
VariableExampleValues: langConfig.GetVariableExampleValues(),
Name: template.Config.TemplateName,
DisplayName: template.Config.DisplayName,
VariableExampleValues: template.Config.GetVariableExampleValues(),
}
languagesInfo = append(languagesInfo, newConfig)
}
Expand Down
2 changes: 1 addition & 1 deletion example/deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func WriteDeploymentFilesExample() error {
// Select the deployment type to generate the files for (must correspond to a directory in the template/deployments directory)
deploymentTemplateType := "deployment-manifests"

// Create a DraftConfig of inputs to the template (must correspond to the inputs in the template/deployments/<deploymentType>/draft.yaml files)
// Create a map of of inputs to the template (must correspond to the inputs in the template/deployments/<deploymentType>/draft.yaml files)
templateVars := map[string]string{
"PORT": "8080",
"APPNAME": "example-app",
Expand Down
57 changes: 27 additions & 30 deletions example/dockerfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,10 @@ package example
import (
"fmt"

"github.com/Azure/draft/pkg/config"
"github.com/Azure/draft/pkg/languages"
"github.com/Azure/draft/pkg/templatewriter"
"github.com/Azure/draft/pkg/handlers"
"github.com/Azure/draft/pkg/templatewriter/writers"
"github.com/Azure/draft/template"
)

// WriteDockerfile generates a Dockerfile and dockerignore using Draft, writing to a Draft TemplateWriter. See the corresponding draft.yaml file in templates/dockerfiles/[language] for the template inputs.
func WriteDockerfile(w templatewriter.TemplateWriter, dockerfileOutputPath string, langConfig *config.DraftConfig, generationLanguage string) error {
l := languages.CreateLanguagesFromEmbedFS(template.Dockerfiles, dockerfileOutputPath)

err := l.CreateDockerfileForLanguage(generationLanguage, langConfig, w)
if err != nil {
return fmt.Errorf("failed to generate dockerfile: %e", err)
}
return nil
}

// WriteDockerfileExample shows how to set up a fileWriter and generate a fileMap using WriteDockerfile
func WriteDockerfileExample() error {
// Create a file map
Expand All @@ -34,27 +20,38 @@ func WriteDockerfileExample() error {
// Select the language to generate the Dockerfile for (must correspond to a directory in the template/dockerfiles directory)
generationLanguage := "go"

// Create a DraftConfig of inputs to the template (must correspond to the inputs in the template/dockerfiles/<language>/draft.yaml files)
langConfig := &config.DraftConfig{
Variables: []*config.BuilderVar{
{
Name: "PORT",
Value: "8080",
},
{
Name: "VERSION",
Value: "1.20",
},
},
// Create a map of inputs to the template (must correspond to the inputs in the template/dockerfiles/<language>/draft.yaml files)
templateVars := map[string]string{
"PORT": "8080",
"APPNAME": "example-app",
"SERVICEPORT": "8080",
"NAMESPACE": "example-namespace",
"IMAGENAME": "example-image",
"IMAGETAG": "latest",
"GENERATORLABEL": "draft",
}

// Set the output path for the Dockerfile
outputPath := "./"

// Write the Dockerfile
err := WriteDockerfile(&w, outputPath, langConfig, generationLanguage)
// Get the dockerfile template
d, err := handlers.GetTemplate(fmt.Sprintf("dockerfile-%s", generationLanguage), "", outputPath, &w)
if err != nil {
return err
return fmt.Errorf("failed to get template: %e", err)
}
if d == nil {
return fmt.Errorf("template is nil")
}

// Set the variable values within the template
for k, v := range templateVars {
d.Config.SetVariable(k, v)
}

// Generate the dockerfile files
err = d.Generate()
if err != nil {
return fmt.Errorf("failed to generate dockerfile: %e", err)
}

// Read written files from the file map
Expand Down
Loading

0 comments on commit df5053b

Please sign in to comment.