diff --git a/pkg/config/draftconfig.go b/pkg/config/draftconfig.go index c08863dd..b28ae9a1 100644 --- a/pkg/config/draftconfig.go +++ b/pkg/config/draftconfig.go @@ -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 @@ -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 @@ -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) { @@ -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) @@ -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) @@ -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 { @@ -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) diff --git a/pkg/config/draftconfig_template_test.go b/pkg/config/draftconfig_template_test.go index c3873682..ae131b4f 100644 --- a/pkg/config/draftconfig_template_test.go +++ b/pkg/config/draftconfig_template_test.go @@ -4,6 +4,7 @@ import ( "fmt" "io/fs" "regexp" + "slices" "strings" "testing" @@ -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, @@ -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 == "" { @@ -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) + } } } @@ -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) + } } } @@ -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) } diff --git a/pkg/config/validators/validators.go b/pkg/config/validators/validators.go index a2c750e3..1bf3948b 100644 --- a/pkg/config/validators/validators.go +++ b/pkg/config/validators/validators.go @@ -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 } diff --git a/pkg/config/validators/validators_test.go b/pkg/config/validators/validators_test.go index 7e8a6e1b..54b38fff 100644 --- a/pkg/config/validators/validators_test.go +++ b/pkg/config/validators/validators_test.go @@ -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")) } diff --git a/pkg/fixtures/deployments/helm/charts/templates/deployment.yaml b/pkg/fixtures/deployments/helm/charts/templates/deployment.yaml index 033c511a..6fd038d9 100644 --- a/pkg/fixtures/deployments/helm/charts/templates/deployment.yaml +++ b/pkg/fixtures/deployments/helm/charts/templates/deployment.yaml @@ -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 }} diff --git a/pkg/fixtures/deployments/helm/charts/values.yaml b/pkg/fixtures/deployments/helm/charts/values.yaml index 5af9625e..f5afa3b1 100644 --- a/pkg/fixtures/deployments/helm/charts/values.yaml +++ b/pkg/fixtures/deployments/helm/charts/values.yaml @@ -31,11 +31,11 @@ resources: # resources, such as Minikube. If you do want to specify resources, uncomment the following # lines, adjust them as necessary, and remove the curly braces after 'resources:'. limits: - cpu: "2" + cpu: "1" memory: "1Gi" requests: cpu: "1" - memory: "512Mi" + memory: "1Gi" autoscaling: enabled: false @@ -55,6 +55,14 @@ readinessProbe: failureThreshold: 1 successThreshold: 1 initialDelaySeconds: 3 +startupProbe: + tcpSocket: + port: 80 + periodSeconds: 1 + timeoutSeconds: 3 + failureThreshold: 1 + successThreshold: 1 + initialDelaySeconds: 5 nodeSelector: {} diff --git a/pkg/fixtures/deployments/kustomize/base/deployment.yaml b/pkg/fixtures/deployments/kustomize/base/deployment.yaml index 3972476c..afc5a25d 100644 --- a/pkg/fixtures/deployments/kustomize/base/deployment.yaml +++ b/pkg/fixtures/deployments/kustomize/base/deployment.yaml @@ -25,13 +25,16 @@ spec: resources: requests: cpu: "1" - memory: "512Mi" + memory: "1Gi" limits: - cpu: "2" + cpu: "1" memory: "1Gi" envFrom: - configMapRef: name: testapp-config + - secretRef: + name: secret-ref + optional: true livenessProbe: tcpSocket: port: 80 @@ -43,6 +46,14 @@ spec: failureThreshold: 1 successThreshold: 1 initialDelaySeconds: 3 + startupProbe: + tcpSocket: + port: 80 + periodSeconds: 1 + timeoutSeconds: 3 + failureThreshold: 1 + successThreshold: 1 + initialDelaySeconds: 5 securityContext: seccompProfile: type: RuntimeDefault diff --git a/pkg/fixtures/deployments/manifest/manifests/deployment.yaml b/pkg/fixtures/deployments/manifest/manifests/deployment.yaml index 3972476c..afc5a25d 100644 --- a/pkg/fixtures/deployments/manifest/manifests/deployment.yaml +++ b/pkg/fixtures/deployments/manifest/manifests/deployment.yaml @@ -25,13 +25,16 @@ spec: resources: requests: cpu: "1" - memory: "512Mi" + memory: "1Gi" limits: - cpu: "2" + cpu: "1" memory: "1Gi" envFrom: - configMapRef: name: testapp-config + - secretRef: + name: secret-ref + optional: true livenessProbe: tcpSocket: port: 80 @@ -43,6 +46,14 @@ spec: failureThreshold: 1 successThreshold: 1 initialDelaySeconds: 3 + startupProbe: + tcpSocket: + port: 80 + periodSeconds: 1 + timeoutSeconds: 3 + failureThreshold: 1 + successThreshold: 1 + initialDelaySeconds: 5 securityContext: seccompProfile: type: RuntimeDefault diff --git a/pkg/fixtures/manifests/hpa/hpa-memory.yaml b/pkg/fixtures/manifests/hpa/hpa-memory.yaml new file mode 100755 index 00000000..74791e61 --- /dev/null +++ b/pkg/fixtures/manifests/hpa/hpa-memory.yaml @@ -0,0 +1,22 @@ +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: test-app + labels: + app.kubernetes.io/name: test-app + app.kubernetes.io/part-of: test-app-project + kubernetes.azure.com/generator: draft +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: test-app + minReplicas: 2 + maxReplicas: 5 + metrics: + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: 80 \ No newline at end of file diff --git a/pkg/handlers/template_test.go b/pkg/handlers/template_test.go index 62032542..b0d99d96 100644 --- a/pkg/handlers/template_test.go +++ b/pkg/handlers/template_test.go @@ -1,37 +1,13 @@ package handlers import ( - "fmt" - "path/filepath" "reflect" - "regexp" - "strings" "testing" - "github.com/Azure/draft/pkg/fixtures" "github.com/Azure/draft/pkg/templatewriter/writers" "github.com/stretchr/testify/assert" ) -func AlwaysFailingValidator(value string) error { - return fmt.Errorf("this is a failing validator") -} - -func AlwaysFailingTransformer(value string) (any, error) { - return "", fmt.Errorf("this is a failing transformer") -} - -func K8sLabelValidator(value string) error { - labelRegex, err := regexp.Compile("^((A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?$") - if err != nil { - return err - } - if !labelRegex.MatchString(value) { - return fmt.Errorf("invalid label: %s", value) - } - return nil -} - func TestDeepCopy(t *testing.T) { // This will fail on adding a new field to the undelying structs that arent handled in DeepCopy testTemplate, err := GetTemplate("deployment-manifests", "0.0.1", ".", &writers.FileMapWriter{}) @@ -41,538 +17,3 @@ func TestDeepCopy(t *testing.T) { assert.True(t, reflect.DeepEqual(deepCopy, testTemplate)) } - -func TestTemplateHandlerValidation(t *testing.T) { - tests := []struct { - name string - templateName string - fixturesBaseDir string - version string - dest string - templateWriter *writers.FileMapWriter - varMap map[string]string - fileNameOverride map[string]string - expectedErr error - validators map[string]func(string) error - transformers map[string]func(string) (any, error) - }{ - { - name: "valid manifest deployment", - templateName: "deployment-manifests", - fixturesBaseDir: "../fixtures/deployments/manifest", - version: "0.0.1", - dest: "./validation/.././", - templateWriter: &writers.FileMapWriter{}, - varMap: map[string]string{ - "APPNAME": "testapp", - "NAMESPACE": "default", - "PORT": "80", - "IMAGENAME": "testimage", - "IMAGETAG": "latest", - "GENERATORLABEL": "draft", - "SERVICEPORT": "80", - "ENVVARS": `{"key1":"value1","key2":"value2"}`, - }, - }, - { - name: "valid helm deployment", - templateName: "deployment-helm", - fixturesBaseDir: "../fixtures/deployments/helm", - version: "0.0.1", - dest: ".", - templateWriter: &writers.FileMapWriter{}, - varMap: map[string]string{ - "APPNAME": "testapp", - "NAMESPACE": "default", - "PORT": "80", - "IMAGENAME": "testimage", - "IMAGETAG": "latest", - "GENERATORLABEL": "draft", - "SERVICEPORT": "80", - }, - }, - { - name: "valid kustomize deployment", - templateName: "deployment-kustomize", - fixturesBaseDir: "../fixtures/deployments/kustomize", - version: "0.0.1", - dest: ".", - templateWriter: &writers.FileMapWriter{}, - varMap: map[string]string{ - "APPNAME": "testapp", - "NAMESPACE": "default", - "PORT": "80", - "IMAGENAME": "testimage", - "IMAGETAG": "latest", - "GENERATORLABEL": "draft", - "SERVICEPORT": "80", - }, - }, - { - name: "valid manifest deployment with filename override", - templateName: "deployment-manifests", - fixturesBaseDir: "../fixtures/deployments/manifest", - version: "0.0.1", - dest: ".", - templateWriter: &writers.FileMapWriter{}, - varMap: map[string]string{ - "APPNAME": "testapp", - "NAMESPACE": "default", - "PORT": "80", - "IMAGENAME": "testimage", - "IMAGETAG": "latest", - "GENERATORLABEL": "draft", - "SERVICEPORT": "80", - "ENVVARS": `{"key1":"value1","key2":"value2"}`, - }, - fileNameOverride: map[string]string{ - "deployment.yaml": "deployment-override.yaml", - }, - }, - { - name: "insufficient variables for manifest deployment", - templateName: "deployment-manifests", - fixturesBaseDir: "../fixtures/deployments/manifest", - version: "0.0.1", - dest: ".", - templateWriter: &writers.FileMapWriter{}, - varMap: map[string]string{}, - expectedErr: fmt.Errorf("create workflow files: variable APPNAME has no default value"), - }, - { - name: "valid clojure dockerfile", - templateName: "dockerfile-clojure", - fixturesBaseDir: "../fixtures/dockerfiles/clojure", - version: "0.0.1", - dest: ".", - templateWriter: &writers.FileMapWriter{}, - varMap: map[string]string{ - "PORT": "80", - "VERSION": "19-jdk-alpine", - }, - }, - { - name: "valid csharp dockerfile", - templateName: "dockerfile-csharp", - fixturesBaseDir: "../fixtures/dockerfiles/csharp", - version: "0.0.1", - dest: ".", - templateWriter: &writers.FileMapWriter{}, - varMap: map[string]string{ - "PORT": "80", - "VERSION": "6.0", - }, - }, - { - name: "valid erlang dockerfile", - templateName: "dockerfile-erlang", - fixturesBaseDir: "../fixtures/dockerfiles/erlang", - version: "0.0.1", - dest: ".", - templateWriter: &writers.FileMapWriter{}, - varMap: map[string]string{ - "PORT": "80", - "BUILDVERSION": "27.0-alpine", - "VERSION": "3.17", - }, - }, - { - name: "valid go dockerfile", - templateName: "dockerfile-go", - fixturesBaseDir: "../fixtures/dockerfiles/go", - version: "0.0.1", - dest: ".", - templateWriter: &writers.FileMapWriter{}, - varMap: map[string]string{ - "PORT": "80", - "VERSION": "1.23", - }, - }, - { - name: "valid gomodule dockerfile", - templateName: "dockerfile-gomodule", - fixturesBaseDir: "../fixtures/dockerfiles/gomodule", - version: "0.0.1", - dest: ".", - templateWriter: &writers.FileMapWriter{}, - varMap: map[string]string{ - "PORT": "80", - "VERSION": "1.23", - }, - }, - { - name: "valid gradle dockerfile", - templateName: "dockerfile-gradle", - fixturesBaseDir: "../fixtures/dockerfiles/gradle", - version: "0.0.1", - dest: ".", - templateWriter: &writers.FileMapWriter{}, - varMap: map[string]string{ - "PORT": "80", - "BUILDVERSION": "jdk21", - "VERSION": "21-jre", - }, - }, - { - name: "valid gradlew dockerfile", - templateName: "dockerfile-gradlew", - fixturesBaseDir: "../fixtures/dockerfiles/gradlew", - version: "0.0.1", - dest: ".", - templateWriter: &writers.FileMapWriter{}, - varMap: map[string]string{ - "PORT": "80", - "BUILDVERSION": "jdk21", - "VERSION": "21-jre", - }, - }, - { - name: "valid java dockerfile", - templateName: "dockerfile-java", - fixturesBaseDir: "../fixtures/dockerfiles/java", - version: "0.0.1", - dest: ".", - templateWriter: &writers.FileMapWriter{}, - varMap: map[string]string{ - "PORT": "80", - "BUILDVERSION": "3 (jdk-21)", - "VERSION": "21-jre", - }, - }, - { - name: "valid javascript dockerfile", - templateName: "dockerfile-javascript", - fixturesBaseDir: "../fixtures/dockerfiles/javascript", - version: "0.0.1", - dest: ".", - templateWriter: &writers.FileMapWriter{}, - varMap: map[string]string{ - "PORT": "80", - "VERSION": "14.15.4", - }, - }, - { - name: "valid php dockerfile", - templateName: "dockerfile-php", - fixturesBaseDir: "../fixtures/dockerfiles/php", - version: "0.0.1", - dest: ".", - templateWriter: &writers.FileMapWriter{}, - varMap: map[string]string{ - "PORT": "80", - "BUILDVERSION": "1", - "VERSION": "7.1-apache", - }, - }, - { - name: "valid python dockerfile", - templateName: "dockerfile-python", - fixturesBaseDir: "../fixtures/dockerfiles/python", - version: "0.0.1", - dest: ".", - templateWriter: &writers.FileMapWriter{}, - varMap: map[string]string{ - "PORT": "80", - "ENTRYPOINT": "app.py", - "VERSION": "3.9", - }, - }, - { - name: "valid ruby dockerfile", - templateName: "dockerfile-ruby", - fixturesBaseDir: "../fixtures/dockerfiles/ruby", - version: "0.0.1", - dest: ".", - templateWriter: &writers.FileMapWriter{}, - varMap: map[string]string{ - "PORT": "80", - "VERSION": "3.1.2", - }, - }, - { - name: "valid rust dockerfile", - templateName: "dockerfile-rust", - fixturesBaseDir: "../fixtures/dockerfiles/rust", - version: "0.0.1", - dest: ".", - templateWriter: &writers.FileMapWriter{}, - varMap: map[string]string{ - "PORT": "80", - "VERSION": "1.70.0", - }, - }, - { - name: "valid swift dockerfile", - templateName: "dockerfile-swift", - fixturesBaseDir: "../fixtures/dockerfiles/swift", - version: "0.0.1", - dest: ".", - templateWriter: &writers.FileMapWriter{}, - varMap: map[string]string{ - "PORT": "80", - "VERSION": "5.5", - }, - }, - { - name: "valid azpipeline manifests deployment", - templateName: "azure-pipeline-manifests", - fixturesBaseDir: "../fixtures/workflows/azurepipelines/manifests", - version: "0.0.1", - dest: ".", - templateWriter: &writers.FileMapWriter{}, - varMap: map[string]string{ - "ARMSERVICECONNECTION": "testserviceconnection", - "AZURECONTAINERREGISTRY": "myacr.acr.io", - "CONTAINERNAME": "myapp", - "CLUSTERRESOURCEGROUP": "myrg", - "ACRRESOURCEGROUP": "myrg", - "CLUSTERNAME": "testcluster", - }, - }, - { - name: "valid azpipeline kustomize deployment", - templateName: "azure-pipeline-kustomize", - fixturesBaseDir: "../fixtures/workflows/azurepipelines/kustomize", - version: "0.0.1", - dest: ".", - templateWriter: &writers.FileMapWriter{}, - varMap: map[string]string{ - "ARMSERVICECONNECTION": "testserviceconnection", - "AZURECONTAINERREGISTRY": "myacr.acr.io", - "CONTAINERNAME": "myapp", - "CLUSTERRESOURCEGROUP": "myrg", - "ACRRESOURCEGROUP": "myrg", - "CLUSTERNAME": "testcluster", - }, - }, - { - name: "valid app-routing ingress", - templateName: "app-routing-ingress", - fixturesBaseDir: "../fixtures/addons/ingress", - version: "0.0.1", - dest: ".", - templateWriter: &writers.FileMapWriter{}, - varMap: map[string]string{ - "ingress-tls-cert-keyvault-uri": "test.uri", - "ingress-use-osm-mtls": "false", - "ingress-host": "host", - "service-name": "test-service", - "service-namespace": "test-namespace", - "service-port": "80", - }, - }, - { - name: "manifest deployment vars with err from validators", - templateName: "deployment-manifests", - fixturesBaseDir: "../fixtures/deployments/manifest", - version: "0.0.1", - dest: ".", - templateWriter: &writers.FileMapWriter{}, - varMap: map[string]string{ - "APPNAME": "testapp", - "NAMESPACE": "default", - "PORT": "80", - "IMAGENAME": "testimage", - "IMAGETAG": "latest", - "GENERATORLABEL": "draft", - "SERVICEPORT": "80", - }, - validators: map[string]func(string) error{ - "kubernetesResourceName": AlwaysFailingValidator, - }, - expectedErr: fmt.Errorf("this is a failing validator"), - }, - { - name: "manifest deployment vars with err from transformers", - templateName: "deployment-manifests", - fixturesBaseDir: "../fixtures/deployments/manifest", - version: "0.0.1", - dest: ".", - templateWriter: &writers.FileMapWriter{}, - varMap: map[string]string{ - "APPNAME": "testapp", - "NAMESPACE": "default", - "PORT": "80", - "IMAGENAME": "testimage", - "IMAGETAG": "latest", - "GENERATORLABEL": "draft", - "SERVICEPORT": "80", - }, - transformers: map[string]func(string) (any, error){ - "kubernetesResourceName": AlwaysFailingTransformer, - }, - expectedErr: fmt.Errorf("this is a failing transformer"), - }, - { - name: "manifest deployment vars with err from label validator", - templateName: "deployment-manifests", - fixturesBaseDir: "../fixtures/deployments/manifest", - version: "0.0.1", - dest: ".", - templateWriter: &writers.FileMapWriter{}, - varMap: map[string]string{ - "APPNAME": "*myTestApp", - "NAMESPACE": "default", - "PORT": "80", - "IMAGENAME": "testimage", - "IMAGETAG": "latest", - "GENERATORLABEL": "draft", - "SERVICEPORT": "80", - }, - validators: map[string]func(string) error{ - "kubernetesResourceName": K8sLabelValidator, - }, - expectedErr: fmt.Errorf("invalid label: *myTestApp"), - }, - { - name: "valid helm workflow", - templateName: "github-workflow-helm", - fixturesBaseDir: "../fixtures/workflows/github/helm", - version: "0.0.1", - dest: ".", - templateWriter: &writers.FileMapWriter{}, - varMap: map[string]string{ - "WORKFLOWNAME": "testWorkflow", - "BRANCHNAME": "testBranch", - "ACRRESOURCEGROUP": "testAcrRG", - "AZURECONTAINERREGISTRY": "testAcr", - "CONTAINERNAME": "testContainer", - "CLUSTERRESOURCEGROUP": "testClusterRG", - "CLUSTERNAME": "testCluster", - "KUSTOMIZEPATH": "./overlays/production", - "DEPLOYMENTMANIFESTPATH": "./manifests", - "DOCKERFILE": "./Dockerfile", - "BUILDCONTEXTPATH": "test", - "CHARTPATH": "testPath", - "CHARTOVERRIDEPATH": "testOverridePath", - "CHARTOVERRIDES": "replicas:2", - "NAMESPACE": "default", - }, - }, - { - name: "valid helm workflow", - templateName: "github-workflow-kustomize", - fixturesBaseDir: "../fixtures/workflows/github/kustomize", - version: "0.0.1", - dest: ".", - templateWriter: &writers.FileMapWriter{}, - varMap: map[string]string{ - "WORKFLOWNAME": "testWorkflow", - "BRANCHNAME": "testBranch", - "ACRRESOURCEGROUP": "testAcrRG", - "AZURECONTAINERREGISTRY": "testAcr", - "CONTAINERNAME": "testContainer", - "CLUSTERRESOURCEGROUP": "testClusterRG", - "CLUSTERNAME": "testCluster", - "DEPLOYMENTMANIFESTPATH": "./manifests", - "DOCKERFILE": "./Dockerfile", - "BUILDCONTEXTPATH": "test", - "NAMESPACE": "default", - }, - }, - { - name: "valid manifest workflow", - templateName: "github-workflow-manifests", - fixturesBaseDir: "../fixtures/workflows/github/manifests", - version: "0.0.1", - dest: ".", - templateWriter: &writers.FileMapWriter{}, - varMap: map[string]string{ - "WORKFLOWNAME": "testWorkflow", - "BRANCHNAME": "testBranch", - "ACRRESOURCEGROUP": "testAcrRG", - "AZURECONTAINERREGISTRY": "testAcr", - "CONTAINERNAME": "testContainer", - "CLUSTERRESOURCEGROUP": "testClusterRG", - "CLUSTERNAME": "testCluster", - "DEPLOYMENTMANIFESTPATH": "./manifests", - "DOCKERFILE": "./Dockerfile", - "BUILDCONTEXTPATH": "test", - "NAMESPACE": "default", - }, - }, - { - name: "valid hpa manifest", - templateName: "horizontalPodAutoscaler-manifests", - fixturesBaseDir: "../fixtures/manifests/hpa", - version: "0.0.1", - dest: ".", - templateWriter: &writers.FileMapWriter{}, - varMap: map[string]string{ - "APPNAME": "test-app", - "PARTOF": "test-app-project", - }, - }, - { - name: "valid pdb manifest", - templateName: "podDisruptionBudget-manifests", - fixturesBaseDir: "../fixtures/manifests/pdb", - version: "0.0.1", - dest: ".", - templateWriter: &writers.FileMapWriter{}, - varMap: map[string]string{ - "APPNAME": "test-app", - "PARTOF": "test-app-project", - }, - }, - { - name: "valid service manifest", - templateName: "service-manifests", - fixturesBaseDir: "../fixtures/manifests/service", - version: "0.0.1", - dest: ".", - templateWriter: &writers.FileMapWriter{}, - varMap: map[string]string{ - "APPNAME": "test-app", - "PARTOF": "test-app-project", - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - template, err := GetTemplate(tt.templateName, tt.version, tt.dest, tt.templateWriter) - assert.Nil(t, err) - assert.NotNil(t, template) - - for k, v := range tt.varMap { - template.Config.SetVariable(k, v) - } - - for k, v := range tt.validators { - template.Config.SetVariableValidator(k, v) - } - - for k, v := range tt.transformers { - template.Config.SetVariableTransformer(k, v) - } - - overrideReverseLookup := make(map[string]string) - for k, v := range tt.fileNameOverride { - template.Config.SetFileNameOverride(k, v) - overrideReverseLookup[v] = k - } - - err = template.Generate() - if tt.expectedErr != nil { - if err == nil { - t.Errorf("expected error %v, got nil", tt.expectedErr) - return - } - assert.True(t, strings.Contains(err.Error(), tt.expectedErr.Error())) - return - } - assert.Nil(t, err) - - for k, v := range tt.templateWriter.FileMap { - fileName := k - if overrideFile, ok := overrideReverseLookup[filepath.Base(k)]; ok { - fileName = strings.Replace(fileName, filepath.Base(k), overrideFile, 1) - } - - err = fixtures.ValidateContentAgainstFixture(v, fmt.Sprintf("%s/%s", tt.fixturesBaseDir, fileName)) - assert.Nil(t, err) - } - }) - } -} diff --git a/pkg/handlers/templatetests/deployment_helm_test.go b/pkg/handlers/templatetests/deployment_helm_test.go new file mode 100644 index 00000000..4c6c782b --- /dev/null +++ b/pkg/handlers/templatetests/deployment_helm_test.go @@ -0,0 +1,33 @@ +package templatetests + +import ( + "testing" + + "github.com/Azure/draft/pkg/templatewriter/writers" +) + +func TestDeploymentHelmTemplates(t *testing.T) { + tests := []TestInput{ + { + Name: "valid helm deployment", + TemplateName: "deployment-helm", + FixturesBaseDir: "../../fixtures/deployments/helm", + Version: "0.0.1", + Dest: ".", + TemplateWriter: &writers.FileMapWriter{}, + VarMap: map[string]string{ + "APPNAME": "testapp", + "NAMESPACE": "default", + "PORT": "80", + "IMAGENAME": "testimage", + "IMAGETAG": "latest", + "GENERATORLABEL": "draft", + "SERVICEPORT": "80", + }, + }, + } + + for _, test := range tests { + RunTemplateTest(t, test) + } +} diff --git a/pkg/handlers/templatetests/deployment_kustomize_test.go b/pkg/handlers/templatetests/deployment_kustomize_test.go new file mode 100644 index 00000000..3f786824 --- /dev/null +++ b/pkg/handlers/templatetests/deployment_kustomize_test.go @@ -0,0 +1,33 @@ +package templatetests + +import ( + "testing" + + "github.com/Azure/draft/pkg/templatewriter/writers" +) + +func TestDeploymentKustomizeTemplates(t *testing.T) { + tests := []TestInput{ + { + Name: "valid kustomize deployment", + TemplateName: "deployment-kustomize", + FixturesBaseDir: "../../fixtures/deployments/kustomize", + Version: "0.0.1", + Dest: ".", + TemplateWriter: &writers.FileMapWriter{}, + VarMap: map[string]string{ + "APPNAME": "testapp", + "NAMESPACE": "default", + "PORT": "80", + "IMAGENAME": "testimage", + "IMAGETAG": "latest", + "GENERATORLABEL": "draft", + "SERVICEPORT": "80", + }, + }, + } + + for _, test := range tests { + RunTemplateTest(t, test) + } +} diff --git a/pkg/handlers/templatetests/deployment_manifest_test.go b/pkg/handlers/templatetests/deployment_manifest_test.go new file mode 100644 index 00000000..ca4593d9 --- /dev/null +++ b/pkg/handlers/templatetests/deployment_manifest_test.go @@ -0,0 +1,130 @@ +package templatetests + +import ( + "fmt" + "testing" + + "github.com/Azure/draft/pkg/templatewriter/writers" +) + +func TestDeploymentManifestTemplates(t *testing.T) { + tests := []TestInput{ + { + Name: "valid manifest deployment", + TemplateName: "deployment-manifests", + FixturesBaseDir: "../../fixtures/deployments/manifest", + Version: "0.0.1", + Dest: "./validation/.././", + TemplateWriter: &writers.FileMapWriter{}, + VarMap: map[string]string{ + "APPNAME": "testapp", + "NAMESPACE": "default", + "PORT": "80", + "IMAGENAME": "testimage", + "IMAGETAG": "latest", + "GENERATORLABEL": "draft", + "SERVICEPORT": "80", + "ENVVARS": `{"key1":"value1","key2":"value2"}`, + }, + }, + { + Name: "valid manifest deployment with filename override", + TemplateName: "deployment-manifests", + FixturesBaseDir: "../../fixtures/deployments/manifest", + Version: "0.0.1", + Dest: ".", + TemplateWriter: &writers.FileMapWriter{}, + VarMap: map[string]string{ + "APPNAME": "testapp", + "NAMESPACE": "default", + "PORT": "80", + "IMAGENAME": "testimage", + "IMAGETAG": "latest", + "GENERATORLABEL": "draft", + "SERVICEPORT": "80", + "ENVVARS": `{"key1":"value1","key2":"value2"}`, + }, + FileNameOverride: map[string]string{ + "deployment.yaml": "deployment-override.yaml", + }, + UseBaseFixtureWithFileNameOverride: true, + }, + { + Name: "insufficient variables for manifest deployment", + TemplateName: "deployment-manifests", + FixturesBaseDir: "../../fixtures/deployments/manifest", + Version: "0.0.1", + Dest: ".", + TemplateWriter: &writers.FileMapWriter{}, + VarMap: map[string]string{}, + ExpectedErr: fmt.Errorf("create workflow files: variable APPNAME has no default value"), + }, + { + Name: "manifest deployment vars with err from validators", + TemplateName: "deployment-manifests", + FixturesBaseDir: "../../fixtures/deployments/manifest", + Version: "0.0.1", + Dest: ".", + TemplateWriter: &writers.FileMapWriter{}, + VarMap: map[string]string{ + "APPNAME": "testapp", + "NAMESPACE": "default", + "PORT": "80", + "IMAGENAME": "testimage", + "IMAGETAG": "latest", + "GENERATORLABEL": "draft", + "SERVICEPORT": "80", + }, + Validators: map[string]func(string) error{ + "kubernetesResourceName": AlwaysFailingValidator, + }, + ExpectedErr: fmt.Errorf("this is a failing validator"), + }, + { + Name: "manifest deployment vars with err from transformers", + TemplateName: "deployment-manifests", + FixturesBaseDir: "../../fixtures/deployments/manifest", + Version: "0.0.1", + Dest: ".", + TemplateWriter: &writers.FileMapWriter{}, + VarMap: map[string]string{ + "APPNAME": "testapp", + "NAMESPACE": "default", + "PORT": "80", + "IMAGENAME": "testimage", + "IMAGETAG": "latest", + "GENERATORLABEL": "draft", + "SERVICEPORT": "80", + }, + Transformers: map[string]func(string) (any, error){ + "kubernetesResourceName": AlwaysFailingTransformer, + }, + ExpectedErr: fmt.Errorf("this is a failing transformer"), + }, + { + Name: "manifest deployment vars with err from label validator", + TemplateName: "deployment-manifests", + FixturesBaseDir: "../../fixtures/deployments/manifest", + Version: "0.0.1", + Dest: ".", + TemplateWriter: &writers.FileMapWriter{}, + VarMap: map[string]string{ + "APPNAME": "*myTestApp", + "NAMESPACE": "default", + "PORT": "80", + "IMAGENAME": "testimage", + "IMAGETAG": "latest", + "GENERATORLABEL": "draft", + "SERVICEPORT": "80", + }, + Validators: map[string]func(string) error{ + "kubernetesResourceName": K8sLabelValidator, + }, + ExpectedErr: fmt.Errorf("invalid label: *myTestApp"), + }, + } + + for _, test := range tests { + RunTemplateTest(t, test) + } +} diff --git a/pkg/handlers/templatetests/dockerfile_test.go b/pkg/handlers/templatetests/dockerfile_test.go new file mode 100644 index 00000000..e209dd7c --- /dev/null +++ b/pkg/handlers/templatetests/dockerfile_test.go @@ -0,0 +1,190 @@ +package templatetests + +import ( + "testing" + + "github.com/Azure/draft/pkg/templatewriter/writers" +) + +func TestDockerfileTemplates(t *testing.T) { + tests := []TestInput{ + { + Name: "valid clojure dockerfile", + TemplateName: "dockerfile-clojure", + FixturesBaseDir: "../../fixtures/dockerfiles/clojure", + Version: "0.0.1", + Dest: ".", + TemplateWriter: &writers.FileMapWriter{}, + VarMap: map[string]string{ + "PORT": "80", + "VERSION": "19-jdk-alpine", + }, + }, + { + Name: "valid csharp dockerfile", + TemplateName: "dockerfile-csharp", + FixturesBaseDir: "../../fixtures/dockerfiles/csharp", + Version: "0.0.1", + Dest: ".", + TemplateWriter: &writers.FileMapWriter{}, + VarMap: map[string]string{ + "PORT": "80", + "VERSION": "6.0", + }, + }, + { + Name: "valid erlang dockerfile", + TemplateName: "dockerfile-erlang", + FixturesBaseDir: "../../fixtures/dockerfiles/erlang", + Version: "0.0.1", + Dest: ".", + TemplateWriter: &writers.FileMapWriter{}, + VarMap: map[string]string{ + "PORT": "80", + "BUILDVERSION": "27.0-alpine", + "VERSION": "3.17", + }, + }, + { + Name: "valid go dockerfile", + TemplateName: "dockerfile-go", + FixturesBaseDir: "../../fixtures/dockerfiles/go", + Version: "0.0.1", + Dest: ".", + TemplateWriter: &writers.FileMapWriter{}, + VarMap: map[string]string{ + "PORT": "80", + "VERSION": "1.23", + }, + }, + { + Name: "valid gomodule dockerfile", + TemplateName: "dockerfile-gomodule", + FixturesBaseDir: "../../fixtures/dockerfiles/gomodule", + Version: "0.0.1", + Dest: ".", + TemplateWriter: &writers.FileMapWriter{}, + VarMap: map[string]string{ + "PORT": "80", + "VERSION": "1.23", + }, + }, + { + Name: "valid gradle dockerfile", + TemplateName: "dockerfile-gradle", + FixturesBaseDir: "../../fixtures/dockerfiles/gradle", + Version: "0.0.1", + Dest: ".", + TemplateWriter: &writers.FileMapWriter{}, + VarMap: map[string]string{ + "PORT": "80", + "BUILDVERSION": "jdk21", + "VERSION": "21-jre", + }, + }, + { + Name: "valid gradlew dockerfile", + TemplateName: "dockerfile-gradlew", + FixturesBaseDir: "../../fixtures/dockerfiles/gradlew", + Version: "0.0.1", + Dest: ".", + TemplateWriter: &writers.FileMapWriter{}, + VarMap: map[string]string{ + "PORT": "80", + "BUILDVERSION": "jdk21", + "VERSION": "21-jre", + }, + }, + { + Name: "valid java dockerfile", + TemplateName: "dockerfile-java", + FixturesBaseDir: "../../fixtures/dockerfiles/java", + Version: "0.0.1", + Dest: ".", + TemplateWriter: &writers.FileMapWriter{}, + VarMap: map[string]string{ + "PORT": "80", + "BUILDVERSION": "3 (jdk-21)", + "VERSION": "21-jre", + }, + }, + { + Name: "valid javascript dockerfile", + TemplateName: "dockerfile-javascript", + FixturesBaseDir: "../../fixtures/dockerfiles/javascript", + Version: "0.0.1", + Dest: ".", + TemplateWriter: &writers.FileMapWriter{}, + VarMap: map[string]string{ + "PORT": "80", + "VERSION": "14.15.4", + }, + }, + { + Name: "valid php dockerfile", + TemplateName: "dockerfile-php", + FixturesBaseDir: "../../fixtures/dockerfiles/php", + Version: "0.0.1", + Dest: ".", + TemplateWriter: &writers.FileMapWriter{}, + VarMap: map[string]string{ + "PORT": "80", + "BUILDVERSION": "1", + "VERSION": "7.1-apache", + }, + }, + { + Name: "valid python dockerfile", + TemplateName: "dockerfile-python", + FixturesBaseDir: "../../fixtures/dockerfiles/python", + Version: "0.0.1", + Dest: ".", + TemplateWriter: &writers.FileMapWriter{}, + VarMap: map[string]string{ + "PORT": "80", + "ENTRYPOINT": "app.py", + "VERSION": "3.9", + }, + }, + { + Name: "valid ruby dockerfile", + TemplateName: "dockerfile-ruby", + FixturesBaseDir: "../../fixtures/dockerfiles/ruby", + Version: "0.0.1", + Dest: ".", + TemplateWriter: &writers.FileMapWriter{}, + VarMap: map[string]string{ + "PORT": "80", + "VERSION": "3.1.2", + }, + }, + { + Name: "valid rust dockerfile", + TemplateName: "dockerfile-rust", + FixturesBaseDir: "../../fixtures/dockerfiles/rust", + Version: "0.0.1", + Dest: ".", + TemplateWriter: &writers.FileMapWriter{}, + VarMap: map[string]string{ + "PORT": "80", + "VERSION": "1.70.0", + }, + }, + { + Name: "valid swift dockerfile", + TemplateName: "dockerfile-swift", + FixturesBaseDir: "../../fixtures/dockerfiles/swift", + Version: "0.0.1", + Dest: ".", + TemplateWriter: &writers.FileMapWriter{}, + VarMap: map[string]string{ + "PORT": "80", + "VERSION": "5.5", + }, + }, + } + + for _, test := range tests { + RunTemplateTest(t, test) + } +} diff --git a/pkg/handlers/templatetests/manifests_hpa_test.go b/pkg/handlers/templatetests/manifests_hpa_test.go new file mode 100644 index 00000000..2563f622 --- /dev/null +++ b/pkg/handlers/templatetests/manifests_hpa_test.go @@ -0,0 +1,59 @@ +package templatetests + +import ( + "fmt" + "testing" + + "github.com/Azure/draft/pkg/templatewriter/writers" +) + +func TestManifestsHPATemplates(t *testing.T) { + tests := []TestInput{ + { + Name: "valid hpa manifest", + TemplateName: "horizontalPodAutoscaler-manifests", + FixturesBaseDir: "../../fixtures/manifests/hpa", + Version: "0.0.1", + Dest: ".", + TemplateWriter: &writers.FileMapWriter{}, + VarMap: map[string]string{ + "APPNAME": "test-app", + "PARTOF": "test-app-project", + }, + }, + { + Name: "valid hpa manifest with memory utilization", + TemplateName: "horizontalPodAutoscaler-manifests", + FixturesBaseDir: "../../fixtures/manifests/hpa", + Version: "0.0.1", + Dest: ".", + TemplateWriter: &writers.FileMapWriter{}, + VarMap: map[string]string{ + "APPNAME": "test-app", + "PARTOF": "test-app-project", + "RESOURCETYPE": "memory", + }, + FileNameOverride: map[string]string{ + "hpa.yaml": "hpa-memory.yaml", + }, + }, + { + Name: "invalid hpa manifest with invalid resource type", + TemplateName: "horizontalPodAutoscaler-manifests", + FixturesBaseDir: "../../fixtures/manifests/hpa", + Version: "0.0.1", + Dest: ".", + TemplateWriter: &writers.FileMapWriter{}, + VarMap: map[string]string{ + "APPNAME": "test-app", + "PARTOF": "test-app-project", + "RESOURCETYPE": "http", + }, + ExpectedErr: fmt.Errorf("invalid scaling resource type"), + }, + } + + for _, test := range tests { + RunTemplateTest(t, test) + } +} diff --git a/pkg/handlers/templatetests/manifests_ingress_test.go b/pkg/handlers/templatetests/manifests_ingress_test.go new file mode 100644 index 00000000..9c910520 --- /dev/null +++ b/pkg/handlers/templatetests/manifests_ingress_test.go @@ -0,0 +1,32 @@ +package templatetests + +import ( + "testing" + + "github.com/Azure/draft/pkg/templatewriter/writers" +) + +func TestManifestsIngressTemplates(t *testing.T) { + tests := []TestInput{ + { + Name: "valid app-routing ingress", + TemplateName: "app-routing-ingress", + FixturesBaseDir: "../../fixtures/addons/ingress", + Version: "0.0.1", + Dest: ".", + TemplateWriter: &writers.FileMapWriter{}, + VarMap: map[string]string{ + "ingress-tls-cert-keyvault-uri": "test.uri", + "ingress-use-osm-mtls": "false", + "ingress-host": "host", + "service-name": "test-service", + "service-namespace": "test-namespace", + "service-port": "80", + }, + }, + } + + for _, test := range tests { + RunTemplateTest(t, test) + } +} diff --git a/pkg/handlers/templatetests/manifests_pdb_test.go b/pkg/handlers/templatetests/manifests_pdb_test.go new file mode 100644 index 00000000..e2d29eb4 --- /dev/null +++ b/pkg/handlers/templatetests/manifests_pdb_test.go @@ -0,0 +1,28 @@ +package templatetests + +import ( + "testing" + + "github.com/Azure/draft/pkg/templatewriter/writers" +) + +func TestManifestsPDBTemplates(t *testing.T) { + tests := []TestInput{ + { + Name: "valid pdb manifest", + TemplateName: "podDisruptionBudget-manifests", + FixturesBaseDir: "../../fixtures/manifests/pdb", + Version: "0.0.1", + Dest: ".", + TemplateWriter: &writers.FileMapWriter{}, + VarMap: map[string]string{ + "APPNAME": "test-app", + "PARTOF": "test-app-project", + }, + }, + } + + for _, test := range tests { + RunTemplateTest(t, test) + } +} diff --git a/pkg/handlers/templatetests/manifests_service_test.go b/pkg/handlers/templatetests/manifests_service_test.go new file mode 100644 index 00000000..c581e10a --- /dev/null +++ b/pkg/handlers/templatetests/manifests_service_test.go @@ -0,0 +1,28 @@ +package templatetests + +import ( + "testing" + + "github.com/Azure/draft/pkg/templatewriter/writers" +) + +func TestManifestsServiceTemplates(t *testing.T) { + tests := []TestInput{ + { + Name: "valid service manifest", + TemplateName: "service-manifests", + FixturesBaseDir: "../../fixtures/manifests/service", + Version: "0.0.1", + Dest: ".", + TemplateWriter: &writers.FileMapWriter{}, + VarMap: map[string]string{ + "APPNAME": "test-app", + "PARTOF": "test-app-project", + }, + }, + } + + for _, test := range tests { + RunTemplateTest(t, test) + } +} diff --git a/pkg/handlers/templatetests/template_test.go b/pkg/handlers/templatetests/template_test.go new file mode 100644 index 00000000..e268d2af --- /dev/null +++ b/pkg/handlers/templatetests/template_test.go @@ -0,0 +1,107 @@ +package templatetests + +import ( + "fmt" + "os" + "path/filepath" + "regexp" + "strings" + "testing" + + "github.com/Azure/draft/pkg/fixtures" + "github.com/Azure/draft/pkg/handlers" + "github.com/Azure/draft/pkg/templatewriter/writers" + "github.com/stretchr/testify/assert" +) + +type TestInput struct { + Name string + TemplateName string + FixturesBaseDir string + Version string + Dest string + TemplateWriter *writers.FileMapWriter + VarMap map[string]string + FileNameOverride map[string]string + ExpectedErr error + Validators map[string]func(string) error + Transformers map[string]func(string) (any, error) + + UseBaseFixtureWithFileNameOverride bool + GenerateBaseTemplate bool +} + +func RunTemplateTest(t *testing.T, testInput TestInput) { + t.Run(testInput.Name, func(t *testing.T) { + template, err := handlers.GetTemplate(testInput.TemplateName, testInput.Version, testInput.Dest, testInput.TemplateWriter) + assert.Nil(t, err) + assert.NotNil(t, template) + + for k, v := range testInput.VarMap { + template.Config.SetVariable(k, v) + } + + for k, v := range testInput.Validators { + template.Config.SetVariableValidator(k, v) + } + + for k, v := range testInput.Transformers { + template.Config.SetVariableTransformer(k, v) + } + + overrideReverseLookup := make(map[string]string) + for k, v := range testInput.FileNameOverride { + template.Config.SetFileNameOverride(k, v) + overrideReverseLookup[v] = k + } + + err = template.Generate() + if testInput.ExpectedErr != nil { + if err == nil { + t.Errorf("expected error %v, got nil", testInput.ExpectedErr) + return + } + assert.True(t, strings.Contains(err.Error(), testInput.ExpectedErr.Error())) + return + } + assert.Nil(t, err) + + for k, v := range testInput.TemplateWriter.FileMap { + if testInput.GenerateBaseTemplate { + err = os.MkdirAll(testInput.FixturesBaseDir, os.ModePerm) + assert.Nil(t, err, "error creating base dir for new template fixture") + err = os.WriteFile(fmt.Sprintf("%s/%s", testInput.FixturesBaseDir, k), []byte(v), os.ModePerm) + assert.Nil(t, err, "error writing new template fixture") + // skip the file validation checks + continue + } + + fileName := k + if overrideFile, ok := overrideReverseLookup[filepath.Base(k)]; ok && testInput.UseBaseFixtureWithFileNameOverride { + fileName = strings.Replace(fileName, filepath.Base(k), overrideFile, 1) + } + + err = fixtures.ValidateContentAgainstFixture(v, fmt.Sprintf("%s/%s", testInput.FixturesBaseDir, fileName)) + assert.Nil(t, err) + } + }) +} + +func AlwaysFailingValidator(value string) error { + return fmt.Errorf("this is a failing validator") +} + +func AlwaysFailingTransformer(value string) (any, error) { + return "", fmt.Errorf("this is a failing transformer") +} + +func K8sLabelValidator(value string) error { + labelRegex, err := regexp.Compile("^((A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?$") + if err != nil { + return err + } + if !labelRegex.MatchString(value) { + return fmt.Errorf("invalid label: %s", value) + } + return nil +} diff --git a/pkg/handlers/templatetests/workflows_azure_kustomize_test.go b/pkg/handlers/templatetests/workflows_azure_kustomize_test.go new file mode 100644 index 00000000..897d4291 --- /dev/null +++ b/pkg/handlers/templatetests/workflows_azure_kustomize_test.go @@ -0,0 +1,32 @@ +package templatetests + +import ( + "testing" + + "github.com/Azure/draft/pkg/templatewriter/writers" +) + +func TestAzureWorkflowKustomizeTemplates(t *testing.T) { + tests := []TestInput{ + { + Name: "valid azpipeline manifests deployment", + TemplateName: "azure-pipeline-kustomize", + FixturesBaseDir: "../../fixtures/workflows/azurepipelines/kustomize", + Version: "0.0.1", + Dest: ".", + TemplateWriter: &writers.FileMapWriter{}, + VarMap: map[string]string{ + "ARMSERVICECONNECTION": "testserviceconnection", + "AZURECONTAINERREGISTRY": "myacr.acr.io", + "CONTAINERNAME": "myapp", + "CLUSTERRESOURCEGROUP": "myrg", + "ACRRESOURCEGROUP": "myrg", + "CLUSTERNAME": "testcluster", + }, + }, + } + + for _, test := range tests { + RunTemplateTest(t, test) + } +} diff --git a/pkg/handlers/templatetests/workflows_azure_manifest_test.go b/pkg/handlers/templatetests/workflows_azure_manifest_test.go new file mode 100644 index 00000000..988e30bc --- /dev/null +++ b/pkg/handlers/templatetests/workflows_azure_manifest_test.go @@ -0,0 +1,32 @@ +package templatetests + +import ( + "testing" + + "github.com/Azure/draft/pkg/templatewriter/writers" +) + +func TestAzureWorkflowManifestTemplates(t *testing.T) { + tests := []TestInput{ + { + Name: "valid azpipeline manifests deployment", + TemplateName: "azure-pipeline-manifests", + FixturesBaseDir: "../../fixtures/workflows/azurepipelines/manifests", + Version: "0.0.1", + Dest: ".", + TemplateWriter: &writers.FileMapWriter{}, + VarMap: map[string]string{ + "ARMSERVICECONNECTION": "testserviceconnection", + "AZURECONTAINERREGISTRY": "myacr.acr.io", + "CONTAINERNAME": "myapp", + "CLUSTERRESOURCEGROUP": "myrg", + "ACRRESOURCEGROUP": "myrg", + "CLUSTERNAME": "testcluster", + }, + }, + } + + for _, test := range tests { + RunTemplateTest(t, test) + } +} diff --git a/pkg/handlers/templatetests/workflows_github_helm_test.go b/pkg/handlers/templatetests/workflows_github_helm_test.go new file mode 100644 index 00000000..179d1f03 --- /dev/null +++ b/pkg/handlers/templatetests/workflows_github_helm_test.go @@ -0,0 +1,41 @@ +package templatetests + +import ( + "testing" + + "github.com/Azure/draft/pkg/templatewriter/writers" +) + +func TestGitHubWorkflowHelmTemplates(t *testing.T) { + tests := []TestInput{ + { + Name: "valid helm workflow", + TemplateName: "github-workflow-helm", + FixturesBaseDir: "../../fixtures/workflows/github/helm", + Version: "0.0.1", + Dest: ".", + TemplateWriter: &writers.FileMapWriter{}, + VarMap: map[string]string{ + "WORKFLOWNAME": "testWorkflow", + "BRANCHNAME": "testBranch", + "ACRRESOURCEGROUP": "testAcrRG", + "AZURECONTAINERREGISTRY": "testAcr", + "CONTAINERNAME": "testContainer", + "CLUSTERRESOURCEGROUP": "testClusterRG", + "CLUSTERNAME": "testCluster", + "KUSTOMIZEPATH": "./overlays/production", + "DEPLOYMENTMANIFESTPATH": "./manifests", + "DOCKERFILE": "./Dockerfile", + "BUILDCONTEXTPATH": "test", + "CHARTPATH": "testPath", + "CHARTOVERRIDEPATH": "testOverridePath", + "CHARTOVERRIDES": "replicas:2", + "NAMESPACE": "default", + }, + }, + } + + for _, test := range tests { + RunTemplateTest(t, test) + } +} diff --git a/pkg/handlers/templatetests/workflows_github_kustomize_test.go b/pkg/handlers/templatetests/workflows_github_kustomize_test.go new file mode 100644 index 00000000..69894be0 --- /dev/null +++ b/pkg/handlers/templatetests/workflows_github_kustomize_test.go @@ -0,0 +1,37 @@ +package templatetests + +import ( + "testing" + + "github.com/Azure/draft/pkg/templatewriter/writers" +) + +func TestGitHubWorkflowKustomizeTemplates(t *testing.T) { + tests := []TestInput{ + { + Name: "valid kustomize workflow", + TemplateName: "github-workflow-kustomize", + FixturesBaseDir: "../../fixtures/workflows/github/kustomize", + Version: "0.0.1", + Dest: ".", + TemplateWriter: &writers.FileMapWriter{}, + VarMap: map[string]string{ + "WORKFLOWNAME": "testWorkflow", + "BRANCHNAME": "testBranch", + "ACRRESOURCEGROUP": "testAcrRG", + "AZURECONTAINERREGISTRY": "testAcr", + "CONTAINERNAME": "testContainer", + "CLUSTERRESOURCEGROUP": "testClusterRG", + "CLUSTERNAME": "testCluster", + "DEPLOYMENTMANIFESTPATH": "./manifests", + "DOCKERFILE": "./Dockerfile", + "BUILDCONTEXTPATH": "test", + "NAMESPACE": "default", + }, + }, + } + + for _, test := range tests { + RunTemplateTest(t, test) + } +} diff --git a/pkg/prompts/prompts.go b/pkg/prompts/prompts.go index 39f8385e..8b4d3bf8 100644 --- a/pkg/prompts/prompts.go +++ b/pkg/prompts/prompts.go @@ -53,6 +53,15 @@ func RunPromptsFromConfigWithSkipsIO(draftConfig *config.DraftConfig, Stdin io.R continue } + isVarActive, err := draftConfig.CheckActiveWhenConstraint(variable) + if err != nil { + return fmt.Errorf("unable to check ActiveWhen constraint: %w", err) + } + + if !isVarActive { + continue + } + log.Debugf("constructing prompt for: %s", variable.Name) if variable.Type == "bool" { input, err := RunBoolPrompt(variable, Stdin, Stdout) diff --git a/template/deployments/helm/charts/templates/deployment.yaml b/template/deployments/helm/charts/templates/deployment.yaml index e4bad855..14807734 100644 --- a/template/deployments/helm/charts/templates/deployment.yaml +++ b/template/deployments/helm/charts/templates/deployment.yaml @@ -26,6 +26,9 @@ spec: ` -}} labels: {{ .Config.GetVariableValue "APPNAME" | printf "{{- include \"%s.selectorLabels\" . | nindent 8 }}" }} + {{- if eq (.Config.GetVariableValue "ENABLEWORKLOADIDENTITY") "true" }} + azure.workload.identity/use: "true" + {{- end}} namespace: {{ print "{{ .Values.namespace }}" }} spec: {{- ` @@ -33,6 +36,11 @@ spec: imagePullSecrets: {{- toYaml . | nindent 8 }} {{- end }} + ` -}} + {{- if eq (.Config.GetVariableValue "ENABLEWORKLOADIDENTITY") "true" }} + serviceAccountName: {{ .Config.GetVariableValue "SERVICEACCOUNT" }} + {{- end}} + {{- ` securityContext: {{- toYaml .Values.podSecurityContext | nindent 8 }} containers: @@ -49,12 +57,17 @@ 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: {{ .Config.GetVariableValue "APPNAME" | printf "{{ include \"%s.fullname\" . }}-config" }} + - secretRef: + name: {{ .Config.GetVariableValue "ENVSECRETREF" }} + optional: true {{- ` {{- with .Values.nodeSelector }} nodeSelector: diff --git a/template/deployments/helm/charts/values.yaml b/template/deployments/helm/charts/values.yaml index 8ace3114..1b0608d9 100644 --- a/template/deployments/helm/charts/values.yaml +++ b/template/deployments/helm/charts/values.yaml @@ -10,7 +10,7 @@ containerPort: {{ .Config.GetVariableValue "PORT" }} image: repository: {{ .Config.GetVariableValue "IMAGENAME" }} tag: {{ .Config.GetVariableValue "IMAGETAG" }} - pullPolicy: Always + pullPolicy: {{ .Config.GetVariableValue "IMAGEPULLPOLICY" }} imagePullSecrets: [] nameOverride: "" @@ -25,6 +25,10 @@ service: type: LoadBalancer port: {{ .Config.GetVariableValue "SERVICEPORT" }} +{{- if eq (.Config.GetVariableValue "ENABLEWORKLOADIDENTITY") "true" }} +serviceAccountName: {{ .Config.GetVariableValue "SERVICEACCOUNT" }} +{{- end}} + resources: # We usually recommend not to specify default resources and to leave this as a conscious # choice for the user. This also increases chances charts run on environments with little @@ -45,16 +49,42 @@ autoscaling: # targetMemoryUtilizationPercentage: 80 livenessProbe: +{{- if eq (.Config.GetVariableValue "PROBETYPE") "httpGet" }} + httpGet: + path: {{ .Config.GetVariableValue "PROBEHTTPPATH" }} + port: {{ .Config.GetVariableValue "PORT" }} +{{- else if eq (.Config.GetVariableValue "PROBETYPE") "tcpSocket" }} tcpSocket: port: {{ .Config.GetVariableValue "PORT" }} +{{- end }} readinessProbe: +{{- if eq (.Config.GetVariableValue "PROBETYPE") "httpGet" }} + httpGet: + path: {{ .Config.GetVariableValue "PROBEHTTPPATH" }} + port: {{ .Config.GetVariableValue "PORT" }} +{{- else if eq (.Config.GetVariableValue "PROBETYPE") "tcpSocket" }} tcpSocket: port: {{ .Config.GetVariableValue "PORT" }} +{{- end }} periodSeconds: {{ .Config.GetVariableValue "READINESSPERIOD" }} timeoutSeconds: {{ .Config.GetVariableValue "READINESSTIMEOUT" }} failureThreshold: {{ .Config.GetVariableValue "READINESSFAILURETHRESHOLD" }} successThreshold: {{ .Config.GetVariableValue "READINESSSUCCESSTHRESHOLD" }} initialDelaySeconds: {{ .Config.GetVariableValue "READINESSINITIALDELAY" }} +startupProbe: +{{- if eq (.Config.GetVariableValue "PROBETYPE") "httpGet" }} + httpGet: + path: {{ .Config.GetVariableValue "PROBEHTTPPATH" }} + port: {{ .Config.GetVariableValue "PORT" }} +{{- else if eq (.Config.GetVariableValue "PROBETYPE") "tcpSocket" }} + tcpSocket: + port: {{ .Config.GetVariableValue "PORT" }} +{{- end }} + periodSeconds: {{ .Config.GetVariableValue "STARTUPPERIOD" }} + timeoutSeconds: {{ .Config.GetVariableValue "STARTUPTIMEOUT" }} + failureThreshold: {{ .Config.GetVariableValue "STARTUPFAILURETHRESHOLD" }} + successThreshold: {{ .Config.GetVariableValue "STARTUPSUCCESSTHRESHOLD" }} + initialDelaySeconds: {{ .Config.GetVariableValue "STARTUPINITIALDELAY" }} nodeSelector: {} diff --git a/template/deployments/helm/draft.yaml b/template/deployments/helm/draft.yaml index 6e5a0e37..e779471a 100644 --- a/template/deployments/helm/draft.yaml +++ b/template/deployments/helm/draft.yaml @@ -45,6 +45,18 @@ variables: value: "latest" description: "the tag of the image to use in the deployment" versions: ">=0.0.1" + - name: "IMAGEPULLPOLICY" + type: "string" + kind: "imagePullPolicy" + default: + disablePrompt: true + value: "Always" + allowedValues: + - "Always" + - "IfNotPresent" + - "Never" + description: "the imagePullPolicy" + versions: ">=0.0.1" - name: "GENERATORLABEL" type: "string" kind: "label" @@ -66,7 +78,7 @@ variables: kind: "kubernetesResourceRequest" default: disablePrompt: true - value: "512Mi" + value: "1Gi" description: "resource request for Memory" versions: ">=0.0.1" - name: "CPULIMIT" @@ -74,7 +86,7 @@ variables: kind: "kubernetesResourceLimit" default: disablePrompt: true - value: "2" + value: "1" description: "resource limit for CPU" versions: ">=0.0.1" - name: "MEMLIMIT" @@ -85,6 +97,66 @@ variables: value: "1Gi" description: "resource request for Memory" versions: ">=0.0.1" + - name: "PROBETYPE" + type: "string" + kind: "kubernetesProbeType" + default: + disablePrompt: true + value: "tcpSocket" + description: "The type of probe to use for container probes. Options are httpGet or tcpSocket" + versions: ">=0.0.1" + allowedValues: + - "httpGet" + - "tcpSocket" + - name: "PROBEHTTPPATH" + type: "string" + kind: "kubernetesProbeHttpPath" + activeWhen: + - variableName: "PROBETYPE" + value: "httpGet" + condition: "equals" + description: "The path to use for the httpGet probes" + versions: ">=0.0.1" + - name: "STARTUPPERIOD" + type: "int" + kind: "kubernetesProbePeriod" + default: + disablePrompt: true + value: 1 + description: "kubernetes startup probe period in seconds" + versions: ">=0.0.1" + - name: "STARTUPTIMEOUT" + type: "int" + kind: "kubernetesProbeTimeout" + default: + disablePrompt: true + value: 3 + description: "kubernetes startup probe timeout in seconds" + versions: ">=0.0.1" + - name: "STARTUPFAILURETHRESHOLD" + type: "int" + kind: "kubernetesProbeThreshold" + default: + disablePrompt: true + value: 1 + description: "kubernetes startup probe failure threshold" + versions: ">=0.0.1" + - name: "STARTUPSUCCESSTHRESHOLD" + type: "int" + kind: "kubernetesProbeThreshold" + default: + disablePrompt: true + value: 1 + description: "kubernetes startup probe success threshold" + versions: ">=0.0.1" + - name: "STARTUPINITIALDELAY" + type: "int" + kind: "kubernetesProbeDelay" + default: + disablePrompt: true + value: 5 + description: "kubernetes startup probe initial delay in seconds" + versions: ">=0.0.1" - name: "READINESSPERIOD" type: "int" kind: "kubernetesProbePeriod" @@ -132,4 +204,29 @@ variables: disablePrompt: true value: "{}" description: "a map of key/value environment variables to be set in the deployment" + versions: ">=0.0.1" + - name: "ENABLEWORKLOADIDENTITY" + type: "bool" + kind: "flag" + default: + disablePrompt: true + value: false + description: "flag to enable workload identity" + versions: ">=0.0.1" + - name: "SERVICEACCOUNT" + type: "string" + kind: "kubernetesResourceName" + activeWhen: + - variableName: "ENABLEWORKLOADIDENTITY" + value: "true" + condition: "equals" + description: "the name of the service account to use with workload identity" + versions: ">=0.0.1" + - name: "ENVSECRETREF" + type: "string" + kind: "kubernetesResourceName" + default: + disablePrompt: true + value: "secret-ref" + description: "the name of the kubernetes secret reference" versions: ">=0.0.1" \ No newline at end of file diff --git a/template/deployments/kustomize/base/deployment.yaml b/template/deployments/kustomize/base/deployment.yaml index 7f216e0d..aae864c6 100644 --- a/template/deployments/kustomize/base/deployment.yaml +++ b/template/deployments/kustomize/base/deployment.yaml @@ -5,6 +5,9 @@ metadata: labels: app.kubernetes.io/name: {{ .Config.GetVariableValue "APPNAME" }} kubernetes.azure.com/generator: {{ .Config.GetVariableValue "GENERATORLABEL" }} + {{- if eq (.Config.GetVariableValue "ENABLEWORKLOADIDENTITY") "true" }} + azure.workload.identity/use: "true" + {{- end}} namespace: {{ .Config.GetVariableValue "NAMESPACE" }} spec: replicas: 1 @@ -16,10 +19,13 @@ spec: labels: app.kubernetes.io/name: {{ .Config.GetVariableValue "APPNAME" }} spec: + {{- if eq (.Config.GetVariableValue "ENABLEWORKLOADIDENTITY") "true" }} + serviceAccountName: {{ .Config.GetVariableValue "SERVICEACCOUNT" }} + {{- end}} containers: - name: {{ .Config.GetVariableValue "APPNAME" }} image: {{ .Config.GetVariableValue "IMAGENAME" }}:{{ .Config.GetVariableValue "IMAGETAG" }} - imagePullPolicy: Always + imagePullPolicy: {{ .Config.GetVariableValue "IMAGEPULLPOLICY" }} ports: - containerPort: {{ .Config.GetVariableValue "PORT"}} resources: @@ -32,17 +38,46 @@ spec: envFrom: - configMapRef: name: {{ .Config.GetVariableValue "APPNAME" | printf "%s-config" }} + - secretRef: + name: {{ .Config.GetVariableValue "ENVSECRETREF" }} + optional: true livenessProbe: + {{- if eq (.Config.GetVariableValue "PROBETYPE") "httpGet" }} + httpGet: + path: {{ .Config.GetVariableValue "PROBEHTTPPATH" }} + port: {{ .Config.GetVariableValue "PORT" }} + {{- else if eq (.Config.GetVariableValue "PROBETYPE") "tcpSocket" }} tcpSocket: port: {{ .Config.GetVariableValue "PORT" }} + {{- end }} readinessProbe: + {{- if eq (.Config.GetVariableValue "PROBETYPE") "httpGet" }} + httpGet: + path: {{ .Config.GetVariableValue "PROBEHTTPPATH" }} + port: {{ .Config.GetVariableValue "PORT" }} + {{- else if eq (.Config.GetVariableValue "PROBETYPE") "tcpSocket" }} tcpSocket: port: {{ .Config.GetVariableValue "PORT" }} + {{- end }} periodSeconds: {{ .Config.GetVariableValue "READINESSPERIOD" }} timeoutSeconds: {{ .Config.GetVariableValue "READINESSTIMEOUT" }} failureThreshold: {{ .Config.GetVariableValue "READINESSFAILURETHRESHOLD" }} successThreshold: {{ .Config.GetVariableValue "READINESSSUCCESSTHRESHOLD" }} initialDelaySeconds: {{ .Config.GetVariableValue "READINESSINITIALDELAY" }} + startupProbe: + {{- if eq (.Config.GetVariableValue "PROBETYPE") "httpGet" }} + httpGet: + path: {{ .Config.GetVariableValue "PROBEHTTPPATH" }} + port: {{ .Config.GetVariableValue "PORT" }} + {{- else if eq (.Config.GetVariableValue "PROBETYPE") "tcpSocket" }} + tcpSocket: + port: {{ .Config.GetVariableValue "PORT" }} + {{- end }} + periodSeconds: {{ .Config.GetVariableValue "STARTUPPERIOD" }} + timeoutSeconds: {{ .Config.GetVariableValue "STARTUPTIMEOUT" }} + failureThreshold: {{ .Config.GetVariableValue "STARTUPFAILURETHRESHOLD" }} + successThreshold: {{ .Config.GetVariableValue "STARTUPSUCCESSTHRESHOLD" }} + initialDelaySeconds: {{ .Config.GetVariableValue "STARTUPINITIALDELAY" }} securityContext: seccompProfile: type: RuntimeDefault diff --git a/template/deployments/kustomize/draft.yaml b/template/deployments/kustomize/draft.yaml index 074510a0..53c85b65 100644 --- a/template/deployments/kustomize/draft.yaml +++ b/template/deployments/kustomize/draft.yaml @@ -45,6 +45,18 @@ variables: value: "latest" description: "the tag of the image to use in the deployment" versions: ">=0.0.1" + - name: "IMAGEPULLPOLICY" + type: "string" + kind: "imagePullPolicy" + default: + disablePrompt: true + value: "Always" + allowedValues: + - "Always" + - "IfNotPresent" + - "Never" + description: "the imagePullPolicy" + versions: ">=0.0.1" - name: "GENERATORLABEL" type: "string" kind: "label" @@ -66,7 +78,7 @@ variables: kind: "kubernetesResourceRequest" default: disablePrompt: true - value: "512Mi" + value: "1Gi" description: "resource request for Memory" versions: ">=0.0.1" - name: "CPULIMIT" @@ -74,7 +86,7 @@ variables: kind: "kubernetesResourceLimit" default: disablePrompt: true - value: "2" + value: "1" description: "resource limit for CPU" versions: ">=0.0.1" - name: "MEMLIMIT" @@ -85,6 +97,66 @@ variables: value: "1Gi" description: "resource request for Memory" versions: ">=0.0.1" + - name: "PROBETYPE" + type: "string" + kind: "kubernetesProbeType" + default: + disablePrompt: true + value: "tcpSocket" + description: "The type of probe to use for container probes. Options are httpGet or tcpSocket" + versions: ">=0.0.1" + allowedValues: + - "httpGet" + - "tcpSocket" + - name: "PROBEHTTPPATH" + type: "string" + kind: "kubernetesProbeHttpPath" + activeWhen: + - variableName: "PROBETYPE" + value: "httpGet" + condition: "equals" + description: "The path to use for the httpGet probes" + versions: ">=0.0.1" + - name: "STARTUPPERIOD" + type: "int" + kind: "kubernetesProbePeriod" + default: + disablePrompt: true + value: 1 + description: "kubernetes startup probe period in seconds" + versions: ">=0.0.1" + - name: "STARTUPTIMEOUT" + type: "int" + kind: "kubernetesProbeTimeout" + default: + disablePrompt: true + value: 3 + description: "kubernetes startup probe timeout in seconds" + versions: ">=0.0.1" + - name: "STARTUPFAILURETHRESHOLD" + type: "int" + kind: "kubernetesProbeThreshold" + default: + disablePrompt: true + value: 1 + description: "kubernetes startup probe failure threshold" + versions: ">=0.0.1" + - name: "STARTUPSUCCESSTHRESHOLD" + type: "int" + kind: "kubernetesProbeThreshold" + default: + disablePrompt: true + value: 1 + description: "kubernetes startup probe success threshold" + versions: ">=0.0.1" + - name: "STARTUPINITIALDELAY" + type: "int" + kind: "kubernetesProbeDelay" + default: + disablePrompt: true + value: 5 + description: "kubernetes startup probe initial delay in seconds" + versions: ">=0.0.1" - name: "READINESSPERIOD" type: "int" kind: "kubernetesProbePeriod" @@ -132,4 +204,29 @@ variables: disablePrompt: true value: "{}" description: "a map of key/value environment variables to be set in the deployment" + versions: ">=0.0.1" + - name: "ENVSECRETREF" + type: "string" + kind: "kubernetesResourceName" + default: + disablePrompt: true + value: "secret-ref" + description: "the name of the kubernetes secret reference" + versions: ">=0.0.1" + - name: "ENABLEWORKLOADIDENTITY" + type: "bool" + kind: "flag" + default: + disablePrompt: true + value: false + description: "flag to enable workload identity" + versions: ">=0.0.1" + - name: "SERVICEACCOUNT" + type: "string" + kind: "kubernetesResourceName" + activeWhen: + - variableName: "ENABLEWORKLOADIDENTITY" + value: "true" + condition: "equals" + description: "the name of the service account to use with workload identity" versions: ">=0.0.1" \ No newline at end of file diff --git a/template/deployments/manifests/draft.yaml b/template/deployments/manifests/draft.yaml index cfc25e2c..225a429a 100644 --- a/template/deployments/manifests/draft.yaml +++ b/template/deployments/manifests/draft.yaml @@ -45,6 +45,18 @@ variables: value: "latest" description: "the tag of the image to use in the deployment" versions: ">=0.0.1" + - name: "IMAGEPULLPOLICY" + type: "string" + kind: "imagePullPolicy" + default: + disablePrompt: true + value: "Always" + allowedValues: + - "Always" + - "IfNotPresent" + - "Never" + description: "the imagePullPolicy" + versions: ">=0.0.1" - name: "GENERATORLABEL" type: "string" kind: "label" @@ -66,7 +78,7 @@ variables: kind: "kubernetesResourceRequest" default: disablePrompt: true - value: "512Mi" + value: "1Gi" description: "resource request for Memory" versions: ">=0.0.1" - name: "CPULIMIT" @@ -74,7 +86,7 @@ variables: kind: "kubernetesResourceLimit" default: disablePrompt: true - value: "2" + value: "1" description: "resource limit for CPU" versions: ">=0.0.1" - name: "MEMLIMIT" @@ -85,6 +97,66 @@ variables: value: "1Gi" description: "resource request for Memory" versions: ">=0.0.1" + - name: "PROBETYPE" + type: "string" + kind: "kubernetesProbeType" + default: + disablePrompt: true + value: "tcpSocket" + description: "The type of probe to use for container probes. Options are httpGet or tcpSocket" + versions: ">=0.0.1" + allowedValues: + - "httpGet" + - "tcpSocket" + - name: "PROBEHTTPPATH" + type: "string" + kind: "kubernetesProbeHttpPath" + activeWhen: + - variableName: "PROBETYPE" + value: "httpGet" + condition: "equals" + description: "The path to use for the httpGet probes" + versions: ">=0.0.1" + - name: "STARTUPPERIOD" + type: "int" + kind: "kubernetesProbePeriod" + default: + disablePrompt: true + value: 1 + description: "kubernetes startup probe period in seconds" + versions: ">=0.0.1" + - name: "STARTUPTIMEOUT" + type: "int" + kind: "kubernetesProbeTimeout" + default: + disablePrompt: true + value: 3 + description: "kubernetes startup probe timeout in seconds" + versions: ">=0.0.1" + - name: "STARTUPFAILURETHRESHOLD" + type: "int" + kind: "kubernetesProbeThreshold" + default: + disablePrompt: true + value: 1 + description: "kubernetes startup probe failure threshold" + versions: ">=0.0.1" + - name: "STARTUPSUCCESSTHRESHOLD" + type: "int" + kind: "kubernetesProbeThreshold" + default: + disablePrompt: true + value: 1 + description: "kubernetes startup probe success threshold" + versions: ">=0.0.1" + - name: "STARTUPINITIALDELAY" + type: "int" + kind: "kubernetesProbeDelay" + default: + disablePrompt: true + value: 5 + description: "kubernetes startup probe initial delay in seconds" + versions: ">=0.0.1" - name: "READINESSPERIOD" type: "int" kind: "kubernetesProbePeriod" @@ -131,5 +203,30 @@ variables: default: disablePrompt: true value: "{}" - description: "a map of key/value environment variables to be set in the deployment" + description: "a json map of string -> string key/value environment variables to be set in the deployment" + versions: ">=0.0.1" + - name: "ENABLEWORKLOADIDENTITY" + type: "bool" + kind: "flag" + default: + disablePrompt: true + value: false + description: "flag to enable workload identity" + versions: ">=0.0.1" + - name: "SERVICEACCOUNT" + type: "string" + kind: "kubernetesResourceName" + activeWhen: + - variableName: "ENABLEWORKLOADIDENTITY" + value: "true" + condition: "equals" + description: "the name of the service account to use with workload identity" + versions: ">=0.0.1" + - name: "ENVSECRETREF" + type: "string" + kind: "kubernetesResourceName" + default: + disablePrompt: true + value: "secret-ref" + description: "the name of the kubernetes secret reference" versions: ">=0.0.1" \ No newline at end of file diff --git a/template/deployments/manifests/manifests/deployment.yaml b/template/deployments/manifests/manifests/deployment.yaml index 7f216e0d..aae864c6 100644 --- a/template/deployments/manifests/manifests/deployment.yaml +++ b/template/deployments/manifests/manifests/deployment.yaml @@ -5,6 +5,9 @@ metadata: labels: app.kubernetes.io/name: {{ .Config.GetVariableValue "APPNAME" }} kubernetes.azure.com/generator: {{ .Config.GetVariableValue "GENERATORLABEL" }} + {{- if eq (.Config.GetVariableValue "ENABLEWORKLOADIDENTITY") "true" }} + azure.workload.identity/use: "true" + {{- end}} namespace: {{ .Config.GetVariableValue "NAMESPACE" }} spec: replicas: 1 @@ -16,10 +19,13 @@ spec: labels: app.kubernetes.io/name: {{ .Config.GetVariableValue "APPNAME" }} spec: + {{- if eq (.Config.GetVariableValue "ENABLEWORKLOADIDENTITY") "true" }} + serviceAccountName: {{ .Config.GetVariableValue "SERVICEACCOUNT" }} + {{- end}} containers: - name: {{ .Config.GetVariableValue "APPNAME" }} image: {{ .Config.GetVariableValue "IMAGENAME" }}:{{ .Config.GetVariableValue "IMAGETAG" }} - imagePullPolicy: Always + imagePullPolicy: {{ .Config.GetVariableValue "IMAGEPULLPOLICY" }} ports: - containerPort: {{ .Config.GetVariableValue "PORT"}} resources: @@ -32,17 +38,46 @@ spec: envFrom: - configMapRef: name: {{ .Config.GetVariableValue "APPNAME" | printf "%s-config" }} + - secretRef: + name: {{ .Config.GetVariableValue "ENVSECRETREF" }} + optional: true livenessProbe: + {{- if eq (.Config.GetVariableValue "PROBETYPE") "httpGet" }} + httpGet: + path: {{ .Config.GetVariableValue "PROBEHTTPPATH" }} + port: {{ .Config.GetVariableValue "PORT" }} + {{- else if eq (.Config.GetVariableValue "PROBETYPE") "tcpSocket" }} tcpSocket: port: {{ .Config.GetVariableValue "PORT" }} + {{- end }} readinessProbe: + {{- if eq (.Config.GetVariableValue "PROBETYPE") "httpGet" }} + httpGet: + path: {{ .Config.GetVariableValue "PROBEHTTPPATH" }} + port: {{ .Config.GetVariableValue "PORT" }} + {{- else if eq (.Config.GetVariableValue "PROBETYPE") "tcpSocket" }} tcpSocket: port: {{ .Config.GetVariableValue "PORT" }} + {{- end }} periodSeconds: {{ .Config.GetVariableValue "READINESSPERIOD" }} timeoutSeconds: {{ .Config.GetVariableValue "READINESSTIMEOUT" }} failureThreshold: {{ .Config.GetVariableValue "READINESSFAILURETHRESHOLD" }} successThreshold: {{ .Config.GetVariableValue "READINESSSUCCESSTHRESHOLD" }} initialDelaySeconds: {{ .Config.GetVariableValue "READINESSINITIALDELAY" }} + startupProbe: + {{- if eq (.Config.GetVariableValue "PROBETYPE") "httpGet" }} + httpGet: + path: {{ .Config.GetVariableValue "PROBEHTTPPATH" }} + port: {{ .Config.GetVariableValue "PORT" }} + {{- else if eq (.Config.GetVariableValue "PROBETYPE") "tcpSocket" }} + tcpSocket: + port: {{ .Config.GetVariableValue "PORT" }} + {{- end }} + periodSeconds: {{ .Config.GetVariableValue "STARTUPPERIOD" }} + timeoutSeconds: {{ .Config.GetVariableValue "STARTUPTIMEOUT" }} + failureThreshold: {{ .Config.GetVariableValue "STARTUPFAILURETHRESHOLD" }} + successThreshold: {{ .Config.GetVariableValue "STARTUPSUCCESSTHRESHOLD" }} + initialDelaySeconds: {{ .Config.GetVariableValue "STARTUPINITIALDELAY" }} securityContext: seccompProfile: type: RuntimeDefault diff --git a/template/manifests/HorizontalPodAutoscaler/manifests/draft.yaml b/template/manifests/HorizontalPodAutoscaler/manifests/draft.yaml index c2dcc98e..9195c51e 100644 --- a/template/manifests/HorizontalPodAutoscaler/manifests/draft.yaml +++ b/template/manifests/HorizontalPodAutoscaler/manifests/draft.yaml @@ -42,6 +42,9 @@ variables: versions: ">=0.0.1" default: value: "cpu" + allowedValues: + - "cpu" + - "memory" - name: "AVGUTILIZATION" type: "int" kind: "scalingResourceUtilization" diff --git a/test/integration/clojure/helm.yaml b/test/integration/clojure/helm.yaml index c7b57b21..b4570ad0 100644 --- a/test/integration/clojure/helm.yaml +++ b/test/integration/clojure/helm.yaml @@ -10,6 +10,8 @@ deployVariables: value: "testapp" - name: "IMAGENAME" value: "host.minikube.internal:5001/testapp" + - name: "STARTUPINITIALDELAY" + value: 30 languageVariables: - name: "VERSION" value: "8-jdk-alpine" diff --git a/test/integration/clojure/kustomize.yaml b/test/integration/clojure/kustomize.yaml index 5cfee3cd..fa0f4f73 100644 --- a/test/integration/clojure/kustomize.yaml +++ b/test/integration/clojure/kustomize.yaml @@ -10,6 +10,8 @@ deployVariables: value: "testapp" - name: "IMAGENAME" value: "host.minikube.internal:5001/testapp" + - name: "STARTUPINITIALDELAY" + value: 30 languageVariables: - name: "VERSION" value: "8-jdk-alpine" diff --git a/test/integration/clojure/manifest.yaml b/test/integration/clojure/manifest.yaml index f9ac4edf..f0a27248 100644 --- a/test/integration/clojure/manifest.yaml +++ b/test/integration/clojure/manifest.yaml @@ -10,6 +10,8 @@ deployVariables: value: "testapp" - name: "IMAGENAME" value: "host.minikube.internal:5001/testapp" + - name: "STARTUPINITIALDELAY" + value: 30 languageVariables: - name: "VERSION" value: "8-jdk-alpine" diff --git a/test/integration/gradle/helm.yaml b/test/integration/gradle/helm.yaml index 4c85a036..9fbcd1ac 100644 --- a/test/integration/gradle/helm.yaml +++ b/test/integration/gradle/helm.yaml @@ -10,6 +10,8 @@ deployVariables: value: "testapp" - name: "IMAGENAME" value: "host.minikube.internal:5001/testapp" + - name: "STARTUPINITIALDELAY" + value: 30 languageVariables: - name: "VERSION" value: "11-jre" diff --git a/test/integration/gradle/kustomize.yaml b/test/integration/gradle/kustomize.yaml index c351a773..9f4da724 100644 --- a/test/integration/gradle/kustomize.yaml +++ b/test/integration/gradle/kustomize.yaml @@ -10,6 +10,8 @@ deployVariables: value: "testapp" - name: "IMAGENAME" value: "host.minikube.internal:5001/testapp" + - name: "STARTUPINITIALDELAY" + value: 30 languageVariables: - name: "VERSION" value: "11-jre" diff --git a/test/integration/gradle/manifest.yaml b/test/integration/gradle/manifest.yaml index a3781268..dee9ef7e 100644 --- a/test/integration/gradle/manifest.yaml +++ b/test/integration/gradle/manifest.yaml @@ -10,6 +10,8 @@ deployVariables: value: "testapp" - name: "IMAGENAME" value: "host.minikube.internal:5001/testapp" + - name: "STARTUPINITIALDELAY" + value: 30 languageVariables: - name: "VERSION" value: "11-jre" diff --git a/test/integration/java/helm.yaml b/test/integration/java/helm.yaml index 8c338a80..8ac5b16f 100644 --- a/test/integration/java/helm.yaml +++ b/test/integration/java/helm.yaml @@ -10,6 +10,8 @@ deployVariables: value: "testapp" - name: "IMAGENAME" value: "host.minikube.internal:5001/testapp" + - name: "STARTUPINITIALDELAY" + value: 30 languageVariables: - name: "VERSION" value: "11-jre" diff --git a/test/integration/java/kustomize.yaml b/test/integration/java/kustomize.yaml index 3aca136f..db0a432f 100644 --- a/test/integration/java/kustomize.yaml +++ b/test/integration/java/kustomize.yaml @@ -10,6 +10,8 @@ deployVariables: value: "testapp" - name: "IMAGENAME" value: "host.minikube.internal:5001/testapp" + - name: "STARTUPINITIALDELAY" + value: 30 languageVariables: - name: "VERSION" value: "11-jre" diff --git a/test/integration/java/manifest.yaml b/test/integration/java/manifest.yaml index 1aa1cfbc..d31b63d5 100644 --- a/test/integration/java/manifest.yaml +++ b/test/integration/java/manifest.yaml @@ -10,6 +10,8 @@ deployVariables: value: "testapp" - name: "IMAGENAME" value: "host.minikube.internal:5001/testapp" + - name: "STARTUPINITIALDELAY" + value: 30 languageVariables: - name: "VERSION" value: "11-jre" diff --git a/test/integration/swift/helm.yaml b/test/integration/swift/helm.yaml index c750d8be..843ce49d 100644 --- a/test/integration/swift/helm.yaml +++ b/test/integration/swift/helm.yaml @@ -14,6 +14,8 @@ deployVariables: value: "3" - name: "MEMLIMIT" value: "2Gi" + - name: "STARTUPINITIALDELAY" + value: 30 languageVariables: - name: "VERSION" value: "5.5" diff --git a/test/integration/swift/kustomize.yaml b/test/integration/swift/kustomize.yaml index e03493e8..45985c01 100644 --- a/test/integration/swift/kustomize.yaml +++ b/test/integration/swift/kustomize.yaml @@ -14,6 +14,8 @@ deployVariables: value: "3" - name: "MEMLIMIT" value: "2Gi" + - name: "STARTUPINITIALDELAY" + value: 30 languageVariables: - name: "VERSION" value: "5.5" diff --git a/test/integration/swift/manifest.yaml b/test/integration/swift/manifest.yaml index 1c1a4609..300b59ba 100644 --- a/test/integration/swift/manifest.yaml +++ b/test/integration/swift/manifest.yaml @@ -14,6 +14,8 @@ deployVariables: value: "3" - name: "MEMLIMIT" value: "2Gi" + - name: "STARTUPINITIALDELAY" + value: 30 languageVariables: - name: "VERSION" value: "5.5"