From dd2b19116ccb037ecf0bc4ce4fe1c75a59408bfd Mon Sep 17 00:00:00 2001
From: Sheogorath <sheogorath@shivering-isles.com>
Date: Sun, 8 Oct 2023 01:48:29 +0200
Subject: [PATCH] feat(nut-exporter): Add tests and serviceaccount

This patch adjusts the helm chart to become more standardised. It uses
helm unittest to valdiate changes as well as implementing a
serviceaccount as best practice. It also goes back to all the default
helm helpers for labels, names etc.
---
 charts/nut-exporter/Chart.yaml                |   2 +-
 charts/nut-exporter/README.md                 |   5 +-
 charts/nut-exporter/templates/_helpers.tpl    |  48 ++++-
 .../templates/configmap-dashboard.yaml        |   2 +-
 charts/nut-exporter/templates/deployment.yaml |   1 +
 charts/nut-exporter/templates/podmonitor.yaml |   2 +-
 .../templates/prometheus-rules.yaml           |   2 +-
 .../templates/serviceaccount.yaml             |  12 ++
 .../__snapshot__/snapshot_test.yaml.snap      | 171 ++++++++++++++++++
 .../nut-exporter/tests/helmlabels_test.yaml   |  23 +++
 charts/nut-exporter/tests/snapshot_test.yaml  |  14 ++
 charts/nut-exporter/values.yaml               |   8 +
 12 files changed, 277 insertions(+), 13 deletions(-)
 create mode 100644 charts/nut-exporter/templates/serviceaccount.yaml
 create mode 100644 charts/nut-exporter/tests/__snapshot__/snapshot_test.yaml.snap
 create mode 100644 charts/nut-exporter/tests/helmlabels_test.yaml
 create mode 100644 charts/nut-exporter/tests/snapshot_test.yaml

diff --git a/charts/nut-exporter/Chart.yaml b/charts/nut-exporter/Chart.yaml
index d729590c7..8fd807adc 100644
--- a/charts/nut-exporter/Chart.yaml
+++ b/charts/nut-exporter/Chart.yaml
@@ -13,5 +13,5 @@ sources:
   - https://github.com/acolombier/nut_exporter/tree/feat/add-helm-chart
 
 type: application
-version: 0.1.0
+version: 0.2.0
 appVersion: 3.0.0
diff --git a/charts/nut-exporter/README.md b/charts/nut-exporter/README.md
index 92ae38f77..a2cbd879e 100644
--- a/charts/nut-exporter/README.md
+++ b/charts/nut-exporter/README.md
@@ -1,6 +1,6 @@
 # nut-exporter
 
-![Version: 0.1.0](https://img.shields.io/badge/Version-0.1.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 3.0.0](https://img.shields.io/badge/AppVersion-3.0.0-informational?style=flat-square)
+![Version: 0.2.0](https://img.shields.io/badge/Version-0.2.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 3.0.0](https://img.shields.io/badge/AppVersion-3.0.0-informational?style=flat-square)
 
 Installs NUT exporter in Kubernetes
 
@@ -34,6 +34,9 @@ Installs NUT exporter in Kubernetes
 | securityContext.allowPrivilegeEscalation | bool | `false` |  |
 | securityContext.capabilities.drop[0] | string | `"ALL"` |  |
 | securityContext.readOnlyRootFilesystem | bool | `true` |  |
+| serviceAccount.annotations | object | `{}` | Annotations to add to the service account |
+| serviceAccount.create | bool | `true` | Specifies whether a service account should be created |
+| serviceAccount.name | string | `""` | The name of the service account to use. If not set and create is true, a name is generated using the fullname template |
 | tolerations | list | `[]` |  |
 
 ----------------------------------------------
diff --git a/charts/nut-exporter/templates/_helpers.tpl b/charts/nut-exporter/templates/_helpers.tpl
index 484493f09..0cf5723f2 100644
--- a/charts/nut-exporter/templates/_helpers.tpl
+++ b/charts/nut-exporter/templates/_helpers.tpl
@@ -1,30 +1,62 @@
 {{/*
-Defining names
+Expand the name of the chart.
 */}}
 {{- define "nutexporter.name" -}}
-{{- .Release.Name }}-nut-exporter
+{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
 {{- end }}
 
-{{- define "nutexporter.fullName" -}}
-{{- .Release.Namespace }}-{{ include "nutexporter.name" . }}
+{{/*
+Create a default fully qualified app name.
+We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
+If release name contains chart name it will be used as a full name.
+*/}}
+{{- define "nutexporter.fullname" -}}
+{{- if .Values.fullnameOverride }}
+{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
+{{- else }}
+{{- $name := default .Chart.Name .Values.nameOverride }}
+{{- if contains $name .Release.Name }}
+{{- .Release.Name | trunc 63 | trimSuffix "-" }}
+{{- else }}
+{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
+{{- end }}
+{{- end }}
 {{- end }}
 
+{{/*
+Create chart name and version as used by the chart label.
+*/}}
+{{- define "nutexporter.chart" -}}
+{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
+{{- end }}
 
 {{/*
 Common labels
 */}}
 {{- define "nutexporter.labels" -}}
+helm.sh/chart: {{ include "nutexporter.chart" . }}
 {{ include "nutexporter.selectorLabels" . }}
+{{- if .Chart.AppVersion }}
+app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
+{{- end }}
 app.kubernetes.io/managed-by: {{ .Release.Service }}
-app.kubernetes.io/part-of: nut-exporter
-version: {{ .Chart.Version }}
 {{- end }}
 
 {{/*
 Selector labels
 */}}
 {{- define "nutexporter.selectorLabels" -}}
-app.kubernetes.io/component: server
+app.kubernetes.io/name: {{ include "nutexporter.name" . }}
 app.kubernetes.io/instance: {{ .Release.Name }}
-app.kubernetes.io/name: nut-exporter
+{{- end }}
+
+{{/*
+Create the name of the service account to use
+*/}}
+{{- define "nutexporter.serviceAccountName" -}}
+{{- if .Values.serviceAccount.create }}
+{{- default (include "nutexporter.fullname" .) .Values.serviceAccount.name }}
+{{- else }}
+{{- default "default" .Values.serviceAccount.name }}
+{{- end }}
 {{- end }}
\ No newline at end of file
diff --git a/charts/nut-exporter/templates/configmap-dashboard.yaml b/charts/nut-exporter/templates/configmap-dashboard.yaml
index acbd2ceeb..ad7eb37d5 100644
--- a/charts/nut-exporter/templates/configmap-dashboard.yaml
+++ b/charts/nut-exporter/templates/configmap-dashboard.yaml
@@ -2,7 +2,7 @@
 apiVersion: v1
 kind: ConfigMap
 metadata:
-  name: {{ include "nutexporter.name" . }}-dashboards
+  name: {{ include "nutexporter.fullname" . }}-dashboards
   labels:
     {{- include "nutexporter.labels" . | nindent 4 }}
     {{- toYaml .Values.dashboard.labels | nindent 4 }}
diff --git a/charts/nut-exporter/templates/deployment.yaml b/charts/nut-exporter/templates/deployment.yaml
index 3b455d6fd..0d90c56b5 100644
--- a/charts/nut-exporter/templates/deployment.yaml
+++ b/charts/nut-exporter/templates/deployment.yaml
@@ -16,6 +16,7 @@ spec:
       labels:
         {{- include "nutexporter.selectorLabels" . | nindent 8 }}
     spec:
+      serviceAccountName: {{ include "nutexporter.serviceAccountName" . }}
       containers:
         - name: nut-exporter
           image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
diff --git a/charts/nut-exporter/templates/podmonitor.yaml b/charts/nut-exporter/templates/podmonitor.yaml
index 3255e5cdf..e6b7f2715 100644
--- a/charts/nut-exporter/templates/podmonitor.yaml
+++ b/charts/nut-exporter/templates/podmonitor.yaml
@@ -7,7 +7,7 @@ metadata:
     {{- with .Values.podMonitor.labels  }}
     {{- toYaml . | nindent 4 }}
     {{- end }}
-  name: {{ include "nutexporter.name" . }}
+  name: {{ include "nutexporter.fullname" . }}
 spec:
   podMetricsEndpoints:
   - interval: 15s
diff --git a/charts/nut-exporter/templates/prometheus-rules.yaml b/charts/nut-exporter/templates/prometheus-rules.yaml
index c7dcfb783..104ba1eef 100644
--- a/charts/nut-exporter/templates/prometheus-rules.yaml
+++ b/charts/nut-exporter/templates/prometheus-rules.yaml
@@ -2,7 +2,7 @@
 apiVersion: monitoring.coreos.com/v1
 kind: PrometheusRule
 metadata:
-  name: {{ include "nutexporter.name" . }}-rules
+  name: {{ include "nutexporter.fullname" . }}-rules
   labels:
     {{- include "nutexporter.labels" . | nindent 4 }}
     {{- with .Values.rules.labels  }}
diff --git a/charts/nut-exporter/templates/serviceaccount.yaml b/charts/nut-exporter/templates/serviceaccount.yaml
new file mode 100644
index 000000000..6e2e2f244
--- /dev/null
+++ b/charts/nut-exporter/templates/serviceaccount.yaml
@@ -0,0 +1,12 @@
+{{- if .Values.serviceAccount.create -}}
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: {{ include "nutexporter.serviceAccountName" . }}
+  labels:
+    {{- include "nutexporter.labels" . | nindent 4 }}
+  {{- with .Values.serviceAccount.annotations }}
+  annotations:
+    {{- toYaml . | nindent 4 }}
+  {{- end }}
+{{- end }}
diff --git a/charts/nut-exporter/tests/__snapshot__/snapshot_test.yaml.snap b/charts/nut-exporter/tests/__snapshot__/snapshot_test.yaml.snap
new file mode 100644
index 000000000..041da4977
--- /dev/null
+++ b/charts/nut-exporter/tests/__snapshot__/snapshot_test.yaml.snap
@@ -0,0 +1,171 @@
+should match basic snapshot:
+  1: |
+    apiVersion: apps/v1
+    kind: Deployment
+    metadata:
+      labels:
+        app.kubernetes.io/instance: RELEASE-NAME
+        app.kubernetes.io/managed-by: Helm
+        app.kubernetes.io/name: nut-exporter
+        app.kubernetes.io/version: 4.5.6
+        helm.sh/chart: nut-exporter-1.2.3
+      name: nut-exporter
+    spec:
+      replicas: 1
+      selector:
+        matchLabels:
+          app.kubernetes.io/instance: RELEASE-NAME
+          app.kubernetes.io/name: nut-exporter
+      strategy:
+        type: Recreate
+      template:
+        metadata:
+          labels:
+            app.kubernetes.io/instance: RELEASE-NAME
+            app.kubernetes.io/name: nut-exporter
+        spec:
+          containers:
+            - env:
+                - name: NUT_EXPORTER_SERVER
+                  value: 192.0.2.1
+              image: ghcr.io/druggeri/nut_exporter:4.5.6
+              imagePullPolicy: IfNotPresent
+              livenessProbe:
+                failureThreshold: 5
+                httpGet:
+                  path: /ups_metrics
+                  port: http
+                initialDelaySeconds: 10
+                timeoutSeconds: 2
+              name: nut-exporter
+              ports:
+                - containerPort: 9199
+                  name: http
+                  protocol: TCP
+              readinessProbe:
+                failureThreshold: 3
+                httpGet:
+                  path: /ups_metrics
+                  port: http
+                initialDelaySeconds: 5
+                timeoutSeconds: 2
+              resources:
+                limits:
+                  cpu: 200m
+                  memory: 128Mi
+                requests:
+                  cpu: 50m
+                  memory: 24Mi
+              securityContext:
+                allowPrivilegeEscalation: false
+                capabilities:
+                  drop:
+                    - ALL
+                readOnlyRootFilesystem: true
+          securityContext:
+            runAsGroup: 3642
+            runAsNonRoot: true
+            runAsUser: 3642
+            seccompProfile:
+              type: RuntimeDefault
+          serviceAccountName: RELEASE-NAME-nut-exporter
+  2: |
+    apiVersion: v1
+    data:
+      nutdashboard.json: ""
+    kind: ConfigMap
+    metadata:
+      labels:
+        app.kubernetes.io/instance: RELEASE-NAME
+        app.kubernetes.io/managed-by: Helm
+        app.kubernetes.io/name: nut-exporter
+        app.kubernetes.io/version: 4.5.6
+        grafana_dashboard: "1"
+        helm.sh/chart: nut-exporter-1.2.3
+      name: RELEASE-NAME-nut-exporter-dashboards
+  3: |
+    apiVersion: monitoring.coreos.com/v1
+    kind: PodMonitor
+    metadata:
+      labels:
+        app.kubernetes.io/instance: RELEASE-NAME
+        app.kubernetes.io/managed-by: Helm
+        app.kubernetes.io/name: nut-exporter
+        app.kubernetes.io/version: 4.5.6
+        helm.sh/chart: nut-exporter-1.2.3
+      name: RELEASE-NAME-nut-exporter
+    spec:
+      jobLabel: nut-exporter
+      namespaceSelector:
+        matchNames:
+          - NAMESPACE
+      podMetricsEndpoints:
+        - interval: 15s
+          path: /ups_metrics
+          port: http
+          scheme: http
+      selector:
+        matchLabels:
+          app.kubernetes.io/instance: RELEASE-NAME
+          app.kubernetes.io/name: nut-exporter
+  4: |
+    apiVersion: monitoring.coreos.com/v1
+    kind: PrometheusRule
+    metadata:
+      labels:
+        app.kubernetes.io/instance: RELEASE-NAME
+        app.kubernetes.io/managed-by: Helm
+        app.kubernetes.io/name: nut-exporter
+        app.kubernetes.io/version: 4.5.6
+        helm.sh/chart: nut-exporter-1.2.3
+      name: RELEASE-NAME-nut-exporter-rules
+    spec:
+      groups:
+        - name: NutExporter
+          rules:
+            - alert: UPSBatteryNeedsReplacement
+              annotations:
+                message: '{{ $labels.ups }} is indicating a need for a battery replacement.'
+              expr: network_ups_tools_ups_status{flag="RB"} != 0
+              for: 60s
+              labels:
+                severity: high
+            - alert: UPSLowBattery
+              annotations:
+                message: '{{ $labels.ups }} has low battery and is running on backup. Expect shutdown soon'
+              expr: network_ups_tools_ups_status{flag="LB"} == 0 and network_ups_tools_ups_status{flag="OL"} == 0
+              for: 60s
+              labels:
+                severity: critical
+            - alert: UPSRuntimeShort
+              annotations:
+                message: '{{ $labels.ups }} has only {{ $value | humanizeDuration}} of battery autonomy'
+              expr: network_ups_tools_battery_runtime < 300
+              for: 30s
+              labels:
+                severity: high
+            - alert: UPSMainPowerOutage
+              annotations:
+                message: '{{ $labels.ups }} has no main power and is running on backup.'
+              expr: network_ups_tools_ups_status{flag="OL"} == 0
+              for: 60s
+              labels:
+                severity: critical
+            - alert: UPSIndicatesWarningStatus
+              annotations:
+                message: '{{ $labels.ups }} is indicating a need for a battery replacement.'
+              expr: network_ups_tools_ups_status{flag="HB"} != 0
+              for: 60s
+              labels:
+                severity: warning
+  5: |
+    apiVersion: v1
+    kind: ServiceAccount
+    metadata:
+      labels:
+        app.kubernetes.io/instance: RELEASE-NAME
+        app.kubernetes.io/managed-by: Helm
+        app.kubernetes.io/name: nut-exporter
+        app.kubernetes.io/version: 4.5.6
+        helm.sh/chart: nut-exporter-1.2.3
+      name: RELEASE-NAME-nut-exporter
diff --git a/charts/nut-exporter/tests/helmlabels_test.yaml b/charts/nut-exporter/tests/helmlabels_test.yaml
new file mode 100644
index 000000000..73dabab0f
--- /dev/null
+++ b/charts/nut-exporter/tests/helmlabels_test.yaml
@@ -0,0 +1,23 @@
+suite: Kubernetes recommendations
+templates:
+  - configmap-dashboard.yaml
+  - deployment.yaml
+  - podmonitor.yaml
+  - prometheus-rules.yaml
+  - serviceaccount.yaml
+tests:
+  - it: should have the kubernetes recommended labels
+    release:
+      name: "test-suite"
+    chart:
+      version: 1.2.3
+    asserts:
+      - equal:
+          path: metadata.labels["app.kubernetes.io/instance"]
+          value: "test-suite"
+      - equal:
+          path: metadata.labels["app.kubernetes.io/managed-by"]
+          value: "Helm"
+      - equal:
+          path: metadata.labels["app.kubernetes.io/name"]
+          value: "nut-exporter"
diff --git a/charts/nut-exporter/tests/snapshot_test.yaml b/charts/nut-exporter/tests/snapshot_test.yaml
new file mode 100644
index 000000000..4d1164a0d
--- /dev/null
+++ b/charts/nut-exporter/tests/snapshot_test.yaml
@@ -0,0 +1,14 @@
+suite: NUT-exporter
+templates:
+  - deployment.yaml
+  - configmap-dashboard.yaml
+  - podmonitor.yaml
+  - prometheus-rules.yaml
+  - serviceaccount.yaml
+tests:
+  - it: should match basic snapshot
+    chart:
+      version: 1.2.3
+      appVersion: 4.5.6
+    asserts:
+      - matchSnapshot: {}
\ No newline at end of file
diff --git a/charts/nut-exporter/values.yaml b/charts/nut-exporter/values.yaml
index 55d1cda0a..026074e9f 100644
--- a/charts/nut-exporter/values.yaml
+++ b/charts/nut-exporter/values.yaml
@@ -88,6 +88,14 @@ tolerations: []
 #   operator: "Exists"
 #   effect: NoSchedule
 
+serviceAccount:
+  # -- Specifies whether a service account should be created
+  create: true
+  # -- Annotations to add to the service account
+  annotations: {}
+  # -- The name of the service account to use. If not set and create is true, a name is generated using the fullname template
+  name: ""
+
 # -- Prometheus rules to trigger alerts from UPS
 rules:
   enabled: true
-- 
GitLab