mirror of
https://github.com/stakater/Reloader.git
synced 2026-08-23 22:16:45 +00:00
Merge branch 'feat/update-chart-v2' into feat/add-reloader-enterprise-in-v2-chart
This commit is contained in:
@@ -61,7 +61,7 @@ helm uninstall {{RELEASE_NAME}} -n {{NAMESPACE}}
|
||||
| `reloader.resourceLabelSelector` | List of comma separated label selectors, if multiple are provided they are combined with the AND operator | string | `""` |
|
||||
| `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.namespaces` | Explicit namespaces to watch (scoped mode). When non-empty and `reloader.watchGlobally` is `false`, Reloader watches exactly these namespaces and the chart creates a namespace-scoped Role + RoleBinding in each (no ClusterRole). The release namespace is always included automatically. Accepts either a YAML list (`["team-a","team-b"]`) or a comma-separated string (`"team-a,team-b"`). | list/string | `[]` |
|
||||
| `reloader.namespaces` | Explicit namespaces to watch (scoped mode). When non-empty and `reloader.watchGlobally` is `false`, Reloader watches exactly these namespaces and the chart creates a namespace-scoped Role + RoleBinding in each (no ClusterRole). The release namespace is not watched for reloads unless you list it explicitly; the chart only grants it a minimal Role for Reloader's internal meta-info ConfigMap (and leader-election in HA). Accepts either a YAML list (`["team-a","team-b"]`) or a comma-separated string (`"team-a,team-b"`). | list/string | `[]` |
|
||||
| `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` |
|
||||
|
||||
@@ -91,7 +91,10 @@ Create the namespace selector if it does not watch globally
|
||||
{{/*
|
||||
Namespaces to watch in scoped mode: exactly the user-supplied reloader.namespaces,
|
||||
trimmed, de-duped and sorted. The release namespace is intentionally NOT added here
|
||||
— Reloader watches only what the user asked for (an empty result means global mode).
|
||||
— Reloader watches only what the user asked for. An empty result is not necessarily
|
||||
global mode: with watchGlobally=false it becomes single-namespace mode (the release
|
||||
namespace, injected via --namespaces by reloader-effectiveNamespaces-csv); only with
|
||||
watchGlobally=true does empty mean watch-all.
|
||||
Returns a JSON-encoded list; consumers use mustFromJson to iterate.
|
||||
*/}}
|
||||
{{- define "reloader-watchNamespaces" -}}
|
||||
@@ -116,13 +119,45 @@ Comma-joined form of reloader-watchNamespaces, for the --namespaces CLI flag.
|
||||
{{- include "reloader-watchNamespaces" . | mustFromJson | join "," -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
The effective watched namespaces for the --namespaces CLI flag — the single
|
||||
chart-side source of truth for watch scope, so the binary never has to fall back
|
||||
to the KUBERNETES_NAMESPACE env:
|
||||
- scoped mode -> the cleaned reloader.namespaces list
|
||||
- single-namespace mode -> the release namespace (watchGlobally=false, no list)
|
||||
- global mode -> empty (no --namespaces flag; watch all)
|
||||
Returns a comma-joined string ("" in global mode).
|
||||
*/}}
|
||||
{{- define "reloader-effectiveNamespaces-csv" -}}
|
||||
{{- $watch := include "reloader-watchNamespaces" . | mustFromJson -}}
|
||||
{{- if $watch -}}
|
||||
{{- $watch | join "," -}}
|
||||
{{- else if not .Values.reloader.watchGlobally -}}
|
||||
{{- .Values.namespace | default .Release.Namespace -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Whether Reloader runs in scoped mode. This is the single source of truth for the
|
||||
scoped-vs-global decision: it is true only when the cleaned watch list
|
||||
(reloader-watchNamespaces) is non-empty. Gate on this rather than the raw
|
||||
.Values.reloader.namespaces, which is truthy even for values like " , " that trim
|
||||
to an empty list (those must fall through to global/single-namespace mode).
|
||||
Returns "true" (truthy) or "" (falsy).
|
||||
*/}}
|
||||
{{- define "reloader-isScoped" -}}
|
||||
{{- if include "reloader-watchNamespaces" . | mustFromJson -}}
|
||||
true
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Fails the render on an inconsistent namespace configuration: reloader.namespaces
|
||||
(scoped mode) requires reloader.watchGlobally=false. Included from deployment.yaml
|
||||
so it is validated once regardless of which templates render.
|
||||
*/}}
|
||||
{{- define "reloader-validate-namespaces" -}}
|
||||
{{- if and .Values.reloader.watchGlobally .Values.reloader.namespaces -}}
|
||||
{{- if and .Values.reloader.watchGlobally (include "reloader-isScoped" .) -}}
|
||||
{{- fail "reloader.namespaces is set but reloader.watchGlobally is true; set reloader.watchGlobally=false to use scoped namespace mode." -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
@@ -131,9 +166,9 @@ so it is validated once regardless of which templates render.
|
||||
RBAC rules Reloader needs in its own (release) namespace, independent of the
|
||||
watched namespaces. Reloader publishes an internal meta-info ConfigMap there in
|
||||
every mode, so configmap write access is always granted. In scoped mode the
|
||||
release namespace is not covered by the watch RBAC, so leader-election events
|
||||
(and leases under HA) are granted here too; in global/single mode those are
|
||||
already covered by the ClusterRole or the single-namespace Role.
|
||||
release namespace is not covered by the watch RBAC, so under HA the leader-election
|
||||
leases and the events it emits are granted here too; in global/single mode those
|
||||
are already covered by the ClusterRole or the single-namespace Role.
|
||||
Expects the root context ($) as its argument.
|
||||
*/}}
|
||||
{{- define "reloader-release-rules" }}
|
||||
@@ -146,7 +181,15 @@ Expects the root context ($) as its argument.
|
||||
- create
|
||||
- update
|
||||
- patch
|
||||
{{- if .Values.reloader.namespaces }}
|
||||
{{- if and (include "reloader-isScoped" .) .Values.reloader.enableHA }}
|
||||
- apiGroups:
|
||||
- "coordination.k8s.io"
|
||||
resources:
|
||||
- leases
|
||||
verbs:
|
||||
- create
|
||||
- get
|
||||
- update
|
||||
- apiGroups:
|
||||
- ""
|
||||
- "events.k8s.io"
|
||||
@@ -156,16 +199,6 @@ Expects the root context ($) as its argument.
|
||||
- create
|
||||
- patch
|
||||
- update
|
||||
{{- if .Values.reloader.enableHA }}
|
||||
- apiGroups:
|
||||
- "coordination.k8s.io"
|
||||
resources:
|
||||
- leases
|
||||
verbs:
|
||||
- create
|
||||
- get
|
||||
- update
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end -}}
|
||||
|
||||
|
||||
@@ -144,12 +144,6 @@ spec:
|
||||
fieldRef:
|
||||
fieldPath: {{ $value | quote}}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if and (eq .Values.reloader.watchGlobally false) (not .Values.reloader.namespaces) }}
|
||||
- name: KUBERNETES_NAMESPACE
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.namespace
|
||||
{{- end }}
|
||||
|
||||
- name: RELOADER_NAMESPACE
|
||||
@@ -216,7 +210,7 @@ spec:
|
||||
{{- . | toYaml | nindent 10 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if or (.Values.reloader.logFormat) (.Values.reloader.logLevel) (.Values.reloader.ignoreSecrets) (and .Values.reloader.ignoreNamespaces .Values.reloader.watchGlobally) (.Values.reloader.namespaces) (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) (.Values.reloader.enableCSIIntegration)}}
|
||||
{{- if or (.Values.reloader.logFormat) (.Values.reloader.logLevel) (.Values.reloader.ignoreSecrets) (and .Values.reloader.ignoreNamespaces .Values.reloader.watchGlobally) (include "reloader-effectiveNamespaces-csv" .) (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) (.Values.reloader.enableCSIIntegration)}}
|
||||
args:
|
||||
{{- if .Values.reloader.logFormat }}
|
||||
- "--log-format={{ .Values.reloader.logFormat }}"
|
||||
@@ -237,8 +231,8 @@ spec:
|
||||
{{- else if .Values.reloader.ignoreCronJobs }}
|
||||
- "--ignored-workload-types=cronjobs"
|
||||
{{- end }}
|
||||
{{- if .Values.reloader.namespaces }}
|
||||
- "--namespaces={{ include "reloader-watchNamespaces-csv" . }}"
|
||||
{{- if (include "reloader-effectiveNamespaces-csv" .) }}
|
||||
- "--namespaces={{ include "reloader-effectiveNamespaces-csv" . }}"
|
||||
{{- end }}
|
||||
{{- if and .Values.reloader.ignoreNamespaces .Values.reloader.watchGlobally }}
|
||||
- "--namespaces-to-ignore={{ .Values.reloader.ignoreNamespaces }}"
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
{{- if not (.Capabilities.APIVersions.Has "rbac.authorization.k8s.io/v1") }}
|
||||
{{- $apiVersion = "rbac.authorization.k8s.io/v1beta1" }}
|
||||
{{- end }}
|
||||
{{- if .Values.reloader.namespaces }}
|
||||
{{- if (include "reloader-isScoped" .) }}
|
||||
{{- range $ns := (include "reloader-watchNamespaces" . | mustFromJson) }}
|
||||
apiVersion: {{ $apiVersion }}
|
||||
kind: Role
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
{{- if not (.Capabilities.APIVersions.Has "rbac.authorization.k8s.io/v1") }}
|
||||
{{- $apiVersion = "rbac.authorization.k8s.io/v1beta1" }}
|
||||
{{- end }}
|
||||
{{- if .Values.reloader.namespaces }}
|
||||
{{- if (include "reloader-isScoped" .) }}
|
||||
{{- range $ns := (include "reloader-watchNamespaces" . | mustFromJson) }}
|
||||
apiVersion: {{ $apiVersion }}
|
||||
kind: RoleBinding
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
suite: Namespace watch flags & validation
|
||||
templates:
|
||||
- deployment.yaml
|
||||
release:
|
||||
name: reloader
|
||||
namespace: reloader-ns
|
||||
tests:
|
||||
# ---------------------------------------------------------------- scoped mode
|
||||
- it: scoped mode passes the watched namespaces via --namespaces
|
||||
set:
|
||||
reloader:
|
||||
watchGlobally: false
|
||||
namespaces:
|
||||
- team-a
|
||||
- team-b
|
||||
asserts:
|
||||
- contains:
|
||||
path: spec.template.spec.containers[0].args
|
||||
content: "--namespaces=team-a,team-b"
|
||||
|
||||
- it: namespaces are de-duplicated and sorted for the --namespaces flag
|
||||
set:
|
||||
reloader:
|
||||
watchGlobally: false
|
||||
namespaces:
|
||||
- team-b
|
||||
- team-a
|
||||
- team-a
|
||||
asserts:
|
||||
- contains:
|
||||
path: spec.template.spec.containers[0].args
|
||||
content: "--namespaces=team-a,team-b"
|
||||
|
||||
- it: a comma-separated string value is accepted for reloader.namespaces
|
||||
set:
|
||||
reloader:
|
||||
watchGlobally: false
|
||||
namespaces: "team-a,team-b"
|
||||
asserts:
|
||||
- contains:
|
||||
path: spec.template.spec.containers[0].args
|
||||
content: "--namespaces=team-a,team-b"
|
||||
|
||||
# ----------------------------------------------------------- single-ns mode
|
||||
- it: single-namespace mode passes the release namespace via --namespaces
|
||||
set:
|
||||
reloader:
|
||||
watchGlobally: false
|
||||
asserts:
|
||||
- contains:
|
||||
path: spec.template.spec.containers[0].args
|
||||
content: "--namespaces=reloader-ns"
|
||||
|
||||
- it: whitespace-only namespaces fall through to single-namespace mode
|
||||
set:
|
||||
reloader:
|
||||
watchGlobally: false
|
||||
namespaces:
|
||||
- " "
|
||||
- " "
|
||||
asserts:
|
||||
- contains:
|
||||
path: spec.template.spec.containers[0].args
|
||||
content: "--namespaces=reloader-ns"
|
||||
|
||||
- it: KUBERNETES_NAMESPACE env is no longer injected in single-namespace mode
|
||||
set:
|
||||
reloader:
|
||||
watchGlobally: false
|
||||
asserts:
|
||||
- notContains:
|
||||
path: spec.template.spec.containers[0].env
|
||||
content:
|
||||
name: KUBERNETES_NAMESPACE
|
||||
any: true
|
||||
|
||||
# ---------------------------------------------------------------- global mode
|
||||
- it: global mode does not pass a --namespaces flag
|
||||
set:
|
||||
reloader:
|
||||
watchGlobally: true
|
||||
logLevel: info
|
||||
asserts:
|
||||
- notContains:
|
||||
path: spec.template.spec.containers[0].args
|
||||
content: "--namespaces=reloader-ns"
|
||||
- notContains:
|
||||
path: spec.template.spec.containers[0].args
|
||||
content: "--namespaces=team-a"
|
||||
|
||||
- it: namespaces-to-ignore is only emitted in global mode
|
||||
set:
|
||||
reloader:
|
||||
watchGlobally: true
|
||||
ignoreNamespaces: "kube-system"
|
||||
asserts:
|
||||
- contains:
|
||||
path: spec.template.spec.containers[0].args
|
||||
content: "--namespaces-to-ignore=kube-system"
|
||||
|
||||
- it: namespaces-to-ignore is dropped in single-namespace mode
|
||||
set:
|
||||
reloader:
|
||||
watchGlobally: false
|
||||
ignoreNamespaces: "kube-system"
|
||||
asserts:
|
||||
- notContains:
|
||||
path: spec.template.spec.containers[0].args
|
||||
content: "--namespaces-to-ignore=kube-system"
|
||||
|
||||
# ------------------------------------------------------------- validation
|
||||
- it: fails to render when reloader.namespaces is set but watchGlobally is true
|
||||
set:
|
||||
reloader:
|
||||
watchGlobally: true
|
||||
namespaces:
|
||||
- team-a
|
||||
asserts:
|
||||
- failedTemplate:
|
||||
errorMessage: "reloader.namespaces is set but reloader.watchGlobally is true; set reloader.watchGlobally=false to use scoped namespace mode."
|
||||
|
||||
- it: does not fail validation for whitespace-only namespaces with watchGlobally true
|
||||
set:
|
||||
reloader:
|
||||
watchGlobally: true
|
||||
namespaces:
|
||||
- " "
|
||||
asserts:
|
||||
# whitespace trims to an empty watch list, so this is global mode, not scoped -> renders fine
|
||||
- hasDocuments:
|
||||
count: 1
|
||||
- notContains:
|
||||
path: spec.template.spec.containers[0].args
|
||||
content: "--namespaces=reloader-ns"
|
||||
@@ -0,0 +1,234 @@
|
||||
suite: Namespace-scoped RBAC
|
||||
templates:
|
||||
- role.yaml
|
||||
- rolebinding.yaml
|
||||
- clusterrole.yaml
|
||||
release:
|
||||
name: reloader
|
||||
namespace: reloader-ns
|
||||
tests:
|
||||
# ---------------------------------------------------------------- scoped mode
|
||||
- it: scoped mode renders a Role + RoleBinding per watched namespace plus the metadata role, and no ClusterRole
|
||||
set:
|
||||
reloader:
|
||||
watchGlobally: false
|
||||
namespaces:
|
||||
- team-a
|
||||
- team-b
|
||||
asserts:
|
||||
# 2 watched Roles + 1 metadata Role
|
||||
- hasDocuments:
|
||||
count: 3
|
||||
template: role.yaml
|
||||
- hasDocuments:
|
||||
count: 3
|
||||
template: rolebinding.yaml
|
||||
- hasDocuments:
|
||||
count: 0
|
||||
template: clusterrole.yaml
|
||||
|
||||
- it: scoped mode grants the namespaced workload rules in a watched namespace
|
||||
set:
|
||||
reloader:
|
||||
watchGlobally: false
|
||||
namespaces:
|
||||
- team-a
|
||||
- team-b
|
||||
template: role.yaml
|
||||
documentSelector:
|
||||
path: metadata.namespace
|
||||
value: team-a
|
||||
asserts:
|
||||
- isKind:
|
||||
of: Role
|
||||
- equal:
|
||||
path: rules[0].apiGroups[0]
|
||||
value: ""
|
||||
- contains:
|
||||
path: rules[0].resources
|
||||
content: secrets
|
||||
- contains:
|
||||
path: rules[0].resources
|
||||
content: configmaps
|
||||
- contains:
|
||||
path: rules
|
||||
content:
|
||||
apiGroups:
|
||||
- "apps"
|
||||
resources:
|
||||
- deployments
|
||||
- daemonsets
|
||||
- statefulsets
|
||||
verbs:
|
||||
- list
|
||||
- get
|
||||
- watch
|
||||
- update
|
||||
- patch
|
||||
|
||||
- it: scoped RoleBinding in a watched namespace binds the release ServiceAccount to the namespaced Role
|
||||
set:
|
||||
reloader:
|
||||
watchGlobally: false
|
||||
namespaces:
|
||||
- team-a
|
||||
- team-b
|
||||
template: rolebinding.yaml
|
||||
documentSelector:
|
||||
path: metadata.namespace
|
||||
value: team-b
|
||||
asserts:
|
||||
- isKind:
|
||||
of: RoleBinding
|
||||
- equal:
|
||||
path: roleRef.name
|
||||
value: reloader-reloader-role
|
||||
- equal:
|
||||
path: subjects[0].namespace
|
||||
value: reloader-ns
|
||||
|
||||
# ------------------------------------------------------------- metadata role
|
||||
- it: metadata role is created in the release namespace with configmap write in scoped mode
|
||||
set:
|
||||
reloader:
|
||||
watchGlobally: false
|
||||
namespaces:
|
||||
- team-a
|
||||
template: role.yaml
|
||||
documentSelector:
|
||||
path: metadata.name
|
||||
value: reloader-reloader-metadata-role
|
||||
asserts:
|
||||
- equal:
|
||||
path: metadata.namespace
|
||||
value: reloader-ns
|
||||
- contains:
|
||||
path: rules[0].resources
|
||||
content: configmaps
|
||||
- contains:
|
||||
path: rules[0].verbs
|
||||
content: create
|
||||
|
||||
- it: metadata role also grants leases and events in the release namespace under HA + scoped mode
|
||||
set:
|
||||
reloader:
|
||||
watchGlobally: false
|
||||
enableHA: true
|
||||
namespaces:
|
||||
- team-a
|
||||
template: role.yaml
|
||||
documentSelector:
|
||||
path: metadata.name
|
||||
value: reloader-reloader-metadata-role
|
||||
asserts:
|
||||
- contains:
|
||||
path: rules
|
||||
content:
|
||||
apiGroups:
|
||||
- "coordination.k8s.io"
|
||||
resources:
|
||||
- leases
|
||||
verbs:
|
||||
- create
|
||||
- get
|
||||
- update
|
||||
|
||||
- it: metadata role does NOT grant leases when HA is disabled in scoped mode
|
||||
set:
|
||||
reloader:
|
||||
watchGlobally: false
|
||||
enableHA: false
|
||||
namespaces:
|
||||
- team-a
|
||||
template: role.yaml
|
||||
documentSelector:
|
||||
path: metadata.name
|
||||
value: reloader-reloader-metadata-role
|
||||
asserts:
|
||||
- notContains:
|
||||
path: rules
|
||||
content:
|
||||
apiGroups:
|
||||
- "coordination.k8s.io"
|
||||
resources:
|
||||
- leases
|
||||
verbs:
|
||||
- create
|
||||
- get
|
||||
- update
|
||||
|
||||
# ----------------------------------------------------------- single-ns mode
|
||||
- it: single-namespace mode (watchGlobally=false, no list) renders one Role in the release namespace and no ClusterRole
|
||||
set:
|
||||
reloader:
|
||||
watchGlobally: false
|
||||
asserts:
|
||||
# 1 single Role + 1 metadata Role
|
||||
- hasDocuments:
|
||||
count: 2
|
||||
template: role.yaml
|
||||
- hasDocuments:
|
||||
count: 0
|
||||
template: clusterrole.yaml
|
||||
- equal:
|
||||
path: metadata.namespace
|
||||
value: reloader-ns
|
||||
template: role.yaml
|
||||
documentIndex: 0
|
||||
|
||||
# ---------------------------------------------------------------- global mode
|
||||
- it: global mode renders a ClusterRole and only the metadata Role (no watched Roles)
|
||||
set:
|
||||
reloader:
|
||||
watchGlobally: true
|
||||
asserts:
|
||||
- hasDocuments:
|
||||
count: 1
|
||||
template: clusterrole.yaml
|
||||
- isKind:
|
||||
of: ClusterRole
|
||||
template: clusterrole.yaml
|
||||
# only the metadata role remains from role.yaml
|
||||
- hasDocuments:
|
||||
count: 1
|
||||
template: role.yaml
|
||||
- equal:
|
||||
path: metadata.name
|
||||
value: reloader-reloader-metadata-role
|
||||
template: role.yaml
|
||||
documentIndex: 0
|
||||
|
||||
# -------------------------------------------------------- input normalization
|
||||
- it: whitespace-only namespaces fall through to single-namespace mode (no watched Roles)
|
||||
set:
|
||||
reloader:
|
||||
watchGlobally: false
|
||||
namespaces:
|
||||
- " "
|
||||
- " "
|
||||
asserts:
|
||||
# cleaned list is empty -> single Role + metadata Role, both in release ns
|
||||
- hasDocuments:
|
||||
count: 2
|
||||
template: role.yaml
|
||||
- equal:
|
||||
path: metadata.namespace
|
||||
value: reloader-ns
|
||||
template: role.yaml
|
||||
documentIndex: 0
|
||||
|
||||
- it: rbac disabled renders no Roles, RoleBindings, or ClusterRole
|
||||
set:
|
||||
reloader:
|
||||
watchGlobally: false
|
||||
rbac:
|
||||
enabled: false
|
||||
namespaces:
|
||||
- team-a
|
||||
asserts:
|
||||
- hasDocuments:
|
||||
count: 0
|
||||
template: role.yaml
|
||||
- hasDocuments:
|
||||
count: 0
|
||||
template: rolebinding.yaml
|
||||
Reference in New Issue
Block a user