17 KiB
Reloader E2E Tests
End-to-end tests that verify Reloader works correctly in a real Kubernetes cluster. Tests create workloads, modify their referenced ConfigMaps/Secrets/SecretProviderClasses, and verify that Reloader triggers the appropriate rolling updates.
Table of Contents
Quick Start
# One-time setup: create Kind cluster and install dependencies
make e2e-setup
# Run all e2e tests
make e2e
# Cleanup when done
make e2e-cleanup
Prerequisites
| Requirement | Version | Purpose |
|---|---|---|
| Go | 1.25+ | Test execution |
| Docker/Podman | Latest | Image building |
| Kind | 0.20+ | Local Kubernetes cluster |
| kubectl | Latest | Cluster interaction |
| Helm | 3.x | Reloader deployment |
Optional Dependencies
| Component | Purpose | Auto-installed by |
|---|---|---|
| Argo Rollouts | Argo Rollout tests | make e2e-setup |
| CSI Secrets Store Driver | SecretProviderClass tests | make e2e-setup |
| Vault | CSI provider backend | make e2e-setup |
| OpenShift | DeploymentConfig tests | Requires OpenShift cluster |
Running Tests
Make Targets
| Target | Description |
|---|---|
make e2e-setup |
Create Kind cluster and install all dependencies (Argo, CSI, Vault) |
make e2e |
Build image, load to Kind, run all tests |
make e2e-cleanup |
Remove test resources and delete Kind cluster |
make e2e-ci |
Full CI pipeline: setup → test → cleanup |
Common Workflows
# Development workflow
make e2e-setup # Once at the start
make e2e # Run tests (repeat as needed)
make e2e # ...iterate...
make e2e-cleanup # When done
# CI workflow
make e2e-ci # Does everything
# Test specific image
SKIP_BUILD=true RELOADER_IMAGE=ghcr.io/stakater/reloader:v1.2.0 make e2e
Running Specific Tests
# Run a specific test suite
go tool ginkgo -v ./test/e2e/core/...
go tool ginkgo -v ./test/e2e/annotations/...
go tool ginkgo -v ./test/e2e/csi/...
# Run tests matching a pattern
go tool ginkgo -v --focus="should reload when ConfigMap" ./test/e2e/...
# Run tests with specific labels
go tool ginkgo -v --label-filter="csi" ./test/e2e/...
go tool ginkgo -v --label-filter="!argo && !openshift" ./test/e2e/...
# Run all tests, continue on failure
go tool ginkgo --keep-going -v ./test/e2e/...
Environment Variables
| Variable | Description | Default |
|---|---|---|
RELOADER_IMAGE |
Image to test | ghcr.io/stakater/reloader:test |
SKIP_BUILD |
Skip image build | false |
KIND_CLUSTER |
Kind cluster name | reloader-e2e |
KUBECONFIG |
Kubernetes config path | ~/.kube/config |
E2E_TIMEOUT |
Test timeout | 45m |
Test Coverage
Workload Types
| Workload | Annotations | EnvVars | CSI | Special Handling |
|---|---|---|---|---|
| Deployment | ✅ | ✅ | ✅ | Standard rolling update |
| DaemonSet | ✅ | ✅ | ✅ | Standard rolling update |
| StatefulSet | ✅ | ✅ | ✅ | Standard rolling update |
| CronJob | ✅ | ❌ | ❌ | Updates job template |
| Job | ✅ | ❌ | ❌ | Recreates job |
| Argo Rollout | ✅ | ✅ | ❌ | Supports restart strategy |
| DeploymentConfig | ✅ | ✅ | ❌ | OpenShift only |
Resource Types
ConfigMaps & Secrets
Standard Kubernetes resources that trigger reloads when their data changes.
Tested Scenarios:
- Data changes trigger reload
- Label-only changes do NOT trigger reload
- Annotation-only changes do NOT trigger reload
- Multiple resources in single annotation (comma-separated)
- Regex patterns for resource names
SecretProviderClass (CSI)
CSI Secrets Store Driver integration for external secret providers (Vault, Azure, AWS, etc.).
Tested Scenarios:
- SecretProviderClassPodStatus changes trigger reload
- Label-only changes on SPCPS do NOT trigger reload
- Auto-detection with
secretproviderclass.reloader.stakater.com/auto: "true" - Exclude specific SPCs from auto-reload
- Init containers with CSI volumes
- Multiple CSI volumes per workload
Reload Strategies
Annotations Strategy (Default)
Adds/updates reloader.stakater.com/last-reloaded-from annotation on pod template.
spec:
template:
metadata:
annotations:
reloader.stakater.com/last-reloaded-from: "my-configmap"
EnvVars Strategy
Adds STAKATER_<RESOURCE>_<TYPE> environment variable to containers.
spec:
template:
spec:
containers:
- env:
- name: STAKATER_MY_CONFIGMAP_CONFIGMAP
value: "<sha256-hash>"
Reference Methods
All methods are tested for Deployment, DaemonSet, and StatefulSet:
| Method | Description | ConfigMap | Secret | CSI |
|---|---|---|---|---|
envFrom |
All keys as env vars | ✅ | ✅ | - |
valueFrom.configMapKeyRef |
Single key as env var | ✅ | - | - |
valueFrom.secretKeyRef |
Single key as env var | - | ✅ | - |
| Volume mount | Mount as files | ✅ | ✅ | ✅ |
| Projected volume | Combined sources | ✅ | ✅ | - |
| Init container (envFrom) | Init container env | ✅ | ✅ | - |
| Init container (volume) | Init container mount | ✅ | ✅ | ✅ |
Annotations
Reload Triggers
| Annotation | Description |
|---|---|
configmap.reloader.stakater.com/reload |
Reload on specific ConfigMap(s) change |
secret.reloader.stakater.com/reload |
Reload on specific Secret(s) change |
secretproviderclass.reloader.stakater.com/reload |
Reload on specific SPC(s) change |
Auto-Detection
| Annotation | Description |
|---|---|
reloader.stakater.com/auto: "true" |
Auto-detect all mounted resources |
configmap.reloader.stakater.com/auto: "true" |
Auto-detect ConfigMaps only |
secret.reloader.stakater.com/auto: "true" |
Auto-detect Secrets only |
secretproviderclass.reloader.stakater.com/auto: "true" |
Auto-detect SPCs only |
Exclusions
| Annotation | Description |
|---|---|
configmaps.exclude.reloader.stakater.com/reload |
Exclude ConfigMaps from auto |
secrets.exclude.reloader.stakater.com/reload |
Exclude Secrets from auto |
secretproviderclasses.exclude.reloader.stakater.com/reload |
Exclude SPCs from auto |
reloader.stakater.com/ignore: "true" |
On resource: prevents any reload |
Search & Match
| Annotation | Target | Description |
|---|---|---|
reloader.stakater.com/search: "true" |
Workload | Watch for matching resources |
reloader.stakater.com/match: "true" |
Resource | Trigger watchers on change |
Other
| Annotation | Description |
|---|---|
reloader.stakater.com/pause-period |
Pause deployment after reload |
CLI Flags
Tests verify these Reloader command-line flags:
| Flag | Description |
|---|---|
--namespaces-to-ignore |
Skip specified namespaces |
--namespace-selector |
Only watch namespaces with matching labels |
--watch-globally |
Watch all namespaces vs own namespace only |
--resource-label-selector |
Only watch resources with matching labels |
--ignore-secrets |
Ignore all Secret changes |
--ignore-configmaps |
Ignore all ConfigMap changes |
--ignore-cronjobs |
Skip CronJob workloads |
--ignore-jobs |
Skip Job workloads |
--reload-on-create |
Trigger reload on resource creation |
--reload-on-delete |
Trigger reload on resource deletion |
--auto-reload-all |
Auto-reload all workloads without annotations |
--enable-csi-integration |
Enable SecretProviderClass support |
Test Organization
test/e2e/
├── core/ # Core workload tests
│ ├── core_suite_test.go
│ └── workloads_test.go # All workload types, both strategies
│
├── annotations/ # Annotation behavior tests
│ ├── annotations_suite_test.go
│ ├── auto_reload_test.go # Auto-detection variations
│ ├── combination_test.go # Multiple annotations together
│ ├── exclude_test.go # Exclude annotations
│ ├── pause_period_test.go # Pause after reload
│ ├── resource_ignore_test.go # Ignore annotation on resources
│ └── search_match_test.go # Search/match pattern
│
├── flags/ # CLI flag tests
│ ├── flags_suite_test.go
│ ├── auto_reload_all_test.go
│ ├── ignore_resources_test.go
│ ├── ignored_workloads_test.go
│ ├── namespace_ignore_test.go
│ ├── namespace_selector_test.go
│ ├── reload_on_create_test.go
│ ├── reload_on_delete_test.go
│ ├── resource_selector_test.go
│ └── watch_globally_test.go
│
├── advanced/ # Advanced scenarios
│ ├── advanced_suite_test.go
│ ├── job_reload_test.go # Job recreation
│ ├── multi_container_test.go # Multiple containers
│ ├── pod_annotations_test.go # Pod template annotations
│ └── regex_test.go # Regex patterns
│
├── csi/ # CSI SecretProviderClass tests
│ ├── csi_suite_test.go
│ └── csi_test.go # SPC-specific scenarios
│
├── argo/ # Argo Rollouts (requires installation)
│ ├── argo_suite_test.go
│ └── rollout_test.go
│
└── utils/ # Shared test utilities
├── annotations.go # Annotation builders
├── constants.go # Test constants
├── csi.go # CSI client and helpers
├── resources.go # Resource creation helpers
├── testenv.go # Test environment setup
├── wait.go # Wait/polling utilities
├── workload_adapter.go # Workload abstraction interface
├── workload_deployment.go # Deployment adapter
├── workload_daemonset.go # DaemonSet adapter
├── workload_statefulset.go # StatefulSet adapter
├── workload_cronjob.go # CronJob adapter
├── workload_job.go # Job adapter
├── workload_argo.go # Argo Rollout adapter
└── workload_openshift.go # DeploymentConfig adapter
Debugging
View Test Output
# Verbose output
go tool ginkgo -v ./test/e2e/core/...
# Focus on specific test
go tool ginkgo -v --focus="should reload when ConfigMap" ./test/e2e/...
# Show all spec names
go tool ginkgo -v --dry-run ./test/e2e/...
Check Reloader Logs
# Find Reloader pod
kubectl get pods -A | grep reloader
# View logs
kubectl logs -n <namespace> -l app.kubernetes.io/name=reloader --tail=100 -f
# Check events
kubectl get events -n <namespace> --sort-by='.lastTimestamp'
Inspect Test Resources
# List test namespaces
kubectl get ns | grep reloader
# Check workloads in test namespace
kubectl get deploy,ds,sts,cronjob,job -n <test-namespace>
# Check ConfigMaps/Secrets
kubectl get cm,secret -n <test-namespace>
# Check CSI resources
kubectl get secretproviderclass,secretproviderclasspodstatus -n <test-namespace>
Common Issues
| Issue | Cause | Solution |
|---|---|---|
| Tests timeout | Reloader not running | Check pod status and logs |
| CSI tests skipped | CSI driver not installed | Run make e2e-setup |
| Argo tests skipped | Argo Rollouts not installed | Run make e2e-setup |
| OpenShift tests skipped | Not an OpenShift cluster | Expected on Kind |
| "resource not found" | Missing CRDs | Install required components |
| Duplicate volume names | Test bug | Check CSI volume naming |
Writing Tests
Using the Workload Adapter Pattern
Test the same behavior across multiple workload types:
DescribeTable("should reload when ConfigMap changes",
func(workloadType utils.WorkloadType) {
adapter := registry.Get(workloadType)
if adapter == nil {
Skip(fmt.Sprintf("%s not available", workloadType))
}
// Create ConfigMap
_, err := utils.CreateConfigMap(ctx, kubeClient, testNamespace, configMapName,
map[string]string{"key": "initial"}, nil)
Expect(err).NotTo(HaveOccurred())
// Create workload via adapter
err = adapter.Create(ctx, testNamespace, workloadName, utils.WorkloadConfig{
ConfigMapName: configMapName,
UseConfigMapEnvFrom: true,
Annotations: utils.BuildConfigMapReloadAnnotation(configMapName),
})
Expect(err).NotTo(HaveOccurred())
// Wait for ready
err = adapter.WaitReady(ctx, testNamespace, workloadName, utils.DeploymentReady)
Expect(err).NotTo(HaveOccurred())
// Update ConfigMap
err = utils.UpdateConfigMap(ctx, kubeClient, testNamespace, configMapName,
map[string]string{"key": "updated"})
Expect(err).NotTo(HaveOccurred())
// Verify reload
reloaded, err := adapter.WaitReloaded(ctx, testNamespace, workloadName,
utils.AnnotationLastReloadedFrom, utils.ReloadTimeout)
Expect(err).NotTo(HaveOccurred())
Expect(reloaded).To(BeTrue())
},
Entry("Deployment", utils.WorkloadDeployment),
Entry("DaemonSet", utils.WorkloadDaemonSet),
Entry("StatefulSet", utils.WorkloadStatefulSet),
)
Direct Resource Creation
For Deployment-specific tests:
It("should reload with custom setup", func() {
_, err := utils.CreateConfigMap(ctx, kubeClient, testNamespace, configMapName,
map[string]string{"key": "value"}, nil)
Expect(err).NotTo(HaveOccurred())
_, err = utils.CreateDeployment(ctx, kubeClient, testNamespace, deploymentName,
utils.WithConfigMapEnvFrom(configMapName),
utils.WithAnnotations(utils.BuildAutoTrueAnnotation()),
)
Expect(err).NotTo(HaveOccurred())
// ... test logic ...
})
CSI Tests
It("should reload when SecretProviderClassPodStatus changes", func() {
if !utils.IsCSIDriverInstalled(ctx, csiClient) {
Skip("CSI driver not installed")
}
// Create SPC
_, err := utils.CreateSecretProviderClass(ctx, csiClient, testNamespace, spcName, nil)
Expect(err).NotTo(HaveOccurred())
// Create SPCPS
_, err = utils.CreateSecretProviderClassPodStatus(ctx, csiClient, testNamespace, spcpsName, spcName,
utils.NewSPCPSObjects("secret1", "v1"))
Expect(err).NotTo(HaveOccurred())
// Create Deployment with CSI volume
_, err = utils.CreateDeployment(ctx, kubeClient, testNamespace, deploymentName,
utils.WithCSIVolume(spcName),
utils.WithAnnotations(utils.BuildSecretProviderClassReloadAnnotation(spcName)),
)
Expect(err).NotTo(HaveOccurred())
// Update SPCPS
err = utils.UpdateSecretProviderClassPodStatus(ctx, csiClient, testNamespace, spcpsName,
utils.NewSPCPSObjects("secret1", "v2"))
Expect(err).NotTo(HaveOccurred())
// Verify reload using adapter
adapter := utils.NewDeploymentAdapter(kubeClient)
reloaded, err := adapter.WaitReloaded(ctx, testNamespace, deploymentName,
utils.AnnotationLastReloadedFrom, utils.ReloadTimeout)
Expect(err).NotTo(HaveOccurred())
Expect(reloaded).To(BeTrue())
})
Negative Tests
Verify that something does NOT trigger a reload:
It("should NOT reload when only labels change", func() {
// Setup...
adapter := utils.NewDeploymentAdapter(kubeClient)
// Make a change that shouldn't trigger reload
err = utils.UpdateConfigMapLabels(ctx, kubeClient, testNamespace, configMapName,
map[string]string{"new-label": "value"})
Expect(err).NotTo(HaveOccurred())
// Wait briefly, then verify NO reload
time.Sleep(utils.NegativeTestWait)
reloaded, err := adapter.WaitReloaded(ctx, testNamespace, deploymentName,
utils.AnnotationLastReloadedFrom, utils.ShortTimeout)
Expect(err).NotTo(HaveOccurred())
Expect(reloaded).To(BeFalse(), "Should NOT have reloaded")
})
Test Labels
Use labels to categorize tests:
Entry("Deployment", Label("csi"), utils.WorkloadDeployment),
Entry("with OpenShift", Label("openshift"), utils.WorkloadDeploymentConfig),
Entry("with Argo", Label("argo"), utils.WorkloadArgoRollout),
Run by label:
go tool ginkgo --label-filter="csi" ./test/e2e/...
go tool ginkgo --label-filter="!openshift && !argo" ./test/e2e/...