Skip to content

[Integration][ArgoCD] Remove cluster-based filtering from get_resources to fix selector compatibility#3027

Merged
mk-armah merged 16 commits intomainfrom
PORT-17567
Apr 2, 2026
Merged

[Integration][ArgoCD] Remove cluster-based filtering from get_resources to fix selector compatibility#3027
mk-armah merged 16 commits intomainfrom
PORT-17567

Conversation

@dennis-quabynah-bilson
Copy link
Copy Markdown
Member

@dennis-quabynah-bilson dennis-quabynah-bilson commented Mar 29, 2026

Description

The default behavior of pulling all applications fails when the selector=cluster=<cluster-name> parameter is added to API requests. Not all ArgoCD instances have this filtering capability, causing requests to fail or return incomplete data. This reverts to the simpler behavior of fetching all resources directly without per-cluster filtering.

Summary

  • Reverted get_resources to fetch all applications without cluster-based selector filtering, as not all ArgoCD instances support the cluster= query parameter
  • Removed skip_unavailable_clusters parameter from get_clusters and the now-unused get_available_clusters method
  • Added early return in get_paginated_resources when API response contains no items
  • Updated tests to align with the simplified client API

Type of change

Please leave one option from the following and delete the rest:

  • Bug fix (non-breaking change which fixes an issue)
  • Non-breaking change (fix of existing functionality that will not change current behavior)

All tests should be run against the port production environment(using a testing org).

Integration testing checklist

  • Integration able to create all default resources from scratch
  • Completed a full resync from a freshly installed integration and it completed successfully
  • Resync able to create entities
  • Resync able to update entities
  • Resync able to detect and delete entities
  • Resync finishes successfully
  • Docs PR link here

Preflight checklist

  • Handled rate limiting
  • Handled pagination
  • Implemented the code in async
  • Support Multi account

Screenshots

image image image

API Documentation

Provide links to the API documentation used for this integration.

…ontains no items, preventing unnecessary processing of empty data sets.
…l. Removed the `skip_unavailable_clusters` parameter and consolidated resource fetching logic, enhancing code clarity and maintainability.
@dennis-quabynah-bilson dennis-quabynah-bilson marked this pull request as ready for review March 29, 2026 11:50
@dennis-quabynah-bilson dennis-quabynah-bilson requested a review from a team as a code owner March 29, 2026 11:50
Copilot AI review requested due to automatic review settings March 29, 2026 11:50
@qodo-code-review
Copy link
Copy Markdown
Contributor

Review Summary by Qodo

Remove cluster-based filtering from ArgoCD resource retrieval

🐞 Bug fix

Grey Divider

Walkthroughs

Description
• Removed cluster-based selector filtering from get_resources method
• Simplified resource retrieval to fetch all resources without per-cluster filtering
• Eliminated skip_unavailable_clusters parameter and get_available_clusters method
• Added early return in get_paginated_resources for empty API responses
• Updated all tests to reflect simplified client API
Diagram
flowchart LR
  A["get_resources_for_available_clusters<br/>with cluster selector"] -->|Refactored| B["get_resources<br/>without selector"]
  C["get_available_clusters<br/>filter logic"] -->|Removed| D["Simplified API calls"]
  E["Empty response handling"] -->|Added| F["Early return optimization"]
  B --> G["All resources fetched<br/>directly from API"]
Loading

Grey Divider

File Changes

1. integrations/argocd/client.py 🐞 Bug fix +8/-43

Simplify resource retrieval by removing cluster filtering

• Removed unused imports (asyncio, functools, async iterator utilities)
• Added early return in get_paginated_resources when API response contains no items
• Removed skip_unavailable_clusters parameter from get_clusters method
• Deleted get_available_clusters method entirely
• Renamed get_resources_for_available_clusters to get_resources with simplified logic
• Removed cluster-based selector filtering and concurrent request management

integrations/argocd/client.py


2. integrations/argocd/main.py Refactoring +2/-2

Update method calls to use simplified get_resources

• Updated on_resources_resync to call get_resources instead of
 get_resources_for_available_clusters
• Updated on_managed_resources_resync to call get_resources instead of
 get_resources_for_available_clusters

integrations/argocd/main.py


3. integrations/argocd/tests/test_client.py 🧪 Tests +102/-221

Update tests for simplified resource retrieval API

• Removed unused imports (asyncio, MAXIMUM_CONCURRENT_CLUSTER_REQUESTS)
• Renamed test test_get_resources_for_available_clusters to test_get_resources
• Simplified test to remove cluster mocking and selector parameter validation
• Updated test_get_clusters_filters_unavailable_clusters to
 test_get_clusters_returns_all_clusters
• Changed test expectations to verify all clusters are returned regardless of status
• Removed concurrency and multiple cluster batch tests
• Updated all method call patches from get_resources_for_available_clusters to get_resources
• Simplified streaming and non-streaming test cases

integrations/argocd/tests/test_client.py


View more (2)
4. integrations/argocd/CHANGELOG.md 📝 Documentation +8/-0

Add changelog entry for version 0.2.31

• Added version 0.2.31 release notes
• Documented bug fix for cluster-based selector filtering removal
• Noted compatibility improvement with ArgoCD instances lacking cluster selector support

integrations/argocd/CHANGELOG.md


5. integrations/argocd/pyproject.toml ⚙️ Configuration changes +1/-1

Bump version to 0.2.31

• Incremented version from 0.2.30 to 0.2.31

integrations/argocd/pyproject.toml


Grey Divider

Qodo Logo

@qodo-code-review
Copy link
Copy Markdown
Contributor

qodo-code-review bot commented Mar 29, 2026

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (0) 📎 Requirement gaps (0)

Grey Divider


Remediation recommended

1. Unbounded non-streaming fetch 🐞 Bug ☼ Reliability
Description
In non-streaming mode, get_paginated_resources performs exactly one API call and then batches the
fully-materialized "items" list locally; after removing cluster selector filtering, get_resources
may now fetch a substantially larger dataset in a single response. This increases the risk of
timeouts and high memory usage during resync when streaming is disabled.
Code

integrations/argocd/client.py[R99-105]

                response_data = await self._send_api_request(
                    url=url, query_params=params
                )
+                if not response_data.get("items"):
+                    return
                for batch in batched(response_data.get("items", []), PAGE_SIZE):
                    yield list(batch)
Evidence
get_paginated_resources (non-streaming) fetches the full JSON response once and only then splits
"items" into PAGE_SIZE batches, meaning payload size directly affects memory/time. get_resources now
calls get_paginated_resources with no selector params, so the single response can contain all
resources across clusters, and main resync paths call get_resources for ingestion.

integrations/argocd/client.py[94-114]
integrations/argocd/client.py[151-157]
integrations/argocd/main.py[20-31]
integrations/argocd/main.py[54-66]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`get_paginated_resources` loads the full non-streaming response into memory, then batches locally. After removing cluster selector filtering, this single response can be much larger, increasing timeout/OOM risk when `use_streaming=False`.

## Issue Context
The integration already has a streaming implementation (`StreamingClientWrapper.stream_json`). The risky path is specifically the `not self.use_streaming` branch used by `get_resources` and `get_clusters` when streaming is disabled.

## Fix Focus Areas
- integrations/argocd/client.py[94-114]
- integrations/argocd/client.py[151-157]

## Implementation direction
- Make resource listing endpoints (`get_resources`, optionally `get_clusters`) always use `streaming_client.stream_json` regardless of the global `use_streaming` flag (or add a dedicated flag for these high-cardinality endpoints).
- If keeping the non-streaming path, implement true request pagination (multiple HTTP requests) rather than client-side batching of a single response.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Advisory comments

2. Dead concurrency constant 🐞 Bug ⚙ Maintainability
Description
MAXIMUM_CONCURRENT_CLUSTER_REQUESTS is now unused after removing the per-cluster concurrent request
logic, leaving misleading dead code in the client module. This can confuse future maintenance and
suggests a concurrency behavior that no longer exists.
Code

integrations/argocd/client.py[L178-189]

-        semaphore = asyncio.Semaphore(MAXIMUM_CONCURRENT_CLUSTER_REQUESTS)
-        tasks = [
-            semaphore_async_iterator(
-                semaphore,
-                functools.partial(
-                    self.get_paginated_resources,
-                    url,
-                    params={"selector": f"cluster={name}"},
-                ),
-            )
-            for name in cluster_names
-        ]
Evidence
The module still defines MAXIMUM_CONCURRENT_CLUSTER_REQUESTS, but get_resources no longer creates
semaphores/tasks and instead delegates directly to get_paginated_resources, so the constant no
longer affects runtime behavior.

integrations/argocd/client.py[25-33]
integrations/argocd/client.py[151-157]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`MAXIMUM_CONCURRENT_CLUSTER_REQUESTS` is dead code after removing the per-cluster concurrency implementation.

## Issue Context
The per-cluster task fan-out and semaphore usage were removed, so this constant is no longer referenced anywhere.

## Fix Focus Areas
- integrations/argocd/client.py[25-33]

## Implementation direction
- Delete `MAXIMUM_CONCURRENT_CLUSTER_REQUESTS` (and any related unused imports/symbols if still present).
- Ensure no tests or external imports rely on this constant; if they do, remove/replace those references accordingly.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR updates the ArgoCD integration client to avoid using cluster-based selector=cluster=<name> filtering when listing resources, improving compatibility with ArgoCD instances that don’t support that selector.

Changes:

  • Simplified resource fetching to retrieve all resources without per-cluster selector filtering.
  • Removed cluster availability filtering API surface (skip_unavailable_clusters, get_available_clusters) and updated call sites/tests accordingly.
  • Added an early exit in non-streaming pagination when the API returns no items.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
integrations/argocd/client.py Removes cluster-based resource fetching and simplifies get_clusters; adds early return when items is empty.
integrations/argocd/main.py Switches resync handlers to call get_resources instead of cluster-filtered resource fetching.
integrations/argocd/tests/test_client.py Updates tests to reflect simplified APIs and removal of cluster selector logic.
integrations/argocd/pyproject.toml Bumps integration version to 0.2.31.
integrations/argocd/CHANGELOG.md Adds release note for selector compatibility fix.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +27 to 30
async for cluster in argocd_client.get_resources(
resource_kind=ObjectKind(kind)
):
yield cluster
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

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

The loop variable name cluster is now misleading: get_resources no longer yields per-cluster batches, it yields generic resource batches. Rename the loop variable (e.g., resource_batch) to avoid confusion and reduce the chance of future logic bugs.

Suggested change
async for cluster in argocd_client.get_resources(
resource_kind=ObjectKind(kind)
):
yield cluster
async for resource_batch in argocd_client.get_resources(
resource_kind=ObjectKind(kind)
):
yield resource_batch

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is valid

[tool.poetry]
name = "argocd"
version = "0.2.30"
version = "0.2.31"
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

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

Version was bumped as a patch release, but this PR removes/renames client methods/parameters (breaking API changes). To stay consistent with SemVer, either restore backward compatibility (deprecated aliases) or bump the minor version and note breaking changes in the changelog/towncrier output.

Suggested change
version = "0.2.31"
version = "0.3.0"

Copilot uses AI. Check for mistakes.

- Removed cluster-based `selector` filtering from `get_resources` to fix compatibility with ArgoCD instances that do not support cluster selector queries


Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

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

The release note describes a bugfix, but the PR also removes/renames public client APIs (get_resources_for_available_clusters, skip_unavailable_clusters). If you keep these breaking changes, they should be called out explicitly (and ideally categorized under "Breaking Changes" per towncrier config), or otherwise add deprecated compatibility wrappers.

Suggested change
### Breaking Changes
- Removed/renamed public client APIs `get_resources_for_available_clusters` and `skip_unavailable_clusters`. Callers relying on these helpers must migrate to the new APIs or use `get_resources` directly.

Copilot uses AI. Check for mistakes.
async def get_clusters(
self, skip_unavailable_clusters: bool = False
) -> AsyncGenerator[list[dict[str, Any]], None]:
async def get_clusters(self) -> AsyncGenerator[list[dict[str, Any]], None]:
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

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

get_clusters removed the skip_unavailable_clusters parameter, which is a backward-incompatible API change. Since the project states it follows Semantic Versioning, either keep the parameter (deprecated/no-op) for compatibility or bump the minor version and document this as a breaking change.

Suggested change
async def get_clusters(self) -> AsyncGenerator[list[dict[str, Any]], None]:
async def get_clusters(
self, skip_unavailable_clusters: Optional[bool] = None
) -> AsyncGenerator[list[dict[str, Any]], None]:
if skip_unavailable_clusters is not None:
logger.warning(
"The 'skip_unavailable_clusters' parameter in get_clusters is deprecated "
"and has no effect. It will be removed in a future version."
)

Copilot uses AI. Check for mistakes.
async for resources in stream_async_iterators_tasks(*tasks):
async for resources in self.get_paginated_resources(url):
yield resources

Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

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

get_resources_for_available_clusters was removed/renamed to get_resources, which can break external consumers importing ArgocdClient from this package. Consider reintroducing get_resources_for_available_clusters as a deprecated wrapper around get_resources (or bump minor and mark as breaking in release notes).

Suggested change
async def get_resources_for_available_clusters(
self, resource_kind: ObjectKind
) -> AsyncGenerator[list[dict[str, Any]], None]:
"""
Deprecated wrapper for `get_resources`.
This method is kept for backwards compatibility with external consumers
that relied on `get_resources_for_available_clusters`. It will be removed
in a future release; prefer calling `get_resources` directly.
"""
logger.warning(
f"get_resources_for_available_clusters is deprecated. {DEPRECATION_WARNING}"
)
async for resources in self.get_resources(resource_kind=resource_kind):
yield resources

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member

@mk-armah mk-armah left a comment

Choose a reason for hiding this comment

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

LGTM

mk-armah and others added 3 commits March 31, 2026 10:58
…placed cluster name filtering with query parameters for application kind. Added new integration.py file for application resource configuration and selector handling. Updated client methods to support query parameters in resource fetching. Enhanced tests to validate new functionality.
Comment on lines +27 to 30
async for cluster in argocd_client.get_resources(
resource_kind=ObjectKind(kind)
):
yield cluster
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is valid

Comment on lines +34 to +40
params: dict[str, Any] | None = None
parsed_kind = ObjectKind(kind)
if parsed_kind == ObjectKind.APPLICATION:
selector = cast(ApplicationResourceConfig, event.resource_config).selector
params = selector.query_params
async for cluster in argocd_client.get_resources(
resource_kind=parsed_kind, query_params=params
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

isolate/detach the application kind from this generic

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done

Comment on lines 69 to 72

async for app_batch in argocd_client.get_resources_for_available_clusters(
async for app_batch in argocd_client.get_resources(
resource_kind=ObjectKind.APPLICATION
):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

what happens to managed resources ? query params on application doesn't apply ?
How do we provide capability of getting managed resources for applications that are only within available clusters for instance

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@mk-armah I have extended the managed-resource kind to accept similar query params to filter out applications before fetching the former

…for application handling. Updated client methods and tests to align with new resource kind structure, enhancing clarity and consistency in resource fetching and configuration.
…y parameter handling. Added ApplicationQueryParams model for better application filtering and updated methods to utilize this new structure, ensuring more robust resource fetching and clearer debug information.
Comment on lines +54 to +67
@property
def generate_request_params(self) -> dict[str, Any]:
params: dict[str, Any] = {}
if self.selector:
params["selector"] = self.selector
if self.app_namespace:
params["appNamespace"] = self.app_namespace
if self.projects:
params["projects"] = self.projects
if self.resource_version:
params["resourceVersion"] = self.resource_version
if self.repo:
params["repo"] = self.repo
return params
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

use model_dump(exclude=None) or its equivalent on the v1 pydantic


class ManagedResourceResourceConfig(ResourceConfig):
kind: Literal["managed-resource"]
selector: ApplicationSelector
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

ApplicationSelector shouldn't be a top level selector as its not controlling the ManagedResource kind directly.

So the experience should be:

selector:
   - appFilters:
      # application selectors follows

…r handling. Replaced manual parameter construction in ApplicationQueryParams with a direct dictionary conversion, and introduced ManagedResourceSelector to encapsulate application filters for improved resource configuration.
…ndling and ObjectKind definitions in a new misc.py file.
Copy link
Copy Markdown
Member

@mk-armah mk-armah left a comment

Choose a reason for hiding this comment

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

LGTM

@mk-armah mk-armah merged commit 8c68d08 into main Apr 2, 2026
20 checks passed
@mk-armah mk-armah deleted the PORT-17567 branch April 2, 2026 17:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants