Skip to content

Conversation

@chetan-rns
Copy link
Collaborator

@chetan-rns chetan-rns commented Jan 28, 2026

What does this PR do / why we need it:
Support mapping applications to target agents based on the .spec.destination.name instead of the app namespace.

  1. Added ARGOCD_PRINCIPAL_DESTINATION_BASED_MAPPING and ARGOCD_AGENT_DESTINATION_BASED_MAPPING to toggle the feature
  2. Added ARGOCD_AGENT_CREATE_NAMESPACE to create namespaces on the fly
  3. Added logic to route the apps based on the destination name
  4. Track the app to agent mapping, which the redis proxy component will use.
  5. Only managed mode is supported
  6. Added unit tests, e2e tests, and docs.

Which issue(s) this PR fixes:

Fixes #593

How to test changes / Special notes to the reviewer:

  1. Start the principal with --destination-based-mapping and --allowed-namespaces configured
  2. Start the agent with destination-based-mapping, --create-namespace, and --allowed-namespaces configured
  3. Create a new namespace on the principal called test
  4. Create an Argo CD app in this test namespace with the spec.destination.name pointing to the agent name (agent-managed)
  5. The managed agent should create a new namespace test and sync the app to this test namespace instead of the agent's namespace.

Checklist

  • [✅ ] Documentation update is required by this PR (and has been updated) OR no documentation update is required.

Summary by CodeRabbit

  • New Features

    • Destination-based mapping with CLI/env flags, allowed-namespaces support, and optional automatic creation of target namespaces; Redis proxy and server paths now route by app destination when enabled.
  • Behavior Changes

    • When enabled, apps are created/managed in their original destination namespaces; autonomous agents remain namespace-bound. Invalid flag combinations are rejected.
  • Documentation

    • New concept guide with migration guidance and examples.
  • Tests

    • Extensive unit and end-to-end coverage added.
  • Chores

    • Helm/Kubernetes manifests and RBAC updated to expose new config keys and namespace permissions.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 28, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds destination-based mapping allowing applications to be routed by spec.destination.name instead of agent namespace. Introduces agent/principal flags and options, propagates mapping through Redis proxy, resync, callbacks, ApplicationManager/AppProject, adds optional namespace creation, CLI flags, docs, unit and E2E tests, and RBAC/chart updates.

Changes

Cohort / File(s) Summary
Agent core & options
agent/agent.go, agent/options.go
New Agent fields/options destinationBasedMapping, createNamespace; validation and wiring into NewAgent; option constructors added.
Agent inbound & Redis
agent/inbound.go, agent/inbound_redis.go, agent/inbound_test.go
Resolve per-application target namespace, validate/optionally create namespace, and conditionally skip namespace-stripping for Redis keys; new unit test for create with destination-based mapping.
Agent connection / resync
agent/connection.go
Resync handler now receives destination-based mapping via WithDestinationBasedMapping.
Principal server & callbacks
principal/server.go, principal/callbacks.go, principal/event.go, principal/options.go
Server adds destinationBasedMapping flag and appToAgent map; callbacks determine agent by destination or namespace, track/untrack apps, and propagate mapping to routing and resync.
Principal Redis proxy
principal/redisproxy/redisproxy.go, principal/redisproxy/redisproxy_test.go
Add AgentLookupFunc and setter; extractAgentNameFromRedisCommandKey becomes method using lookup for destination-based resolution; tests inject lookup.
Application manager & AppProject
internal/manager/application/application.go, internal/manager/appproject/appproject.go, internal/manager/appproject/appproject_test.go
ApplicationManager and AgentSpecificAppProject gain destination-based flag; preserve SourceNamespaces when enabled; conditional namespace handling in update/compare/status/delete flows.
Resync handler
internal/resync/resync.go
RequestHandler adds destinationBasedMapping flag and fluent setter; request processing uses actual resource Namespace when enabled; AppProject helper calls updated.
CLI wiring
cmd/argocd-agent/agent.go, cmd/argocd-agent/principal.go
New CLI flags: --destination-based-mapping, --create-namespace, --allowed-namespaces; options propagated into startup.
Docs & dev env
docs/concepts/agent-mapping.md, mkdocs.yml, hack/dev-env/*
New concept doc and mkdocs nav entry; dev scripts export new env defaults and pass allowed-namespaces flag.
E2E & tests
test/e2e/dest_mapping_test.go, principal/callbacks_test.go, principal/redisproxy/redisproxy_test.go, agent/inbound_test.go, test/e2e/redis_proxy_test.go
New E2E suite for destination mapping; extensive principal callback unit tests; RedisProxy tests updated; helper and unit test additions.
Helm/K8s manifests & RBAC
install/.../agent-params-cm.yaml, install/.../agent-deployment.yaml, install/.../agent-clusterrole.yaml, install/.../principal-params-cm.yaml, install/.../principal-deployment.yaml, install/.../values.yaml, install/helm-repo/.../values.schema.json, install/helm-repo/.../README.md
New config keys/env vars, RBAC additions (namespace create, Application verbs), chart values and schema for destination-based mapping, createNamespace, and allowedNamespaces.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Principal
  participant RedisProxy
  participant Agent
  participant AppManager
  Principal->>Principal: receive App create/update (has spec.destination.name)
  Principal->>Principal: determine agentName (destinationBasedMapping? use destination.name : use namespace)
  Principal->>RedisProxy: publish event keyed by agentName
  RedisProxy->>Agent: forward pub/sub to agent queue (agentName)
  Agent->>Agent: resolve target namespace (getTargetNamespaceForApp)
  Agent->>AppManager: create/update Application in resolved namespace
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • jannfis
  • mikeshng
  • jgwest

Poem

🐰
I hopped through namespaces far and near,
Destinations now whisper which agent is near.
Apps land where they belong, tidy and bright,
Namespaces appear when asked—just right.
Hop, deploy, celebrate—carrots for a job done right! 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: support destination based agent mapping' accurately summarizes the main change: adding destination-based agent mapping as a new feature.
Linked Issues check ✅ Passed The PR implementation fulfills issue #593 requirements by enabling destination-based mapping via spec.destination.name, supporting multi-tenant isolation, preserving AppProject destination restrictions, and implementing the feature in managed mode with comprehensive configuration options.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing destination-based agent mapping: core routing logic, configuration flags, namespace management, RBAC updates, Helm/Kubernetes deployment configurations, documentation, and comprehensive test coverage align with the PR objectives.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov-commenter
Copy link

codecov-commenter commented Jan 28, 2026

Codecov Report

❌ Patch coverage is 56.70498% with 113 lines in your changes missing coverage. Please review.
✅ Project coverage is 46.48%. Comparing base (abce285) to head (446ade1).

Files with missing lines Patch % Lines
principal/callbacks.go 72.91% 21 Missing and 5 partials ⚠️
agent/inbound.go 60.86% 12 Missing and 6 partials ⚠️
cmd/argocd-agent/agent.go 0.00% 15 Missing ⚠️
agent/agent.go 20.00% 9 Missing and 3 partials ⚠️
agent/options.go 0.00% 8 Missing ⚠️
internal/resync/resync.go 0.00% 8 Missing ⚠️
principal/redisproxy/redisproxy.go 73.91% 6 Missing ⚠️
cmd/argocd-agent/principal.go 0.00% 5 Missing ⚠️
agent/inbound_redis.go 63.63% 2 Missing and 2 partials ⚠️
principal/options.go 0.00% 4 Missing ⚠️
... and 3 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #726      +/-   ##
==========================================
+ Coverage   46.18%   46.48%   +0.30%     
==========================================
  Files         101      101              
  Lines       12513    12709     +196     
==========================================
+ Hits         5779     5908     +129     
- Misses       6179     6233      +54     
- Partials      555      568      +13     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@chetan-rns chetan-rns force-pushed the destination-mapping branch 2 times, most recently from 24f36df to 5137c23 Compare February 2, 2026 07:30
@chetan-rns chetan-rns marked this pull request as ready for review February 2, 2026 12:25
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
principal/callbacks.go (1)

100-144: ⚠️ Potential issue | 🟠 Major

Guard against empty agent name on updates.

When spec.destination.name is empty, updateAppCallback will create/route to a queue keyed by "", while create/delete already short‑circuit. Add the same guard here to avoid misrouting and silent queue creation.

🛠️ Suggested fix
-	agentName := s.getAgentNameForApp(new)
-
-	logCtx := log().WithFields(logrus.Fields{
+	agentName := s.getAgentNameForApp(new)
+
+	logCtx := log().WithFields(logrus.Fields{
 		"component":        "EventCallback",
 		"queue":            agentName,
 		"event":            "application_update",
 		"application_name": old.Name,
 	})
+	if agentName == "" {
+		logCtx.Error("Failed to get agent name for application")
+		return
+	}
🤖 Fix all issues with AI agents
In `@agent/inbound.go`:
- Around line 910-941: The agent currently may create namespaces without
checking the allowlist; before calling ensureNamespaceExists() in
createApplication() and updateApplication() add a validation step that, when
a.createNamespace and a.destinationBasedMapping are enabled, checks the target
namespace against the agent's allowedNamespaces (or equivalent allowlist) and
rejects the request with an error if the namespace is not permitted; ensure this
check runs prior to any call to ensureNamespaceExists() (and returns a clear,
early error) so disallowed namespaces are never passed to
ensureNamespaceExists() or created.

In `@docs/concepts/agent-mapping.md`:
- Around line 99-102: Hyphenate the compound modifiers in the prose: change
"destination based mapping" to "destination-based mapping" and "same namespace
as the principal" context if present change "namespace based" to
"namespace-based" for consistency; update the sentence in the Create Namespace
note (and the other occurrence referenced) to use "destination-based" and
"namespace-based" where appropriate so it matches the rest of the document and
grammar checks.
- Around line 9-16: The Markdown table in docs/concepts/agent-mapping.md
violates MD060 due to inconsistent column pipe spacing; fix it by aligning pipes
and cell padding so each row uses the same number of spaces around pipe
separators (ensure header, separator, and all rows match), e.g., adjust the
header row "| Feature | Namespace-Based Mapping | Destination-Based Mapping |",
the separator row
"|---------|------------------------|---------------------------|", and each
subsequent row so columns are vertically aligned and consistent across the
table.

In `@principal/redisproxy/redisproxy.go`:
- Around line 826-835: The current fallback in the agent resolution always
returns namespace even when an agentLookupFn is configured but returns no match;
change the behavior in the branch that checks rp.agentLookupFn so that if
rp.agentLookupFn(namespace, appName) returns an empty string you return "" (and
nil error) instead of falling through to return namespace, or alternatively
update the comment/contract to explicitly state that a configured agentLookupFn
will cause an empty result to be treated as “no route” and not fall back to
namespace; locate the logic using rp.agentLookupFn, the variables namespace and
appName, and the final return of namespace to implement this change.

In `@test/e2e/dest_mapping_test.go`:
- Around line 51-53: The TearDownSuite currently resets SourceNamespaces to "*"
and deletes the ConfigMap key without restoring prior state; update
DestinationMappingTestSuite to capture the original config in SetupSuite (add
fields on DestinationMappingTestSuite like origSourceNamespaces and
origConfigMapData), read and save the existing SourceNamespaces/ConfigMap values
there, and then in TearDownSuite restore those originals (write back the saved
SourceNamespaces or remove the key if it was absent) using the same ConfigMap
update functions; ensure error handling around read/write operations in
SetupSuite and TearDownSuite so tests don’t leak state.
🧹 Nitpick comments (1)
principal/redisproxy/redisproxy.go (1)

400-405: Avoid re-deriving agentName inside handleAgentGet.
The caller already resolved it; re-parsing can diverge or do extra work.

Proposed diff
-	agentName, err := rp.extractAgentNameFromRedisCommandKey(key, logCtx)
-	if err != nil {
-		logCtx.WithError(err).Error("unable to get agent name: " + key)
-		return err
-	}
-
 	logCtx.Trace("sending get command to agent")

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
principal/callbacks.go (1)

100-147: ⚠️ Potential issue | 🟠 Major

Guard against empty destination.name in update flow.
When destination-based mapping is enabled and destination.name becomes empty, agentName is empty but the update path still creates a queue and dispatches events to the empty name. This can misroute updates and create a phantom queue.

Consider returning early after handling agent change if agentName is empty.

🛠️ Suggested fix
 if s.destinationBasedMapping {
 	s.handleAppAgentChange(ctx, old, new, logCtx)
 }
+if agentName == "" {
+	logCtx.Warn("Destination name is empty; skipping update dispatch")
+	return
+}
🤖 Fix all issues with AI agents
In `@agent/inbound.go`:
- Around line 525-534: When destination-based mapping is enabled
(a.destinationBasedMapping) enforce the namespace allowlist by calling
a.isNamespaceAllowed(targetNamespace) regardless of a.createNamespace; only gate
actual namespace creation on a.createNamespace by calling
a.ensureNamespaceExists(targetNamespace) when createNamespace is true. Update
the conditional in the inbound handling code (the block using a.createNamespace,
a.destinationBasedMapping, a.isNamespaceAllowed and a.ensureNamespaceExists) so
that isNamespaceAllowed runs whenever destination-based mapping is true and
ensureNamespaceExists runs only if createNamespace is true; apply the same
change to the other similar block that uses these same symbols.

In `@docs/concepts/agent-mapping.md`:
- Around line 103-109: Wrap the indented code block inside the "Allowed
Namespaces" admonition with markdownlint disable/enable comments for MD046 so
the linter ignores the indented-block rule only for that section; specifically,
insert the markdownlint HTML comment directive to disable MD046 immediately
before the indented code block and re-enable it immediately after, targeting the
admonition titled "Allowed Namespaces" in docs/concepts/agent-mapping.md.

In `@principal/callbacks.go`:
- Around line 817-826: The GetAgentForApp implementation contradicts its
comment: when s.destinationBasedMapping is true but s.appToAgent is nil it
currently returns the namespace, which silently falls back to namespace-based
routing; update GetAgentForApp so that when s.destinationBasedMapping (or
destination-based mapping is enabled) and s.appToAgent == nil it returns an
empty string instead of namespace, otherwise keep building qualifiedName :=
fmt.Sprintf("%s/%s", namespace, name) and returning
s.appToAgent.Get(qualifiedName).

In `@test/e2e/dest_mapping_test.go`:
- Around line 484-490: The call to fixture.EnsureUpdate that appends a finalizer
to the Deployment is missing an error check so failures are ignored; update the
code around EnsureUpdate (the call that uses fixture.EnsureUpdate(suite.Ctx,
suite.ManagedAgentClient, depl, func(obj fixture.KubeObject) { ... })) to
capture its returned error into a variable and immediately assert/handle it
(e.g., require.NoError or t.Fatalf) so the test fails if the finalizer update
did not succeed, and also check the earlier err variable after modifying
depl.Finalizers to ensure both update attempts are validated.
- Around line 269-292: The deferred cleanup unconditionally deletes the test
namespace(s); change the logic to track whether each namespace was actually
created and only call fixture.EnsureDeletion when the test created it.
Concretely, add a bool flag (e.g., principalNsCreated) set true when
suite.PrincipalClient.Create(ns) succeeds (and false if errors.IsAlreadyExists),
use that flag in the first defer to skip deletion when false; do the same for
the agent-side namespace (agentNs and a separate agentNsCreated) in the second
defer that currently calls fixture.EnsureDeletion on agentNs, ensuring you check
the corresponding creation result before attempting deletion.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
agent/inbound.go (1)

622-653: ⚠️ Potential issue | 🟠 Major

Use the target namespace when deleting applications.

deleteApplication sets the target namespace on the app, but a.appManager.Delete still uses a.namespace. In destination-based mapping this can delete the wrong namespace or fail to delete at all. Use the app’s resolved namespace instead.

🔧 Proposed fix
-	err = a.appManager.Delete(a.context, a.namespace, app, &deletionPropagation)
+	err = a.appManager.Delete(a.context, app.Namespace, app, &deletionPropagation)
principal/callbacks.go (1)

100-147: ⚠️ Potential issue | 🟠 Major

Guard against empty agentName before queue operations.
If spec.destination.name is empty, this can create/lookup a queue with an empty key and misroute updates. Add the same early return used in create/delete.

🔧 Proposed fix
 	agentName := s.getAgentNameForApp(new)
+	if agentName == "" {
+		logCtx.Error("Failed to get agent name for application")
+		return
+	}

 	logCtx := log().WithFields(logrus.Fields{
 		"component":        "EventCallback",
 		"queue":            agentName,
🤖 Fix all issues with AI agents
In `@principal/callbacks.go`:
- Around line 782-865: The handler handleAppAgentChange should early-return for
autonomous resources to avoid routing/deleting transient autonomous updates; add
a guard at the top of handleAppAgentChange that checks
isResourceFromAutonomousAgent(old) || isResourceFromAutonomousAgent(new) and
returns immediately if true. This will prevent calls to untrackAppToAgent,
trackAppToAgent, resources.Remove/resources.Add and enqueueing delete events on
queues for autonomous apps.

In `@test/e2e/dest_mapping_test.go`:
- Around line 146-148: In DestinationMappingTestSuite.TearDownSuite() remove the
incorrect call to suite.BaseSuite.TearDownTest() (per-test teardown) — delete
that line and keep only suite-level cleanup code (e.g., retains suite.Require()
usage or add any suite-level teardown logic here); do not attempt to call a
non-existent BaseSuite.TearDownSuite().
🧹 Nitpick comments (1)
test/e2e/dest_mapping_test.go (1)

484-489: Drop the redundant pre-append of the finalizer.
The direct depl.Finalizers append is overwritten by EnsureUpdate’s internal GET, so it doesn’t affect the update.

♻️ Simplify finalizer update
-    depl.Finalizers = append(depl.Finalizers, "test-e2e/my-test-finalizer")
-    err = fixture.EnsureUpdate(suite.Ctx, suite.ManagedAgentClient, depl, func(obj fixture.KubeObject) {
+    err = fixture.EnsureUpdate(suite.Ctx, suite.ManagedAgentClient, depl, func(obj fixture.KubeObject) {
         depl := obj.(*appsv1.Deployment)
         depl.Finalizers = append(depl.Finalizers, "test-e2e/my-test-finalizer")
     })

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@principal/callbacks.go`:
- Around line 100-111: The update callback is missing a check that agentName
(from s.getAgentNameForApp(new)) is non-empty before using it; add the same
validation pattern used in newAppCallback and deleteAppCallback: after computing
agentName and before using it or calling s.handleAppAgentChange(ctx, old, new,
logCtx), verify agentName != "" and if empty log a warning/error on logCtx and
return early so destinationBasedMapping paths won't operate on an invalid queue
key (references: getAgentNameForApp, agentName, destinationBasedMapping,
handleAppAgentChange, newAppCallback, deleteAppCallback).
🧹 Nitpick comments (3)
principal/callbacks_test.go (3)

1284-1404: Clarify empty destination behavior in the test comment.

Line 1394 says “fall back to namespace,” but the test asserts no processing. Align the comment with the expected behavior to avoid confusion.

💡 Suggested fix
-					Name: "", // Empty - should fall back to namespace
+					Name: "", // Empty - should skip processing when destination-based mapping is enabled

1724-1898: Assert queue items directly to avoid ambiguity.

Line 1760 uses s.queues.Len(), which may reflect queue count rather than queued items. Consider asserting the send queue length so the test explicitly verifies the create event was queued.

🔧 Suggested tweak
 		s.newAppCallback(oldApp)
-		assert.Equal(t, 1, s.queues.Len())
-		drainQueue(t, s.queues.SendQ("target-cluster"))
+		preQ := s.queues.SendQ("target-cluster")
+		require.NotNil(t, preQ)
+		assert.Equal(t, 1, preQ.Len())
+		drainQueue(t, preQ)

1901-2159: Pre-populate old resources to verify removal logic.

Right now, the delete-side resource assertions can pass even if removal is broken because the old resources are never added. Seeding the old agent’s resources strengthens the test’s guarantee.

🧪 Suggested enhancement
 			// Create queues for agents involved
 			if tt.expectedDeleteAgent != "" {
 				s.queues.Create(tt.expectedDeleteAgent)
 			}
 			if tt.expectedAddAgent != "" {
 				s.queues.Create(tt.expectedAddAgent)
 			}
+			if tt.expectedDeleteAgent != "" {
+				s.resources.Add(tt.expectedDeleteAgent, resources.NewResourceKeyFromApp(tt.oldApp))
+			}
 
 			logCtx := logrus.WithField("test", "handleAppAgentChange")

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@principal/callbacks.go`:
- Around line 106-117: Compute agentName via s.getAgentNameForApp(new) as before
but do not early-return when agentName == ""; instead, still call
s.handleAppAgentChange(ctx, old, new, logCtx) when s.destinationBasedMapping is
true so cleanup runs even if destination.name became empty; keep the
logCtx.Error("Failed to get agent name for application") for visibility, and
only add logCtx = logCtx.WithField("queue", agentName) when agentName is
non-empty to avoid adding an empty field.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@principal/callbacks_test.go`:
- Around line 2224-2227: The assertion message is misleading: update the
NotContainsf message used with resources.NewResourceKeyFromApp(tt.oldApp) and
s.resources.Get(tt.expectedDeleteAgent).GetAll() (inside the
tt.expectedDeleteAgent != "" branch) to reflect that the resource should be
removed from the old agent (e.g. "Resource should be removed from old agent" or
similar) instead of "Resource should be added to new agent".

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@principal/callbacks_test.go`:
- Around line 1977-2235: The test fails to seed state for the old agent so the
removal assertion is vacuous; before calling s.handleAppAgentChange add the app
resource to s.resources for tt.expectedDeleteAgent (use
resources.NewResourceKeyFromApp(tt.oldApp) and add that key to
s.resources.Get(tt.expectedDeleteAgent)) and set the app->agent mapping in
s.appToAgent to the old agent (use tt.oldApp.QualifiedName()) so the handler
actually removes something to validate removal behavior.

Copy link
Contributor

@drewbailey drewbailey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two small things that are probably worth calling out,

  • The agent-clusterrole needs permissions to manage argoproj.io resources similar to the agent-role thats included in the install dir.
  • Agent allowed namespaces is not currently wired up in the agent-deplyoment / cm

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
install/kubernetes/agent/agent-clusterrole.yaml (1)

10-19: ⚠️ Potential issue | 🟠 Major

Gate cluster-wide namespace creation behind an optional binding.

Granting namespaces/create at cluster scope is a high-privilege expansion. The agent uses this to auto-create namespaces (when ARGOCD_AGENT_CREATE_NAMESPACE is enabled), but this should not be part of the default install. Split this into a dedicated ClusterRole and bind it only when the feature flag is enabled.

💡 Suggested split to keep default RBAC minimal
   resources:
   - namespaces
   verbs:
-  - create
   - get
   - list
   - watch
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  name: argocd-agent-agent-namespace-create
+rules:
+- apiGroups:
+  - ""
+  resources:
+  - namespaces
+  verbs:
+  - create
🤖 Fix all issues with AI agents
In `@install/kubernetes/agent/agent-clusterrole.yaml`:
- Around line 19-30: The ClusterRole granting verbs
(create,get,list,watch,update,delete,patch) on the argoproj.io resource
"applications" is currently cluster-wide; restrict it by converting this broad
ClusterRole binding into namespace-scoped RBAC (create a Role with the same
resources/verbs and bind it via RoleBinding only in each allowed namespace) or
keep the ClusterRole but remove/warn against cluster-scoped binding and instead
create RoleBindings conditionally per allowed namespace (or add a managed-mode
installation path that only creates RoleBindings for allowed-namespaces). Update
the manifest references to the "applications" resource and any
ClusterRoleBinding usage so permissions are applied per-namespace (or behind a
managed-mode/allowed-namespaces conditional) rather than cluster-wide.
🧹 Nitpick comments (1)
install/helm-repo/argocd-agent-agent/templates/agent-clusterrole.yaml (1)

15-30: Consider least‑privilege scoping for Application CRUD.

This ClusterRole grants cluster‑wide create/update/delete/patch on Applications (and namespace create/get). If allowed‑namespaces or destination‑based mapping is intended to restrict scope, consider making this rule conditional or offering Role/RoleBinding alternatives per namespace to reduce blast radius.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@docs/concepts/agent-mapping.md`:
- Around line 100-101: Change the phrasing "in the same namespace as the
principal" by replacing "on the same namespace" with "in the same namespace" in
the sentence that begins "With destination-based mapping" (also update the
follow-up sentence that mentions the `--create-namespace` flag if it uses "on"
there) so both occurrences use the standard "in the same namespace" wording.
- Line 64: Replace the namespace placeholder `{agentName}/` with `{agentName}`
in the "Destination-based mapping" sentence so it no longer implies a filesystem
path; update the text that currently reads "autonomous apps are always stored in
the `{agentName}/` namespace" to use `{agentName}` (remove the trailing slash)
to clarify it's a namespace name rather than a path.

Signed-off-by: Chetan Banavikalmutt <[email protected]>
Signed-off-by: Chetan Banavikalmutt <[email protected]>
Signed-off-by: Chetan Banavikalmutt <[email protected]>
Signed-off-by: Chetan Banavikalmutt <[email protected]>
Signed-off-by: Chetan Banavikalmutt <[email protected]>
Signed-off-by: Chetan Banavikalmutt <[email protected]>
Signed-off-by: Chetan Banavikalmutt <[email protected]>
Signed-off-by: Chetan Banavikalmutt <[email protected]>
Signed-off-by: Chetan Banavikalmutt <[email protected]>
Signed-off-by: Chetan Banavikalmutt <[email protected]>
Signed-off-by: Chetan Banavikalmutt <[email protected]>
Signed-off-by: Chetan Banavikalmutt <[email protected]>
Signed-off-by: Chetan Banavikalmutt <[email protected]>
Signed-off-by: Chetan Banavikalmutt <[email protected]>
Signed-off-by: Chetan Banavikalmutt <[email protected]>
Signed-off-by: Chetan Banavikalmutt <[email protected]>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
principal/redisproxy/redisproxy.go (1)

400-404: ⚠️ Potential issue | 🟡 Minor

Avoid re-resolving agent name inside handleAgentGet.
A second lookup can return empty and still proceed to send to an invalid agent. Prefer the already-resolved parameter or guard against empty.

🛠️ Proposed fix
-	agentName, err := rp.extractAgentNameFromRedisCommandKey(key, logCtx)
-	if err != nil {
-		logCtx.WithError(err).Error("unable to get agent name: " + key)
-		return err
-	}
+	if agentName == "" {
+		logCtx.Error("empty agent name for key: " + key)
+		return fmt.Errorf("empty agent name for key: %s", key)
+	}
🤖 Fix all issues with AI agents
In `@docs/concepts/agent-mapping.md`:
- Line 191: Replace the inconsistent use of "ArgoCD" with the official two-word
form "Argo CD" in this document by locating and updating the string "ArgoCD"
(specifically the occurrences matching the phrase in the diff and at the
instances referenced around lines where "Apps-in-Any-Namespace" and other
mentions appear) so all mentions of the project use "Argo CD" consistently.
🧹 Nitpick comments (6)
install/helm-repo/argocd-agent-agent/templates/agent-clusterrole.yaml (1)

14-30: Consider gating the new permissions on their respective feature flags.

The create verb on namespaces is only needed when createNamespace is enabled, and the argoproj.io/applications rule is only needed for destination-based mapping. Since this is a Helm template, you could conditionally include these using {{- if .Values.createNamespace }} / {{- if .Values.destinationBasedMapping }} to follow least-privilege by default.

That said, I see from prior discussion that the broad ClusterRole is intentional and users are expected to customize RBAC per their deployment. If that's the chosen approach, this is fine as-is — just noting the option for tighter defaults. Based on learnings, the ClusterRole is intentionally broad because allowed namespaces vary by mapping mode, and documentation guides users to use namespace-scoped RoleBindings.

test/e2e/redis_proxy_test.go (1)

113-113: Nit: The resourceTreeURL does not URL-encode appNamespace.

If the namespace ever contains special characters (unlikely but possible with destination-based mapping enabling arbitrary namespaces), this could break the URL. Currently low risk given the test uses "agent-managed".

docs/concepts/agent-mapping.md (1)

195-207: AppProject example could be more specific.

The placeholder <namespaces> is vague. Consider showing a concrete example (e.g., "*" or specific namespace names) to make it immediately actionable, especially since this is a required configuration for the feature.

principal/callbacks_test.go (1)

1673-1673: Remove debug print from test output.
It adds noise to test logs without assertions.

🧹 Proposed cleanup
-		fmt.Println(app.Operation.String())
agent/agent.go (1)

566-574: Consider using an explicit empty slice instead of nil for clarity.

When destinationBasedMapping is true, namespaces remains nil. While this likely works (nil slice = no namespace restriction = all namespaces), it depends on the List implementation treating nil and []string{} equivalently. An explicit empty slice would make the "all namespaces" intent clearer.

💡 Suggested change
-	var namespaces []string
-	if !a.destinationBasedMapping {
-		namespaces = []string{a.namespace}
+	namespaces := []string{a.namespace}
+	if a.destinationBasedMapping {
+		// Watch all namespaces when destination-based mapping is enabled
+		namespaces = []string{}
 	}
agent/inbound.go (1)

150-152: Redundant namespace setting — getTargetNamespaceForApp is called both here and again inside createApplication/updateApplication/deleteApplication.

The namespace is set on incomingApp here, and then overwritten in each handler method. While the result is the same (idempotent), this double-assignment adds cognitive overhead. Consider setting it only in the handler methods (which need to be self-contained for when they're called from other paths), and removing it from processIncomingApplication.

@jannfis
Copy link
Collaborator

jannfis commented Feb 10, 2026

I'm justing testing this PR locally, and there's one thing I stumbled over:

When you create an application in the control plane's namespace (i.e. argocd in the dev-env), and set the destination to an agent, the Application is synced correctly to the agent and Argo CD sync works too. However, once you go to the resource tree of that app, something is not right as it doesn't render correctly.

18:39:45        principal | time="2026-02-10T18:39:45Z" level=error msg="unexpected lack of '_' namespace/name separate: 'app|managed-resources|guestbook|1.8.3'" connUUID=5313cbe3-033f-4b1d-97b3-35ee8bd47dc3 function=redisFxn module=redisProxy
18:39:45        principal | time="2026-02-10T18:39:45Z" level=error msg="exit due to unable to get agent name: app|managed-resources|guestbook|1.8.3" connUUID=5313cbe3-033f-4b1d-97b3-35ee8bd47dc3 error="unexpected lack of '_' namespace/name separate: 'app|managed-resources|guestbook|1.8.3'" function=redisFxn module=redisProxy
18:39:45        principal | time="2026-02-10T18:39:45Z" level=debug msg="handleConnectionMessageLoop has exited" connUUID=5313cbe3-033f-4b1d-97b3-35ee8bd47dc3 function=redisFxn module=redisProxy
18:39:45        principal | time="2026-02-10T18:39:45Z" level=debug msg="unable to read from Argo CD redis endpoint input, usually due to closed connection" connUUID=5313cbe3-033f-4b1d-97b3-35ee8bd47dc3 error="readArray ReadString error: read tcp 192.168.121.167:6379->10.1.13.203:39222: use of closed network connection" function=redisFxn module=redisProxy

@jannfis
Copy link
Collaborator

jannfis commented Feb 10, 2026

I think regarding my previous comment about an application in the control plane namespace, we may ignore the situation for the sake of getting this PR merged and fix the issue in a follow-up PR.

If we aren't able to come up with a good solution until the next release, we document it as known issue.

@chetan-rns
Copy link
Collaborator Author

unexpected lack of '_' namespace/name separate: 'app|managed-resources|guestbook|1.8.3

@jannfis The Redis keys for apps created in the argocd installation namespace will only have the app name. i.e. app|resources-tree|my-app|1.8.3.gz instead of app|resources-tree|<agent-name>_my-app|1.8.3.gz I've updated the redis proxy code to handle this case and I don't see this error anymore.

@jannfis
Copy link
Collaborator

jannfis commented Feb 12, 2026

Oops. I'm sorry, I didn't intend to merge main into this branch.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Consider alternative to apps-in-any-namespace for cluster mapping

5 participants