Reloader E2E Tests
These tests verify that Reloader actually works in a real Kubernetes cluster. They spin up a Kind cluster, build and deploy Reloader, then create workloads and change their ConfigMaps/Secrets to make sure everything reloads correctly.
Running the Tests
# Run everything (creates Kind cluster, builds image, runs tests)
make e2e
# Test a specific image without building
SKIP_BUILD=true RELOADER_IMAGE=stakater/reloader:v1.0.0 make e2e
# Run just one test suite
go test -v -timeout 30m ./test/e2e/core/...
go test -v -timeout 30m ./test/e2e/annotations/...
go test -v -timeout 30m ./test/e2e/flags/...
# Skip Argo/OpenShift tests (if you don't have them installed)
go test -v ./test/e2e/core/... --ginkgo.label-filter="!argo && !openshift"
What You Need
- Go 1.21+
- Docker
- Kind
- kubectl
- Helm 3
- Argo Rollouts (optional, for Argo tests)
- OpenShift (optional, for DeploymentConfig tests)
What Gets Tested
Deployments
Deployments are the most thoroughly tested workload. Here's everything we verify:
Basic Reload Behavior
- Reloads when a referenced ConfigMap's data changes
- Reloads when a referenced Secret's data changes
- Reloads when using
auto=trueannotation (auto-detects all mounted ConfigMaps/Secrets) - Does NOT reload when only ConfigMap/Secret labels change (data must change)
- Does NOT reload when
auto=falseis set
Different Ways to Reference ConfigMaps/Secrets
envFrom- inject all keys as environment variablesvalueFrom.configMapKeyRef- single key as env varvalueFrom.secretKeyRef- single key as env var- Volume mounts - mount ConfigMap/Secret as files
- Projected volumes - multiple sources combined into one mount
- Init containers with envFrom
- Init containers with volume mounts
Annotation Variations
configmap.reloader.stakater.com/reload: my-config- explicit ConfigMapsecret.reloader.stakater.com/reload: my-secret- explicit Secretreloader.stakater.com/auto: "true"- auto-detect everythingconfigmap.reloader.stakater.com/auto: "true"- auto-detect only ConfigMapssecret.reloader.stakater.com/auto: "true"- auto-detect only Secrets- Multiple ConfigMaps/Secrets in one annotation (comma-separated)
- Annotations on pod template vs deployment metadata (both work)
Search & Match
- Deployments with
searchannotation find ConfigMaps withmatchannotation - Only reloads if both sides have the right annotations
Exclude & Ignore
- Exclude specific ConfigMaps/Secrets from auto-reload
- Ignore annotation on ConfigMap/Secret prevents any reload
Pause Period
- Deployment gets paused after reload when pause-period annotation is set
Regex Patterns
- Pattern matching for ConfigMap/Secret names (e.g.,
app-config-.*)
Multi-Container
- Works when multiple containers share the same ConfigMap
- Works when different containers use different ConfigMaps
EnvVars Strategy
- Adds
STAKATER_environment variables instead of pod annotations - Verifies the env var appears after ConfigMap/Secret change
DaemonSets
DaemonSets get the same treatment as Deployments:
- Reloads when ConfigMap data changes
- Reloads when Secret data changes
- Works with
auto=trueannotation - Does NOT reload on label-only changes
- Supports all reference methods (envFrom, valueFrom, volumes, projected, init containers)
- EnvVars strategy works
StatefulSets
StatefulSets are tested identically to Deployments and DaemonSets:
- Reloads when ConfigMap data changes
- Reloads when Secret data changes
- Works with
auto=trueannotation - Does NOT reload on label-only changes
- Supports all reference methods
- EnvVars strategy works
CronJobs
CronJobs are a bit special - when a CronJob's ConfigMap changes, Reloader updates the CronJob spec so the next Job it creates will have the new config.
What's Tested
- CronJob spec updates when referenced ConfigMap changes
- CronJob spec updates when referenced Secret changes
- Works with
auto=trueannotation - Works with explicit reload annotations
- Does NOT update on label-only changes
Note: CronJobs don't support the EnvVars strategy since they don't have running pods to inject env vars into.
Jobs
Jobs require special handling - since you can't modify a running Job, Reloader deletes and recreates it with the new config.
What's Tested
- Job gets recreated (new UID) when ConfigMap changes
- Job gets recreated when Secret changes
- Works with
auto=trueannotation - Works with explicit reload annotations
- Works with
valueFrom.configMapKeyRefreferences - Works with
valueFrom.secretKeyRefreferences
Note: Jobs don't support the EnvVars strategy.
Argo Rollouts
Argo Rollouts are Kubernetes Deployments on steroids with advanced deployment strategies. Tests require Argo Rollouts to be installed.
What's Tested
- Reloads when ConfigMap data changes
- Reloads when Secret data changes
- Works with
auto=trueannotation - Does NOT reload on label-only changes
- Default strategy (annotation-based, like Deployments)
- Restart strategy (sets
spec.restartAtfield instead of annotations) - Supports all reference methods
- EnvVars strategy works
DeploymentConfigs (OpenShift)
OpenShift's legacy workload type. Tests only run on OpenShift clusters.
What's Tested
- Reloads when ConfigMap data changes
- Reloads when Secret data changes
- Works with
auto=trueannotation - Does NOT reload on label-only changes
- Supports all reference methods
- EnvVars strategy works
CLI Flag Tests
These tests verify Reloader's command-line options work correctly. Each test deploys Reloader with different flags.
Namespace Filtering
namespaceSelector
- Only watches namespaces with matching labels
- Ignores ConfigMap changes in non-matching namespaces
ignoreNamespaces
- Skips specified namespaces entirely
- Still watches all other namespaces
watchGlobally
true(default): watches all namespacesfalse: only watches Reloader's own namespace
Resource Filtering
resourceLabelSelector
- Only watches ConfigMaps/Secrets with matching labels
- Ignores changes to resources without the label
ignoreSecrets
- Completely ignores all Secret changes
- Still watches ConfigMaps
ignoreConfigMaps
- Completely ignores all ConfigMap changes
- Still watches Secrets
Workload Filtering
ignoreCronJobs
- Skips CronJobs, still handles Deployments/etc
ignoreJobs
- Skips Jobs, still handles other workloads
Reload Triggers
reloadOnCreate
true: triggers reload when a new ConfigMap/Secret is createdfalse(default): only triggers on updates
reloadOnDelete
true: triggers reload when a ConfigMap/Secret is deletedfalse(default): only triggers on updates
Global Auto-Reload
autoReloadAll
true: all workloads auto-reload without needing annotationsauto=falseon a workload still opts it out
Annotation-Specific Tests
Auto Reload Variations
reloader.stakater.com/auto: "true"- watches both ConfigMaps and Secretsreloader.stakater.com/auto: "false"- completely disables reloadconfigmap.reloader.stakater.com/auto: "true"- only watches ConfigMapssecret.reloader.stakater.com/auto: "true"- only watches Secrets
Combining Annotations
auto=true+ explicit reload annotation work together- Auto-detected resources + explicitly listed resources both trigger reload
- Exclude annotations override auto-detection
Search & Match
The search/match system lets you decouple workloads from specific resource names:
- Workload has
reloader.stakater.com/search: "true" - ConfigMap has
reloader.stakater.com/match: "true" - When ConfigMap changes, workload reloads
Tests verify:
- Reload happens when both annotations present
- No reload when workload has search but ConfigMap lacks match
- No reload when ConfigMap has match but no workload has search
- Multiple workloads can have search, only ones with search reload
Exclude Annotations
Exclude specific resources from auto-reload:
configmap.reloader.stakater.com/exclude: "config-to-skip"secret.reloader.stakater.com/exclude: "secret-to-skip"
Tests verify:
- Excluded ConfigMap changes don't trigger reload
- Non-excluded ConfigMap changes still trigger reload
- Same behavior for Secrets
Resource Ignore
Put this on the ConfigMap/Secret itself to prevent any reload:
reloader.stakater.com/ignore: "true"
Tests verify:
- ConfigMap with ignore annotation never triggers reload
- Secret with ignore annotation never triggers reload
- Even with explicit reload annotation on workload
Pause Period
Delay between detecting change and triggering reload:
reloader.stakater.com/pause-period: "10s"
Tests verify:
- Deployment gets paused-at annotation after reload
- Without pause-period, no paused-at annotation
Advanced Scenarios
Pod Template Annotations
Reloader reads annotations from both places:
- Deployment/DaemonSet/etc metadata
- Pod template metadata (inside spec.template.metadata)
Tests verify:
- Annotation only on pod template still works
- Annotation on both locations works
- Mismatched annotations (ConfigMap annotation but updating Secret) correctly doesn't reload
Regex Patterns
Use regex in the reload annotation:
configmap.reloader.stakater.com/reload: "app-config-.*"secret.reloader.stakater.com/reload: "db-creds-.*"
Tests verify:
- Matching ConfigMap/Secret triggers reload
- Non-matching ConfigMap/Secret doesn't trigger reload
Multiple Containers
Tests verify:
- Multiple containers sharing one ConfigMap - changes trigger reload
- Multiple containers with different ConfigMaps - change to either triggers reload
Test Organization
test/e2e/
├── core/ # Main tests (all workload types)
│ ├── workloads_test.go # Basic reload behavior
│ └── reference_methods_test.go # envFrom, volumes, etc.
├── annotations/ # Annotation-specific behavior
│ ├── auto_reload_test.go
│ ├── combination_test.go
│ ├── exclude_test.go
│ ├── search_match_test.go
│ ├── pause_period_test.go
│ └── resource_ignore_test.go
├── flags/ # CLI flag behavior
│ ├── namespace_selector_test.go
│ ├── namespace_ignore_test.go
│ ├── resource_selector_test.go
│ ├── ignore_resources_test.go
│ ├── ignored_workloads_test.go
│ ├── auto_reload_all_test.go
│ ├── reload_on_create_test.go
│ ├── reload_on_delete_test.go
│ └── watch_globally_test.go
├── advanced/ # Edge cases
│ ├── job_reload_test.go
│ ├── multi_container_test.go
│ ├── pod_annotations_test.go
│ └── regex_test.go
├── argo/ # Argo Rollouts (requires installation)
│ └── rollout_test.go
├── openshift/ # OpenShift (requires cluster)
│ └── deploymentconfig_test.go
└── utils/ # Shared test helpers
Debugging Failed Tests
See What's Happening
# Verbose output
go test -v ./test/e2e/core/...
# Run one specific test
go test -v ./test/e2e/core/... --ginkgo.focus="should reload when ConfigMap"
# Keep the cluster around after tests
SKIP_CLEANUP=true make e2e
Check Reloader Logs
# Find the Reloader pod
kubectl get pods -A | grep reloader
# Check its logs
kubectl logs -n <namespace> -l app=reloader-reloader --tail=100
Common Problems
| Problem | Solution |
|---|---|
| Test timeout | Reloader might not be running - check pod status |
| Argo tests skipped | Install Argo Rollouts first |
| OpenShift tests skipped | Only work on OpenShift clusters |
| "resource not found" | Missing CRDs (Argo, OpenShift) |
Environment Variables
| Variable | What it does | Default |
|---|---|---|
RELOADER_IMAGE |
Image to test | ghcr.io/stakater/reloader:test |
SKIP_BUILD |
Don't build the image | false |
SKIP_CLEANUP |
Keep cluster after tests | false |
KIND_CLUSTER |
Kind cluster name | kind |
KUBECONFIG |
Kubernetes config path | ~/.kube/config |
Writing New Tests
For Multiple Workload Types
Use the adapter pattern to test the same behavior across Deployments, DaemonSets, etc:
DescribeTable("should reload when ConfigMap changes",
func(workloadType utils.WorkloadType) {
adapter := registry.Get(workloadType)
// ... create ConfigMap, workload, update ConfigMap, verify reload
},
Entry("Deployment", utils.WorkloadDeployment),
Entry("DaemonSet", utils.WorkloadDaemonSet),
Entry("StatefulSet", utils.WorkloadStatefulSet),
)
For Deployment-Only Tests
Use the direct creation helpers:
It("should reload with my specific setup", func() {
_, err := utils.CreateConfigMap(ctx, kubeClient, testNamespace, configMapName,
map[string]string{"key": "value"}, nil)
_, err = utils.CreateDeployment(ctx, kubeClient, testNamespace, deploymentName,
utils.WithConfigMapEnvFrom(configMapName),
utils.WithAnnotations(utils.BuildAutoTrueAnnotation()),
)
// Update and verify...
})
Negative Tests (Verifying Nothing Happens)
It("should NOT reload when only labels change", func() {
// Setup...
// Make a change that shouldn't trigger reload
err = utils.UpdateConfigMapLabels(ctx, kubeClient, testNamespace, configMapName,
map[string]string{"new-label": "value"})
// Wait a bit, then verify NO reload happened
time.Sleep(utils.NegativeTestWait)
reloaded, _ := utils.WaitForDeploymentReloaded(...)
Expect(reloaded).To(BeFalse())
})