refactor: extract matcher into separate package

This commit is contained in:
Safwan
2026-07-21 19:51:17 +05:00
parent 0098e6aef8
commit fcee92dc29
36 changed files with 157 additions and 146 deletions
@@ -9,12 +9,13 @@ import (
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"github.com/stakater/Reloader/internal/pkg/alerting"
"github.com/stakater/Reloader/pkg/config"
"github.com/stakater/Reloader/internal/pkg/events"
"github.com/stakater/Reloader/internal/pkg/metrics"
"github.com/stakater/Reloader/pkg/reload"
"github.com/stakater/Reloader/internal/pkg/reload"
"github.com/stakater/Reloader/internal/pkg/webhook"
"github.com/stakater/Reloader/internal/pkg/workload"
"github.com/stakater/Reloader/pkg/config"
"github.com/stakater/Reloader/pkg/matcher"
)
// ConfigMapReconciler watches ConfigMaps and triggers workload reloads.
@@ -49,7 +50,7 @@ func NewConfigMapReconciler(
NamespaceCache: nsCache,
},
ResourceConfig[*corev1.ConfigMap]{
ResourceType: reload.ResourceTypeConfigMap,
ResourceType: matcher.ResourceTypeConfigMap,
NewResource: func() *corev1.ConfigMap { return &corev1.ConfigMap{} },
CreateChange: func(cm *corev1.ConfigMap, eventType reload.EventType) reload.ResourceChange {
return reload.ConfigMapChange{ConfigMap: cm, EventType: eventType}
@@ -3,8 +3,8 @@ package controller_test
import (
"testing"
"github.com/stakater/Reloader/pkg/config"
"github.com/stakater/Reloader/internal/pkg/testutil"
"github.com/stakater/Reloader/pkg/config"
)
func TestConfigMapReconciler_NotFound(t *testing.T) {
@@ -10,8 +10,8 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/predicate"
"github.com/stakater/Reloader/internal/pkg/reload"
"github.com/stakater/Reloader/pkg/config"
"github.com/stakater/Reloader/pkg/reload"
)
// DeploymentReconciler reconciles Deployment objects to handle pause expiration.
+1 -1
View File
@@ -6,8 +6,8 @@ import (
"sigs.k8s.io/controller-runtime/pkg/event"
"sigs.k8s.io/controller-runtime/pkg/predicate"
"github.com/stakater/Reloader/internal/pkg/reload"
"github.com/stakater/Reloader/pkg/config"
"github.com/stakater/Reloader/pkg/reload"
)
// BuildEventFilter combines a resource-specific predicate with common filters.
+5 -4
View File
@@ -11,9 +11,10 @@ import (
"github.com/stakater/Reloader/internal/pkg/alerting"
"github.com/stakater/Reloader/internal/pkg/events"
"github.com/stakater/Reloader/internal/pkg/metrics"
"github.com/stakater/Reloader/pkg/reload"
"github.com/stakater/Reloader/internal/pkg/reload"
"github.com/stakater/Reloader/internal/pkg/webhook"
"github.com/stakater/Reloader/internal/pkg/workload"
"github.com/stakater/Reloader/pkg/matcher"
)
// ReloadHandler handles the common reload workflow.
@@ -32,7 +33,7 @@ type ReloadHandler struct {
func (h *ReloadHandler) Process(
ctx context.Context,
namespace, resourceName string,
resourceType reload.ResourceType,
resourceType matcher.ResourceType,
getDecisions func([]workload.Workload) []reload.ReloadDecision,
log logr.Logger,
) (ctrl.Result, error) {
@@ -76,7 +77,7 @@ func (h *ReloadHandler) Process(
func (h *ReloadHandler) sendWebhook(
ctx context.Context,
resourceName, namespace string,
resourceType reload.ResourceType,
resourceType matcher.ResourceType,
decisions []reload.ReloadDecision,
log logr.Logger,
) (ctrl.Result, error) {
@@ -127,7 +128,7 @@ func (h *ReloadHandler) sendWebhook(
func (h *ReloadHandler) applyReloads(
ctx context.Context,
resourceName, resourceNamespace string,
resourceType reload.ResourceType,
resourceType matcher.ResourceType,
decisions []reload.ReloadDecision,
log logr.Logger,
) {
+2 -2
View File
@@ -18,12 +18,12 @@ import (
csiv1 "sigs.k8s.io/secrets-store-csi-driver/apis/v1"
"github.com/stakater/Reloader/internal/pkg/alerting"
"github.com/stakater/Reloader/pkg/config"
"github.com/stakater/Reloader/internal/pkg/events"
"github.com/stakater/Reloader/internal/pkg/metrics"
"github.com/stakater/Reloader/pkg/reload"
"github.com/stakater/Reloader/internal/pkg/reload"
"github.com/stakater/Reloader/internal/pkg/webhook"
"github.com/stakater/Reloader/internal/pkg/workload"
"github.com/stakater/Reloader/pkg/config"
)
var runtimeScheme = runtime.NewScheme()
@@ -11,8 +11,8 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"github.com/stakater/Reloader/internal/pkg/reload"
"github.com/stakater/Reloader/pkg/config"
"github.com/stakater/Reloader/pkg/reload"
)
// NamespaceCache provides thread-safe access to the set of namespaces
@@ -5,9 +5,9 @@ import (
"k8s.io/apimachinery/pkg/labels"
"github.com/stakater/Reloader/pkg/config"
"github.com/stakater/Reloader/internal/pkg/controller"
"github.com/stakater/Reloader/internal/pkg/testutil"
"github.com/stakater/Reloader/pkg/config"
)
func TestNamespaceCache_Basic(t *testing.T) {
@@ -11,12 +11,13 @@ import (
"sigs.k8s.io/controller-runtime/pkg/predicate"
"github.com/stakater/Reloader/internal/pkg/alerting"
"github.com/stakater/Reloader/pkg/config"
"github.com/stakater/Reloader/internal/pkg/events"
"github.com/stakater/Reloader/internal/pkg/metrics"
"github.com/stakater/Reloader/pkg/reload"
"github.com/stakater/Reloader/internal/pkg/reload"
"github.com/stakater/Reloader/internal/pkg/webhook"
"github.com/stakater/Reloader/internal/pkg/workload"
"github.com/stakater/Reloader/pkg/config"
"github.com/stakater/Reloader/pkg/matcher"
)
// ResourceReconcilerDeps holds shared dependencies for resource reconcilers.
@@ -39,7 +40,7 @@ type ResourceReconcilerDeps struct {
// ResourceConfig provides type-specific configuration for a resource reconciler.
type ResourceConfig[T client.Object] struct {
// ResourceType identifies the type of resource (configmap or secret).
ResourceType reload.ResourceType
ResourceType matcher.ResourceType
// NewResource creates a new instance of the resource type.
NewResource func() T
+7 -6
View File
@@ -7,8 +7,9 @@ import (
"k8s.io/client-go/util/retry"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/stakater/Reloader/pkg/reload"
"github.com/stakater/Reloader/internal/pkg/reload"
"github.com/stakater/Reloader/internal/pkg/workload"
"github.com/stakater/Reloader/pkg/matcher"
)
// UpdateObjectWithRetry updates a Kubernetes object with retry on conflict.
@@ -58,7 +59,7 @@ func UpdateWorkloadWithRetry(
pauseHandler *reload.PauseHandler,
wl workload.Workload,
resourceName string,
resourceType reload.ResourceType,
resourceType matcher.ResourceType,
namespace string,
hash string,
autoReload bool,
@@ -83,7 +84,7 @@ func retryWithReload(
reloadService *reload.Service,
wl workload.Workload,
resourceName string,
resourceType reload.ResourceType,
resourceType matcher.ResourceType,
namespace string,
hash string,
autoReload bool,
@@ -131,7 +132,7 @@ func updateStandardWorkload(
reloadService *reload.Service,
wl workload.Workload,
resourceName string,
resourceType reload.ResourceType,
resourceType matcher.ResourceType,
namespace string,
hash string,
autoReload bool,
@@ -152,7 +153,7 @@ func updateDeploymentWithPause(
pauseHandler *reload.PauseHandler,
wl workload.Workload,
resourceName string,
resourceType reload.ResourceType,
resourceType matcher.ResourceType,
namespace string,
hash string,
autoReload bool,
@@ -180,7 +181,7 @@ func updateWithSpecialStrategy(
reloadService *reload.Service,
wl workload.Workload,
resourceName string,
resourceType reload.ResourceType,
resourceType matcher.ResourceType,
namespace string,
hash string,
autoReload bool,
+19 -18
View File
@@ -13,11 +13,12 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
"github.com/stakater/Reloader/pkg/config"
"github.com/stakater/Reloader/internal/pkg/controller"
"github.com/stakater/Reloader/pkg/reload"
"github.com/stakater/Reloader/internal/pkg/reload"
"github.com/stakater/Reloader/internal/pkg/testutil"
"github.com/stakater/Reloader/internal/pkg/workload"
"github.com/stakater/Reloader/pkg/config"
"github.com/stakater/Reloader/pkg/matcher"
)
func TestUpdateWorkloadWithRetry_WorkloadTypes(t *testing.T) {
@@ -25,7 +26,7 @@ func TestUpdateWorkloadWithRetry_WorkloadTypes(t *testing.T) {
name string
object runtime.Object
workload func(runtime.Object) workload.Workload
resourceType reload.ResourceType
resourceType matcher.ResourceType
verify func(t *testing.T, c client.Client)
}{
{
@@ -34,7 +35,7 @@ func TestUpdateWorkloadWithRetry_WorkloadTypes(t *testing.T) {
workload: func(o runtime.Object) workload.Workload {
return workload.NewDeploymentWorkload(o.(*appsv1.Deployment))
},
resourceType: reload.ResourceTypeConfigMap,
resourceType: matcher.ResourceTypeConfigMap,
verify: func(t *testing.T, c client.Client) {
var result appsv1.Deployment
if err := c.Get(context.Background(), types.NamespacedName{Name: "test-deployment", Namespace: "default"}, &result); err != nil {
@@ -51,7 +52,7 @@ func TestUpdateWorkloadWithRetry_WorkloadTypes(t *testing.T) {
workload: func(o runtime.Object) workload.Workload {
return workload.NewDaemonSetWorkload(o.(*appsv1.DaemonSet))
},
resourceType: reload.ResourceTypeSecret,
resourceType: matcher.ResourceTypeSecret,
verify: func(t *testing.T, c client.Client) {
var result appsv1.DaemonSet
if err := c.Get(context.Background(), types.NamespacedName{Name: "test-daemonset", Namespace: "default"}, &result); err != nil {
@@ -68,7 +69,7 @@ func TestUpdateWorkloadWithRetry_WorkloadTypes(t *testing.T) {
workload: func(o runtime.Object) workload.Workload {
return workload.NewStatefulSetWorkload(o.(*appsv1.StatefulSet))
},
resourceType: reload.ResourceTypeConfigMap,
resourceType: matcher.ResourceTypeConfigMap,
verify: func(t *testing.T, c client.Client) {
var result appsv1.StatefulSet
if err := c.Get(context.Background(), types.NamespacedName{Name: "test-statefulset", Namespace: "default"}, &result); err != nil {
@@ -85,7 +86,7 @@ func TestUpdateWorkloadWithRetry_WorkloadTypes(t *testing.T) {
workload: func(o runtime.Object) workload.Workload {
return workload.NewJobWorkload(o.(*batchv1.Job))
},
resourceType: reload.ResourceTypeConfigMap,
resourceType: matcher.ResourceTypeConfigMap,
verify: func(t *testing.T, c client.Client) {
var jobs batchv1.JobList
if err := c.List(context.Background(), &jobs, client.InNamespace("default")); err != nil {
@@ -102,7 +103,7 @@ func TestUpdateWorkloadWithRetry_WorkloadTypes(t *testing.T) {
workload: func(o runtime.Object) workload.Workload {
return workload.NewCronJobWorkload(o.(*batchv1.CronJob))
},
resourceType: reload.ResourceTypeSecret,
resourceType: matcher.ResourceTypeSecret,
verify: func(t *testing.T, c client.Client) {
var jobs batchv1.JobList
if err := c.List(context.Background(), &jobs, client.InNamespace("default")); err != nil {
@@ -220,7 +221,7 @@ func TestUpdateWorkloadWithRetry_Strategies(t *testing.T) {
nil, // no pause handler for this test
wl,
"test-cm",
reload.ResourceTypeConfigMap,
matcher.ResourceTypeConfigMap,
"default",
"abc123",
false,
@@ -272,7 +273,7 @@ func TestUpdateWorkloadWithRetry_NoUpdate(t *testing.T) {
nil, // no pause handler
wl,
"test-cm",
reload.ResourceTypeConfigMap,
matcher.ResourceTypeConfigMap,
"default",
"abc123", // Same hash as already set
false,
@@ -288,11 +289,11 @@ func TestUpdateWorkloadWithRetry_NoUpdate(t *testing.T) {
func TestResourceTypeKind(t *testing.T) {
tests := []struct {
resourceType reload.ResourceType
resourceType matcher.ResourceType
expectedKind string
}{
{reload.ResourceTypeConfigMap, "ConfigMap"},
{reload.ResourceTypeSecret, "Secret"},
{matcher.ResourceTypeConfigMap, "ConfigMap"},
{matcher.ResourceTypeSecret, "Secret"},
}
for _, tt := range tests {
@@ -332,7 +333,7 @@ func TestUpdateWorkloadWithRetry_PauseDeployment(t *testing.T) {
pauseHandler,
wl,
"test-cm",
reload.ResourceTypeConfigMap,
matcher.ResourceTypeConfigMap,
"default",
"abc123",
true,
@@ -393,7 +394,7 @@ func TestUpdateWorkloadWithRetry_PauseWithExplicitAnnotation(t *testing.T) {
pauseHandler,
wl,
"test-cm",
reload.ResourceTypeConfigMap,
matcher.ResourceTypeConfigMap,
"default",
"abc123",
false, // NOT auto reload
@@ -454,7 +455,7 @@ func TestUpdateWorkloadWithRetry_PauseWithSecretReload(t *testing.T) {
pauseHandler,
wl,
"test-secret",
reload.ResourceTypeSecret,
matcher.ResourceTypeSecret,
"default",
"abc123",
false,
@@ -511,7 +512,7 @@ func TestUpdateWorkloadWithRetry_PauseWithAutoSecret(t *testing.T) {
pauseHandler,
wl,
"test-secret",
reload.ResourceTypeSecret,
matcher.ResourceTypeSecret,
"default",
"abc123",
true,
@@ -561,7 +562,7 @@ func TestUpdateWorkloadWithRetry_NoPauseWithoutAnnotation(t *testing.T) {
pauseHandler,
wl,
"test-cm",
reload.ResourceTypeConfigMap,
matcher.ResourceTypeConfigMap,
"default",
"abc123",
true,
+4 -3
View File
@@ -9,12 +9,13 @@ import (
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"github.com/stakater/Reloader/internal/pkg/alerting"
"github.com/stakater/Reloader/pkg/config"
"github.com/stakater/Reloader/internal/pkg/events"
"github.com/stakater/Reloader/internal/pkg/metrics"
"github.com/stakater/Reloader/pkg/reload"
"github.com/stakater/Reloader/internal/pkg/reload"
"github.com/stakater/Reloader/internal/pkg/webhook"
"github.com/stakater/Reloader/internal/pkg/workload"
"github.com/stakater/Reloader/pkg/config"
"github.com/stakater/Reloader/pkg/matcher"
)
// SecretReconciler watches Secrets and triggers workload reloads.
@@ -49,7 +50,7 @@ func NewSecretReconciler(
NamespaceCache: nsCache,
},
ResourceConfig[*corev1.Secret]{
ResourceType: reload.ResourceTypeSecret,
ResourceType: matcher.ResourceTypeSecret,
NewResource: func() *corev1.Secret { return &corev1.Secret{} },
CreateChange: func(s *corev1.Secret, eventType reload.EventType) reload.ResourceChange {
return reload.SecretChange{Secret: s, EventType: eventType}
@@ -3,8 +3,8 @@ package controller_test
import (
"testing"
"github.com/stakater/Reloader/pkg/config"
"github.com/stakater/Reloader/internal/pkg/testutil"
"github.com/stakater/Reloader/pkg/config"
)
func TestSecretReconciler_NotFound(t *testing.T) {
@@ -8,8 +8,8 @@ import (
"sigs.k8s.io/controller-runtime/pkg/event"
csiv1 "sigs.k8s.io/secrets-store-csi-driver/apis/v1"
"github.com/stakater/Reloader/internal/pkg/reload"
"github.com/stakater/Reloader/pkg/config"
"github.com/stakater/Reloader/pkg/reload"
)
// TestSecretProviderClassReconciler_FilterIgnoresResourceLabelSelector pins the
@@ -12,8 +12,9 @@ import (
"sigs.k8s.io/controller-runtime/pkg/reconcile"
csiv1 "sigs.k8s.io/secrets-store-csi-driver/apis/v1"
"github.com/stakater/Reloader/internal/pkg/reload"
"github.com/stakater/Reloader/pkg/config"
"github.com/stakater/Reloader/pkg/reload"
"github.com/stakater/Reloader/pkg/matcher"
)
// SecretProviderClassReconciler watches SecretProviderClassPodStatus (the per-pod
@@ -28,7 +29,7 @@ func NewSecretProviderClassReconciler(deps ResourceReconcilerDeps, apiReader cli
return NewResourceReconciler(
deps,
ResourceConfig[*csiv1.SecretProviderClassPodStatus]{
ResourceType: reload.ResourceTypeSecretProviderClass,
ResourceType: matcher.ResourceTypeSecretProviderClass,
NewResource: func() *csiv1.SecretProviderClassPodStatus { return &csiv1.SecretProviderClassPodStatus{} },
ResolveChange: resolveSecretProviderClassChange,
SkipOnNotFound: true,
@@ -10,10 +10,10 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
csiv1 "sigs.k8s.io/secrets-store-csi-driver/apis/v1"
"github.com/stakater/Reloader/pkg/config"
"github.com/stakater/Reloader/internal/pkg/controller"
"github.com/stakater/Reloader/pkg/reload"
"github.com/stakater/Reloader/internal/pkg/reload"
"github.com/stakater/Reloader/internal/pkg/testutil"
"github.com/stakater/Reloader/pkg/config"
)
// newSecretProviderClassReconcilerWithClient creates a SecretProviderClassReconciler for
+2 -2
View File
@@ -13,14 +13,14 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client/fake"
"github.com/stakater/Reloader/internal/pkg/alerting"
"github.com/stakater/Reloader/pkg/config"
"github.com/stakater/Reloader/internal/pkg/controller"
"github.com/stakater/Reloader/internal/pkg/events"
"github.com/stakater/Reloader/internal/pkg/metrics"
"github.com/stakater/Reloader/pkg/reload"
"github.com/stakater/Reloader/internal/pkg/reload"
"github.com/stakater/Reloader/internal/pkg/testutil"
"github.com/stakater/Reloader/internal/pkg/webhook"
"github.com/stakater/Reloader/internal/pkg/workload"
"github.com/stakater/Reloader/pkg/config"
)
// testDeps holds shared test dependencies.
@@ -3,6 +3,8 @@ package reload
import (
corev1 "k8s.io/api/core/v1"
csiv1 "sigs.k8s.io/secrets-store-csi-driver/apis/v1"
"github.com/stakater/Reloader/pkg/matcher"
)
// EventType represents the type of change event.
@@ -24,7 +26,7 @@ type ResourceChange interface {
GetName() string
GetNamespace() string
GetAnnotations() map[string]string
GetResourceType() ResourceType
GetResourceType() matcher.ResourceType
ComputeHash(hasher *Hasher) string
}
@@ -34,13 +36,13 @@ type ConfigMapChange struct {
EventType EventType
}
func (c ConfigMapChange) IsNil() bool { return c.ConfigMap == nil }
func (c ConfigMapChange) GetEventType() EventType { return c.EventType }
func (c ConfigMapChange) GetName() string { return c.ConfigMap.Name }
func (c ConfigMapChange) GetNamespace() string { return c.ConfigMap.Namespace }
func (c ConfigMapChange) GetAnnotations() map[string]string { return c.ConfigMap.Annotations }
func (c ConfigMapChange) GetResourceType() ResourceType { return ResourceTypeConfigMap }
func (c ConfigMapChange) ComputeHash(h *Hasher) string { return h.HashConfigMap(c.ConfigMap) }
func (c ConfigMapChange) IsNil() bool { return c.ConfigMap == nil }
func (c ConfigMapChange) GetEventType() EventType { return c.EventType }
func (c ConfigMapChange) GetName() string { return c.ConfigMap.Name }
func (c ConfigMapChange) GetNamespace() string { return c.ConfigMap.Namespace }
func (c ConfigMapChange) GetAnnotations() map[string]string { return c.ConfigMap.Annotations }
func (c ConfigMapChange) GetResourceType() matcher.ResourceType { return matcher.ResourceTypeConfigMap }
func (c ConfigMapChange) ComputeHash(h *Hasher) string { return h.HashConfigMap(c.ConfigMap) }
// SecretChange represents a change event for a Secret.
type SecretChange struct {
@@ -48,13 +50,13 @@ type SecretChange struct {
EventType EventType
}
func (c SecretChange) IsNil() bool { return c.Secret == nil }
func (c SecretChange) GetEventType() EventType { return c.EventType }
func (c SecretChange) GetName() string { return c.Secret.Name }
func (c SecretChange) GetNamespace() string { return c.Secret.Namespace }
func (c SecretChange) GetAnnotations() map[string]string { return c.Secret.Annotations }
func (c SecretChange) GetResourceType() ResourceType { return ResourceTypeSecret }
func (c SecretChange) ComputeHash(h *Hasher) string { return h.HashSecret(c.Secret) }
func (c SecretChange) IsNil() bool { return c.Secret == nil }
func (c SecretChange) GetEventType() EventType { return c.EventType }
func (c SecretChange) GetName() string { return c.Secret.Name }
func (c SecretChange) GetNamespace() string { return c.Secret.Namespace }
func (c SecretChange) GetAnnotations() map[string]string { return c.Secret.Annotations }
func (c SecretChange) GetResourceType() matcher.ResourceType { return matcher.ResourceTypeSecret }
func (c SecretChange) ComputeHash(h *Hasher) string { return h.HashSecret(c.Secret) }
// SecretProviderClassChange represents a change event derived from a
// SecretProviderClassPodStatus update. Name/Annotations refer to the resolved
@@ -74,8 +76,8 @@ func (c SecretProviderClassChange) GetNamespace() string { return c.Namespace
func (c SecretProviderClassChange) GetAnnotations() map[string]string {
return c.Annotations
}
func (c SecretProviderClassChange) GetResourceType() ResourceType {
return ResourceTypeSecretProviderClass
func (c SecretProviderClassChange) GetResourceType() matcher.ResourceType {
return matcher.ResourceTypeSecretProviderClass
}
func (c SecretProviderClassChange) ComputeHash(h *Hasher) string {
return h.HashSecretProviderClass(c.Status)
@@ -4,6 +4,8 @@ import (
"testing"
csiv1 "sigs.k8s.io/secrets-store-csi-driver/apis/v1"
"github.com/stakater/Reloader/pkg/matcher"
)
func TestSecretProviderClassChange(t *testing.T) {
@@ -28,7 +30,7 @@ func TestSecretProviderClassChange(t *testing.T) {
if c.GetNamespace() != "ns1" {
t.Fatalf("GetNamespace() = %q", c.GetNamespace())
}
if c.GetResourceType() != ResourceTypeSecretProviderClass {
if c.GetResourceType() != matcher.ResourceTypeSecretProviderClass {
t.Fatalf("GetResourceType() = %q", c.GetResourceType())
}
if c.GetEventType() != EventTypeUpdate {
@@ -5,9 +5,7 @@ import (
)
// ReloadDecision contains the result of evaluating whether to reload a workload.
//
// NOTE: not part of the public API — the Workload field is an internal/pkg/workload
// type and is therefore not usable from outside this module.
// External, decision-only consumers should use the public matcher package instead.
type ReloadDecision struct {
// Workload is the workload accessor.
Workload workload.Workload
@@ -11,9 +11,6 @@ import (
)
// PauseHandler handles pause deployment logic.
//
// NOTE: not part of the public API — its methods reference internal/pkg/workload
// and are therefore not usable from outside this module.
type PauseHandler struct {
cfg *config.Config
}
@@ -11,18 +11,16 @@ import (
"github.com/stakater/Reloader/internal/pkg/workload"
"github.com/stakater/Reloader/pkg/config"
"github.com/stakater/Reloader/pkg/matcher"
)
// Service orchestrates the reload logic for ConfigMaps and Secrets.
//
// NOTE: not part of the public API — its methods reference internal/pkg/workload
// and are therefore not usable from outside this module. External, decision-only
// consumers should use Matcher instead.
// External, decision-only consumers should use the public matcher package instead.
type Service struct {
cfg *config.Config
log logr.Logger
hasher *Hasher
matcher *Matcher
matcher *matcher.Matcher
strategy Strategy
}
@@ -32,7 +30,7 @@ func NewService(cfg *config.Config, log logr.Logger) *Service {
cfg: cfg,
log: log,
hasher: NewHasher(),
matcher: NewMatcher(cfg),
matcher: matcher.NewMatcher(cfg),
strategy: NewStrategy(cfg),
}
}
@@ -66,7 +64,7 @@ func (s *Service) processResource(
resourceName string,
resourceNamespace string,
resourceAnnotations map[string]string,
resourceType ResourceType,
resourceType matcher.ResourceType,
hash string,
workloads []workload.Workload,
) []ReloadDecision {
@@ -83,17 +81,17 @@ func (s *Service) processResource(
var usesResource bool
switch resourceType {
case ResourceTypeConfigMap:
case matcher.ResourceTypeConfigMap:
usesResource = wl.UsesConfigMap(resourceName)
case ResourceTypeSecret:
case matcher.ResourceTypeSecret:
usesResource = wl.UsesSecret(resourceName)
case ResourceTypeSecretProviderClass:
case matcher.ResourceTypeSecretProviderClass:
// Annotation-only matching (parity with master): the workload's
// annotations alone decide the reload; no volume-uses scan.
usesResource = true
}
input := MatchInput{
input := matcher.MatchInput{
ResourceName: resourceName,
ResourceNamespace: resourceNamespace,
ResourceType: resourceType,
@@ -141,7 +139,7 @@ func (s *Service) ApplyReload(
ctx context.Context,
wl workload.Workload,
resourceName string,
resourceType ResourceType,
resourceType matcher.ResourceType,
namespace string,
hash string,
autoReload bool,
@@ -176,7 +174,7 @@ func (s *Service) ApplyReload(
func (s *Service) setAttributionAnnotation(
wl workload.Workload,
resourceName string,
resourceType ResourceType,
resourceType matcher.ResourceType,
namespace string,
hash string,
container *corev1.Container,
@@ -207,7 +205,7 @@ func (s *Service) setAttributionAnnotation(
func (s *Service) findTargetContainer(
wl workload.Workload,
resourceName string,
resourceType ResourceType,
resourceType matcher.ResourceType,
autoReload bool,
) *corev1.Container {
containers := wl.GetContainers()
@@ -247,10 +245,10 @@ func (s *Service) findTargetContainer(
return &containers[0]
}
func (s *Service) findVolumeUsingResource(volumes []corev1.Volume, resourceName string, resourceType ResourceType) string {
func (s *Service) findVolumeUsingResource(volumes []corev1.Volume, resourceName string, resourceType matcher.ResourceType) string {
for _, vol := range volumes {
switch resourceType {
case ResourceTypeConfigMap:
case matcher.ResourceTypeConfigMap:
if vol.ConfigMap != nil && vol.ConfigMap.Name == resourceName {
return vol.Name
}
@@ -261,7 +259,7 @@ func (s *Service) findVolumeUsingResource(volumes []corev1.Volume, resourceName
}
}
}
case ResourceTypeSecret:
case matcher.ResourceTypeSecret:
if vol.Secret != nil && vol.Secret.SecretName == resourceName {
return vol.Name
}
@@ -272,7 +270,7 @@ func (s *Service) findVolumeUsingResource(volumes []corev1.Volume, resourceName
}
}
}
case ResourceTypeSecretProviderClass:
case matcher.ResourceTypeSecretProviderClass:
// Match the CSI volume that references this SPC.
if vol.CSI != nil && vol.CSI.VolumeAttributes["secretProviderClass"] == resourceName {
return vol.Name
@@ -293,18 +291,18 @@ func (s *Service) findContainerWithVolumeMount(containers []corev1.Container, vo
return nil
}
func (s *Service) findContainerWithEnvRef(containers []corev1.Container, resourceName string, resourceType ResourceType) *corev1.Container {
func (s *Service) findContainerWithEnvRef(containers []corev1.Container, resourceName string, resourceType matcher.ResourceType) *corev1.Container {
for i := range containers {
for _, env := range containers[i].Env {
if env.ValueFrom == nil {
continue
}
switch resourceType {
case ResourceTypeConfigMap:
case matcher.ResourceTypeConfigMap:
if env.ValueFrom.ConfigMapKeyRef != nil && env.ValueFrom.ConfigMapKeyRef.Name == resourceName {
return &containers[i]
}
case ResourceTypeSecret:
case matcher.ResourceTypeSecret:
if env.ValueFrom.SecretKeyRef != nil && env.ValueFrom.SecretKeyRef.Name == resourceName {
return &containers[i]
}
@@ -313,11 +311,11 @@ func (s *Service) findContainerWithEnvRef(containers []corev1.Container, resourc
for _, envFrom := range containers[i].EnvFrom {
switch resourceType {
case ResourceTypeConfigMap:
case matcher.ResourceTypeConfigMap:
if envFrom.ConfigMapRef != nil && envFrom.ConfigMapRef.Name == resourceName {
return &containers[i]
}
case ResourceTypeSecret:
case matcher.ResourceTypeSecret:
if envFrom.SecretRef != nil && envFrom.SecretRef.Name == resourceName {
return &containers[i]
}
@@ -13,6 +13,7 @@ import (
"github.com/stakater/Reloader/internal/pkg/testutil"
"github.com/stakater/Reloader/internal/pkg/workload"
"github.com/stakater/Reloader/pkg/config"
"github.com/stakater/Reloader/pkg/matcher"
)
func TestService_ProcessConfigMap_AutoReload(t *testing.T) {
@@ -319,7 +320,7 @@ func TestService_ApplyReload_EnvVarStrategy(t *testing.T) {
accessor := workload.NewDeploymentWorkload(deploy)
ctx := context.Background()
updated, err := svc.ApplyReload(ctx, accessor, "test-cm", ResourceTypeConfigMap, "default", "abc123hash", false)
updated, err := svc.ApplyReload(ctx, accessor, "test-cm", matcher.ResourceTypeConfigMap, "default", "abc123hash", false)
if err != nil {
t.Fatalf("ApplyReload failed: %v", err)
@@ -363,7 +364,7 @@ func TestService_ApplyReload_AnnotationStrategy(t *testing.T) {
accessor := workload.NewDeploymentWorkload(deploy)
ctx := context.Background()
updated, err := svc.ApplyReload(ctx, accessor, "test-cm", ResourceTypeConfigMap, "default", "abc123hash", false)
updated, err := svc.ApplyReload(ctx, accessor, "test-cm", matcher.ResourceTypeConfigMap, "default", "abc123hash", false)
if err != nil {
t.Fatalf("ApplyReload failed: %v", err)
@@ -395,7 +396,7 @@ func TestService_ApplyReload_EnvVarDeletion(t *testing.T) {
ctx := context.Background()
// Empty hash signals deletion
updated, err := svc.ApplyReload(ctx, accessor, "test-cm", ResourceTypeConfigMap, "default", "", false)
updated, err := svc.ApplyReload(ctx, accessor, "test-cm", matcher.ResourceTypeConfigMap, "default", "", false)
if err != nil {
t.Fatalf("ApplyReload failed: %v", err)
@@ -439,7 +440,7 @@ func TestService_ApplyReload_NoChangeIfSameHash(t *testing.T) {
accessor := workload.NewDeploymentWorkload(deploy)
ctx := context.Background()
updated, err := svc.ApplyReload(ctx, accessor, "test-cm", ResourceTypeConfigMap, "default", "abc123hash", false)
updated, err := svc.ApplyReload(ctx, accessor, "test-cm", matcher.ResourceTypeConfigMap, "default", "abc123hash", false)
if err != nil {
t.Fatalf("ApplyReload failed: %v", err)
@@ -673,7 +674,7 @@ func TestService_findVolumeUsingResource_ConfigMap(t *testing.T) {
name string
volumes []corev1.Volume
resourceName string
resourceType ResourceType
resourceType matcher.ResourceType
wantVolume string
}{
{
@@ -689,7 +690,7 @@ func TestService_findVolumeUsingResource_ConfigMap(t *testing.T) {
},
},
resourceName: "my-cm",
resourceType: ResourceTypeConfigMap,
resourceType: matcher.ResourceTypeConfigMap,
wantVolume: "config-vol",
},
{
@@ -711,7 +712,7 @@ func TestService_findVolumeUsingResource_ConfigMap(t *testing.T) {
},
},
resourceName: "projected-cm",
resourceType: ResourceTypeConfigMap,
resourceType: matcher.ResourceTypeConfigMap,
wantVolume: "projected-vol",
},
{
@@ -727,14 +728,14 @@ func TestService_findVolumeUsingResource_ConfigMap(t *testing.T) {
},
},
resourceName: "my-cm",
resourceType: ResourceTypeConfigMap,
resourceType: matcher.ResourceTypeConfigMap,
wantVolume: "",
},
{
name: "empty volumes",
volumes: []corev1.Volume{},
resourceName: "my-cm",
resourceType: ResourceTypeConfigMap,
resourceType: matcher.ResourceTypeConfigMap,
wantVolume: "",
},
}
@@ -817,7 +818,7 @@ func TestService_findVolumeUsingResource_Secret(t *testing.T) {
for _, tt := range tests {
t.Run(
tt.name, func(t *testing.T) {
got := svc.findVolumeUsingResource(tt.volumes, tt.resourceName, ResourceTypeSecret)
got := svc.findVolumeUsingResource(tt.volumes, tt.resourceName, matcher.ResourceTypeSecret)
if got != tt.wantVolume {
t.Errorf("findVolumeUsingResource() = %q, want %q", got, tt.wantVolume)
}
@@ -995,7 +996,7 @@ func TestService_findContainerWithEnvRef_ConfigMap(t *testing.T) {
for _, tt := range tests {
t.Run(
tt.name, func(t *testing.T) {
got := svc.findContainerWithEnvRef(tt.containers, tt.resourceName, ResourceTypeConfigMap)
got := svc.findContainerWithEnvRef(tt.containers, tt.resourceName, matcher.ResourceTypeConfigMap)
if tt.shouldMatch {
if got == nil {
t.Error("Expected to find a container, got nil")
@@ -1084,7 +1085,7 @@ func TestService_findContainerWithEnvRef_Secret(t *testing.T) {
for _, tt := range tests {
t.Run(
tt.name, func(t *testing.T) {
got := svc.findContainerWithEnvRef(tt.containers, tt.resourceName, ResourceTypeSecret)
got := svc.findContainerWithEnvRef(tt.containers, tt.resourceName, matcher.ResourceTypeSecret)
if tt.shouldMatch {
if got == nil {
t.Error("Expected to find a container, got nil")
@@ -1128,7 +1129,7 @@ func TestService_findTargetContainer_AutoReload(t *testing.T) {
}
accessor := workload.NewDeploymentWorkload(deploy)
container := svc.findTargetContainer(accessor, "my-cm", ResourceTypeConfigMap, true)
container := svc.findTargetContainer(accessor, "my-cm", matcher.ResourceTypeConfigMap, true)
if container == nil {
t.Fatal("Expected to find a container")
}
@@ -1166,7 +1167,7 @@ func TestService_findTargetContainer_AutoReload_EnvRef(t *testing.T) {
}
accessor := workload.NewDeploymentWorkload(deploy)
container := svc.findTargetContainer(accessor, "my-cm", ResourceTypeConfigMap, true)
container := svc.findTargetContainer(accessor, "my-cm", matcher.ResourceTypeConfigMap, true)
if container == nil {
t.Fatal("Expected to find a container")
}
@@ -1208,7 +1209,7 @@ func TestService_findTargetContainer_AutoReload_InitContainer(t *testing.T) {
}
accessor := workload.NewDeploymentWorkload(deploy)
container := svc.findTargetContainer(accessor, "my-cm", ResourceTypeConfigMap, true)
container := svc.findTargetContainer(accessor, "my-cm", matcher.ResourceTypeConfigMap, true)
if container == nil {
t.Fatal("Expected to find a container")
}
@@ -1249,7 +1250,7 @@ func TestService_findTargetContainer_AutoReload_InitContainerEnvRef(t *testing.T
}
accessor := workload.NewDeploymentWorkload(deploy)
container := svc.findTargetContainer(accessor, "my-cm", ResourceTypeConfigMap, true)
container := svc.findTargetContainer(accessor, "my-cm", matcher.ResourceTypeConfigMap, true)
if container == nil {
t.Fatal("Expected to find a container")
}
@@ -1267,7 +1268,7 @@ func TestService_findTargetContainer_NoContainers(t *testing.T) {
deploy.Spec.Template.Spec.Containers = []corev1.Container{}
accessor := workload.NewDeploymentWorkload(deploy)
container := svc.findTargetContainer(accessor, "my-cm", ResourceTypeConfigMap, false)
container := svc.findTargetContainer(accessor, "my-cm", matcher.ResourceTypeConfigMap, false)
if container != nil {
t.Error("Expected nil container for empty container list")
}
@@ -1285,7 +1286,7 @@ func TestService_findTargetContainer_NonAutoReload(t *testing.T) {
accessor := workload.NewDeploymentWorkload(deploy)
// Without autoReload, should return first container
container := svc.findTargetContainer(accessor, "my-cm", ResourceTypeConfigMap, false)
container := svc.findTargetContainer(accessor, "my-cm", matcher.ResourceTypeConfigMap, false)
if container == nil {
t.Fatal("Expected to find a container")
}
@@ -1306,7 +1307,7 @@ func TestService_findTargetContainer_AutoReload_FallbackToFirst(t *testing.T) {
}
accessor := workload.NewDeploymentWorkload(deploy)
container := svc.findTargetContainer(accessor, "non-existent", ResourceTypeConfigMap, true)
container := svc.findTargetContainer(accessor, "non-existent", matcher.ResourceTypeConfigMap, true)
if container == nil {
t.Fatal("Expected to find a container")
}
@@ -1420,7 +1421,7 @@ func TestService_ApplyReload_SPC_TargetsMountingContainer(t *testing.T) {
accessor := workload.NewDeploymentWorkload(dep)
// autoReload=true exercises volume-based container targeting.
updated, err := svc.ApplyReload(context.Background(), accessor, "my-spc", ResourceTypeSecretProviderClass, "default", "spchash", true)
updated, err := svc.ApplyReload(context.Background(), accessor, "my-spc", matcher.ResourceTypeSecretProviderClass, "default", "spchash", true)
if err != nil {
t.Fatalf("ApplyReload failed: %v", err)
}
@@ -10,6 +10,7 @@ import (
corev1 "k8s.io/api/core/v1"
"github.com/stakater/Reloader/pkg/config"
"github.com/stakater/Reloader/pkg/matcher"
)
const (
@@ -32,7 +33,7 @@ type Strategy interface {
// StrategyInput contains the information needed to apply a reload strategy.
type StrategyInput struct {
ResourceName string
ResourceType ResourceType
ResourceType matcher.ResourceType
Namespace string
Hash string
Container *corev1.Container
@@ -103,14 +104,14 @@ func (s *EnvVarStrategy) removeEnvVar(container *corev1.Container, name string)
return false
}
func (s *EnvVarStrategy) envVarName(resourceName string, resourceType ResourceType) string {
func (s *EnvVarStrategy) envVarName(resourceName string, resourceType matcher.ResourceType) string {
var postfix string
switch resourceType {
case ResourceTypeConfigMap:
case matcher.ResourceTypeConfigMap:
postfix = ConfigmapEnvVarPostfix
case ResourceTypeSecret:
case matcher.ResourceTypeSecret:
postfix = SecretEnvVarPostfix
case ResourceTypeSecretProviderClass:
case matcher.ResourceTypeSecretProviderClass:
postfix = SecretProviderClassEnvVarPostfix
}
return EnvVarPrefix + convertToEnvVarName(resourceName) + "_" + postfix
@@ -7,6 +7,7 @@ import (
corev1 "k8s.io/api/core/v1"
"github.com/stakater/Reloader/pkg/config"
"github.com/stakater/Reloader/pkg/matcher"
)
func TestEnvVarStrategy_Apply(t *testing.T) {
@@ -20,7 +21,7 @@ func TestEnvVarStrategy_Apply(t *testing.T) {
input := StrategyInput{
ResourceName: "my-config",
ResourceType: ResourceTypeConfigMap,
ResourceType: matcher.ResourceTypeConfigMap,
Namespace: "default",
Hash: "abc123",
Container: container,
@@ -57,7 +58,7 @@ func TestEnvVarStrategy_Apply(t *testing.T) {
input := StrategyInput{
ResourceName: "my-config",
ResourceType: ResourceTypeConfigMap,
ResourceType: matcher.ResourceTypeConfigMap,
Namespace: "default",
Hash: "new-hash",
Container: container,
@@ -87,7 +88,7 @@ func TestEnvVarStrategy_Apply(t *testing.T) {
input := StrategyInput{
ResourceName: "my-config",
ResourceType: ResourceTypeConfigMap,
ResourceType: matcher.ResourceTypeConfigMap,
Namespace: "default",
Hash: "same-hash",
Container: container,
@@ -105,7 +106,7 @@ func TestEnvVarStrategy_Apply(t *testing.T) {
t.Run("error when container is nil", func(t *testing.T) {
input := StrategyInput{
ResourceName: "my-config",
ResourceType: ResourceTypeConfigMap,
ResourceType: matcher.ResourceTypeConfigMap,
Namespace: "default",
Hash: "abc123",
Container: nil,
@@ -125,7 +126,7 @@ func TestEnvVarStrategy_Apply(t *testing.T) {
input := StrategyInput{
ResourceName: "my-secret",
ResourceType: ResourceTypeSecret,
ResourceType: matcher.ResourceTypeSecret,
Namespace: "default",
Hash: "abc123",
Container: container,
@@ -158,15 +159,15 @@ func TestEnvVarStrategy_EnvVarName(t *testing.T) {
tests := []struct {
resourceName string
resourceType ResourceType
resourceType matcher.ResourceType
expected string
}{
{"my-config", ResourceTypeConfigMap, "STAKATER_MY_CONFIG_CONFIGMAP"},
{"my-secret", ResourceTypeSecret, "STAKATER_MY_SECRET_SECRET"},
{"app-config-v2", ResourceTypeConfigMap, "STAKATER_APP_CONFIG_V2_CONFIGMAP"},
{"my.dotted.config", ResourceTypeConfigMap, "STAKATER_MY_DOTTED_CONFIG_CONFIGMAP"},
{"MyMixedCase", ResourceTypeConfigMap, "STAKATER_MYMIXEDCASE_CONFIGMAP"},
{"config-with-123-numbers", ResourceTypeConfigMap, "STAKATER_CONFIG_WITH_123_NUMBERS_CONFIGMAP"},
{"my-config", matcher.ResourceTypeConfigMap, "STAKATER_MY_CONFIG_CONFIGMAP"},
{"my-secret", matcher.ResourceTypeSecret, "STAKATER_MY_SECRET_SECRET"},
{"app-config-v2", matcher.ResourceTypeConfigMap, "STAKATER_APP_CONFIG_V2_CONFIGMAP"},
{"my.dotted.config", matcher.ResourceTypeConfigMap, "STAKATER_MY_DOTTED_CONFIG_CONFIGMAP"},
{"MyMixedCase", matcher.ResourceTypeConfigMap, "STAKATER_MYMIXEDCASE_CONFIGMAP"},
{"config-with-123-numbers", matcher.ResourceTypeConfigMap, "STAKATER_CONFIG_WITH_123_NUMBERS_CONFIGMAP"},
}
for _, tt := range tests {
@@ -218,7 +219,7 @@ func TestAnnotationStrategy_Apply(t *testing.T) {
input := StrategyInput{
ResourceName: "my-config",
ResourceType: ResourceTypeConfigMap,
ResourceType: matcher.ResourceTypeConfigMap,
Namespace: "default",
Hash: "abc123",
Container: container,
@@ -244,8 +245,8 @@ func TestAnnotationStrategy_Apply(t *testing.T) {
if err := json.Unmarshal([]byte(annotationValue), &source); err != nil {
t.Fatalf("failed to unmarshal annotation: %v", err)
}
if source.Kind != string(ResourceTypeConfigMap) {
t.Errorf("expected kind=%s, got %s", ResourceTypeConfigMap, source.Kind)
if source.Kind != string(matcher.ResourceTypeConfigMap) {
t.Errorf("expected kind=%s, got %s", matcher.ResourceTypeConfigMap, source.Kind)
}
if source.Name != "my-config" {
t.Errorf("expected name=my-config, got %s", source.Name)
@@ -259,7 +260,7 @@ func TestAnnotationStrategy_Apply(t *testing.T) {
annotations := make(map[string]string)
input := StrategyInput{
ResourceName: "my-config",
ResourceType: ResourceTypeConfigMap,
ResourceType: matcher.ResourceTypeConfigMap,
Namespace: "default",
Hash: "abc123",
Container: &corev1.Container{Name: "c"},
@@ -302,7 +303,7 @@ func TestAnnotationStrategy_Apply(t *testing.T) {
t.Run("error when annotations map is nil", func(t *testing.T) {
input := StrategyInput{
ResourceName: "my-config",
ResourceType: ResourceTypeConfigMap,
ResourceType: matcher.ResourceTypeConfigMap,
Namespace: "default",
Hash: "abc123",
PodAnnotations: nil,
@@ -338,7 +339,7 @@ func TestNewStrategy(t *testing.T) {
func TestEnvVarNameSecretProviderClass(t *testing.T) {
s := NewEnvVarStrategy()
got := s.envVarName("my-vault-spc", ResourceTypeSecretProviderClass)
got := s.envVarName("my-vault-spc", matcher.ResourceTypeSecretProviderClass)
want := "STAKATER_MY_VAULT_SPC_SECRETPROVIDERCLASS"
if got != want {
t.Fatalf("envVarName = %q, want %q", got, want)
@@ -1,4 +1,8 @@
package reload
// Package matcher provides the annotation-based reload decision API. Given a
// changed resource and a workload's annotations, Matcher reports whether the
// workload should be reloaded. It is workload-type agnostic and safe to import
// from outside this module.
package matcher
import (
"regexp"
@@ -1,4 +1,4 @@
package reload
package matcher
import (
"testing"
@@ -1,4 +1,4 @@
package reload
package matcher
// ResourceType represents the type of Kubernetes resource.
type ResourceType string
@@ -1,4 +1,4 @@
package reload
package matcher
import (
"testing"