From b5eb61a43d3520a8bbf17a1e06477fe8a07ebe29 Mon Sep 17 00:00:00 2001 From: Jansen Fuller Date: Tue, 9 Jun 2026 23:41:50 +0000 Subject: [PATCH] Adding health probes, resource requests and fixing container permissions (#210) * Adding health probes and fixing container permissions * Bump version * Cleanup * No ServiceAccount anymore * No ServiceAccount anymore * Update pull secrets * Fixing yaml issue * Fixed resource requests and limits * Bumping version --- .github/workflows/helm-test.yml | 2 - helm/taskchampion-sync-server/.helmignore | 4 +- helm/taskchampion-sync-server/Chart.yaml | 2 +- .../examples/postgres-values.yaml | 11 ++-- .../examples/sqlite-values.yaml | 9 ++- .../templates/NOTES.txt | 46 +++++++++++++++ .../templates/_helpers.tpl | 8 +-- .../templates/deployment.yaml | 44 ++++++++++---- .../templates/ingress.yaml | 29 ++++++++- .../templates/serviceaccount.yaml | 38 ------------ .../templates/validation-config.yaml | 7 +++ helm/taskchampion-sync-server/values.yaml | 59 +++++++++++++------ 12 files changed, 172 insertions(+), 87 deletions(-) create mode 100644 helm/taskchampion-sync-server/templates/NOTES.txt delete mode 100644 helm/taskchampion-sync-server/templates/serviceaccount.yaml diff --git a/.github/workflows/helm-test.yml b/.github/workflows/helm-test.yml index 111bb2d..3b54c0f 100644 --- a/.github/workflows/helm-test.yml +++ b/.github/workflows/helm-test.yml @@ -49,7 +49,6 @@ jobs: grep -q 'kind: Deployment' /tmp/helm-sqlite.yaml grep -q 'kind: Service' /tmp/helm-sqlite.yaml grep -q 'kind: Ingress' /tmp/helm-sqlite.yaml - grep -q 'kind: ServiceAccount' /tmp/helm-sqlite.yaml # Verify SQLite-specific rendering grep -q 'emptyDir' /tmp/helm-sqlite.yaml @@ -82,7 +81,6 @@ jobs: grep -q 'kind: Deployment' /tmp/helm-postgres.yaml grep -q 'kind: Service' /tmp/helm-postgres.yaml grep -q 'kind: Secret' /tmp/helm-postgres.yaml - grep -q 'kind: ServiceAccount' /tmp/helm-postgres.yaml # Verify PostgreSQL-specific rendering grep -q 'kind: HTTPRoute' /tmp/helm-postgres.yaml diff --git a/helm/taskchampion-sync-server/.helmignore b/helm/taskchampion-sync-server/.helmignore index 6c3a50f..1a65796 100644 --- a/helm/taskchampion-sync-server/.helmignore +++ b/helm/taskchampion-sync-server/.helmignore @@ -1,5 +1,5 @@ # Patterns to ignore when building the Helm chart .git -.gitignore -*.md examples/ +.DS_Store +*.tgz diff --git a/helm/taskchampion-sync-server/Chart.yaml b/helm/taskchampion-sync-server/Chart.yaml index a7ac268..0506d22 100644 --- a/helm/taskchampion-sync-server/Chart.yaml +++ b/helm/taskchampion-sync-server/Chart.yaml @@ -2,7 +2,7 @@ apiVersion: v2 name: taskchampion-sync-server description: A Helm chart for deploying TaskChampion Sync Server on Kubernetes type: application -version: 0.1.2 +version: 0.2.0 appVersion: "0.7.0" keywords: - taskchampion diff --git a/helm/taskchampion-sync-server/examples/postgres-values.yaml b/helm/taskchampion-sync-server/examples/postgres-values.yaml index ed2ae0b..1e2dbf3 100644 --- a/helm/taskchampion-sync-server/examples/postgres-values.yaml +++ b/helm/taskchampion-sync-server/examples/postgres-values.yaml @@ -10,9 +10,12 @@ postgres: clientIdSecret: "taskchampion-client-ids" env: - RUST_LOG: debug - LISTEN: "0.0.0.0:8080" - CREATE_CLIENTS: "false" + - name: RUST_LOG + value: debug + - name: LISTEN + value: "0.0.0.0:8080" + - name: CREATE_CLIENTS + value: "false" replicas: enabled: true @@ -20,7 +23,7 @@ replicas: image: pullSecrets: - - my-registry-secret + - name: my-registry-secret httpRoute: enabled: true diff --git a/helm/taskchampion-sync-server/examples/sqlite-values.yaml b/helm/taskchampion-sync-server/examples/sqlite-values.yaml index 9159979..6beee3c 100644 --- a/helm/taskchampion-sync-server/examples/sqlite-values.yaml +++ b/helm/taskchampion-sync-server/examples/sqlite-values.yaml @@ -12,9 +12,12 @@ postgres: clientIdSecret: "taskchampion-client-ids" env: - RUST_LOG: info - LISTEN: "0.0.0.0:8080" - CREATE_CLIENTS: "false" + - name: RUST_LOG + value: info + - name: LISTEN + value: "0.0.0.0:8080" + - name: CREATE_CLIENTS + value: "false" ingress: enabled: true diff --git a/helm/taskchampion-sync-server/templates/NOTES.txt b/helm/taskchampion-sync-server/templates/NOTES.txt new file mode 100644 index 0000000..6ede624 --- /dev/null +++ b/helm/taskchampion-sync-server/templates/NOTES.txt @@ -0,0 +1,46 @@ +Thank you for installing {{ .Chart.Name }} — version {{ .Chart.Version }} (app: {{ .Chart.AppVersion }}). + +## Storage Backend + +{{- if eq .Values.sqlite.enabled true }} +**SQLite** — Data is stored at `{{ .Values.sqlite.dataDir }}`. +{{- if .Values.sqlite.persistence.enabled }} + PersistentVolumeClaim: `{{ include "taskchampion-sync-server.fullname" . }}-pvc` +{{- else if .Values.sqlite.existingPV }} + Using existing PVC: `{{ .Values.sqlite.existingPV }}` +{{- else }} + Using an emptyDir volume (ephemeral — data is lost on pod restart). + Set `sqlite.persistence.enabled=true` for persistent storage. +{{- end }} +{{- end }} + +{{- if eq .Values.postgres.enabled true }} +**PostgreSQL** — Connection URI stored in Secret `{{ include "taskchampion-sync-server.postgres-secret-name" . }}`. + Host: {{ .Values.postgres.host }}:{{ .Values.postgres.port }} + Database: {{ .Values.postgres.database }} +{{- end }} + +## Verify the Deployment + + kubectl get pods -l {{ include "taskchampion-sync-server.selectorLabels" . | replace ": " "=" | replace "\n" "," | trimSuffix "," }} + +## Check Pod Logs + + kubectl logs -l {{ include "taskchampion-sync-server.selectorLabels" . | replace ": " "=" | replace "\n" "," | trimSuffix "," }} + +## Test the API + + kubectl port-forward svc/{{ include "taskchampion-sync-server.fullname" . }} {{ .Values.service.port }}:{{ .Values.service.port }} + curl http://localhost:{{ .Values.service.port }}/ + +## View Connection Secret (PostgreSQL only) + +{{- if eq .Values.postgres.enabled true }} + kubectl get secret {{ include "taskchampion-sync-server.postgres-secret-name" . }} -o jsonpath="{.data.connection}" | base64 -d +{{- end }} + +## Configuration + +For full configuration options, see: + + https://github.com/GothenburgBitFactory/taskchampion-sync-server diff --git a/helm/taskchampion-sync-server/templates/_helpers.tpl b/helm/taskchampion-sync-server/templates/_helpers.tpl index 1b04f91..2e8e33a 100644 --- a/helm/taskchampion-sync-server/templates/_helpers.tpl +++ b/helm/taskchampion-sync-server/templates/_helpers.tpl @@ -23,6 +23,7 @@ taskchampion-sync-server helpers helm.sh/chart: {{ printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} app.kubernetes.io/name: {{ include "taskchampion-sync-server.name" . }} app.kubernetes.io/instance: {{ .Release.Name }} +app.kubernetes.io/component: server app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} app.kubernetes.io/managed-by: {{ .Release.Service }} {{- end }} @@ -32,13 +33,6 @@ app.kubernetes.io/name: {{ include "taskchampion-sync-server.name" . }} app.kubernetes.io/instance: {{ .Release.Name }} {{- end }} -{{- define "taskchampion-sync-server.serviceAccountName" -}} -{{- if .Values.serviceAccount.create }} -{{- default (include "taskchampion-sync-server.fullname" .) .Values.serviceAccount.name }} -{{- else }} -{{- default "default" .Values.serviceAccount.name }} -{{- end }} -{{- end }} {{- define "taskchampion-sync-server.postgres-connection" -}} {{- $host := .Values.postgres.host -}} diff --git a/helm/taskchampion-sync-server/templates/deployment.yaml b/helm/taskchampion-sync-server/templates/deployment.yaml index 2ee7409..9c2a2da 100644 --- a/helm/taskchampion-sync-server/templates/deployment.yaml +++ b/helm/taskchampion-sync-server/templates/deployment.yaml @@ -18,10 +18,10 @@ spec: labels: {{- include "taskchampion-sync-server.selectorLabels" . | nindent 8 }} spec: - {{- if .Values.serviceAccount.create }} - serviceAccountName: {{ include "taskchampion-sync-server.fullname" . }} - {{- else if .Values.serviceAccount.name }} - serviceAccountName: {{ .Values.serviceAccount.name }} + + {{- with .Values.image.pullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} {{- end }} securityContext: {{- toYaml .Values.securityContext | nindent 8 }} @@ -32,6 +32,8 @@ spec: imagePullPolicy: {{ .Values.postgres.initContainer.imagePullPolicy }} securityContext: allowPrivilegeEscalation: false + runAsNonRoot: true + runAsUser: 999 env: - name: PGURI valueFrom: @@ -90,11 +92,18 @@ spec: imagePullPolicy: {{ .Values.image.pullPolicy }} securityContext: allowPrivilegeEscalation: false + runAsNonRoot: true + runAsUser: 1092 + readOnlyRootFilesystem: true + {{- if eq .Values.postgres.enabled true }} + command: + - /bin/taskchampion-sync-server-postgres + {{- else }} + command: + - /bin/taskchampion-sync-server + {{- end }} env: - {{- range $name, $value := .Values.env }} - - name: {{ $name }} - value: {{ $value | quote }} - {{- end }} + {{- toYaml .Values.env | nindent 12 }} {{- if .Values.clientIdSecret }} - name: CLIENT_ID valueFrom: @@ -117,9 +126,24 @@ spec: - name: http containerPort: {{ .Values.service.targetPort }} protocol: TCP - {{- with .Values.resources }} + livenessProbe: + httpGet: + path: / + port: http + initialDelaySeconds: 5 + periodSeconds: 10 + readinessProbe: + httpGet: + path: / + port: http + initialDelaySeconds: 5 + periodSeconds: 10 + {{- if eq .Values.postgres.enabled true }} resources: - {{- toYaml . | nindent 12 }} + {{- toYaml .Values.postgres.resources | nindent 12 }} + {{- else }} + resources: + {{- toYaml .Values.sqlite.resources | nindent 12 }} {{- end }} {{- if eq .Values.sqlite.enabled true }} volumeMounts: diff --git a/helm/taskchampion-sync-server/templates/ingress.yaml b/helm/taskchampion-sync-server/templates/ingress.yaml index 8565711..c33db5e 100644 --- a/helm/taskchampion-sync-server/templates/ingress.yaml +++ b/helm/taskchampion-sync-server/templates/ingress.yaml @@ -18,7 +18,10 @@ spec: {{- toYaml .Values.ingress.tls | nindent 4 }} {{- end }} rules: + {{- $svcName := include "taskchampion-sync-server.fullname" . -}} + {{- $svcPort := $.Values.service.port -}} {{- range .Values.ingress.hosts }} + {{- if kindIs "string" . }} - host: {{ . | quote }} http: paths: @@ -26,8 +29,30 @@ spec: pathType: Prefix backend: service: - name: {{ include "taskchampion-sync-server.fullname" $ }} + name: {{ $svcName }} port: - number: {{ $.Values.service.port }} + number: {{ $svcPort }} + {{- else }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path | default "/" | quote }} + pathType: {{ .pathType | default "Prefix" }} + backend: + service: + name: {{ $svcName }} + port: + number: {{ $svcPort }} + {{- else }} + - path: / + pathType: Prefix + backend: + service: + name: {{ $svcName }} + port: + number: {{ $svcPort }} + {{- end }} + {{- end }} {{- end }} {{- end }} diff --git a/helm/taskchampion-sync-server/templates/serviceaccount.yaml b/helm/taskchampion-sync-server/templates/serviceaccount.yaml deleted file mode 100644 index d4742a8..0000000 --- a/helm/taskchampion-sync-server/templates/serviceaccount.yaml +++ /dev/null @@ -1,38 +0,0 @@ -{{- if .Values.serviceAccount.create -}} -apiVersion: v1 -kind: ServiceAccount -metadata: - name: {{ include "taskchampion-sync-server.fullname" . }} - labels: - {{- include "taskchampion-sync-server.labels" . | nindent 4 }} - {{- with .Values.serviceAccount.annotations }} - annotations: - {{- toYaml . | nindent 4 }} - {{- end }} ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - name: {{ include "taskchampion-sync-server.fullname" . }}-secret-manager - labels: - {{- include "taskchampion-sync-server.labels" . | nindent 4 }} -rules: -- apiGroups: [""] - resources: ["secrets"] - verbs: ["create", "get", "update", "patch"] ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: {{ include "taskchampion-sync-server.fullname" . }}-secret-binding - labels: - {{- include "taskchampion-sync-server.labels" . | nindent 4 }} -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: Role - name: {{ include "taskchampion-sync-server.fullname" . }}-secret-manager -subjects: -- kind: ServiceAccount - name: {{ include "taskchampion-sync-server.fullname" . }} - namespace: {{ .Release.Namespace }} -{{- end -}} \ No newline at end of file diff --git a/helm/taskchampion-sync-server/templates/validation-config.yaml b/helm/taskchampion-sync-server/templates/validation-config.yaml index 9add1b5..75e2b9e 100644 --- a/helm/taskchampion-sync-server/templates/validation-config.yaml +++ b/helm/taskchampion-sync-server/templates/validation-config.yaml @@ -1,3 +1,10 @@ +{{- /* Validate imagePullSecrets entries are objects (not plain strings) */ -}} +{{- range $i, $secret := .Values.image.pullSecrets -}} + {{- if not (kindIs "map" $secret) -}} + {{- fail (printf "ERROR: image.pullSecrets[%d] is a string (%q). Each entry must be an object with a 'name' key, e.g.\n image:\n pullSecrets:\n - name: %s" $i $secret $secret) -}} + {{- end -}} +{{- end -}} + {{- if and (eq .Values.sqlite.enabled false) (eq .Values.postgres.enabled false) -}} {{- fail "ERROR: Exactly one storage backend must be enabled. Either sqlite.enabled or postgres.enabled must be true." -}} {{- end -}} diff --git a/helm/taskchampion-sync-server/values.yaml b/helm/taskchampion-sync-server/values.yaml index 1c07ab3..8191727 100644 --- a/helm/taskchampion-sync-server/values.yaml +++ b/helm/taskchampion-sync-server/values.yaml @@ -9,6 +9,9 @@ image: tag: "0.7.0" pullPolicy: IfNotPresent pullSecrets: [] + # pullSecrets expects a list of objects with a "name" key: + # pullSecrets: + # - name: my-registry-cred # Existing secret containing client IDs (comma-separated UUIDs) # Expected key: client-ids @@ -16,10 +19,19 @@ clientIdSecret: "" # Environment variables passed directly to the container # NOTE: DATA_DIR and CONNECTION are set automatically based on backend +# To add secret/configMap references, use valueFrom: +# - name: MY_SECRET_VAR +# valueFrom: +# secretKeyRef: +# name: my-secret +# key: my-key env: - RUST_LOG: info - LISTEN: "0.0.0.0:8080" - CREATE_CLIENTS: "true" + - name: RUST_LOG + value: info + - name: LISTEN + value: "0.0.0.0:8080" + - name: CREATE_CLIENTS + value: "true" # Service configuration service: @@ -28,6 +40,14 @@ service: targetPort: 8080 # Ingress configuration +# Each host can be a string (simple) or a map with path rules: +# hosts: +# - host: app.example.com +# paths: +# - path: / +# pathType: Prefix +# - path: /api +# pathType: Exact ingress: enabled: false className: "" @@ -71,15 +91,6 @@ httpRoute: path: "/" port: 8080 -# Resource limits and requests -resources: {} - # limits: - # memory: 128Mi - # cpu: 100m - # requests: - # memory: 64Mi - # cpu: 50m - # Replica configuration (only applies when postgres is enabled) replicas: enabled: false @@ -96,6 +107,15 @@ securityContext: sqlite: enabled: false dataDir: /var/lib/taskchampion-sync-server/data + # Resource limits and requests + resources: + limits: + memory: 25Mi + cpu: 100m + requests: + # 5Mi is sufficient for the SQLite image + memory: 5Mi + cpu: 10m existingPV: "" emptyDir: sizeLimit: "" @@ -107,12 +127,6 @@ sqlite: storageClass: "" existingClaim: "" -# Service account configuration -serviceAccount: - create: true # Default: automatically create service account - name: "" # Optional: use existing service account - annotations: {} - # PostgreSQL configuration postgres: enabled: false @@ -134,3 +148,12 @@ postgres: # Override the schema URL. Defaults to the official schema for this chart's appVersion. # e.g. https://raw.githubusercontent.com/GothenburgBitFactory/taskchampion-sync-server/v0.7.0/postgres/schema.sql schemaUrl: "" + # Resource limits and requests + resources: + limits: + memory: 100Mi + cpu: 100m + requests: + # 20Mi is the minimum for the Postgres image + memory: 20Mi + cpu: 10m