Compare commits

...

21 Commits

Author SHA1 Message Date
Safwan
db275d516e update docker file to use go 1.24.6 2025-09-15 15:23:40 +05:00
github-actions[bot]
eabf88515a Bump version to 1.4.8 (#1008)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: msafwankarim <66724151+msafwankarim@users.noreply.github.com>
2025-09-15 14:43:32 +05:00
Sebastien NICOT
3a1cc8f348 fix: bump go version to fix CVEs (#1007) 2025-09-15 14:18:31 +05:00
Pradeep Lakshmi Narasimha
dd0807e951 fix: Controller not respecting ignore* flags 2025-08-29 12:24:02 +02:00
Muhammad Safwan Karim
b8edc25177 Fix comment for adding Stakater Helm repository (#1004) 2025-08-25 18:58:32 +05:00
Muhammad Safwan Karim
f9d658d3b4 Clarify PR process for Helm chart version updates (#1003)
Updated instructions for creating a PR to include a specific label for Helm chart version updates.
2025-08-25 18:50:27 +05:00
Muhammad Safwan Karim
816ad6d430 bump image tag and chart version (#1002) 2025-08-25 17:53:22 +05:00
M Ahmad Mujtaba
19a76258d0 Merge pull request #998 from stakater/fix-broken-test
fixed broken test
2025-08-22 15:22:40 +05:00
Felix Tonnvik
aa481d9568 fixed broken test 2025-08-19 11:06:08 +02:00
Muhammad Safwan Karim
177d2756a8 Moved some functions to common package (#988)
* separate methods

* basic refactoring

* moved common code to util package to use it in gateway

* common check for argo rollouts

* made code compilable with latest changes on master

* Moved options to separate package and created CommandLineOptions instance that will be in sync with options values.

* reverted extra changes

* initialize CommandLineOptions with default options in module init

* wait for paused at annotation before checking deployment paused

* moved things around to fix things

* reverted unnecessary changes

* reverted rolling_upgrade changes

* reverted extra change

* additional checks in reloader

* refactor: ShouldReloadInternal method. It will be called by Reloader
ShouldReload has some additional resource/namespace filter checks which are not needed for Reloader

* added test cases

* moved config to sharable packae

* moved resource selector and label selctor methods

* fixed pipeline

* removed map.yaml

* removed vague comment
2025-08-15 15:51:47 +05:00
Felix
9b2af6f9b7 Merge pull request #989 from praddy26/fix-operator-pod-crash
fix: handle operator crash when using workloadRef
2025-08-15 12:06:09 +02:00
Felix
7c4899a7eb Merge pull request #981 from nitinverma9/add-dnsConfig-support-for-deployment
reloader: Add in support for dnsConfig for pod reloader.
2025-08-15 12:04:30 +02:00
Abdul Aziz
54d44858f8 Update README.md (#993)
* Update README.md

* Update README.md

Updated sponsor badge

* Update README.md

Fix line breaks for lint
2025-08-14 07:39:49 +02:00
Abdul Aziz
6304a9e5ab Update index.md (#995)
Add sponsor link at the bottom
2025-08-14 07:39:29 +02:00
Pradeep Lakshmi Narasimha
1e6a6ec2d9 fix: handle operator crash when using workloadRef 2025-08-14 07:55:56 +05:30
Nitin Verma
42cd7e71a2 reloader: Add in support for dnsConfig for pod reloader. This is supported as a common standard in most public helm charts, so decided to create a PR here. 2025-08-04 07:52:57 +01:00
M Ahmad Mujtaba
1107fee109 fix applied for issue-PR-975 (#987)
Co-authored-by: mahmadmujtaba <ahmad.mujtaba@stakater.com>
2025-08-01 15:54:32 +05:00
Muhammad Safwan Karim
9e33dac9ef Bump chart (#986)
* Bump chart

* empty
2025-07-30 13:20:23 +05:00
Muhammad Safwan Karim
517fd33fb1 Added pause deployment annotation overrides (#983)
* added pprof flag

* updated chart

* fixed linting and spelling

* custom_annotation for pause deployments related annotaitons
2025-07-29 19:50:13 +05:00
Muhammad Safwan Karim
1e46d44c7c Added pprof flag (#980)
* added pprof flag

* updated chart

* fixed linting and spelling
2025-07-29 15:49:30 +05:00
Muhammad Safwan Karim
49409dce54 Extracted some functions to public package to reuse them in gateway (#966)
* separate methods

* basic refactoring

* moved common code to util package to use it in gateway

* common check for argo rollouts

* made code compilable with latest changes on master

* Moved options to separate package and created CommandLineOptions instance that will be in sync with options values.

* reverted extra changes

* initialize CommandLineOptions with default options in module init

* wait for paused at annotation before checking deployment paused

* moved things around to fix things

* reverted unnecessary changes

* reverted rolling_upgrade changes

* reverted extra change
2025-07-29 11:50:02 +05:00
32 changed files with 1321 additions and 584 deletions

View File

@@ -1,3 +1,3 @@
# Code of Conduct
Reloader follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md).
Reloader follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/main/code-of-conduct.md).

View File

@@ -2,7 +2,7 @@ ARG BUILDER_IMAGE
ARG BASE_IMAGE
# Build the manager binary
FROM --platform=${BUILDPLATFORM} ${BUILDER_IMAGE:-golang:1.24.4} AS builder
FROM --platform=${BUILDPLATFORM} ${BUILDER_IMAGE:-golang:1.24.6} AS builder
ARG TARGETOS
ARG TARGETARCH
@@ -34,9 +34,9 @@ RUN CGO_ENABLED=0 \
GOPROXY=${GOPROXY} \
GOPRIVATE=${GOPRIVATE} \
GO111MODULE=on \
go build -ldflags="-s -w -X github.com/stakater/Reloader/pkg/metainfo.Version=${VERSION} \
-X github.com/stakater/Reloader/pkg/metainfo.Commit=${COMMIT} \
-X github.com/stakater/Reloader/pkg/metainfo.BuildDate=${BUILD_DATE}" \
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 ./
# Use distroless as minimal base image to package the manager binary

View File

@@ -2,6 +2,7 @@
<img src="assets/web/reloader.jpg" alt="Reloader" width="40%"/>
</p>
[![💖 Sponsor Our Work](https://img.shields.io/badge/Sponsor%20Our%20Work-FF8C00?style=flat-square&logo=github-sponsors&logoColor=white)](https://github.com/sponsors/stakater?utm_source=github&utm_medium=readme&utm_campaign=reloader)
[![Go Report Card](https://goreportcard.com/badge/github.com/stakater/reloader?style=flat-square)](https://goreportcard.com/report/github.com/stakater/reloader)
[![Go Doc](https://img.shields.io/badge/godoc-reference-blue.svg?style=flat-square)](https://godoc.org/github.com/stakater/reloader)
[![Release](https://img.shields.io/github/release/stakater/reloader.svg?style=flat-square)](https://github.com/stakater/reloader/releases/latest)
@@ -328,13 +329,30 @@ Reloader supports multiple strategies for triggering rolling updates when a watc
|------|-------------|
| `--resources-to-ignore=configmaps` | Ignore ConfigMaps (only one type can be ignored at a time) |
| `--resources-to-ignore=secrets` | Ignore Secrets (cannot combine with configMaps) |
| `--ignored-workload-types=jobs,cronjobs` | Ignore specific workload types from reload monitoring |
| `--resource-label-selector=key=value` | Only watch ConfigMaps/Secrets with matching labels |
> **⚠️ Note:**
> Only **one** resource type can be ignored at a time.
> Trying to ignore **both `configmaps` and `secrets`** will cause an error in Reloader.
> **⚠️ Note:**
>
> Only **one** resource type can be ignored at a time.
> Trying to ignore **both `configmaps` and `secrets`** will cause an error in Reloader.
> ✅ **Workaround:** Scale the Reloader deployment to `0` replicas if you want to disable it completely.
**💡 Workload Type Examples:**
```bash
# Ignore only Jobs
--ignored-workload-types=jobs
# Ignore only CronJobs
--ignored-workload-types=cronjobs
# Ignore both (comma-separated)
--ignored-workload-types=jobs,cronjobs
```
> **🔧 Use Case:** Ignoring workload types is useful when you don't want certain types of workloads to be automatically reloaded.
#### 3. 🧩 Namespace Filtering
| Flag | Description |
@@ -358,6 +376,13 @@ These flags allow you to redefine annotation keys used in your workloads or reso
| `--pause-deployment-annotation` | Overrides `deployment.reloader.stakater.com/pause-period` |
| `--pause-deployment-time-annotation` | Overrides `deployment.reloader.stakater.com/paused-at` |
### 5. 🕷️ Debugging
| Flag | Description |
|--- |-------------|
| `--enable-pprof` | Enables `pprof` for profiling |
| `--pprof-addr` | Address to start `pprof` server on. Default is `:6060` |
## Compatibility
Reloader is compatible with Kubernetes >= 1.19
@@ -412,7 +437,7 @@ To make a GitHub release:
1. Code owners run a dispatch mode workflow to automatically generate version and manifests on the release branch
1. A PR is created to bump the image version on the release branch, example: [PR-798](https://github.com/stakater/Reloader/pull/798)
1. Code owners create a GitHub release with tag `vX.Y.Z` and target branch `release-vX.Y.Z`, which triggers creation of images
1. Code owners create a PR to update the Helm chart version, example: [PR-846](https://github.com/stakater/Reloader/pull/846)
1. Code owners create a PR with `release/helm-chart` label to update the Helm chart version, example: [PR-846](https://github.com/stakater/Reloader/pull/846)
_Repository git tagging_: Push to the main branch will create a merge-image and merge-tag named `merge-${{ github.event.number }}`, for example `merge-800` when pull request number 800 is merged.

View File

@@ -1 +1 @@
1.1.0
1.4.8

View File

@@ -1,8 +1,8 @@
apiVersion: v1
name: reloader
description: Reloader chart that runs on kubernetes
version: 2.2.0
appVersion: v1.4.5
version: 2.2.2
appVersion: v1.4.7
keywords:
- Reloader
- kubernetes

View File

@@ -5,6 +5,7 @@ If you have configured helm on your cluster, you can add Reloader to helm from o
## Installation
```bash
# Add stakater helm repoository
helm repo add stakater https://stakater.github.io/stakater-charts
helm repo update
@@ -14,6 +15,8 @@ helm install stakater/reloader # For helm3 add --generate-name flag or set the r
helm install {{RELEASE_NAME}} stakater/reloader -n {{NAMESPACE}} --set reloader.watchGlobally=false # By default, Reloader watches in all namespaces. To watch in single namespace, set watchGlobally=false
helm install stakater/reloader --set reloader.watchGlobally=false --namespace test --generate-name # Install Reloader in `test` namespace which will only watch `Deployments`, `Daemonsets` `Statefulsets` and `Rollouts` in `test` namespace.
helm install stakater/reloader --set reloader.ignoreJobs=true --set reloader.ignoreCronJobs=true --generate-name # Install Reloader ignoring Jobs and CronJobs from reload monitoring
```
## Uninstalling
@@ -47,6 +50,8 @@ helm uninstall {{RELEASE_NAME}} -n {{NAMESPACE}}
| `reloader.isOpenshift` | Enable OpenShift DeploymentConfigs. Valid value are either `true` or `false` | boolean | `false` |
| `reloader.ignoreSecrets` | To ignore secrets. Valid value are either `true` or `false`. Either `ignoreSecrets` or `ignoreConfigMaps` can be ignored, not both at the same time | boolean | `false` |
| `reloader.ignoreConfigMaps` | To ignore configmaps. Valid value are either `true` or `false` | boolean | `false` |
| `reloader.ignoreJobs` | To ignore jobs from reload monitoring. Valid value are either `true` or `false`. Translates to `--ignored-workload-types=jobs` | boolean | `false` |
| `reloader.ignoreCronJobs` | To ignore CronJobs from reload monitoring. Valid value are either `true` or `false`. Translates to `--ignored-workload-types=cronjobs` | boolean | `false` |
| `reloader.reloadOnCreate` | Enable reload on create events. Valid value are either `true` or `false` | boolean | `false` |
| `reloader.reloadOnDelete` | Enable reload on delete events. Valid value are either `true` or `false` | boolean | `false` |
| `reloader.syncAfterRestart` | Enable sync after Reloader restarts for **Add** events, works only when reloadOnCreate is `true`. Valid value are either `true` or `false` | boolean | `false` |
@@ -57,6 +62,8 @@ helm uninstall {{RELEASE_NAME}} -n {{NAMESPACE}}
| `reloader.logFormat` | Set type of log format. Value could be either `json` or `""` | string | `""` |
| `reloader.watchGlobally` | Allow Reloader to watch in all namespaces (`true`) or just in a single namespace (`false`) | boolean | `true` |
| `reloader.enableHA` | Enable leadership election allowing you to run multiple replicas | boolean | `false` |
| `reloader.enablePProf` | Enables pprof for profiling | boolean | `false` |
| `reloader.pprofAddr` | Address to start pprof server on | string | `:6060` |
| `reloader.readOnlyRootFileSystem` | Enforce readOnlyRootFilesystem | boolean | `false` |
| `reloader.legacy.rbac` | | boolean | `false` |
| `reloader.matchLabels` | Pod labels to match | map | `{}` |
@@ -82,7 +89,10 @@ helm uninstall {{RELEASE_NAME}} -n {{NAMESPACE}}
| `reloader.deployment.resources` | Set container requests and limits (e.g. CPU or memory) | map | `{}` |
| `reloader.deployment.pod.annotations` | Set annotations for pod | map | `{}` |
| `reloader.deployment.priorityClassName` | Set priority class for pod in cluster | string | `""` |
| `reloader.deployment.volumeMounts` | Mount volume | array | `[]` |
| `reloader.deployment.volumes` | Add volume to a pod | array | `[]` |
| `reloader.deployment.dnsConfig` | dns configuration for pods | map | `{}` |
### Other Reloader Parameters
| Parameter | Description | Type | Default |
@@ -110,6 +120,10 @@ helm uninstall {{RELEASE_NAME}} -n {{NAMESPACE}}
- Only one of these resources can be ignored at a time:
- `ignoreConfigMaps` **or** `ignoreSecrets`
- Trying to ignore both will cause Helm template compilation errors
- The `ignoreJobs` and `ignoreCronJobs` flags can be used together or individually
- When both are enabled, translates to `--ignored-workload-types=jobs,cronjobs`
- When used individually, translates to `--ignored-workload-types=jobs` or `--ignored-workload-types=cronjobs`
- These flags prevent Reloader from monitoring and reloading the specified workload types
### Special Integrations
- OpenShift (`DeploymentConfig`) and Argo Rollouts support must be **explicitly enabled**

View File

@@ -71,6 +71,10 @@ spec:
{{- if .Values.reloader.deployment.priorityClassName }}
priorityClassName: {{ .Values.reloader.deployment.priorityClassName }}
{{- end }}
{{- with .Values.reloader.deployment.dnsConfig }}
dnsConfig:
{{- toYaml . | nindent 8 }}
{{- end }}
containers:
{{- if .Values.global.imageRegistry }}
- image: "{{ .Values.global.imageRegistry }}/{{ .Values.image.name }}:{{ .Values.image.tag }}"
@@ -151,7 +155,7 @@ spec:
- name: RELOADER_DEPLOYMENT_NAME
value: {{ template "reloader-fullname" . }}
{{- if .Values.reloader.enableHA }}
- name: POD_NAME
valueFrom:
@@ -206,7 +210,7 @@ spec:
{{- . | toYaml | nindent 10 }}
{{- end }}
{{- end }}
{{- if or (.Values.reloader.logFormat) (.Values.reloader.logLevel) (.Values.reloader.ignoreSecrets) (.Values.reloader.ignoreNamespaces) (include "reloader-namespaceSelector" .) (.Values.reloader.resourceLabelSelector) (.Values.reloader.ignoreConfigMaps) (.Values.reloader.custom_annotations) (eq .Values.reloader.isArgoRollouts true) (eq .Values.reloader.reloadOnCreate true) (eq .Values.reloader.reloadOnDelete true) (ne .Values.reloader.reloadStrategy "default") (.Values.reloader.enableHA) (.Values.reloader.autoReloadAll)}}
{{- if or (.Values.reloader.logFormat) (.Values.reloader.logLevel) (.Values.reloader.ignoreSecrets) (.Values.reloader.ignoreNamespaces) (include "reloader-namespaceSelector" .) (.Values.reloader.resourceLabelSelector) (.Values.reloader.ignoreConfigMaps) (.Values.reloader.custom_annotations) (eq .Values.reloader.isArgoRollouts true) (eq .Values.reloader.reloadOnCreate true) (eq .Values.reloader.reloadOnDelete true) (ne .Values.reloader.reloadStrategy "default") (.Values.reloader.enableHA) (.Values.reloader.autoReloadAll) (.Values.reloader.ignoreJobs) (.Values.reloader.ignoreCronJobs)}}
args:
{{- if .Values.reloader.logFormat }}
- "--log-format={{ .Values.reloader.logFormat }}"
@@ -220,6 +224,13 @@ spec:
{{- if .Values.reloader.ignoreConfigMaps }}
- "--resources-to-ignore=configMaps"
{{- end }}
{{- if and (.Values.reloader.ignoreJobs) (.Values.reloader.ignoreCronJobs) }}
- "--ignored-workload-types=jobs,cronjobs"
{{- else if .Values.reloader.ignoreJobs }}
- "--ignored-workload-types=jobs"
{{- else if .Values.reloader.ignoreCronJobs }}
- "--ignored-workload-types=cronjobs"
{{- end }}
{{- if .Values.reloader.ignoreNamespaces }}
- "--namespaces-to-ignore={{ .Values.reloader.ignoreNamespaces }}"
{{- end }}
@@ -229,6 +240,12 @@ spec:
{{- if .Values.reloader.resourceLabelSelector }}
- "--resource-label-selector={{ .Values.reloader.resourceLabelSelector }}"
{{- end }}
{{- if .Values.reloader.enablePProf }}
- "--enable-pprof"
{{- if and .Values.reloader.pprofAddr }}
- "--pprof-addr={{ .Values.reloader.pprofAddr }}"
{{- end }}
{{- end }}
{{- if .Values.reloader.custom_annotations }}
{{- if .Values.reloader.custom_annotations.configmap }}
- "--configmap-annotation"
@@ -257,6 +274,14 @@ spec:
{{- if .Values.reloader.custom_annotations.match }}
- "--search-match-annotation"
- "{{ .Values.reloader.custom_annotations.match }}"
{{- end }}
{{- if .Values.reloader.custom_annotations.pausePeriod }}
- "--pause-deployment-annotation"
- "{{ .Values.reloader.custom_annotations.pausePeriod }}"
{{- end }}
{{- if .Values.reloader.custom_annotations.pauseTime }}
- "--pause-deployment-annotation"
- "{{ .Values.reloader.custom_annotations.pauseTime }}"
{{- end }}
{{- if .Values.reloader.webhookUrl }}
- "--webhook-url"

View File

@@ -61,3 +61,44 @@ tests:
valueFrom:
fieldRef:
fieldPath: metadata.name
- it: sets ignored-workload-types argument when ignoreJobs is true
set:
reloader:
ignoreJobs: true
asserts:
- contains:
path: spec.template.spec.containers[0].args
content: "--ignored-workload-types=jobs"
- it: sets ignored-workload-types argument when ignoreCronJobs is true
set:
reloader:
ignoreCronJobs: true
asserts:
- contains:
path: spec.template.spec.containers[0].args
content: "--ignored-workload-types=cronjobs"
- it: sets ignored-workload-types argument when both ignoreJobs and ignoreCronJobs are true
set:
reloader:
ignoreJobs: true
ignoreCronJobs: true
asserts:
- contains:
path: spec.template.spec.containers[0].args
content: "--ignored-workload-types=jobs,cronjobs"
- it: does not set ignored-workload-types argument when both ignoreJobs and ignoreCronJobs are false
set:
reloader:
ignoreJobs: false
ignoreCronJobs: false
asserts:
- notContains:
path: spec.template.spec.containers[0].args
content: "--ignored-workload-types=jobs"
- notContains:
path: spec.template.spec.containers[0].args
content: "--ignored-workload-types=cronjobs"

View File

@@ -17,7 +17,7 @@ fullnameOverride: ""
image:
name: stakater/reloader
repository: ghcr.io/stakater/reloader
tag: v1.4.5
tag: v1.4.7
# digest: sha256:1234567
pullPolicy: IfNotPresent
@@ -27,7 +27,11 @@ reloader:
isOpenshift: false
ignoreSecrets: false
ignoreConfigMaps: false
# Set to true to exclude Job workloads from automatic reload monitoring
# Useful when you don't want Jobs to be restarted when their referenced ConfigMaps/Secrets change
ignoreJobs: false
# Set to true to exclude CronJob workloads from automatic reload monitoring
# Useful when you don't want CronJobs to be restarted when their referenced ConfigMaps/Secrets change
ignoreCronJobs: false
reloadOnCreate: false
reloadOnDelete: false
@@ -41,6 +45,10 @@ reloader:
watchGlobally: true
# Set to true to enable leadership election allowing you to run multiple replicas
enableHA: false
# Set to true to enable pprof for profiling
enablePProf: false
# Address to start pprof server on. Default is ":6060"
pprofAddr: ":6060"
# Set to true if you have a pod security policy that enforces readOnlyRootFilesystem
readOnlyRootFileSystem: false
legacy:
@@ -49,6 +57,19 @@ reloader:
# Set to true to expose a prometheus counter of reloads by namespace (this metric may have high cardinality in clusters with many namespaces)
enableMetricsByNamespace: false
deployment:
# Specifies the deployment DNS configuration.
dnsConfig: {}
# nameservers:
# - 1.2.3.4
# searches:
# - ns1.svc.cluster-domain.example
# - my.dns.search.suffix
# options:
# - name: ndots
# value: "1"
# - name: attempts
# value: "3"
# If you wish to run multiple replicas set reloader.enableHA = true
replicas: 1
@@ -68,6 +89,9 @@ reloader:
# operator: "Exists"
affinity: {}
volumeMounts: []
volumes: []
securityContext:
runAsNonRoot: true
runAsUser: 65534
@@ -106,7 +130,7 @@ reloader:
labels:
provider: stakater
group: com.stakater.platform
version: v1.4.5
version: v1.4.7
# Support for extra environment variables.
env:
# Open supports Key value pair as environment variables.
@@ -337,8 +361,4 @@ reloader:
# are applied during the life of a Pod. Possible values are "Off", "Initial", "Recreate", and "Auto".
updateMode: Auto
volumeMounts: []
volumes: []
webhookUrl: ""

View File

@@ -17,7 +17,7 @@ spec:
app: reloader-reloader
spec:
containers:
- image: "ghcr.io/stakater/reloader:v1.1.0"
- image: "ghcr.io/stakater/reloader:v1.4.8"
imagePullPolicy: IfNotPresent
name: reloader-reloader
env:

View File

@@ -141,7 +141,7 @@ spec:
fieldPath: metadata.namespace
- name: RELOADER_DEPLOYMENT_NAME
value: reloader-reloader
image: ghcr.io/stakater/reloader:latest
image: ghcr.io/stakater/reloader:v1.4.8
imagePullPolicy: IfNotPresent
livenessProbe:
failureThreshold: 5

View File

@@ -10,3 +10,17 @@ These are the key features of Reloader:
1. Restart pod in a `rollout` on change in linked/related `ConfigMaps` or `Secrets`
This site contains more details on how Reloader works. For an overview, please see the repository's [README file](https://github.com/stakater/Reloader/blob/master/README.md).
---
<div align="center">
[![💖 Sponsor our work](https://img.shields.io/badge/Sponsor%20Our%20Work-FF8C00?style=for-the-badge&logo=github-sponsors&logoColor=white)](https://github.com/sponsors/stakater?utm_source=docs&utm_medium=footer&utm_campaign=reloader)
<p>
Your support funds maintenance, security updates, and new features for Reloader, plus continued investment in other open source tools.
</p>
</div>
---

2
go.mod
View File

@@ -1,6 +1,6 @@
module github.com/stakater/Reloader
go 1.24.4
go 1.24.6
require (
github.com/argoproj/argo-rollouts v1.8.2

View File

@@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"net/http"
_ "net/http/pprof"
"os"
"strings"
@@ -19,6 +20,7 @@ import (
"github.com/stakater/Reloader/internal/pkg/metrics"
"github.com/stakater/Reloader/internal/pkg/options"
"github.com/stakater/Reloader/internal/pkg/util"
"github.com/stakater/Reloader/pkg/common"
"github.com/stakater/Reloader/pkg/kube"
)
@@ -101,6 +103,7 @@ func getHAEnvs() (string, string) {
}
func startReloader(cmd *cobra.Command, args []string) {
common.GetCommandLineOptions()
err := configureLogging(options.LogFormat, options.LogLevel)
if err != nil {
logrus.Warn(err)
@@ -130,13 +133,13 @@ func startReloader(cmd *cobra.Command, args []string) {
namespaceLabelSelector := ""
if isGlobal {
namespaceLabelSelector, err = util.GetNamespaceLabelSelector()
namespaceLabelSelector, err = common.GetNamespaceLabelSelector(options.NamespaceSelectors)
if err != nil {
logrus.Fatal(err)
}
}
resourceLabelSelector, err := util.GetResourceLabelSelector()
resourceLabelSelector, err := common.GetResourceLabelSelector(options.ResourceSelectors)
if err != nil {
logrus.Fatal(err)
}
@@ -188,8 +191,19 @@ func startReloader(cmd *cobra.Command, args []string) {
go leadership.RunLeaderElection(lock, ctx, cancel, podName, controllers)
}
util.PublishMetaInfoConfigmap(clientset)
common.PublishMetaInfoConfigmap(clientset)
if options.EnablePProf {
go startPProfServer()
}
leadership.SetupLivenessEndpoint()
logrus.Fatal(http.ListenAndServe(constants.DefaultHttpListenAddr, nil))
}
func startPProfServer() {
logrus.Infof("Starting pprof server on %s", options.PProfAddr)
if err := http.ListenAndServe(options.PProfAddr, nil); err != nil {
logrus.Errorf("Failed to start pprof server: %v", err)
}
}

View File

@@ -15,6 +15,7 @@ import (
"github.com/stakater/Reloader/internal/pkg/options"
"github.com/stakater/Reloader/internal/pkg/testutil"
"github.com/stakater/Reloader/internal/pkg/util"
"github.com/stakater/Reloader/pkg/common"
"github.com/stakater/Reloader/pkg/kube"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -94,7 +95,7 @@ func TestControllerUpdatingConfigmapShouldCreatePodAnnotationInDeployment(t *tes
// Verifying deployment update
logrus.Infof("Verifying pod annotation has been created")
shaData := testutil.ConvertResourceToSHA(testutil.ConfigmapResourceType, namespace, configmapName, "www.stakater.com")
config := util.Config{
config := common.Config{
Namespace: namespace,
ResourceName: configmapName,
SHAValue: shaData,
@@ -147,7 +148,7 @@ func TestControllerUpdatingConfigmapShouldAutoCreatePodAnnotationInDeployment(t
// Verifying deployment update
logrus.Infof("Verifying pod annotation has been created")
shaData := testutil.ConvertResourceToSHA(testutil.ConfigmapResourceType, namespace, configmapName, "www.stakater.com")
config := util.Config{
config := common.Config{
Namespace: namespace,
ResourceName: configmapName,
SHAValue: shaData,
@@ -212,7 +213,7 @@ func TestControllerCreatingConfigmapShouldCreatePodAnnotationInDeployment(t *tes
// Verifying deployment update
logrus.Infof("Verifying pod annotation has been created")
shaData := testutil.ConvertResourceToSHA(testutil.ConfigmapResourceType, namespace, configmapName, "www.stakater.com")
config := util.Config{
config := common.Config{
Namespace: namespace,
ResourceName: configmapName,
SHAValue: shaData,
@@ -271,7 +272,7 @@ func TestControllerForUpdatingConfigmapShouldUpdateDeploymentUsingArs(t *testing
// Verifying deployment update
logrus.Infof("Verifying pod annotation has been updated")
shaData := testutil.ConvertResourceToSHA(testutil.ConfigmapResourceType, namespace, configmapName, "aurorasolutions.io")
config := util.Config{
config := common.Config{
Namespace: namespace,
ResourceName: configmapName,
SHAValue: shaData,
@@ -326,7 +327,7 @@ func TestControllerUpdatingConfigmapLabelsShouldNotCreateOrCreatePodAnnotationIn
// Verifying deployment update
logrus.Infof("Verifying pod annotation has been created")
shaData := testutil.ConvertResourceToSHA(testutil.ConfigmapResourceType, namespace, configmapName, "www.google.com")
config := util.Config{
config := common.Config{
Namespace: namespace,
ResourceName: configmapName,
SHAValue: shaData,
@@ -390,7 +391,7 @@ func TestControllerCreatingSecretShouldCreatePodAnnotationInDeployment(t *testin
// Verifying Upgrade
logrus.Infof("Verifying pod annotation has been created")
shaData := testutil.ConvertResourceToSHA(testutil.SecretResourceType, namespace, secretName, newData)
config := util.Config{
config := common.Config{
Namespace: namespace,
ResourceName: secretName,
SHAValue: shaData,
@@ -443,7 +444,7 @@ func TestControllerUpdatingSecretShouldCreatePodAnnotationInDeployment(t *testin
// Verifying Upgrade
logrus.Infof("Verifying pod annotation has been created")
shaData := testutil.ConvertResourceToSHA(testutil.SecretResourceType, namespace, secretName, newData)
config := util.Config{
config := common.Config{
Namespace: namespace,
ResourceName: secretName,
SHAValue: shaData,
@@ -501,7 +502,7 @@ func TestControllerUpdatingSecretShouldUpdatePodAnnotationInDeployment(t *testin
// Verifying Upgrade
logrus.Infof("Verifying pod annotation has been updated")
shaData := testutil.ConvertResourceToSHA(testutil.SecretResourceType, namespace, secretName, updatedData)
config := util.Config{
config := common.Config{
Namespace: namespace,
ResourceName: secretName,
SHAValue: shaData,
@@ -552,7 +553,7 @@ func TestControllerUpdatingSecretLabelsShouldNotCreateOrUpdatePodAnnotationInDep
// Verifying Upgrade
logrus.Infof("Verifying pod annotation has been created")
shaData := testutil.ConvertResourceToSHA(testutil.SecretResourceType, namespace, secretName, data)
config := util.Config{
config := common.Config{
Namespace: namespace,
ResourceName: secretName,
SHAValue: shaData,
@@ -604,7 +605,7 @@ func TestControllerUpdatingConfigmapShouldCreatePodAnnotationInDaemonSet(t *test
// Verifying DaemonSet update
logrus.Infof("Verifying pod annotation has been created")
shaData := testutil.ConvertResourceToSHA(testutil.ConfigmapResourceType, namespace, configmapName, "www.stakater.com")
config := util.Config{
config := common.Config{
Namespace: namespace,
ResourceName: configmapName,
SHAValue: shaData,
@@ -667,7 +668,7 @@ func TestControllerForUpdatingConfigmapShouldUpdateDaemonSetUsingArs(t *testing.
// Verifying DaemonSet update
logrus.Infof("Verifying pod annotation has been updated")
shaData := testutil.ConvertResourceToSHA(testutil.ConfigmapResourceType, namespace, configmapName, "aurorasolutions.io")
config := util.Config{
config := common.Config{
Namespace: namespace,
ResourceName: configmapName,
SHAValue: shaData,
@@ -720,7 +721,7 @@ func TestControllerUpdatingSecretShouldCreatePodAnnotationInDaemonSet(t *testing
// Verifying Upgrade
logrus.Infof("Verifying pod annotation has been created")
shaData := testutil.ConvertResourceToSHA(testutil.SecretResourceType, namespace, secretName, newData)
config := util.Config{
config := common.Config{
Namespace: namespace,
ResourceName: secretName,
SHAValue: shaData,
@@ -779,7 +780,7 @@ func TestControllerUpdatingSecretShouldUpdatePodAnnotationInDaemonSet(t *testing
// Verifying Upgrade
logrus.Infof("Verifying pod annotation has been updated")
shaData := testutil.ConvertResourceToSHA(testutil.SecretResourceType, namespace, secretName, updatedData)
config := util.Config{
config := common.Config{
Namespace: namespace,
ResourceName: secretName,
SHAValue: shaData,
@@ -830,7 +831,7 @@ func TestControllerUpdatingSecretLabelsShouldNotCreateOrUpdatePodAnnotationInDae
// Verifying Upgrade
logrus.Infof("Verifying pod annotation has been created")
shaData := testutil.ConvertResourceToSHA(testutil.SecretResourceType, namespace, secretName, data)
config := util.Config{
config := common.Config{
Namespace: namespace,
ResourceName: secretName,
SHAValue: shaData,
@@ -882,7 +883,7 @@ func TestControllerUpdatingConfigmapShouldCreatePodAnnotationInStatefulSet(t *te
// Verifying StatefulSet update
logrus.Infof("Verifying pod annotation has been created")
shaData := testutil.ConvertResourceToSHA(testutil.ConfigmapResourceType, namespace, configmapName, "www.stakater.com")
config := util.Config{
config := common.Config{
Namespace: namespace,
ResourceName: configmapName,
SHAValue: shaData,
@@ -941,7 +942,7 @@ func TestControllerForUpdatingConfigmapShouldUpdateStatefulSetUsingArs(t *testin
// Verifying StatefulSet update
logrus.Infof("Verifying pod annotation has been updated")
shaData := testutil.ConvertResourceToSHA(testutil.ConfigmapResourceType, namespace, configmapName, "aurorasolutions.io")
config := util.Config{
config := common.Config{
Namespace: namespace,
ResourceName: configmapName,
SHAValue: shaData,
@@ -994,7 +995,7 @@ func TestControllerUpdatingSecretShouldCreatePodAnnotationInStatefulSet(t *testi
// Verifying Upgrade
logrus.Infof("Verifying pod annotation has been created")
shaData := testutil.ConvertResourceToSHA(testutil.SecretResourceType, namespace, secretName, newData)
config := util.Config{
config := common.Config{
Namespace: namespace,
ResourceName: secretName,
SHAValue: shaData,
@@ -1046,7 +1047,7 @@ func TestControllerUpdatingConfigmapShouldCreateEnvInDeployment(t *testing.T) {
// Verifying deployment update
logrus.Infof("Verifying env var has been created")
shaData := testutil.ConvertResourceToSHA(testutil.ConfigmapResourceType, namespace, configmapName, "www.stakater.com")
config := util.Config{
config := common.Config{
Namespace: namespace,
ResourceName: configmapName,
SHAValue: shaData,
@@ -1099,7 +1100,7 @@ func TestControllerUpdatingConfigmapShouldAutoCreateEnvInDeployment(t *testing.T
// Verifying deployment update
logrus.Infof("Verifying env var has been created")
shaData := testutil.ConvertResourceToSHA(testutil.ConfigmapResourceType, namespace, configmapName, "www.stakater.com")
config := util.Config{
config := common.Config{
Namespace: namespace,
ResourceName: configmapName,
SHAValue: shaData,
@@ -1164,7 +1165,7 @@ func TestControllerCreatingConfigmapShouldCreateEnvInDeployment(t *testing.T) {
// Verifying deployment update
logrus.Infof("Verifying env var has been created")
shaData := testutil.ConvertResourceToSHA(testutil.ConfigmapResourceType, namespace, configmapName, "www.stakater.com")
config := util.Config{
config := common.Config{
Namespace: namespace,
ResourceName: configmapName,
SHAValue: shaData,
@@ -1223,7 +1224,7 @@ func TestControllerForUpdatingConfigmapShouldUpdateDeploymentUsingErs(t *testing
// Verifying deployment update
logrus.Infof("Verifying env var has been updated")
shaData := testutil.ConvertResourceToSHA(testutil.ConfigmapResourceType, namespace, configmapName, "aurorasolutions.io")
config := util.Config{
config := common.Config{
Namespace: namespace,
ResourceName: configmapName,
SHAValue: shaData,
@@ -1278,7 +1279,7 @@ func TestControllerUpdatingConfigmapLabelsShouldNotCreateOrUpdateEnvInDeployment
// Verifying deployment update
logrus.Infof("Verifying env var has been created")
shaData := testutil.ConvertResourceToSHA(testutil.ConfigmapResourceType, namespace, configmapName, "www.google.com")
config := util.Config{
config := common.Config{
Namespace: namespace,
ResourceName: configmapName,
SHAValue: shaData,
@@ -1342,7 +1343,7 @@ func TestControllerCreatingSecretShouldCreateEnvInDeployment(t *testing.T) {
// Verifying Upgrade
logrus.Infof("Verifying env var has been created")
shaData := testutil.ConvertResourceToSHA(testutil.SecretResourceType, namespace, secretName, newData)
config := util.Config{
config := common.Config{
Namespace: namespace,
ResourceName: secretName,
SHAValue: shaData,
@@ -1395,7 +1396,7 @@ func TestControllerUpdatingSecretShouldCreateEnvInDeployment(t *testing.T) {
// Verifying Upgrade
logrus.Infof("Verifying env var has been created")
shaData := testutil.ConvertResourceToSHA(testutil.SecretResourceType, namespace, secretName, newData)
config := util.Config{
config := common.Config{
Namespace: namespace,
ResourceName: secretName,
SHAValue: shaData,
@@ -1453,7 +1454,7 @@ func TestControllerUpdatingSecretShouldUpdateEnvInDeployment(t *testing.T) {
// Verifying Upgrade
logrus.Infof("Verifying env var has been updated")
shaData := testutil.ConvertResourceToSHA(testutil.SecretResourceType, namespace, secretName, updatedData)
config := util.Config{
config := common.Config{
Namespace: namespace,
ResourceName: secretName,
SHAValue: shaData,
@@ -1504,7 +1505,7 @@ func TestControllerUpdatingSecretLabelsShouldNotCreateOrUpdateEnvInDeployment(t
// Verifying Upgrade
logrus.Infof("Verifying env var has been created")
shaData := testutil.ConvertResourceToSHA(testutil.SecretResourceType, namespace, secretName, data)
config := util.Config{
config := common.Config{
Namespace: namespace,
ResourceName: secretName,
SHAValue: shaData,
@@ -1556,7 +1557,7 @@ func TestControllerUpdatingConfigmapShouldCreateEnvInDaemonSet(t *testing.T) {
// Verifying DaemonSet update
logrus.Infof("Verifying env var has been created")
shaData := testutil.ConvertResourceToSHA(testutil.ConfigmapResourceType, namespace, configmapName, "www.stakater.com")
config := util.Config{
config := common.Config{
Namespace: namespace,
ResourceName: configmapName,
SHAValue: shaData,
@@ -1619,7 +1620,7 @@ func TestControllerForUpdatingConfigmapShouldUpdateDaemonSetUsingErs(t *testing.
// Verifying DaemonSet update
logrus.Infof("Verifying env var has been updated")
shaData := testutil.ConvertResourceToSHA(testutil.ConfigmapResourceType, namespace, configmapName, "aurorasolutions.io")
config := util.Config{
config := common.Config{
Namespace: namespace,
ResourceName: configmapName,
SHAValue: shaData,
@@ -1672,7 +1673,7 @@ func TestControllerUpdatingSecretShouldCreateEnvInDaemonSet(t *testing.T) {
// Verifying Upgrade
logrus.Infof("Verifying env var has been created")
shaData := testutil.ConvertResourceToSHA(testutil.SecretResourceType, namespace, secretName, newData)
config := util.Config{
config := common.Config{
Namespace: namespace,
ResourceName: secretName,
SHAValue: shaData,
@@ -1731,7 +1732,7 @@ func TestControllerUpdatingSecretShouldUpdateEnvInDaemonSet(t *testing.T) {
// Verifying Upgrade
logrus.Infof("Verifying env var has been updated")
shaData := testutil.ConvertResourceToSHA(testutil.SecretResourceType, namespace, secretName, updatedData)
config := util.Config{
config := common.Config{
Namespace: namespace,
ResourceName: secretName,
SHAValue: shaData,
@@ -1782,7 +1783,7 @@ func TestControllerUpdatingSecretLabelsShouldNotCreateOrUpdateEnvInDaemonSet(t *
// Verifying Upgrade
logrus.Infof("Verifying env var has been created")
shaData := testutil.ConvertResourceToSHA(testutil.SecretResourceType, namespace, secretName, data)
config := util.Config{
config := common.Config{
Namespace: namespace,
ResourceName: secretName,
SHAValue: shaData,
@@ -1834,7 +1835,7 @@ func TestControllerUpdatingConfigmapShouldCreateEnvInStatefulSet(t *testing.T) {
// Verifying StatefulSet update
logrus.Infof("Verifying env var has been created")
shaData := testutil.ConvertResourceToSHA(testutil.ConfigmapResourceType, namespace, configmapName, "www.stakater.com")
config := util.Config{
config := common.Config{
Namespace: namespace,
ResourceName: configmapName,
SHAValue: shaData,
@@ -1893,7 +1894,7 @@ func TestControllerForUpdatingConfigmapShouldUpdateStatefulSetUsingErs(t *testin
// Verifying StatefulSet update
logrus.Infof("Verifying env var has been updated")
shaData := testutil.ConvertResourceToSHA(testutil.ConfigmapResourceType, namespace, configmapName, "aurorasolutions.io")
config := util.Config{
config := common.Config{
Namespace: namespace,
ResourceName: configmapName,
SHAValue: shaData,
@@ -1946,7 +1947,7 @@ func TestControllerUpdatingSecretShouldCreateEnvInStatefulSet(t *testing.T) {
// Verifying Upgrade
logrus.Infof("Verifying env var has been created")
shaData := testutil.ConvertResourceToSHA(testutil.SecretResourceType, namespace, secretName, newData)
config := util.Config{
config := common.Config{
Namespace: namespace,
ResourceName: secretName,
SHAValue: shaData,
@@ -2004,7 +2005,7 @@ func TestControllerUpdatingSecretShouldUpdateEnvInStatefulSet(t *testing.T) {
// Verifying Upgrade
logrus.Infof("Verifying env var has been updated")
shaData := testutil.ConvertResourceToSHA(testutil.SecretResourceType, namespace, secretName, updatedData)
config := util.Config{
config := common.Config{
Namespace: namespace,
ResourceName: secretName,
SHAValue: shaData,
@@ -2062,7 +2063,7 @@ func TestControllerUpdatingSecretShouldUpdatePodAnnotationInStatefulSet(t *testi
// Verifying Upgrade
logrus.Infof("Verifying pod annotation has been updated")
shaData := testutil.ConvertResourceToSHA(testutil.SecretResourceType, namespace, secretName, updatedData)
config := util.Config{
config := common.Config{
Namespace: namespace,
ResourceName: secretName,
SHAValue: shaData,

View File

@@ -4,7 +4,7 @@ import (
"github.com/sirupsen/logrus"
"github.com/stakater/Reloader/internal/pkg/metrics"
"github.com/stakater/Reloader/internal/pkg/options"
"github.com/stakater/Reloader/internal/pkg/util"
"github.com/stakater/Reloader/pkg/common"
v1 "k8s.io/api/core/v1"
"k8s.io/client-go/tools/record"
)
@@ -33,13 +33,13 @@ func (r ResourceCreatedHandler) Handle() error {
}
// GetConfig gets configurations containing SHA, annotations, namespace and resource name
func (r ResourceCreatedHandler) GetConfig() (util.Config, string) {
func (r ResourceCreatedHandler) GetConfig() (common.Config, string) {
var oldSHAData string
var config util.Config
var config common.Config
if _, ok := r.Resource.(*v1.ConfigMap); ok {
config = util.GetConfigmapConfig(r.Resource.(*v1.ConfigMap))
config = common.GetConfigmapConfig(r.Resource.(*v1.ConfigMap))
} else if _, ok := r.Resource.(*v1.Secret); ok {
config = util.GetSecretConfig(r.Resource.(*v1.Secret))
config = common.GetSecretConfig(r.Resource.(*v1.Secret))
} else {
logrus.Warnf("Invalid resource: Resource should be 'Secret' or 'Configmap' but found, %v", r.Resource)
}

View File

@@ -10,7 +10,7 @@ import (
"github.com/stakater/Reloader/internal/pkg/metrics"
"github.com/stakater/Reloader/internal/pkg/options"
"github.com/stakater/Reloader/internal/pkg/testutil"
"github.com/stakater/Reloader/internal/pkg/util"
"github.com/stakater/Reloader/pkg/common"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
@@ -42,20 +42,20 @@ func (r ResourceDeleteHandler) Handle() error {
}
// GetConfig gets configurations containing SHA, annotations, namespace and resource name
func (r ResourceDeleteHandler) GetConfig() (util.Config, string) {
func (r ResourceDeleteHandler) GetConfig() (common.Config, string) {
var oldSHAData string
var config util.Config
var config common.Config
if _, ok := r.Resource.(*v1.ConfigMap); ok {
config = util.GetConfigmapConfig(r.Resource.(*v1.ConfigMap))
config = common.GetConfigmapConfig(r.Resource.(*v1.ConfigMap))
} else if _, ok := r.Resource.(*v1.Secret); ok {
config = util.GetSecretConfig(r.Resource.(*v1.Secret))
config = common.GetSecretConfig(r.Resource.(*v1.Secret))
} else {
logrus.Warnf("Invalid resource: Resource should be 'Secret' or 'Configmap' but found, %v", r.Resource)
}
return config, oldSHAData
}
func invokeDeleteStrategy(upgradeFuncs callbacks.RollingUpgradeFuncs, item runtime.Object, config util.Config, autoReload bool) InvokeStrategyResult {
func invokeDeleteStrategy(upgradeFuncs callbacks.RollingUpgradeFuncs, item runtime.Object, config common.Config, autoReload bool) InvokeStrategyResult {
if options.ReloadStrategy == constants.AnnotationsReloadStrategy {
return removePodAnnotations(upgradeFuncs, item, config, autoReload)
}
@@ -63,12 +63,12 @@ func invokeDeleteStrategy(upgradeFuncs callbacks.RollingUpgradeFuncs, item runti
return removeContainerEnvVars(upgradeFuncs, item, config, autoReload)
}
func removePodAnnotations(upgradeFuncs callbacks.RollingUpgradeFuncs, item runtime.Object, config util.Config, autoReload bool) InvokeStrategyResult {
func removePodAnnotations(upgradeFuncs callbacks.RollingUpgradeFuncs, item runtime.Object, config common.Config, autoReload bool) InvokeStrategyResult {
config.SHAValue = testutil.GetSHAfromEmptyData()
return updatePodAnnotations(upgradeFuncs, item, config, autoReload)
}
func removeContainerEnvVars(upgradeFuncs callbacks.RollingUpgradeFuncs, item runtime.Object, config util.Config, autoReload bool) InvokeStrategyResult {
func removeContainerEnvVars(upgradeFuncs callbacks.RollingUpgradeFuncs, item runtime.Object, config common.Config, autoReload bool) InvokeStrategyResult {
envVar := getEnvVarName(config.ResourceName, config.Type)
container := getContainerUsingResource(upgradeFuncs, item, config, autoReload)

View File

@@ -1,11 +1,9 @@
package handler
import (
"github.com/stakater/Reloader/internal/pkg/util"
)
import "github.com/stakater/Reloader/pkg/common"
// ResourceHandler handles the creation and update of resources
type ResourceHandler interface {
Handle() error
GetConfig() (util.Config, string)
GetConfig() (common.Config, string)
}

View File

@@ -5,6 +5,7 @@ import (
"github.com/stakater/Reloader/internal/pkg/metrics"
"github.com/stakater/Reloader/internal/pkg/options"
"github.com/stakater/Reloader/internal/pkg/util"
"github.com/stakater/Reloader/pkg/common"
v1 "k8s.io/api/core/v1"
"k8s.io/client-go/tools/record"
)
@@ -36,15 +37,15 @@ func (r ResourceUpdatedHandler) Handle() error {
}
// GetConfig gets configurations containing SHA, annotations, namespace and resource name
func (r ResourceUpdatedHandler) GetConfig() (util.Config, string) {
func (r ResourceUpdatedHandler) GetConfig() (common.Config, string) {
var oldSHAData string
var config util.Config
var config common.Config
if _, ok := r.Resource.(*v1.ConfigMap); ok {
oldSHAData = util.GetSHAfromConfigmap(r.OldResource.(*v1.ConfigMap))
config = util.GetConfigmapConfig(r.Resource.(*v1.ConfigMap))
config = common.GetConfigmapConfig(r.Resource.(*v1.ConfigMap))
} else if _, ok := r.Resource.(*v1.Secret); ok {
oldSHAData = util.GetSHAfromSecret(r.OldResource.(*v1.Secret).Data)
config = util.GetSecretConfig(r.Resource.(*v1.Secret))
config = common.GetSecretConfig(r.Resource.(*v1.Secret))
} else {
logrus.Warnf("Invalid resource: Resource should be 'Secret' or 'Configmap' but found, %v", r.Resource)
}

View File

@@ -7,9 +7,6 @@ import (
"fmt"
"io"
"os"
"regexp"
"strconv"
"strings"
"github.com/parnurzeal/gorequest"
"github.com/prometheus/client_golang/prometheus"
@@ -20,6 +17,7 @@ import (
"github.com/stakater/Reloader/internal/pkg/metrics"
"github.com/stakater/Reloader/internal/pkg/options"
"github.com/stakater/Reloader/internal/pkg/util"
"github.com/stakater/Reloader/pkg/common"
"github.com/stakater/Reloader/pkg/kube"
app "k8s.io/api/apps/v1"
v1 "k8s.io/api/core/v1"
@@ -140,7 +138,7 @@ func GetArgoRolloutRollingUpgradeFuncs() callbacks.RollingUpgradeFuncs {
}
}
func sendUpgradeWebhook(config util.Config, webhookUrl string) error {
func sendUpgradeWebhook(config common.Config, webhookUrl string) error {
logrus.Infof("Changes detected in '%s' of type '%s' in namespace '%s', Sending webhook to '%s'",
config.ResourceName, config.Type, config.Namespace, webhookUrl)
@@ -171,7 +169,7 @@ func sendWebhook(url string) (string, []error) {
return buffer.String(), nil
}
func doRollingUpgrade(config util.Config, collectors metrics.Collectors, recorder record.EventRecorder, invoke invokeStrategy) error {
func doRollingUpgrade(config common.Config, collectors metrics.Collectors, recorder record.EventRecorder, invoke invokeStrategy) error {
clients := kube.GetClients()
err := rollingUpgrade(clients, config, GetDeploymentRollingUpgradeFuncs(), collectors, recorder, invoke)
@@ -205,7 +203,7 @@ func doRollingUpgrade(config util.Config, collectors metrics.Collectors, recorde
return nil
}
func rollingUpgrade(clients kube.Clients, config util.Config, upgradeFuncs callbacks.RollingUpgradeFuncs, collectors metrics.Collectors, recorder record.EventRecorder, strategy invokeStrategy) error {
func rollingUpgrade(clients kube.Clients, config common.Config, upgradeFuncs callbacks.RollingUpgradeFuncs, collectors metrics.Collectors, recorder record.EventRecorder, strategy invokeStrategy) error {
err := PerformAction(clients, config, upgradeFuncs, collectors, recorder, strategy)
if err != nil {
logrus.Errorf("Rolling upgrade for '%s' failed with error = %v", config.ResourceName, err)
@@ -214,7 +212,7 @@ func rollingUpgrade(clients kube.Clients, config util.Config, upgradeFuncs callb
}
// PerformAction invokes the deployment if there is any change in configmap or secret data
func PerformAction(clients kube.Clients, config util.Config, upgradeFuncs callbacks.RollingUpgradeFuncs, collectors metrics.Collectors, recorder record.EventRecorder, strategy invokeStrategy) error {
func PerformAction(clients kube.Clients, config common.Config, upgradeFuncs callbacks.RollingUpgradeFuncs, collectors metrics.Collectors, recorder record.EventRecorder, strategy invokeStrategy) error {
items := upgradeFuncs.ItemsFunc(clients, config.Namespace)
for _, item := range items {
@@ -251,7 +249,7 @@ func retryOnConflict(backoff wait.Backoff, fn func(_ bool) error) error {
return err
}
func upgradeResource(clients kube.Clients, config util.Config, upgradeFuncs callbacks.RollingUpgradeFuncs, collectors metrics.Collectors, recorder record.EventRecorder, strategy invokeStrategy, resource runtime.Object, fetchResource bool) error {
func upgradeResource(clients kube.Clients, config common.Config, upgradeFuncs callbacks.RollingUpgradeFuncs, collectors metrics.Collectors, recorder record.EventRecorder, strategy invokeStrategy, resource runtime.Object, fetchResource bool) error {
accessor, err := meta.Accessor(resource)
if err != nil {
return err
@@ -264,143 +262,76 @@ func upgradeResource(clients kube.Clients, config util.Config, upgradeFuncs call
return err
}
}
annotations := upgradeFuncs.AnnotationsFunc(resource)
podAnnotations := upgradeFuncs.PodAnnotationsFunc(resource)
result := common.ShouldReload(config, upgradeFuncs.ResourceType, annotations, podAnnotations, common.GetCommandLineOptions())
ignoreResourceAnnotatonValue := config.ResourceAnnotations[options.IgnoreResourceAnnotation]
if ignoreResourceAnnotatonValue == "true" {
if !result.ShouldReload {
logrus.Debugf("No changes detected in '%s' of type '%s' in namespace '%s'", config.ResourceName, config.Type, config.Namespace)
return nil
}
strategyResult := strategy(upgradeFuncs, resource, config, result.AutoReload)
if strategyResult.Result != constants.Updated {
return nil
}
// find correct annotation and update the resource
annotations := upgradeFuncs.AnnotationsFunc(resource)
annotationValue, found := annotations[config.Annotation]
searchAnnotationValue, foundSearchAnn := annotations[options.AutoSearchAnnotation]
reloaderEnabledValue, foundAuto := annotations[options.ReloaderAutoAnnotation]
typedAutoAnnotationEnabledValue, foundTypedAuto := annotations[config.TypedAutoAnnotation]
excludeConfigmapAnnotationValue, foundExcludeConfigmap := annotations[options.ConfigmapExcludeReloaderAnnotation]
excludeSecretAnnotationValue, foundExcludeSecret := annotations[options.SecretExcludeReloaderAnnotation]
pauseInterval, foundPauseInterval := annotations[options.PauseDeploymentAnnotation]
if !found && !foundAuto && !foundTypedAuto && !foundSearchAnn {
annotations = upgradeFuncs.PodAnnotationsFunc(resource)
annotationValue = annotations[config.Annotation]
searchAnnotationValue = annotations[options.AutoSearchAnnotation]
reloaderEnabledValue = annotations[options.ReloaderAutoAnnotation]
typedAutoAnnotationEnabledValue = annotations[config.TypedAutoAnnotation]
}
isResourceExcluded := false
switch config.Type {
case constants.ConfigmapEnvVarPostfix:
if foundExcludeConfigmap {
isResourceExcluded = checkIfResourceIsExcluded(config.ResourceName, excludeConfigmapAnnotationValue)
}
case constants.SecretEnvVarPostfix:
if foundExcludeSecret {
isResourceExcluded = checkIfResourceIsExcluded(config.ResourceName, excludeSecretAnnotationValue)
}
}
if isResourceExcluded {
return nil
}
strategyResult := InvokeStrategyResult{constants.NotUpdated, nil}
reloaderEnabled, _ := strconv.ParseBool(reloaderEnabledValue)
typedAutoAnnotationEnabled, _ := strconv.ParseBool(typedAutoAnnotationEnabledValue)
if reloaderEnabled || typedAutoAnnotationEnabled || reloaderEnabledValue == "" && typedAutoAnnotationEnabledValue == "" && options.AutoReloadAll {
strategyResult = strategy(upgradeFuncs, resource, config, true)
}
if strategyResult.Result != constants.Updated && annotationValue != "" {
values := strings.Split(annotationValue, ",")
for _, value := range values {
value = strings.TrimSpace(value)
re := regexp.MustCompile("^" + value + "$")
if re.Match([]byte(config.ResourceName)) {
strategyResult = strategy(upgradeFuncs, resource, config, false)
if strategyResult.Result == constants.Updated {
break
}
}
}
}
if strategyResult.Result != constants.Updated && searchAnnotationValue == "true" {
matchAnnotationValue := config.ResourceAnnotations[options.SearchMatchAnnotation]
if matchAnnotationValue == "true" {
strategyResult = strategy(upgradeFuncs, resource, config, true)
}
}
if strategyResult.Result == constants.Updated {
if foundPauseInterval {
deployment, ok := resource.(*app.Deployment)
if !ok {
logrus.Warnf("Annotation '%s' only applicable for deployments", options.PauseDeploymentAnnotation)
} else {
_, err = PauseDeployment(deployment, clients, config.Namespace, pauseInterval)
if err != nil {
logrus.Errorf("Failed to pause deployment '%s' in namespace '%s': %v", resourceName, config.Namespace, err)
return err
}
}
}
var err error
if upgradeFuncs.SupportsPatch && strategyResult.Patch != nil {
err = upgradeFuncs.PatchFunc(clients, config.Namespace, resource, strategyResult.Patch.Type, strategyResult.Patch.Bytes)
if foundPauseInterval {
deployment, ok := resource.(*app.Deployment)
if !ok {
logrus.Warnf("Annotation '%s' only applicable for deployments", options.PauseDeploymentAnnotation)
} else {
err = upgradeFuncs.UpdateFunc(clients, config.Namespace, resource)
_, err = PauseDeployment(deployment, clients, config.Namespace, pauseInterval)
if err != nil {
logrus.Errorf("Failed to pause deployment '%s' in namespace '%s': %v", resourceName, config.Namespace, err)
return err
}
}
}
if err != nil {
message := fmt.Sprintf("Update for '%s' of type '%s' in namespace '%s' failed with error %v", resourceName, upgradeFuncs.ResourceType, config.Namespace, err)
logrus.Errorf("Update for '%s' of type '%s' in namespace '%s' failed with error %v", resourceName, upgradeFuncs.ResourceType, config.Namespace, err)
if upgradeFuncs.SupportsPatch && strategyResult.Patch != nil {
err = upgradeFuncs.PatchFunc(clients, config.Namespace, resource, strategyResult.Patch.Type, strategyResult.Patch.Bytes)
} else {
err = upgradeFuncs.UpdateFunc(clients, config.Namespace, resource)
}
collectors.Reloaded.With(prometheus.Labels{"success": "false"}).Inc()
collectors.ReloadedByNamespace.With(prometheus.Labels{"success": "false", "namespace": config.Namespace}).Inc()
if recorder != nil {
recorder.Event(resource, v1.EventTypeWarning, "ReloadFail", message)
}
return err
} else {
message := fmt.Sprintf("Changes detected in '%s' of type '%s' in namespace '%s'", config.ResourceName, config.Type, config.Namespace)
message += fmt.Sprintf(", Updated '%s' of type '%s' in namespace '%s'", resourceName, upgradeFuncs.ResourceType, config.Namespace)
if err != nil {
message := fmt.Sprintf("Update for '%s' of type '%s' in namespace '%s' failed with error %v", resourceName, upgradeFuncs.ResourceType, config.Namespace, err)
logrus.Errorf("Update for '%s' of type '%s' in namespace '%s' failed with error %v", resourceName, upgradeFuncs.ResourceType, config.Namespace, err)
logrus.Infof("Changes detected in '%s' of type '%s' in namespace '%s'; updated '%s' of type '%s' in namespace '%s'", config.ResourceName, config.Type, config.Namespace, resourceName, upgradeFuncs.ResourceType, config.Namespace)
collectors.Reloaded.With(prometheus.Labels{"success": "false"}).Inc()
collectors.ReloadedByNamespace.With(prometheus.Labels{"success": "false", "namespace": config.Namespace}).Inc()
if recorder != nil {
recorder.Event(resource, v1.EventTypeWarning, "ReloadFail", message)
}
return err
} else {
message := fmt.Sprintf("Changes detected in '%s' of type '%s' in namespace '%s'", config.ResourceName, config.Type, config.Namespace)
message += fmt.Sprintf(", Updated '%s' of type '%s' in namespace '%s'", resourceName, upgradeFuncs.ResourceType, config.Namespace)
collectors.Reloaded.With(prometheus.Labels{"success": "true"}).Inc()
collectors.ReloadedByNamespace.With(prometheus.Labels{"success": "true", "namespace": config.Namespace}).Inc()
alert_on_reload, ok := os.LookupEnv("ALERT_ON_RELOAD")
if recorder != nil {
recorder.Event(resource, v1.EventTypeNormal, "Reloaded", message)
}
if ok && alert_on_reload == "true" {
msg := fmt.Sprintf(
"Reloader detected changes in *%s* of type *%s* in namespace *%s*. Hence reloaded *%s* of type *%s* in namespace *%s*",
config.ResourceName, config.Type, config.Namespace, resourceName, upgradeFuncs.ResourceType, config.Namespace)
alert.SendWebhookAlert(msg)
}
logrus.Infof("Changes detected in '%s' of type '%s' in namespace '%s'; updated '%s' of type '%s' in namespace '%s'", config.ResourceName, config.Type, config.Namespace, resourceName, upgradeFuncs.ResourceType, config.Namespace)
collectors.Reloaded.With(prometheus.Labels{"success": "true"}).Inc()
collectors.ReloadedByNamespace.With(prometheus.Labels{"success": "true", "namespace": config.Namespace}).Inc()
alert_on_reload, ok := os.LookupEnv("ALERT_ON_RELOAD")
if recorder != nil {
recorder.Event(resource, v1.EventTypeNormal, "Reloaded", message)
}
if ok && alert_on_reload == "true" {
msg := fmt.Sprintf(
"Reloader detected changes in *%s* of type *%s* in namespace *%s*. Hence reloaded *%s* of type *%s* in namespace *%s*",
config.ResourceName, config.Type, config.Namespace, resourceName, upgradeFuncs.ResourceType, config.Namespace)
alert.SendWebhookAlert(msg)
}
}
return nil
}
func checkIfResourceIsExcluded(resourceName, excludedResources string) bool {
if excludedResources == "" {
return false
}
excludedResourcesList := strings.Split(excludedResources, ",")
for _, excludedResource := range excludedResourcesList {
if strings.TrimSpace(excludedResource) == resourceName {
return true
}
}
return false
}
func getVolumeMountName(volumes []v1.Volume, mountType string, volumeName string) string {
for i := range volumes {
if mountType == constants.ConfigmapEnvVarPostfix {
@@ -472,7 +403,7 @@ func getContainerWithEnvReference(containers []v1.Container, resourceName string
return nil
}
func getContainerUsingResource(upgradeFuncs callbacks.RollingUpgradeFuncs, item runtime.Object, config util.Config, autoReload bool) *v1.Container {
func getContainerUsingResource(upgradeFuncs callbacks.RollingUpgradeFuncs, item runtime.Object, config common.Config, autoReload bool) *v1.Container {
volumes := upgradeFuncs.VolumesFunc(item)
containers := upgradeFuncs.ContainersFunc(item)
initContainers := upgradeFuncs.InitContainersFunc(item)
@@ -486,7 +417,11 @@ func getContainerUsingResource(upgradeFuncs callbacks.RollingUpgradeFuncs, item
container = getContainerWithVolumeMount(initContainers, volumeMountName)
if container != nil {
// if configmap/secret is being used in init container then return the first Pod container to save reloader env
return &containers[0]
if len(containers) > 0 {
return &containers[0]
}
// No containers available, return nil to avoid crash
return nil
}
} else if container != nil {
return container
@@ -499,13 +434,21 @@ func getContainerUsingResource(upgradeFuncs callbacks.RollingUpgradeFuncs, item
container = getContainerWithEnvReference(initContainers, config.ResourceName, config.Type)
if container != nil {
// if configmap/secret is being used in init container then return the first Pod container to save reloader env
return &containers[0]
if len(containers) > 0 {
return &containers[0]
}
// No containers available, return nil to avoid crash
return nil
}
}
// Get the first container if the annotation is related to specified configmap or secret i.e. configmap.reloader.stakater.com/reload
if container == nil && !autoReload {
return &containers[0]
if len(containers) > 0 {
return &containers[0]
}
// No containers available, return nil to avoid crash
return nil
}
return container
@@ -521,16 +464,16 @@ type InvokeStrategyResult struct {
Patch *Patch
}
type invokeStrategy func(upgradeFuncs callbacks.RollingUpgradeFuncs, item runtime.Object, config util.Config, autoReload bool) InvokeStrategyResult
type invokeStrategy func(upgradeFuncs callbacks.RollingUpgradeFuncs, item runtime.Object, config common.Config, autoReload bool) InvokeStrategyResult
func invokeReloadStrategy(upgradeFuncs callbacks.RollingUpgradeFuncs, item runtime.Object, config util.Config, autoReload bool) InvokeStrategyResult {
func invokeReloadStrategy(upgradeFuncs callbacks.RollingUpgradeFuncs, item runtime.Object, config common.Config, autoReload bool) InvokeStrategyResult {
if options.ReloadStrategy == constants.AnnotationsReloadStrategy {
return updatePodAnnotations(upgradeFuncs, item, config, autoReload)
}
return updateContainerEnvVars(upgradeFuncs, item, config, autoReload)
}
func updatePodAnnotations(upgradeFuncs callbacks.RollingUpgradeFuncs, item runtime.Object, config util.Config, autoReload bool) InvokeStrategyResult {
func updatePodAnnotations(upgradeFuncs callbacks.RollingUpgradeFuncs, item runtime.Object, config common.Config, autoReload bool) InvokeStrategyResult {
container := getContainerUsingResource(upgradeFuncs, item, config, autoReload)
if container == nil {
return InvokeStrategyResult{constants.NoContainerFound, nil}
@@ -538,7 +481,7 @@ func updatePodAnnotations(upgradeFuncs callbacks.RollingUpgradeFuncs, item runti
// Generate reloaded annotations. Attaching this to the item's annotation will trigger a rollout
// Note: the data on this struct is purely informational and is not used for future updates
reloadSource := util.NewReloadSourceFromConfig(config, []string{container.Name})
reloadSource := common.NewReloadSourceFromConfig(config, []string{container.Name})
annotations, patch, err := createReloadedAnnotations(&reloadSource, upgradeFuncs)
if err != nil {
logrus.Errorf("Failed to create reloaded annotations for %s! error = %v", config.ResourceName, err)
@@ -565,7 +508,7 @@ func getReloaderAnnotationKey() string {
)
}
func createReloadedAnnotations(target *util.ReloadSource, upgradeFuncs callbacks.RollingUpgradeFuncs) (map[string]string, []byte, error) {
func createReloadedAnnotations(target *common.ReloadSource, upgradeFuncs callbacks.RollingUpgradeFuncs) (map[string]string, []byte, error) {
if target == nil {
return nil, nil, errors.New("target is required")
}
@@ -600,7 +543,7 @@ func getEnvVarName(resourceName string, typeName string) string {
return constants.EnvVarPrefix + util.ConvertToEnvVarName(resourceName) + "_" + typeName
}
func updateContainerEnvVars(upgradeFuncs callbacks.RollingUpgradeFuncs, item runtime.Object, config util.Config, autoReload bool) InvokeStrategyResult {
func updateContainerEnvVars(upgradeFuncs callbacks.RollingUpgradeFuncs, item runtime.Object, config common.Config, autoReload bool) InvokeStrategyResult {
envVar := getEnvVarName(config.ResourceName, config.Type)
container := getContainerUsingResource(upgradeFuncs, item, config, autoReload)

View File

@@ -7,6 +7,7 @@ import (
"testing"
"time"
argorolloutv1alpha1 "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1"
"github.com/prometheus/client_golang/prometheus"
promtestutil "github.com/prometheus/client_golang/prometheus/testutil"
"github.com/sirupsen/logrus"
@@ -16,8 +17,10 @@ import (
"github.com/stakater/Reloader/internal/pkg/options"
"github.com/stakater/Reloader/internal/pkg/testutil"
"github.com/stakater/Reloader/internal/pkg/util"
"github.com/stakater/Reloader/pkg/common"
"github.com/stakater/Reloader/pkg/kube"
"github.com/stretchr/testify/assert"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -672,7 +675,7 @@ func teardownArs() {
logrus.Errorf("Error while deleting statefulSet with secret as env var source %v", statefulSetError)
}
// Deleting Deployment with pasuse annotation
// Deleting Deployment with pause annotation
deploymentError = testutil.DeleteDeployment(clients.KubernetesClient, arsNamespace, arsConfigmapWithPausedDeployment)
if deploymentError != nil {
logrus.Errorf("Error while deleting deployment with configmap %v", deploymentError)
@@ -708,7 +711,7 @@ func teardownArs() {
logrus.Errorf("Error while deleting the configmap %v", err)
}
// Deleting Configmap used projected volume in init containers
// Deleting secret used in projected volume in init containers
err = testutil.DeleteSecret(clients.KubernetesClient, arsNamespace, arsProjectedSecretWithInitContainer)
if err != nil {
logrus.Errorf("Error while deleting the secret %v", err)
@@ -1392,7 +1395,7 @@ func teardownErs() {
logrus.Errorf("Error while deleting the configmap %v", err)
}
// Deleting Configmap used projected volume in init containers
// Deleting secret used in projected volume in init containers
err = testutil.DeleteSecret(clients.KubernetesClient, ersNamespace, ersProjectedSecretWithInitContainer)
if err != nil {
logrus.Errorf("Error while deleting the secret %v", err)
@@ -1475,7 +1478,7 @@ func teardownErs() {
logrus.Errorf("Error while deleting the configmap used with configmap exclude annotation: %v", err)
}
// Deleting ConfigMap for testins pausing deployments
// Deleting ConfigMap for testing pausing deployments
err = testutil.DeleteConfigMap(clients.KubernetesClient, ersNamespace, ersConfigmapWithPausedDeployment)
if err != nil {
logrus.Errorf("Error while deleting the configmap: %v", err)
@@ -1486,13 +1489,13 @@ func teardownErs() {
}
func getConfigWithAnnotations(resourceType string, name string, shaData string, annotation string, typedAutoAnnotation string) util.Config {
func getConfigWithAnnotations(resourceType string, name string, shaData string, annotation string, typedAutoAnnotation string) common.Config {
ns := ersNamespace
if options.ReloadStrategy == constants.AnnotationsReloadStrategy {
ns = arsNamespace
}
return util.Config{
return common.Config{
Namespace: ns,
ResourceName: name,
SHAValue: shaData,
@@ -1509,7 +1512,7 @@ func getCollectors() metrics.Collectors {
var labelSucceeded = prometheus.Labels{"success": "true"}
var labelFailed = prometheus.Labels{"success": "false"}
func testRollingUpgradeInvokeDeleteStrategyArs(t *testing.T, clients kube.Clients, config util.Config, upgradeFuncs callbacks.RollingUpgradeFuncs, collectors metrics.Collectors, envVarPostfix string) {
func testRollingUpgradeInvokeDeleteStrategyArs(t *testing.T, clients kube.Clients, config common.Config, upgradeFuncs callbacks.RollingUpgradeFuncs, collectors metrics.Collectors, envVarPostfix string) {
err := PerformAction(clients, config, upgradeFuncs, collectors, nil, invokeDeleteStrategy)
time.Sleep(5 * time.Second)
if err != nil {
@@ -1527,7 +1530,7 @@ func testRollingUpgradeInvokeDeleteStrategyArs(t *testing.T, clients kube.Client
}
}
func testRollingUpgradeWithPatchAndInvokeDeleteStrategyArs(t *testing.T, clients kube.Clients, config util.Config, upgradeFuncs callbacks.RollingUpgradeFuncs, collectors metrics.Collectors, envVarPostfix string) {
func testRollingUpgradeWithPatchAndInvokeDeleteStrategyArs(t *testing.T, clients kube.Clients, config common.Config, upgradeFuncs callbacks.RollingUpgradeFuncs, collectors metrics.Collectors, envVarPostfix string) {
err := PerformAction(clients, config, upgradeFuncs, collectors, nil, invokeDeleteStrategy)
upgradeFuncs.PatchFunc = func(client kube.Clients, namespace string, resource runtime.Object, patchType patchtypes.PatchType, bytes []byte) error {
assert.Equal(t, patchtypes.StrategicMergePatchType, patchType)
@@ -2247,7 +2250,7 @@ func TestRollingUpgradeForDeploymentWithSecretExcludeAnnotationUsingArs(t *testi
logrus.Infof("Verifying deployment did not update")
updated := testutil.VerifyResourceAnnotationUpdate(clients, config, deploymentFuncs)
if updated {
t.Errorf("Deployment which had to be exluded was updated")
t.Errorf("Deployment which had to be excluded was updated")
}
}
@@ -2907,7 +2910,7 @@ func TestIgnoreAnnotationNoReloadUsingErs(t *testing.T) {
}
}
func testRollingUpgradeInvokeDeleteStrategyErs(t *testing.T, clients kube.Clients, config util.Config, upgradeFuncs callbacks.RollingUpgradeFuncs, collectors metrics.Collectors, envVarPostfix string) {
func testRollingUpgradeInvokeDeleteStrategyErs(t *testing.T, clients kube.Clients, config common.Config, upgradeFuncs callbacks.RollingUpgradeFuncs, collectors metrics.Collectors, envVarPostfix string) {
err := PerformAction(clients, config, upgradeFuncs, collectors, nil, invokeDeleteStrategy)
time.Sleep(5 * time.Second)
if err != nil {
@@ -2924,7 +2927,7 @@ func testRollingUpgradeInvokeDeleteStrategyErs(t *testing.T, clients kube.Client
}
}
func testRollingUpgradeWithPatchAndInvokeDeleteStrategyErs(t *testing.T, clients kube.Clients, config util.Config, upgradeFuncs callbacks.RollingUpgradeFuncs, collectors metrics.Collectors, envVarPostfix string) {
func testRollingUpgradeWithPatchAndInvokeDeleteStrategyErs(t *testing.T, clients kube.Clients, config common.Config, upgradeFuncs callbacks.RollingUpgradeFuncs, collectors metrics.Collectors, envVarPostfix string) {
assert.NotEmpty(t, upgradeFuncs.PatchTemplatesFunc().DeleteEnvVarTemplate)
err := PerformAction(clients, config, upgradeFuncs, collectors, nil, invokeDeleteStrategy)
@@ -4125,6 +4128,13 @@ func testPausingDeployment(t *testing.T, reloadStrategy string, testName string,
_ = PerformAction(clients, config, deploymentFuncs, collectors, nil, invokeReloadStrategy)
// Wait for deployment to have paused-at annotation
logrus.Infof("Waiting for deployment %s to have paused-at annotation", testName)
err := waitForDeploymentPausedAtAnnotation(clients, deploymentFuncs, config.Namespace, testName, 30*time.Second)
if err != nil {
t.Errorf("Failed to wait for deployment paused-at annotation: %v", err)
}
if promtestutil.ToFloat64(collectors.Reloaded.With(labelSucceeded)) != 1 {
t.Errorf("Counter was not increased")
}
@@ -4185,3 +4195,94 @@ func isDeploymentPaused(deployments []runtime.Object, deploymentName string) (bo
}
return IsPaused(deployment), nil
}
// waitForDeploymentPausedAtAnnotation waits for a deployment to have the pause-period annotation
func waitForDeploymentPausedAtAnnotation(clients kube.Clients, deploymentFuncs callbacks.RollingUpgradeFuncs, namespace, deploymentName string, timeout time.Duration) error {
start := time.Now()
for time.Since(start) < timeout {
items := deploymentFuncs.ItemsFunc(clients, namespace)
deployment, err := FindDeploymentByName(items, deploymentName)
if err == nil {
annotations := deployment.GetAnnotations()
if annotations != nil {
if _, exists := annotations[options.PauseDeploymentTimeAnnotation]; exists {
return nil
}
}
}
time.Sleep(100 * time.Millisecond)
}
return fmt.Errorf("timeout waiting for deployment %s to have pause-period annotation", deploymentName)
}
// MockArgoRolloutWithEmptyContainers creates a mock Argo Rollout with no containers
// This simulates the scenario where Argo Rollouts with workloadRef return empty containers
func MockArgoRolloutWithEmptyContainers(namespace, name string) *runtime.Object {
rollout := &argorolloutv1alpha1.Rollout{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
},
Spec: argorolloutv1alpha1.RolloutSpec{
Template: v1.PodTemplateSpec{
Spec: v1.PodSpec{
Containers: []v1.Container{}, // Empty containers slice
InitContainers: []v1.Container{}, // Empty init containers slice
Volumes: []v1.Volume{}, // Empty volumes slice
},
},
},
}
var obj runtime.Object = rollout
return &obj
}
// TestGetContainerUsingResourceWithArgoRolloutEmptyContainers tests with real Argo Rollout functions
func TestGetContainerUsingResourceWithArgoRolloutEmptyContainers(t *testing.T) {
namespace := "test-namespace"
resourceName := "test-configmap"
// Use real Argo Rollout functions but mock the containers function
rolloutFuncs := GetArgoRolloutRollingUpgradeFuncs()
originalContainersFunc := rolloutFuncs.ContainersFunc
originalInitContainersFunc := rolloutFuncs.InitContainersFunc
// Override to return empty containers (simulating workloadRef scenario)
rolloutFuncs.ContainersFunc = func(item runtime.Object) []v1.Container {
return []v1.Container{} // Empty like workloadRef rollouts
}
rolloutFuncs.InitContainersFunc = func(item runtime.Object) []v1.Container {
return []v1.Container{} // Empty like workloadRef rollouts
}
// Restore original functions after test
defer func() {
rolloutFuncs.ContainersFunc = originalContainersFunc
rolloutFuncs.InitContainersFunc = originalInitContainersFunc
}()
// Use proper Argo Rollout object instead of Pod
mockRollout := MockArgoRolloutWithEmptyContainers(namespace, "test-rollout")
config := common.Config{
Namespace: namespace,
ResourceName: resourceName,
Type: constants.ConfigmapEnvVarPostfix,
SHAValue: "test-sha",
}
// Test both autoReload scenarios using subtests as suggested by Felix
for _, autoReload := range []bool{true, false} {
t.Run(fmt.Sprintf("autoReload_%t", autoReload), func(t *testing.T) {
// This tests the actual fix in the context of Argo Rollouts
result := getContainerUsingResource(rolloutFuncs, *mockRollout, config, autoReload)
if result != nil {
t.Errorf("Expected nil when using real Argo Rollout functions with empty containers (workloadRef scenario), got %v", result)
}
})
}
}

View File

@@ -16,7 +16,7 @@ import (
"github.com/stakater/Reloader/internal/pkg/metrics"
"github.com/stakater/Reloader/internal/pkg/options"
"github.com/stakater/Reloader/internal/pkg/testutil"
"github.com/stakater/Reloader/internal/pkg/util"
"github.com/stakater/Reloader/pkg/common"
"github.com/stakater/Reloader/pkg/kube"
)
@@ -159,7 +159,7 @@ func TestRunLeaderElectionWithControllers(t *testing.T) {
// Verifying deployment update
logrus.Infof("Verifying pod envvars has been created")
shaData := testutil.ConvertResourceToSHA(testutil.ConfigmapResourceType, testutil.Namespace, configmapName, "www.stakater.com")
config := util.Config{
config := common.Config{
Namespace: testutil.Namespace,
ResourceName: configmapName,
SHAValue: shaData,
@@ -186,7 +186,7 @@ func TestRunLeaderElectionWithControllers(t *testing.T) {
// Verifying that the deployment was not updated as leadership has been lost
logrus.Infof("Verifying pod envvars has not been updated")
shaData = testutil.ConvertResourceToSHA(testutil.ConfigmapResourceType, testutil.Namespace, configmapName, "www.stakater.com/new")
config = util.Config{
config = common.Config{
Namespace: testutil.Namespace,
ResourceName: configmapName,
SHAValue: shaData,

View File

@@ -63,14 +63,21 @@ var (
EnableHA = false
// Url to send a request to instead of triggering a reload
WebhookUrl = ""
// ResourcesToIgnore is a list of resources to ignore when watching for changes
ResourcesToIgnore = []string{}
// WorkloadTypesToIgnore is a list of workload types to ignore when watching for changes
WorkloadTypesToIgnore = []string{}
// NamespacesToIgnore is a list of namespace names to ignore when watching for changes
NamespacesToIgnore = []string{}
// NamespaceSelectors is a list of namespace selectors to watch for changes
NamespaceSelectors = []string{}
// ResourceSelectors is a list of resource selectors to watch for changes
ResourceSelectors = []string{}
// EnablePProf enables pprof for profiling
EnablePProf = false
// PProfAddr is the address to start pprof server on
// Default is :6060
PProfAddr = ":6060"
)
func ToArgoRolloutStrategy(s string) ArgoRolloutStrategy {

View File

@@ -21,6 +21,7 @@ import (
"github.com/stakater/Reloader/internal/pkg/metrics"
"github.com/stakater/Reloader/internal/pkg/options"
"github.com/stakater/Reloader/internal/pkg/util"
"github.com/stakater/Reloader/pkg/common"
"github.com/stakater/Reloader/pkg/kube"
appsv1 "k8s.io/api/apps/v1"
batchv1 "k8s.io/api/batch/v1"
@@ -733,7 +734,7 @@ func GetResourceSHAFromAnnotation(podAnnotations map[string]string) string {
return ""
}
var last util.ReloadSource
var last common.ReloadSource
bytes := []byte(annotationJson)
err := json.Unmarshal(bytes, &last)
if err != nil {
@@ -1058,7 +1059,7 @@ func RandSeq(n int) string {
}
// VerifyResourceEnvVarUpdate verifies whether the rolling upgrade happened or not
func VerifyResourceEnvVarUpdate(clients kube.Clients, config util.Config, envVarPostfix string, upgradeFuncs callbacks.RollingUpgradeFuncs) bool {
func VerifyResourceEnvVarUpdate(clients kube.Clients, config common.Config, envVarPostfix string, upgradeFuncs callbacks.RollingUpgradeFuncs) bool {
items := upgradeFuncs.ItemsFunc(clients, config.Namespace)
for _, i := range items {
containers := upgradeFuncs.ContainersFunc(i)
@@ -1104,7 +1105,7 @@ func VerifyResourceEnvVarUpdate(clients kube.Clients, config util.Config, envVar
}
// VerifyResourceEnvVarRemoved verifies whether the rolling upgrade happened or not and all Envvars SKAKATER_name_CONFIGMAP/SECRET are removed
func VerifyResourceEnvVarRemoved(clients kube.Clients, config util.Config, envVarPostfix string, upgradeFuncs callbacks.RollingUpgradeFuncs) bool {
func VerifyResourceEnvVarRemoved(clients kube.Clients, config common.Config, envVarPostfix string, upgradeFuncs callbacks.RollingUpgradeFuncs) bool {
items := upgradeFuncs.ItemsFunc(clients, config.Namespace)
for _, i := range items {
containers := upgradeFuncs.ContainersFunc(i)
@@ -1153,7 +1154,7 @@ func VerifyResourceEnvVarRemoved(clients kube.Clients, config util.Config, envVa
}
// VerifyResourceAnnotationUpdate verifies whether the rolling upgrade happened or not
func VerifyResourceAnnotationUpdate(clients kube.Clients, config util.Config, upgradeFuncs callbacks.RollingUpgradeFuncs) bool {
func VerifyResourceAnnotationUpdate(clients kube.Clients, config common.Config, upgradeFuncs callbacks.RollingUpgradeFuncs) bool {
items := upgradeFuncs.ItemsFunc(clients, config.Namespace)
for _, i := range items {
podAnnotations := upgradeFuncs.PodAnnotationsFunc(i)

View File

@@ -2,24 +2,17 @@ package util
import (
"bytes"
"context"
"encoding/base64"
"errors"
"fmt"
"os"
"sort"
"strings"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/stakater/Reloader/internal/pkg/constants"
"github.com/stakater/Reloader/internal/pkg/crypto"
"github.com/stakater/Reloader/internal/pkg/options"
"github.com/stakater/Reloader/pkg/metainfo"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/kubernetes"
)
// ConvertToEnvVarName converts the given text into a usable env var
@@ -64,43 +57,8 @@ func GetSHAfromSecret(data map[string][]byte) string {
return crypto.GenerateSHA(strings.Join(values, ";"))
}
func PublishMetaInfoConfigmap(clientset kubernetes.Interface) {
namespace := os.Getenv("RELOADER_NAMESPACE")
if namespace == "" {
logrus.Warn("RELOADER_NAMESPACE is not set, skipping meta info configmap creation")
return
}
metaInfo := &metainfo.MetaInfo{
BuildInfo: *metainfo.NewBuildInfo(),
ReloaderOptions: *metainfo.GetReloaderOptions(),
DeploymentInfo: metav1.ObjectMeta{
Name: os.Getenv("RELOADER_DEPLOYMENT_NAME"),
Namespace: namespace,
},
}
configMap := metaInfo.ToConfigMap()
if _, err := clientset.CoreV1().ConfigMaps(namespace).Get(context.Background(), configMap.Name, metav1.GetOptions{}); err == nil {
logrus.Info("Meta info configmap already exists, updating it")
_, err = clientset.CoreV1().ConfigMaps(namespace).Update(context.Background(), configMap, metav1.UpdateOptions{})
if err != nil {
logrus.Warn("Failed to update existing meta info configmap: ", err)
}
return
}
_, err := clientset.CoreV1().ConfigMaps(namespace).Create(context.Background(), configMap, metav1.CreateOptions{})
if err != nil {
logrus.Warn("Failed to create meta info configmap: ", err)
}
}
type List []string
type Map map[string]string
func (l *List) Contains(s string) bool {
for _, v := range *l {
if v == s {
@@ -125,6 +83,7 @@ func ConfigureReloaderFlags(cmd *cobra.Command) {
cmd.PersistentFlags().StringVar(&options.LogLevel, "log-level", "info", "Log level to use (trace, debug, info, warning, error, fatal and panic)")
cmd.PersistentFlags().StringVar(&options.WebhookUrl, "webhook-url", "", "webhook to trigger instead of performing a reload")
cmd.PersistentFlags().StringSliceVar(&options.ResourcesToIgnore, "resources-to-ignore", options.ResourcesToIgnore, "list of resources to ignore (valid options 'configMaps' or 'secrets')")
cmd.PersistentFlags().StringSliceVar(&options.WorkloadTypesToIgnore, "ignored-workload-types", options.WorkloadTypesToIgnore, "list of workload types to ignore (valid options: 'jobs', 'cronjobs', or both)")
cmd.PersistentFlags().StringSliceVar(&options.NamespacesToIgnore, "namespaces-to-ignore", options.NamespacesToIgnore, "list of namespaces to ignore")
cmd.PersistentFlags().StringSliceVar(&options.NamespaceSelectors, "namespace-selector", options.NamespaceSelectors, "list of key:value labels to filter on for namespaces")
cmd.PersistentFlags().StringSliceVar(&options.ResourceSelectors, "resource-label-selector", options.ResourceSelectors, "list of key:value labels to filter on for configmaps and secrets")
@@ -134,68 +93,8 @@ func ConfigureReloaderFlags(cmd *cobra.Command) {
cmd.PersistentFlags().StringVar(&options.ReloadOnDelete, "reload-on-delete", "false", "Add support to watch delete events")
cmd.PersistentFlags().BoolVar(&options.EnableHA, "enable-ha", false, "Adds support for running multiple replicas via leadership election")
cmd.PersistentFlags().BoolVar(&options.SyncAfterRestart, "sync-after-restart", false, "Sync add events after reloader restarts")
}
func GetNamespaceLabelSelector() (string, error) {
slice := options.NamespaceSelectors
for i, kv := range slice {
// Legacy support for ":" as a delimiter and "*" for wildcard.
if strings.Contains(kv, ":") {
split := strings.Split(kv, ":")
if split[1] == "*" {
slice[i] = split[0]
} else {
slice[i] = split[0] + "=" + split[1]
}
}
// Convert wildcard to valid apimachinery operator
if strings.Contains(kv, "=") {
split := strings.Split(kv, "=")
if split[1] == "*" {
slice[i] = split[0]
}
}
}
namespaceLabelSelector := strings.Join(slice[:], ",")
_, err := labels.Parse(namespaceLabelSelector)
if err != nil {
logrus.Fatal(err)
}
return namespaceLabelSelector, nil
}
func GetResourceLabelSelector() (string, error) {
slice := options.ResourceSelectors
for i, kv := range slice {
// Legacy support for ":" as a delimiter and "*" for wildcard.
if strings.Contains(kv, ":") {
split := strings.Split(kv, ":")
if split[1] == "*" {
slice[i] = split[0]
} else {
slice[i] = split[0] + "=" + split[1]
}
}
// Convert wildcard to valid apimachinery operator
if strings.Contains(kv, "=") {
split := strings.Split(kv, "=")
if split[1] == "*" {
slice[i] = split[0]
}
}
}
resourceLabelSelector := strings.Join(slice[:], ",")
_, err := labels.Parse(resourceLabelSelector)
if err != nil {
logrus.Fatal(err)
}
return resourceLabelSelector, nil
cmd.PersistentFlags().BoolVar(&options.EnablePProf, "enable-pprof", false, "Enable pprof for profiling")
cmd.PersistentFlags().StringVar(&options.PProfAddr, "pprof-addr", ":6060", "Address to start pprof server on. Default is :6060")
}
func GetIgnoredResourcesList() (List, error) {
@@ -214,3 +113,16 @@ func GetIgnoredResourcesList() (List, error) {
return ignoredResourcesList, nil
}
func GetIgnoredWorkloadTypesList() (List, error) {
ignoredWorkloadTypesList := options.WorkloadTypesToIgnore
for _, v := range ignoredWorkloadTypesList {
if v != "jobs" && v != "cronjobs" {
return nil, fmt.Errorf("'ignored-workload-types' accepts 'jobs', 'cronjobs', or both, not '%s'", v)
}
}
return ignoredWorkloadTypesList, nil
}

View File

@@ -3,6 +3,7 @@ package util
import (
"testing"
"github.com/stakater/Reloader/internal/pkg/options"
v1 "k8s.io/api/core/v1"
)
@@ -45,3 +46,141 @@ func TestGetHashFromConfigMap(t *testing.T) {
}
}
}
func TestGetIgnoredWorkloadTypesList(t *testing.T) {
// Save original state
originalWorkloadTypes := options.WorkloadTypesToIgnore
defer func() {
options.WorkloadTypesToIgnore = originalWorkloadTypes
}()
tests := []struct {
name string
workloadTypes []string
expectError bool
expected []string
}{
{
name: "Both jobs and cronjobs",
workloadTypes: []string{"jobs", "cronjobs"},
expectError: false,
expected: []string{"jobs", "cronjobs"},
},
{
name: "Only jobs",
workloadTypes: []string{"jobs"},
expectError: false,
expected: []string{"jobs"},
},
{
name: "Only cronjobs",
workloadTypes: []string{"cronjobs"},
expectError: false,
expected: []string{"cronjobs"},
},
{
name: "Empty list",
workloadTypes: []string{},
expectError: false,
expected: []string{},
},
{
name: "Invalid workload type",
workloadTypes: []string{"invalid"},
expectError: true,
expected: nil,
},
{
name: "Mixed valid and invalid",
workloadTypes: []string{"jobs", "invalid"},
expectError: true,
expected: nil,
},
{
name: "Duplicate values",
workloadTypes: []string{"jobs", "jobs"},
expectError: false,
expected: []string{"jobs", "jobs"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Set the global option
options.WorkloadTypesToIgnore = tt.workloadTypes
result, err := GetIgnoredWorkloadTypesList()
if tt.expectError && err == nil {
t.Errorf("Expected error but got none")
}
if !tt.expectError && err != nil {
t.Errorf("Expected no error but got: %v", err)
}
if !tt.expectError {
if len(result) != len(tt.expected) {
t.Errorf("Expected %v, got %v", tt.expected, result)
return
}
for i, expected := range tt.expected {
if i >= len(result) || result[i] != expected {
t.Errorf("Expected %v, got %v", tt.expected, result)
break
}
}
}
})
}
}
func TestListContains(t *testing.T) {
tests := []struct {
name string
list List
item string
expected bool
}{
{
name: "List contains item",
list: List{"jobs", "cronjobs"},
item: "jobs",
expected: true,
},
{
name: "List does not contain item",
list: List{"jobs"},
item: "cronjobs",
expected: false,
},
{
name: "Empty list",
list: List{},
item: "jobs",
expected: false,
},
{
name: "Case sensitive matching",
list: List{"jobs", "cronjobs"},
item: "Jobs",
expected: false,
},
{
name: "Multiple occurrences",
list: List{"jobs", "jobs", "cronjobs"},
item: "jobs",
expected: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := tt.list.Contains(tt.item)
if result != tt.expected {
t.Errorf("Expected %v, got %v", tt.expected, result)
}
})
}
}

358
pkg/common/common.go Normal file
View File

@@ -0,0 +1,358 @@
package common
import (
"context"
"os"
"regexp"
"strconv"
"strings"
"github.com/sirupsen/logrus"
"github.com/stakater/Reloader/internal/pkg/constants"
"github.com/stakater/Reloader/internal/pkg/options"
"github.com/stakater/Reloader/internal/pkg/util"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/kubernetes"
)
type Map map[string]string
type ReloadCheckResult struct {
ShouldReload bool
AutoReload bool
}
// ReloaderOptions contains all configurable options for the Reloader controller.
// These options control how Reloader behaves when watching for changes in ConfigMaps and Secrets.
type ReloaderOptions struct {
// AutoReloadAll enables automatic reloading of all resources when their corresponding ConfigMaps/Secrets are updated
AutoReloadAll bool `json:"autoReloadAll"`
// ConfigmapUpdateOnChangeAnnotation is the annotation key used to detect changes in ConfigMaps specified by name
ConfigmapUpdateOnChangeAnnotation string `json:"configmapUpdateOnChangeAnnotation"`
// SecretUpdateOnChangeAnnotation is the annotation key used to detect changes in Secrets specified by name
SecretUpdateOnChangeAnnotation string `json:"secretUpdateOnChangeAnnotation"`
// ReloaderAutoAnnotation is the annotation key used to detect changes in any referenced ConfigMaps or Secrets
ReloaderAutoAnnotation string `json:"reloaderAutoAnnotation"`
// IgnoreResourceAnnotation is the annotation key used to ignore resources from being watched
IgnoreResourceAnnotation string `json:"ignoreResourceAnnotation"`
// ConfigmapReloaderAutoAnnotation is the annotation key used to detect changes in ConfigMaps only
ConfigmapReloaderAutoAnnotation string `json:"configmapReloaderAutoAnnotation"`
// SecretReloaderAutoAnnotation is the annotation key used to detect changes in Secrets only
SecretReloaderAutoAnnotation string `json:"secretReloaderAutoAnnotation"`
// ConfigmapExcludeReloaderAnnotation is the annotation key containing comma-separated list of ConfigMaps to exclude from watching
ConfigmapExcludeReloaderAnnotation string `json:"configmapExcludeReloaderAnnotation"`
// SecretExcludeReloaderAnnotation is the annotation key containing comma-separated list of Secrets to exclude from watching
SecretExcludeReloaderAnnotation string `json:"secretExcludeReloaderAnnotation"`
// AutoSearchAnnotation is the annotation key used to detect changes in ConfigMaps/Secrets tagged with SearchMatchAnnotation
AutoSearchAnnotation string `json:"autoSearchAnnotation"`
// SearchMatchAnnotation is the annotation key used to tag ConfigMaps/Secrets to be found by AutoSearchAnnotation
SearchMatchAnnotation string `json:"searchMatchAnnotation"`
// RolloutStrategyAnnotation is the annotation key used to define the rollout update strategy for workloads
RolloutStrategyAnnotation string `json:"rolloutStrategyAnnotation"`
// PauseDeploymentAnnotation is the annotation key used to define the time period to pause a deployment after
PauseDeploymentAnnotation string `json:"pauseDeploymentAnnotation"`
// PauseDeploymentTimeAnnotation is the annotation key used to indicate when a deployment was paused by Reloader
PauseDeploymentTimeAnnotation string `json:"pauseDeploymentTimeAnnotation"`
// LogFormat specifies the log format to use (json, or empty string for default text format)
LogFormat string `json:"logFormat"`
// LogLevel specifies the log level to use (trace, debug, info, warning, error, fatal, panic)
LogLevel string `json:"logLevel"`
// IsArgoRollouts indicates whether support for Argo Rollouts is enabled
IsArgoRollouts bool `json:"isArgoRollouts"`
// ReloadStrategy specifies the strategy used to trigger resource reloads (env-vars or annotations)
ReloadStrategy string `json:"reloadStrategy"`
// ReloadOnCreate indicates whether to trigger reloads when ConfigMaps/Secrets are created
ReloadOnCreate bool `json:"reloadOnCreate"`
// ReloadOnDelete indicates whether to trigger reloads when ConfigMaps/Secrets are deleted
ReloadOnDelete bool `json:"reloadOnDelete"`
// SyncAfterRestart indicates whether to sync add events after Reloader restarts (only works when ReloadOnCreate is true)
SyncAfterRestart bool `json:"syncAfterRestart"`
// EnableHA indicates whether High Availability mode is enabled with leader election
EnableHA bool `json:"enableHA"`
// WebhookUrl is the URL to send webhook notifications to instead of performing reloads
WebhookUrl string `json:"webhookUrl"`
// ResourcesToIgnore is a list of resource types to ignore (e.g., "configmaps" or "secrets")
ResourcesToIgnore []string `json:"resourcesToIgnore"`
// WorkloadTypesToIgnore is a list of workload types to ignore (e.g., "jobs" or "cronjobs")
WorkloadTypesToIgnore []string `json:"workloadTypesToIgnore"`
// NamespaceSelectors is a list of label selectors to filter namespaces to watch
NamespaceSelectors []string `json:"namespaceSelectors"`
// ResourceSelectors is a list of label selectors to filter ConfigMaps and Secrets to watch
ResourceSelectors []string `json:"resourceSelectors"`
// NamespacesToIgnore is a list of namespace names to ignore when watching for changes
NamespacesToIgnore []string `json:"namespacesToIgnore"`
// EnablePProf enables pprof for profiling
EnablePProf bool `json:"enablePProf"`
// PProfAddr is the address to start pprof server on
PProfAddr string `json:"pprofAddr"`
}
var CommandLineOptions *ReloaderOptions
func PublishMetaInfoConfigmap(clientset kubernetes.Interface) {
namespace := os.Getenv("RELOADER_NAMESPACE")
if namespace == "" {
logrus.Warn("RELOADER_NAMESPACE is not set, skipping meta info configmap creation")
return
}
metaInfo := &MetaInfo{
BuildInfo: *NewBuildInfo(),
ReloaderOptions: *GetCommandLineOptions(),
DeploymentInfo: metav1.ObjectMeta{
Name: os.Getenv("RELOADER_DEPLOYMENT_NAME"),
Namespace: namespace,
},
}
configMap := metaInfo.ToConfigMap()
if _, err := clientset.CoreV1().ConfigMaps(namespace).Get(context.Background(), configMap.Name, metav1.GetOptions{}); err == nil {
logrus.Info("Meta info configmap already exists, updating it")
_, err = clientset.CoreV1().ConfigMaps(namespace).Update(context.Background(), configMap, metav1.UpdateOptions{})
if err != nil {
logrus.Warn("Failed to update existing meta info configmap: ", err)
}
return
}
_, err := clientset.CoreV1().ConfigMaps(namespace).Create(context.Background(), configMap, metav1.CreateOptions{})
if err != nil {
logrus.Warn("Failed to create meta info configmap: ", err)
}
}
func GetNamespaceLabelSelector(slice []string) (string, error) {
for i, kv := range slice {
// Legacy support for ":" as a delimiter and "*" for wildcard.
if strings.Contains(kv, ":") {
split := strings.Split(kv, ":")
if split[1] == "*" {
slice[i] = split[0]
} else {
slice[i] = split[0] + "=" + split[1]
}
}
// Convert wildcard to valid apimachinery operator
if strings.Contains(kv, "=") {
split := strings.Split(kv, "=")
if split[1] == "*" {
slice[i] = split[0]
}
}
}
namespaceLabelSelector := strings.Join(slice[:], ",")
_, err := labels.Parse(namespaceLabelSelector)
if err != nil {
logrus.Fatal(err)
}
return namespaceLabelSelector, nil
}
func GetResourceLabelSelector(slice []string) (string, error) {
for i, kv := range slice {
// Legacy support for ":" as a delimiter and "*" for wildcard.
if strings.Contains(kv, ":") {
split := strings.Split(kv, ":")
if split[1] == "*" {
slice[i] = split[0]
} else {
slice[i] = split[0] + "=" + split[1]
}
}
// Convert wildcard to valid apimachinery operator
if strings.Contains(kv, "=") {
split := strings.Split(kv, "=")
if split[1] == "*" {
slice[i] = split[0]
}
}
}
resourceLabelSelector := strings.Join(slice[:], ",")
_, err := labels.Parse(resourceLabelSelector)
if err != nil {
logrus.Fatal(err)
}
return resourceLabelSelector, nil
}
// ShouldReload checks if a resource should be reloaded based on its annotations and the provided options.
func ShouldReload(config Config, resourceType string, annotations Map, podAnnotations Map, options *ReloaderOptions) ReloadCheckResult {
// Check if this workload type should be ignored
if len(options.WorkloadTypesToIgnore) > 0 {
ignoredWorkloadTypes, err := util.GetIgnoredWorkloadTypesList()
if err != nil {
logrus.Errorf("Failed to parse ignored workload types: %v", err)
} else {
// Map Kubernetes resource types to CLI-friendly names for comparison
var resourceToCheck string
switch resourceType {
case "Job":
resourceToCheck = "jobs"
case "CronJob":
resourceToCheck = "cronjobs"
default:
resourceToCheck = resourceType // For other types, use as-is
}
// Check if current resource type should be ignored
if ignoredWorkloadTypes.Contains(resourceToCheck) {
return ReloadCheckResult{
ShouldReload: false,
}
}
}
}
ignoreResourceAnnotatonValue := config.ResourceAnnotations[options.IgnoreResourceAnnotation]
if ignoreResourceAnnotatonValue == "true" {
return ReloadCheckResult{
ShouldReload: false,
}
}
annotationValue, found := annotations[config.Annotation]
searchAnnotationValue, foundSearchAnn := annotations[options.AutoSearchAnnotation]
reloaderEnabledValue, foundAuto := annotations[options.ReloaderAutoAnnotation]
typedAutoAnnotationEnabledValue, foundTypedAuto := annotations[config.TypedAutoAnnotation]
excludeConfigmapAnnotationValue, foundExcludeConfigmap := annotations[options.ConfigmapExcludeReloaderAnnotation]
excludeSecretAnnotationValue, foundExcludeSecret := annotations[options.SecretExcludeReloaderAnnotation]
if !found && !foundAuto && !foundTypedAuto && !foundSearchAnn {
annotations = podAnnotations
annotationValue = annotations[config.Annotation]
searchAnnotationValue = annotations[options.AutoSearchAnnotation]
reloaderEnabledValue = annotations[options.ReloaderAutoAnnotation]
typedAutoAnnotationEnabledValue = annotations[config.TypedAutoAnnotation]
}
isResourceExcluded := false
switch config.Type {
case constants.ConfigmapEnvVarPostfix:
if foundExcludeConfigmap {
isResourceExcluded = checkIfResourceIsExcluded(config.ResourceName, excludeConfigmapAnnotationValue)
}
case constants.SecretEnvVarPostfix:
if foundExcludeSecret {
isResourceExcluded = checkIfResourceIsExcluded(config.ResourceName, excludeSecretAnnotationValue)
}
}
if isResourceExcluded {
return ReloadCheckResult{
ShouldReload: false,
}
}
reloaderEnabled, _ := strconv.ParseBool(reloaderEnabledValue)
typedAutoAnnotationEnabled, _ := strconv.ParseBool(typedAutoAnnotationEnabledValue)
if reloaderEnabled || typedAutoAnnotationEnabled || reloaderEnabledValue == "" && typedAutoAnnotationEnabledValue == "" && options.AutoReloadAll {
return ReloadCheckResult{
ShouldReload: true,
AutoReload: true,
}
}
values := strings.Split(annotationValue, ",")
for _, value := range values {
value = strings.TrimSpace(value)
re := regexp.MustCompile("^" + value + "$")
if re.Match([]byte(config.ResourceName)) {
return ReloadCheckResult{
ShouldReload: true,
AutoReload: false,
}
}
}
if searchAnnotationValue == "true" {
matchAnnotationValue := config.ResourceAnnotations[options.SearchMatchAnnotation]
if matchAnnotationValue == "true" {
return ReloadCheckResult{
ShouldReload: true,
AutoReload: true,
}
}
}
return ReloadCheckResult{
ShouldReload: false,
}
}
func checkIfResourceIsExcluded(resourceName, excludedResources string) bool {
if excludedResources == "" {
return false
}
excludedResourcesList := strings.Split(excludedResources, ",")
for _, excludedResource := range excludedResourcesList {
if strings.TrimSpace(excludedResource) == resourceName {
return true
}
}
return false
}
func init() {
GetCommandLineOptions()
}
func GetCommandLineOptions() *ReloaderOptions {
if CommandLineOptions == nil {
CommandLineOptions = &ReloaderOptions{}
}
CommandLineOptions.AutoReloadAll = options.AutoReloadAll
CommandLineOptions.ConfigmapUpdateOnChangeAnnotation = options.ConfigmapUpdateOnChangeAnnotation
CommandLineOptions.SecretUpdateOnChangeAnnotation = options.SecretUpdateOnChangeAnnotation
CommandLineOptions.ReloaderAutoAnnotation = options.ReloaderAutoAnnotation
CommandLineOptions.IgnoreResourceAnnotation = options.IgnoreResourceAnnotation
CommandLineOptions.ConfigmapReloaderAutoAnnotation = options.ConfigmapReloaderAutoAnnotation
CommandLineOptions.SecretReloaderAutoAnnotation = options.SecretReloaderAutoAnnotation
CommandLineOptions.ConfigmapExcludeReloaderAnnotation = options.ConfigmapExcludeReloaderAnnotation
CommandLineOptions.SecretExcludeReloaderAnnotation = options.SecretExcludeReloaderAnnotation
CommandLineOptions.AutoSearchAnnotation = options.AutoSearchAnnotation
CommandLineOptions.SearchMatchAnnotation = options.SearchMatchAnnotation
CommandLineOptions.RolloutStrategyAnnotation = options.RolloutStrategyAnnotation
CommandLineOptions.PauseDeploymentAnnotation = options.PauseDeploymentAnnotation
CommandLineOptions.PauseDeploymentTimeAnnotation = options.PauseDeploymentTimeAnnotation
CommandLineOptions.LogFormat = options.LogFormat
CommandLineOptions.LogLevel = options.LogLevel
CommandLineOptions.ReloadStrategy = options.ReloadStrategy
CommandLineOptions.SyncAfterRestart = options.SyncAfterRestart
CommandLineOptions.EnableHA = options.EnableHA
CommandLineOptions.WebhookUrl = options.WebhookUrl
CommandLineOptions.ResourcesToIgnore = options.ResourcesToIgnore
CommandLineOptions.WorkloadTypesToIgnore = options.WorkloadTypesToIgnore
CommandLineOptions.NamespaceSelectors = options.NamespaceSelectors
CommandLineOptions.ResourceSelectors = options.ResourceSelectors
CommandLineOptions.NamespacesToIgnore = options.NamespacesToIgnore
CommandLineOptions.IsArgoRollouts = parseBool(options.IsArgoRollouts)
CommandLineOptions.ReloadOnCreate = parseBool(options.ReloadOnCreate)
CommandLineOptions.ReloadOnDelete = parseBool(options.ReloadOnDelete)
CommandLineOptions.EnablePProf = options.EnablePProf
CommandLineOptions.PProfAddr = options.PProfAddr
return CommandLineOptions
}
func parseBool(value string) bool {
if value == "" {
return false
}
result, err := strconv.ParseBool(value)
if err != nil {
return false // Default to false if parsing fails
}
return result
}

224
pkg/common/common_test.go Normal file
View File

@@ -0,0 +1,224 @@
package common
import (
"testing"
"github.com/stakater/Reloader/internal/pkg/options"
)
func TestShouldReload_IgnoredWorkloadTypes(t *testing.T) {
// Save original state
originalWorkloadTypes := options.WorkloadTypesToIgnore
defer func() {
options.WorkloadTypesToIgnore = originalWorkloadTypes
}()
tests := []struct {
name string
ignoredWorkloadTypes []string
resourceType string
shouldReload bool
description string
}{
{
name: "Jobs ignored - Job should not reload",
ignoredWorkloadTypes: []string{"jobs"},
resourceType: "Job",
shouldReload: false,
description: "When jobs are ignored, Job resources should not be reloaded",
},
{
name: "Jobs ignored - CronJob should reload",
ignoredWorkloadTypes: []string{"jobs"},
resourceType: "CronJob",
shouldReload: true,
description: "When jobs are ignored, CronJob resources should still be processed",
},
{
name: "CronJobs ignored - CronJob should not reload",
ignoredWorkloadTypes: []string{"cronjobs"},
resourceType: "CronJob",
shouldReload: false,
description: "When cronjobs are ignored, CronJob resources should not be reloaded",
},
{
name: "CronJobs ignored - Job should reload",
ignoredWorkloadTypes: []string{"cronjobs"},
resourceType: "Job",
shouldReload: true,
description: "When cronjobs are ignored, Job resources should still be processed",
},
{
name: "Both ignored - Job should not reload",
ignoredWorkloadTypes: []string{"jobs", "cronjobs"},
resourceType: "Job",
shouldReload: false,
description: "When both are ignored, Job resources should not be reloaded",
},
{
name: "Both ignored - CronJob should not reload",
ignoredWorkloadTypes: []string{"jobs", "cronjobs"},
resourceType: "CronJob",
shouldReload: false,
description: "When both are ignored, CronJob resources should not be reloaded",
},
{
name: "Both ignored - Deployment should reload",
ignoredWorkloadTypes: []string{"jobs", "cronjobs"},
resourceType: "Deployment",
shouldReload: true,
description: "When both are ignored, other workload types should still be processed",
},
{
name: "None ignored - Job should reload",
ignoredWorkloadTypes: []string{},
resourceType: "Job",
shouldReload: true,
description: "When nothing is ignored, all workload types should be processed",
},
{
name: "None ignored - CronJob should reload",
ignoredWorkloadTypes: []string{},
resourceType: "CronJob",
shouldReload: true,
description: "When nothing is ignored, all workload types should be processed",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Set the ignored workload types
options.WorkloadTypesToIgnore = tt.ignoredWorkloadTypes
// Create minimal test config and options
config := Config{
ResourceName: "test-resource",
Annotation: "configmap.reloader.stakater.com/reload",
}
annotations := Map{
"configmap.reloader.stakater.com/reload": "test-config",
}
// Create ReloaderOptions with the ignored workload types
opts := &ReloaderOptions{
WorkloadTypesToIgnore: tt.ignoredWorkloadTypes,
AutoReloadAll: true, // Enable auto-reload to simplify test
ReloaderAutoAnnotation: "reloader.stakater.com/auto",
}
// Call ShouldReload
result := ShouldReload(config, tt.resourceType, annotations, Map{}, opts)
// Check the result
if result.ShouldReload != tt.shouldReload {
t.Errorf("For resource type %s with ignored types %v, expected ShouldReload=%v, got=%v",
tt.resourceType, tt.ignoredWorkloadTypes, tt.shouldReload, result.ShouldReload)
}
t.Logf("✓ %s", tt.description)
})
}
}
func TestShouldReload_IgnoredWorkloadTypes_ValidationError(t *testing.T) {
// Save original state
originalWorkloadTypes := options.WorkloadTypesToIgnore
defer func() {
options.WorkloadTypesToIgnore = originalWorkloadTypes
}()
// Test with invalid workload type - should still continue processing
options.WorkloadTypesToIgnore = []string{"invalid"}
config := Config{
ResourceName: "test-resource",
Annotation: "configmap.reloader.stakater.com/reload",
}
annotations := Map{
"configmap.reloader.stakater.com/reload": "test-config",
}
opts := &ReloaderOptions{
WorkloadTypesToIgnore: []string{"invalid"},
AutoReloadAll: true, // Enable auto-reload to simplify test
ReloaderAutoAnnotation: "reloader.stakater.com/auto",
}
// Should not panic and should continue with normal processing
result := ShouldReload(config, "Job", annotations, Map{}, opts)
// Since validation failed, it should continue with normal processing (should reload)
if !result.ShouldReload {
t.Errorf("Expected ShouldReload=true when validation fails, got=%v", result.ShouldReload)
}
}
// Test that validates the fix for issue #996
func TestShouldReload_IssueRBACPermissionFixed(t *testing.T) {
// Save original state
originalWorkloadTypes := options.WorkloadTypesToIgnore
defer func() {
options.WorkloadTypesToIgnore = originalWorkloadTypes
}()
tests := []struct {
name string
ignoredWorkloadTypes []string
resourceType string
description string
}{
{
name: "Issue #996 - ignoreJobs prevents Job processing",
ignoredWorkloadTypes: []string{"jobs"},
resourceType: "Job",
description: "Job resources are skipped entirely, preventing RBAC permission errors",
},
{
name: "Issue #996 - ignoreCronJobs prevents CronJob processing",
ignoredWorkloadTypes: []string{"cronjobs"},
resourceType: "CronJob",
description: "CronJob resources are skipped entirely, preventing RBAC permission errors",
},
{
name: "Issue #996 - both ignored prevent both types",
ignoredWorkloadTypes: []string{"jobs", "cronjobs"},
resourceType: "Job",
description: "Job resources are skipped entirely when both types are ignored",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Set the ignored workload types
options.WorkloadTypesToIgnore = tt.ignoredWorkloadTypes
config := Config{
ResourceName: "test-resource",
Annotation: "configmap.reloader.stakater.com/reload",
}
annotations := Map{
"configmap.reloader.stakater.com/reload": "test-config",
}
opts := &ReloaderOptions{
WorkloadTypesToIgnore: tt.ignoredWorkloadTypes,
AutoReloadAll: true, // Enable auto-reload to simplify test
ReloaderAutoAnnotation: "reloader.stakater.com/auto",
}
// Call ShouldReload
result := ShouldReload(config, tt.resourceType, annotations, Map{}, opts)
// Should not reload when workload type is ignored
if result.ShouldReload {
t.Errorf("Expected ShouldReload=false for ignored workload type %s, got=%v",
tt.resourceType, result.ShouldReload)
}
t.Logf("✓ %s", tt.description)
})
}
}

View File

@@ -1,12 +1,13 @@
package util
package common
import (
"github.com/stakater/Reloader/internal/pkg/constants"
"github.com/stakater/Reloader/internal/pkg/options"
"github.com/stakater/Reloader/internal/pkg/util"
v1 "k8s.io/api/core/v1"
)
//Config contains rolling upgrade configuration parameters
// Config contains rolling upgrade configuration parameters
type Config struct {
Namespace string
ResourceName string
@@ -15,6 +16,7 @@ type Config struct {
TypedAutoAnnotation string
SHAValue string
Type string
Labels map[string]string
}
// GetConfigmapConfig provides utility config for configmap
@@ -25,8 +27,9 @@ func GetConfigmapConfig(configmap *v1.ConfigMap) Config {
ResourceAnnotations: configmap.Annotations,
Annotation: options.ConfigmapUpdateOnChangeAnnotation,
TypedAutoAnnotation: options.ConfigmapReloaderAutoAnnotation,
SHAValue: GetSHAfromConfigmap(configmap),
SHAValue: util.GetSHAfromConfigmap(configmap),
Type: constants.ConfigmapEnvVarPostfix,
Labels: configmap.Labels,
}
}
@@ -38,7 +41,8 @@ func GetSecretConfig(secret *v1.Secret) Config {
ResourceAnnotations: secret.Annotations,
Annotation: options.SecretUpdateOnChangeAnnotation,
TypedAutoAnnotation: options.SecretReloaderAutoAnnotation,
SHAValue: GetSHAfromSecret(secret.Data),
SHAValue: util.GetSHAfromSecret(secret.Data),
Type: constants.SecretEnvVarPostfix,
Labels: secret.Labels,
}
}

129
pkg/common/metainfo.go Normal file
View File

@@ -0,0 +1,129 @@
package common
import (
"encoding/json"
"fmt"
"runtime"
"time"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// Version, Commit, and BuildDate are set during the build process
// using the -X linker flag to inject these values into the binary.
// They provide metadata about the build version, commit hash, build date, and whether there are
// uncommitted changes in the source code at the time of build.
// This information is useful for debugging and tracking the specific build of the Reloader binary.
var Version = "dev"
var Commit = "unknown"
var BuildDate = "unknown"
const (
MetaInfoConfigmapName = "reloader-meta-info"
MetaInfoConfigmapLabelKey = "reloader.stakater.com/meta-info"
MetaInfoConfigmapLabelValue = "reloader-oss"
)
// MetaInfo contains comprehensive metadata about the Reloader instance.
// This includes build information, configuration options, and deployment details.
type MetaInfo struct {
// BuildInfo contains information about the build version, commit, and compilation details
BuildInfo BuildInfo `json:"buildInfo"`
// ReloaderOptions contains all the configuration options and flags used by this Reloader instance
ReloaderOptions ReloaderOptions `json:"reloaderOptions"`
// DeploymentInfo contains metadata about the Kubernetes deployment of this Reloader instance
DeploymentInfo metav1.ObjectMeta `json:"deploymentInfo"`
}
// BuildInfo contains information about the build and version of the Reloader binary.
// This includes Go version, release version, commit details, and build timestamp.
type BuildInfo struct {
// GoVersion is the version of Go used to compile the binary
GoVersion string `json:"goVersion"`
// ReleaseVersion is the version tag or branch of the Reloader release
ReleaseVersion string `json:"releaseVersion"`
// CommitHash is the Git commit hash of the source code used to build this binary
CommitHash string `json:"commitHash"`
// CommitTime is the timestamp of the Git commit used to build this binary
CommitTime time.Time `json:"commitTime"`
}
func NewBuildInfo() *BuildInfo {
metaInfo := &BuildInfo{
GoVersion: runtime.Version(),
ReleaseVersion: Version,
CommitHash: Commit,
CommitTime: ParseUTCTime(BuildDate),
}
return metaInfo
}
func (m *MetaInfo) ToConfigMap() *v1.ConfigMap {
return &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: MetaInfoConfigmapName,
Namespace: m.DeploymentInfo.Namespace,
Labels: map[string]string{
MetaInfoConfigmapLabelKey: MetaInfoConfigmapLabelValue,
},
},
Data: map[string]string{
"buildInfo": toJson(m.BuildInfo),
"reloaderOptions": toJson(m.ReloaderOptions),
"deploymentInfo": toJson(m.DeploymentInfo),
},
}
}
func NewMetaInfo(configmap *v1.ConfigMap) (*MetaInfo, error) {
var buildInfo BuildInfo
if val, ok := configmap.Data["buildInfo"]; ok {
err := json.Unmarshal([]byte(val), &buildInfo)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal buildInfo: %w", err)
}
}
var reloaderOptions ReloaderOptions
if val, ok := configmap.Data["reloaderOptions"]; ok {
err := json.Unmarshal([]byte(val), &reloaderOptions)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal reloaderOptions: %w", err)
}
}
var deploymentInfo metav1.ObjectMeta
if val, ok := configmap.Data["deploymentInfo"]; ok {
err := json.Unmarshal([]byte(val), &deploymentInfo)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal deploymentInfo: %w", err)
}
}
return &MetaInfo{
BuildInfo: buildInfo,
ReloaderOptions: reloaderOptions,
DeploymentInfo: deploymentInfo,
}, nil
}
func toJson(data interface{}) string {
jsonData, err := json.Marshal(data)
if err != nil {
return ""
}
return string(jsonData)
}
func ParseUTCTime(value string) time.Time {
if value == "" {
return time.Time{} // Return zero time if value is empty
}
t, err := time.Parse(time.RFC3339, value)
if err != nil {
return time.Time{} // Return zero time if parsing fails
}
return t
}

View File

@@ -1,4 +1,4 @@
package util
package common
import "time"

View File

@@ -1,234 +0,0 @@
package metainfo
import (
"encoding/json"
"fmt"
"runtime"
"strconv"
"time"
"github.com/stakater/Reloader/internal/pkg/options"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// Version, Commit, and BuildDate are set during the build process
// using the -X linker flag to inject these values into the binary.
// They provide metadata about the build version, commit hash, build date, and whether there are
// uncommitted changes in the source code at the time of build.
// This information is useful for debugging and tracking the specific build of the Reloader binary.
var Version = "dev"
var Commit = "unknown"
var BuildDate = "unknown"
const (
MetaInfoConfigmapName = "reloader-meta-info"
MetaInfoConfigmapLabelKey = "reloader.stakater.com/meta-info"
MetaInfoConfigmapLabelValue = "reloader-oss"
)
// ReloaderOptions contains all configurable options for the Reloader controller.
// These options control how Reloader behaves when watching for changes in ConfigMaps and Secrets.
type ReloaderOptions struct {
// AutoReloadAll enables automatic reloading of all resources when their corresponding ConfigMaps/Secrets are updated
AutoReloadAll bool `json:"autoReloadAll"`
// ConfigmapUpdateOnChangeAnnotation is the annotation key used to detect changes in ConfigMaps specified by name
ConfigmapUpdateOnChangeAnnotation string `json:"configmapUpdateOnChangeAnnotation"`
// SecretUpdateOnChangeAnnotation is the annotation key used to detect changes in Secrets specified by name
SecretUpdateOnChangeAnnotation string `json:"secretUpdateOnChangeAnnotation"`
// ReloaderAutoAnnotation is the annotation key used to detect changes in any referenced ConfigMaps or Secrets
ReloaderAutoAnnotation string `json:"reloaderAutoAnnotation"`
// IgnoreResourceAnnotation is the annotation key used to ignore resources from being watched
IgnoreResourceAnnotation string `json:"ignoreResourceAnnotation"`
// ConfigmapReloaderAutoAnnotation is the annotation key used to detect changes in ConfigMaps only
ConfigmapReloaderAutoAnnotation string `json:"configmapReloaderAutoAnnotation"`
// SecretReloaderAutoAnnotation is the annotation key used to detect changes in Secrets only
SecretReloaderAutoAnnotation string `json:"secretReloaderAutoAnnotation"`
// ConfigmapExcludeReloaderAnnotation is the annotation key containing comma-separated list of ConfigMaps to exclude from watching
ConfigmapExcludeReloaderAnnotation string `json:"configmapExcludeReloaderAnnotation"`
// SecretExcludeReloaderAnnotation is the annotation key containing comma-separated list of Secrets to exclude from watching
SecretExcludeReloaderAnnotation string `json:"secretExcludeReloaderAnnotation"`
// AutoSearchAnnotation is the annotation key used to detect changes in ConfigMaps/Secrets tagged with SearchMatchAnnotation
AutoSearchAnnotation string `json:"autoSearchAnnotation"`
// SearchMatchAnnotation is the annotation key used to tag ConfigMaps/Secrets to be found by AutoSearchAnnotation
SearchMatchAnnotation string `json:"searchMatchAnnotation"`
// RolloutStrategyAnnotation is the annotation key used to define the rollout update strategy for workloads
RolloutStrategyAnnotation string `json:"rolloutStrategyAnnotation"`
// PauseDeploymentAnnotation is the annotation key used to define the time period to pause a deployment after
PauseDeploymentAnnotation string `json:"pauseDeploymentAnnotation"`
// PauseDeploymentTimeAnnotation is the annotation key used to indicate when a deployment was paused by Reloader
PauseDeploymentTimeAnnotation string `json:"pauseDeploymentTimeAnnotation"`
// LogFormat specifies the log format to use (json, or empty string for default text format)
LogFormat string `json:"logFormat"`
// LogLevel specifies the log level to use (trace, debug, info, warning, error, fatal, panic)
LogLevel string `json:"logLevel"`
// IsArgoRollouts indicates whether support for Argo Rollouts is enabled
IsArgoRollouts bool `json:"isArgoRollouts"`
// ReloadStrategy specifies the strategy used to trigger resource reloads (env-vars or annotations)
ReloadStrategy string `json:"reloadStrategy"`
// ReloadOnCreate indicates whether to trigger reloads when ConfigMaps/Secrets are created
ReloadOnCreate bool `json:"reloadOnCreate"`
// ReloadOnDelete indicates whether to trigger reloads when ConfigMaps/Secrets are deleted
ReloadOnDelete bool `json:"reloadOnDelete"`
// SyncAfterRestart indicates whether to sync add events after Reloader restarts (only works when ReloadOnCreate is true)
SyncAfterRestart bool `json:"syncAfterRestart"`
// EnableHA indicates whether High Availability mode is enabled with leader election
EnableHA bool `json:"enableHA"`
// WebhookUrl is the URL to send webhook notifications to instead of performing reloads
WebhookUrl string `json:"webhookUrl"`
// ResourcesToIgnore is a list of resource types to ignore (e.g., "configmaps" or "secrets")
ResourcesToIgnore []string `json:"resourcesToIgnore"`
// NamespaceSelectors is a list of label selectors to filter namespaces to watch
NamespaceSelectors []string `json:"namespaceSelectors"`
// ResourceSelectors is a list of label selectors to filter ConfigMaps and Secrets to watch
ResourceSelectors []string `json:"resourceSelectors"`
// NamespacesToIgnore is a list of namespace names to ignore when watching for changes
NamespacesToIgnore []string `json:"namespacesToIgnore"`
}
// MetaInfo contains comprehensive metadata about the Reloader instance.
// This includes build information, configuration options, and deployment details.
type MetaInfo struct {
// BuildInfo contains information about the build version, commit, and compilation details
BuildInfo BuildInfo `json:"buildInfo"`
// ReloaderOptions contains all the configuration options and flags used by this Reloader instance
ReloaderOptions ReloaderOptions `json:"reloaderOptions"`
// DeploymentInfo contains metadata about the Kubernetes deployment of this Reloader instance
DeploymentInfo metav1.ObjectMeta `json:"deploymentInfo"`
}
func GetReloaderOptions() *ReloaderOptions {
return &ReloaderOptions{
AutoReloadAll: options.AutoReloadAll,
ConfigmapUpdateOnChangeAnnotation: options.ConfigmapUpdateOnChangeAnnotation,
SecretUpdateOnChangeAnnotation: options.SecretUpdateOnChangeAnnotation,
ReloaderAutoAnnotation: options.ReloaderAutoAnnotation,
IgnoreResourceAnnotation: options.IgnoreResourceAnnotation,
ConfigmapReloaderAutoAnnotation: options.ConfigmapReloaderAutoAnnotation,
SecretReloaderAutoAnnotation: options.SecretReloaderAutoAnnotation,
ConfigmapExcludeReloaderAnnotation: options.ConfigmapExcludeReloaderAnnotation,
SecretExcludeReloaderAnnotation: options.SecretExcludeReloaderAnnotation,
AutoSearchAnnotation: options.AutoSearchAnnotation,
SearchMatchAnnotation: options.SearchMatchAnnotation,
RolloutStrategyAnnotation: options.RolloutStrategyAnnotation,
PauseDeploymentAnnotation: options.PauseDeploymentAnnotation,
PauseDeploymentTimeAnnotation: options.PauseDeploymentTimeAnnotation,
LogFormat: options.LogFormat,
LogLevel: options.LogLevel,
IsArgoRollouts: parseBool(options.IsArgoRollouts),
ReloadStrategy: options.ReloadStrategy,
ReloadOnCreate: parseBool(options.ReloadOnCreate),
ReloadOnDelete: parseBool(options.ReloadOnDelete),
SyncAfterRestart: options.SyncAfterRestart,
EnableHA: options.EnableHA,
WebhookUrl: options.WebhookUrl,
ResourcesToIgnore: options.ResourcesToIgnore,
NamespaceSelectors: options.NamespaceSelectors,
ResourceSelectors: options.ResourceSelectors,
NamespacesToIgnore: options.NamespacesToIgnore,
}
}
// BuildInfo contains information about the build and version of the Reloader binary.
// This includes Go version, release version, commit details, and build timestamp.
type BuildInfo struct {
// GoVersion is the version of Go used to compile the binary
GoVersion string `json:"goVersion"`
// ReleaseVersion is the version tag or branch of the Reloader release
ReleaseVersion string `json:"releaseVersion"`
// CommitHash is the Git commit hash of the source code used to build this binary
CommitHash string `json:"commitHash"`
// CommitTime is the timestamp of the Git commit used to build this binary
CommitTime time.Time `json:"commitTime"`
}
func NewBuildInfo() *BuildInfo {
metaInfo := &BuildInfo{
GoVersion: runtime.Version(),
ReleaseVersion: Version,
CommitHash: Commit,
CommitTime: ParseUTCTime(BuildDate),
}
return metaInfo
}
func (m *MetaInfo) ToConfigMap() *v1.ConfigMap {
return &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: MetaInfoConfigmapName,
Namespace: m.DeploymentInfo.Namespace,
Labels: map[string]string{
MetaInfoConfigmapLabelKey: MetaInfoConfigmapLabelValue,
},
},
Data: map[string]string{
"buildInfo": toJson(m.BuildInfo),
"reloaderOptions": toJson(m.ReloaderOptions),
"deploymentInfo": toJson(m.DeploymentInfo),
},
}
}
func NewMetaInfo(configmap *v1.ConfigMap) (*MetaInfo, error) {
var buildInfo BuildInfo
if val, ok := configmap.Data["buildInfo"]; ok {
err := json.Unmarshal([]byte(val), &buildInfo)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal buildInfo: %w", err)
}
}
var reloaderOptions ReloaderOptions
if val, ok := configmap.Data["reloaderOptions"]; ok {
err := json.Unmarshal([]byte(val), &reloaderOptions)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal reloaderOptions: %w", err)
}
}
var deploymentInfo metav1.ObjectMeta
if val, ok := configmap.Data["deploymentInfo"]; ok {
err := json.Unmarshal([]byte(val), &deploymentInfo)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal deploymentInfo: %w", err)
}
}
return &MetaInfo{
BuildInfo: buildInfo,
ReloaderOptions: reloaderOptions,
DeploymentInfo: deploymentInfo,
}, nil
}
func toJson(data interface{}) string {
jsonData, err := json.Marshal(data)
if err != nil {
return ""
}
return string(jsonData)
}
func parseBool(value string) bool {
if value == "" {
return false
}
result, err := strconv.ParseBool(value)
if err != nil {
return false // Default to false if parsing fails
}
return result
}
func ParseUTCTime(value string) time.Time {
if value == "" {
return time.Time{} // Return zero time if value is empty
}
t, err := time.Parse(time.RFC3339, value)
if err != nil {
return time.Time{} // Return zero time if parsing fails
}
return t
}