mirror of
https://github.com/stakater/Reloader.git
synced 2026-08-23 14:06:27 +00:00
refactoring
This commit is contained in:
@@ -968,4 +968,19 @@ func TestSecretProviderClassPodStatusPredicates(t *testing.T) {
|
||||
if p.Update(event.UpdateEvent{ObjectOld: oldObj, ObjectNew: newObjSame}) {
|
||||
t.Fatal("UpdateFunc should return false on unchanged status")
|
||||
}
|
||||
|
||||
// A metadata/label-only change (same Status) must NOT trigger a reload,
|
||||
// since the predicate hashes only the status. (Master tested this via
|
||||
// UpdateSecretProviderClassPodStatusLabels.)
|
||||
labelOnly := oldObj.DeepCopy()
|
||||
labelOnly.Labels = map[string]string{"unrelated": "changed"}
|
||||
labelOnly.Annotations = map[string]string{"note": "touched"}
|
||||
if p.Update(event.UpdateEvent{ObjectOld: oldObj, ObjectNew: labelOnly}) {
|
||||
t.Fatal("UpdateFunc should return false on a label/metadata-only change")
|
||||
}
|
||||
|
||||
// Type-assertion failure (wrong object type) must be rejected, not panic.
|
||||
if p.Update(event.UpdateEvent{ObjectOld: &corev1.ConfigMap{}, ObjectNew: &corev1.ConfigMap{}}) {
|
||||
t.Fatal("UpdateFunc should return false when objects are not SPCPS")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -268,6 +268,11 @@ func (s *Service) findVolumeUsingResource(volumes []corev1.Volume, resourceName
|
||||
}
|
||||
}
|
||||
}
|
||||
case ResourceTypeSecretProviderClass:
|
||||
// Match the CSI volume that references this SPC.
|
||||
if vol.CSI != nil && vol.CSI.VolumeAttributes["secretProviderClass"] == resourceName {
|
||||
return vol.Name
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/go-logr/logr/testr"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
csiv1 "sigs.k8s.io/secrets-store-csi-driver/apis/v1"
|
||||
@@ -1385,3 +1386,88 @@ func TestService_ProcessCreateEventDisabled(t *testing.T) {
|
||||
t.Errorf("Expected nil decisions when create events disabled, got %v", decisions)
|
||||
}
|
||||
}
|
||||
|
||||
func TestService_ApplyReload_SPC_TargetsMountingContainer(t *testing.T) {
|
||||
cfg := config.NewDefault()
|
||||
cfg.ReloadStrategy = config.ReloadStrategyEnvVars
|
||||
svc := NewService(cfg, testr.New(t))
|
||||
|
||||
// Two containers; only the second mounts the CSI volume that references the SPC.
|
||||
dep := &appsv1.Deployment{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "multi", Namespace: "default"},
|
||||
Spec: appsv1.DeploymentSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{Name: "c0"},
|
||||
{Name: "c1", VolumeMounts: []corev1.VolumeMount{{Name: "spc-vol", MountPath: "/mnt/secrets-store"}}},
|
||||
},
|
||||
Volumes: []corev1.Volume{
|
||||
{
|
||||
Name: "spc-vol",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
CSI: &corev1.CSIVolumeSource{
|
||||
Driver: "secrets-store.csi.k8s.io",
|
||||
VolumeAttributes: map[string]string{"secretProviderClass": "my-spc"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
accessor := workload.NewDeploymentWorkload(dep)
|
||||
|
||||
// autoReload=true exercises volume-based container targeting.
|
||||
updated, err := svc.ApplyReload(context.Background(), accessor, "my-spc", ResourceTypeSecretProviderClass, "default", "spchash", true)
|
||||
if err != nil {
|
||||
t.Fatalf("ApplyReload failed: %v", err)
|
||||
}
|
||||
if !updated {
|
||||
t.Fatal("expected updated=true")
|
||||
}
|
||||
|
||||
containers := accessor.GetContainers()
|
||||
const envName = "STAKATER_MY_SPC_SECRETPROVIDERCLASS"
|
||||
hasEnv := func(c corev1.Container) bool {
|
||||
for _, e := range c.Env {
|
||||
if e.Name == envName {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
if hasEnv(containers[0]) {
|
||||
t.Error("env var must NOT land on container[0] (it does not mount the SPC volume)")
|
||||
}
|
||||
if !hasEnv(containers[1]) {
|
||||
t.Error("env var must land on container[1], which mounts the SPC CSI volume")
|
||||
}
|
||||
}
|
||||
|
||||
func TestService_ProcessSecretProviderClass_GenericAuto(t *testing.T) {
|
||||
cfg := config.NewDefault()
|
||||
svc := NewService(cfg, testr.New(t))
|
||||
|
||||
// Generic auto annotation (not the typed SPC one) must also trigger an SPC reload.
|
||||
deploy := testutil.NewDeployment("test-deploy", "default", map[string]string{
|
||||
"reloader.stakater.com/auto": "true",
|
||||
})
|
||||
workloads := []workload.Workload{workload.NewDeploymentWorkload(deploy)}
|
||||
|
||||
change := SecretProviderClassChange{
|
||||
Name: "my-spc",
|
||||
Namespace: "default",
|
||||
Status: csiv1.SecretProviderClassPodStatusStatus{
|
||||
SecretProviderClassName: "my-spc",
|
||||
Objects: []csiv1.SecretProviderClassObject{{ID: "a", Version: "1"}},
|
||||
},
|
||||
EventType: EventTypeUpdate,
|
||||
}
|
||||
|
||||
decisions := svc.Process(change, workloads)
|
||||
if len(decisions) != 1 || !decisions[0].ShouldReload {
|
||||
t.Fatalf("expected generic-auto SPC reload, got %+v", decisions)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,8 +179,15 @@ func (s *AnnotationStrategy) Apply(input StrategyInput) (bool, error) {
|
||||
annotationKey := s.cfg.Annotations.LastReloadedFrom
|
||||
existingValue := input.PodAnnotations[annotationKey]
|
||||
|
||||
if existingValue == string(sourceJSON) {
|
||||
return false, nil
|
||||
// Idempotent on kind+name+hash, ignoring ReloadedAt: a timestamped compare
|
||||
// would force a rollout every reconcile (one CSI rotation fans out to N
|
||||
// SecretProviderClassPodStatus updates → N rollouts).
|
||||
if existingValue != "" {
|
||||
var prev ReloadSource
|
||||
if err := json.Unmarshal([]byte(existingValue), &prev); err == nil &&
|
||||
prev.Kind == source.Kind && prev.Name == source.Name && prev.Hash == source.Hash {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
input.PodAnnotations[annotationKey] = string(sourceJSON)
|
||||
|
||||
@@ -255,6 +255,50 @@ func TestAnnotationStrategy_Apply(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("idempotent for same resource and hash (ignores timestamp)", func(t *testing.T) {
|
||||
annotations := make(map[string]string)
|
||||
input := StrategyInput{
|
||||
ResourceName: "my-config",
|
||||
ResourceType: ResourceTypeConfigMap,
|
||||
Namespace: "default",
|
||||
Hash: "abc123",
|
||||
Container: &corev1.Container{Name: "c"},
|
||||
PodAnnotations: annotations,
|
||||
}
|
||||
|
||||
changed, err := strategy.Apply(input)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !changed {
|
||||
t.Fatal("expected changed=true on first apply")
|
||||
}
|
||||
firstValue := annotations[cfg.Annotations.LastReloadedFrom]
|
||||
|
||||
// Re-applying the identical change must NOT report a change, even though
|
||||
// a fresh ReloadedAt timestamp would make the serialized value differ.
|
||||
changed, err = strategy.Apply(input)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if changed {
|
||||
t.Error("expected changed=false when re-applying the same resource+hash")
|
||||
}
|
||||
if annotations[cfg.Annotations.LastReloadedFrom] != firstValue {
|
||||
t.Error("annotation value must not change on an idempotent re-apply")
|
||||
}
|
||||
|
||||
// A different hash (real content change) must trigger an update.
|
||||
input.Hash = "def456"
|
||||
changed, err = strategy.Apply(input)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !changed {
|
||||
t.Error("expected changed=true when the hash changes")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("error when annotations map is nil", func(t *testing.T) {
|
||||
input := StrategyInput{
|
||||
ResourceName: "my-config",
|
||||
|
||||
Reference in New Issue
Block a user