Skip to content

Commit ede9b50

Browse files
committed
address comments
1 parent 0186b85 commit ede9b50

File tree

6 files changed

+200
-87
lines changed

6 files changed

+200
-87
lines changed

pkg/config/draftconfig.go

Lines changed: 122 additions & 23 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,16 +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-
AllowedValues []string `yaml:"allowedValues"`
41-
Type string `yaml:"type"`
42-
Kind string `yaml:"kind"`
43-
Value string `yaml:"value"`
44-
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"`
4556
}
4657

4758
// BuilderVarDefault holds info on the default value of a variable
@@ -51,10 +62,11 @@ type BuilderVarDefault struct {
5162
Value string `yaml:"value"`
5263
}
5364

54-
// BuilderVarConditionalReference holds a reference to a variable thats value can effect usage/validation/transformation of the associated variable
55-
type BuilderVarConditionalReference struct {
56-
ReferenceVar string `yaml:"referenceVar"`
57-
ConditionValue string `yaml:"conditionValue"`
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"`
5870
}
5971

6072
func NewConfigFromFS(fileSys fs.FS, path string) (*DraftConfig, error) {
@@ -190,6 +202,28 @@ func (d *DraftConfig) ApplyDefaultVariables() error {
190202
variable.Value = defaultVal
191203
}
192204

205+
if len(variable.ActiveWhenConstraints) > 0 {
206+
isVarActive := true
207+
for _, activeWhen := range variable.ActiveWhenConstraints {
208+
refVar, err := d.GetVariable(activeWhen.VariableName)
209+
if err != nil {
210+
return fmt.Errorf("unable to get ActiveWhen reference variable: %w", err)
211+
}
212+
213+
isConditionTrue, err := d.CheckActiveWhenConstraint(refVar, activeWhen)
214+
if err != nil {
215+
return fmt.Errorf("unable to check ActiveWhen constraint: %w", err)
216+
}
217+
218+
if !isConditionTrue {
219+
isVarActive = false
220+
}
221+
}
222+
if !isVarActive {
223+
continue
224+
}
225+
}
226+
193227
if variable.Value == "" {
194228
if variable.Default.Value != "" {
195229
log.Infof("Variable %s defaulting to value %s", variable.Name, variable.Default.Value)
@@ -246,6 +280,28 @@ func (d *DraftConfig) ApplyDefaultVariablesForVersion(version string) error {
246280
variable.Value = defaultVal
247281
}
248282

283+
if len(variable.ActiveWhenConstraints) > 0 {
284+
isVarActive := true
285+
for _, activeWhen := range variable.ActiveWhenConstraints {
286+
refVar, err := d.GetVariable(activeWhen.VariableName)
287+
if err != nil {
288+
return fmt.Errorf("unable to get ActiveWhen reference variable: %w", err)
289+
}
290+
291+
isConditionTrue, err := d.CheckActiveWhenConstraint(refVar, activeWhen)
292+
if err != nil {
293+
return fmt.Errorf("unable to check ActiveWhen constraint: %w", err)
294+
}
295+
296+
if !isConditionTrue {
297+
isVarActive = false
298+
}
299+
}
300+
if !isVarActive {
301+
continue
302+
}
303+
}
304+
249305
if variable.Value == "" {
250306
if variable.Default.Value != "" {
251307
log.Infof("Variable %s defaulting to value %s", variable.Name, variable.Default.Value)
@@ -260,6 +316,36 @@ func (d *DraftConfig) ApplyDefaultVariablesForVersion(version string) error {
260316
return nil
261317
}
262318

319+
func (d *DraftConfig) CheckActiveWhenConstraint(refVar *BuilderVar, activeWhen ActiveWhenConstraint) (bool, error) {
320+
checkValue := refVar.Value
321+
if checkValue == "" {
322+
if refVar.Default.Value != "" {
323+
checkValue = refVar.Default.Value
324+
}
325+
326+
if refVar.Default.ReferenceVar != "" {
327+
refValue, err := d.recurseReferenceVars(refVar, refVar, true)
328+
if err != nil {
329+
return false, err
330+
}
331+
if refValue == "" {
332+
return false, errors.New("reference variable has no value")
333+
}
334+
335+
checkValue = refValue
336+
}
337+
}
338+
339+
switch activeWhen.Condition {
340+
case EqualTo:
341+
return checkValue == activeWhen.Value, nil
342+
case NotEqualTo:
343+
return checkValue != activeWhen.Value, nil
344+
}
345+
346+
return false, nil
347+
}
348+
263349
// 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.
264350
func (d *DraftConfig) recurseReferenceVars(referenceVar *BuilderVar, variableCheck *BuilderVar, isFirst bool) (string, error) {
265351
if !isFirst && referenceVar.Name == variableCheck.Name {
@@ -321,20 +407,33 @@ func (d *DraftConfig) DeepCopy() *DraftConfig {
321407

322408
func (bv *BuilderVar) DeepCopy() *BuilderVar {
323409
newVar := &BuilderVar{
324-
Name: bv.Name,
325-
Default: bv.Default,
326-
Description: bv.Description,
327-
Type: bv.Type,
328-
Kind: bv.Kind,
329-
Value: bv.Value,
330-
Versions: bv.Versions,
331-
ExampleValues: make([]string, len(bv.ExampleValues)),
410+
Name: bv.Name,
411+
Default: bv.Default,
412+
Description: bv.Description,
413+
Type: bv.Type,
414+
Kind: bv.Kind,
415+
Value: bv.Value,
416+
Versions: bv.Versions,
417+
ExampleValues: make([]string, len(bv.ExampleValues)),
418+
AllowedValues: make([]string, len(bv.AllowedValues)),
419+
ActiveWhenConstraints: make([]ActiveWhenConstraint, len(bv.ActiveWhenConstraints)),
332420
}
333-
421+
for i, awc := range bv.ActiveWhenConstraints {
422+
newVar.ActiveWhenConstraints[i] = *awc.DeepCopy()
423+
}
424+
copy(newVar.AllowedValues, bv.AllowedValues)
334425
copy(newVar.ExampleValues, bv.ExampleValues)
335426
return newVar
336427
}
337428

429+
func (awc ActiveWhenConstraint) DeepCopy() *ActiveWhenConstraint {
430+
return &ActiveWhenConstraint{
431+
VariableName: awc.VariableName,
432+
Value: awc.Value,
433+
Condition: awc.Condition,
434+
}
435+
}
436+
338437
// TemplateVariableRecorder is an interface for recording variables that are read using draft configs
339438
type TemplateVariableRecorder interface {
340439
Record(key, value string)

pkg/config/draftconfig_template_test.go

Lines changed: 32 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

@@ -125,7 +126,7 @@ func loadTemplatesWithValidation() error {
125126
}
126127

127128
referenceVarMap := map[string]*BuilderVar{}
128-
conditionRefMap := map[string]*BuilderVar{}
129+
activeWhenRefMap := map[string]*BuilderVar{}
129130
allVariables := map[string]*BuilderVar{}
130131
for _, variable := range currTemplate.Variables {
131132
if variable.Name == "" {
@@ -149,8 +150,13 @@ func loadTemplatesWithValidation() error {
149150
referenceVarMap[variable.Name] = variable
150151
}
151152

152-
if variable.ConditionalRef.ReferenceVar != "" {
153-
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+
}
154160
}
155161
}
156162

@@ -169,14 +175,25 @@ func loadTemplatesWithValidation() error {
169175
}
170176
}
171177

172-
for _, currVar := range conditionRefMap {
173-
refVar, ok := allVariables[currVar.ConditionalRef.ReferenceVar]
174-
if !ok {
175-
return fmt.Errorf("template %s has a variable %s with conditional reference to a non-existent variable: %s", path, currVar.Name, currVar.ConditionalRef.ReferenceVar)
176-
}
177-
178-
if isCyclicalConditionalVariableReference(currVar, refVar, allVariables, map[string]bool{}) {
179-
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+
}
180197
}
181198
}
182199

@@ -207,24 +224,11 @@ func isCyclicalDefaultVariableReference(initialVar, currRefVar *BuilderVar, allV
207224
return isCyclicalDefaultVariableReference(initialVar, refVar, allVariables, visited)
208225
}
209226

210-
func isCyclicalConditionalVariableReference(initialVar, currRefVar *BuilderVar, allVariables map[string]*BuilderVar, visited map[string]bool) bool {
211-
if initialVar.Name == currRefVar.Name {
212-
return true
213-
}
214-
215-
if _, ok := visited[currRefVar.Name]; ok {
227+
func isValidVariableCondition(condition VariableCondition) bool {
228+
switch condition {
229+
case EqualTo, NotEqualTo:
216230
return true
217-
}
218-
219-
if currRefVar.ConditionalRef.ReferenceVar == "" {
220-
return false
221-
}
222-
223-
refVar, ok := allVariables[currRefVar.ConditionalRef.ReferenceVar]
224-
if !ok {
231+
default:
225232
return false
226233
}
227-
228-
visited[currRefVar.Name] = true
229-
return isCyclicalConditionalVariableReference(initialVar, refVar, allVariables, visited)
230234
}

pkg/prompts/prompts.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,28 @@ func RunPromptsFromConfigWithSkipsIO(draftConfig *config.DraftConfig, Stdin io.R
5353
continue
5454
}
5555

56+
if len(variable.ActiveWhenConstraints) > 0 {
57+
isVarActive := true
58+
for _, activeWhen := range variable.ActiveWhenConstraints {
59+
refVar, err := draftConfig.GetVariable(activeWhen.VariableName)
60+
if err != nil {
61+
return fmt.Errorf("unable to get ActiveWhen reference variable: %w", err)
62+
}
63+
64+
isConditionTrue, err := draftConfig.CheckActiveWhenConstraint(refVar, activeWhen)
65+
if err != nil {
66+
return fmt.Errorf("unable to check ActiveWhen constraint: %w", err)
67+
}
68+
69+
if !isConditionTrue {
70+
isVarActive = false
71+
}
72+
}
73+
if !isVarActive {
74+
continue
75+
}
76+
}
77+
5678
log.Debugf("constructing prompt for: %s", variable.Name)
5779
if variable.Type == "bool" {
5880
input, err := RunBoolPrompt(variable, Stdin, Stdout)

template/deployments/helm/draft.yaml

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -111,12 +111,10 @@ variables:
111111
- name: "PROBEHTTPPATH"
112112
type: "string"
113113
kind: "kubernetesProbeHttpPath"
114-
default:
115-
disablePrompt: true
116-
value: "/"
117-
conditionalReference:
118-
variable: "PROBETYPE"
119-
condition: "httpGet"
114+
activeWhen:
115+
- variableName: "PROBETYPE"
116+
value: "httpGet"
117+
condition: "equals"
120118
description: "The path to use for the httpGet probes"
121119
versions: ">=0.0.1"
122120
- name: "STARTUPPERIOD"
@@ -218,12 +216,10 @@ variables:
218216
- name: "SERVICEACCOUNT"
219217
type: "string"
220218
kind: "kubernetesResourceName"
221-
conditionalReference:
222-
variable: "ENABLEWORKLOADIDENTITY"
223-
conditionValue: true
224-
default:
225-
disablePrompt: true
226-
value: "service-account"
219+
activeWhen:
220+
- variableName: "ENABLEWORKLOADIDENTITY"
221+
value: "true"
222+
condition: "equals"
227223
description: "the name of the service account to use with workload identity"
228224
versions: ">=0.0.1"
229225
- name: "ENVSECRETREF"

template/deployments/kustomize/draft.yaml

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -111,12 +111,10 @@ variables:
111111
- name: "PROBEHTTPPATH"
112112
type: "string"
113113
kind: "kubernetesProbeHttpPath"
114-
default:
115-
disablePrompt: true
116-
value: "/"
117-
conditionalReference:
118-
variable: "PROBETYPE"
119-
conditionValue: "httpGet"
114+
activeWhen:
115+
- variableName: "PROBETYPE"
116+
value: "httpGet"
117+
condition: "equals"
120118
description: "The path to use for the httpGet probes"
121119
versions: ">=0.0.1"
122120
- name: "STARTUPPERIOD"
@@ -226,11 +224,9 @@ variables:
226224
- name: "SERVICEACCOUNT"
227225
type: "string"
228226
kind: "kubernetesResourceName"
229-
conditionalReference:
230-
variable: "ENABLEWORKLOADIDENTITY"
231-
conditionValue: true
232-
default:
233-
disablePrompt: true
234-
value: "service-account"
227+
activeWhen:
228+
- variableName: "ENABLEWORKLOADIDENTITY"
229+
value: "true"
230+
condition: "equals"
235231
description: "the name of the service account to use with workload identity"
236232
versions: ">=0.0.1"

0 commit comments

Comments
 (0)