Skip to content

Commit

Permalink
Adding Generic Template Handler (#391)
Browse files Browse the repository at this point in the history
  • Loading branch information
bfoley13 committed Oct 7, 2024
1 parent b4c1f53 commit b7ed7a0
Show file tree
Hide file tree
Showing 26 changed files with 682 additions and 95 deletions.
22 changes: 10 additions & 12 deletions cmd/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ import (

"gopkg.in/yaml.v3"

"github.com/Azure/draft/pkg/handlers"
"github.com/Azure/draft/pkg/reporeader"
"github.com/Azure/draft/pkg/reporeader/readers"
"github.com/manifoldco/promptui"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"

"github.com/Azure/draft/pkg/config"
"github.com/Azure/draft/pkg/deployments"
dryrunpkg "github.com/Azure/draft/pkg/dryrun"
"github.com/Azure/draft/pkg/filematches"
"github.com/Azure/draft/pkg/languages"
Expand Down Expand Up @@ -304,21 +304,20 @@ func (cc *createCmd) generateDockerfile(langConfig *config.DraftConfig, lowerLan

func (cc *createCmd) createDeployment() error {
log.Info("--- Deployment File Creation ---")
d := deployments.CreateDeploymentsFromEmbedFS(template.Deployments, cc.dest)
var deployType string
var deployConfig *config.DraftConfig
var deployTemplate *handlers.Template
var err error

if cc.createConfig.DeployType != "" {
deployType = strings.ToLower(cc.createConfig.DeployType)
deployConfig, err = d.GetConfig(deployType)
deployTemplate, err = handlers.GetTemplate(fmt.Sprintf("deployment-%s", template.Deployments, deployType), "", cc.dest, cc.templateWriter)
if err != nil {
return err
}
if deployConfig == nil {
if deployTemplate == nil || deployTemplate.Config == nil {
return errors.New("invalid deployment type")
}
err = validateConfigInputsToPrompts(deployConfig, cc.createConfig.DeployVariables)
err = validateConfigInputsToPrompts(deployTemplate.Config, cc.createConfig.DeployVariables)
if err != nil {
return err
}
Expand All @@ -337,28 +336,27 @@ func (cc *createCmd) createDeployment() error {
deployType = cc.deployType
}

deployConfig, err = d.GetConfig(deployType)
deployTemplate, err = handlers.GetTemplate(fmt.Sprintf("deployments-%s", template.Deployments, deployType), "", cc.dest, cc.templateWriter)
if err != nil {
return err
}

deployConfig.VariableMapToDraftConfig(flagVariablesMap)
deployTemplate.Config.VariableMapToDraftConfig(flagVariablesMap)

err = prompts.RunPromptsFromConfigWithSkips(deployConfig)
err = prompts.RunPromptsFromConfigWithSkips(deployTemplate.Config)
if err != nil {
return err
}
}

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

log.Infof("--> Creating %s Kubernetes resources...\n", deployType)

return d.CopyDeploymentFiles(deployType, deployConfig, cc.templateWriter)
return deployTemplate.Generate()
}

func (cc *createCmd) createFiles(detectedLang *config.DraftConfig, lowerLang string) error {
Expand Down
57 changes: 57 additions & 0 deletions pkg/config/draftconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (

log "github.com/sirupsen/logrus"
"gopkg.in/yaml.v2"

"github.com/blang/semver/v4"
)

const draftConfigFile = "draft.yaml"
Expand Down Expand Up @@ -125,6 +127,61 @@ func (d *DraftConfig) ApplyDefaultVariables() error {
return nil
}

func (d *DraftConfig) ApplyDefaultVariablesForVersion(version string) error {
v, err := semver.Parse(version)
if err != nil {
return fmt.Errorf("invalid version: %w", err)
}

expectedConfigVersionRange, err := semver.ParseRange(d.Versions)
if err != nil {
return fmt.Errorf("invalid config version range: %w", err)
}

if !expectedConfigVersionRange(v) {
return fmt.Errorf("version %s is outside of config version range %s", version, d.Versions)
}

for _, variable := range d.Variables {
if variable.Value == "" {
expectedRange, err := semver.ParseRange(variable.Versions)
if err != nil {
return fmt.Errorf("invalid variable versions: %w", err)
}

if !expectedRange(v) {
log.Infof("Variable %s versions %s is outside input version %s, skipping", variable.Name, variable.Versions, version)
continue
}

if variable.Default.ReferenceVar != "" {
referenceVar, err := d.GetVariable(variable.Default.ReferenceVar)
if err != nil {
return fmt.Errorf("apply default variables: %w", err)
}

defaultVal, err := d.recurseReferenceVars(referenceVar, referenceVar, true)
if err != nil {
return fmt.Errorf("apply default variables: %w", err)
}
log.Infof("Variable %s defaulting to value %s", variable.Name, defaultVal)
variable.Value = defaultVal
}

if variable.Value == "" {
if variable.Default.Value != "" {
log.Infof("Variable %s defaulting to value %s", variable.Name, variable.Default.Value)
variable.Value = variable.Default.Value
} else {
return errors.New("variable " + variable.Name + " has no default value")
}
}
}
}

return nil
}

// recurseReferenceVars recursively checks each variable's ReferenceVar if it doesn't have a custom input. If there's no more ReferenceVars, it will return the default value of the last ReferenceVar.
func (d *DraftConfig) recurseReferenceVars(referenceVar *BuilderVar, variableCheck *BuilderVar, isFirst bool) (string, error) {
if !isFirst && referenceVar.Name == variableCheck.Name {
Expand Down
58 changes: 57 additions & 1 deletion pkg/config/draftconfig_template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,14 +88,21 @@ func loadTemplatesWithValidation() error {
return fmt.Errorf("template %s has no template name", path)
}

if _, ok := allTemplates[currTemplate.TemplateName]; ok {
if _, ok := allTemplates[strings.ToLower(currTemplate.TemplateName)]; ok {
return fmt.Errorf("template %s has a duplicate template name", path)
}

if _, ok := validTemplateTypes[currTemplate.Type]; !ok {
return fmt.Errorf("template %s has an invalid type: %s", path, currTemplate.Type)
}

// version range check once we define versions
// if _, err := semver.ParseRange(currTemplate.Versions); err != nil {
// return fmt.Errorf("template %s has an invalid version range: %s", path, currTemplate.Versions)
// }

referenceVarMap := map[string]*BuilderVar{}
allVariables := map[string]*BuilderVar{}
for _, variable := range currTemplate.Variables {
if variable.Name == "" {
return fmt.Errorf("template %s has a variable with no name", path)
Expand All @@ -108,7 +115,56 @@ func loadTemplatesWithValidation() error {
if _, ok := validVariableKinds[variable.Kind]; !ok {
return fmt.Errorf("template %s has an invalid variable kind: %s", path, variable.Kind)
}

// version range check once we define versions
// if _, err := semver.ParseRange(variable.Versions); err != nil {
// return fmt.Errorf("template %s has an invalid version range: %s", path, variable.Versions)
// }

allVariables[variable.Name] = variable
if variable.Default.ReferenceVar != "" {
referenceVarMap[variable.Name] = variable
}
}

for _, currVar := range referenceVarMap {
refVar, ok := allVariables[currVar.Default.ReferenceVar]
if !ok {
return fmt.Errorf("template %s has a variable %s with reference to a non-existent variable: %s", path, currVar.Name, currVar.Default.ReferenceVar)
}

if currVar.Name == refVar.Name {
return fmt.Errorf("template %s has a variable with cyclical reference to itself: %s", path, currVar.Name)
}

if isCyclicalVariableReference(currVar, refVar, allVariables, map[string]bool{}) {
return fmt.Errorf("template %s has a variable with cyclical reference to itself: %s", path, currVar.Name)
}
}

allTemplates[strings.ToLower(currTemplate.TemplateName)] = currTemplate
return nil
})
}

func isCyclicalVariableReference(initialVar, currRefVar *BuilderVar, allVariables map[string]*BuilderVar, visited map[string]bool) bool {
if initialVar.Name == currRefVar.Name {
return true
}

if _, ok := visited[currRefVar.Name]; ok {
return true
}

if currRefVar.Default.ReferenceVar == "" {
return false
}

refVar, ok := allVariables[currRefVar.Default.ReferenceVar]
if !ok {
return false
}

visited[currRefVar.Name] = true
return isCyclicalVariableReference(initialVar, refVar, allVariables, visited)
}
168 changes: 168 additions & 0 deletions pkg/config/draftconfig_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,3 +197,171 @@ func TestApplyDefaultVariables(t *testing.T) {
})
}
}

func TestApplyDefaultVariablesForVersion(t *testing.T) {
tests := []struct {
testName string
version string
draftConfig DraftConfig
customInputs map[string]string
want map[string]string
wantErrMsg string
}{
{
testName: "excludeOutOfVersionRangeVariables",
version: "0.0.1",
draftConfig: DraftConfig{
Versions: ">=0.0.1 <=0.0.2",
Variables: []*BuilderVar{
{
Name: "var1",
Value: "",
Versions: ">=0.0.1",
Default: BuilderVarDefault{
Value: "default-value-1",
},
},
{
Name: "var2",
Value: "custom-value-2",
Versions: ">=0.0.2",
Default: BuilderVarDefault{
Value: "default-value-2",
},
},
},
},
want: map[string]string{
"var1": "default-value-1",
},
},
{
testName: "emptyInputVersion",
version: "",
draftConfig: DraftConfig{
Versions: ">=0.0.1 <=0.0.2",
Variables: []*BuilderVar{
{
Name: "var1",
Value: "",
Versions: ">=0.0.1",
Default: BuilderVarDefault{
Value: "default-value-1",
},
},
{
Name: "var2",
Value: "",
Versions: ">=0.0.2",
Default: BuilderVarDefault{
Value: "default-value-2",
},
},
},
},
wantErrMsg: "invalid version: Version string empty",
},
{
testName: "inputVersionOutOfRange",
version: "0.0.3",
draftConfig: DraftConfig{
Versions: ">=0.0.1 <=0.0.2",
Variables: []*BuilderVar{
{
Name: "var1",
Value: "",
Versions: ">=0.0.1",
Default: BuilderVarDefault{
Value: "default-value-1",
},
},
{
Name: "var2",
Value: "",
Versions: ">=0.0.2",
Default: BuilderVarDefault{
Value: "default-value-2",
},
},
},
},
wantErrMsg: "version 0.0.3 is outside of config version range >=0.0.1 <=0.0.2",
},
{
testName: "overwriteDevfaultValue",
version: "0.0.2",
draftConfig: DraftConfig{
Versions: ">=0.0.1 <=0.0.2",
Variables: []*BuilderVar{
{
Name: "var1",
Value: "custom-value-1",
Versions: ">=0.0.1",
Default: BuilderVarDefault{
Value: "default-value-1",
},
},
{
Name: "var2",
Value: "custom-value-2",
Versions: ">=0.0.2",
Default: BuilderVarDefault{
Value: "default-value-2",
},
},
},
},
want: map[string]string{
"var1": "custom-value-1",
"var2": "custom-value-2",
},
},
{
testName: "referenceVarOverwrite",
version: "0.0.2",
draftConfig: DraftConfig{
Versions: ">=0.0.1 <=0.0.2",
Variables: []*BuilderVar{
{
Name: "var1",
Value: "",
Versions: ">=0.0.1",
Default: BuilderVarDefault{
ReferenceVar: "var2",
},
},
{
Name: "var2",
Value: "custom-value-2",
Versions: ">=0.0.2",
Default: BuilderVarDefault{
Value: "default-value-2",
},
},
},
},
want: map[string]string{
"var1": "custom-value-2",
"var2": "custom-value-2",
},
},
}
for _, tt := range tests {
t.Run(tt.testName, func(t *testing.T) {
if err := tt.draftConfig.ApplyDefaultVariablesForVersion(tt.version); err != nil && err.Error() != tt.wantErrMsg {
t.Error(err)
} else {
for k, v := range tt.want {
variable, err := tt.draftConfig.GetVariable(k)
if err != nil {
t.Error(err)
}

if variable.Value != v {
t.Errorf("got: %s, want: %s, for test: %s", variable.Value, v, tt.testName)
}
}
}
})
}
}
Loading

0 comments on commit b7ed7a0

Please sign in to comment.