Skip to content

Commit e896b21

Browse files
authored
Updating templates and moving tests (#428)
1 parent 53599db commit e896b21

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+1501
-629
lines changed

pkg/config/draftconfig.go

Lines changed: 109 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,17 @@ import (
1313
"github.com/blang/semver/v4"
1414
)
1515

16+
type VariableCondition string
17+
18+
const (
19+
EqualTo VariableCondition = "equals"
20+
NotEqualTo VariableCondition = "notequals"
21+
)
22+
23+
func (v VariableCondition) String() string {
24+
return string(v)
25+
}
26+
1627
const draftConfigFile = "draft.yaml"
1728

1829
type VariableValidator func(string) error
@@ -32,15 +43,16 @@ type DraftConfig struct {
3243
}
3344

3445
type BuilderVar struct {
35-
Name string `yaml:"name"`
36-
ConditionalRef BuilderVarConditionalReference `yaml:"conditionalReference"`
37-
Default BuilderVarDefault `yaml:"default"`
38-
Description string `yaml:"description"`
39-
ExampleValues []string `yaml:"exampleValues"`
40-
Type string `yaml:"type"`
41-
Kind string `yaml:"kind"`
42-
Value string `yaml:"value"`
43-
Versions string `yaml:"versions"`
46+
Name string `yaml:"name"`
47+
ActiveWhenConstraints []ActiveWhenConstraint `yaml:"activeWhen"`
48+
Default BuilderVarDefault `yaml:"default"`
49+
Description string `yaml:"description"`
50+
ExampleValues []string `yaml:"exampleValues"`
51+
AllowedValues []string `yaml:"allowedValues"`
52+
Type string `yaml:"type"`
53+
Kind string `yaml:"kind"`
54+
Value string `yaml:"value"`
55+
Versions string `yaml:"versions"`
4456
}
4557

4658
// BuilderVarDefault holds info on the default value of a variable
@@ -50,9 +62,11 @@ type BuilderVarDefault struct {
5062
Value string `yaml:"value"`
5163
}
5264

53-
// BuilderVarConditionalReference holds a reference to a variable thats value can effect validation/transformation of the associated variable
54-
type BuilderVarConditionalReference struct {
55-
ReferenceVar string `yaml:"referenceVar"`
65+
// ActiveWhenConstraints holds information on when a variable is actively used by a template based off other variable values
66+
type ActiveWhenConstraint struct {
67+
VariableName string `yaml:"variableName"`
68+
Value string `yaml:"value"`
69+
Condition VariableCondition `yaml:"condition"`
5670
}
5771

5872
func NewConfigFromFS(fileSys fs.FS, path string) (*DraftConfig, error) {
@@ -188,6 +202,15 @@ func (d *DraftConfig) ApplyDefaultVariables() error {
188202
variable.Value = defaultVal
189203
}
190204

205+
isVarActive, err := d.CheckActiveWhenConstraint(variable)
206+
if err != nil {
207+
return fmt.Errorf("unable to check ActiveWhen constraint: %w", err)
208+
}
209+
210+
if !isVarActive {
211+
continue
212+
}
213+
191214
if variable.Value == "" {
192215
if variable.Default.Value != "" {
193216
log.Infof("Variable %s defaulting to value %s", variable.Name, variable.Default.Value)
@@ -244,6 +267,15 @@ func (d *DraftConfig) ApplyDefaultVariablesForVersion(version string) error {
244267
variable.Value = defaultVal
245268
}
246269

270+
isVarActive, err := d.CheckActiveWhenConstraint(variable)
271+
if err != nil {
272+
return fmt.Errorf("unable to check ActiveWhen constraint: %w", err)
273+
}
274+
275+
if !isVarActive {
276+
continue
277+
}
278+
247279
if variable.Value == "" {
248280
if variable.Default.Value != "" {
249281
log.Infof("Variable %s defaulting to value %s", variable.Name, variable.Default.Value)
@@ -258,6 +290,49 @@ func (d *DraftConfig) ApplyDefaultVariablesForVersion(version string) error {
258290
return nil
259291
}
260292

293+
func (d *DraftConfig) CheckActiveWhenConstraint(variable *BuilderVar) (bool, error) {
294+
if len(variable.ActiveWhenConstraints) > 0 {
295+
isVarActive := true
296+
for _, activeWhen := range variable.ActiveWhenConstraints {
297+
refVar, err := d.GetVariable(activeWhen.VariableName)
298+
if err != nil {
299+
return false, fmt.Errorf("unable to get ActiveWhen reference variable: %w", err)
300+
}
301+
302+
checkValue := refVar.Value
303+
if checkValue == "" {
304+
if refVar.Default.Value != "" {
305+
checkValue = refVar.Default.Value
306+
}
307+
308+
if refVar.Default.ReferenceVar != "" {
309+
refValue, err := d.recurseReferenceVars(refVar, refVar, true)
310+
if err != nil {
311+
return false, err
312+
}
313+
if refValue == "" {
314+
return false, errors.New("reference variable has no value")
315+
}
316+
317+
checkValue = refValue
318+
}
319+
}
320+
321+
switch activeWhen.Condition {
322+
case EqualTo:
323+
isVarActive = checkValue == activeWhen.Value
324+
case NotEqualTo:
325+
isVarActive = checkValue != activeWhen.Value
326+
default:
327+
return false, fmt.Errorf("invalid activeWhen condition: %s", activeWhen.Condition)
328+
}
329+
}
330+
return isVarActive, nil
331+
}
332+
333+
return true, nil
334+
}
335+
261336
// 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.
262337
func (d *DraftConfig) recurseReferenceVars(referenceVar *BuilderVar, variableCheck *BuilderVar, isFirst bool) (string, error) {
263338
if !isFirst && referenceVar.Name == variableCheck.Name {
@@ -319,20 +394,33 @@ func (d *DraftConfig) DeepCopy() *DraftConfig {
319394

320395
func (bv *BuilderVar) DeepCopy() *BuilderVar {
321396
newVar := &BuilderVar{
322-
Name: bv.Name,
323-
Default: bv.Default,
324-
Description: bv.Description,
325-
Type: bv.Type,
326-
Kind: bv.Kind,
327-
Value: bv.Value,
328-
Versions: bv.Versions,
329-
ExampleValues: make([]string, len(bv.ExampleValues)),
397+
Name: bv.Name,
398+
Default: bv.Default,
399+
Description: bv.Description,
400+
Type: bv.Type,
401+
Kind: bv.Kind,
402+
Value: bv.Value,
403+
Versions: bv.Versions,
404+
ExampleValues: make([]string, len(bv.ExampleValues)),
405+
AllowedValues: make([]string, len(bv.AllowedValues)),
406+
ActiveWhenConstraints: make([]ActiveWhenConstraint, len(bv.ActiveWhenConstraints)),
330407
}
331-
408+
for i, awc := range bv.ActiveWhenConstraints {
409+
newVar.ActiveWhenConstraints[i] = *awc.DeepCopy()
410+
}
411+
copy(newVar.AllowedValues, bv.AllowedValues)
332412
copy(newVar.ExampleValues, bv.ExampleValues)
333413
return newVar
334414
}
335415

416+
func (awc ActiveWhenConstraint) DeepCopy() *ActiveWhenConstraint {
417+
return &ActiveWhenConstraint{
418+
VariableName: awc.VariableName,
419+
Value: awc.Value,
420+
Condition: awc.Condition,
421+
}
422+
}
423+
336424
// TemplateVariableRecorder is an interface for recording variables that are read using draft configs
337425
type TemplateVariableRecorder interface {
338426
Record(key, value string)

pkg/config/draftconfig_template_test.go

Lines changed: 35 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55
"io/fs"
66
"regexp"
7+
"slices"
78
"strings"
89
"testing"
910

@@ -44,11 +45,14 @@ var validVariableKinds = map[string]bool{
4445
"filePath": true,
4546
"flag": true,
4647
"helmChartOverrides": true,
48+
"imagePullPolicy": true,
4749
"ingressHostName": true,
4850
"kubernetesNamespace": true,
51+
"kubernetesProbeHttpPath": true,
4952
"kubernetesProbePeriod": true,
5053
"kubernetesProbeTimeout": true,
5154
"kubernetesProbeThreshold": true,
55+
"kubernetesProbeType": true,
5256
"kubernetesProbeDelay": true,
5357
"kubernetesResourceLimit": true,
5458
"kubernetesResourceName": true,
@@ -122,7 +126,7 @@ func loadTemplatesWithValidation() error {
122126
}
123127

124128
referenceVarMap := map[string]*BuilderVar{}
125-
conditionRefMap := map[string]*BuilderVar{}
129+
activeWhenRefMap := map[string]*BuilderVar{}
126130
allVariables := map[string]*BuilderVar{}
127131
for _, variable := range currTemplate.Variables {
128132
if variable.Name == "" {
@@ -146,8 +150,13 @@ func loadTemplatesWithValidation() error {
146150
referenceVarMap[variable.Name] = variable
147151
}
148152

149-
if variable.ConditionalRef.ReferenceVar != "" {
150-
conditionRefMap[variable.Name] = variable
153+
for _, activeWhen := range variable.ActiveWhenConstraints {
154+
if activeWhen.VariableName != "" {
155+
activeWhenRefMap[variable.Name] = variable
156+
}
157+
if !isValidVariableCondition(activeWhen.Condition) {
158+
return fmt.Errorf("template %s has a variable %s with an invalid activeWhen condition: %s", path, variable.Name, activeWhen.Condition)
159+
}
151160
}
152161
}
153162

@@ -166,14 +175,25 @@ func loadTemplatesWithValidation() error {
166175
}
167176
}
168177

169-
for _, currVar := range conditionRefMap {
170-
refVar, ok := allVariables[currVar.ConditionalRef.ReferenceVar]
171-
if !ok {
172-
return fmt.Errorf("template %s has a variable %s with conditional reference to a non-existent variable: %s", path, currVar.Name, currVar.ConditionalRef.ReferenceVar)
173-
}
174-
175-
if isCyclicalConditionalVariableReference(currVar, refVar, allVariables, map[string]bool{}) {
176-
return fmt.Errorf("template %s has a variable with cyclical conditional reference to itself or references a non existing variable: %s", path, currVar.Name)
178+
for _, currVar := range activeWhenRefMap {
179+
180+
for _, activeWhen := range currVar.ActiveWhenConstraints {
181+
refVar, ok := allVariables[activeWhen.VariableName]
182+
if !ok {
183+
return fmt.Errorf("template %s has a variable %s with ActiveWhen reference to a non-existent variable: %s", path, currVar.Name, activeWhen.VariableName)
184+
}
185+
186+
if currVar.Name == refVar.Name {
187+
return fmt.Errorf("template %s has a variable with cyclical conditional reference to itself: %s", path, currVar.Name)
188+
}
189+
190+
if refVar.Type == "bool" {
191+
if activeWhen.Value != "true" && activeWhen.Value != "false" {
192+
return fmt.Errorf("template %s has a variable %s with ActiveWhen reference to a non-boolean value: %s", path, currVar.Name, activeWhen.Value)
193+
}
194+
} else if !slices.Contains(refVar.AllowedValues, activeWhen.Value) {
195+
return fmt.Errorf("template %s has a variable %s with ActiveWhen reference to a non-existent allowed value: %s", path, currVar.Name, activeWhen.Value)
196+
}
177197
}
178198
}
179199

@@ -204,24 +224,11 @@ func isCyclicalDefaultVariableReference(initialVar, currRefVar *BuilderVar, allV
204224
return isCyclicalDefaultVariableReference(initialVar, refVar, allVariables, visited)
205225
}
206226

207-
func isCyclicalConditionalVariableReference(initialVar, currRefVar *BuilderVar, allVariables map[string]*BuilderVar, visited map[string]bool) bool {
208-
if initialVar.Name == currRefVar.Name {
209-
return true
210-
}
211-
212-
if _, ok := visited[currRefVar.Name]; ok {
227+
func isValidVariableCondition(condition VariableCondition) bool {
228+
switch condition {
229+
case EqualTo, NotEqualTo:
213230
return true
214-
}
215-
216-
if currRefVar.ConditionalRef.ReferenceVar == "" {
217-
return false
218-
}
219-
220-
refVar, ok := allVariables[currRefVar.ConditionalRef.ReferenceVar]
221-
if !ok {
231+
default:
222232
return false
223233
}
224-
225-
visited[currRefVar.Name] = true
226-
return isCyclicalConditionalVariableReference(initialVar, refVar, allVariables, visited)
227234
}

pkg/config/validators/validators.go

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,52 @@ import (
88
func GetValidator(variableKind string) func(string) error {
99
switch variableKind {
1010
case "envVarMap":
11-
return KeyValueMapValidator
11+
return keyValueMapValidator
12+
case "imagePullPolicy":
13+
return imagePullPolicyValidator
14+
case "kubernetesProbeType":
15+
return kubernetesProbeTypeValidator
16+
case "scalingResourceType":
17+
return scalingResourceTypeValidator
1218
default:
13-
return DefaultValidator
19+
return defaultValidator
1420
}
1521
}
1622

17-
func KeyValueMapValidator(input string) error {
23+
func imagePullPolicyValidator(input string) error {
24+
switch input {
25+
case "Always", "IfNotPresent", "Never":
26+
return nil
27+
default:
28+
return fmt.Errorf("invalid image pull policy: %s. valid values: Always, IfNotPresent, Never", input)
29+
}
30+
}
31+
32+
func scalingResourceTypeValidator(input string) error {
33+
switch input {
34+
case "cpu", "memory":
35+
return nil
36+
default:
37+
return fmt.Errorf("invalid scaling resource type: %s. valid values: cpu, memory", input)
38+
}
39+
}
40+
41+
func kubernetesProbeTypeValidator(input string) error {
42+
switch input {
43+
case "httpGet", "tcpSocket":
44+
return nil
45+
default:
46+
return fmt.Errorf("invalid probe type: %s. valid values: httpGet, tcpSocket", input)
47+
}
48+
}
49+
50+
func keyValueMapValidator(input string) error {
1851
if err := json.Unmarshal([]byte(input), &map[string]string{}); err != nil {
1952
return fmt.Errorf("failed to unmarshal variable as map[string]string: %s", err)
2053
}
2154
return nil
2255
}
2356

24-
func DefaultValidator(input string) error {
57+
func defaultValidator(input string) error {
2558
return nil
2659
}

pkg/config/validators/validators_test.go

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,29 @@ func TestGetValidator(t *testing.T) {
1111
}
1212

1313
func TestDefaultValidator(t *testing.T) {
14-
assert.Nil(t, DefaultValidator("test"))
14+
assert.Nil(t, defaultValidator("test"))
15+
}
16+
17+
func TestKubernetesProbeTypeValidator(t *testing.T) {
18+
assert.Nil(t, kubernetesProbeTypeValidator("httpGet"))
19+
assert.Nil(t, kubernetesProbeTypeValidator("tcpSocket"))
20+
assert.NotNil(t, kubernetesProbeTypeValidator("exec"))
21+
}
22+
23+
func TestKeyValueMapValidator(t *testing.T) {
24+
assert.Nil(t, keyValueMapValidator(`{"key": "value"}`))
25+
assert.NotNil(t, keyValueMapValidator(`{"key": "value"`))
26+
}
27+
28+
func TestScalingResourceTypeValidator(t *testing.T) {
29+
assert.Nil(t, scalingResourceTypeValidator("cpu"))
30+
assert.Nil(t, scalingResourceTypeValidator("memory"))
31+
assert.NotNil(t, scalingResourceTypeValidator("disk"))
32+
}
33+
34+
func TestImagePullPolicyValidator(t *testing.T) {
35+
assert.Nil(t, imagePullPolicyValidator("Always"))
36+
assert.Nil(t, imagePullPolicyValidator("IfNotPresent"))
37+
assert.Nil(t, imagePullPolicyValidator("Never"))
38+
assert.NotNil(t, imagePullPolicyValidator("Sometimes"))
1539
}

pkg/fixtures/deployments/helm/charts/templates/deployment.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,16 @@ spec:
4242
{{- toYaml .Values.livenessProbe | nindent 12 }}
4343
readinessProbe:
4444
{{- toYaml .Values.readinessProbe | nindent 12 }}
45+
startupProbe:
46+
{{- toYaml .Values.startupProbe | nindent 12 }}
4547
resources:
4648
{{- toYaml .Values.resources | nindent 12 }}
4749
envFrom:
4850
- configMapRef:
4951
name: {{ include "testapp.fullname" . }}-config
52+
- secretRef:
53+
name: secret-ref
54+
optional: true
5055
{{- with .Values.nodeSelector }}
5156
nodeSelector:
5257
{{- toYaml . | nindent 8 }}

0 commit comments

Comments
 (0)