Skip to content

Conversation

@miltalex
Copy link
Member

@miltalex miltalex commented Dec 3, 2025

Fixes #13468

Motivation

Currently, argo-workflows only supports the '=' operator for the field selector metadata.namespace when listing workflows. This limitation restricts the flexibility of our queries, particularly when users wish to exclude specific namespaces from their results. The goal of this PR is to expand support to include the '==' and '!=' operators for metadata.namespace, aligning our capabilities with native Kubernetes functionality. This enhancement allows users to perform exclusion queries (e.g., "list all workflows except those in the 'test' namespace"), improving overall system usability.

Modifications

  • ListOptions Expansion:

    • Extended the ListOptions struct in server/utils to include a new NamespaceFilter field to store the operator type.
  • BuildListOptions Enhancement:

    • Modified the BuildListOptions function to parse metadata.namespace== and metadata.namespace!=.
    • Logic added to ensure != sets the correct filter type and == behaves consistent with =.
  • Database Selector Update:

    • Updated BuildArchivedWorkflowSelector and BuildWorkflowSelector in persist/sqldb to handle the NamespaceFilter.
    • Implemented SQL generation for namespace exclusion (namespace != ?) to support the NotEquals filter in the archive.
  • Workflow Server Authorization:

    • Updated ListWorkflows in server/workflow/workflow_server.go to handle authorization for namespace exclusion.
    • Implemented strict security logic: If metadata.namespace!= is used, the user must have cluster-wide list permissions. If they do not, the request is denied immediately. This mirrors kubectl behavior and prevents potential security issues where excluding a namespace might imply access to all others.

Verification

  • Unit Testing:

    • Added comprehensive unit tests in server/utils/list_options_test.go to cover parsing of the new operators and conflict detection.
    • Updated server/workflowarchive/archived_workflow_server_test.go to verify that the new options are correctly passed to the repository layer.
  • E2E Testing:

    • Added end-to-e2e tests in test/e2e/argo_server_test.go.
    • Validated that argo list --field-selector metadata.namespace!=<ns> returns the correct workflows for authorized users.
    • Validated that unauthorized users (lacking cluster-wide permissions) receive a 403 Forbidden error when attempting to use the exclusion operator.

Example audit log from local testing:

{
    "kind": "Event",
    "apiVersion": "audit.k8s.io/v1",
    "level": "Metadata",
    "auditID": "f8a139b4-9201-4dee-b57b-15e095887f79",
    "stage": "ResponseComplete",
    "requestURI": "/apis/argoproj.io/v1alpha1/workflows?fieldSelector=metadata.namespace%21%3Dargo-test-2\u0026labelSelector=%21workflows.argoproj.io%2Fcontroller-instanceid",
    "verb": "list",
    "user": {
        "username": "system:admin",
        "groups": [
            "system:masters",
            "system:authenticated"
        ]
    },
    "sourceIPs": [
        "192.168.107.3"
    ],
    "userAgent": "argo/v0.0.0 (darwin/amd64) kubernetes/$Format/argo-workflows/latest+6fb2b9f.dirty argo-api-client",
    "objectRef": {
        "resource": "workflows",
        "apiGroup": "argoproj.io",
        "apiVersion": "v1alpha1"
    },
    "responseStatus": {
        "metadata": {},
        "code": 200
    },
    "requestReceivedTimestamp": "2025-12-03T10:34:35.475997Z",
    "stageTimestamp": "2025-12-03T10:34:35.483591Z",
    "annotations": {
        "authorization.k8s.io/decision": "allow",
        "authorization.k8s.io/reason": ""
    }
}

Summary by CodeRabbit

  • New Features

    • Added namespace field selector operators: != for excluding namespaces and == for exact namespace matching. Users can now filter workflows using expressions like namespace!=kube-system or namespace==production.
  • Tests

    • Expanded test coverage for namespace field selector filtering and cluster-scoped RBAC authorization scenarios.
  • Documentation

    • Added documentation describing the new namespace field selector operators and usage examples.

✏️ Tip: You can customize this high-level summary in your review settings.

@miltalex miltalex force-pushed the fix/add-namespace-operators branch 3 times, most recently from 8874b3c to d79bf8d Compare December 3, 2025 11:03
@miltalex miltalex marked this pull request as draft December 3, 2025 11:18
@miltalex miltalex force-pushed the fix/add-namespace-operators branch from d79bf8d to 25bd9d2 Compare December 3, 2025 13:02
@miltalex miltalex force-pushed the fix/add-namespace-operators branch from 25bd9d2 to 03388db Compare December 3, 2025 13:03
@miltalex miltalex marked this pull request as ready for review December 3, 2025 13:26
@MasonM MasonM self-requested a review December 15, 2025 05:11
@Joibel
Copy link
Member

Joibel commented Dec 17, 2025

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Dec 17, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link

coderabbitai bot commented Dec 17, 2025

Walkthrough

Implements support for namespace field selector operators (!= and ==), extending filtering capabilities beyond simple equality. Updates parsing, SQL query building, authorization logic, and adds comprehensive test coverage including cluster-scoped RBAC scenarios.

Changes

Cohort / File(s) Summary
Documentation
.features/pending/namespace-field-selectors-operators.md
Introduces feature documentation describing new namespace field selector operators (!= and ==) with usage examples for workflow filtering by namespace exclusion or exact matching.
Field Selector Parsing
server/utils/list_options.go, server/utils/list_options_test.go
Adds NamespaceFilter field to ListOptions and extends parser to handle metadata.namespace!= (NotEquals) and metadata.namespace== (Equals) operators with conflict detection between query parameters and field selectors. Includes comprehensive test coverage for all operator combinations.
SQL Query Building
persist/sqldb/selector.go, persist/sqldb/workflow_archive.go
Implements conditional namespace filtering in query builders: applies namespace != when NamespaceFilter is "NotEquals", otherwise defaults to namespace =. Introduces namespaceNotEqual helper function in workflow archive queries.
Authorization Logic
server/workflow/workflow_server.go
Updates permission checks in ListWorkflows to override targetNamespace to empty string when NamespaceFilter equals "NotEquals", preventing namespace-scoped authorization from incorrectly restricting NotEquals queries.
Integration & E2E Tests
server/workflowarchive/archived_workflow_server_test.go, test/e2e/argo_server_test.go, test/e2e/testdata/argo-server-test-clusterrole.yaml
Adds test coverage for namespace field filtering with new and equals operators across archived and non-archived workflow listing; introduces cluster-scoped RBAC setup and corresponding test scenarios for cluster-wide authorization validation.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20–30 minutes

  • Areas requiring extra attention:
    • server/utils/list_options.go: Verify conflict detection logic between query parameter and field selector specifications for all operator combinations (!=, ==, =)
    • persist/sqldb/workflow_archive.go: Confirm namespaceNotEqual helper correctly generates SQL conditions and integrates seamlessly with existing filter chains
    • server/workflow/workflow_server.go: Ensure authorization override for NotEquals filters doesn't inadvertently bypass intended permission checks
    • test/e2e/argo_server_test.go: Validate cluster-scoped RBAC setup and all permission test scenarios (including 403 error case)

Poem

🐰 A rabbit hops through namespaces wide,
With != and == as trusty guides,
Filtering workflows left and right,
Excluding clutter, keeping sight,
Field selectors shine so bright!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: add !=, == support for namespace field selector' clearly summarizes the main change—extending namespace field selector operators—and follows conventional commit format.
Description check ✅ Passed The PR description is comprehensive, follows the template structure with clear Motivation, Modifications, and Verification sections including unit and E2E tests, and provides specific implementation details and usage examples.
Linked Issues check ✅ Passed The PR successfully implements all coding requirements from issue #13468: adds support for != and == operators for metadata.namespace field selectors, implements proper authorization checks for exclusion queries, and includes comprehensive unit and E2E test coverage.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing !=, == support for namespace field selectors as specified in #13468; no extraneous modifications were introduced outside the scope of this feature.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

Copy link

@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

🧹 Nitpick comments (3)
server/workflow/workflow_server.go (1)

191-201: The error message may be confusing for NotEquals namespace filter.

When NamespaceFilter == "NotEquals", the targetNamespace becomes an empty string. The error message at line 201 will then display:

Permission denied, you are not allowed to list workflows in namespace "". Maybe you want to specify a namespace with query parameter `.namespace=`?

This message is confusing because the user did specify a namespace (with !=), and they need cluster-wide permissions, not a specific namespace. Consider providing a more descriptive message for this case:

 if !allowed {
+    if options.NamespaceFilter == "NotEquals" {
+        return nil, status.Error(codes.PermissionDenied, "Permission denied, listing workflows with namespace exclusion (!=) requires cluster-wide list permissions")
+    }
     return nil, status.Error(codes.PermissionDenied, fmt.Sprintf("Permission denied, you are not allowed to list workflows in namespace \"%s\". Maybe you want to specify a namespace with query parameter `.namespace=%s`?", targetNamespace, targetNamespace))
 }
persist/sqldb/selector.go (1)

13-17: LGTM on the conditional namespace filtering logic.

The implementation correctly branches on NamespaceFilter == "NotEquals" to generate either namespace != ? or namespace = ? SQL conditions. The logic is consistent between BuildArchivedWorkflowSelector (lines 13-17) and BuildWorkflowSelector (lines 67-71), and both helper functions (namespaceNotEqual and namespaceEqual) are properly implemented in workflow_archive.go.

Consider defining a constant for "NotEquals" to avoid the magic string that appears throughout the codebase:

const NamespaceFilterNotEquals = "NotEquals"
persist/sqldb/workflow_archive.go (1)

291-297: Suggest using a constant for the "NotEquals" filter value.

The conditional logic correctly implements namespace filtering based on NamespaceFilter. However, the string literal "NotEquals" appears multiple times across this file (lines 293, 320, 355, 382, 419, 446) and likely in other files as well.

Consider defining a constant to improve maintainability and prevent typos:

const (
    FilterNotEquals = "NotEquals"
    FilterExact     = "Exact"
    FilterContains  = "Contains"
    FilterPrefix    = "Prefix"
)

Then use it consistently:

-if options.NamespaceFilter == "NotEquals" {
+if options.NamespaceFilter == FilterNotEquals {

This pattern should be applied across all filter comparisons in the codebase for consistency.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6a7caf0 and 03388db.

📒 Files selected for processing (9)
  • .features/pending/namespace-field-selectors-operators.md (1 hunks)
  • persist/sqldb/selector.go (2 hunks)
  • persist/sqldb/workflow_archive.go (4 hunks)
  • server/utils/list_options.go (4 hunks)
  • server/utils/list_options_test.go (1 hunks)
  • server/workflow/workflow_server.go (1 hunks)
  • server/workflowarchive/archived_workflow_server_test.go (2 hunks)
  • test/e2e/argo_server_test.go (3 hunks)
  • test/e2e/testdata/argo-server-test-clusterrole.yaml (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
persist/sqldb/workflow_archive.go (1)
test/e2e/fixtures/e2e_suite.go (1)
  • Namespace (42-42)
persist/sqldb/selector.go (1)
test/e2e/fixtures/e2e_suite.go (1)
  • Namespace (42-42)
test/e2e/argo_server_test.go (1)
test/e2e/fixtures/util.go (1)
  • LoadObject (65-84)
server/utils/list_options_test.go (1)
server/utils/list_options.go (1)
  • ListOptions (15-25)
server/workflowarchive/archived_workflow_server_test.go (2)
server/utils/list_options.go (1)
  • ListOptions (15-25)
pkg/apiclient/workflowarchive/workflow-archive.pb.go (3)
  • ListArchivedWorkflowsRequest (32-41)
  • ListArchivedWorkflowsRequest (45-45)
  • ListArchivedWorkflowsRequest (46-48)
🪛 LanguageTool
.features/pending/namespace-field-selectors-operators.md

[style] ~8-~8: Using many exclamation marks might seem excessive (in this case: 3 exclamation marks for a text that’s 561 characters long)
Context: ...eries. For example, you can filter with namespace!=kube-system to exclude system namespac...

(EN_EXCESSIVE_EXCLAMATION)

🔇 Additional comments (12)
server/utils/list_options.go (2)

101-114: The != operator unconditionally overwrites namespace without conflict detection.

When metadata.namespace!= is parsed, it sets namespace and namespaceFilter without checking for conflicts with an existing ns query parameter. This is asymmetric with the == and = handlers which perform conflict checks.

For example, if a user specifies ?namespace=foo&listOptions.fieldSelector=metadata.namespace!=bar, the namespace will be silently overwritten to bar with NotEquals filter, which may not be the intended behavior.

Consider whether conflict detection should also apply here, or if this behavior is intentional (e.g., != always takes precedence).


18-18: LGTM on the NamespaceFilter field addition and propagation.

The new NamespaceFilter field is properly declared in the struct, initialized in the function, and correctly propagated to the returned ListOptions.

Also applies to: 77-77, 166-166

test/e2e/testdata/argo-server-test-clusterrole.yaml (1)

1-16: LGTM on the ClusterRole definition.

The ClusterRole correctly defines cluster-scoped permissions for Argo workflows, enabling the e2e tests to validate that users with cluster-wide list permissions can use the metadata.namespace!= filter.

server/utils/list_options_test.go (1)

133-193: LGTM on the new namespace field selector test cases.

The test cases comprehensively cover:

  • metadata.namespace!= (NotEquals) handling
  • metadata.namespace== (Equals) handling
  • Conflict detection between != and == in different orders
  • Conflict detection between field selectors and ns query parameter
  • Valid matching scenarios

The inline comments clearly document the expected behavior for each case.

test/e2e/argo_server_test.go (3)

511-560: LGTM on the cluster-scoped RBAC setup for e2e tests.

The test setup correctly:

  • Creates a cluster-scoped ServiceAccount
  • Loads and creates the ClusterRole from testdata
  • Creates a ClusterRoleBinding linking the SA to the ClusterRole
  • Retrieves the SA token for use in subsequent tests
  • Properly cleans up resources with deferred deletions

608-652: Well-structured test cases for namespace field selector operators.

The tests effectively validate:

  1. metadata.namespace!=argo returns empty results when excluding the only namespace with workflows
  2. metadata.namespace!=argo-excluded returns workflows from argo namespace (exclusion doesn't match)
  3. metadata.namespace==argo correctly matches and returns workflows

The token swapping pattern with deferred restoration is consistent with the existing test patterns.


687-692: LGTM on the authorization test for NotEquals with insufficient permissions.

This test correctly validates that using metadata.namespace!= without cluster-wide permissions results in a 403 Forbidden response, enforcing the security requirement that namespace exclusion queries require elevated permissions.

server/workflowarchive/archived_workflow_server_test.go (2)

71-72: LGTM! Mock expectations align with the new namespace filtering feature.

The mock expectations correctly set up test data for the namespace NotEquals filter (line 71) and regular namespace filtering (line 72). These expectations match the test cases added later in the file.


203-213: LGTM! Test coverage validates namespace operator parsing and propagation.

The test cases verify that both != and == operators are correctly parsed from the field selector and propagate through to the repository layer. The assertions confirm expected behavior for item counts and pagination.

persist/sqldb/workflow_archive.go (3)

513-518: LGTM! Helper function follows established patterns.

The namespaceNotEqual function correctly mirrors the existing nameNotEqual helper (lines 527-532), maintaining consistency in the codebase. The conditional logic ensures that an empty namespace doesn't add a filter clause.


353-359: LGTM! Consistent implementation across counting methods.

The namespace filtering logic matches the pattern used in CountWorkflows (lines 291-297), ensuring consistent behavior. The conditional correctly applies namespaceNotEqual when the filter is set to "NotEquals".


417-423: LGTM! Consistent namespace filtering across all query methods.

The namespace filtering logic is correctly applied in HasMoreWorkflows, maintaining consistency with CountWorkflows (lines 291-297) and countWorkflowsOptimized (lines 353-359). This ensures uniform behavior across all workflow listing and counting operations.

Comment on lines +6 to +8
You can now use the `!=` and `==` operators when filtering workflows by namespace field.
This provides more flexible query capabilities, allowing you to easily exclude specific namespaces or match exact namespace values in your workflow queries.
For example, you can filter with `namespace!=kube-system` to exclude system namespaces or `namespace==production` to target only production environments.
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Field selector syntax in examples is inconsistent with the implementation.

The examples use namespace!=kube-system and namespace==production, but the actual field selector syntax requires the full path: metadata.namespace!=kube-system and metadata.namespace==production.

-For example, you can filter with `namespace!=kube-system` to exclude system namespaces or `namespace==production` to target only production environments.
+For example, you can filter with `metadata.namespace!=kube-system` to exclude system namespaces or `metadata.namespace==production` to target only production environments.
🧰 Tools
🪛 LanguageTool

[style] ~8-~8: Using many exclamation marks might seem excessive (in this case: 3 exclamation marks for a text that’s 561 characters long)
Context: ...eries. For example, you can filter with namespace!=kube-system to exclude system namespac...

(EN_EXCESSIVE_EXCLAMATION)

🤖 Prompt for AI Agents
In .features/pending/namespace-field-selectors-operators.md around lines 6 to 8,
the examples use shorthand field selectors like `namespace!=kube-system` and
`namespace==production` which are inconsistent with the implementation that
expects the full field path; update the examples to use the full selector path
`metadata.namespace!=kube-system` and `metadata.namespace==production` (and any
other occurrences in this file) so they match the actual field selector syntax
used by the code.

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.

Field selectors should support all native k8s operators

2 participants