diff --git a/plugins/lookup/secret.py b/plugins/lookup/secret.py index eab18e3..3a69387 100644 --- a/plugins/lookup/secret.py +++ b/plugins/lookup/secret.py @@ -25,8 +25,9 @@ description: - Field name to extract from credential. - Supported fields depend on credential type. - - Common fields include username, password, domain, connectionString, apiId, apiKey, tenantId, clientId, clientSecret, privateKeyData, - publicKeyData, privateKeyPassPhrase. + - "Common fields include: username, password, domain, connectionString," + - "apiId, apiKey, tenantId, clientId, clientSecret, privateKeyData," + - "publicKeyData, privateKeyPassPhrase." type: str default: password server_base_url: @@ -56,8 +57,6 @@ notes: - Requires network access to DVLS server. - Authentication token is cached for the duration of the playbook run. - - Supported fields include username, password, domain, connectionString, apiId, apiKey, tenantId, clientId, clientSecret, privateKeyData, - publicKeyData, privateKeyPassPhrase. """ EXAMPLES = r""" diff --git a/plugins/module_utils/lookup_base.py b/plugins/module_utils/lookup_base.py index c3830c5..a93e8d5 100644 --- a/plugins/module_utils/lookup_base.py +++ b/plugins/module_utils/lookup_base.py @@ -39,6 +39,10 @@ "privateKeyPassPhrase", } +_token_cache = {} +_cleanup_registered = False +_display = None + class DVLSLookupHelper: """Helper class for DVLS lookup plugins with shared authentication and retrieval logic.""" @@ -51,20 +55,24 @@ def __init__(self, display_instance, ansible_error_class): display_instance: Display instance for logging ansible_error_class: AnsibleError class for raising exceptions """ - self._token = None - self._server_base_url = None - self._cleanup_registered = False + global _display self._display = display_instance self._ansible_error = ansible_error_class + _display = display_instance + + @staticmethod + def _cleanup_tokens(): + """Cleanup method called at interpreter exit to logout all cached tokens""" + global _token_cache - def _cleanup(self): - """Cleanup method called at interpreter exit""" - if self._token and self._server_base_url: + for server_url, token in _token_cache.items(): try: - self._display.vvv("Logging out from DVLS") - logout(self._server_base_url, self._token) + logout(server_url, token) + except (ConnectionError, TimeoutError, OSError): + pass except Exception as e: - self._display.warning(f"Failed to logout from DVLS during cleanup: {e}") + _display.warning(f"Failed to logout from {server_url}: {e}") + _token_cache.clear() def _is_uuid(self, value): """Check if a string matches UUID format""" @@ -93,18 +101,22 @@ def get_config(self, get_option, variables): } def authenticate(self, server_base_url, app_key, app_secret): - """Authenticate to DVLS and cache the token""" - if not self._token: + """Authenticate to DVLS and cache the token at module level""" + global _token_cache, _cleanup_registered + + if server_base_url not in _token_cache: try: self._display.vvv(f"Authenticating to DVLS at {server_base_url}") - self._token = login(server_base_url, app_key, app_secret) - self._server_base_url = server_base_url + token = login(server_base_url, app_key, app_secret) + _token_cache[server_base_url] = token - if not self._cleanup_registered: - atexit.register(self._cleanup) - self._cleanup_registered = True + if not _cleanup_registered: + atexit.register(DVLSLookupHelper._cleanup_tokens) + _cleanup_registered = True except Exception as e: raise self._ansible_error(f"DVLS authentication failed: {e}") from e + else: + self._display.vvv(f"Using cached token for {server_base_url}") def get_credential(self, server_base_url, vault_id, term): """ @@ -118,16 +130,24 @@ def get_credential(self, server_base_url, vault_id, term): Returns: dict: Complete credential object """ + global _token_cache + self._display.vvv(f"Looking up credential: {term}") + token = _token_cache.get(server_base_url) + if not token: + raise self._ansible_error( + f"Authentication token not found for server '{server_base_url}'. " + "Ensure authenticate() was called first." + ) if self._is_uuid(term): self._display.vvv(f"Using ID lookup for {term}") - response = get_vault_entry(server_base_url, self._token, vault_id, term) + response = get_vault_entry(server_base_url, token, vault_id, term) credential = response.get("data", {}) else: self._display.vvv(f"Using name lookup for {term}") response = get_vault_entry_from_name( - server_base_url, self._token, vault_id, term + server_base_url, token, vault_id, term ) entries = response.get("data", []) if not entries: @@ -135,9 +155,18 @@ def get_credential(self, server_base_url, vault_id, term): f"Credential '{term}' not found in vault {vault_id}" ) entry_id = entries[0].get("id") + if not entry_id: + raise self._ansible_error( + f"Entry for '{term}' is missing required 'id' field" + ) full_response = get_vault_entry( - server_base_url, self._token, vault_id, entry_id + server_base_url, token, vault_id, entry_id ) credential = full_response.get("data", {}) + if not credential: + raise self._ansible_error( + f"Credential '{term}' returned empty data from vault {vault_id}" + ) + return credential diff --git a/plugins/module_utils/vaults.py b/plugins/module_utils/vaults.py index 21948e8..0ed84a6 100644 --- a/plugins/module_utils/vaults.py +++ b/plugins/module_utils/vaults.py @@ -146,6 +146,7 @@ def get_vault_entry_from_path(server_base_url, token, vault_id, entry_path): return filter_folders( result, exact_match_field="path", exact_match_value=entry_path ) + except Exception as e: raise Exception(f"An error occurred while getting a vault entry: {e}") @@ -164,6 +165,7 @@ def get_vault_entry_from_type(server_base_url, token, vault_id, entry_type): return filter_folders( result, exact_match_field="type", exact_match_value=entry_type ) + except Exception as e: raise Exception(f"An error occurred while getting a vault entry: {e}") @@ -174,8 +176,6 @@ def get_vault_entries(server_base_url, token, vault_id): all_entries = [] page = 1 - response = requests - try: while True: response = requests.get(