Skip to content
Open
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
b9a7b89
feat: add v2 foundation packages for config and workload abstraction
TheiLLeniumStudios Dec 28, 2025
dce45a4
refactor: migrate reloader command to use new config package
TheiLLeniumStudios Dec 28, 2025
94e3fcd
feat: Add reload package with core matching and strategy logic
TheiLLeniumStudios Dec 28, 2025
0a2aa12
feat: Add controller-runtime reconcilers and unit tests for reload pa…
TheiLLeniumStudios Dec 28, 2025
ce1e7df
feat: Add webhook notification and conflict retry for Reloader v2
TheiLLeniumStudios Dec 28, 2025
b5df945
feat: reload execution and observability
TheiLLeniumStudios Dec 28, 2025
3cb45e8
feat: Implement NamespaceReconciler for namespace label selector filt…
TheiLLeniumStudios Dec 28, 2025
f70c4d2
feat: Argo rollouts workload + refactor of alerting
TheiLLeniumStudios Dec 28, 2025
8b3ad89
chore: Fix formatting issues
TheiLLeniumStudios Dec 28, 2025
f48c5ac
feat: Migration to new implementation
TheiLLeniumStudios Dec 28, 2025
9d588ca
feat: Add reconciler test cases and config test cases
TheiLLeniumStudios Dec 28, 2025
3defc8b
refactor: Move all common reconcile logic to lister and reload handler
TheiLLeniumStudios Dec 28, 2025
c19058a
refactor: Re-use a lot of code and move to specific packages
TheiLLeniumStudios Dec 28, 2025
3cf0119
refactor(workload): centralize workload listing with registry-based l…
TheiLLeniumStudios Dec 28, 2025
3a8c300
refactor: unify label set implementations and rename variables for cl…
TheiLLeniumStudios Dec 28, 2025
fa60c1d
refactor(reload): move resource types and reload decision structs to …
TheiLLeniumStudios Dec 28, 2025
841f6f3
feat: Improve test coverage of important packages
TheiLLeniumStudios Dec 28, 2025
0c40064
build: update build configuration to use internal metadata package an…
TheiLLeniumStudios Dec 28, 2025
1725175
feat: e2e tests and a lot of refactoring for existing tests
TheiLLeniumStudios Dec 28, 2025
ced6ffa
refactor: Move test helpers to testutil
TheiLLeniumStudios Dec 28, 2025
cca7383
fix: Linting issues
TheiLLeniumStudios Dec 28, 2025
612006c
feat: Upgrade all go packages
TheiLLeniumStudios Dec 28, 2025
2eeb44d
fix: Add missing cmd for reloader due to gitignore issues
TheiLLeniumStudios Dec 28, 2025
9a5fbf1
feat: Improve slack alerts
TheiLLeniumStudios Dec 28, 2025
2f9633c
feat: Use viper for config handling and flags
TheiLLeniumStudios Dec 28, 2025
4db9e59
fix: Linting issues
TheiLLeniumStudios Dec 28, 2025
8bce8e9
chore: Cleanup code
TheiLLeniumStudios Dec 28, 2025
9f331ca
refactor(reloader): replace CreateOrUpdate with Runnable for metadata…
TheiLLeniumStudios Dec 28, 2025
fa5f185
feat: DeploymentConfig support
TheiLLeniumStudios Dec 28, 2025
b55e597
fix: Issues with paused rollouts and test cases
TheiLLeniumStudios Dec 28, 2025
da6f33c
feat: Test cases for envvar strategy and more
TheiLLeniumStudios Dec 28, 2025
c785067
feat: Use strategic merge to patch workloads and add metrics for load…
TheiLLeniumStudios Jan 4, 2026
5548ce5
feat: Introduce a Generic ResourceReconciler and a generic BaseWorklo…
TheiLLeniumStudios Jan 4, 2026
bd030fe
fix: A bunch of issues surfaced up via e2e tests and remove old e2e ones
TheiLLeniumStudios Jan 7, 2026
5f7f3a5
feat: golangci-lint config and fixes
TheiLLeniumStudios Jan 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .github/workflows/pull_request.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,12 @@ jobs:
kubectl cluster-info


- name: Test
- name: Unit Tests
run: make test

- name: E2E Tests
run: make e2e

- name: Generate Tags
id: generate_tag
run: |
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ _gopath/
.vscode
vendor
dist
Reloader
/reloader
/Reloader
!**/chart/reloader
*.tgz
styles/
Expand Down
9 changes: 8 additions & 1 deletion .goreleaser.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
builds:
- env:
- main: ./cmd/reloader
binary: reloader
env:
- CGO_ENABLED=0
goos:
- windows
Expand All @@ -11,6 +13,11 @@ builds:
- arm
- arm64
- ppc64le
ldflags:
- -s -w
- -X github.com/stakater/Reloader/internal/pkg/metadata.Version={{.Version}}
- -X github.com/stakater/Reloader/internal/pkg/metadata.Commit={{.Commit}}
- -X github.com/stakater/Reloader/internal/pkg/metadata.BuildDate={{.Date}}
archives:
- name_template: "{{ .ProjectName }}_v{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}"
snapshot:
Expand Down
12 changes: 6 additions & 6 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,8 @@ COPY go.sum go.sum
RUN go mod download

# Copy the go source
COPY main.go main.go
COPY cmd/ cmd/
COPY internal/ internal/
COPY pkg/ pkg/

# Build
RUN CGO_ENABLED=0 \
Expand All @@ -34,10 +33,11 @@ RUN CGO_ENABLED=0 \
GOPROXY=${GOPROXY} \
GOPRIVATE=${GOPRIVATE} \
GO111MODULE=on \
go build -ldflags="-s -w -X github.com/stakater/Reloader/pkg/common.Version=${VERSION} \
-X github.com/stakater/Reloader/pkg/common.Commit=${COMMIT} \
-X github.com/stakater/Reloader/pkg/common.BuildDate=${BUILD_DATE}" \
-installsuffix 'static' -mod=mod -a -o manager ./
go build -ldflags="-s -w \
-X github.com/stakater/Reloader/internal/pkg/metadata.Version=${VERSION} \
-X github.com/stakater/Reloader/internal/pkg/metadata.Commit=${COMMIT} \
-X github.com/stakater/Reloader/internal/pkg/metadata.BuildDate=${BUILD_DATE}" \
-installsuffix 'static' -mod=mod -a -o manager ./cmd/reloader

# Use distroless as minimal base image to package the manager binary
# Refer to https://github.com/GoogleContainerTools/distroless for more details
Expand Down
1 change: 1 addition & 0 deletions Dockerfile.ubi
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
ARG BUILDER_IMAGE
ARG BASE_IMAGE

# First stage: Build the binary (using the standard Dockerfile as builder)
FROM --platform=${BUILDPLATFORM} ${BUILDER_IMAGE} AS SRC

Check warning on line 5 in Dockerfile.ubi

View workflow job for this annotation

GitHub Actions / Build

Default value for global ARG results in an empty or invalid base image name

InvalidDefaultArgInFrom: Default value for ARG ${BUILDER_IMAGE} results in empty or invalid base image name More info: https://docs.docker.com/go/dockerfile/rule/invalid-default-arg-in-from/

Check warning on line 5 in Dockerfile.ubi

View workflow job for this annotation

GitHub Actions / Build

Stage names should be lowercase

StageNameCasing: Stage name 'SRC' should be lowercase More info: https://docs.docker.com/go/dockerfile/rule/stage-name-casing/

FROM ${BASE_IMAGE:-registry.access.redhat.com/ubi9/ubi:latest} AS ubi
ARG TARGETARCH
Expand Down
18 changes: 14 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,17 @@ BUILD=

GOCMD = go
GOFLAGS ?= $(GOFLAGS:)
LDFLAGS =
GOPROXY ?=
GOPRIVATE ?=

# Version information for ldflags
GIT_COMMIT ?= $(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown")
BUILD_DATE ?= $(shell date -u +"%Y-%m-%dT%H:%M:%SZ")
LDFLAGS = -s -w \
-X github.com/stakater/Reloader/internal/pkg/metadata.Version=$(VERSION) \
-X github.com/stakater/Reloader/internal/pkg/metadata.Commit=$(GIT_COMMIT) \
-X github.com/stakater/Reloader/internal/pkg/metadata.BuildDate=$(BUILD_DATE)

## Location to install dependencies to
LOCALBIN ?= $(shell pwd)/bin
$(LOCALBIN):
Expand Down Expand Up @@ -97,10 +104,10 @@ install:
"$(GOCMD)" mod download

run:
go run ./main.go
go run ./cmd/reloader

build:
"$(GOCMD)" build ${GOFLAGS} ${LDFLAGS} -o "${BINARY}"
"$(GOCMD)" build ${GOFLAGS} -ldflags '${LDFLAGS}' -o "${BINARY}" ./cmd/reloader

lint: golangci-lint ## Run golangci-lint on the codebase
$(GOLANGCI_LINT) run ./...
Expand Down Expand Up @@ -140,7 +147,10 @@ manifest:
docker manifest annotate --arch $(ARCH) $(REPOSITORY_GENERIC) $(REPOSITORY_ARCH)

test:
"$(GOCMD)" test -timeout 1800s -v ./...
"$(GOCMD)" test -timeout 1800s -v -short ./cmd/... ./internal/...

e2e:
"$(GOCMD)" test -timeout 1800s -v ./test/...

stop:
@docker stop "${BINARY}"
Expand Down
218 changes: 218 additions & 0 deletions cmd/reloader/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
package main

import (
"context"
"fmt"
"net/http"
_ "net/http/pprof"
"os"
"os/signal"
"syscall"
"time"

"github.com/go-logr/logr"
"github.com/go-logr/zerologr"
"github.com/rs/zerolog"
"github.com/spf13/cobra"
"k8s.io/client-go/discovery"
controllerruntime "sigs.k8s.io/controller-runtime"

"github.com/stakater/Reloader/internal/pkg/config"
"github.com/stakater/Reloader/internal/pkg/controller"
"github.com/stakater/Reloader/internal/pkg/metadata"
"github.com/stakater/Reloader/internal/pkg/metrics"
"github.com/stakater/Reloader/internal/pkg/openshift"
)

// Environment variable names for pod identity in HA mode.
const (
podNameEnv = "POD_NAME"
podNamespaceEnv = "POD_NAMESPACE"
)

// cfg holds the configuration for this reloader instance.
var cfg *config.Config

func main() {
if err := newReloaderCommand().Execute(); err != nil {
os.Exit(1)
}
}

func newReloaderCommand() *cobra.Command {
cfg = config.NewDefault()

cmd := &cobra.Command{
Use: "reloader",
Short: "A watcher for your Kubernetes cluster",
RunE: run,
}

config.BindFlags(cmd.PersistentFlags(), cfg)
return cmd
}

func run(cmd *cobra.Command, args []string) error {
if err := config.ApplyFlags(cfg); err != nil {
return fmt.Errorf("applying flags: %w", err)
}

if err := cfg.Validate(); err != nil {
return fmt.Errorf("validating config: %w", err)
}

if cfg.EnableHA {
if err := validateHAEnvs(); err != nil {
return err
}
cfg.LeaderElection.Identity = os.Getenv(podNameEnv)
if cfg.LeaderElection.Namespace == "" {
cfg.LeaderElection.Namespace = os.Getenv(podNamespaceEnv)
}
}

log, err := configureLogging(cfg.LogFormat, cfg.LogLevel)
if err != nil {
return fmt.Errorf("configuring logging: %w", err)
}

controllerruntime.SetLogger(log)

log.Info("Starting Reloader")

if ns := os.Getenv("KUBERNETES_NAMESPACE"); ns == "" {
log.Info("KUBERNETES_NAMESPACE is unset, will detect changes in all namespaces")
}

if len(cfg.NamespaceSelectors) > 0 {
log.Info("namespace-selector is set", "selectors", cfg.NamespaceSelectorStrings)
}

if len(cfg.ResourceSelectors) > 0 {
log.Info("resource-label-selector is set", "selectors", cfg.ResourceSelectorStrings)
}

if cfg.WebhookURL != "" {
log.Info("webhook-url is set, will only send webhook, no resources will be reloaded", "url", cfg.WebhookURL)
}

if cfg.EnableHA {
log.Info(
"high-availability mode enabled",
"leaderElectionID", cfg.LeaderElection.LockName,
"leaderElectionNamespace", cfg.LeaderElection.Namespace,
)
}

collectors := metrics.SetupPrometheusEndpoint()

mgr, err := controller.NewManager(
controller.ManagerOptions{
Config: cfg,
Log: log,
Collectors: &collectors,
},
)
if err != nil {
return fmt.Errorf("creating manager: %w", err)
}

if config.ShouldAutoDetectOpenShift() {
restConfig := controllerruntime.GetConfigOrDie()
discoveryClient, err := discovery.NewDiscoveryClientForConfig(restConfig)
if err != nil {
log.V(1).Info("Failed to create discovery client for DeploymentConfig detection", "error", err)
} else if openshift.HasDeploymentConfigSupport(discoveryClient, log) {
cfg.DeploymentConfigEnabled = true
}
}

if err := controller.SetupReconcilers(mgr, cfg, log, &collectors); err != nil {
return fmt.Errorf("setting up reconcilers: %w", err)
}

if err := mgr.Add(metadata.Runnable(mgr.GetClient(), cfg, log)); err != nil {
log.Error(err, "Failed to add metadata publisher")
// Non-fatal, continue starting
}

if cfg.EnablePProf {
go startPProfServer(log)
}

ctx, cancel := context.WithCancel(context.Background())
defer cancel()

sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
go func() {
sig := <-sigCh
log.Info("Received signal, shutting down", "signal", sig)
cancel()
}()

log.Info("Starting controller manager")
if err := controller.RunManager(ctx, mgr, log); err != nil {
return fmt.Errorf("manager exited with error: %w", err)
}

log.Info("Reloader shutdown complete")
return nil
}

func configureLogging(logFormat, logLevel string) (logr.Logger, error) {
// Parse log level
var level zerolog.Level
switch logLevel {
case "trace":
level = zerolog.TraceLevel
case "debug":
level = zerolog.DebugLevel
case "info", "":
level = zerolog.InfoLevel
case "warn", "warning":
level = zerolog.WarnLevel
case "error":
level = zerolog.ErrorLevel
default:
return logr.Logger{}, fmt.Errorf("unsupported log level: %q", logLevel)
}

var zl zerolog.Logger
switch logFormat {
case "json":
zl = zerolog.New(os.Stdout).Level(level).With().Timestamp().Logger()
case "":
// Human-readable console output
zl = zerolog.New(
zerolog.ConsoleWriter{
Out: os.Stdout,
TimeFormat: time.RFC3339,
},
).Level(level).With().Timestamp().Logger()
default:
return logr.Logger{}, fmt.Errorf("unsupported log format: %q", logFormat)
}

return zerologr.New(&zl), nil
}

func validateHAEnvs() error {
podName := os.Getenv(podNameEnv)
podNamespace := os.Getenv(podNamespaceEnv)

if podName == "" {
return fmt.Errorf("%s not set, cannot run in HA mode", podNameEnv)
}
if podNamespace == "" {
return fmt.Errorf("%s not set, cannot run in HA mode", podNamespaceEnv)
}
return nil
}

func startPProfServer(log logr.Logger) {
log.Info("Starting pprof server", "addr", cfg.PProfAddr)
if err := http.ListenAndServe(cfg.PProfAddr, nil); err != nil {
log.Error(err, "Failed to start pprof server")
}
}
2 changes: 1 addition & 1 deletion deployments/kubernetes/chart/reloader/Chart.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
apiVersion: v1
name: reloader
description: Reloader chart that runs on kubernetes
version: 2.2.7
version: 2.3.0
appVersion: v1.4.12
keywords:
- Reloader
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ rules:
- get
- update
- patch
- watch
{{- if .Values.reloader.ignoreCronJobs }}{{- else }}
- apiGroups:
- "batch"
Expand Down
1 change: 1 addition & 0 deletions deployments/kubernetes/chart/reloader/templates/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ rules:
- get
- update
- patch
- watch
- apiGroups:
- "batch"
resources:
Expand Down
Loading
Loading