Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .github/workflows/unittest.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: Test Charts

on:
pull_request:
push:
branches:
- main

jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up Helm
uses: azure/setup-helm@v4
with:
version: v3.15.4

- name: Install helm-unittest plugin
run: helm plugin install https://github.com/helm-unittest/helm-unittest

- name: Lint chart
run: helm lint ./valkey

- name: Run unit tests
run: helm unittest ./valkey
30 changes: 30 additions & 0 deletions Justfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Valkey Helm Chart Tasks

# Run helm-unittest tests
test:
@echo "=== Running Unit Tests ==="
helm unittest ./valkey

# Lint the Helm chart
lint:
@echo "=== Linting Valkey Helm Chart ==="
helm lint ./valkey

# Render templates with default values
template:
helm template valkey ./valkey

# Render templates with auth enabled
template-auth:
helm template valkey ./valkey \
--set auth.enabled=true \
--set auth.generateDefaultUser.enabled=true

# Package the chart
package:
helm package ./valkey

# Run all validations
validate: lint test
@echo "=== All validations passed ==="

5 changes: 4 additions & 1 deletion valkey/.helmignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,7 @@
*.tmproj
.vscode/

.github/
.github/

# Unit tests (only ignore root-level tests/, not templates/tests/)
/tests/
30 changes: 30 additions & 0 deletions valkey/templates/_helpers.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,33 @@ Creating Image Pull Secrets
{{- end }}
{{- end }}

{{/*
Validate auth configuration
*/}}
{{- define "valkey.validateAuthConfig" -}}
{{- if .Values.auth.enabled }}
{{- $methodCount := 0 }}
{{- if .Values.auth.generateDefaultUser.enabled }}
{{- $methodCount = add $methodCount 1 }}
{{- end }}
{{- if .Values.auth.existingSecret }}
{{- $methodCount = add $methodCount 1 }}
{{- end }}
{{- /* Check if aclConfig has actual content (not just comments/whitespace) */}}
{{- if .Values.auth.aclConfig }}
{{- $trimmed := .Values.auth.aclConfig | trim }}
{{- /* Use regex to check for any non-empty, non-comment line */}}
Copy link

Copilot AI Oct 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The regex pattern uses (?m) multiline flag but could be simplified. Consider using a more readable approach or add a comment explaining the regex logic for maintainability.

Suggested change
{{- /* Use regex to check for any non-empty, non-comment line */}}
{{- /* Use regex to check for any non-empty, non-comment line */}}
{{- /*
The regex pattern "(?m)^(\s*[^#\s].*)$" uses the multiline flag (?m) so that ^ and $ match the start/end of each line.
It matches any line that is not just whitespace and does not start with a '#' (comment).
This ensures aclConfig contains at least one meaningful, non-comment line.
*/}}

Copilot uses AI. Check for mistakes.
{{- $hasContent := regexMatch "(?m)^(\s*[^#\s].*)$" $trimmed }}
{{- if $hasContent }}
{{- $methodCount = add $methodCount 1 }}
{{- end }}
{{- end }}
{{- if eq $methodCount 0 }}
{{- fail "auth.enabled is true but no authentication method is configured. Please enable one of: auth.generateDefaultUser.enabled, auth.existingSecret, or provide auth.aclConfig" }}
{{- end }}
{{- if gt $methodCount 1 }}
{{- fail "Multiple authentication methods are enabled. Please enable only ONE of: auth.generateDefaultUser.enabled, auth.existingSecret, or auth.aclConfig" }}
{{- end }}
{{- end }}
{{- end }}

16 changes: 16 additions & 0 deletions valkey/templates/deploy_valkey.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{{- $fullname := include "valkey.fullname" . }}
{{- $storage := .Values.dataStorage }}
{{- $createPVC := and $storage.enabled (not (empty $storage.requestedSize)) (empty $storage.persistentVolumeClaimName) }}
{{- include "valkey.validateAuthConfig" . }}
apiVersion: apps/v1
kind: Deployment
metadata:
Expand Down Expand Up @@ -57,6 +58,11 @@ spec:
- name: extravalkeyconfigs-volume
mountPath: /extravalkeyconfigs
{{- end }}
{{- if and .Values.auth.enabled (or .Values.auth.generateDefaultUser.enabled .Values.auth.existingSecret) }}
- name: valkey-auth
mountPath: /valkey-auth
readOnly: true
{{- end }}
{{- with .Values.initResources }}
resources:
{{- toYaml . | nindent 12 }}
Expand Down Expand Up @@ -133,6 +139,16 @@ spec:
defaultMode: {{ .defaultMode | default 0440 }}
{{- end }}
{{- end }}
{{- if and .Values.auth.enabled (or .Values.auth.generateDefaultUser.enabled .Values.auth.existingSecret) }}
- name: valkey-auth
secret:
{{- if .Values.auth.generateDefaultUser.enabled }}
secretName: {{ include "valkey.fullname" . }}-auth
{{- else }}
secretName: {{ .Values.auth.existingSecret }}
{{- end }}
defaultMode: 0440
{{- end }}
- name: {{ $storage.volumeName }}
{{- if $storage.persistentVolumeClaimName }}
persistentVolumeClaim:
Expand Down
12 changes: 12 additions & 0 deletions valkey/templates/init_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,22 @@ data:

{{- if .Values.auth.enabled }}
echo "aclfile /data/conf/users.acl" >>"$VALKEY_CONFIG"
{{- if or .Values.auth.generateDefaultUser.enabled .Values.auth.existingSecret }}
# Copy ACL from mounted secret
if [ -f /valkey-auth/users.acl ]; then
log "Using ACL configuration from secret"
cp /valkey-auth/users.acl /data/conf/users.acl
else
log "ERROR: auth.enabled is true but no ACL file found in secret"
exit 1
fi
{{- else }}
# Use inline ACL configuration
cat <<EOF > /data/conf/users.acl
{{ .Values.auth.aclConfig | indent 6 }}
EOF
{{- end }}
{{- end }}

# Append extra configs if present
if [ -f /usr/local/etc/valkey/valkey.conf ]; then
Expand Down
21 changes: 21 additions & 0 deletions valkey/templates/secret.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{{- if and .Values.auth.enabled .Values.auth.generateDefaultUser.enabled }}
apiVersion: v1
kind: Secret
metadata:
name: {{ include "valkey.fullname" . }}-auth
labels:
{{- include "valkey.labels" . | nindent 4 }}
type: Opaque
data:
{{- $password := .Values.auth.generateDefaultUser.password }}
{{- if not $password }}
{{- $existingSecret := (lookup "v1" "Secret" .Release.Namespace (printf "%s-auth" (include "valkey.fullname" .))) }}
{{- if and $existingSecret $existingSecret.data $existingSecret.data.password }}
{{- $password = ($existingSecret.data.password | b64dec) }}
{{- else }}
{{- $password = randAlphaNum 32 }}
{{- end }}
{{- end }}
password: {{ $password | b64enc | quote }}
users.acl: {{ printf "user %s on >%s %s" .Values.auth.generateDefaultUser.username $password .Values.auth.generateDefaultUser.permissions | b64enc | quote }}
{{- end }}
120 changes: 120 additions & 0 deletions valkey/templates/tests/auth.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
{{- if and .Values.auth.enabled .Values.auth.generateDefaultUser.enabled }}
apiVersion: v1
kind: Pod
metadata:
name: {{ include "valkey.fullname" . }}-test-auth-generated
labels:
{{- include "valkey.labels" . | nindent 4 }}
annotations:
"helm.sh/hook": test
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
spec:
restartPolicy: Never
containers:
- name: test-auth
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
command:
- sh
- -c
- |
set -e
echo "Testing authentication with generated default user..."
# Extract password from secret
PASSWORD=$(cat /valkey-auth/password)
# Test authentication
valkey-cli -h {{ include "valkey.fullname" . }} -p {{ .Values.service.port }} --user {{ .Values.auth.generateDefaultUser.username }} --pass "$PASSWORD" PING
# Test that we can set and get a value
valkey-cli -h {{ include "valkey.fullname" . }} -p {{ .Values.service.port }} --user {{ .Values.auth.generateDefaultUser.username }} --pass "$PASSWORD" SET test-key-generated "test-value"
VALUE=$(valkey-cli -h {{ include "valkey.fullname" . }} -p {{ .Values.service.port }} --user {{ .Values.auth.generateDefaultUser.username }} --pass "$PASSWORD" GET test-key-generated)
if [ "$VALUE" = "test-value" ]; then
echo "✓ Authentication test passed (generated user)"
exit 0
else
echo "✗ Authentication test failed: expected 'test-value', got '$VALUE'"
exit 1
fi
volumeMounts:
- name: valkey-auth
mountPath: /valkey-auth
readOnly: true
volumes:
- name: valkey-auth
secret:
secretName: {{ include "valkey.fullname" . }}-auth
{{- end }}
---
{{- if and .Values.auth.enabled .Values.auth.existingSecret }}
apiVersion: v1
kind: Pod
metadata:
name: {{ include "valkey.fullname" . }}-test-auth-existing
labels:
{{- include "valkey.labels" . | nindent 4 }}
annotations:
"helm.sh/hook": test
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
spec:
restartPolicy: Never
containers:
- name: test-auth
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
command:
- sh
- -c
- |
set -e
echo "Testing authentication with existing secret..."
# Verify ACL file exists
if [ ! -f /valkey-auth/users.acl ]; then
echo "✗ ACL file not found in existing secret"
exit 1
fi
# Extract first user and password from ACL file (basic parsing)
# Expected format: user <username> on ><password> ...
ACL_CONTENT=$(cat /valkey-auth/users.acl)
echo "ACL content preview: $(echo "$ACL_CONTENT" | head -c 100)"
# Test basic connection (no auth - will fail if auth is properly configured)
if valkey-cli -h {{ include "valkey.fullname" . }} -p {{ .Values.service.port }} PING 2>/dev/null; then
echo "✗ Authentication test failed: server allows unauthenticated access"
exit 1
fi
echo "✓ Authentication is enforced (unauthenticated access denied)"
# If password key exists in secret, test with it
if [ -f /valkey-auth/password ]; then
PASSWORD=$(cat /valkey-auth/password)
Copy link

Copilot AI Oct 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The USERNAME variable is used but never defined in this context. This appears to be a shell variable that should either be defined or the fallback logic should be clarified.

Suggested change
PASSWORD=$(cat /valkey-auth/password)
PASSWORD=$(cat /valkey-auth/password)
# Extract the first username from the ACL file, fallback to "default" if not found
USERNAME=$(awk '/^user / {print $2; exit}' /valkey-auth/users.acl)

Copilot uses AI. Check for mistakes.
USERNAME="${USERNAME:-default}"
if valkey-cli -h {{ include "valkey.fullname" . }} -p {{ .Values.service.port }} --user "$USERNAME" --pass "$PASSWORD" PING; then
echo "✓ Authentication test passed (existing secret with password)"
# Test write operation if permissions allow
valkey-cli -h {{ include "valkey.fullname" . }} -p {{ .Values.service.port }} --user "$USERNAME" --pass "$PASSWORD" SET test-key-existing "test-value" || echo "⚠ Write operation not permitted (may be expected based on ACL)"
exit 0
else
echo "✗ Authentication failed with provided credentials"
exit 1
fi
else
echo "✓ ACL file verified, but no password file for testing"
echo "⚠ Manual verification recommended for existing secret configuration"
exit 0
fi
volumeMounts:
- name: valkey-auth
mountPath: /valkey-auth
readOnly: true
volumes:
- name: valkey-auth
secret:
secretName: {{ .Values.auth.existingSecret }}
{{- end }}
Loading
Loading