diff --git a/deploy/k8s/platform/base/kustomization.yaml b/deploy/k8s/platform/base/kustomization.yaml index a1f9908..4e81699 100644 --- a/deploy/k8s/platform/base/kustomization.yaml +++ b/deploy/k8s/platform/base/kustomization.yaml @@ -2,6 +2,8 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - namespace.yaml + - utility-namespace.yaml + - ntfy.yaml - traefik-config.yaml - observability.yaml - headlamp.yaml diff --git a/deploy/k8s/platform/base/ntfy.yaml b/deploy/k8s/platform/base/ntfy.yaml new file mode 100644 index 0000000..5724a0d --- /dev/null +++ b/deploy/k8s/platform/base/ntfy.yaml @@ -0,0 +1,86 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: ntfy-config + namespace: utility +data: + server.yml: | + base-url: http://ntfy.utility.svc.cluster.local + cache-file: /var/cache/ntfy/cache.db + attachment-cache-dir: /var/cache/ntfy/attachments +--- +apiVersion: v1 +kind: Service +metadata: + name: ntfy + namespace: utility + labels: + app: ntfy + app.kubernetes.io/part-of: unrip3 +spec: + type: ClusterIP + selector: + app: ntfy + ports: + - name: http + port: 80 + targetPort: http +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: ntfy + namespace: utility + labels: + app: ntfy + app.kubernetes.io/part-of: unrip3 +spec: + replicas: 1 + selector: + matchLabels: + app: ntfy + template: + metadata: + labels: + app: ntfy + app.kubernetes.io/part-of: unrip3 + spec: + containers: + - name: ntfy + image: binwiederhier/ntfy:v2.21.0 + imagePullPolicy: IfNotPresent + args: ["serve"] + ports: + - name: http + containerPort: 80 + readinessProbe: + httpGet: + path: /v1/health + port: http + initialDelaySeconds: 5 + periodSeconds: 10 + livenessProbe: + httpGet: + path: /v1/health + port: http + initialDelaySeconds: 20 + periodSeconds: 30 + resources: + requests: + cpu: 25m + memory: 64Mi + limits: + cpu: 250m + memory: 128Mi + volumeMounts: + - name: config + mountPath: /etc/ntfy + readOnly: true + - name: cache + mountPath: /var/cache/ntfy + volumes: + - name: config + configMap: + name: ntfy-config + - name: cache + emptyDir: {} diff --git a/deploy/k8s/platform/base/utility-namespace.yaml b/deploy/k8s/platform/base/utility-namespace.yaml new file mode 100644 index 0000000..7c6b29e --- /dev/null +++ b/deploy/k8s/platform/base/utility-namespace.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: utility + labels: + app.kubernetes.io/part-of: unrip3 + project.pi.io/type: utility diff --git a/test/ntfy_manifest_test.py b/test/ntfy_manifest_test.py new file mode 100644 index 0000000..dae6032 --- /dev/null +++ b/test/ntfy_manifest_test.py @@ -0,0 +1,37 @@ +import pathlib +import re +import subprocess +import unittest + +ROOT = pathlib.Path(__file__).resolve().parents[1] + + +class NtfyManifestTest(unittest.TestCase): + def test_platform_kustomization_owns_internal_ntfy_utility_resources(self): + source = (ROOT / 'deploy/k8s/platform/base/kustomization.yaml').read_text() + self.assertIn('utility-namespace.yaml', source) + self.assertIn('ntfy.yaml', source) + + def test_ntfy_manifest_is_internal_clusterip_service_with_health_checks(self): + source = (ROOT / 'deploy/k8s/platform/base/ntfy.yaml').read_text() + self.assertIn('namespace: utility', source) + self.assertIn('image: binwiederhier/ntfy:v2.21.0', source) + self.assertRegex(source, r'kind: Service[\s\S]*type: ClusterIP') + self.assertIn('path: /v1/health', source) + self.assertIn('base-url: http://ntfy.utility.svc.cluster.local', source) + self.assertNotIn('kind: Ingress', source) + + def test_overlay_render_contains_cluster_owned_ntfy_without_public_ingress(self): + rendered = subprocess.check_output( + ['kubectl', 'kustomize', 'deploy/k8s/overlays/hetzner-single-node'], + cwd=ROOT, + text=True, + ) + self.assertIn('name: utility', rendered) + self.assertIn('name: ntfy', rendered) + self.assertIn('image: binwiederhier/ntfy:v2.21.0', rendered) + self.assertNotRegex(rendered, re.compile(r'kind: Ingress[\s\S]*name: ntfy')) + + +if __name__ == '__main__': + unittest.main()