#3794·atlas

fix(atlas-operator): add toggle for cluster-wide secret access to the operator binary

Author: AidilRiskiCreated Sep 4, 2026Updated Sep 4, 2026

Summary

Related to https://github.com/ariga/atlas/issues/3789.

After disabling the cluster-wide secret access, the binary fails to start as it tries to watch the secrets. Is it possible to also add a toggle here? Maybe using an environment variable.

It should probably look like this: https://github.com/ariga/atlas-operator/compare/master...AidilRiski:atlas-operator:fix-conditional-secret-watch

Happy to open a PR if you'd accept contributions for this.

Diff

diff
diff --git a/cmd/main.go b/cmd/main.go
index ccc29b8..7db7867 100644
--- a/cmd/main.go
+++ b/cmd/main.go
@@ -66,6 +66,12 @@ const (
 	prewarmDevDB = "PREWARM_DEVDB"
 	// allowCustomConfig when enabled it allows the use of custom config
 	allowsCustomConfig = "ALLOW_CUSTOM_CONFIG"
+	// watchSecrets when enabled it registers Secret informers so the controllers
+	// re-reconcile when referenced Secrets change. Requires RBAC permission to
+	// list/watch Secrets. Disable in environments that provide credentials through
+	// other means (e.g. file-based injection via OpenBao/Vault) and do not grant
+	// the operator Secret access.
+	envWatchSecrets = "WATCH_SECRETS"
 )
 
 func init() {
@@ -191,12 +197,16 @@ func main() {
 	}
 	prewarmDevDB := getPrewarmDevDBEnv()
 	allowCustomConfig := getAllowCustomConfigEnv()
+	watchSecrets := getWatchSecretsEnv()
 	// Setup controller for AtlasSchema
 	schemaController := controller.NewAtlasSchemaReconciler(mgr, prewarmDevDB)
 	schemaController.SetAtlasClient(controller.NewAtlasExec)
 	if allowCustomConfig {
 		schemaController.AllowCustomConfig()
 	}
+	if watchSecrets {
+		schemaController.WatchSecrets()
+	}
 	if err := schemaController.SetupWithManager(mgr); err != nil {
 		setupLog.Error(err, "unable to create controller", "controller", "AtlasSchema")
 		os.Exit(1)
@@ -207,6 +217,9 @@ func main() {
 	if allowCustomConfig {
 		migrationController.AllowCustomConfig()
 	}
+	if watchSecrets {
+		migrationController.WatchSecrets()
+	}
 	if err = migrationController.SetupWithManager(mgr); err != nil {
 		setupLog.Error(err, "unable to create controller", "controller", "AtlasMigration")
 		os.Exit(1)
@@ -356,3 +369,18 @@ func getAllowCustomConfigEnv() bool {
 	}
 	return allowsCustomConfig
 }
+
+// getWatchSecretsEnv returns the value of the env var WATCH_SECRETS.
+// if the env var is not set, it returns true (backwards compatible).
+func getWatchSecretsEnv() bool {
+	env := os.Getenv(envWatchSecrets)
+	if env == "" {
+		return true
+	}
+	watchSecrets, err := strconv.ParseBool(env)
+	if err != nil {
+		setupLog.Error(err, "invalid value for env var WATCH_SECRETS, expected true or false")
+		os.Exit(1)
+	}
+	return watchSecrets
+}
diff --git a/internal/controller/atlasmigration_controller.go b/internal/controller/atlasmigration_controller.go
index dcc1c82..c8d3fbe 100644
--- a/internal/controller/atlasmigration_controller.go
+++ b/internal/controller/atlasmigration_controller.go
@@ -68,6 +68,7 @@ type (
 		devDB            *devDBReconciler
 		// AllowCustomConfig allows the controller to use custom atlas.hcl config.
 		allowCustomConfig bool
+		watchSecrets      bool
 	}
 	// migrationData is the data used to render the HCL template
 	// that will be used for Atlas CLI
@@ -178,15 +179,25 @@ func (r *AtlasMigrationReconciler) storeDirState(ctx context.Context, obj client
 
 // SetupWithManager sets up the controller with the Manager.
 func (r *AtlasMigrationReconciler) SetupWithManager(mgr ctrl.Manager) error {
-	return ctrl.NewControllerManagedBy(mgr).
+	b := ctrl.NewControllerManagedBy(mgr).
 		WithOptions(controller.Options{
 			MaxConcurrentReconciles: runtime.NumCPU(),
 		}).
 		For(&dbv1alpha1.AtlasMigration{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})).
 		Owns(&dbv1alpha1.AtlasMigration{}).
-		Watches(&corev1.Secret{}, r.secretWatcher).
-		Watches(&corev1.ConfigMap{}, r.configMapWatcher).
-		Complete(r)
+		Watches(&corev1.ConfigMap{}, r.configMapWatcher)
+	if r.watchSecrets {
+		b = b.Watches(&corev1.Secret{}, r.secretWatcher)
+	}
+	return b.Complete(r)
+}
+
+// WatchSecrets enables the Secret informer so the controller re-reconciles
+// when a referenced Secret changes. When disabled, the controller skips the
+// cluster-wide Secret LIST/WATCH, avoiding RBAC errors in environments that
+// provide credentials through other means (e.g. file-based injection).
+func (r *AtlasMigrationReconciler) WatchSecrets() {
+	r.watchSecrets = true
 }
 
 // SetAtlasClient sets the Atlas client for the reconciler.
diff --git a/internal/controller/atlasschema_controller.go b/internal/controller/atlasschema_controller.go
index f5301c0..05fbf9a 100644
--- a/internal/controller/atlasschema_controller.go
+++ b/internal/controller/atlasschema_controller.go
@@ -67,6 +67,7 @@ type (
 		devDB            *devDBReconciler
 		// AllowCustomConfig allows the controller to use custom atlas.hcl config.
 		allowCustomConfig bool
+		watchSecrets      bool
 	}
 	// managedData contains information about the managed database and its desired state.
 	managedData struct {
@@ -453,15 +454,25 @@ func (r *AtlasSchemaReconciler) schemaApply(
 
 // SetupWithManager sets up the controller with the Manager.
 func (r *AtlasSchemaReconciler) SetupWithManager(mgr ctrl.Manager) error {
-	return ctrl.NewControllerManagedBy(mgr).
+	b := ctrl.NewControllerManagedBy(mgr).
 		WithOptions(controller.Options{
 			MaxConcurrentReconciles: runtime.NumCPU(),
 		}).
 		For(&dbv1alpha1.AtlasSchema{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})).
 		Owns(&dbv1alpha1.AtlasSchema{}).
-		Watches(&corev1.ConfigMap{}, r.configMapWatcher).
-		Watches(&corev1.Secret{}, r.secretWatcher).
-		Complete(r)
+		Watches(&corev1.ConfigMap{}, r.configMapWatcher)
+	if r.watchSecrets {
+		b = b.Watches(&corev1.Secret{}, r.secretWatcher)
+	}
+	return b.Complete(r)
+}
+
+// WatchSecrets enables the Secret informer so the controller re-reconciles
+// when a referenced Secret changes. When disabled, the controller skips the
+// cluster-wide Secret LIST/WATCH, avoiding RBAC errors in environments that
+// provide credentials through other means (e.g. file-based injection).
+func (r *AtlasSchemaReconciler) WatchSecrets() {
+	r.watchSecrets = true
 }
 
 func (r *AtlasSchemaReconciler) watchRefs(res *dbv1alpha1.AtlasSchema) {