@@ -3,13 +3,17 @@ package config
33import (
44 "fmt"
55 "io/fs"
6+ "regexp"
67 "strings"
78 "testing"
89
910 "github.com/Azure/draft/template"
11+ "github.com/blang/semver/v4"
1012 "github.com/stretchr/testify/assert"
1113)
1214
15+ const alphaNumUnderscoreHyphen = "^[A-Za-z][A-Za-z0-9-_]{1,62}[A-Za-z0-9]$"
16+
1317var allTemplates = map [string ]* DraftConfig {}
1418
1519var validTemplateTypes = map [string ]bool {
@@ -67,6 +71,7 @@ func TestTempalteValidation(t *testing.T) {
6771}
6872
6973func loadTemplatesWithValidation () error {
74+ regexp := regexp .MustCompile (alphaNumUnderscoreHyphen )
7075 return fs .WalkDir (template .Templates , "." , func (path string , d fs.DirEntry , err error ) error {
7176 if err != nil {
7277 return err
@@ -93,6 +98,10 @@ func loadTemplatesWithValidation() error {
9398 return fmt .Errorf ("template %s has no template name" , path )
9499 }
95100
101+ if ! regexp .MatchString (currTemplate .TemplateName ) {
102+ return fmt .Errorf ("template %s name must match the alpha-numeric-underscore-hyphen regex: %s" , path , currTemplate .TemplateName )
103+ }
104+
96105 if _ , ok := allTemplates [strings .ToLower (currTemplate .TemplateName )]; ok {
97106 return fmt .Errorf ("template %s has a duplicate template name" , path )
98107 }
@@ -101,12 +110,12 @@ func loadTemplatesWithValidation() error {
101110 return fmt .Errorf ("template %s has an invalid type: %s" , path , currTemplate .Type )
102111 }
103112
104- // version range check once we define versions
105- // if _, err := semver.ParseRange(currTemplate.Versions); err != nil {
106- // return fmt.Errorf("template %s has an invalid version range: %s", path, currTemplate.Versions)
107- // }
113+ if _ , err := semver .ParseRange (currTemplate .Versions ); err != nil {
114+ return fmt .Errorf ("template %s has an invalid version range: %s" , path , currTemplate .Versions )
115+ }
108116
109117 referenceVarMap := map [string ]* BuilderVar {}
118+ conditionRefMap := map [string ]* BuilderVar {}
110119 allVariables := map [string ]* BuilderVar {}
111120 for _ , variable := range currTemplate .Variables {
112121 if variable .Name == "" {
@@ -121,29 +130,43 @@ func loadTemplatesWithValidation() error {
121130 return fmt .Errorf ("template %s has an invalid variable kind: %s" , path , variable .Kind )
122131 }
123132
124- // version range check once we define versions
125- // if _, err := semver.ParseRange(variable.Versions); err != nil {
126- // return fmt.Errorf("template %s has an invalid version range: %s", path, variable.Versions)
127- // }
133+ if _ , err := semver .ParseRange (variable .Versions ); err != nil {
134+ return fmt .Errorf ("template %s has an invalid version range: %s" , path , variable .Versions )
135+ }
128136
129137 allVariables [variable .Name ] = variable
130138 if variable .Default .ReferenceVar != "" {
131139 referenceVarMap [variable .Name ] = variable
132140 }
141+
142+ if variable .ConditionalRef .ReferenceVar != "" {
143+ conditionRefMap [variable .Name ] = variable
144+ }
133145 }
134146
135147 for _ , currVar := range referenceVarMap {
136148 refVar , ok := allVariables [currVar .Default .ReferenceVar ]
137149 if ! ok {
138- return fmt .Errorf ("template %s has a variable %s with reference to a non-existent variable: %s" , path , currVar .Name , currVar .Default .ReferenceVar )
150+ return fmt .Errorf ("template %s has a variable %s with default reference to a non-existent variable: %s" , path , currVar .Name , currVar .Default .ReferenceVar )
139151 }
140152
141153 if currVar .Name == refVar .Name {
142- return fmt .Errorf ("template %s has a variable with cyclical reference to itself: %s" , path , currVar .Name )
154+ return fmt .Errorf ("template %s has a variable with cyclical default reference to itself: %s" , path , currVar .Name )
155+ }
156+
157+ if isCyclicalDefaultVariableReference (currVar , refVar , allVariables , map [string ]bool {}) {
158+ return fmt .Errorf ("template %s has a variable with cyclical default reference to itself: %s" , path , currVar .Name )
159+ }
160+ }
161+
162+ for _ , currVar := range conditionRefMap {
163+ refVar , ok := allVariables [currVar .ConditionalRef .ReferenceVar ]
164+ if ! ok {
165+ return fmt .Errorf ("template %s has a variable %s with conditional reference to a non-existent variable: %s" , path , currVar .Name , currVar .ConditionalRef .ReferenceVar )
143166 }
144167
145- if isCyclicalVariableReference (currVar , refVar , allVariables , map [string ]bool {}) {
146- return fmt .Errorf ("template %s has a variable with cyclical reference to itself: %s" , path , currVar .Name )
168+ if isCyclicalConditionalVariableReference (currVar , refVar , allVariables , map [string ]bool {}) {
169+ return fmt .Errorf ("template %s has a variable with cyclical conditional reference to itself or references a non existing variable : %s" , path , currVar .Name )
147170 }
148171 }
149172
@@ -152,7 +175,7 @@ func loadTemplatesWithValidation() error {
152175 })
153176}
154177
155- func isCyclicalVariableReference (initialVar , currRefVar * BuilderVar , allVariables map [string ]* BuilderVar , visited map [string ]bool ) bool {
178+ func isCyclicalDefaultVariableReference (initialVar , currRefVar * BuilderVar , allVariables map [string ]* BuilderVar , visited map [string ]bool ) bool {
156179 if initialVar .Name == currRefVar .Name {
157180 return true
158181 }
@@ -171,5 +194,27 @@ func isCyclicalVariableReference(initialVar, currRefVar *BuilderVar, allVariable
171194 }
172195
173196 visited [currRefVar .Name ] = true
174- return isCyclicalVariableReference (initialVar , refVar , allVariables , visited )
197+ return isCyclicalDefaultVariableReference (initialVar , refVar , allVariables , visited )
198+ }
199+
200+ func isCyclicalConditionalVariableReference (initialVar , currRefVar * BuilderVar , allVariables map [string ]* BuilderVar , visited map [string ]bool ) bool {
201+ if initialVar .Name == currRefVar .Name {
202+ return true
203+ }
204+
205+ if _ , ok := visited [currRefVar .Name ]; ok {
206+ return true
207+ }
208+
209+ if currRefVar .ConditionalRef .ReferenceVar == "" {
210+ return false
211+ }
212+
213+ refVar , ok := allVariables [currRefVar .ConditionalRef .ReferenceVar ]
214+ if ! ok {
215+ return false
216+ }
217+
218+ visited [currRefVar .Name ] = true
219+ return isCyclicalConditionalVariableReference (initialVar , refVar , allVariables , visited )
175220}
0 commit comments