diff --git a/application/tests/alertmanagerconfig_test.yaml b/application/tests/alertmanagerconfig_test.yaml new file mode 100644 index 00000000..2ddc962e --- /dev/null +++ b/application/tests/alertmanagerconfig_test.yaml @@ -0,0 +1,71 @@ +suite: AlertmanagerConfig + +templates: + - alertmanagerconfig.yaml + +tests: + - it: does not yield AlertmanagerConfig if alertmanagerConfig.enabled is false + set: + alertmanagerConfig: + enabled: false + asserts: + - hasDocuments: + count: 0 + + - it: does not yield AlertmanagerConfig if API version is not available + set: + alertmanagerConfig: + enabled: true + capabilities: + apiVersions: + - apps/v1 + asserts: + - hasDocuments: + count: 0 + + - it: yields AlertmanagerConfig if enabled and API version is available + set: + alertmanagerConfig: + enabled: true + spec: + route: + groupBy: ['alertname'] + groupInterval: 5m + groupWait: 30s + repeatInterval: 1h + receivers: + - name: 'team-X-mails' + emailConfigs: + - to: 'team-X@example.com' + inhibitRules: + - sourceMatch: + severity: warning + targetMatch: + severity: critical + equal: ['alertname', 'team'] + capabilities: + apiVersions: + - monitoring.coreos.com/v1alpha1 + asserts: + - hasDocuments: + count: 1 + - isKind: + of: AlertmanagerConfig + + - it: includes additional labels when defined in values + set: + alertmanagerConfig: + enabled: true + selectionLabels: + foo: bar + test: ing + capabilities: + apiVersions: + - monitoring.coreos.com/v1alpha1 + asserts: + - equal: + path: metadata.labels.foo + value: bar + - equal: + path: metadata.labels.test + value: ing diff --git a/application/tests/backup_test.yaml b/application/tests/backup_test.yaml new file mode 100644 index 00000000..4c469122 --- /dev/null +++ b/application/tests/backup_test.yaml @@ -0,0 +1,93 @@ +suite: Backup + +templates: + - backup.yaml + +tests: + - it: does not yield Backup resource if backup.enabled is false + set: + backup: + enabled: false + asserts: + - hasDocuments: + count: 0 + + - it: yields Backup resource if backup.enabled is true + set: + backup: + enabled: true + namespace: my-namespace + storageLocation: my-storage-location + asserts: + - hasDocuments: + count: 1 + - isKind: + of: Backup + + - it: includes correct metadata name + set: + backup: + enabled: true + namespace: my-namespace + storageLocation: my-storage-location + applicationName: my-app + asserts: + - equal: + path: metadata.name + value: my-app-backup + + - it: includes correct namespace + set: + backup: + enabled: true + namespace: my-namespace + storageLocation: my-storage-location + asserts: + - equal: + path: metadata.namespace + value: my-namespace + + - it: uses default values for optional fields + set: + backup: + enabled: true + namespace: my-namespace + storageLocation: my-storage-location + asserts: + - equal: + path: spec.defaultVolumesToRestic + value: true + - equal: + path: spec.snapshotVolumes + value: true + - equal: + path: spec.ttl + value: "1h0m0s" + + - it: includes includedResources when defined + set: + backup: + enabled: true + includedResources: + - deployments + - services + asserts: + - equal: + path: spec.includedResources + value: + - deployments + - services + + - it: includes excludedResources when defined + set: + backup: + enabled: true + excludedResources: + - secrets + - configmaps + asserts: + - equal: + path: spec.excludedResources + value: + - secrets + - configmaps diff --git a/application/tests/certificate_test.yaml b/application/tests/certificate_test.yaml new file mode 100644 index 00000000..271f0b0e --- /dev/null +++ b/application/tests/certificate_test.yaml @@ -0,0 +1,160 @@ +suite: Certificate + +templates: + - certificate.yaml + +tests: + - it: does not yield Certificate resource if certificate.enabled is false + set: + certificate: + enabled: false + asserts: + - hasDocuments: + count: 0 + + - it: does not yield Certificate resource if cert-manager API version is not available + set: + certificate: + enabled: true + capabilities: + apiVersions: + - apps/v1 + asserts: + - hasDocuments: + count: 0 + + - it: yields Certificate resource if enabled and API version is available + set: + certificate: + enabled: true + secretName: my-secret + duration: 2160h + renewBefore: 360h + subject: + organizations: + - MyOrg + commonName: mydomain.com + usages: + - digital signature + - key encipherment + dnsNames: + - mydomain.com + - www.mydomain.com + issuerRef: + name: my-issuer + kind: Issuer + capabilities: + apiVersions: + - cert-manager.io/v1 + asserts: + - hasDocuments: + count: 1 + - isKind: + of: Certificate + + - it: includes additional labels when defined + set: + certificate: + enabled: true + additionalLabels: + foo: bar + test: ing + capabilities: + apiVersions: + - cert-manager.io/v1 + asserts: + - equal: + path: metadata.labels.foo + value: bar + - equal: + path: metadata.labels.test + value: ing + + - it: includes annotations when defined + set: + certificate: + enabled: true + annotations: + foo: bar + test: ing + capabilities: + apiVersions: + - cert-manager.io/v1 + asserts: + - equal: + path: metadata.annotations.foo + value: bar + - equal: + path: metadata.annotations.test + value: ing + + - it: does not include annotations if none are defined + set: + certificate: + enabled: true + annotations: {} + capabilities: + apiVersions: + - cert-manager.io/v1 + asserts: + - notExists: + path: metadata.annotations + + - it: includes issuer reference details + set: + certificate: + enabled: true + issuerRef: + name: my-issuer + kind: Issuer + group: cert-manager.io + capabilities: + apiVersions: + - cert-manager.io/v1 + asserts: + - equal: + path: spec.issuerRef.name + value: my-issuer + - equal: + path: spec.issuerRef.kind + value: Issuer + - equal: + path: spec.issuerRef.group + value: cert-manager.io + + - it: does not include keystores if not enabled + set: + certificate: + enabled: true + keystores: + enabled: false + capabilities: + apiVersions: + - cert-manager.io/v1 + asserts: + - notExists: + path: spec.keystores + + - it: includes keystores if enabled + set: + certificate: + enabled: true + keystores: + enabled: true + jks: + create: true + key: my-jks-key + name: my-jks-name + capabilities: + apiVersions: + - cert-manager.io/v1 + asserts: + - equal: + path: spec.keystores.jks.create + value: true + - equal: + path: spec.keystores.jks.passwordSecretRef.key + value: my-jks-key + - equal: + path: spec.keystores.jks.passwordSecretRef.name + value: my-jks-name diff --git a/application/tests/configmap_test.yaml b/application/tests/configmap_test.yaml new file mode 100644 index 00000000..fe51a916 --- /dev/null +++ b/application/tests/configmap_test.yaml @@ -0,0 +1,127 @@ +suite: ConfigMap + +templates: + - configmap.yaml + +tests: + - it: does not yield ConfigMap if configMap.enabled is false + set: + configMap: + enabled: false + asserts: + - hasDocuments: + count: 0 + + - it: yields ConfigMap if configMap.enabled is true + set: + configMap: + enabled: true + files: + test-config: + key1: value1 + asserts: + - hasDocuments: + count: 1 + - isKind: + of: ConfigMap + + - it: includes correct metadata name + set: + configMap: + enabled: true + files: + test-config: + key1: value1 + applicationName: my-app + asserts: + - equal: + path: metadata.name + value: my-app-test-config + + - it: includes correct namespace + set: + configMap: + enabled: true + files: + test-config: + key1: value1 + applicationName: my-app + release: + namespace: test-namespace + asserts: + - equal: + path: metadata.namespace + value: test-namespace + + - it: includes additional labels when defined + set: + configMap: + enabled: true + additionalLabels: + foo: bar + test: ing + files: + test-config: + key1: value1 + asserts: + - equal: + path: metadata.labels.foo + value: bar + - equal: + path: metadata.labels.test + value: ing + + - it: includes annotations when defined + set: + configMap: + enabled: true + annotations: + custom: annotation + files: + test-config: + key1: value1 + asserts: + - equal: + path: metadata.annotations.custom + value: annotation + + - it: does not include annotations if none are defined + set: + configMap: + enabled: true + annotations: {} + files: + test-config: + key1: value1 + asserts: + - notExists: + path: metadata.annotations + + - it: includes data from files correctly + set: + configMap: + enabled: true + files: + test-config: + key1: value1 + key2: value2 + asserts: + - equal: + path: data.key1 + value: value1 + - equal: + path: data.key2 + value: value2 + + - it: renders multiple ConfigMaps when multiple files are defined + set: + configMap: + enabled: true + files: + config1: + key1: value1 + config2: + key2: value2 + asserts: + - hasDocuments: + count: 2 diff --git a/application/tests/forecastle_test.yaml b/application/tests/forecastle_test.yaml new file mode 100644 index 00000000..55fe75c9 --- /dev/null +++ b/application/tests/forecastle_test.yaml @@ -0,0 +1,140 @@ +suite: Forecastle + +templates: + - forecastle.yaml + +tests: + - it: does not yield ForecastleApp resource if forecastle.enabled is false + set: + forecastle: + enabled: false + asserts: + - hasDocuments: + count: 0 + + - it: does not yield ForecastleApp resource if forecastle API version is not available + set: + forecastle: + enabled: true + capabilities: + apiVersions: + - apps/v1 + asserts: + - hasDocuments: + count: 0 + + - it: does not yield ForecastleApp resource if ingress is not enabled + set: + forecastle: + enabled: true + displayName: "My App" + icon: "https://example.com/icon.png" + group: "my-group" + additionalLabels: + foo: bar + ingress: + enabled: false + capabilities: + apiVersions: + - forecastle.stakater.com/v1alpha1 + asserts: + - hasDocuments: + count: 0 + + - it: yields ForecastleApp resource if enabled, API version is available, and ingress is enabled + set: + forecastle: + enabled: true + displayName: "My App" + icon: "https://example.com/icon.png" + group: "my-group" + additionalLabels: + foo: bar + ingress: + enabled: true + capabilities: + apiVersions: + - forecastle.stakater.com/v1alpha1 + asserts: + - hasDocuments: + count: 1 + - isKind: + of: ForecastleApp + + - it: yields ForecastleApp resource with correct metadata + set: + applicationName: "my-app" + forecastle: + enabled: true + displayName: "My App" + icon: "https://example.com/icon.png" + group: "my-group" + additionalLabels: + foo: bar + ingress: + enabled: true + capabilities: + apiVersions: + - forecastle.stakater.com/v1alpha1 + asserts: + - equal: + path: metadata.name + value: "my-app" + - equal: + path: metadata.labels.foo + value: bar + + - it: yields ForecastleApp resource with correct namespace + set: + forecastle: + enabled: true + displayName: "My App" + icon: "https://example.com/icon.png" + group: "my-group" + ingress: + enabled: true + capabilities: + apiVersions: + - forecastle.stakater.com/v1alpha1 + asserts: + - equal: + path: metadata.namespace + value: NAMESPACE + + - it: yields ForecastleApp resource with specified namespace when namespaceOverride is set + set: + forecastle: + enabled: true + ingress: + enabled: true + namespaceOverride: "app-namespace" + capabilities: + apiVersions: + - forecastle.stakater.com/v1alpha1 + asserts: + - equal: + path: metadata.namespace + value: app-namespace + + - it: yields ForecastleApp resource with correct spec properties + set: + forecastle: + enabled: true + displayName: "My App" + icon: "https://example.com/icon.png" + group: "my-group" + properties: + key1: value1 + key2: value2 + ingress: + enabled: true + capabilities: + apiVersions: + - forecastle.stakater.com/v1alpha1 + asserts: + - equal: + path: spec.properties.key1 + value: value1 + - equal: + path: spec.properties.key2 + value: value2 diff --git a/application/tests/grafanadashboard_test.yaml b/application/tests/grafanadashboard_test.yaml new file mode 100644 index 00000000..715a659d --- /dev/null +++ b/application/tests/grafanadashboard_test.yaml @@ -0,0 +1,210 @@ +suite: GrafanaDashboard + +templates: + - grafanadashboard.yaml + +tests: + - it: does not render when grafanaDashboard is not enabled + set: + grafanaDashboard: + enabled: false + asserts: + - hasDocuments: + count: 0 + + - it: does not render when API version is not available + set: + grafanaDashboard: + enabled: true + capabilities: + apiVersions: + - "v1" + asserts: + - hasDocuments: + count: 0 + + - it: renders a basic dashboard with minimal configuration + set: + grafanaDashboard: + enabled: true + contents: + example-dashboard: + json: + title: "Example Dashboard" + panels: + - type: graph + title: "Example Graph" + targets: + - target: "some_metric" + time: "now" + capabilities: + apiVersions: + - "grafana.integreatly.org/v1beta1" + asserts: + - hasDocuments: + count: 1 + - isKind: + of: GrafanaDashboard + - equal: + path: metadata.name + value: example-dashboard + - equal: + path: spec.json.title + value: "Example Dashboard" + + - it: renders multiple dashboards + set: + grafanaDashboard: + enabled: true + contents: + dashboard1: + json: '{"dashboard": "1"}' + dashboard2: + json: '{"dashboard": "2"}' + capabilities: + apiVersions: + - "grafana.integreatly.org/v1beta1" + asserts: + - hasDocuments: + count: 2 + + - it: includes additional labels when specified + set: + grafanaDashboard: + enabled: true + contents: + test-dashboard: + json: '{}' + additionalLabels: + custom: label + capabilities: + apiVersions: + - "grafana.integreatly.org/v1beta1" + asserts: + - equal: + path: metadata.labels.custom + value: label + + - it: includes annotations when specified + set: + grafanaDashboard: + enabled: true + contents: + test-dashboard: + json: '{}' + annotations: + custom: annotation + capabilities: + apiVersions: + - "grafana.integreatly.org/v1beta1" + asserts: + - equal: + path: metadata.annotations.custom + value: annotation + + - it: renders dashboard with URL instead of JSON + set: + grafanaDashboard: + enabled: true + contents: + test-dashboard: + url: https://example.com/dashboard + capabilities: + apiVersions: + - "grafana.integreatly.org/v1beta1" + asserts: + - equal: + path: spec.url + value: https://example.com/dashboard + + - it: includes allowCrossNamespaceImport when specified + set: + grafanaDashboard: + enabled: true + contents: + test-dashboard: + json: '{}' + allowCrossNamespaceImport: true + capabilities: + apiVersions: + - "grafana.integreatly.org/v1beta1" + asserts: + - equal: + path: spec.allowCrossNamespaceImport + value: true + + - it: includes folder when specified + set: + grafanaDashboard: + enabled: true + contents: + test-dashboard: + json: '{}' + folder: TestFolder + capabilities: + apiVersions: + - "grafana.integreatly.org/v1beta1" + asserts: + - equal: + path: spec.folder + value: TestFolder + + - it: includes instanceSelector when specified + set: + grafanaDashboard: + enabled: true + contents: + test-dashboard: + json: '{}' + instanceSelector: + matchLabels: + grafana: main + capabilities: + apiVersions: + - "grafana.integreatly.org/v1beta1" + asserts: + - equal: + path: spec.instanceSelector.matchLabels.grafana + value: main + + - it: includes configMapRef when specified + set: + grafanaDashboard: + enabled: true + contents: + test-dashboard: + json: '{}' + configMapRef: + name: dashboard-config + key: dashboard.json + capabilities: + apiVersions: + - "grafana.integreatly.org/v1beta1" + asserts: + - equal: + path: spec.configMapRef.name + value: dashboard-config + - equal: + path: spec.configMapRef.key + value: dashboard.json + + - it: includes datasources when specified + set: + grafanaDashboard: + enabled: true + contents: + test-dashboard: + json: '{}' + datasources: + - inputName: DS_PROMETHEUS + datasourceName: Prometheus + capabilities: + apiVersions: + - "grafana.integreatly.org/v1beta1" + asserts: + - equal: + path: spec.datasources[0].inputName + value: DS_PROMETHEUS + - equal: + path: spec.datasources[0].datasourceName + value: Prometheus diff --git a/application/tests/ingress_test.yaml b/application/tests/ingress_test.yaml new file mode 100644 index 00000000..8af396a5 --- /dev/null +++ b/application/tests/ingress_test.yaml @@ -0,0 +1,192 @@ +suite: Ingress + +templates: + - ingress.yaml + +tests: + - it: does not render when ingress is not enabled + set: + ingress: + enabled: false + asserts: + - hasDocuments: + count: 0 + + - it: renders a basic ingress with minimal configuration + set: + applicationName: "my-app" + ingress: + enabled: true + hosts: + - host: example.com + paths: + - path: / + pathType: ImplementationSpecific + serviceName: example-service + servicePort: http + asserts: + - hasDocuments: + count: 1 + - isKind: + of: Ingress + - equal: + path: metadata.name + value: my-app + - equal: + path: spec.rules[0].host + value: example.com + - equal: + path: spec.rules[0].http.paths[0].path + value: / + + - it: renders ingress with additional labels + set: + ingress: + enabled: true + additionalLabels: + custom: label + hosts: + - host: example.com + paths: + - path: / + pathType: ImplementationSpecific + serviceName: example-service + servicePort: http + asserts: + - equal: + path: metadata.labels.custom + value: label + + - it: renders ingress with annotations + set: + ingress: + enabled: true + annotations: + custom: annotation + hosts: + - host: example.com + paths: + - path: / + pathType: ImplementationSpecific + serviceName: example-service + servicePort: http + asserts: + - equal: + path: metadata.annotations.custom + value: annotation + + - it: renders ingress with ingressClassName + set: + ingress: + enabled: true + ingressClassName: nginx + hosts: + - host: example.com + paths: + - path: / + pathType: ImplementationSpecific + serviceName: example-service + servicePort: http + asserts: + - equal: + path: spec.ingressClassName + value: nginx + + - it: fails when no paths are specified for a host + set: + ingress: + enabled: true + hosts: + - host: example.com + paths: [] # No paths provided + asserts: + - failedTemplate: + errorMessage: "Specify paths for ingress host, check values.yaml" + + - it: renders ingress with multiple hosts + set: + ingress: + enabled: true + hosts: + - host: example.com + paths: + - path: / + pathType: ImplementationSpecific + serviceName: example-service + servicePort: http + - host: another-example.com + paths: + - path: /api + pathType: Prefix + serviceName: another-service + servicePort: http + asserts: + - hasDocuments: + count: 1 + - equal: + path: spec.rules[0].host + value: example.com + - equal: + path: spec.rules[1].host + value: another-example.com + + - it: renders ingress with TLS configuration + set: + ingress: + enabled: true + tls: + - hosts: + - example.com + secretName: example-tls + hosts: + - host: example.com + paths: + - path: / + pathType: ImplementationSpecific + serviceName: example-service + servicePort: http + asserts: + - equal: + path: spec.tls[0].hosts[0] + value: example.com + - equal: + path: spec.tls[0].secretName + value: example-tls + + - it: renders ingress with multiple paths for a single host + set: + ingress: + enabled: true + hosts: + - host: example.com + paths: + - path: / + pathType: ImplementationSpecific + serviceName: example-service + servicePort: http + - path: /api + pathType: Prefix + serviceName: api-service + servicePort: http + asserts: + - equal: + path: spec.rules[0].http.paths[0].path + value: / + - equal: + path: spec.rules[0].http.paths[1].path + value: /api + + - it: renders ingress with default pathType when not specified + set: + ingress: + enabled: true + hosts: + - host: example.com + paths: + - path: / + serviceName: example-service + servicePort: http + asserts: + - equal: + path: spec.rules[0].http.paths[0].pathType + value: ImplementationSpecific diff --git a/application/tests/networkpolicy_test.yaml b/application/tests/networkpolicy_test.yaml new file mode 100644 index 00000000..6ce2e15a --- /dev/null +++ b/application/tests/networkpolicy_test.yaml @@ -0,0 +1,165 @@ +suite: NetworkPolicy + +templates: + - networkpolicy.yaml + +tests: + - it: does not render when networkPolicy is not enabled + set: + networkPolicy: + enabled: false + asserts: + - hasDocuments: + count: 0 + + - it: renders a basic network policy with minimal configuration + set: + applicationName: my-app + networkPolicy: + enabled: true + ingress: [] + egress: [] + asserts: + - hasDocuments: + count: 1 + - isKind: + of: NetworkPolicy + - equal: + path: metadata.name + value: my-app + + - it: renders network policy with additional labels + set: + networkPolicy: + enabled: true + additionalLabels: + custom: label + ingress: [] + egress: [] + asserts: + - equal: + path: metadata.labels.custom + value: label + + - it: renders network policy with annotations + set: + networkPolicy: + enabled: true + annotations: + custom: annotation + ingress: [] + egress: [] + asserts: + - equal: + path: metadata.annotations.custom + value: annotation + + - it: renders network policy with ingress rules + set: + networkPolicy: + enabled: true + ingress: + - from: + - podSelector: + matchLabels: + role: frontend + ports: + - protocol: TCP + port: 80 + egress: [] + asserts: + - equal: + path: spec.ingress[0].from[0].podSelector.matchLabels.role + value: frontend + - equal: + path: spec.ingress[0].ports[0].protocol + value: TCP + - equal: + path: spec.ingress[0].ports[0].port + value: 80 + + - it: renders network policy with egress rules + set: + networkPolicy: + enabled: true + ingress: [] + egress: + - to: + - podSelector: + matchLabels: + role: backend + ports: + - protocol: TCP + port: 443 + asserts: + - equal: + path: spec.egress[0].to[0].podSelector.matchLabels.role + value: backend + - equal: + path: spec.egress[0].ports[0].protocol + value: TCP + - equal: + path: spec.egress[0].ports[0].port + value: 443 + + - it: renders network policy with both ingress and egress rules + set: + networkPolicy: + enabled: true + ingress: + - from: + - podSelector: + matchLabels: + role: frontend + ports: + - protocol: TCP + port: 80 + egress: + - to: + - podSelector: + matchLabels: + role: backend + ports: + - protocol: TCP + port: 443 + asserts: + - hasDocuments: + count: 1 + - equal: + path: spec.ingress[0].from[0].podSelector.matchLabels.role + value: frontend + - equal: + path: spec.egress[0].to[0].podSelector.matchLabels.role + value: backend + + - it: renders network policy with deployment pod labels + set: + networkPolicy: + enabled: true + ingress: [] + egress: [] + deployment: + podLabels: + app: my-app + asserts: + - equal: + path: spec.podSelector.matchLabels.app + value: my-app + + - it: renders network policy with both additional labels and annotations + set: + networkPolicy: + enabled: true + additionalLabels: + custom: label + annotations: + custom: annotation + ingress: [] + egress: [] + asserts: + - equal: + path: metadata.labels.custom + value: label + - equal: + path: metadata.annotations.custom + value: annotation diff --git a/application/tests/prometheusrule_test.yaml b/application/tests/prometheusrule_test.yaml new file mode 100644 index 00000000..43fe5bd6 --- /dev/null +++ b/application/tests/prometheusrule_test.yaml @@ -0,0 +1,138 @@ +suite: PrometheusRule + +templates: + - prometheusrule.yaml + +tests: + - it: does not render when prometheusRule is not enabled + set: + prometheusRule: + enabled: false + asserts: + - hasDocuments: + count: 0 + + - it: does not render when the API version is not available + set: + prometheusRule: + enabled: true + groups: [] + capabilities: + apiVersions: + - "v1" + asserts: + - hasDocuments: + count: 0 + + - it: renders a basic PrometheusRule with minimal configuration + set: + applicationName: my-app + prometheusRule: + enabled: true + groups: + - name: example-group + rules: + - alert: ExampleAlert + expr: up == 0 + for: 5m + labels: + severity: critical + annotations: + summary: "Instance {{ $labels.instance }} is down" + capabilities: + apiVersions: + - "monitoring.coreos.com/v1" + asserts: + - hasDocuments: + count: 1 + - isKind: + of: PrometheusRule + - equal: + path: metadata.name + value: my-app + - equal: + path: spec.groups[0].name + value: example-group + - equal: + path: spec.groups[0].rules[0].alert + value: ExampleAlert + + - it: renders PrometheusRule with additional labels + set: + prometheusRule: + enabled: true + additionalLabels: + custom: label + groups: + - name: example-group + rules: + - alert: ExampleAlert + expr: up == 0 + for: 5m + labels: + severity: critical + annotations: + summary: "Instance {{ $labels.instance }} is down" + capabilities: + apiVersions: + - "monitoring.coreos.com/v1" + asserts: + - equal: + path: metadata.labels.custom + value: label + + - it: renders PrometheusRule with multiple groups + set: + prometheusRule: + enabled: true + groups: + - name: group1 + rules: + - alert: Alert1 + expr: up == 0 + for: 5m + labels: + severity: critical + annotations: + summary: "Instance {{ $labels.instance }} is down" + - name: group2 + rules: + - alert: Alert2 + expr: http_requests_total > 100 + for: 10m + labels: + severity: warning + annotations: + summary: "High request rate on {{ $labels.instance }}" + capabilities: + apiVersions: + - "monitoring.coreos.com/v1" + asserts: + - equal: + path: spec.groups[0].name + value: group1 + - equal: + path: spec.groups[1].name + value: group2 + + - it: renders PrometheusRule with complex rules + set: + prometheusRule: + enabled: true + groups: + - name: complex-group + rules: + - alert: ComplexAlert + expr: sum(rate(http_requests_total[5m])) by (instance) > 100 + for: 10m + labels: + severity: warning + annotations: + summary: "High request rate on {{ $labels.instance }}" + capabilities: + apiVersions: + - "monitoring.coreos.com/v1" + asserts: + - equal: + path: spec.groups[0].rules[0].expr + value: sum(rate(http_requests_total[5m])) by (instance) > 100 diff --git a/application/tests/role_test.yaml b/application/tests/role_test.yaml new file mode 100644 index 00000000..47c52094 --- /dev/null +++ b/application/tests/role_test.yaml @@ -0,0 +1,161 @@ +suite: Role + +templates: + - role.yaml + +tests: + - it: does not render when RBAC is not enabled + set: + rbac: + enabled: false + roles: [] + asserts: + - hasDocuments: + count: 0 + + - it: does not render when no roles are defined + set: + rbac: + enabled: true + roles: [] + asserts: + - hasDocuments: + count: 0 + + - it: renders a basic Role with minimal configuration + set: + applicationName: my-app + rbac: + enabled: true + roles: + - name: example + rules: + - apiGroups: ["*"] + resources: ["pods"] + verbs: ["get", "list"] + asserts: + - hasDocuments: + count: 1 + - isKind: + of: Role + - equal: + path: metadata.name + value: my-app-role-example + - equal: + path: rules[0].apiGroups[0] + value: "*" + - equal: + path: rules[0].resources + value: ["pods"] + - equal: + path: rules[0].verbs + value: ["get", "list"] + + - it: renders Role with additional labels + set: + rbac: + enabled: true + additionalLabels: + custom: label + roles: + - name: example + rules: + - apiGroups: ["*"] + resources: ["pods"] + verbs: ["get", "list"] + asserts: + - equal: + path: metadata.labels.custom + value: label + + - it: renders Role with annotations + set: + rbac: + enabled: true + annotations: + custom: annotation + roles: + - name: example + rules: + - apiGroups: ["*"] + resources: ["pods"] + verbs: ["get", "list"] + asserts: + - equal: + path: metadata.annotations.custom + value: annotation + + - it: renders Role with multiple rules + set: + rbac: + enabled: true + roles: + - name: example + rules: + - apiGroups: ["*"] + resources: ["pods"] + verbs: ["get", "list"] + - apiGroups: ["apps"] + resources: ["deployments"] + verbs: ["create", "update"] + asserts: + - equal: + path: rules[1].apiGroups[0] + value: "apps" + - equal: + path: rules[1].resources[0] + value: "deployments" + - equal: + path: rules[1].verbs[0] + value: "create" + + - it: renders multiple roles + set: + applicationName: my-app + rbac: + enabled: true + roles: + - name: role1 + rules: + - apiGroups: ["*"] + resources: ["pods"] + verbs: ["get"] + - name: role2 + rules: + - apiGroups: ["apps"] + resources: ["deployments"] + verbs: ["create"] + asserts: + - hasDocuments: + count: 2 + + - it: renders Role with specific namespace + set: + rbac: + enabled: true + roles: + - name: example + rules: + - apiGroups: ["*"] + resources: ["pods"] + verbs: ["get", "list"] + asserts: + - equal: + path: metadata.namespace + value: NAMESPACE + + - it: renders Role with namespace override + set: + rbac: + enabled: true + roles: + - name: example + rules: + - apiGroups: ["*"] + resources: ["pods"] + verbs: ["get", "list"] + namespaceOverride: "custom-namespace" + asserts: + - equal: + path: metadata.namespace + value: custom-namespace diff --git a/application/tests/rolebinding_test.yaml b/application/tests/rolebinding_test.yaml new file mode 100644 index 00000000..7e4875eb --- /dev/null +++ b/application/tests/rolebinding_test.yaml @@ -0,0 +1,140 @@ +suite: RoleBinding + +templates: + - rolebinding.yaml + +tests: + - it: does not render when RBAC is not enabled + set: + rbac: + enabled: false + roles: [] + asserts: + - hasDocuments: + count: 0 + + - it: does not render when no roles are defined + set: + rbac: + enabled: true + roles: [] + asserts: + - hasDocuments: + count: 0 + + - it: renders a RoleBinding with minimal configuration + set: + applicationName: my-app + rbac: + enabled: true + roles: + - name: example + asserts: + - hasDocuments: + count: 1 + - isKind: + of: RoleBinding + - equal: + path: metadata.name + value: my-app-rolebinding-example + - equal: + path: roleRef.name + value: my-app-role-example + + - it: includes correct namespace + set: + applicationName: my-app + rbac: + enabled: true + roles: + - name: example + release: + namespace: test-namespace + asserts: + - equal: + path: metadata.namespace + value: test-namespace + + - it: includes additional labels when defined + set: + applicationName: my-app + rbac: + enabled: true + roles: + - name: example + additionalLabels: + foo: bar + test: ing + asserts: + - equal: + path: metadata.labels.foo + value: bar + - equal: + path: metadata.labels.test + value: ing + + - it: includes annotations when defined + set: + applicationName: my-app + rbac: + enabled: true + roles: + - name: example + annotations: + custom: annotation + asserts: + - equal: + path: metadata.annotations.custom + value: annotation + + - it: does not include annotations if none are defined + set: + applicationName: my-app + rbac: + enabled: true + roles: + - name: example + annotations: {} + asserts: + - notExists: + path: metadata.annotations + + - it: uses specified service account name when provided + set: + applicationName: my-app + rbac: + enabled: true + roles: + - name: example + serviceAccount: + name: custom-sa + asserts: + - equal: + path: subjects[0].name + value: custom-sa + + - it: uses generated service account name when not provided + set: + applicationName: my-app + rbac: + enabled: true + roles: + - name: example + serviceAccount: + name: "" + asserts: + - equal: + path: subjects[0].name + value: my-app + + - it: renders multiple RoleBindings when multiple roles are defined + set: + applicationName: my-app + rbac: + enabled: true + roles: + - name: role1 + - name: role2 + asserts: + - hasDocuments: + count: 2 diff --git a/application/tests/secretproviderclass_test.yaml b/application/tests/secretproviderclass_test.yaml new file mode 100644 index 00000000..da62ad80 --- /dev/null +++ b/application/tests/secretproviderclass_test.yaml @@ -0,0 +1,109 @@ +suite: SecretProviderClass + +templates: + - secretproviderclass.yaml + +tests: + - it: does not render when secretProviderClass is not enabled + set: + secretProviderClass: + enabled: false + asserts: + - hasDocuments: + count: 0 + + - it: does not render when the API version is not available + set: + secretProviderClass: + enabled: true + capabilities: + apiVersions: + - "v1" + asserts: + - hasDocuments: + count: 0 + + - it: renders a basic SecretProviderClass with minimal configuration + set: + secretProviderClass: + enabled: true + name: example-secret-provider + provider: azure + roleName: example-role + capabilities: + apiVersions: + - "secrets-store.csi.x-k8s.io/v1alpha1" + asserts: + - hasDocuments: + count: 1 + - isKind: + of: SecretProviderClass + - equal: + path: metadata.name + value: example-secret-provider + - equal: + path: spec.provider + value: azure + - equal: + path: spec.parameters.roleName + value: example-role + + - it: renders SecretProviderClass with vaultAddress + set: + secretProviderClass: + enabled: true + name: example-secret-provider + provider: azure + roleName: example-role + vaultAddress: https://vault.example.com + capabilities: + apiVersions: + - "secrets-store.csi.x-k8s.io/v1alpha1" + asserts: + - equal: + path: spec.parameters.vaultAddress + value: https://vault.example.com + + - it: renders SecretProviderClass with objects + set: + secretProviderClass: + enabled: true + name: example-secret-provider + provider: azure + roleName: example-role + objects: | + array: + - | + objectName: secret1 + objectType: secret + objectVersion: "" + capabilities: + apiVersions: + - "secrets-store.csi.x-k8s.io/v1alpha1" + asserts: + - exists: + path: spec.parameters.objects + + - it: renders SecretProviderClass with secretObjects + set: + secretProviderClass: + enabled: true + name: example-secret-provider + provider: azure + roleName: example-role + secretObjects: + - data: + - key: username + objectName: secret1 + secretName: my-secret + type: Opaque + capabilities: + apiVersions: + - "secrets-store.csi.x-k8s.io/v1alpha1" + asserts: + - equal: + path: spec.secretObjects[0].secretName + value: my-secret + - equal: + path: spec.secretObjects[0].data[0].key + value: username diff --git a/application/tests/service_test.yaml b/application/tests/service_test.yaml new file mode 100644 index 00000000..acb106e1 --- /dev/null +++ b/application/tests/service_test.yaml @@ -0,0 +1,176 @@ +suite: Service +templates: + - service.yaml + +tests: + - it: does not yield Service if service.enabled is false + set: + service: + enabled: false + asserts: + - hasDocuments: + count: 0 + + - it: yields Service if service.enabled is true + set: + service: + enabled: true + asserts: + - hasDocuments: + count: 1 + - isKind: + of: Service + + - it: includes correct metadata name + set: + service: + enabled: true + applicationName: my-app + asserts: + - equal: + path: metadata.name + value: my-app + + - it: includes correct namespace + set: + service: + enabled: true + applicationName: my-app + release: + namespace: test-namespace + asserts: + - equal: + path: metadata.namespace + value: test-namespace + + - it: includes additional labels when defined + set: + service: + enabled: true + additionalLabels: + foo: bar + test: ing + asserts: + - equal: + path: metadata.labels.foo + value: bar + - equal: + path: metadata.labels.test + value: ing + + - it: includes annotations when defined + set: + service: + enabled: true + annotations: + custom: annotation + asserts: + - equal: + path: metadata.annotations.custom + value: annotation + + - it: does not include annotations if none are defined + set: + service: + enabled: true + annotations: {} + asserts: + - notExists: + path: metadata.annotations + + - it: includes clusterIP when defined + set: + service: + enabled: true + clusterIP: 10.0.0.1 + asserts: + - equal: + path: spec.clusterIP + value: 10.0.0.1 + + - it: does not include clusterIP if not defined + set: + service: + enabled: true + asserts: + - notExists: + path: spec.clusterIP + + - it: includes loadBalancerIP when service type is LoadBalancer + set: + service: + enabled: true + type: LoadBalancer + loadBalancerIP: 10.0.0.2 + asserts: + - equal: + path: spec.loadBalancerIP + value: 10.0.0.2 + + - it: does not include loadBalancerIP if service type is not LoadBalancer + set: + service: + enabled: true + type: ClusterIP + asserts: + - notExists: + path: spec.loadBalancerIP + + - it: includes loadBalancerSourceRanges when defined + set: + service: + enabled: true + type: LoadBalancer + loadBalancerSourceRanges: + - 0.0.0.0/0 + asserts: + - equal: + path: spec.loadBalancerSourceRanges[0] + value: 0.0.0.0/0 + + - it: does not include loadBalancerSourceRanges if service type is not LoadBalancer + set: + service: + enabled: true + type: ClusterIP + asserts: + - notExists: + path: spec.loadBalancerSourceRanges + + - it: includes externalIPs when defined + set: + service: + enabled: true + externalIPs: + - 192.168.1.1 + asserts: + - equal: + path: spec.externalIPs[0] + value: 192.168.1.1 + + - it: does not include externalIPs if not defined + set: + service: + enabled: true + asserts: + - notExists: + path: spec.externalIPs + + - it: includes ports when defined + set: + service: + enabled: true + ports: + - name: http + port: 80 + targetPort: 8080 + asserts: + - equal: + path: spec.ports[0].name + value: http + - equal: + path: spec.ports[0].port + value: 80 + - equal: + path: spec.ports[0].targetPort + value: 8080 diff --git a/application/tests/servicemonitor_test.yaml b/application/tests/servicemonitor_test.yaml new file mode 100644 index 00000000..664ea980 --- /dev/null +++ b/application/tests/servicemonitor_test.yaml @@ -0,0 +1,133 @@ +suite: ServiceMonitor + +templates: + - servicemonitor.yaml + +tests: + - it: does not render when serviceMonitor is not enabled + set: + serviceMonitor: + enabled: false + asserts: + - hasDocuments: + count: 0 + + - it: does not render when the API version is not available + set: + serviceMonitor: + enabled: true + capabilities: + apiVersions: + - "v1" + asserts: + - hasDocuments: + count: 0 + + - it: renders a basic ServiceMonitor with minimal configuration + set: + applicationName: my-app + serviceMonitor: + enabled: true + endpoints: + - port: http + interval: 30s + capabilities: + apiVersions: + - "monitoring.coreos.com/v1" + asserts: + - hasDocuments: + count: 1 + - isKind: + of: ServiceMonitor + - equal: + path: metadata.name + value: my-app-svc-monitor + - equal: + path: spec.endpoints[0].port + value: http + - equal: + path: spec.endpoints[0].interval + value: 30s + + - it: renders ServiceMonitor with additional labels + set: + serviceMonitor: + enabled: true + additionalLabels: + custom: label + endpoints: + - port: http + interval: 30s + capabilities: + apiVersions: + - "monitoring.coreos.com/v1" + asserts: + - equal: + path: metadata.labels.custom + value: label + + - it: renders ServiceMonitor with annotations + set: + serviceMonitor: + enabled: true + annotations: + custom: annotation + endpoints: + - port: http + interval: 30s + capabilities: + apiVersions: + - "monitoring.coreos.com/v1" + asserts: + - equal: + path: metadata.annotations.custom + value: annotation + + - it: renders ServiceMonitor with multiple endpoints + set: + serviceMonitor: + enabled: true + endpoints: + - port: http + interval: 30s + - port: https + interval: 30s + capabilities: + apiVersions: + - "monitoring.coreos.com/v1" + asserts: + - equal: + path: spec.endpoints[0].port + value: http + - equal: + path: spec.endpoints[1].port + value: https + + - it: renders ServiceMonitor with namespaceSelector + set: + serviceMonitor: + enabled: true + endpoints: + - port: http + interval: 30s + release: + namespace: my-namespace + capabilities: + apiVersions: + - "monitoring.coreos.com/v1" + asserts: + - equal: + path: spec.namespaceSelector.matchNames[0] + value: my-namespace + + - it: fails when no endpoints are specified + set: + serviceMonitor: + enabled: true + endpoints: [] + capabilities: + apiVersions: + - "monitoring.coreos.com/v1" + asserts: + - hasDocuments: + count: 0 diff --git a/application/tests/vpa_test.yaml b/application/tests/vpa_test.yaml new file mode 100644 index 00000000..761411fb --- /dev/null +++ b/application/tests/vpa_test.yaml @@ -0,0 +1,134 @@ +suite: VerticalPodAutoscaler + +templates: + - vpa.yaml + +tests: + - it: does not render when VPA is not enabled + set: + vpa: + enabled: false + asserts: + - hasDocuments: + count: 0 + + - it: does not render when the API version is not available + set: + vpa: + enabled: true + capabilities: + apiVersions: + - "v1" + asserts: + - failedTemplate: + errorMessage: "There is no VerticalPodAutoscaler resource definition in the target cluster!" + + - it: renders a basic VerticalPodAutoscaler with minimal configuration + set: + applicationName: example + vpa: + enabled: true + containerPolicies: + - containerName: "*" + minAllowed: + cpu: "100m" + memory: "128Mi" + maxAllowed: + cpu: "500m" + memory: "512Mi" + updatePolicy: + updateMode: "Auto" + capabilities: + apiVersions: + - "autoscaling.k8s.io/v1/VerticalPodAutoscaler" + asserts: + - hasDocuments: + count: 1 + - isKind: + of: VerticalPodAutoscaler + - equal: + path: metadata.name + value: example + - equal: + path: spec.targetRef.kind + value: Deployment + - equal: + path: spec.resourcePolicy.containerPolicies[0].minAllowed.cpu + value: "100m" + + - it: renders VerticalPodAutoscaler with additional labels + set: + vpa: + enabled: true + additionalLabels: + custom: label + containerPolicies: + - containerName: "*" + minAllowed: + cpu: "100m" + memory: "128Mi" + maxAllowed: + cpu: "500m" + memory: "512Mi" + updatePolicy: + updateMode: "Auto" + capabilities: + apiVersions: + - "autoscaling.k8s.io/v1/VerticalPodAutoscaler" + asserts: + - equal: + path: metadata.labels.custom + value: label + + - it: renders VerticalPodAutoscaler with annotations + set: + vpa: + enabled: true + annotations: + custom: annotation + containerPolicies: + - containerName: "*" + minAllowed: + cpu: "100m" + memory: "128Mi" + maxAllowed: + cpu: "500m" + memory: "512Mi" + updatePolicy: + updateMode: "Auto" + capabilities: + apiVersions: + - "autoscaling.k8s.io/v1/VerticalPodAutoscaler" + asserts: + - equal: + path: metadata.annotations.custom + value: annotation + + - it: renders VerticalPodAutoscaler with multiple container policies + set: + vpa: + enabled: true + containerPolicies: + - containerName: "app-container" + minAllowed: + cpu: "100m" + memory: "128Mi" + maxAllowed: + cpu: "500m" + memory: "512Mi" + - containerName: "sidecar-container" + minAllowed: + cpu: "50m" + memory: "64Mi" + maxAllowed: + cpu: "200m" + memory: "256Mi" + updatePolicy: + updateMode: "Auto" + capabilities: + apiVersions: + - "autoscaling.k8s.io/v1/VerticalPodAutoscaler" + asserts: + - equal: + path: spec.resourcePolicy.containerPolicies[1].containerName + value: sidecar-container