Files
2026-10-18 14:50:07 -04:00

151 lines
7.6 KiB
YAML

# SPDX-License-Identifier: MIT-0
---
- name: Validate rendered Kubernetes resources without a cluster
hosts: localhost
connection: local
gather_facts: false
vars:
template_directory: "{{ playbook_dir }}/../templates"
tasks:
- name: Load the role defaults
ansible.builtin.include_vars:
file: "{{ playbook_dir }}/../defaults/main.yml"
- name: Set required test inputs
ansible.builtin.set_fact:
the_button_image: registry.example.com/the-button:1.0.0
the_button_ingress_host: button.example.com
- name: Render default manifests using Ansible's template engine
ansible.builtin.set_fact:
deployment: "{{ lookup('ansible.builtin.template', template_directory ~ '/deployment.yml.j2') | from_yaml }}"
service: "{{ lookup('ansible.builtin.template', template_directory ~ '/service.yml.j2') | from_yaml }}"
ingress: "{{ lookup('ansible.builtin.template', template_directory ~ '/ingress.yml.j2') | from_yaml }}"
pvc: "{{ lookup('ansible.builtin.template', template_directory ~ '/pvc.yml.j2') | from_yaml }}"
- name: Check TLS routing, persistent storage, and single-writer invariants
ansible.builtin.assert:
that:
- deployment.apiVersion == 'apps/v1'
- deployment.spec.replicas == 1
- deployment.spec.strategy.type == 'Recreate'
- deployment.spec.selector.matchLabels == service.spec.selector
- deployment.spec.template.metadata.labels == service.spec.selector
- deployment.spec.template.spec.volumes[0].persistentVolumeClaim.claimName == pvc.metadata.name
- deployment.spec.template.spec.containers[0].volumeMounts[0].mountPath == '/data'
- deployment.spec.template.spec.containers[0].image == the_button_image
- deployment.spec.template.spec.containers[0].ports[0].containerPort == 8000
- service.spec.type == 'ClusterIP'
- service.spec.ports[0].port == 80
- service.spec.ports[0].targetPort == 'http'
- ingress.apiVersion == 'networking.k8s.io/v1'
- ingress.metadata.annotations['cert-manager.io/cluster-issuer'] == 'ca-issuer'
- ingress.spec.tls[0].hosts == [the_button_ingress_host]
- ingress.spec.tls[0].secretName == 'the-button-tls'
- ingress.spec.rules[0].host == the_button_ingress_host
- ingress.spec.rules[0].http.paths[0].backend.service.name == service.metadata.name
- ingress.spec.rules[0].http.paths[0].backend.service.port.name == 'http'
- ingress.spec.rules[0].http.paths[0].pathType == 'Prefix'
- "'ingressClassName' not in ingress.spec"
- "'storageClassName' not in pvc.spec"
- pvc.spec.accessModes == ['ReadWriteOnce']
- pvc.spec.resources.requests.storage == '1Gi'
- name: Check container hardening and health probes
ansible.builtin.assert:
that:
- not deployment.spec.template.spec.automountServiceAccountToken
- deployment.spec.template.spec.securityContext.runAsNonRoot
- deployment.spec.template.spec.securityContext.runAsUser == 10001
- deployment.spec.template.spec.securityContext.fsGroup == 10001
- deployment.spec.template.spec.securityContext.seccompProfile.type == 'RuntimeDefault'
- not deployment.spec.template.spec.containers[0].securityContext.allowPrivilegeEscalation
- deployment.spec.template.spec.containers[0].securityContext.readOnlyRootFilesystem
- deployment.spec.template.spec.containers[0].securityContext.capabilities.drop == ['ALL']
- deployment.spec.template.spec.containers[0].startupProbe.httpGet.path == '/api/state'
- deployment.spec.template.spec.containers[0].readinessProbe.httpGet.port == 'http'
- deployment.spec.template.spec.containers[0].livenessProbe.httpGet.port == 'http'
- name: Configure custom deployment settings
ansible.builtin.set_fact:
the_button_name: custom-button
the_button_namespace: games
the_button_ingress_class_name: nginx
the_button_ingress_annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
cert-manager.io/cluster-issuer: custom-ca-issuer
the_button_existing_claim: restored-game-data
the_button_storage_class: fast
the_button_image_pull_secrets:
- private-registry
the_button_duration_seconds: 60
the_button_node_selector:
kubernetes.io/os: linux
the_button_tolerations:
- key: games
operator: Exists
effect: NoSchedule
- name: Render custom manifests
ansible.builtin.set_fact:
deployment: "{{ lookup('ansible.builtin.template', template_directory ~ '/deployment.yml.j2') | from_yaml }}"
ingress: "{{ lookup('ansible.builtin.template', template_directory ~ '/ingress.yml.j2') | from_yaml }}"
pvc: "{{ lookup('ansible.builtin.template', template_directory ~ '/pvc.yml.j2') | from_yaml }}"
- name: Check configurable settings and custom annotations
ansible.builtin.assert:
that:
- deployment.metadata.name == 'custom-button'
- deployment.metadata.namespace == 'games'
- deployment.spec.template.spec.volumes[0].persistentVolumeClaim.claimName == 'restored-game-data'
- deployment.spec.template.spec.imagePullSecrets == [{'name':'private-registry'}]
- deployment.spec.template.spec.nodeSelector['kubernetes.io/os'] == 'linux'
- deployment.spec.template.spec.tolerations[0].key == 'games'
- deployment.spec.template.spec.containers[0].env[2].value == '60'
- ingress.spec.ingressClassName == 'nginx'
- ingress.metadata.annotations['nginx.ingress.kubernetes.io/ssl-redirect'] == 'true'
- ingress.metadata.annotations['cert-manager.io/cluster-issuer'] == 'custom-ca-issuer'
- ingress.spec.tls[0].secretName == 'custom-button-tls'
- pvc.spec.storageClassName == 'fast'
- name: Configure a namespace-scoped issuer through annotations
ansible.builtin.set_fact:
the_button_ingress_annotations:
cert-manager.io/issuer: namespace-issuer
- name: Render ingress with a namespace-scoped issuer
ansible.builtin.set_fact:
ingress: "{{ lookup('ansible.builtin.template', template_directory ~ '/ingress.yml.j2') | from_yaml }}"
- name: Check issuer annotations are rendered without injected defaults
ansible.builtin.assert:
that:
- ingress.metadata.annotations == the_button_ingress_annotations
- "'cert-manager.io/cluster-issuer' not in ingress.metadata.annotations"
- name: Configure an empty annotations map
ansible.builtin.set_fact:
the_button_ingress_annotations: {}
- name: Render ingress without annotations
ansible.builtin.set_fact:
ingress: "{{ lookup('ansible.builtin.template', template_directory ~ '/ingress.yml.j2') | from_yaml }}"
- name: Check annotations can be cleared entirely
ansible.builtin.assert:
that:
- ingress.metadata.annotations == {}
- name: Disable dynamic provisioning
ansible.builtin.set_fact:
the_button_storage_class: ""
- name: Render a claim with dynamic provisioning disabled
ansible.builtin.set_fact:
static_pvc: "{{ lookup('ansible.builtin.template', template_directory ~ '/pvc.yml.j2') | from_yaml }}"
- name: Check explicit empty StorageClass is preserved
ansible.builtin.assert:
that:
- static_pvc.spec.storageClassName == ''