Skip to content

Commit cf636b0

Browse files
committed
feat: allow Argo Image Updater to watch specific namespaces
Add configuration to scope the controller to specific namespaces, enabling use of namespace-scoped Roles instead of ClusterRoles. Useful for multi-tenant environments where cluster-wide RBAC is not permitted. Signed-off-by: Graham Beckley <gbeckley@mozilla.com>
1 parent ab6da64 commit cf636b0

File tree

5 files changed

+123
-0
lines changed

5 files changed

+123
-0
lines changed

cmd/run.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"github.com/sirupsen/logrus"
1616
"github.com/spf13/cobra"
1717
ctrl "sigs.k8s.io/controller-runtime"
18+
"sigs.k8s.io/controller-runtime/pkg/cache"
1819
"sigs.k8s.io/controller-runtime/pkg/healthz"
1920
"sigs.k8s.io/controller-runtime/pkg/metrics/filters"
2021
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
@@ -161,13 +162,33 @@ This enables a CRD-driven approach to automated image updates with Argo CD.
161162
metricsServerOptions.FilterProvider = filters.WithAuthenticationAndAuthorization
162163
}
163164

165+
// Build cache options for namespace-scoped operation
166+
var cacheOptions cache.Options
167+
if cfg.WatchNamespaces != "" {
168+
setupLogger.Info("Configuring namespace-scoped operation", "watchNamespaces", cfg.WatchNamespaces)
169+
nsMap := make(map[string]cache.Config)
170+
for _, ns := range strings.Split(cfg.WatchNamespaces, ",") {
171+
ns = strings.TrimSpace(ns)
172+
if ns != "" {
173+
nsMap[ns] = cache.Config{}
174+
}
175+
}
176+
if len(nsMap) == 0 {
177+
return fmt.Errorf("--watch-namespaces flag provided but no valid namespaces specified")
178+
}
179+
cacheOptions = cache.Options{
180+
DefaultNamespaces: nsMap,
181+
}
182+
}
183+
164184
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
165185
Scheme: scheme,
166186
Metrics: metricsServerOptions,
167187
HealthProbeBindAddress: probeAddr,
168188
LeaderElection: enableLeaderElection,
169189
LeaderElectionID: "c21b75f2.argoproj.io",
170190
LeaderElectionNamespace: leaderElectionNamespace,
191+
Cache: cacheOptions,
171192
})
172193
if err != nil {
173194
setupLogger.Error(err, "unable to start manager")
@@ -300,6 +321,7 @@ This enables a CRD-driven approach to automated image updates with Argo CD.
300321
controllerCmd.Flags().IntVar(&cfg.MaxConcurrentApps, "max-concurrent-apps", env.ParseNumFromEnv("MAX_CONCURRENT_APPS", 10, 1, 100), "maximum number of ArgoCD applications that can be updated concurrently (must be >= 1)")
301322
controllerCmd.Flags().IntVar(&MaxConcurrentReconciles, "max-concurrent-reconciles", env.ParseNumFromEnv("MAX_CONCURRENT_RECONCILES", 1, 1, 10), "maximum number of concurrent Reconciles which can be run (must be >= 1)")
302323
controllerCmd.Flags().StringVar(&cfg.ArgocdNamespace, "argocd-namespace", env.GetStringVal("ARGOCD_NAMESPACE", ""), "namespace where ArgoCD runs in (controller namespace by default)")
324+
controllerCmd.Flags().StringVar(&cfg.WatchNamespaces, "watch-namespaces", env.GetStringVal("IMAGE_UPDATER_WATCH_NAMESPACES", ""), "Comma-separated list of namespaces to watch. Restricts controller to namespace-scoped operation. Empty means cluster-wide.")
303325
controllerCmd.Flags().BoolVar(&warmUpCache, "warmup-cache", true, "whether to perform a cache warm-up on startup")
304326
controllerCmd.Flags().BoolVar(&cfg.DisableKubeEvents, "disable-kube-events", env.GetBoolVal("IMAGE_UPDATER_KUBE_EVENTS", false), "Disable kubernetes events")
305327

config/manager/manager.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,12 @@ spec:
176176
name: argocd-image-updater-config
177177
key: webhook.ratelimit-allowed
178178
optional: true
179+
- name: IMAGE_UPDATER_WATCH_NAMESPACES
180+
valueFrom:
181+
configMapKeyRef:
182+
name: argocd-image-updater-config
183+
key: watch-namespaces
184+
optional: true
179185
securityContext:
180186
allowPrivilegeEscalation: false
181187
capabilities:

docs/install/cmd/run.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,19 @@ Can also be set using the *MAX_CONCURRENT_RECONCILES* environment variable.
173173

174174
port to start the metrics server on, "0" to disable (default "0")
175175

176+
**--watch-namespaces *namespace-list***
177+
178+
Comma-separated list of namespaces to watch. When set, the controller operates
179+
in namespace-scoped mode, only watching ImageUpdater CRs and Applications in
180+
the specified namespaces. This allows deployment with namespace-scoped RBAC
181+
(Role/RoleBinding) instead of cluster-wide RBAC (ClusterRole/ClusterRoleBinding).
182+
183+
When not set or empty, the controller watches all namespaces (cluster-wide mode).
184+
185+
Example: `--watch-namespaces=argocd,team-a,team-b`
186+
187+
Can also be set using the *IMAGE_UPDATER_WATCH_NAMESPACES* environment variable.
188+
176189
**--metrics-secure *enabled***
177190

178191
If set, the metrics endpoint is served securely via HTTPS. Use `--metrics-secure="false"` to use HTTP instead.

docs/install/installation.md

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,87 @@ subjects:
117117
namespace: <updater_namespace>
118118
```
119119
120+
#### Option 3: Namespace-scoped installation (without cluster-wide RBAC)
121+
122+
For multi-tenant environments or when cluster-wide RBAC is not permitted, the controller can operate in namespace-scoped mode. This restricts the controller to only watch specific namespaces using namespace-scoped `Role` and `RoleBinding` resources instead of `ClusterRole` and `ClusterRoleBinding`.
123+
124+
Let's assume you want to watch namespaces `argocd` and `team-a`, and the controller is installed in the `argocd` namespace.
125+
126+
1. **Install the Controller**
127+
128+
First, apply the installation manifest. You will modify the RBAC in the next step.
129+
130+
```shell
131+
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj-labs/argocd-image-updater/stable/config/install.yaml
132+
```
133+
134+
2. **Replace Cluster-wide RBAC with Namespace-scoped RBAC**
135+
136+
The default installation includes `ClusterRole` and `ClusterRoleBinding` resources that grant cluster-wide permissions. For namespace-scoped operation, you must replace these with `Role` and `RoleBinding` resources in each namespace you want to watch.
137+
138+
First, delete the cluster-wide RBAC resources:
139+
140+
```shell
141+
kubectl delete clusterrole argocd-image-updater-manager-role
142+
kubectl delete clusterrolebinding argocd-image-updater-manager-rolebinding
143+
```
144+
145+
Then, for each namespace you want to watch (e.g., `argocd`, `team-a`), create a `Role` and `RoleBinding`. The required permissions mirror those in `config/rbac/role.yaml`:
146+
147+
```yaml
148+
apiVersion: rbac.authorization.k8s.io/v1
149+
kind: Role
150+
metadata:
151+
name: argocd-image-updater-manager-role
152+
namespace: <target_namespace>
153+
rules:
154+
- apiGroups: [""]
155+
resources: ["events"]
156+
verbs: ["create"]
157+
- apiGroups: [""]
158+
resources: ["secrets", "configmaps"]
159+
verbs: ["get", "list", "watch"]
160+
- apiGroups: ["argocd-image-updater.argoproj.io"]
161+
resources: ["imageupdaters"]
162+
verbs: ["create", "delete", "get", "list", "patch", "update", "watch"]
163+
- apiGroups: ["argocd-image-updater.argoproj.io"]
164+
resources: ["imageupdaters/finalizers"]
165+
verbs: ["update"]
166+
- apiGroups: ["argocd-image-updater.argoproj.io"]
167+
resources: ["imageupdaters/status"]
168+
verbs: ["get", "patch", "update"]
169+
- apiGroups: ["argoproj.io"]
170+
resources: ["applications"]
171+
verbs: ["get", "list", "patch", "update", "watch"]
172+
---
173+
apiVersion: rbac.authorization.k8s.io/v1
174+
kind: RoleBinding
175+
metadata:
176+
name: argocd-image-updater-manager-rolebinding
177+
namespace: <target_namespace>
178+
roleRef:
179+
apiGroup: rbac.authorization.k8s.io
180+
kind: Role
181+
name: argocd-image-updater-manager-role
182+
subjects:
183+
- kind: ServiceAccount
184+
name: argocd-image-updater-controller
185+
namespace: argocd
186+
```
187+
188+
3. **Configure the Controller to Watch Specific Namespaces**
189+
190+
The controller must be configured to only watch the namespaces where you have granted permissions. Edit the `argocd-image-updater-config` ConfigMap and add the `watch-namespaces` key:
191+
192+
```yaml
193+
data:
194+
watch-namespaces: "argocd,team-a"
195+
```
196+
197+
Alternatively, you can add the `--watch-namespaces=argocd,team-a` flag to the container's `command` arguments in the deployment manifest, or set the `IMAGE_UPDATER_WATCH_NAMESPACES` environment variable.
198+
199+
The controller logs `Configuring namespace-scoped operation` on startup when this mode is active
200+
120201
### Configure the desired log level
121202

122203
While this step is optional, we recommend to set the log level explicitly.

internal/controller/imageupdater_controller.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ type ImageUpdaterConfig struct {
5959
DisableKubeEvents bool
6060
GitCreds git.CredsStore
6161
EnableWebhook bool
62+
WatchNamespaces string
6263
}
6364

6465
// ImageUpdaterReconciler reconciles a ImageUpdater object

0 commit comments

Comments
 (0)