Skip to content

Commit

Permalink
Updating templates and moving tests (#428)
Browse files Browse the repository at this point in the history
  • Loading branch information
bfoley13 authored Nov 21, 2024
1 parent 53599db commit e896b21
Show file tree
Hide file tree
Showing 44 changed files with 1,501 additions and 629 deletions.
130 changes: 109 additions & 21 deletions pkg/config/draftconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,17 @@ import (
"github.com/blang/semver/v4"
)

type VariableCondition string

const (
EqualTo VariableCondition = "equals"
NotEqualTo VariableCondition = "notequals"
)

func (v VariableCondition) String() string {
return string(v)
}

const draftConfigFile = "draft.yaml"

type VariableValidator func(string) error
Expand All @@ -32,15 +43,16 @@ type DraftConfig struct {
}

type BuilderVar struct {
Name string `yaml:"name"`
ConditionalRef BuilderVarConditionalReference `yaml:"conditionalReference"`
Default BuilderVarDefault `yaml:"default"`
Description string `yaml:"description"`
ExampleValues []string `yaml:"exampleValues"`
Type string `yaml:"type"`
Kind string `yaml:"kind"`
Value string `yaml:"value"`
Versions string `yaml:"versions"`
Name string `yaml:"name"`
ActiveWhenConstraints []ActiveWhenConstraint `yaml:"activeWhen"`
Default BuilderVarDefault `yaml:"default"`
Description string `yaml:"description"`
ExampleValues []string `yaml:"exampleValues"`
AllowedValues []string `yaml:"allowedValues"`
Type string `yaml:"type"`
Kind string `yaml:"kind"`
Value string `yaml:"value"`
Versions string `yaml:"versions"`
}

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

// BuilderVarConditionalReference holds a reference to a variable thats value can effect validation/transformation of the associated variable
type BuilderVarConditionalReference struct {
ReferenceVar string `yaml:"referenceVar"`
// ActiveWhenConstraints holds information on when a variable is actively used by a template based off other variable values
type ActiveWhenConstraint struct {
VariableName string `yaml:"variableName"`
Value string `yaml:"value"`
Condition VariableCondition `yaml:"condition"`
}

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

isVarActive, err := d.CheckActiveWhenConstraint(variable)
if err != nil {
return fmt.Errorf("unable to check ActiveWhen constraint: %w", err)
}

if !isVarActive {
continue
}

if variable.Value == "" {
if variable.Default.Value != "" {
log.Infof("Variable %s defaulting to value %s", variable.Name, variable.Default.Value)
Expand Down Expand Up @@ -244,6 +267,15 @@ func (d *DraftConfig) ApplyDefaultVariablesForVersion(version string) error {
variable.Value = defaultVal
}

isVarActive, err := d.CheckActiveWhenConstraint(variable)
if err != nil {
return fmt.Errorf("unable to check ActiveWhen constraint: %w", err)
}

if !isVarActive {
continue
}

if variable.Value == "" {
if variable.Default.Value != "" {
log.Infof("Variable %s defaulting to value %s", variable.Name, variable.Default.Value)
Expand All @@ -258,6 +290,49 @@ func (d *DraftConfig) ApplyDefaultVariablesForVersion(version string) error {
return nil
}

func (d *DraftConfig) CheckActiveWhenConstraint(variable *BuilderVar) (bool, error) {
if len(variable.ActiveWhenConstraints) > 0 {
isVarActive := true
for _, activeWhen := range variable.ActiveWhenConstraints {
refVar, err := d.GetVariable(activeWhen.VariableName)
if err != nil {
return false, fmt.Errorf("unable to get ActiveWhen reference variable: %w", err)
}

checkValue := refVar.Value
if checkValue == "" {
if refVar.Default.Value != "" {
checkValue = refVar.Default.Value
}

if refVar.Default.ReferenceVar != "" {
refValue, err := d.recurseReferenceVars(refVar, refVar, true)
if err != nil {
return false, err
}
if refValue == "" {
return false, errors.New("reference variable has no value")
}

checkValue = refValue
}
}

switch activeWhen.Condition {
case EqualTo:
isVarActive = checkValue == activeWhen.Value
case NotEqualTo:
isVarActive = checkValue != activeWhen.Value
default:
return false, fmt.Errorf("invalid activeWhen condition: %s", activeWhen.Condition)
}
}
return isVarActive, nil
}

return true, 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 Expand Up @@ -319,20 +394,33 @@ func (d *DraftConfig) DeepCopy() *DraftConfig {

func (bv *BuilderVar) DeepCopy() *BuilderVar {
newVar := &BuilderVar{
Name: bv.Name,
Default: bv.Default,
Description: bv.Description,
Type: bv.Type,
Kind: bv.Kind,
Value: bv.Value,
Versions: bv.Versions,
ExampleValues: make([]string, len(bv.ExampleValues)),
Name: bv.Name,
Default: bv.Default,
Description: bv.Description,
Type: bv.Type,
Kind: bv.Kind,
Value: bv.Value,
Versions: bv.Versions,
ExampleValues: make([]string, len(bv.ExampleValues)),
AllowedValues: make([]string, len(bv.AllowedValues)),
ActiveWhenConstraints: make([]ActiveWhenConstraint, len(bv.ActiveWhenConstraints)),
}

for i, awc := range bv.ActiveWhenConstraints {
newVar.ActiveWhenConstraints[i] = *awc.DeepCopy()
}
copy(newVar.AllowedValues, bv.AllowedValues)
copy(newVar.ExampleValues, bv.ExampleValues)
return newVar
}

func (awc ActiveWhenConstraint) DeepCopy() *ActiveWhenConstraint {
return &ActiveWhenConstraint{
VariableName: awc.VariableName,
Value: awc.Value,
Condition: awc.Condition,
}
}

// TemplateVariableRecorder is an interface for recording variables that are read using draft configs
type TemplateVariableRecorder interface {
Record(key, value string)
Expand Down
63 changes: 35 additions & 28 deletions pkg/config/draftconfig_template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"io/fs"
"regexp"
"slices"
"strings"
"testing"

Expand Down Expand Up @@ -44,11 +45,14 @@ var validVariableKinds = map[string]bool{
"filePath": true,
"flag": true,
"helmChartOverrides": true,
"imagePullPolicy": true,
"ingressHostName": true,
"kubernetesNamespace": true,
"kubernetesProbeHttpPath": true,
"kubernetesProbePeriod": true,
"kubernetesProbeTimeout": true,
"kubernetesProbeThreshold": true,
"kubernetesProbeType": true,
"kubernetesProbeDelay": true,
"kubernetesResourceLimit": true,
"kubernetesResourceName": true,
Expand Down Expand Up @@ -122,7 +126,7 @@ func loadTemplatesWithValidation() error {
}

referenceVarMap := map[string]*BuilderVar{}
conditionRefMap := map[string]*BuilderVar{}
activeWhenRefMap := map[string]*BuilderVar{}
allVariables := map[string]*BuilderVar{}
for _, variable := range currTemplate.Variables {
if variable.Name == "" {
Expand All @@ -146,8 +150,13 @@ func loadTemplatesWithValidation() error {
referenceVarMap[variable.Name] = variable
}

if variable.ConditionalRef.ReferenceVar != "" {
conditionRefMap[variable.Name] = variable
for _, activeWhen := range variable.ActiveWhenConstraints {
if activeWhen.VariableName != "" {
activeWhenRefMap[variable.Name] = variable
}
if !isValidVariableCondition(activeWhen.Condition) {
return fmt.Errorf("template %s has a variable %s with an invalid activeWhen condition: %s", path, variable.Name, activeWhen.Condition)
}
}
}

Expand All @@ -166,14 +175,25 @@ func loadTemplatesWithValidation() error {
}
}

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

if isCyclicalConditionalVariableReference(currVar, refVar, allVariables, map[string]bool{}) {
return fmt.Errorf("template %s has a variable with cyclical conditional reference to itself or references a non existing variable: %s", path, currVar.Name)
for _, currVar := range activeWhenRefMap {

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

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

if refVar.Type == "bool" {
if activeWhen.Value != "true" && activeWhen.Value != "false" {
return fmt.Errorf("template %s has a variable %s with ActiveWhen reference to a non-boolean value: %s", path, currVar.Name, activeWhen.Value)
}
} else if !slices.Contains(refVar.AllowedValues, activeWhen.Value) {
return fmt.Errorf("template %s has a variable %s with ActiveWhen reference to a non-existent allowed value: %s", path, currVar.Name, activeWhen.Value)
}
}
}

Expand Down Expand Up @@ -204,24 +224,11 @@ func isCyclicalDefaultVariableReference(initialVar, currRefVar *BuilderVar, allV
return isCyclicalDefaultVariableReference(initialVar, refVar, allVariables, visited)
}

func isCyclicalConditionalVariableReference(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 {
func isValidVariableCondition(condition VariableCondition) bool {
switch condition {
case EqualTo, NotEqualTo:
return true
}

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

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

visited[currRefVar.Name] = true
return isCyclicalConditionalVariableReference(initialVar, refVar, allVariables, visited)
}
41 changes: 37 additions & 4 deletions pkg/config/validators/validators.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,52 @@ import (
func GetValidator(variableKind string) func(string) error {
switch variableKind {
case "envVarMap":
return KeyValueMapValidator
return keyValueMapValidator
case "imagePullPolicy":
return imagePullPolicyValidator
case "kubernetesProbeType":
return kubernetesProbeTypeValidator
case "scalingResourceType":
return scalingResourceTypeValidator
default:
return DefaultValidator
return defaultValidator
}
}

func KeyValueMapValidator(input string) error {
func imagePullPolicyValidator(input string) error {
switch input {
case "Always", "IfNotPresent", "Never":
return nil
default:
return fmt.Errorf("invalid image pull policy: %s. valid values: Always, IfNotPresent, Never", input)
}
}

func scalingResourceTypeValidator(input string) error {
switch input {
case "cpu", "memory":
return nil
default:
return fmt.Errorf("invalid scaling resource type: %s. valid values: cpu, memory", input)
}
}

func kubernetesProbeTypeValidator(input string) error {
switch input {
case "httpGet", "tcpSocket":
return nil
default:
return fmt.Errorf("invalid probe type: %s. valid values: httpGet, tcpSocket", input)
}
}

func keyValueMapValidator(input string) error {
if err := json.Unmarshal([]byte(input), &map[string]string{}); err != nil {
return fmt.Errorf("failed to unmarshal variable as map[string]string: %s", err)
}
return nil
}

func DefaultValidator(input string) error {
func defaultValidator(input string) error {
return nil
}
26 changes: 25 additions & 1 deletion pkg/config/validators/validators_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,29 @@ func TestGetValidator(t *testing.T) {
}

func TestDefaultValidator(t *testing.T) {
assert.Nil(t, DefaultValidator("test"))
assert.Nil(t, defaultValidator("test"))
}

func TestKubernetesProbeTypeValidator(t *testing.T) {
assert.Nil(t, kubernetesProbeTypeValidator("httpGet"))
assert.Nil(t, kubernetesProbeTypeValidator("tcpSocket"))
assert.NotNil(t, kubernetesProbeTypeValidator("exec"))
}

func TestKeyValueMapValidator(t *testing.T) {
assert.Nil(t, keyValueMapValidator(`{"key": "value"}`))
assert.NotNil(t, keyValueMapValidator(`{"key": "value"`))
}

func TestScalingResourceTypeValidator(t *testing.T) {
assert.Nil(t, scalingResourceTypeValidator("cpu"))
assert.Nil(t, scalingResourceTypeValidator("memory"))
assert.NotNil(t, scalingResourceTypeValidator("disk"))
}

func TestImagePullPolicyValidator(t *testing.T) {
assert.Nil(t, imagePullPolicyValidator("Always"))
assert.Nil(t, imagePullPolicyValidator("IfNotPresent"))
assert.Nil(t, imagePullPolicyValidator("Never"))
assert.NotNil(t, imagePullPolicyValidator("Sometimes"))
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,16 @@ spec:
{{- toYaml .Values.livenessProbe | nindent 12 }}
readinessProbe:
{{- toYaml .Values.readinessProbe | nindent 12 }}
startupProbe:
{{- toYaml .Values.startupProbe | nindent 12 }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
envFrom:
- configMapRef:
name: {{ include "testapp.fullname" . }}-config
- secretRef:
name: secret-ref
optional: true
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
Expand Down
Loading

0 comments on commit e896b21

Please sign in to comment.