Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
37a3d5e
Add Squelch to cdp_client
rsuplina Nov 26, 2025
40dc287
Add CDP IAM API client implementation
rsuplina Nov 26, 2025
00bd18c
Update iam_group module
rsuplina Nov 26, 2025
8188a17
Add tests for group module
rsuplina Nov 26, 2025
a3c81e7
Linting
rsuplina Nov 26, 2025
81c3fc3
Update squelch
rsuplina Nov 26, 2025
c9c7b80
Update IAM Group tests
rsuplina Dec 8, 2025
d568af6
Refactor CdpIamClient unit tests (and prep the integration tests)
wmudge Dec 9, 2025
cd87f43
Update squelch to be an empty dict
wmudge Dec 9, 2025
919e9b8
Update for Python linting
wmudge Dec 9, 2025
bcdb4f2
Update return types of process() and execute() methods
wmudge Dec 9, 2025
b88255b
Remove unused import
wmudge Dec 9, 2025
69cb032
Refactor present/absent logic
wmudge Dec 9, 2025
443cf38
Add mock for load_cdp_config()
wmudge Dec 9, 2025
0329e37
Rename test method to reflect function under test
wmudge Dec 9, 2025
60df71e
Update for linting
wmudge Dec 9, 2025
04f09d5
Merge pull request #1 from wmudge/update/test-reorg
rsuplina Dec 10, 2025
c1e0677
Refactor CDP IAM API tests to use correct mocker fixture
rsuplina Dec 10, 2025
b9e65d6
Add iam group module tests
rsuplina Dec 10, 2025
f60e872
Collapse RestClient into CdpClient
wmudge Dec 11, 2025
57d68f4
Fix invalid CDP environment variable
wmudge Dec 11, 2025
301313a
Create TestRestClient for integration test fixtures
wmudge Dec 11, 2025
7300d8f
Add 404 squelch to deleteGroup
wmudge Dec 11, 2025
b9ef3e2
Remove comment for IAM endpoint URL
wmudge Dec 11, 2025
cac492b
Update TestRestClient HTTP methods to handle credential headers
wmudge Dec 11, 2025
80c4357
Update iam_group integration tests to use fixture closures for asset …
wmudge Dec 11, 2025
29bec71
Rename TestRestClient and API client fixtures
wmudge Dec 11, 2025
d991fe0
Convert HTTP response parsing into decorator
wmudge Dec 11, 2025
61bbcbb
Consolidate header and body code into functions
wmudge Dec 11, 2025
cd49314
Update linting
wmudge Dec 11, 2025
4cb999a
Add pytest mark for slow tests
wmudge Dec 11, 2025
a9455e9
Merge pull request #2 from wmudge/feature/pytest-rest-client
rsuplina Dec 12, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 43 additions & 60 deletions plugins/module_utils/cdp_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
from cryptography.hazmat.primitives.asymmetric import ed25519
from email.utils import formatdate
from typing import Any, Dict, Optional, List, Tuple, Union
from urllib.parse import urlencode, urlparse
from urllib.parse import urlparse

from ansible.module_utils.urls import fetch_url

Expand Down Expand Up @@ -230,7 +230,7 @@ def __init__(self, msg: str, status: Optional[int] = None):
self.status = status


class RestClient:
class CdpClient:
"""Abstract base class for CDP REST API clients."""

def __init__(self, default_page_size: int = 100):
Expand All @@ -244,7 +244,7 @@ def __init__(self, default_page_size: int = 100):

# Abstract HTTP methods that must be implemented by subclasses
@abc.abstractmethod
def _get(
def get(
self,
path: str,
params: Optional[Dict[str, Any]] = None,
Expand All @@ -253,27 +253,33 @@ def _get(
pass

@abc.abstractmethod
def _post(
def post(
self,
path: str,
data: Optional[Dict[str, Any]] = None,
json_data: Optional[Dict[str, Any]] = None,
squelch: Dict[int, Any] = {},
) -> Dict[str, Any]:
"""Execute HTTP POST request."""
pass

@abc.abstractmethod
def _put(
def put(
self,
path: str,
data: Optional[Dict[str, Any]] = None,
json_data: Optional[Dict[str, Any]] = None,
squelch: Dict[int, Any] = {},
) -> Dict[str, Any]:
"""Execute HTTP PUT request."""
pass

@abc.abstractmethod
def _delete(self, path: str) -> Dict[str, Any]:
def delete(
self,
path: str,
squelch: Dict[int, Any] = {},
) -> Dict[str, Any]:
"""Execute HTTP DELETE request."""
pass

Expand All @@ -283,7 +289,7 @@ def paginated(default_page_size=100):
Decorator to handle automatic pagination for CDP API methods.

Usage:
@RestClient.paginated()
@CdpClient.paginated()
def some_api_method(self, param1, param2, startingToken=None, pageSize=None):
# Method implementation
pass
Expand Down Expand Up @@ -378,52 +384,7 @@ def wrapper(self, *args, **kwargs):
return decorator


class CdpClient:
"""CDP client that uses a RestClient instance to delegate HTTP methods."""

def __init__(
self,
api_client: RestClient,
default_page_size: int = 100,
):
"""
Initialize Delegated CDP client.

Args:
api_client: CdpClient instance to delegate HTTP methods to
default_page_size: Default page size for paginated requests
"""
self.default_page_size = default_page_size
self.api_client: RestClient = api_client

def get(
self,
path: str,
params: Optional[Dict[str, Any]] = None,
) -> Dict[str, Any]:
return self.api_client._get(path, params)

def post(
self,
path: str,
data: Optional[Dict[str, Any]] = None,
json_data: Optional[Dict[str, Any]] = None,
) -> Dict[str, Any]:
return self.api_client._post(path, data, json_data)

def put(
self,
path: str,
data: Optional[Dict[str, Any]] = None,
json_data: Optional[Dict[str, Any]] = None,
) -> Dict[str, Any]:
return self.api_client._put(path, data, json_data)

def delete(self, path: str) -> Dict[str, Any]:
return self.api_client._delete(path)


class AnsibleCdpClient(RestClient):
class AnsibleCdpClient(CdpClient):
"""Ansible-based CDP client using native Ansible HTTP methods."""

def __init__(
Expand Down Expand Up @@ -477,6 +438,7 @@ def _make_request(
data: Optional[Union[Dict[str, Any], List[Any]]] = None,
json_data: Optional[Union[Dict[str, Any], List[Any]]] = None,
max_retries: int = 3,
squelch: Dict[int, Any] = {},
) -> Any:
"""
Make HTTP request with retry logic using Ansible's fetch_url.
Expand All @@ -488,6 +450,7 @@ def _make_request(
data: Form data
json_data: JSON data
max_retries: Maximum number of retry attempts
squelch: Dictionary of HTTP status codes to squelch with default return values

Returns:
Response data as dictionary or None for 204 responses
Expand Down Expand Up @@ -556,6 +519,12 @@ def _make_request(
if status_code == 403:
raise CdpError(f"Forbidden access to {path}", status=403)

if status_code in squelch:
self.module.warn(
f"Squelched error {status_code} for {url}",
)
return squelch[status_code]

# Handle success responses
if 200 <= status_code < 300:
# 204 No Content - return None
Expand Down Expand Up @@ -627,32 +596,46 @@ def _make_request(
except Exception as e:
self.module.fail_json(msg=str(e))

def _get(
def get(
self,
path: str,
params: Optional[Dict[str, Any]] = None,
) -> Dict[str, Any]:
"""Execute HTTP GET request."""
return self._make_request("GET", path, params=params)

def _post(
def post(
self,
path: str,
data: Optional[Dict[str, Any]] = None,
json_data: Optional[Dict[str, Any]] = None,
squelch: Dict[int, Any] = {},
) -> Dict[str, Any]:
"""Execute HTTP POST request."""
return self._make_request("POST", path, data=data, json_data=json_data)
return self._make_request(
"POST",
path,
data=data,
json_data=json_data,
squelch=squelch,
)

def _put(
def put(
self,
path: str,
data: Optional[Dict[str, Any]] = None,
json_data: Optional[Dict[str, Any]] = None,
squelch: Dict[int, Any] = {},
) -> Dict[str, Any]:
"""Execute HTTP PUT request."""
return self._make_request("PUT", path, data=data, json_data=json_data)
return self._make_request(
"PUT",
path,
data=data,
json_data=json_data,
squelch=squelch,
)

def _delete(self, path: str) -> Dict[str, Any]:
def delete(self, path: str, squelch: Dict[int, Any] = {}) -> Dict[str, Any]:
"""Execute HTTP DELETE request."""
return self._make_request("DELETE", path)
return self._make_request("DELETE", path, squelch=squelch)
13 changes: 6 additions & 7 deletions plugins/module_utils/cdp_consumption.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,24 +21,23 @@
from typing import Any, Dict, Optional

from ansible_collections.cloudera.cloud.plugins.module_utils.cdp_client import (
RestClient,
CdpClient,
)


class CdpConsumptionClient(CdpClient):
class CdpConsumptionClient:
"""CDP Consumption API client."""

def __init__(self, api_client: RestClient):
def __init__(self, api_client: CdpClient):
"""
Initialize CDP Consumption client.

Args:
api_client: RestClient instance for managing HTTP method calls
api_client: CdpClient instance for managing HTTP method calls
"""
super().__init__(api_client=api_client)
self.api_client = api_client

@RestClient.paginated()
@CdpClient.paginated()
def list_compute_usage_records(
self,
from_timestamp: str,
Expand Down Expand Up @@ -69,7 +68,7 @@ def list_compute_usage_records(
if pageSize is not None:
json_data["pageSize"] = pageSize

return self.post(
return self.api_client.post(
"/api/v1/consumption/listComputeUsageRecords",
json_data=json_data,
)
Loading
Loading