summaryrefslogtreecommitdiffstats
path: root/ansible_collections/community/general/plugins/lookup
diff options
context:
space:
mode:
Diffstat (limited to 'ansible_collections/community/general/plugins/lookup')
-rw-r--r--ansible_collections/community/general/plugins/lookup/bitwarden.py40
-rw-r--r--ansible_collections/community/general/plugins/lookup/bitwarden_secrets_manager.py21
-rw-r--r--ansible_collections/community/general/plugins/lookup/passwordstore.py31
3 files changed, 73 insertions, 19 deletions
diff --git a/ansible_collections/community/general/plugins/lookup/bitwarden.py b/ansible_collections/community/general/plugins/lookup/bitwarden.py
index 2cb2d19a1..7584cd98a 100644
--- a/ansible_collections/community/general/plugins/lookup/bitwarden.py
+++ b/ansible_collections/community/general/plugins/lookup/bitwarden.py
@@ -29,7 +29,7 @@ DOCUMENTATION = """
- Field to retrieve, for example V(name) or V(id).
- If set to V(id), only zero or one element can be returned.
Use the Jinja C(first) filter to get the only list element.
- - When O(collection_id) is set, this field can be undefined to retrieve the whole collection records.
+ - If set to V(None) or V(''), or if O(_terms) is empty, records are not filtered by fields.
type: str
default: name
version_added: 5.7.0
@@ -40,6 +40,10 @@ DOCUMENTATION = """
description: Collection ID to filter results by collection. Leave unset to skip filtering.
type: str
version_added: 6.3.0
+ organization_id:
+ description: Organization ID to filter results by organization. Leave unset to skip filtering.
+ type: str
+ version_added: 8.5.0
bw_session:
description: Pass session key instead of reading from env.
type: str
@@ -142,45 +146,44 @@ class Bitwarden(object):
raise BitwardenException(err)
return to_text(out, errors='surrogate_or_strict'), to_text(err, errors='surrogate_or_strict')
- def _get_matches(self, search_value, search_field, collection_id=None):
+ def _get_matches(self, search_value, search_field, collection_id=None, organization_id=None):
"""Return matching records whose search_field is equal to key.
"""
# Prepare set of params for Bitwarden CLI
- if search_value:
- if search_field == 'id':
- params = ['get', 'item', search_value]
- else:
- params = ['list', 'items', '--search', search_value]
- if collection_id:
- params.extend(['--collectionid', collection_id])
+ if search_field == 'id':
+ params = ['get', 'item', search_value]
else:
- if not collection_id:
- raise AnsibleError("search_value is required if collection_id is not set.")
+ params = ['list', 'items']
+ if search_value:
+ params.extend(['--search', search_value])
- params = ['list', 'items', '--collectionid', collection_id]
+ if collection_id:
+ params.extend(['--collectionid', collection_id])
+ if organization_id:
+ params.extend(['--organizationid', organization_id])
out, err = self._run(params)
# This includes things that matched in different fields.
initial_matches = AnsibleJSONDecoder().raw_decode(out)[0]
- if search_field == 'id' or not search_value:
+ if search_field == 'id':
if initial_matches is None:
initial_matches = []
else:
initial_matches = [initial_matches]
# Filter to only include results from the right field.
- return [item for item in initial_matches if item[search_field] == search_value]
+ return [item for item in initial_matches if not search_value or item[search_field] == search_value]
- def get_field(self, field, search_value=None, search_field="name", collection_id=None):
+ def get_field(self, field, search_value, search_field="name", collection_id=None, organization_id=None):
"""Return a list of the specified field for records whose search_field match search_value
and filtered by collection if collection has been provided.
If field is None, return the whole record for each match.
"""
- matches = self._get_matches(search_value, search_field, collection_id)
+ matches = self._get_matches(search_value, search_field, collection_id, organization_id)
if not field:
return matches
field_matches = []
@@ -215,15 +218,16 @@ class LookupModule(LookupBase):
field = self.get_option('field')
search_field = self.get_option('search')
collection_id = self.get_option('collection_id')
+ organization_id = self.get_option('organization_id')
_bitwarden.session = self.get_option('bw_session')
if not _bitwarden.unlocked:
raise AnsibleError("Bitwarden Vault locked. Run 'bw unlock'.")
if not terms:
- return [_bitwarden.get_field(field, None, search_field, collection_id)]
+ terms = [None]
- return [_bitwarden.get_field(field, term, search_field, collection_id) for term in terms]
+ return [_bitwarden.get_field(field, term, search_field, collection_id, organization_id) for term in terms]
_bitwarden = Bitwarden()
diff --git a/ansible_collections/community/general/plugins/lookup/bitwarden_secrets_manager.py b/ansible_collections/community/general/plugins/lookup/bitwarden_secrets_manager.py
index 2d6706bee..8cabc693f 100644
--- a/ansible_collections/community/general/plugins/lookup/bitwarden_secrets_manager.py
+++ b/ansible_collections/community/general/plugins/lookup/bitwarden_secrets_manager.py
@@ -70,6 +70,7 @@ RETURN = """
"""
from subprocess import Popen, PIPE
+from time import sleep
from ansible.errors import AnsibleLookupError
from ansible.module_utils.common.text.converters import to_text
@@ -84,11 +85,29 @@ class BitwardenSecretsManagerException(AnsibleLookupError):
class BitwardenSecretsManager(object):
def __init__(self, path='bws'):
self._cli_path = path
+ self._max_retries = 3
+ self._retry_delay = 1
@property
def cli_path(self):
return self._cli_path
+ def _run_with_retry(self, args, stdin=None, retries=0):
+ out, err, rc = self._run(args, stdin)
+
+ if rc != 0:
+ if retries >= self._max_retries:
+ raise BitwardenSecretsManagerException("Max retries exceeded. Unable to retrieve secret.")
+
+ if "Too many requests" in err:
+ delay = self._retry_delay * (2 ** retries)
+ sleep(delay)
+ return self._run_with_retry(args, stdin, retries + 1)
+ else:
+ raise BitwardenSecretsManagerException(f"Command failed with return code {rc}: {err}")
+
+ return out, err, rc
+
def _run(self, args, stdin=None):
p = Popen([self.cli_path] + args, stdout=PIPE, stderr=PIPE, stdin=PIPE)
out, err = p.communicate(stdin)
@@ -107,7 +126,7 @@ class BitwardenSecretsManager(object):
'get', 'secret', secret_id
]
- out, err, rc = self._run(params)
+ out, err, rc = self._run_with_retry(params)
if rc != 0:
raise BitwardenSecretsManagerException(to_text(err))
diff --git a/ansible_collections/community/general/plugins/lookup/passwordstore.py b/ansible_collections/community/general/plugins/lookup/passwordstore.py
index 7a6fca7a0..9814fe133 100644
--- a/ansible_collections/community/general/plugins/lookup/passwordstore.py
+++ b/ansible_collections/community/general/plugins/lookup/passwordstore.py
@@ -139,6 +139,21 @@ DOCUMENTATION = '''
type: bool
default: true
version_added: 8.1.0
+ missing_subkey:
+ description:
+ - Preference about what to do if the password subkey is missing.
+ - If set to V(error), the lookup will error out if the subkey does not exist.
+ - If set to V(empty) or V(warn), will return a V(none) in case the subkey does not exist.
+ version_added: 8.6.0
+ type: str
+ default: empty
+ choices:
+ - error
+ - warn
+ - empty
+ ini:
+ - section: passwordstore_lookup
+ key: missing_subkey
notes:
- The lookup supports passing all options as lookup parameters since community.general 6.0.0.
'''
@@ -147,6 +162,7 @@ ansible.cfg: |
[passwordstore_lookup]
lock=readwrite
locktimeout=45s
+ missing_subkey=warn
tasks.yml: |
---
@@ -432,6 +448,20 @@ class LookupModule(LookupBase):
if self.paramvals['subkey'] in self.passdict:
return self.passdict[self.paramvals['subkey']]
else:
+ if self.paramvals["missing_subkey"] == "error":
+ raise AnsibleError(
+ "passwordstore: subkey {0} for passname {1} not found and missing_subkey=error is set".format(
+ self.paramvals["subkey"], self.passname
+ )
+ )
+
+ if self.paramvals["missing_subkey"] == "warn":
+ display.warning(
+ "passwordstore: subkey {0} for passname {1} not found".format(
+ self.paramvals["subkey"], self.passname
+ )
+ )
+
return None
@contextmanager
@@ -481,6 +511,7 @@ class LookupModule(LookupBase):
'umask': self.get_option('umask'),
'timestamp': self.get_option('timestamp'),
'preserve': self.get_option('preserve'),
+ "missing_subkey": self.get_option("missing_subkey"),
}
def run(self, terms, variables, **kwargs):