From e56af747b63816a587cc5a5a2c0521147ad630d3 Mon Sep 17 00:00:00 2001 From: tokio-on-jupiter Date: Thu, 17 Sep 2026 14:59:28 -0400 Subject: [PATCH 1/2] test(application): add helm-unittest suite for the application chart Adds suites under charts/application/tests/, one per template area, covering the workload shapes PR #22 introduced (VPA, KEDA, ExternalSecret, TargetGroupBinding, the env model, CronJob batch/v1, PDB, RBAC overrides). Moves ci/*-values.yaml to tests/fixtures/ and wires them into the suites via suite-level values:, plus a matchSnapshot per fixture so any output drift must be reviewed. Also fixes a bug the tests caught: deployment.terminationGracePeriodSeconds and deployment.automountServiceAccountToken rendered as explicit `null` by default because podSpecCommon checked hasKey instead of the value itself (both keys are pre-declared null in values.yaml). Switched to the same kindIs "invalid" check already used for replicas/revisionHistoryLimit/ suspend elsewhere in this chart. Co-authored-by: Claude --- charts/application/templates/_helpers.tpl | 4 +- .../__snapshot__/fixtures_test.yaml.snap | 2086 +++++++++++++++++ .../application/tests/certificate_test.yaml | 12 + charts/application/tests/configmap_test.yaml | 59 + charts/application/tests/cronjob_test.yaml | 99 + charts/application/tests/deployment_test.yaml | 203 ++ .../tests/externalsecret_test.yaml | 39 + .../fixtures}/cronjob-values.yaml | 0 .../fixtures}/deployment-with-vpa-values.yaml | 0 .../http-service-keda-tgb-values.yaml | 0 .../{ci => tests/fixtures}/legacy-values.yaml | 0 charts/application/tests/fixtures_test.yaml | 27 + charts/application/tests/hpa_test.yaml | 12 + charts/application/tests/keda_test.yaml | 54 + charts/application/tests/pdb_test.yaml | 39 + charts/application/tests/rbac_test.yaml | 101 + charts/application/tests/service_test.yaml | 25 + .../tests/targetgroupbinding_test.yaml | 50 + charts/application/tests/vpa_test.yaml | 34 + 19 files changed, 2842 insertions(+), 2 deletions(-) create mode 100644 charts/application/tests/__snapshot__/fixtures_test.yaml.snap create mode 100644 charts/application/tests/certificate_test.yaml create mode 100644 charts/application/tests/configmap_test.yaml create mode 100644 charts/application/tests/cronjob_test.yaml create mode 100644 charts/application/tests/deployment_test.yaml create mode 100644 charts/application/tests/externalsecret_test.yaml rename charts/application/{ci => tests/fixtures}/cronjob-values.yaml (100%) rename charts/application/{ci => tests/fixtures}/deployment-with-vpa-values.yaml (100%) rename charts/application/{ci => tests/fixtures}/http-service-keda-tgb-values.yaml (100%) rename charts/application/{ci => tests/fixtures}/legacy-values.yaml (100%) create mode 100644 charts/application/tests/fixtures_test.yaml create mode 100644 charts/application/tests/hpa_test.yaml create mode 100644 charts/application/tests/keda_test.yaml create mode 100644 charts/application/tests/pdb_test.yaml create mode 100644 charts/application/tests/rbac_test.yaml create mode 100644 charts/application/tests/service_test.yaml create mode 100644 charts/application/tests/targetgroupbinding_test.yaml create mode 100644 charts/application/tests/vpa_test.yaml diff --git a/charts/application/templates/_helpers.tpl b/charts/application/templates/_helpers.tpl index 2bd620b..e4dc74f 100644 --- a/charts/application/templates/_helpers.tpl +++ b/charts/application/templates/_helpers.tpl @@ -242,10 +242,10 @@ Usage: {{- with $w.priorityClassName }} priorityClassName: {{ . }} {{- end }} -{{- if hasKey $w "terminationGracePeriodSeconds" }} +{{- if not (kindIs "invalid" $w.terminationGracePeriodSeconds) }} terminationGracePeriodSeconds: {{ $w.terminationGracePeriodSeconds }} {{- end }} -{{- if hasKey $w "automountServiceAccountToken" }} +{{- if not (kindIs "invalid" $w.automountServiceAccountToken) }} automountServiceAccountToken: {{ $w.automountServiceAccountToken }} {{- end }} {{- with $w.nodeSelector }} diff --git a/charts/application/tests/__snapshot__/fixtures_test.yaml.snap b/charts/application/tests/__snapshot__/fixtures_test.yaml.snap new file mode 100644 index 0000000..2da3c97 --- /dev/null +++ b/charts/application/tests/__snapshot__/fixtures_test.yaml.snap @@ -0,0 +1,2086 @@ +cronjob-values fixture matches snapshot: + 1: | + apiVersion: v1 + data: + AWS_REGION: us-east-1 + AWS_SDK_JS_SUPPRESS_MAINTENANCE_MODE_MESSAGE: "1" + CASE_STATUS_REFRESH_DEAD_LETTER_QUEUE_URL: https://sqs.us-east-1.amazonaws.com/123456789012/sample-app-case-status-refresh-dead-letter-queue-002 + CASE_STATUS_REFRESH_QUEUE_URL: https://sqs.us-east-1.amazonaws.com/123456789012/sample-app-case-status-refresh-queue-002 + COLLECTOR_TAG: latest + ENVIRONMENT: sample-env + EVENTS_STREAM_ARN: arn:aws:kinesis:us-east-1:123456789012:stream/sample-app-events-stream-002 + INTERNAL_API_BASE_URL: https://api.example.internal + JIRA_INBOUND_DEAD_LETTER_QUEUE_URL: https://sqs.us-east-1.amazonaws.com/123456789012/sample-app-jira-inbound-dead-letter-queue-002 + JIRA_INBOUND_QUEUE_URL: https://sqs.us-east-1.amazonaws.com/123456789012/sample-app-jira-inbound-queue-002 + LOG_LEVEL: error + METRICS_ENABLED: "true" + METRICS_EXPORTER: otlp-grpc + NEO4J_AUTHENTICATION_ENABLED: "true" + NEO4J_READER_HOSTNAME: sample-pod.example.internal + NEO4J_SSL_ENABLED: "true" + NEO4J_USERNAME: neo4j + NEO4J_WRITER_HOSTNAME: sample-pod.example.internal + NODE_OPTIONS: --max-old-space-size=750 + OTEL_COLLECTOR_FQDN: otel-collector.opentelemetry-collector.svc.cluster.local + OTEL_EXPORTER_OTLP_ENDPOINT: http://otel-collector.opentelemetry-collector.svc.cluster.local:4318 + OTEL_EXPORTER_OTLP_INSECURE: "true" + OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE: delta + OTEL_RESOURCE_ATTRIBUTES: service.name=reaper,environment=sample-env,platform=aws_eks,build.id=1.0.32,project=sample-app,owner=@example/platform, + PIPELINE_OUTGOING_STREAM_ARN: arn:aws:kinesis:us-east-1:123456789012:stream/query-domain-pipeline-outgoing-stream + PLATFORM: aws_eks + POD_ID: "002" + POD_NAME: sample-pod + QUERY_QUEUE_URL: https://sqs.us-east-1.amazonaws.com/123456789012/sample-app-query-queue-002 + REGION: us-east-1 + REMEDIATION_REBUILD_DEAD_LETTER_QUEUE_URL: https://sqs.us-east-1.amazonaws.com/123456789012/sample-app-remediation-rebuild-dead-letter-queue-002 + REMEDIATION_REBUILD_QUEUE_URL: https://sqs.us-east-1.amazonaws.com/123456789012/sample-app-remediation-rebuild-queue-002 + SERVICE_NAME: reaper + SHOULD_USE_SECRETS_MANAGER_FOR_NEO4J_CONFIG: "false" + SYNC_JOB_DEAD_LETTER_QUEUE_URL: https://sqs.us-east-1.amazonaws.com/123456789012/sample-app-sync-job-dead-letter-queue-002 + SYNC_JOB_QUEUE_URL: https://sqs.us-east-1.amazonaws.com/123456789012/sample-app-sync-job-queue-002 + TRACING_ENABLED: "false" + immutable: false + kind: ConfigMap + metadata: + labels: + ManagedEnvironment: sample-env + ManagedProject: sample-app + ManagedService: reaper + app: reaper + environment: sample-env + platform: aws_eks + service.name: reaper + service.namespace: sample-ns + tags.environment: sample-env + tags.platform: aws_eks + tags.project: sample-app + tags.service.name: reaper + tags.service.namespace: sample-ns + name: reaper + namespace: NAMESPACE + 2: | + apiVersion: batch/v1 + kind: CronJob + metadata: + annotations: + owner: '@example/platform' + tags.CodeOwner: '@example/platform' + labels: + ManagedEnvironment: sample-env + ManagedProject: sample-app + ManagedService: reaper + app: reaper + environment: sample-env + platform: aws_eks + service.name: reaper + service.namespace: sample-ns + tags.environment: sample-env + tags.platform: aws_eks + tags.project: sample-app + tags.service.name: reaper + tags.service.namespace: sample-ns + name: reaper + namespace: NAMESPACE + spec: + concurrencyPolicy: Forbid + failedJobsHistoryLimit: 3 + jobTemplate: + spec: + backoffLimit: 0 + template: + metadata: + annotations: + karpenter.sh/do-not-disrupt: "true" + owner: '@example/platform' + tags.CodeOwner: '@example/platform' + labels: + ManagedEnvironment: sample-env + ManagedProject: sample-app + ManagedService: reaper + app: reaper + environment: sample-env + platform: aws_eks + service.name: reaper + service.namespace: sample-ns + tags.environment: sample-env + tags.platform: aws_eks + tags.project: sample-app + tags.service.name: reaper + tags.service.namespace: sample-ns + spec: + affinity: + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: app + operator: In + values: + - reaper + topologyKey: kubernetes.io/hostname + automountServiceAccountToken: true + containers: + - env: + - name: KUBERNETES_POD_NAME + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: metadata.name + - name: KUBERNETES_NAMESPACE + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: metadata.namespace + - name: AWS_REGION + valueFrom: + configMapKeyRef: + key: AWS_REGION + name: reaper + optional: false + - name: AWS_SDK_JS_SUPPRESS_MAINTENANCE_MODE_MESSAGE + valueFrom: + configMapKeyRef: + key: AWS_SDK_JS_SUPPRESS_MAINTENANCE_MODE_MESSAGE + name: reaper + optional: false + - name: CASE_STATUS_REFRESH_DEAD_LETTER_QUEUE_URL + valueFrom: + configMapKeyRef: + key: CASE_STATUS_REFRESH_DEAD_LETTER_QUEUE_URL + name: reaper + optional: false + - name: CASE_STATUS_REFRESH_QUEUE_URL + valueFrom: + configMapKeyRef: + key: CASE_STATUS_REFRESH_QUEUE_URL + name: reaper + optional: false + - name: COLLECTOR_TAG + valueFrom: + configMapKeyRef: + key: COLLECTOR_TAG + name: reaper + optional: false + - name: ENVIRONMENT + valueFrom: + configMapKeyRef: + key: ENVIRONMENT + name: reaper + optional: false + - name: EVENTS_STREAM_ARN + valueFrom: + configMapKeyRef: + key: EVENTS_STREAM_ARN + name: reaper + optional: false + - name: INTERNAL_API_BASE_URL + valueFrom: + configMapKeyRef: + key: INTERNAL_API_BASE_URL + name: reaper + optional: false + - name: JIRA_INBOUND_DEAD_LETTER_QUEUE_URL + valueFrom: + configMapKeyRef: + key: JIRA_INBOUND_DEAD_LETTER_QUEUE_URL + name: reaper + optional: false + - name: JIRA_INBOUND_QUEUE_URL + valueFrom: + configMapKeyRef: + key: JIRA_INBOUND_QUEUE_URL + name: reaper + optional: false + - name: LOG_LEVEL + valueFrom: + configMapKeyRef: + key: LOG_LEVEL + name: reaper + optional: false + - name: METRICS_ENABLED + valueFrom: + configMapKeyRef: + key: METRICS_ENABLED + name: reaper + optional: false + - name: METRICS_EXPORTER + valueFrom: + configMapKeyRef: + key: METRICS_EXPORTER + name: reaper + optional: false + - name: NEO4J_AUTHENTICATION_ENABLED + valueFrom: + configMapKeyRef: + key: NEO4J_AUTHENTICATION_ENABLED + name: reaper + optional: false + - name: NEO4J_READER_HOSTNAME + valueFrom: + configMapKeyRef: + key: NEO4J_READER_HOSTNAME + name: reaper + optional: false + - name: NEO4J_SSL_ENABLED + valueFrom: + configMapKeyRef: + key: NEO4J_SSL_ENABLED + name: reaper + optional: false + - name: NEO4J_USERNAME + valueFrom: + configMapKeyRef: + key: NEO4J_USERNAME + name: reaper + optional: false + - name: NEO4J_WRITER_HOSTNAME + valueFrom: + configMapKeyRef: + key: NEO4J_WRITER_HOSTNAME + name: reaper + optional: false + - name: NODE_OPTIONS + valueFrom: + configMapKeyRef: + key: NODE_OPTIONS + name: reaper + optional: false + - name: OTEL_COLLECTOR_FQDN + valueFrom: + configMapKeyRef: + key: OTEL_COLLECTOR_FQDN + name: reaper + optional: false + - name: OTEL_EXPORTER_OTLP_ENDPOINT + valueFrom: + configMapKeyRef: + key: OTEL_EXPORTER_OTLP_ENDPOINT + name: reaper + optional: false + - name: OTEL_EXPORTER_OTLP_INSECURE + valueFrom: + configMapKeyRef: + key: OTEL_EXPORTER_OTLP_INSECURE + name: reaper + optional: false + - name: OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE + valueFrom: + configMapKeyRef: + key: OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE + name: reaper + optional: false + - name: OTEL_RESOURCE_ATTRIBUTES + valueFrom: + configMapKeyRef: + key: OTEL_RESOURCE_ATTRIBUTES + name: reaper + optional: false + - name: PIPELINE_OUTGOING_STREAM_ARN + valueFrom: + configMapKeyRef: + key: PIPELINE_OUTGOING_STREAM_ARN + name: reaper + optional: false + - name: PLATFORM + valueFrom: + configMapKeyRef: + key: PLATFORM + name: reaper + optional: false + - name: POD_ID + valueFrom: + configMapKeyRef: + key: POD_ID + name: reaper + optional: false + - name: POD_NAME + valueFrom: + configMapKeyRef: + key: POD_NAME + name: reaper + optional: false + - name: QUERY_QUEUE_URL + valueFrom: + configMapKeyRef: + key: QUERY_QUEUE_URL + name: reaper + optional: false + - name: REGION + valueFrom: + configMapKeyRef: + key: REGION + name: reaper + optional: false + - name: REMEDIATION_REBUILD_DEAD_LETTER_QUEUE_URL + valueFrom: + configMapKeyRef: + key: REMEDIATION_REBUILD_DEAD_LETTER_QUEUE_URL + name: reaper + optional: false + - name: REMEDIATION_REBUILD_QUEUE_URL + valueFrom: + configMapKeyRef: + key: REMEDIATION_REBUILD_QUEUE_URL + name: reaper + optional: false + - name: SERVICE_NAME + valueFrom: + configMapKeyRef: + key: SERVICE_NAME + name: reaper + optional: false + - name: SHOULD_USE_SECRETS_MANAGER_FOR_NEO4J_CONFIG + valueFrom: + configMapKeyRef: + key: SHOULD_USE_SECRETS_MANAGER_FOR_NEO4J_CONFIG + name: reaper + optional: false + - name: SYNC_JOB_DEAD_LETTER_QUEUE_URL + valueFrom: + configMapKeyRef: + key: SYNC_JOB_DEAD_LETTER_QUEUE_URL + name: reaper + optional: false + - name: SYNC_JOB_QUEUE_URL + valueFrom: + configMapKeyRef: + key: SYNC_JOB_QUEUE_URL + name: reaper + optional: false + - name: TRACING_ENABLED + valueFrom: + configMapKeyRef: + key: TRACING_ENABLED + name: reaper + optional: false + - name: NEO4J_PASSWORD + valueFrom: + secretKeyRef: + key: NEO4J_PASSWORD + name: reaper + optional: false + image: 123456789012.dkr.ecr.us-east-1.amazonaws.com/sample/reaper:1.2.3 + name: reaper + resources: + limits: + cpu: 1024m + ephemeral-storage: 3Gi + memory: 1000Mi + requests: + cpu: 1024m + ephemeral-storage: 1Gi + memory: 1000Mi + nodeSelector: + Team: platform-team + karpenter.sh/nodepool: platform-team + priorityClassName: high-priority + restartPolicy: Never + serviceAccountName: sample-ns-reaper + terminationGracePeriodSeconds: 30 + tolerations: + - effect: NoSchedule + key: Team + operator: Equal + value: platform-team + schedule: '*/5 * * * *' + successfulJobsHistoryLimit: 1 + suspend: false + 3: | + apiVersion: external-secrets.io/v1beta1 + kind: ExternalSecret + metadata: + labels: + ManagedEnvironment: sample-env + ManagedProject: sample-app + ManagedService: reaper + app: reaper + environment: sample-env + platform: aws_eks + service.name: reaper + tags.environment: sample-env + tags.platform: aws_eks + tags.project: sample-app + tags.service.name: reaper + name: reaper + namespace: NAMESPACE + spec: + data: + - remoteRef: + conversionStrategy: Default + decodingStrategy: None + key: sample-app/sample-env/NEO4J_PASSWORD_POD_002 + metadataPolicy: None + secretKey: NEO4J_PASSWORD + refreshInterval: 1h + secretStoreRef: + kind: ClusterSecretStore + name: configuration-api-webhook + target: + creationPolicy: Owner + deletionPolicy: Retain + name: reaper + 4: | + apiVersion: rbac.authorization.k8s.io/v1 + kind: Role + metadata: + labels: + ManagedEnvironment: sample-env + ManagedProject: sample-app + ManagedService: reaper + app: reaper + environment: sample-env + platform: aws_eks + service.name: reaper + tags.environment: sample-env + tags.platform: aws_eks + tags.project: sample-app + tags.service.name: reaper + name: reaper-pod-annotations + namespace: NAMESPACE + rules: + - apiGroups: + - "" + resources: + - pods + verbs: + - update + - patch + - get + 5: | + apiVersion: rbac.authorization.k8s.io/v1 + kind: RoleBinding + metadata: + labels: + ManagedEnvironment: sample-env + ManagedProject: sample-app + ManagedService: reaper + app: reaper + environment: sample-env + platform: aws_eks + service.name: reaper + service.namespace: sample-ns + tags.environment: sample-env + tags.platform: aws_eks + tags.project: sample-app + tags.service.name: reaper + tags.service.namespace: sample-ns + name: reaper-pod-annotations + namespace: NAMESPACE + roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: reaper-pod-annotations + subjects: + - kind: ServiceAccount + name: sample-ns-reaper + namespace: NAMESPACE +deployment-with-vpa-values fixture matches snapshot: + 1: | + apiVersion: v1 + data: + AWS_REGION: us-east-1 + AWS_SDK_JS_SUPPRESS_MAINTENANCE_MODE_MESSAGE: "1" + CASE_STATUS_REFRESH_DEAD_LETTER_QUEUE_URL: https://sqs.us-east-1.amazonaws.com/123456789012/sample-app-case-status-refresh-dead-letter-queue-002 + CASE_STATUS_REFRESH_QUEUE_URL: https://sqs.us-east-1.amazonaws.com/123456789012/sample-app-case-status-refresh-queue-002 + COLLECTOR_TAG: latest + ENVIRONMENT: sample-env + EVENTS_STREAM_ARN: arn:aws:kinesis:us-east-1:123456789012:stream/sample-app-events-stream-002 + INTERNAL_API_BASE_URL: https://api.example.internal + J1DS_ROLE_ARN: arn:aws:iam::123456789012:role/example-data-streaming + JIRA_INBOUND_DEAD_LETTER_QUEUE_URL: https://sqs.us-east-1.amazonaws.com/123456789012/sample-app-jira-inbound-dead-letter-queue-002 + JIRA_INBOUND_QUEUE_URL: https://sqs.us-east-1.amazonaws.com/123456789012/sample-app-jira-inbound-queue-002 + LOG_LEVEL: error + MAX_CONCURRENT_MESSAGES: "6" + METRICS_ENABLED: "true" + METRICS_EXPORTER: otlp-grpc + NEO4J_AUTHENTICATION_ENABLED: "true" + NEO4J_READER_HOSTNAME: sample-pod.example.internal + NEO4J_SSL_ENABLED: "true" + NEO4J_USERNAME: neo4j + NEO4J_WRITER_HOSTNAME: sample-pod.example.internal + NODE_OPTIONS: --max-old-space-size=3072 + OTEL_ATTRIBUTE_COUNT_LIMIT: "128" + OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT: "4095" + OTEL_COLLECTOR_FQDN: otel-collector.opentelemetry-collector.svc.cluster.local + OTEL_EXPORTER_OTLP_ENDPOINT: http://otel-collector.opentelemetry-collector.svc.cluster.local:4318 + OTEL_EXPORTER_OTLP_INSECURE: "true" + OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE: delta + OTEL_RESOURCE_ATTRIBUTES: service.name=executor,environment=sample-env,platform=aws_eks,build.id=1.2.3,project=sample-app,owner=@example/platform, + PIPELINE_OUTGOING_STREAM_ARN: arn:aws:kinesis:us-east-1:123456789012:stream/query-domain-pipeline-outgoing-stream + PLATFORM: aws_eks + POD_ID: "002" + POD_NAME: sample-pod + QUERY_QUEUE_URL: https://sqs.us-east-1.amazonaws.com/123456789012/sample-app-query-queue-002 + REGION: us-east-1 + REMEDIATION_REBUILD_DEAD_LETTER_QUEUE_URL: https://sqs.us-east-1.amazonaws.com/123456789012/sample-app-remediation-rebuild-dead-letter-queue-002 + REMEDIATION_REBUILD_QUEUE_URL: https://sqs.us-east-1.amazonaws.com/123456789012/sample-app-remediation-rebuild-queue-002 + SERVICE_NAME: executor + SHOULD_USE_SECRETS_MANAGER_FOR_NEO4J_CONFIG: "false" + SYNC_JOB_DEAD_LETTER_QUEUE_URL: https://sqs.us-east-1.amazonaws.com/123456789012/sample-app-sync-job-dead-letter-queue-002 + SYNC_JOB_QUEUE_URL: https://sqs.us-east-1.amazonaws.com/123456789012/sample-app-sync-job-queue-002 + TRACING_ENABLED: "false" + immutable: false + kind: ConfigMap + metadata: + labels: + ManagedEnvironment: sample-env + ManagedProject: sample-app + ManagedService: executor + app: executor + environment: sample-env + platform: aws_eks + service.name: executor + service.namespace: sample-ns + tags.environment: sample-env + tags.platform: aws_eks + tags.project: sample-app + tags.service.name: executor + tags.service.namespace: sample-ns + name: executor + namespace: NAMESPACE + 2: | + apiVersion: apps/v1 + kind: Deployment + metadata: + annotations: + owner: '@example/platform' + tags.CodeOwner: '@example/platform' + labels: + ManagedEnvironment: sample-env + ManagedProject: sample-app + ManagedService: executor + app: executor + environment: sample-env + platform: aws_eks + service.name: executor + service.namespace: sample-ns + tags.environment: sample-env + tags.platform: aws_eks + tags.project: sample-app + tags.service.name: executor + tags.service.namespace: sample-ns + name: executor + namespace: NAMESPACE + spec: + replicas: 2 + revisionHistoryLimit: 10 + selector: + matchLabels: + app: executor + strategy: + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 + type: RollingUpdate + template: + metadata: + annotations: + owner: '@example/platform' + tags.CodeOwner: '@example/platform' + labels: + ManagedEnvironment: sample-env + ManagedProject: sample-app + ManagedService: executor + app: executor + environment: sample-env + platform: aws_eks + service.name: executor + service.namespace: sample-ns + tags.environment: sample-env + tags.platform: aws_eks + tags.project: sample-app + tags.service.name: executor + tags.service.namespace: sample-ns + spec: + affinity: + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: app + operator: In + values: + - executor + topologyKey: kubernetes.io/hostname + automountServiceAccountToken: true + containers: + - env: + - name: KUBERNETES_POD_NAME + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: metadata.name + - name: KUBERNETES_NAMESPACE + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: metadata.namespace + - name: AWS_REGION + valueFrom: + configMapKeyRef: + key: AWS_REGION + name: executor + optional: false + - name: AWS_SDK_JS_SUPPRESS_MAINTENANCE_MODE_MESSAGE + valueFrom: + configMapKeyRef: + key: AWS_SDK_JS_SUPPRESS_MAINTENANCE_MODE_MESSAGE + name: executor + optional: false + - name: CASE_STATUS_REFRESH_DEAD_LETTER_QUEUE_URL + valueFrom: + configMapKeyRef: + key: CASE_STATUS_REFRESH_DEAD_LETTER_QUEUE_URL + name: executor + optional: false + - name: CASE_STATUS_REFRESH_QUEUE_URL + valueFrom: + configMapKeyRef: + key: CASE_STATUS_REFRESH_QUEUE_URL + name: executor + optional: false + - name: COLLECTOR_TAG + valueFrom: + configMapKeyRef: + key: COLLECTOR_TAG + name: executor + optional: false + - name: ENVIRONMENT + valueFrom: + configMapKeyRef: + key: ENVIRONMENT + name: executor + optional: false + - name: EVENTS_STREAM_ARN + valueFrom: + configMapKeyRef: + key: EVENTS_STREAM_ARN + name: executor + optional: false + - name: INTERNAL_API_BASE_URL + valueFrom: + configMapKeyRef: + key: INTERNAL_API_BASE_URL + name: executor + optional: false + - name: J1DS_ROLE_ARN + valueFrom: + configMapKeyRef: + key: J1DS_ROLE_ARN + name: executor + optional: false + - name: JIRA_INBOUND_DEAD_LETTER_QUEUE_URL + valueFrom: + configMapKeyRef: + key: JIRA_INBOUND_DEAD_LETTER_QUEUE_URL + name: executor + optional: false + - name: JIRA_INBOUND_QUEUE_URL + valueFrom: + configMapKeyRef: + key: JIRA_INBOUND_QUEUE_URL + name: executor + optional: false + - name: LOG_LEVEL + valueFrom: + configMapKeyRef: + key: LOG_LEVEL + name: executor + optional: false + - name: MAX_CONCURRENT_MESSAGES + valueFrom: + configMapKeyRef: + key: MAX_CONCURRENT_MESSAGES + name: executor + optional: false + - name: METRICS_ENABLED + valueFrom: + configMapKeyRef: + key: METRICS_ENABLED + name: executor + optional: false + - name: METRICS_EXPORTER + valueFrom: + configMapKeyRef: + key: METRICS_EXPORTER + name: executor + optional: false + - name: NEO4J_AUTHENTICATION_ENABLED + valueFrom: + configMapKeyRef: + key: NEO4J_AUTHENTICATION_ENABLED + name: executor + optional: false + - name: NEO4J_READER_HOSTNAME + valueFrom: + configMapKeyRef: + key: NEO4J_READER_HOSTNAME + name: executor + optional: false + - name: NEO4J_SSL_ENABLED + valueFrom: + configMapKeyRef: + key: NEO4J_SSL_ENABLED + name: executor + optional: false + - name: NEO4J_USERNAME + valueFrom: + configMapKeyRef: + key: NEO4J_USERNAME + name: executor + optional: false + - name: NEO4J_WRITER_HOSTNAME + valueFrom: + configMapKeyRef: + key: NEO4J_WRITER_HOSTNAME + name: executor + optional: false + - name: NODE_OPTIONS + valueFrom: + configMapKeyRef: + key: NODE_OPTIONS + name: executor + optional: false + - name: OTEL_ATTRIBUTE_COUNT_LIMIT + valueFrom: + configMapKeyRef: + key: OTEL_ATTRIBUTE_COUNT_LIMIT + name: executor + optional: false + - name: OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT + valueFrom: + configMapKeyRef: + key: OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT + name: executor + optional: false + - name: OTEL_COLLECTOR_FQDN + valueFrom: + configMapKeyRef: + key: OTEL_COLLECTOR_FQDN + name: executor + optional: false + - name: OTEL_EXPORTER_OTLP_ENDPOINT + valueFrom: + configMapKeyRef: + key: OTEL_EXPORTER_OTLP_ENDPOINT + name: executor + optional: false + - name: OTEL_EXPORTER_OTLP_INSECURE + valueFrom: + configMapKeyRef: + key: OTEL_EXPORTER_OTLP_INSECURE + name: executor + optional: false + - name: OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE + valueFrom: + configMapKeyRef: + key: OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE + name: executor + optional: false + - name: OTEL_RESOURCE_ATTRIBUTES + valueFrom: + configMapKeyRef: + key: OTEL_RESOURCE_ATTRIBUTES + name: executor + optional: false + - name: PIPELINE_OUTGOING_STREAM_ARN + valueFrom: + configMapKeyRef: + key: PIPELINE_OUTGOING_STREAM_ARN + name: executor + optional: false + - name: PLATFORM + valueFrom: + configMapKeyRef: + key: PLATFORM + name: executor + optional: false + - name: POD_ID + valueFrom: + configMapKeyRef: + key: POD_ID + name: executor + optional: false + - name: POD_NAME + valueFrom: + configMapKeyRef: + key: POD_NAME + name: executor + optional: false + - name: QUERY_QUEUE_URL + valueFrom: + configMapKeyRef: + key: QUERY_QUEUE_URL + name: executor + optional: false + - name: REGION + valueFrom: + configMapKeyRef: + key: REGION + name: executor + optional: false + - name: REMEDIATION_REBUILD_DEAD_LETTER_QUEUE_URL + valueFrom: + configMapKeyRef: + key: REMEDIATION_REBUILD_DEAD_LETTER_QUEUE_URL + name: executor + optional: false + - name: REMEDIATION_REBUILD_QUEUE_URL + valueFrom: + configMapKeyRef: + key: REMEDIATION_REBUILD_QUEUE_URL + name: executor + optional: false + - name: SERVICE_NAME + valueFrom: + configMapKeyRef: + key: SERVICE_NAME + name: executor + optional: false + - name: SHOULD_USE_SECRETS_MANAGER_FOR_NEO4J_CONFIG + valueFrom: + configMapKeyRef: + key: SHOULD_USE_SECRETS_MANAGER_FOR_NEO4J_CONFIG + name: executor + optional: false + - name: SYNC_JOB_DEAD_LETTER_QUEUE_URL + valueFrom: + configMapKeyRef: + key: SYNC_JOB_DEAD_LETTER_QUEUE_URL + name: executor + optional: false + - name: SYNC_JOB_QUEUE_URL + valueFrom: + configMapKeyRef: + key: SYNC_JOB_QUEUE_URL + name: executor + optional: false + - name: TRACING_ENABLED + valueFrom: + configMapKeyRef: + key: TRACING_ENABLED + name: executor + optional: false + - name: NEO4J_PASSWORD + valueFrom: + secretKeyRef: + key: NEO4J_PASSWORD + name: executor + optional: false + image: 123456789012.dkr.ecr.us-east-1.amazonaws.com/sample/executor/development:1.2.3 + livenessProbe: + failureThreshold: 5 + httpGet: + path: /health/liveness + port: 8080 + scheme: HTTP + initialDelaySeconds: 60 + periodSeconds: 30 + successThreshold: 1 + timeoutSeconds: 15 + name: executor + readinessProbe: + failureThreshold: 8 + httpGet: + path: /health + port: 8080 + scheme: HTTP + initialDelaySeconds: 30 + periodSeconds: 15 + successThreshold: 1 + timeoutSeconds: 10 + resources: + limits: + cpu: 1024m + ephemeral-storage: 30Gi + memory: 4Gi + requests: + cpu: 50m + ephemeral-storage: 8Gi + memory: 1Gi + nodeSelector: + Team: platform-team + karpenter.sh/nodepool: platform-team + priorityClassName: high-priority + serviceAccountName: sample-ns-j1ql-executor + terminationGracePeriodSeconds: 30 + tolerations: + - effect: NoSchedule + key: Team + operator: Equal + value: platform-team + 3: | + apiVersion: external-secrets.io/v1beta1 + kind: ExternalSecret + metadata: + labels: + ManagedEnvironment: sample-env + ManagedProject: sample-app + ManagedService: executor + app: executor + environment: sample-env + platform: aws_eks + service.name: executor + tags.environment: sample-env + tags.platform: aws_eks + tags.project: sample-app + tags.service.name: executor + name: executor + namespace: NAMESPACE + spec: + data: + - remoteRef: + conversionStrategy: Default + decodingStrategy: None + key: sample-app/sample-env/NEO4J_PASSWORD_POD_002 + metadataPolicy: None + secretKey: NEO4J_PASSWORD + refreshInterval: 1h + secretStoreRef: + kind: ClusterSecretStore + name: configuration-api-webhook + target: + creationPolicy: Owner + deletionPolicy: Retain + name: executor + 4: | + apiVersion: policy/v1 + kind: PodDisruptionBudget + metadata: + labels: + ManagedEnvironment: sample-env + ManagedProject: sample-app + ManagedService: executor + app: executor + environment: sample-env + platform: aws_eks + service.name: executor + tags.environment: sample-env + tags.platform: aws_eks + tags.project: sample-app + tags.service.name: executor + name: executor + namespace: NAMESPACE + spec: + maxUnavailable: 1 + selector: + matchLabels: + app: executor + 5: | + apiVersion: rbac.authorization.k8s.io/v1 + kind: Role + metadata: + labels: + ManagedEnvironment: sample-env + ManagedProject: sample-app + ManagedService: executor + app: executor + environment: sample-env + platform: aws_eks + service.name: executor + tags.environment: sample-env + tags.platform: aws_eks + tags.project: sample-app + tags.service.name: executor + name: executor-pod-annotations + namespace: NAMESPACE + rules: + - apiGroups: + - "" + resources: + - pods + verbs: + - update + - patch + - get + 6: | + apiVersion: rbac.authorization.k8s.io/v1 + kind: RoleBinding + metadata: + labels: + ManagedEnvironment: sample-env + ManagedProject: sample-app + ManagedService: executor + app: executor + environment: sample-env + platform: aws_eks + service.name: executor + service.namespace: sample-ns + tags.environment: sample-env + tags.platform: aws_eks + tags.project: sample-app + tags.service.name: executor + tags.service.namespace: sample-ns + name: executor-pod-annotations + namespace: NAMESPACE + roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: executor-pod-annotations + subjects: + - kind: ServiceAccount + name: sample-ns-j1ql-executor + namespace: NAMESPACE + 7: | + apiVersion: autoscaling.k8s.io/v1 + kind: VerticalPodAutoscaler + metadata: + labels: + ManagedEnvironment: sample-env + ManagedProject: sample-app + ManagedService: executor + app: executor + environment: sample-env + platform: aws_eks + service.name: executor + tags.environment: sample-env + tags.platform: aws_eks + tags.project: sample-app + tags.service.name: executor + name: executor-vpa + namespace: NAMESPACE + spec: + resourcePolicy: + containerPolicies: + - containerName: '*' + maxAllowed: + cpu: 8 + memory: 8Gi + minAllowed: + cpu: 250m + memory: 1536Mi + targetRef: + apiVersion: apps/v1 + kind: Deployment + name: executor + updatePolicy: + updateMode: "Off" +http-service-keda-tgb-values fixture matches snapshot: + 1: | + apiVersion: v1 + data: + AWS_REGION: us-east-1 + AWS_SDK_JS_SUPPRESS_MAINTENANCE_MODE_MESSAGE: "1" + CASE_STATUS_REFRESH_DEAD_LETTER_QUEUE_URL: https://sqs.us-east-1.amazonaws.com/123456789012/sample-app-case-status-refresh-dead-letter-queue-103 + CASE_STATUS_REFRESH_QUEUE_URL: https://sqs.us-east-1.amazonaws.com/123456789012/sample-app-case-status-refresh-queue-103 + COLLECTOR_TAG: latest + ENVIRONMENT: sample-env + EVENTS_STREAM_ARN: arn:aws:kinesis:us-east-1:123456789012:stream/sample-app-events-stream-103 + HTTP_MODE: "true" + INTERNAL_API_BASE_URL: https://api.example.internal + J1DS_ROLE_ARN: arn:aws:iam::123456789012:role/example-data-streaming + JIRA_INBOUND_DEAD_LETTER_QUEUE_URL: https://sqs.us-east-1.amazonaws.com/123456789012/sample-app-jira-inbound-dead-letter-queue-103 + JIRA_INBOUND_QUEUE_URL: https://sqs.us-east-1.amazonaws.com/123456789012/sample-app-jira-inbound-queue-103 + LOG_LEVEL: error + METRICS_ENABLED: "true" + METRICS_EXPORTER: otlp-grpc + NEO4J_AUTHENTICATION_ENABLED: "true" + NEO4J_READER_HOSTNAME: sample-pod.example.internal + NEO4J_SSL_ENABLED: "true" + NEO4J_USERNAME: neo4j + NEO4J_WRITER_HOSTNAME: sample-pod.example.internal + NODE_OPTIONS: --max-old-space-size=6144 + OTEL_COLLECTOR_FQDN: otel-collector.opentelemetry-collector.svc.cluster.local + OTEL_EXPORTER_OTLP_ENDPOINT: http://otel-collector.opentelemetry-collector.svc.cluster.local:4318 + OTEL_EXPORTER_OTLP_INSECURE: "true" + OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE: delta + OTEL_RESOURCE_ATTRIBUTES: service.name=api,environment=sample-env,platform=aws_eks,build.id=1.0.100,project=sample-app,owner=@example/platform, + PIPELINE_OUTGOING_STREAM_ARN: arn:aws:kinesis:us-east-1:123456789012:stream/query-domain-pipeline-outgoing-stream + PLATFORM: aws_eks + POD_ID: "103" + POD_NAME: sample-pod + QUERY_QUEUE_URL: https://sqs.us-east-1.amazonaws.com/123456789012/sample-app-query-queue-103 + REGION: us-east-1 + REMEDIATION_REBUILD_DEAD_LETTER_QUEUE_URL: https://sqs.us-east-1.amazonaws.com/123456789012/sample-app-remediation-rebuild-dead-letter-queue-103 + REMEDIATION_REBUILD_QUEUE_URL: https://sqs.us-east-1.amazonaws.com/123456789012/sample-app-remediation-rebuild-queue-103 + SERVICE_NAME: api + SHOULD_USE_SECRETS_MANAGER_FOR_NEO4J_CONFIG: "true" + SYNC_JOB_DEAD_LETTER_QUEUE_URL: https://sqs.us-east-1.amazonaws.com/123456789012/sample-app-sync-job-dead-letter-queue-103 + SYNC_JOB_QUEUE_URL: https://sqs.us-east-1.amazonaws.com/123456789012/sample-app-sync-job-queue-103 + TRACING_ENABLED: "false" + immutable: false + kind: ConfigMap + metadata: + labels: + ManagedEnvironment: sample-env + ManagedProject: sample-app + ManagedService: api + app: api + environment: sample-env + platform: aws_eks + service.name: api + service.namespace: sample-ns + tags.environment: sample-env + tags.platform: aws_eks + tags.project: sample-app + tags.service.name: api + tags.service.namespace: sample-ns + name: api + namespace: NAMESPACE + 2: | + apiVersion: apps/v1 + kind: Deployment + metadata: + annotations: + owner: '@example/platform' + tags.CodeOwner: '@example/platform' + labels: + ManagedEnvironment: sample-env + ManagedProject: sample-app + ManagedService: api + app: api + environment: sample-env + platform: aws_eks + service.name: api + service.namespace: sample-ns + tags.environment: sample-env + tags.platform: aws_eks + tags.project: sample-app + tags.service.name: api + tags.service.namespace: sample-ns + name: api + namespace: NAMESPACE + spec: + replicas: 2 + revisionHistoryLimit: 10 + selector: + matchLabels: + app: api + strategy: + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 + type: RollingUpdate + template: + metadata: + annotations: + owner: '@example/platform' + reloader.stakater.com/auto: "true" + tags.CodeOwner: '@example/platform' + labels: + ManagedEnvironment: sample-env + ManagedProject: sample-app + ManagedService: api + app: api + environment: sample-env + platform: aws_eks + service.name: api + service.namespace: sample-ns + tags.environment: sample-env + tags.platform: aws_eks + tags.project: sample-app + tags.service.name: api + tags.service.namespace: sample-ns + spec: + affinity: + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: app + operator: In + values: + - api + topologyKey: kubernetes.io/hostname + automountServiceAccountToken: true + containers: + - env: + - name: KUBERNETES_POD_NAME + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: metadata.name + - name: KUBERNETES_NAMESPACE + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: metadata.namespace + - name: AWS_REGION + valueFrom: + configMapKeyRef: + key: AWS_REGION + name: api + optional: false + - name: AWS_SDK_JS_SUPPRESS_MAINTENANCE_MODE_MESSAGE + valueFrom: + configMapKeyRef: + key: AWS_SDK_JS_SUPPRESS_MAINTENANCE_MODE_MESSAGE + name: api + optional: false + - name: CASE_STATUS_REFRESH_DEAD_LETTER_QUEUE_URL + valueFrom: + configMapKeyRef: + key: CASE_STATUS_REFRESH_DEAD_LETTER_QUEUE_URL + name: api + optional: false + - name: CASE_STATUS_REFRESH_QUEUE_URL + valueFrom: + configMapKeyRef: + key: CASE_STATUS_REFRESH_QUEUE_URL + name: api + optional: false + - name: COLLECTOR_TAG + valueFrom: + configMapKeyRef: + key: COLLECTOR_TAG + name: api + optional: false + - name: ENVIRONMENT + valueFrom: + configMapKeyRef: + key: ENVIRONMENT + name: api + optional: false + - name: EVENTS_STREAM_ARN + valueFrom: + configMapKeyRef: + key: EVENTS_STREAM_ARN + name: api + optional: false + - name: HTTP_MODE + valueFrom: + configMapKeyRef: + key: HTTP_MODE + name: api + optional: false + - name: INTERNAL_API_BASE_URL + valueFrom: + configMapKeyRef: + key: INTERNAL_API_BASE_URL + name: api + optional: false + - name: J1DS_ROLE_ARN + valueFrom: + configMapKeyRef: + key: J1DS_ROLE_ARN + name: api + optional: false + - name: JIRA_INBOUND_DEAD_LETTER_QUEUE_URL + valueFrom: + configMapKeyRef: + key: JIRA_INBOUND_DEAD_LETTER_QUEUE_URL + name: api + optional: false + - name: JIRA_INBOUND_QUEUE_URL + valueFrom: + configMapKeyRef: + key: JIRA_INBOUND_QUEUE_URL + name: api + optional: false + - name: LOG_LEVEL + valueFrom: + configMapKeyRef: + key: LOG_LEVEL + name: api + optional: false + - name: METRICS_ENABLED + valueFrom: + configMapKeyRef: + key: METRICS_ENABLED + name: api + optional: false + - name: METRICS_EXPORTER + valueFrom: + configMapKeyRef: + key: METRICS_EXPORTER + name: api + optional: false + - name: NEO4J_AUTHENTICATION_ENABLED + valueFrom: + configMapKeyRef: + key: NEO4J_AUTHENTICATION_ENABLED + name: api + optional: false + - name: NEO4J_READER_HOSTNAME + valueFrom: + configMapKeyRef: + key: NEO4J_READER_HOSTNAME + name: api + optional: false + - name: NEO4J_SSL_ENABLED + valueFrom: + configMapKeyRef: + key: NEO4J_SSL_ENABLED + name: api + optional: false + - name: NEO4J_USERNAME + valueFrom: + configMapKeyRef: + key: NEO4J_USERNAME + name: api + optional: false + - name: NEO4J_WRITER_HOSTNAME + valueFrom: + configMapKeyRef: + key: NEO4J_WRITER_HOSTNAME + name: api + optional: false + - name: NODE_OPTIONS + valueFrom: + configMapKeyRef: + key: NODE_OPTIONS + name: api + optional: false + - name: OTEL_COLLECTOR_FQDN + valueFrom: + configMapKeyRef: + key: OTEL_COLLECTOR_FQDN + name: api + optional: false + - name: OTEL_EXPORTER_OTLP_ENDPOINT + valueFrom: + configMapKeyRef: + key: OTEL_EXPORTER_OTLP_ENDPOINT + name: api + optional: false + - name: OTEL_EXPORTER_OTLP_INSECURE + valueFrom: + configMapKeyRef: + key: OTEL_EXPORTER_OTLP_INSECURE + name: api + optional: false + - name: OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE + valueFrom: + configMapKeyRef: + key: OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE + name: api + optional: false + - name: OTEL_RESOURCE_ATTRIBUTES + valueFrom: + configMapKeyRef: + key: OTEL_RESOURCE_ATTRIBUTES + name: api + optional: false + - name: PIPELINE_OUTGOING_STREAM_ARN + valueFrom: + configMapKeyRef: + key: PIPELINE_OUTGOING_STREAM_ARN + name: api + optional: false + - name: PLATFORM + valueFrom: + configMapKeyRef: + key: PLATFORM + name: api + optional: false + - name: POD_ID + valueFrom: + configMapKeyRef: + key: POD_ID + name: api + optional: false + - name: POD_NAME + valueFrom: + configMapKeyRef: + key: POD_NAME + name: api + optional: false + - name: QUERY_QUEUE_URL + valueFrom: + configMapKeyRef: + key: QUERY_QUEUE_URL + name: api + optional: false + - name: REGION + valueFrom: + configMapKeyRef: + key: REGION + name: api + optional: false + - name: REMEDIATION_REBUILD_DEAD_LETTER_QUEUE_URL + valueFrom: + configMapKeyRef: + key: REMEDIATION_REBUILD_DEAD_LETTER_QUEUE_URL + name: api + optional: false + - name: REMEDIATION_REBUILD_QUEUE_URL + valueFrom: + configMapKeyRef: + key: REMEDIATION_REBUILD_QUEUE_URL + name: api + optional: false + - name: SERVICE_NAME + valueFrom: + configMapKeyRef: + key: SERVICE_NAME + name: api + optional: false + - name: SHOULD_USE_SECRETS_MANAGER_FOR_NEO4J_CONFIG + valueFrom: + configMapKeyRef: + key: SHOULD_USE_SECRETS_MANAGER_FOR_NEO4J_CONFIG + name: api + optional: false + - name: SYNC_JOB_DEAD_LETTER_QUEUE_URL + valueFrom: + configMapKeyRef: + key: SYNC_JOB_DEAD_LETTER_QUEUE_URL + name: api + optional: false + - name: SYNC_JOB_QUEUE_URL + valueFrom: + configMapKeyRef: + key: SYNC_JOB_QUEUE_URL + name: api + optional: false + - name: TRACING_ENABLED + valueFrom: + configMapKeyRef: + key: TRACING_ENABLED + name: api + optional: false + - name: NEO4J_PASSWORD + valueFrom: + secretKeyRef: + key: NEO4J_PASSWORD + name: api + optional: false + image: 123456789012.dkr.ecr.us-east-1.amazonaws.com/sample/api:1.2.3 + lifecycle: + preStop: + exec: + command: + - sleep + - "20" + livenessProbe: + failureThreshold: 5 + httpGet: + path: /health/liveness + port: 8080 + scheme: HTTP + initialDelaySeconds: 60 + periodSeconds: 30 + successThreshold: 1 + timeoutSeconds: 15 + name: api + ports: + - containerPort: 8080 + protocol: TCP + - containerPort: 4000 + protocol: TCP + readinessProbe: + failureThreshold: 8 + httpGet: + path: /private/engine/103/health-check + port: 4000 + scheme: HTTP + initialDelaySeconds: 30 + periodSeconds: 15 + successThreshold: 1 + timeoutSeconds: 10 + resources: + limits: + cpu: 2048m + ephemeral-storage: 15Gi + memory: 8Gi + requests: + cpu: 2048m + ephemeral-storage: 8Gi + memory: 8Gi + nodeSelector: + Team: platform-team + karpenter.sh/nodepool: platform-team + priorityClassName: high-priority + serviceAccountName: sample-ns-api + terminationGracePeriodSeconds: 60 + tolerations: + - effect: NoSchedule + key: Team + operator: Equal + value: platform-team + 3: | + apiVersion: external-secrets.io/v1beta1 + kind: ExternalSecret + metadata: + labels: + ManagedEnvironment: sample-env + ManagedProject: sample-app + ManagedService: api + app: api + environment: sample-env + platform: aws_eks + service.name: api + tags.environment: sample-env + tags.platform: aws_eks + tags.project: sample-app + tags.service.name: api + name: api + namespace: NAMESPACE + spec: + data: + - remoteRef: + conversionStrategy: Default + decodingStrategy: None + key: sample-app/sample-env/NEO4J_PASSWORD_POD_103 + metadataPolicy: None + secretKey: NEO4J_PASSWORD + refreshInterval: 1h + secretStoreRef: + kind: ClusterSecretStore + name: configuration-api-webhook + target: + creationPolicy: Owner + deletionPolicy: Retain + name: api + 4: | + apiVersion: keda.sh/v1alpha1 + kind: TriggerAuthentication + metadata: + labels: + ManagedEnvironment: sample-env + ManagedProject: sample-app + ManagedService: api + app: api + environment: sample-env + platform: aws_eks + service.name: api + tags.environment: sample-env + tags.platform: aws_eks + tags.project: sample-app + tags.service.name: api + name: api-aws-credentials + namespace: NAMESPACE + spec: + podIdentity: + provider: aws + 5: | + apiVersion: keda.sh/v1alpha1 + kind: ScaledObject + metadata: + labels: + ManagedEnvironment: sample-env + ManagedProject: sample-app + ManagedService: api + app: api + environment: sample-env + platform: aws_eks + service.name: api + tags.environment: sample-env + tags.platform: aws_eks + tags.project: sample-app + tags.service.name: api + name: api-sqs-scaler + namespace: NAMESPACE + spec: + maxReplicaCount: 10 + minReplicaCount: 2 + scaleTargetRef: + name: api + triggers: + - authenticationRef: + name: api-aws-credentials + metadata: + activationTargetMetricValue: "0" + awsRegion: us-east-1 + dimensionName: LoadBalancer;TargetGroup + dimensionValue: app/sample-alb/0123456789abcdef;targetgroup/sample-web-tg/0123456789abcdef + metricCollectionTime: "300" + metricName: TargetResponseTime + metricStat: Average + metricStatPeriod: "300" + minMetricValue: "0" + namespace: AWS/ApplicationELB + targetMetricValue: "5" + type: aws-cloudwatch + 6: | + apiVersion: policy/v1 + kind: PodDisruptionBudget + metadata: + labels: + ManagedEnvironment: sample-env + ManagedProject: sample-app + ManagedService: api + app: api + environment: sample-env + platform: aws_eks + service.name: api + tags.environment: sample-env + tags.platform: aws_eks + tags.project: sample-app + tags.service.name: api + name: api + namespace: NAMESPACE + spec: + maxUnavailable: 1 + selector: + matchLabels: + app: api + 7: | + apiVersion: rbac.authorization.k8s.io/v1 + kind: Role + metadata: + labels: + ManagedEnvironment: sample-env + ManagedProject: sample-app + ManagedService: api + app: api + environment: sample-env + platform: aws_eks + service.name: api + tags.environment: sample-env + tags.platform: aws_eks + tags.project: sample-app + tags.service.name: api + name: api-pod-annotations + namespace: NAMESPACE + rules: + - apiGroups: + - "" + resources: + - pods + verbs: + - update + - patch + - get + 8: | + apiVersion: rbac.authorization.k8s.io/v1 + kind: RoleBinding + metadata: + labels: + ManagedEnvironment: sample-env + ManagedProject: sample-app + ManagedService: api + app: api + environment: sample-env + platform: aws_eks + service.name: api + service.namespace: sample-ns + tags.environment: sample-env + tags.platform: aws_eks + tags.project: sample-app + tags.service.name: api + tags.service.namespace: sample-ns + name: api-pod-annotations + namespace: NAMESPACE + roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: api-pod-annotations + subjects: + - kind: ServiceAccount + name: sample-ns-api + namespace: NAMESPACE + 9: | + apiVersion: v1 + kind: Service + metadata: + labels: + ManagedEnvironment: sample-env + ManagedProject: sample-app + ManagedService: api + app: api + environment: sample-env + platform: aws_eks + service.name: api + tags.environment: sample-env + tags.platform: aws_eks + tags.project: sample-app + tags.service.name: api + name: api + namespace: NAMESPACE + spec: + ports: + - port: 4000 + protocol: TCP + targetPort: 4000 + selector: + app: api + type: ClusterIP + 10: | + apiVersion: elbv2.k8s.aws/v1beta1 + kind: TargetGroupBinding + metadata: + labels: + ManagedEnvironment: sample-env + ManagedProject: sample-app + ManagedService: api + app: api + environment: sample-env + platform: aws_eks + service.name: api + tags.environment: sample-env + tags.platform: aws_eks + tags.project: sample-app + tags.service.name: api + name: api-tgb + namespace: NAMESPACE + spec: + networking: + ingress: + - from: + - securityGroup: + groupID: sg-0123456789abcdef0 + ports: + - port: 4000 + protocol: TCP + serviceRef: + name: api + port: 4000 + targetGroupARN: arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/sample-web-tg/0123456789abcdef + targetType: ip + vpcID: vpc-0123456789abcdef0 + 11: | + apiVersion: autoscaling.k8s.io/v1 + kind: VerticalPodAutoscaler + metadata: + labels: + ManagedEnvironment: sample-env + ManagedProject: sample-app + ManagedService: api + app: api + environment: sample-env + platform: aws_eks + service.name: api + tags.environment: sample-env + tags.platform: aws_eks + tags.project: sample-app + tags.service.name: api + name: api-vpa + namespace: NAMESPACE + spec: + resourcePolicy: + containerPolicies: + - containerName: '*' + maxAllowed: + cpu: 4 + memory: 4Gi + minAllowed: + cpu: 50m + memory: 128Mi + targetRef: + apiVersion: apps/v1 + kind: Deployment + name: api + updatePolicy: + updateMode: "Off" +legacy-values fixture matches snapshot: + 1: | + apiVersion: v1 + data: + LOG_LEVEL: info + kind: ConfigMap + metadata: + labels: + chart: application + group: io.jupiterone.platform + heritage: Helm + release: RELEASE-NAME + team: sre + name: legacy-app-app-config + namespace: NAMESPACE + 2: | + apiVersion: batch/v1 + kind: CronJob + metadata: + labels: + chart: application + group: io.jupiterone.platform + heritage: Helm + release: RELEASE-NAME + team: sre + name: hourly + namespace: NAMESPACE + spec: + jobTemplate: + spec: + template: + metadata: + labels: + app: legacy-app + spec: + containers: + - env: + - name: MODE + value: hourly + image: docker.io/busybox:1.36 + name: hourly + restartPolicy: OnFailure + serviceAccountName: legacy-app + schedule: 0 * * * * + 3: | + apiVersion: batch/v1 + kind: CronJob + metadata: + labels: + chart: application + group: io.jupiterone.platform + heritage: Helm + release: RELEASE-NAME + team: sre + name: nightly + namespace: NAMESPACE + spec: + jobTemplate: + spec: + template: + metadata: + labels: + app: legacy-app + spec: + containers: + - args: + - echo nightly + command: + - /bin/sh + - -c + image: docker.io/busybox:1.36 + name: nightly + restartPolicy: OnFailure + serviceAccountName: legacy-app + schedule: 0 2 * * * + 4: | + apiVersion: apps/v1 + kind: Deployment + metadata: + labels: + chart: application + group: io.jupiterone.platform + heritage: Helm + release: RELEASE-NAME + team: sre + name: legacy-app + namespace: NAMESPACE + spec: + replicas: 2 + selector: + matchLabels: + app: legacy-app + strategy: + type: RollingUpdate + template: + metadata: + labels: + app: legacy-app + spec: + containers: + - env: + - name: ENVIRONMENT + value: dev + envFrom: + - configMapRef: + name: legacy-app-app-config + image: docker.io/nginx:1.27 + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 3 + httpGet: + path: /path + port: 8080 + initialDelaySeconds: 10 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 1 + name: legacy-app + ports: + - containerPort: 8080 + name: http + readinessProbe: + failureThreshold: 3 + httpGet: + path: /path + port: 8080 + initialDelaySeconds: 10 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 1 + resources: + limits: + cpu: 0.5 + memory: 256Mi + requests: + cpu: 0.1 + memory: 128Mi + serviceAccountName: legacy-app + 5: | + apiVersion: autoscaling/v2 + kind: HorizontalPodAutoscaler + metadata: + labels: + chart: application + group: io.jupiterone.platform + heritage: Helm + release: RELEASE-NAME + team: sre + name: legacy-app + namespace: NAMESPACE + spec: + maxReplicas: 4 + metrics: + - resource: + name: cpu + target: + averageUtilization: 70 + type: Utilization + type: Resource + minReplicas: 2 + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: legacy-app + 6: | + apiVersion: networking.k8s.io/v1 + kind: Ingress + metadata: + labels: + chart: application + group: io.jupiterone.platform + heritage: Helm + release: RELEASE-NAME + team: sre + name: legacy-app + namespace: NAMESPACE + spec: + ingressClassName: nginx + rules: + - host: legacy.example.com + http: + paths: + - backend: + service: + name: legacy-app + port: + name: http + path: / + pathType: ImplementationSpecific + 7: | + apiVersion: policy/v1 + kind: PodDisruptionBudget + metadata: + labels: + chart: application + group: io.jupiterone.platform + heritage: Helm + release: RELEASE-NAME + team: sre + name: legacy-app + namespace: NAMESPACE + spec: + minAvailable: 1 + selector: + matchLabels: + app: legacy-app + 8: | + apiVersion: rbac.authorization.k8s.io/v1 + kind: Role + metadata: + labels: + chart: application + group: io.jupiterone.platform + heritage: Helm + release: RELEASE-NAME + team: sre + name: legacy-app-role-configmaps + namespace: NAMESPACE + rules: + - apiGroups: + - "" + resources: + - configmaps + verbs: + - get + 9: | + apiVersion: rbac.authorization.k8s.io/v1 + kind: Role + metadata: + labels: + chart: application + group: io.jupiterone.platform + heritage: Helm + release: RELEASE-NAME + team: sre + name: legacy-app-role-secrets + namespace: NAMESPACE + rules: + - apiGroups: + - "" + resources: + - secrets + verbs: + - get + 10: | + apiVersion: rbac.authorization.k8s.io/v1 + kind: RoleBinding + metadata: + labels: + chart: application + group: io.jupiterone.platform + heritage: Helm + release: RELEASE-NAME + team: sre + name: legacy-app-rolebinding-configmaps + namespace: NAMESPACE + roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: legacy-app-role-configmaps + subjects: + - kind: ServiceAccount + name: legacy-app + namespace: NAMESPACE + 11: | + apiVersion: rbac.authorization.k8s.io/v1 + kind: RoleBinding + metadata: + labels: + chart: application + group: io.jupiterone.platform + heritage: Helm + release: RELEASE-NAME + team: sre + name: legacy-app-rolebinding-secrets + namespace: NAMESPACE + roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: legacy-app-role-secrets + subjects: + - kind: ServiceAccount + name: legacy-app + namespace: NAMESPACE + 12: | + apiVersion: v1 + data: + API_KEY: bm90LWEtcmVhbC1rZXk= + kind: Secret + metadata: + labels: + chart: application + group: io.jupiterone.platform + heritage: Helm + release: RELEASE-NAME + team: sre + name: legacy-app-creds + namespace: NAMESPACE + 13: | + apiVersion: v1 + kind: Service + metadata: + labels: + chart: application + group: io.jupiterone.platform + heritage: Helm + release: RELEASE-NAME + team: sre + name: legacy-app + namespace: NAMESPACE + spec: + ports: + - name: http + port: 8080 + protocol: TCP + targetPort: 8080 + selector: + app: legacy-app + type: ClusterIP + 14: | + apiVersion: v1 + kind: ServiceAccount + metadata: + labels: + chart: application + group: io.jupiterone.platform + heritage: Helm + release: RELEASE-NAME + team: sre + name: legacy-app + namespace: NAMESPACE diff --git a/charts/application/tests/certificate_test.yaml b/charts/application/tests/certificate_test.yaml new file mode 100644 index 0000000..97788bc --- /dev/null +++ b/charts/application/tests/certificate_test.yaml @@ -0,0 +1,12 @@ +suite: certificate +templates: + - certificate.yaml +tests: + - it: renders cert-manager.io/v1 Certificate + set: + certificate.enabled: true + asserts: + - isAPIVersion: + of: cert-manager.io/v1 + - isKind: + of: Certificate diff --git a/charts/application/tests/configmap_test.yaml b/charts/application/tests/configmap_test.yaml new file mode 100644 index 0000000..32e7f64 --- /dev/null +++ b/charts/application/tests/configmap_test.yaml @@ -0,0 +1,59 @@ +suite: configmap +templates: + - configmap.yaml +tests: + - it: fan-out ConfigMap defaults to applicationName + set: + deployment.envFromConfigMap: + keys: + LOG_LEVEL: info + asserts: + - isKind: + of: ConfigMap + - equal: + path: metadata.name + value: application + + - it: fan-out ConfigMap name overrides via envFromConfigMap.name + set: + deployment.envFromConfigMap: + name: my-config + keys: + LOG_LEVEL: info + asserts: + - equal: + path: metadata.name + value: my-config + + - it: immutable renders when set + set: + deployment.envFromConfigMap: + immutable: true + keys: + LOG_LEVEL: info + asserts: + - equal: + path: immutable + value: true + + - it: immutable is absent by default + set: + deployment.envFromConfigMap: + keys: + LOG_LEVEL: info + asserts: + - notExists: + path: immutable + + - it: data equals the keys map with values coerced to strings + set: + deployment.envFromConfigMap: + keys: + PORT: 8080 + ENABLED: true + asserts: + - equal: + path: data + value: + PORT: "8080" + ENABLED: "true" diff --git a/charts/application/tests/cronjob_test.yaml b/charts/application/tests/cronjob_test.yaml new file mode 100644 index 0000000..2f4e506 --- /dev/null +++ b/charts/application/tests/cronjob_test.yaml @@ -0,0 +1,99 @@ +suite: cronjob +templates: + - cronjob.yaml +tests: + - it: renders apiVersion batch/v1 + values: + - fixtures/legacy-values.yaml + asserts: + - isAPIVersion: + of: batch/v1 + - isKind: + of: CronJob + + - it: two jobs render as two documents + values: + - fixtures/legacy-values.yaml + asserts: + - hasDocuments: + count: 2 + + - it: suspend, backoffLimit, restartPolicy, concurrencyPolicy and history limits render + set: + cronJob: + enabled: true + jobs: + sync: + schedule: "*/5 * * * *" + suspend: true + backoffLimit: 2 + restartPolicy: Never + concurrencyPolicy: Forbid + successfulJobsHistoryLimit: 2 + failedJobsHistoryLimit: 4 + image: + repository: docker.io/nginx + tag: "1.27" + asserts: + - equal: + path: spec.suspend + value: true + - equal: + path: spec.jobTemplate.spec.backoffLimit + value: 2 + - equal: + path: spec.jobTemplate.spec.template.spec.restartPolicy + value: Never + - equal: + path: spec.concurrencyPolicy + value: Forbid + - equal: + path: spec.successfulJobsHistoryLimit + value: 2 + - equal: + path: spec.failedJobsHistoryLimit + value: 4 + + - it: jobLabels and jobAnnotations land on spec.jobTemplate.metadata + set: + cronJob: + enabled: true + jobs: + sync: + schedule: "*/5 * * * *" + jobLabels: + team: sre + jobAnnotations: + note: hello + image: + repository: docker.io/nginx + tag: "1.27" + asserts: + - equal: + path: spec.jobTemplate.metadata.labels.team + value: sre + - equal: + path: spec.jobTemplate.metadata.annotations.note + value: hello + + - it: podLabels and podAnnotations land on the pod template + set: + cronJob: + enabled: true + jobs: + sync: + schedule: "*/5 * * * *" + podLabels: + app-role: worker + podAnnotations: + sidecar.io/inject: "false" + image: + repository: docker.io/nginx + tag: "1.27" + asserts: + - equal: + path: spec.jobTemplate.spec.template.metadata.labels.app-role + value: worker + - equal: + path: spec.jobTemplate.spec.template.metadata.annotations["sidecar.io/inject"] + value: "false" diff --git a/charts/application/tests/deployment_test.yaml b/charts/application/tests/deployment_test.yaml new file mode 100644 index 0000000..55aba77 --- /dev/null +++ b/charts/application/tests/deployment_test.yaml @@ -0,0 +1,203 @@ +suite: deployment +templates: + - deployment.yaml +tests: + - it: renders a Deployment + asserts: + - isKind: + of: Deployment + - isAPIVersion: + of: apps/v1 + + - it: env order is downward API, then configMap keys, then secretKeyRef + set: + deployment.envDownwardApi: + - name: KUBERNETES_POD_NAME + fieldPath: metadata.name + - name: KUBERNETES_NAMESPACE + fieldPath: metadata.namespace + deployment.envFromConfigMap: + keys: + AWS_REGION: us-east-1 + LOG_LEVEL: info + deployment.envSecretKeys: + - name: DB_PASSWORD + secretName: my-secret + asserts: + - equal: + path: spec.template.spec.containers[0].env + value: + - name: KUBERNETES_POD_NAME + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: metadata.name + - name: KUBERNETES_NAMESPACE + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: metadata.namespace + - name: AWS_REGION + valueFrom: + configMapKeyRef: + key: AWS_REGION + name: application + optional: false + - name: LOG_LEVEL + valueFrom: + configMapKeyRef: + key: LOG_LEVEL + name: application + optional: false + - name: DB_PASSWORD + valueFrom: + secretKeyRef: + key: DB_PASSWORD + name: my-secret + optional: false + + - it: imagePullPolicy is absent when deployment.image.pullPolicy is null + set: + deployment.image.pullPolicy: "" + asserts: + - notExists: + path: spec.template.spec.containers[0].imagePullPolicy + + - it: image.ref wins over repository/tag + set: + deployment.image.repository: docker.io/nginx + deployment.image.tag: "1.27" + deployment.image.ref: registry.example.com/app@sha256:aaaabbbbccccddddeeeeffff00001111222233334444555566667777888899 + asserts: + - equal: + path: spec.template.spec.containers[0].image + value: registry.example.com/app@sha256:aaaabbbbccccddddeeeeffff00001111222233334444555566667777888899 + + - it: spec.replicas renders when set to 0 + set: + deployment.replicas: 0 + asserts: + - equal: + path: spec.replicas + value: 0 + + - it: scheduling and lifecycle fields are absent by default + asserts: + - notExists: + path: spec.revisionHistoryLimit + - notExists: + path: spec.template.spec.priorityClassName + - notExists: + path: spec.template.spec.terminationGracePeriodSeconds + - notExists: + path: spec.template.spec.automountServiceAccountToken + - notExists: + path: spec.template.spec.containers[0].lifecycle + + - it: scheduling and lifecycle fields render when set + set: + deployment.revisionHistoryLimit: 5 + deployment.priorityClassName: high-priority + deployment.terminationGracePeriodSeconds: 45 + deployment.automountServiceAccountToken: true + deployment.lifecycle: + preStop: + exec: + command: ["sleep", "5"] + asserts: + - equal: + path: spec.revisionHistoryLimit + value: 5 + - equal: + path: spec.template.spec.priorityClassName + value: high-priority + - equal: + path: spec.template.spec.terminationGracePeriodSeconds + value: 45 + - equal: + path: spec.template.spec.automountServiceAccountToken + value: true + - equal: + path: spec.template.spec.containers[0].lifecycle.preStop.exec.command + value: ["sleep", "5"] + + - it: resources renders free-form keys such as ephemeral-storage + set: + deployment.resources: + limits: + cpu: "1" + memory: 1Gi + ephemeral-storage: 3Gi + requests: + cpu: "1" + memory: 1Gi + ephemeral-storage: 1Gi + asserts: + - equal: + path: spec.template.spec.containers[0].resources.limits.ephemeral-storage + value: 3Gi + - equal: + path: spec.template.spec.containers[0].resources.requests.ephemeral-storage + value: 1Gi + + - it: probe timing fields are absent when unset + set: + deployment.readinessProbe.failureThreshold: null + deployment.readinessProbe.periodSeconds: null + deployment.readinessProbe.successThreshold: null + deployment.readinessProbe.timeoutSeconds: null + deployment.readinessProbe.initialDelaySeconds: null + asserts: + - notExists: + path: spec.template.spec.containers[0].readinessProbe.failureThreshold + - notExists: + path: spec.template.spec.containers[0].readinessProbe.periodSeconds + - notExists: + path: spec.template.spec.containers[0].readinessProbe.successThreshold + - notExists: + path: spec.template.spec.containers[0].readinessProbe.timeoutSeconds + - notExists: + path: spec.template.spec.containers[0].readinessProbe.initialDelaySeconds + - exists: + path: spec.template.spec.containers[0].readinessProbe.httpGet + + - it: labels.chartLabels false removes chart provenance labels from metadata + set: + labels.chartLabels: false + asserts: + - notExists: + path: metadata.labels.chart + - notExists: + path: metadata.labels.release + - notExists: + path: metadata.labels.heritage + - notExists: + path: metadata.labels.team + - notExists: + path: metadata.labels.group + - equal: + path: metadata.labels.app + value: application + + - it: commonLabels appear on both metadata and pod template + set: + commonLabels: + foo: bar + asserts: + - equal: + path: metadata.labels.foo + value: bar + - equal: + path: spec.template.metadata.labels.foo + value: bar + + - it: selector is exactly the single app label regardless of commonLabels + set: + applicationName: myapp + commonLabels: + foo: bar + asserts: + - equal: + path: spec.selector.matchLabels + value: + app: myapp diff --git a/charts/application/tests/externalsecret_test.yaml b/charts/application/tests/externalsecret_test.yaml new file mode 100644 index 0000000..c024b88 --- /dev/null +++ b/charts/application/tests/externalsecret_test.yaml @@ -0,0 +1,39 @@ +suite: externalsecret +templates: + - externalsecret.yaml +tests: + - it: enabled false renders nothing + asserts: + - hasDocuments: + count: 0 + + - it: name defaults to applicationName + set: + externalSecret.enabled: true + externalSecret.secretStoreRef.name: my-store + asserts: + - equal: + path: metadata.name + value: application + - equal: + path: spec.target.name + value: application + + - it: data entries carry the ESO admission defaults + set: + externalSecret.enabled: true + externalSecret.secretStoreRef.name: my-store + externalSecret.data: + - secretKey: DB_PASSWORD + remoteRef: + key: my-project/DB_PASSWORD + asserts: + - equal: + path: spec.data[0].remoteRef.conversionStrategy + value: Default + - equal: + path: spec.data[0].remoteRef.decodingStrategy + value: None + - equal: + path: spec.data[0].remoteRef.metadataPolicy + value: None diff --git a/charts/application/ci/cronjob-values.yaml b/charts/application/tests/fixtures/cronjob-values.yaml similarity index 100% rename from charts/application/ci/cronjob-values.yaml rename to charts/application/tests/fixtures/cronjob-values.yaml diff --git a/charts/application/ci/deployment-with-vpa-values.yaml b/charts/application/tests/fixtures/deployment-with-vpa-values.yaml similarity index 100% rename from charts/application/ci/deployment-with-vpa-values.yaml rename to charts/application/tests/fixtures/deployment-with-vpa-values.yaml diff --git a/charts/application/ci/http-service-keda-tgb-values.yaml b/charts/application/tests/fixtures/http-service-keda-tgb-values.yaml similarity index 100% rename from charts/application/ci/http-service-keda-tgb-values.yaml rename to charts/application/tests/fixtures/http-service-keda-tgb-values.yaml diff --git a/charts/application/ci/legacy-values.yaml b/charts/application/tests/fixtures/legacy-values.yaml similarity index 100% rename from charts/application/ci/legacy-values.yaml rename to charts/application/tests/fixtures/legacy-values.yaml diff --git a/charts/application/tests/fixtures_test.yaml b/charts/application/tests/fixtures_test.yaml new file mode 100644 index 0000000..4b15e7e --- /dev/null +++ b/charts/application/tests/fixtures_test.yaml @@ -0,0 +1,27 @@ +# One snapshot per ci fixture so any change to its rendered output must be reviewed. +# The stock values.yaml disables most templates; these fixtures are what exercise them. +suite: fixture snapshots +tests: + - it: cronjob-values fixture matches snapshot + values: + - fixtures/cronjob-values.yaml + asserts: + - matchSnapshot: {} + + - it: deployment-with-vpa-values fixture matches snapshot + values: + - fixtures/deployment-with-vpa-values.yaml + asserts: + - matchSnapshot: {} + + - it: http-service-keda-tgb-values fixture matches snapshot + values: + - fixtures/http-service-keda-tgb-values.yaml + asserts: + - matchSnapshot: {} + + - it: legacy-values fixture matches snapshot + values: + - fixtures/legacy-values.yaml + asserts: + - matchSnapshot: {} diff --git a/charts/application/tests/hpa_test.yaml b/charts/application/tests/hpa_test.yaml new file mode 100644 index 0000000..8438a92 --- /dev/null +++ b/charts/application/tests/hpa_test.yaml @@ -0,0 +1,12 @@ +suite: hpa +templates: + - hpa.yaml +tests: + - it: renders autoscaling/v2 HorizontalPodAutoscaler + set: + autoscaling.enabled: true + asserts: + - isAPIVersion: + of: autoscaling/v2 + - isKind: + of: HorizontalPodAutoscaler diff --git a/charts/application/tests/keda_test.yaml b/charts/application/tests/keda_test.yaml new file mode 100644 index 0000000..f7dcbd5 --- /dev/null +++ b/charts/application/tests/keda_test.yaml @@ -0,0 +1,54 @@ +suite: keda +templates: + - keda.yaml +tests: + - it: enabled false renders nothing + asserts: + - hasDocuments: + count: 0 + + - it: ScaledObject name defaults to applicationName-scaler + set: + keda.enabled: true + asserts: + - equal: + path: metadata.name + value: application-scaler + documentSelector: + path: kind + value: ScaledObject + + - it: each trigger carries authenticationRef.name equal to the TriggerAuthentication name + set: + keda.enabled: true + keda.triggers: + - type: aws-sqs-queue + metadata: + queueURL: https://sqs.us-east-1.amazonaws.com/123456789012/my-queue + asserts: + - equal: + path: metadata.name + value: application-aws-credentials + documentSelector: + path: kind + value: TriggerAuthentication + - equal: + path: spec.triggers[0].authenticationRef.name + value: application-aws-credentials + documentSelector: + path: kind + value: ScaledObject + + - it: no authenticationRef when triggerAuthentication.enabled is false + set: + keda.enabled: true + keda.triggerAuthentication.enabled: false + keda.triggers: + - type: aws-sqs-queue + metadata: + queueURL: https://sqs.us-east-1.amazonaws.com/123456789012/my-queue + asserts: + - hasDocuments: + count: 1 + - notExists: + path: spec.triggers[0].authenticationRef diff --git a/charts/application/tests/pdb_test.yaml b/charts/application/tests/pdb_test.yaml new file mode 100644 index 0000000..84acb5f --- /dev/null +++ b/charts/application/tests/pdb_test.yaml @@ -0,0 +1,39 @@ +suite: pdb +templates: + - pdb.yaml +tests: + - it: maxUnavailable renders alone when minAvailable is null + set: + pdb.enabled: true + pdb.minAvailable: null + pdb.maxUnavailable: 2 + asserts: + - notExists: + path: spec.minAvailable + - equal: + path: spec.maxUnavailable + value: 2 + - exists: + path: metadata.namespace + + - it: both minAvailable and maxUnavailable render when both set + set: + pdb.enabled: true + pdb.minAvailable: 1 + pdb.maxUnavailable: 2 + asserts: + - equal: + path: spec.minAvailable + value: 1 + - equal: + path: spec.maxUnavailable + value: 2 + + - it: selector is the single app label + set: + pdb.enabled: true + asserts: + - equal: + path: spec.selector.matchLabels + value: + app: application diff --git a/charts/application/tests/rbac_test.yaml b/charts/application/tests/rbac_test.yaml new file mode 100644 index 0000000..eeae6ce --- /dev/null +++ b/charts/application/tests/rbac_test.yaml @@ -0,0 +1,101 @@ +suite: rbac +templates: + - role.yaml + - rolebinding.yaml + - serviceaccount.yaml + - deployment.yaml +tests: + - it: roleName and bindingName override the default naming + set: + rbac.roles: + - name: custom + roleName: custom-role-name + bindingName: custom-binding-name + rules: + - apiGroups: [""] + resources: ["pods"] + verbs: ["get"] + asserts: + - equal: + path: metadata.name + value: custom-role-name + template: role.yaml + - equal: + path: metadata.name + value: custom-binding-name + template: rolebinding.yaml + - equal: + path: roleRef.name + value: custom-role-name + template: rolebinding.yaml + + - it: default names follow the applicationName-role-name pattern + set: + applicationName: myapp + rbac.roles: + - name: readers + rules: + - apiGroups: [""] + resources: ["pods"] + verbs: ["get"] + asserts: + - equal: + path: metadata.name + value: myapp-role-readers + template: role.yaml + - equal: + path: metadata.name + value: myapp-rolebinding-readers + template: rolebinding.yaml + - equal: + path: roleRef.name + value: myapp-role-readers + template: rolebinding.yaml + + - it: roleLabels and bindingLabels land on the right object + set: + rbac.roles: + - name: readers + roleLabels: + custom: role-label + bindingLabels: + custom: binding-label + rules: + - apiGroups: [""] + resources: ["pods"] + verbs: ["get"] + asserts: + - equal: + path: metadata.labels.custom + value: role-label + template: role.yaml + - equal: + path: metadata.labels.custom + value: binding-label + template: rolebinding.yaml + + - it: RoleBinding subject namespace equals the release namespace + release: + namespace: my-ns + asserts: + - equal: + path: subjects[0].namespace + value: my-ns + template: rolebinding.yaml + - equal: + path: metadata.namespace + value: my-ns + template: rolebinding.yaml + + - it: no ServiceAccount when disabled; pod uses the configured name + set: + rbac.serviceAccount.enabled: false + rbac.serviceAccount.name: custom-sa + asserts: + - hasDocuments: + count: 0 + template: serviceaccount.yaml + - equal: + path: spec.template.spec.serviceAccountName + value: custom-sa + template: deployment.yaml diff --git a/charts/application/tests/service_test.yaml b/charts/application/tests/service_test.yaml new file mode 100644 index 0000000..ddc06e0 --- /dev/null +++ b/charts/application/tests/service_test.yaml @@ -0,0 +1,25 @@ +suite: service +templates: + - service.yaml +tests: + - it: selector includes podLabels by default + set: + deployment.podLabels: + env: prod + asserts: + - equal: + path: spec.selector + value: + app: application + env: prod + + - it: selector is exactly app when selectorFromPodLabels is false + set: + deployment.podLabels: + env: prod + service.selectorFromPodLabels: false + asserts: + - equal: + path: spec.selector + value: + app: application diff --git a/charts/application/tests/targetgroupbinding_test.yaml b/charts/application/tests/targetgroupbinding_test.yaml new file mode 100644 index 0000000..7d77812 --- /dev/null +++ b/charts/application/tests/targetgroupbinding_test.yaml @@ -0,0 +1,50 @@ +suite: targetgroupbinding +templates: + - targetgroupbinding.yaml +tests: + - it: enabled false renders nothing + asserts: + - hasDocuments: + count: 0 + + - it: name defaults to applicationName-tgb + set: + targetGroupBinding.enabled: true + targetGroupBinding.targetGroupARN: arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/sample-tg/0123456789abcdef0 + targetGroupBinding.serviceRef.port: 8080 + asserts: + - equal: + path: metadata.name + value: application-tgb + + - it: requires one of targetGroupARN or targetGroupName + set: + targetGroupBinding.enabled: true + asserts: + - failedTemplate: + errorMessage: targetGroupBinding.targetGroupARN or targetGroupName is required + + - it: renders networking verbatim + set: + targetGroupBinding.enabled: true + targetGroupBinding.targetGroupARN: arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/sample-tg/0123456789abcdef0 + targetGroupBinding.serviceRef.port: 8080 + targetGroupBinding.networking: + ingress: + - from: + - securityGroup: + groupID: sg-0123456789abcdef0 + ports: + - port: 8080 + protocol: TCP + asserts: + - equal: + path: spec.networking + value: + ingress: + - from: + - securityGroup: + groupID: sg-0123456789abcdef0 + ports: + - port: 8080 + protocol: TCP diff --git a/charts/application/tests/vpa_test.yaml b/charts/application/tests/vpa_test.yaml new file mode 100644 index 0000000..92d75b9 --- /dev/null +++ b/charts/application/tests/vpa_test.yaml @@ -0,0 +1,34 @@ +suite: vpa +templates: + - vpa.yaml +tests: + - it: enabled false renders nothing + asserts: + - hasDocuments: + count: 0 + + - it: renders with the default name and quoted updateMode + set: + vpa.enabled: true + asserts: + - equal: + path: metadata.name + value: application-vpa + - equal: + path: spec.updatePolicy.updateMode + value: "Off" + - equal: + path: spec.targetRef.kind + value: Deployment + - equal: + path: spec.targetRef.name + value: application + + - it: name overrides via vpa.name + set: + vpa.enabled: true + vpa.name: custom-vpa + asserts: + - equal: + path: metadata.name + value: custom-vpa From 7eebf382e8d59de4063e5b9d71bfafe2612a8e5b Mon Sep 17 00:00:00 2001 From: tokio-on-jupiter Date: Thu, 17 Sep 2026 14:59:36 -0400 Subject: [PATCH 2/2] chore(application): run helm-unittest in CI and point the fixture loop at tests/fixtures Bumps the PR workflow's Helm to v3.17.1 (lint, dry-run install and package all still pass) so the helm-unittest plugin can be installed, pinned to v1.0.3 since newer releases need Helm 3.18.10+ for their plugin.yaml. Points the existing render/Pluto/kubeconform loop at tests/fixtures/*-values.yaml now that ci/ moved. Documents both in the READMEs and changelog. Co-authored-by: Claude --- .github/workflows/pull_request.yaml | 20 +++++++++++++++----- README.md | 10 ++++++++++ changelog.md | 8 ++++++++ charts/application/README.md | 10 ++++++++++ 4 files changed, 43 insertions(+), 5 deletions(-) diff --git a/.github/workflows/pull_request.yaml b/.github/workflows/pull_request.yaml index d9e79a3..72517fa 100644 --- a/.github/workflows/pull_request.yaml +++ b/.github/workflows/pull_request.yaml @@ -24,10 +24,12 @@ jobs: with: version: "v0.18.0" # Set Up Helm + # v3.17.1: the helm-unittest plugin needs a Helm newer than the 3.7.0 this workflow + # used to pin; lint, dry-run install and package all still pass under it. - name: Set up Helm uses: azure/setup-helm@v1 with: - version: v3.7.0 + version: v3.17.1 # Check K8s deprecations - name: Setup Pluto uses: FairwindsOps/pluto/github-action@master @@ -37,9 +39,17 @@ jobs: - name: Helm Lint run: | helm lint ${CHART_PATH} - # Every ci/*-values.yaml fixture must render, pass Pluto and validate against the - # Kubernetes + CRD schemas. The stock values.yaml disables most templates, so the - # fixtures are what actually exercise them. + # Pinned to v1.0.3: newer helm-unittest releases moved plugin.yaml to the + # platformHooks schema, which needs Helm 3.18.10+. + - name: Setup helm-unittest + run: | + helm plugin list | grep -q unittest || helm plugin install https://github.com/helm-unittest/helm-unittest --version v1.0.3 + - name: Run helm unittest + run: | + helm unittest ${CHART_PATH} + # Every tests/fixtures/*-values.yaml fixture must render, pass Pluto and validate + # against the Kubernetes + CRD schemas. The stock values.yaml disables most + # templates, so the fixtures are what actually exercise them. - name: Setup kubeconform run: | curl -sSL https://github.com/yannh/kubeconform/releases/download/v0.6.7/kubeconform-linux-amd64.tar.gz \ @@ -47,7 +57,7 @@ jobs: - name: Render and validate fixtures run: | set -euo pipefail - for f in ${CHART_PATH}/ci/*-values.yaml; do + for f in ${CHART_PATH}/tests/fixtures/*-values.yaml; do echo "== $(basename "$f")" helm template fixture ${CHART_PATH} -n fixture -f "$f" > /tmp/rendered.yaml pluto detect /tmp/rendered.yaml diff --git a/README.md b/README.md index 3a4b206..6528ea3 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,16 @@ To uninstall the chart: helm delete -n namespace +## Unit Tests + +Suites live under `charts/application/tests/`, one file per template area, and run with +the [helm-unittest](https://github.com/helm-unittest/helm-unittest) plugin. Fixtures +shared with the PR render/Pluto/kubeconform loop live under +`charts/application/tests/fixtures/`. + + helm plugin list | grep -q unittest || helm plugin install https://github.com/helm-unittest/helm-unittest --version v1.0.3 + helm unittest charts/application + ## Testing - Configuring Env/Secrets File vi ~/.env diff --git a/changelog.md b/changelog.md index 1c6a486..2ba6ff2 100644 --- a/changelog.md +++ b/changelog.md @@ -1,6 +1,14 @@ # Changelog All notable changes to this project will be documented here +### Unreleased +- Added a helm-unittest suite under `charts/application/tests/` covering the templates + added in v1.1.0; `ci/*-values.yaml` fixtures moved to `charts/application/tests/fixtures/` + and are now also used as suite-level `values:` overrides +- Fixed: `deployment.terminationGracePeriodSeconds` and `deployment.automountServiceAccountToken` + rendered as explicit `null` by default instead of being omitted, because the template + checked `hasKey` instead of the value itself + ### v1.1.0 - CronJob moves to `batch/v1`, renders multiple jobs correctly and gains `suspend`, `timeZone`, `startingDeadlineSeconds`, `backoffLimit`, `activeDeadlineSeconds`, `ttlSecondsAfterFinished`, diff --git a/charts/application/README.md b/charts/application/README.md index 3a4b206..6528ea3 100644 --- a/charts/application/README.md +++ b/charts/application/README.md @@ -20,6 +20,16 @@ To uninstall the chart: helm delete -n namespace +## Unit Tests + +Suites live under `charts/application/tests/`, one file per template area, and run with +the [helm-unittest](https://github.com/helm-unittest/helm-unittest) plugin. Fixtures +shared with the PR render/Pluto/kubeconform loop live under +`charts/application/tests/fixtures/`. + + helm plugin list | grep -q unittest || helm plugin install https://github.com/helm-unittest/helm-unittest --version v1.0.3 + helm unittest charts/application + ## Testing - Configuring Env/Secrets File vi ~/.env