diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index fbef1c0d..9df4780f 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -32,6 +32,15 @@ export SC_USERNAME=admin export SC_PASSWORD=admin_pass ``` +## Debugging + +Requests sent to and responses received from HyperCore can be written to a file for debugging. +Environ variable `SC_DEBUG_LOG_TRAFFIC=1` should be set, and `q` python library needs to be installed. +The output is written to file `/tmp/q` on Ansible controller. + +Note that `/tmp/q` will contain login passwords, HTTP session cookies and other sensitive data. +It should not be shared around. + # Integration tests configuration For integration tests we need to configure access to test cluster. diff --git a/plugins/module_utils/client.py b/plugins/module_utils/client.py index fa3294b2..fe9f14ae 100644 --- a/plugins/module_utils/client.py +++ b/plugins/module_utils/client.py @@ -9,6 +9,7 @@ __metaclass__ = type import json +import os import ssl from typing import Any, Optional, Union from io import BufferedReader @@ -30,6 +31,20 @@ DEFAULT_HEADERS = dict(Accept="application/json") +def _str_to_bool(s: str) -> bool: + return s.lower() not in ["", "0", "false", "f", "no", "n"] + + +SC_DEBUG_LOG_TRAFFIC = _str_to_bool(os.environ.get("SC_DEBUG_LOG_TRAFFIC", "0")) +if SC_DEBUG_LOG_TRAFFIC: + try: + import q as q_log # type: ignore + except ImportError: + # a do-nothing replacement + def q_log(*args, **kwargs): # type: ignore + pass + + class AuthMethod(str, enum.Enum): local = "local" oidc = "oidc" @@ -134,6 +149,47 @@ def _request( data: Optional[Union[dict[Any, Any], bytes, str, BufferedReader]] = None, headers: Optional[dict[Any, Any]] = None, timeout: Optional[float] = None, + ) -> Response: + # _request() with debug logging + try: + if SC_DEBUG_LOG_TRAFFIC: + effective_timeout = timeout + if timeout is None: + # If timeout from request is not specifically provided, take it from the Client. + effective_timeout = self.timeout + request_in = dict( + method=method, + path=path, + data=data, + headers=headers, + timeout=effective_timeout, + ) + resp = self._request_no_log(method, path, data, headers, timeout) + if SC_DEBUG_LOG_TRAFFIC: + request_out = dict( + status=resp.status, + data=resp.data, + headers=resp.headers, + ) + q_log(request_in, request_out) + return resp + except Exception as ex: + if SC_DEBUG_LOG_TRAFFIC: + request_out = dict( + status=resp.status, + data=resp.data, + headers=resp.headers, + ) + q_log(request_in, exception=ex) + raise + + def _request_no_log( + self, + method: str, + path: str, + data: Optional[Union[dict[Any, Any], bytes, str, BufferedReader]] = None, + headers: Optional[dict[Any, Any]] = None, + timeout: Optional[float] = None, ) -> Response: if timeout is None: # If timeout from request is not specifically provided, take it from the Client.