Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 3 additions & 4 deletions plugins/lookup/secret.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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"""
Expand Down
67 changes: 48 additions & 19 deletions plugins/module_utils/lookup_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand All @@ -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"""
Expand Down Expand Up @@ -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):
"""
Expand All @@ -118,26 +130,43 @@ 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:
raise self._ansible_error(
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
4 changes: 2 additions & 2 deletions plugins/module_utils/vaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}")

Expand All @@ -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}")

Expand All @@ -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(
Expand Down