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 authored Oct 7, 2024
1 parent b4c1f53 commit abc79e6
Show file tree
Hide file tree
Showing 10 changed files with 602 additions and 2 deletions.
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 abc79e6

Please sign in to comment.