diff --git a/internal/pkg/controller/filter.go b/internal/pkg/controller/filter.go new file mode 100644 index 00000000..c3a387b4 --- /dev/null +++ b/internal/pkg/controller/filter.go @@ -0,0 +1,39 @@ +package controller + +import ( + "github.com/stakater/Reloader/internal/pkg/config" + "github.com/stakater/Reloader/internal/pkg/reload" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/predicate" +) + +// BuildEventFilter combines a resource-specific predicate with common filters. +func BuildEventFilter(resourcePredicate predicate.Predicate, cfg *config.Config, initialized *bool) predicate.Predicate { + return predicate.And( + resourcePredicate, + reload.NamespaceFilterPredicate(cfg), + reload.LabelSelectorPredicate(cfg), + reload.IgnoreAnnotationPredicate(cfg), + createEventPredicate(cfg, initialized), + ) +} + +func createEventPredicate(cfg *config.Config, initialized *bool) predicate.Predicate { + return predicate.Funcs{ + CreateFunc: func(e event.CreateEvent) bool { + if !*initialized && !cfg.SyncAfterRestart { + return false + } + return cfg.ReloadOnCreate + }, + UpdateFunc: func(e event.UpdateEvent) bool { + return true + }, + DeleteFunc: func(e event.DeleteEvent) bool { + return cfg.ReloadOnDelete + }, + GenericFunc: func(e event.GenericEvent) bool { + return false + }, + } +} diff --git a/internal/pkg/controller/handler.go b/internal/pkg/controller/handler.go index caa71fb8..00b7218a 100644 --- a/internal/pkg/controller/handler.go +++ b/internal/pkg/controller/handler.go @@ -6,7 +6,6 @@ import ( "github.com/go-logr/logr" "github.com/stakater/Reloader/internal/pkg/alerting" - "github.com/stakater/Reloader/internal/pkg/config" "github.com/stakater/Reloader/internal/pkg/events" "github.com/stakater/Reloader/internal/pkg/metrics" "github.com/stakater/Reloader/internal/pkg/reload" @@ -14,8 +13,6 @@ import ( "github.com/stakater/Reloader/internal/pkg/workload" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/event" - "sigs.k8s.io/controller-runtime/pkg/predicate" ) // ReloadHandler handles the common reload workflow. @@ -154,34 +151,3 @@ func (h *ReloadHandler) applyReloads( } } } - -// BuildEventFilter combines a resource-specific predicate with common filters. -func BuildEventFilter(resourcePredicate predicate.Predicate, cfg *config.Config, initialized *bool) predicate.Predicate { - return predicate.And( - resourcePredicate, - reload.NamespaceFilterPredicate(cfg), - reload.LabelSelectorPredicate(cfg), - reload.IgnoreAnnotationPredicate(cfg), - createEventPredicate(cfg, initialized), - ) -} - -func createEventPredicate(cfg *config.Config, initialized *bool) predicate.Predicate { - return predicate.Funcs{ - CreateFunc: func(e event.CreateEvent) bool { - if !*initialized && !cfg.SyncAfterRestart { - return false - } - return cfg.ReloadOnCreate - }, - UpdateFunc: func(e event.UpdateEvent) bool { - return true - }, - DeleteFunc: func(e event.DeleteEvent) bool { - return cfg.ReloadOnDelete - }, - GenericFunc: func(e event.GenericEvent) bool { - return false - }, - } -} diff --git a/internal/pkg/metadata/metadata.go b/internal/pkg/metadata/metadata.go index f0e22f12..9bfae8d5 100644 --- a/internal/pkg/metadata/metadata.go +++ b/internal/pkg/metadata/metadata.go @@ -3,19 +3,14 @@ package metadata import ( - "context" "encoding/json" - "fmt" "os" "runtime" "time" - "github.com/go-logr/logr" "github.com/stakater/Reloader/internal/pkg/config" corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "sigs.k8s.io/controller-runtime/pkg/client" ) const ( @@ -195,73 +190,6 @@ func (m *MetaInfo) ToConfigMap() *corev1.ConfigMap { } } -// Publisher handles creating and updating the metadata ConfigMap. -type Publisher struct { - client client.Client - cfg *config.Config - log logr.Logger -} - -// NewPublisher creates a new Publisher. -func NewPublisher(c client.Client, cfg *config.Config, log logr.Logger) *Publisher { - return &Publisher{ - client: c, - cfg: cfg, - log: log, - } -} - -// Publish creates or updates the metadata ConfigMap. -func (p *Publisher) Publish(ctx context.Context) error { - namespace := os.Getenv(EnvReloaderNamespace) - if namespace == "" { - p.log.Info("RELOADER_NAMESPACE is not set, skipping meta info configmap creation") - return nil - } - - metaInfo := NewMetaInfo(p.cfg) - configMap := metaInfo.ToConfigMap() - - existing := &corev1.ConfigMap{} - err := p.client.Get(ctx, client.ObjectKey{ - Name: ConfigMapName, - Namespace: namespace, - }, existing) - - if err != nil { - if !errors.IsNotFound(err) { - return fmt.Errorf("failed to get existing meta info configmap: %w", err) - } - p.log.Info("Creating meta info configmap") - if err := p.client.Create(ctx, configMap, client.FieldOwner(FieldManager)); err != nil { - return fmt.Errorf("failed to create meta info configmap: %w", err) - } - p.log.Info("Meta info configmap created successfully") - return nil - } - - p.log.Info("Meta info configmap already exists, updating it") - existing.Data = configMap.Data - existing.Labels = configMap.Labels - if err := p.client.Update(ctx, existing, client.FieldOwner(FieldManager)); err != nil { - return fmt.Errorf("failed to update meta info configmap: %w", err) - } - p.log.Info("Meta info configmap updated successfully") - return nil -} - -// PublishMetaInfoConfigMap is a convenience function that creates a Publisher and calls Publish. -func PublishMetaInfoConfigMap(ctx context.Context, c client.Client, cfg *config.Config, log logr.Logger) error { - publisher := NewPublisher(c, cfg, log) - return publisher.Publish(ctx) -} - -// CreateOrUpdate creates or updates the metadata ConfigMap using the provided client. -func CreateOrUpdate(c client.Client, cfg *config.Config, log logr.Logger) error { - ctx := context.Background() - return PublishMetaInfoConfigMap(ctx, c, cfg, log) -} - func toJSON(data interface{}) string { jsonData, err := json.Marshal(data) if err != nil { diff --git a/internal/pkg/metadata/publisher.go b/internal/pkg/metadata/publisher.go new file mode 100644 index 00000000..78bfa92a --- /dev/null +++ b/internal/pkg/metadata/publisher.go @@ -0,0 +1,80 @@ +package metadata + +import ( + "context" + "fmt" + "os" + + "github.com/go-logr/logr" + "github.com/stakater/Reloader/internal/pkg/config" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// Publisher handles creating and updating the metadata ConfigMap. +type Publisher struct { + client client.Client + cfg *config.Config + log logr.Logger +} + +// NewPublisher creates a new Publisher. +func NewPublisher(c client.Client, cfg *config.Config, log logr.Logger) *Publisher { + return &Publisher{ + client: c, + cfg: cfg, + log: log, + } +} + +// Publish creates or updates the metadata ConfigMap. +func (p *Publisher) Publish(ctx context.Context) error { + namespace := os.Getenv(EnvReloaderNamespace) + if namespace == "" { + p.log.Info("RELOADER_NAMESPACE is not set, skipping meta info configmap creation") + return nil + } + + metaInfo := NewMetaInfo(p.cfg) + configMap := metaInfo.ToConfigMap() + + existing := &corev1.ConfigMap{} + err := p.client.Get(ctx, client.ObjectKey{ + Name: ConfigMapName, + Namespace: namespace, + }, existing) + + if err != nil { + if !errors.IsNotFound(err) { + return fmt.Errorf("failed to get existing meta info configmap: %w", err) + } + p.log.Info("Creating meta info configmap") + if err := p.client.Create(ctx, configMap, client.FieldOwner(FieldManager)); err != nil { + return fmt.Errorf("failed to create meta info configmap: %w", err) + } + p.log.Info("Meta info configmap created successfully") + return nil + } + + p.log.Info("Meta info configmap already exists, updating it") + existing.Data = configMap.Data + existing.Labels = configMap.Labels + if err := p.client.Update(ctx, existing, client.FieldOwner(FieldManager)); err != nil { + return fmt.Errorf("failed to update meta info configmap: %w", err) + } + p.log.Info("Meta info configmap updated successfully") + return nil +} + +// PublishMetaInfoConfigMap is a convenience function that creates a Publisher and calls Publish. +func PublishMetaInfoConfigMap(ctx context.Context, c client.Client, cfg *config.Config, log logr.Logger) error { + publisher := NewPublisher(c, cfg, log) + return publisher.Publish(ctx) +} + +// CreateOrUpdate creates or updates the metadata ConfigMap using the provided client. +func CreateOrUpdate(c client.Client, cfg *config.Config, log logr.Logger) error { + ctx := context.Background() + return PublishMetaInfoConfigMap(ctx, c, cfg, log) +} diff --git a/internal/pkg/reload/change.go b/internal/pkg/reload/change.go new file mode 100644 index 00000000..b7fa4443 --- /dev/null +++ b/internal/pkg/reload/change.go @@ -0,0 +1,56 @@ +package reload + +import ( + corev1 "k8s.io/api/core/v1" +) + +// EventType represents the type of change event. +type EventType string + +const ( + // EventTypeCreate indicates a resource was created. + EventTypeCreate EventType = "create" + // EventTypeUpdate indicates a resource was updated. + EventTypeUpdate EventType = "update" + // EventTypeDelete indicates a resource was deleted. + EventTypeDelete EventType = "delete" +) + +// ResourceChange represents a change event for a ConfigMap or Secret. +type ResourceChange interface { + IsNil() bool + GetEventType() EventType + GetName() string + GetNamespace() string + GetAnnotations() map[string]string + GetResourceType() ResourceType + ComputeHash(hasher *Hasher) string +} + +// ConfigMapChange represents a change event for a ConfigMap. +type ConfigMapChange struct { + ConfigMap *corev1.ConfigMap + 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) } + +// SecretChange represents a change event for a Secret. +type SecretChange struct { + Secret *corev1.Secret + 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) } diff --git a/internal/pkg/reload/decision.go b/internal/pkg/reload/decision.go new file mode 100644 index 00000000..6002b3b2 --- /dev/null +++ b/internal/pkg/reload/decision.go @@ -0,0 +1,30 @@ +package reload + +import ( + "github.com/stakater/Reloader/internal/pkg/workload" +) + +// ReloadDecision contains the result of evaluating whether to reload a workload. +type ReloadDecision struct { + // Workload is the workload accessor. + Workload workload.WorkloadAccessor + // ShouldReload indicates whether the workload should be reloaded. + ShouldReload bool + // AutoReload indicates if this is an auto-reload. + AutoReload bool + // Reason provides a human-readable explanation. + Reason string + // Hash is the computed hash of the resource content. + Hash string +} + +// FilterDecisions returns only decisions where ShouldReload is true. +func FilterDecisions(decisions []ReloadDecision) []ReloadDecision { + var result []ReloadDecision + for _, d := range decisions { + if d.ShouldReload { + result = append(result, d) + } + } + return result +} diff --git a/internal/pkg/reload/matcher.go b/internal/pkg/reload/matcher.go index d7a26fbd..e817f7f5 100644 --- a/internal/pkg/reload/matcher.go +++ b/internal/pkg/reload/matcher.go @@ -7,28 +7,6 @@ import ( "github.com/stakater/Reloader/internal/pkg/config" ) -// ResourceType represents the type of Kubernetes resource. -type ResourceType string - -const ( - // ResourceTypeConfigMap represents a ConfigMap resource. - ResourceTypeConfigMap ResourceType = "configmap" - // ResourceTypeSecret represents a Secret resource. - ResourceTypeSecret ResourceType = "secret" -) - -// Kind returns the capitalized Kubernetes Kind (e.g., "ConfigMap", "Secret"). -func (r ResourceType) Kind() string { - switch r { - case ResourceTypeConfigMap: - return "ConfigMap" - case ResourceTypeSecret: - return "Secret" - default: - return string(r) - } -} - // MatchResult contains the result of checking if a workload should be reloaded. type MatchResult struct { ShouldReload bool diff --git a/internal/pkg/reload/resource_type.go b/internal/pkg/reload/resource_type.go new file mode 100644 index 00000000..0404e815 --- /dev/null +++ b/internal/pkg/reload/resource_type.go @@ -0,0 +1,23 @@ +package reload + +// ResourceType represents the type of Kubernetes resource. +type ResourceType string + +const ( + // ResourceTypeConfigMap represents a ConfigMap resource. + ResourceTypeConfigMap ResourceType = "configmap" + // ResourceTypeSecret represents a Secret resource. + ResourceTypeSecret ResourceType = "secret" +) + +// Kind returns the capitalized Kubernetes Kind (e.g., "ConfigMap", "Secret"). +func (r ResourceType) Kind() string { + switch r { + case ResourceTypeConfigMap: + return "ConfigMap" + case ResourceTypeSecret: + return "Secret" + default: + return string(r) + } +} diff --git a/internal/pkg/reload/service.go b/internal/pkg/reload/service.go index acec2d59..96460897 100644 --- a/internal/pkg/reload/service.go +++ b/internal/pkg/reload/service.go @@ -28,82 +28,6 @@ func NewService(cfg *config.Config) *Service { } } -// ResourceChange represents a change event for a ConfigMap or Secret. -type ResourceChange interface { - IsNil() bool - GetEventType() EventType - GetName() string - GetNamespace() string - GetAnnotations() map[string]string - GetResourceType() ResourceType - ComputeHash(hasher *Hasher) string -} - -// ConfigMapChange represents a change event for a ConfigMap. -type ConfigMapChange struct { - ConfigMap *corev1.ConfigMap - 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) } - -// SecretChange represents a change event for a Secret. -type SecretChange struct { - Secret *corev1.Secret - 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) } - -// EventType represents the type of change event. -type EventType string - -const ( - // EventTypeCreate indicates a resource was created. - EventTypeCreate EventType = "create" - // EventTypeUpdate indicates a resource was updated. - EventTypeUpdate EventType = "update" - // EventTypeDelete indicates a resource was deleted. - EventTypeDelete EventType = "delete" -) - -// ReloadDecision contains the result of evaluating whether to reload a workload. -type ReloadDecision struct { - // Workload is the workload accessor. - Workload workload.WorkloadAccessor - // ShouldReload indicates whether the workload should be reloaded. - ShouldReload bool - // AutoReload indicates if this is an auto-reload. - AutoReload bool - // Reason provides a human-readable explanation. - Reason string - // Hash is the computed hash of the resource content. - Hash string -} - -// FilterDecisions returns only decisions where ShouldReload is true. -func FilterDecisions(decisions []ReloadDecision) []ReloadDecision { - var result []ReloadDecision - for _, d := range decisions { - if d.ShouldReload { - result = append(result, d) - } - } - return result -} - // Process evaluates all workloads to determine which should be reloaded. func (s *Service) Process(change ResourceChange, workloads []workload.WorkloadAccessor) []ReloadDecision { if change.IsNil() { @@ -129,7 +53,6 @@ func (s *Service) Process(change ResourceChange, workloads []workload.WorkloadAc ) } - func (s *Service) processResource( resourceName string, resourceNamespace string,