1
0
Fork 0
mirror of https://github.com/ansible-collections/community.general.git synced 2024-09-14 20:13:21 +02:00

Lookups: use Ansible's config manager whenever possible (#5440)

* Start using Ansible's config manager to handle options.

* Docs improvements.

* Fix documentation, make options actual lookup options.

* The cyberarkpassword lookup does too strange things.

* The onepassword lookups are converted in #4728, let's not interfere.

* Improve docs.

* Skip shelvefile as well.

* Convert lmdb_kv.

* Convert and fix credstash.

* Convert manifold.

* Drop chef_databag.

* Convert dig.

* Update examples.

* Forgot the most important part.

* Fix lmdb_kv docs.

* Python 2.6 compatibility.

* Convert AnsibleUnicode to str.

* Load lookup with lookup loader.

* Fix environment handling and error message checking.

* Improve docs formatting.
This commit is contained in:
Felix Fontein 2022-11-01 21:58:46 +01:00 committed by GitHub
parent dc66aefa40
commit e718bd8445
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 161 additions and 91 deletions

View file

@ -26,6 +26,7 @@ jobs:
init-fail-on-error: true init-fail-on-error: true
provide-link-targets: | provide-link-targets: |
ansible_collections.ansible.builtin.dict2items_filter ansible_collections.ansible.builtin.dict2items_filter
ansible_collections.ansible.builtin.items_lookup
ansible_collections.ansible.builtin.path_join_filter ansible_collections.ansible.builtin.path_join_filter
ansible_collections.community.kubevirt.kubevirt_cdi_upload_module ansible_collections.community.kubevirt.kubevirt_cdi_upload_module
ansible_collections.community.kubevirt.kubevirt_inventory ansible_collections.community.kubevirt.kubevirt_inventory

View file

@ -0,0 +1,14 @@
minor_changes:
- "cartesian lookup plugin - start using Ansible's configuration manager to parse options (https://github.com/ansible-collections/community.general/pull/5440)."
- "credstash lookup plugin - start using Ansible's configuration manager to parse options (https://github.com/ansible-collections/community.general/pull/5440)."
- "dependent lookup plugin - start using Ansible's configuration manager to parse options (https://github.com/ansible-collections/community.general/pull/5440)."
- "dig lookup plugin - start using Ansible's configuration manager to parse options. All documented options can now also be passed as lookup parameters (https://github.com/ansible-collections/community.general/pull/5440)."
- "dnstxt lookup plugin - start using Ansible's configuration manager to parse options (https://github.com/ansible-collections/community.general/pull/5440)."
- "filetree lookup plugin - start using Ansible's configuration manager to parse options (https://github.com/ansible-collections/community.general/pull/5440)."
- "flattened lookup plugin - start using Ansible's configuration manager to parse options (https://github.com/ansible-collections/community.general/pull/5440)."
- "hiera lookup plugin - start using Ansible's configuration manager to parse options. The Hiera executable and config file can now also be passed as lookup parameters (https://github.com/ansible-collections/community.general/pull/5440)."
- "keyring lookup plugin - start using Ansible's configuration manager to parse options (https://github.com/ansible-collections/community.general/pull/5440)."
- "lmdb_kv lookup plugin - start using Ansible's configuration manager to parse options (https://github.com/ansible-collections/community.general/pull/5440)."
- "manifold lookup plugin - start using Ansible's configuration manager to parse options (https://github.com/ansible-collections/community.general/pull/5440)."
bugfixes:
- "credstash lookup plugin - pass plugin options to credstash for all terms, not just for the first (https://github.com/ansible-collections/community.general/pull/5440)."

View file

@ -15,9 +15,11 @@ DOCUMENTATION = '''
- It is clearer with an example, it turns [1, 2, 3], [a, b] into [1, a], [1, b], [2, a], [2, b], [3, a], [3, b]. - It is clearer with an example, it turns [1, 2, 3], [a, b] into [1, a], [1, b], [2, a], [2, b], [3, a], [3, b].
You can see the exact syntax in the examples section. You can see the exact syntax in the examples section.
options: options:
_raw: _terms:
description: description:
- a set of lists - a set of lists
type: list
elements: list
required: true required: true
''' '''
@ -69,6 +71,7 @@ class LookupModule(LookupBase):
return results return results
def run(self, terms, variables=None, **kwargs): def run(self, terms, variables=None, **kwargs):
self.set_options(var_options=variables, direct=kwargs)
terms = self._lookup_variables(terms) terms = self._lookup_variables(terms)

View file

@ -22,25 +22,33 @@ DOCUMENTATION = '''
required: true required: true
table: table:
description: name of the credstash table to query description: name of the credstash table to query
type: str
default: 'credential-store' default: 'credential-store'
version: version:
description: Credstash version description: Credstash version
type: str
default: ''
region: region:
description: AWS region description: AWS region
type: str
profile_name: profile_name:
description: AWS profile to use for authentication description: AWS profile to use for authentication
type: str
env: env:
- name: AWS_PROFILE - name: AWS_PROFILE
aws_access_key_id: aws_access_key_id:
description: AWS access key ID description: AWS access key ID
type: str
env: env:
- name: AWS_ACCESS_KEY_ID - name: AWS_ACCESS_KEY_ID
aws_secret_access_key: aws_secret_access_key:
description: AWS access key description: AWS access key
type: str
env: env:
- name: AWS_SECRET_ACCESS_KEY - name: AWS_SECRET_ACCESS_KEY
aws_session_token: aws_session_token:
description: AWS session token description: AWS session token
type: str
env: env:
- name: AWS_SESSION_TOKEN - name: AWS_SESSION_TOKEN
''' '''
@ -100,28 +108,39 @@ except ImportError:
class LookupModule(LookupBase): class LookupModule(LookupBase):
def run(self, terms, variables, **kwargs): def run(self, terms, variables=None, **kwargs):
if not CREDSTASH_INSTALLED: if not CREDSTASH_INSTALLED:
raise AnsibleError('The credstash lookup plugin requires credstash to be installed.') raise AnsibleError('The credstash lookup plugin requires credstash to be installed.')
self.set_options(var_options=variables, direct=kwargs)
version = self.get_option('version')
region = self.get_option('region')
table = self.get_option('table')
profile_name = self.get_option('profile_name')
aws_access_key_id = self.get_option('aws_access_key_id')
aws_secret_access_key = self.get_option('aws_secret_access_key')
aws_session_token = self.get_option('aws_session_token')
context = dict(
(k, v) for k, v in kwargs.items()
if k not in ('version', 'region', 'table', 'profile_name', 'aws_access_key_id', 'aws_secret_access_key', 'aws_session_token')
)
kwargs_pass = {
'profile_name': profile_name,
'aws_access_key_id': aws_access_key_id,
'aws_secret_access_key': aws_secret_access_key,
'aws_session_token': aws_session_token,
}
ret = [] ret = []
for term in terms: for term in terms:
try: try:
version = kwargs.pop('version', '') ret.append(credstash.getSecret(term, version, region, table, context=context, **kwargs_pass))
region = kwargs.pop('region', None)
table = kwargs.pop('table', 'credential-store')
profile_name = kwargs.pop('profile_name', os.getenv('AWS_PROFILE', None))
aws_access_key_id = kwargs.pop('aws_access_key_id', os.getenv('AWS_ACCESS_KEY_ID', None))
aws_secret_access_key = kwargs.pop('aws_secret_access_key', os.getenv('AWS_SECRET_ACCESS_KEY', None))
aws_session_token = kwargs.pop('aws_session_token', os.getenv('AWS_SESSION_TOKEN', None))
kwargs_pass = {'profile_name': profile_name, 'aws_access_key_id': aws_access_key_id,
'aws_secret_access_key': aws_secret_access_key, 'aws_session_token': aws_session_token}
val = credstash.getSecret(term, version, region, table, context=kwargs, **kwargs_pass)
except credstash.ItemNotFound: except credstash.ItemNotFound:
raise AnsibleError('Key {0} not found'.format(term)) raise AnsibleError('Key {0} not found'.format(term))
except Exception as e: except Exception as e:
raise AnsibleError('Encountered exception while fetching {0}: {1}'.format(term, e)) raise AnsibleError('Encountered exception while fetching {0}: {1}'.format(term, e))
ret.append(val)
return ret return ret

View file

@ -174,7 +174,6 @@ class LookupModule(LookupBase):
""" """
def run(self, terms, variables=None, **kwargs): def run(self, terms, variables=None, **kwargs):
display.vvvv("%s" % terms) display.vvvv("%s" % terms)
if isinstance(terms, list): if isinstance(terms, list):
return_values = [] return_values = []

View file

@ -16,7 +16,7 @@ description:
or template expressions which evaluate to lists or dicts, composed of the elements of or template expressions which evaluate to lists or dicts, composed of the elements of
the input evaluated lists and dictionaries." the input evaluated lists and dictionaries."
options: options:
_raw: _terms:
description: description:
- A list where the elements are one-element dictionaries, mapping a name to a string, list, or dictionary. - A list where the elements are one-element dictionaries, mapping a name to a string, list, or dictionary.
The name is the index that is used in the result object. The value is iterated over as described below. The name is the index that is used in the result object. The value is iterated over as described below.
@ -180,6 +180,8 @@ class LookupModule(LookupBase):
def run(self, terms, variables=None, **kwargs): def run(self, terms, variables=None, **kwargs):
"""Generate list.""" """Generate list."""
self.set_options(var_options=variables, direct=kwargs)
result = [] result = []
if len(terms) > 0: if len(terms) > 0:
templar = Templar(loader=self._templar._loader) templar = Templar(loader=self._templar._loader)

View file

@ -21,22 +21,26 @@ DOCUMENTATION = '''
- In addition to (default) A record, it is also possible to specify a different record type that should be queried. - In addition to (default) A record, it is also possible to specify a different record type that should be queried.
This can be done by either passing-in additional parameter of format qtype=TYPE to the dig lookup, or by appending /TYPE to the FQDN being queried. This can be done by either passing-in additional parameter of format qtype=TYPE to the dig lookup, or by appending /TYPE to the FQDN being queried.
- If multiple values are associated with the requested record, the results will be returned as a comma-separated list. - If multiple values are associated with the requested record, the results will be returned as a comma-separated list.
In such cases you may want to pass option wantlist=True to the plugin, which will result in the record values being returned as a list In such cases you may want to pass option I(wantlist=true) to the lookup call, or alternatively use C(query) instead of C(lookup),
over which you can iterate later on. which will result in the record values being returned as a list over which you can iterate later on.
- By default, the lookup will rely on system-wide configured DNS servers for performing the query. - By default, the lookup will rely on system-wide configured DNS servers for performing the query.
It is also possible to explicitly specify DNS servers to query using the @DNS_SERVER_1,DNS_SERVER_2,...,DNS_SERVER_N notation. It is also possible to explicitly specify DNS servers to query using the @DNS_SERVER_1,DNS_SERVER_2,...,DNS_SERVER_N notation.
This needs to be passed-in as an additional parameter to the lookup This needs to be passed-in as an additional parameter to the lookup
options: options:
_terms: _terms:
description: Domain(s) to query. description: Domain(s) to query.
type: list
elements: str
qtype: qtype:
description: description:
- Record type to query. - Record type to query.
- C(DLV) has been removed in community.general 6.0.0. - C(DLV) has been removed in community.general 6.0.0.
type: str
default: 'A' default: 'A'
choices: [A, ALL, AAAA, CNAME, DNAME, DNSKEY, DS, HINFO, LOC, MX, NAPTR, NS, NSEC3PARAM, PTR, RP, RRSIG, SOA, SPF, SRV, SSHFP, TLSA, TXT] choices: [A, ALL, AAAA, CNAME, DNAME, DNSKEY, DS, HINFO, LOC, MX, NAPTR, NS, NSEC3PARAM, PTR, RP, RRSIG, SOA, SPF, SRV, SSHFP, TLSA, TXT]
flat: flat:
description: If 0 each record is returned as a dictionary, otherwise a string. description: If 0 each record is returned as a dictionary, otherwise a string.
type: int
default: 1 default: 1
retry_servfail: retry_servfail:
description: Retry a nameserver if it returns SERVFAIL. description: Retry a nameserver if it returns SERVFAIL.
@ -59,6 +63,11 @@ DOCUMENTATION = '''
default: false default: false
type: bool type: bool
version_added: 6.0.0 version_added: 6.0.0
class:
description:
- "Class."
type: str
default: 'IN'
notes: notes:
- ALL is not a record per-se, merely the listed fields are available for any record results you retrieve in the form of a dictionary. - ALL is not a record per-se, merely the listed fields are available for any record results you retrieve in the form of a dictionary.
- While the 'dig' lookup plugin supports anything which dnspython supports out of the box, only a subset can be converted into a dictionary. - While the 'dig' lookup plugin supports anything which dnspython supports out of the box, only a subset can be converted into a dictionary.
@ -74,7 +83,7 @@ EXAMPLES = """
- name: "The TXT record for example.org." - name: "The TXT record for example.org."
ansible.builtin.debug: ansible.builtin.debug:
msg: "{{ lookup('community.general.dig', 'example.org.', 'qtype=TXT') }}" msg: "{{ lookup('community.general.dig', 'example.org.', qtype='TXT') }}"
- name: "The TXT record for example.org, alternative syntax." - name: "The TXT record for example.org, alternative syntax."
ansible.builtin.debug: ansible.builtin.debug:
@ -83,24 +92,24 @@ EXAMPLES = """
- name: use in a loop - name: use in a loop
ansible.builtin.debug: ansible.builtin.debug:
msg: "MX record for gmail.com {{ item }}" msg: "MX record for gmail.com {{ item }}"
with_items: "{{ lookup('community.general.dig', 'gmail.com./MX', wantlist=True) }}" with_items: "{{ lookup('community.general.dig', 'gmail.com./MX', wantlist=true) }}"
- ansible.builtin.debug: - ansible.builtin.debug:
msg: "Reverse DNS for 192.0.2.5 is {{ lookup('community.general.dig', '192.0.2.5/PTR') }}" msg: "Reverse DNS for 192.0.2.5 is {{ lookup('community.general.dig', '192.0.2.5/PTR') }}"
- ansible.builtin.debug: - ansible.builtin.debug:
msg: "Reverse DNS for 192.0.2.5 is {{ lookup('community.general.dig', '5.2.0.192.in-addr.arpa./PTR') }}" msg: "Reverse DNS for 192.0.2.5 is {{ lookup('community.general.dig', '5.2.0.192.in-addr.arpa./PTR') }}"
- ansible.builtin.debug: - ansible.builtin.debug:
msg: "Reverse DNS for 192.0.2.5 is {{ lookup('community.general.dig', '5.2.0.192.in-addr.arpa.', 'qtype=PTR') }}" msg: "Reverse DNS for 192.0.2.5 is {{ lookup('community.general.dig', '5.2.0.192.in-addr.arpa.', qtype='PTR') }}"
- ansible.builtin.debug: - ansible.builtin.debug:
msg: "Querying 198.51.100.23 for IPv4 address for example.com. produces {{ lookup('dig', 'example.com', '@198.51.100.23') }}" msg: "Querying 198.51.100.23 for IPv4 address for example.com. produces {{ lookup('dig', 'example.com', '@198.51.100.23') }}"
- ansible.builtin.debug: - ansible.builtin.debug:
msg: "XMPP service for gmail.com. is available at {{ item.target }} on port {{ item.port }}" msg: "XMPP service for gmail.com. is available at {{ item.target }} on port {{ item.port }}"
with_items: "{{ lookup('community.general.dig', '_xmpp-server._tcp.gmail.com./SRV', 'flat=0', wantlist=True) }}" with_items: "{{ lookup('community.general.dig', '_xmpp-server._tcp.gmail.com./SRV', flat=0, wantlist=true) }}"
- name: Retry nameservers that return SERVFAIL - name: Retry nameservers that return SERVFAIL
ansible.builtin.debug: ansible.builtin.debug:
msg: "{{ lookup('community.general.dig', 'example.org./A', 'retry_servfail=True') }}" msg: "{{ lookup('community.general.dig', 'example.org./A', retry_servfail=true) }}"
""" """
RETURN = """ RETURN = """
@ -279,21 +288,26 @@ class LookupModule(LookupBase):
... flat=0 # returns a dict; default is 1 == string ... flat=0 # returns a dict; default is 1 == string
''' '''
if HAVE_DNS is False: if HAVE_DNS is False:
raise AnsibleError("The dig lookup requires the python 'dnspython' library and it is not installed") raise AnsibleError("The dig lookup requires the python 'dnspython' library and it is not installed")
self.set_options(var_options=variables, direct=kwargs)
# Create Resolver object so that we can set NS if necessary # Create Resolver object so that we can set NS if necessary
myres = dns.resolver.Resolver(configure=True) myres = dns.resolver.Resolver(configure=True)
edns_size = 4096 edns_size = 4096
myres.use_edns(0, ednsflags=dns.flags.DO, payload=edns_size) myres.use_edns(0, ednsflags=dns.flags.DO, payload=edns_size)
domain = None domain = None
qtype = 'A' qtype = self.get_option('qtype')
flat = True flat = self.get_option('flat')
fail_on_error = False fail_on_error = self.get_option('fail_on_error')
real_empty = False real_empty = self.get_option('real_empty')
rdclass = dns.rdataclass.from_text('IN') try:
rdclass = dns.rdataclass.from_text(self.get_option('class'))
except Exception as e:
raise AnsibleError("dns lookup illegal CLASS: %s" % to_native(e))
myres.retry_servfail = self.get_option('retry_servfail')
for t in terms: for t in terms:
if t.startswith('@'): # e.g. "@10.0.1.2,192.0.2.1" is ok. if t.startswith('@'): # e.g. "@10.0.1.2,192.0.2.1" is ok.
@ -316,7 +330,7 @@ class LookupModule(LookupBase):
continue continue
if '=' in t: if '=' in t:
try: try:
opt, arg = t.split('=') opt, arg = t.split('=', 1)
except Exception: except Exception:
pass pass

View file

@ -71,6 +71,7 @@ from ansible.plugins.lookup import LookupBase
class LookupModule(LookupBase): class LookupModule(LookupBase):
def run(self, terms, variables=None, **kwargs): def run(self, terms, variables=None, **kwargs):
self.set_options(var_options=variables, direct=kwargs)
if HAVE_DNS is False: if HAVE_DNS is False:
raise AnsibleError("Can't LOOKUP(dnstxt): module dns.resolver is not installed") raise AnsibleError("Can't LOOKUP(dnstxt): module dns.resolver is not installed")

View file

@ -201,6 +201,8 @@ def file_props(root, path):
class LookupModule(LookupBase): class LookupModule(LookupBase):
def run(self, terms, variables=None, **kwargs): def run(self, terms, variables=None, **kwargs):
self.set_options(var_options=variables, direct=kwargs)
basedir = self.get_basedir(variables) basedir = self.get_basedir(variables)
ret = [] ret = []

View file

@ -11,14 +11,17 @@ DOCUMENTATION = '''
author: Serge van Ginderachter (!UNKNOWN) <serge@vanginderachter.be> author: Serge van Ginderachter (!UNKNOWN) <serge@vanginderachter.be>
short_description: return single list completely flattened short_description: return single list completely flattened
description: description:
- given one or more lists, this lookup will flatten any list elements found recursively until only 1 list is left. - Given one or more lists, this lookup will flatten any list elements found recursively until only 1 list is left.
options: options:
_terms: _terms:
description: lists to flatten description: lists to flatten
type: list
elements: raw
required: true required: true
notes: notes:
- unlike 'items' which only flattens 1 level, this plugin will continue to flatten until it cannot find lists anymore. - Unlike the R(items lookup,ansible_collections.ansible.builtin.items_lookup) which only flattens 1 level,
- aka highlander plugin, there can only be one (list). this plugin will continue to flatten until it cannot find lists anymore.
- Aka highlander plugin, there can only be one (list).
''' '''
EXAMPLES = """ EXAMPLES = """
@ -78,9 +81,10 @@ class LookupModule(LookupBase):
return ret return ret
def run(self, terms, variables, **kwargs): def run(self, terms, variables=None, **kwargs):
if not isinstance(terms, list): if not isinstance(terms, list):
raise AnsibleError("with_flattened expects a list") raise AnsibleError("with_flattened expects a list")
self.set_options(var_options=variables, direct=kwargs)
return self._do_flatten(terms, variables) return self._do_flatten(terms, variables)

View file

@ -14,23 +14,23 @@ DOCUMENTATION = '''
requirements: requirements:
- hiera (command line utility) - hiera (command line utility)
description: description:
- Retrieves data from an Puppetmaster node using Hiera as ENC - Retrieves data from an Puppetmaster node using Hiera as ENC.
options: options:
_hiera_key: _terms:
description: description:
- The list of keys to lookup on the Puppetmaster - The list of keys to lookup on the Puppetmaster.
type: list type: list
elements: string elements: string
required: true required: true
_bin_file: executable:
description: description:
- Binary file to execute Hiera - Binary file to execute Hiera.
default: '/usr/bin/hiera' default: '/usr/bin/hiera'
env: env:
- name: ANSIBLE_HIERA_BIN - name: ANSIBLE_HIERA_BIN
_hierarchy_file: config_file:
description: description:
- File that describes the hierarchy of Hiera - File that describes the hierarchy of Hiera.
default: '/etc/hiera.yaml' default: '/etc/hiera.yaml'
env: env:
- name: ANSIBLE_HIERA_CFG - name: ANSIBLE_HIERA_CFG
@ -67,25 +67,28 @@ from ansible.plugins.lookup import LookupBase
from ansible.utils.cmd_functions import run_cmd from ansible.utils.cmd_functions import run_cmd
from ansible.module_utils.common.text.converters import to_text from ansible.module_utils.common.text.converters import to_text
ANSIBLE_HIERA_CFG = os.getenv('ANSIBLE_HIERA_CFG', '/etc/hiera.yaml')
ANSIBLE_HIERA_BIN = os.getenv('ANSIBLE_HIERA_BIN', '/usr/bin/hiera')
class Hiera(object): class Hiera(object):
def __init__(self, hiera_cfg, hiera_bin):
self.hiera_cfg = hiera_cfg
self.hiera_bin = hiera_bin
def get(self, hiera_key): def get(self, hiera_key):
pargs = [ANSIBLE_HIERA_BIN] pargs = [self.hiera_bin]
pargs.extend(['-c', ANSIBLE_HIERA_CFG]) pargs.extend(['-c', self.hiera_cfg])
pargs.extend(hiera_key) pargs.extend(hiera_key)
rc, output, err = run_cmd("{0} -c {1} {2}".format( rc, output, err = run_cmd("{0} -c {1} {2}".format(
ANSIBLE_HIERA_BIN, ANSIBLE_HIERA_CFG, hiera_key[0])) self.hiera_bin, self.hiera_cfg, hiera_key[0]))
return to_text(output.strip()) return to_text(output.strip())
class LookupModule(LookupBase): class LookupModule(LookupBase):
def run(self, terms, variables=''): def run(self, terms, variables=None, **kwargs):
hiera = Hiera() self.set_options(var_options=variables, direct=kwargs)
hiera = Hiera(self.get_option('config_file'), self.get_option('executable'))
ret = [hiera.get(terms)] ret = [hiera.get(terms)]
return ret return ret

View file

@ -26,7 +26,9 @@ EXAMPLES = """
- 'servicename username' - 'servicename username'
- name: access mysql with password from keyring - name: access mysql with password from keyring
mysql_db: login_password={{lookup('community.general.keyring','mysql joe')}} login_user=joe community.mysql.mysql_db:
login_password: "{{ lookup('community.general.keyring', 'mysql joe') }}"
login_user: joe
""" """
RETURN = """ RETURN = """
@ -53,10 +55,12 @@ display = Display()
class LookupModule(LookupBase): class LookupModule(LookupBase):
def run(self, terms, **kwargs): def run(self, terms, variables=None, **kwargs):
if not HAS_KEYRING: if not HAS_KEYRING:
raise AnsibleError(u"Can't LOOKUP(keyring): missing required python library 'keyring'") raise AnsibleError(u"Can't LOOKUP(keyring): missing required python library 'keyring'")
self.set_options(var_options=variables, direct=kwargs)
display.vvvv(u"keyring: %s" % keyring.get_keyring()) display.vvvv(u"keyring: %s" % keyring.get_keyring())
ret = [] ret = []
for term in terms: for term in terms:

View file

@ -13,15 +13,20 @@ DOCUMENTATION = '''
version_added: '0.2.0' version_added: '0.2.0'
short_description: fetch data from LMDB short_description: fetch data from LMDB
description: description:
- This lookup returns a list of results from an LMDB DB corresponding to a list of items given to it - This lookup returns a list of results from an LMDB DB corresponding to a list of items given to it.
requirements: requirements:
- lmdb (python library https://lmdb.readthedocs.io/en/release/) - lmdb (python library https://lmdb.readthedocs.io/en/release/)
options: options:
_terms: _terms:
description: list of keys to query description: List of keys to query.
type: list
elements: str
db: db:
description: path to LMDB database description: Path to LMDB database.
type: str
default: 'ansible.mdb' default: 'ansible.mdb'
vars:
- name: lmdb_kv_db
''' '''
EXAMPLES = """ EXAMPLES = """
@ -43,8 +48,8 @@ EXAMPLES = """
- item == 'Belgium' - item == 'Belgium'
vars: vars:
- lmdb_kv_db: jp.mdb - lmdb_kv_db: jp.mdb
with_community.general.lmdb_kv: with_community.general.lmdb_kv:
- be - be
""" """
RETURN = """ RETURN = """
@ -58,6 +63,7 @@ _raw:
from ansible.errors import AnsibleError from ansible.errors import AnsibleError
from ansible.plugins.lookup import LookupBase from ansible.plugins.lookup import LookupBase
from ansible.module_utils.common.text.converters import to_native, to_text from ansible.module_utils.common.text.converters import to_native, to_text
HAVE_LMDB = True HAVE_LMDB = True
try: try:
import lmdb import lmdb
@ -67,8 +73,7 @@ except ImportError:
class LookupModule(LookupBase): class LookupModule(LookupBase):
def run(self, terms, variables, **kwargs): def run(self, terms, variables=None, **kwargs):
''' '''
terms contain any number of keys to be retrieved. terms contain any number of keys to be retrieved.
If terms is None, all keys from the database are returned If terms is None, all keys from the database are returned
@ -81,17 +86,15 @@ class LookupModule(LookupBase):
vars: vars:
- lmdb_kv_db: "jp.mdb" - lmdb_kv_db: "jp.mdb"
''' '''
if HAVE_LMDB is False: if HAVE_LMDB is False:
raise AnsibleError("Can't LOOKUP(lmdb_kv): this module requires lmdb to be installed") raise AnsibleError("Can't LOOKUP(lmdb_kv): this module requires lmdb to be installed")
db = variables.get('lmdb_kv_db', None) self.set_options(var_options=variables, direct=kwargs)
if db is None:
db = kwargs.get('db', 'ansible.mdb') db = self.get_option('db')
db = str(db)
try: try:
env = lmdb.open(db, readonly=True) env = lmdb.open(str(db), readonly=True)
except Exception as e: except Exception as e:
raise AnsibleError("LMDB can't open database %s: %s" % (db, to_native(e))) raise AnsibleError("LMDB can't open database %s: %s" % (db, to_native(e)))

View file

@ -207,7 +207,7 @@ class ManifoldApiClient(object):
class LookupModule(LookupBase): class LookupModule(LookupBase):
def run(self, terms, variables=None, api_token=None, project=None, team=None): def run(self, terms, variables=None, **kwargs):
""" """
:param terms: a list of resources lookups to run. :param terms: a list of resources lookups to run.
:param variables: ansible variables active at the time of the lookup :param variables: ansible variables active at the time of the lookup
@ -217,10 +217,11 @@ class LookupModule(LookupBase):
:return: a dictionary of resources credentials :return: a dictionary of resources credentials
""" """
if not api_token: self.set_options(var_options=variables, direct=kwargs)
api_token = os.getenv('MANIFOLD_API_TOKEN')
if not api_token: api_token = self.get_option('api_token')
raise AnsibleError('API token is required. Please set api_token parameter or MANIFOLD_API_TOKEN env var') project = self.get_option('project')
team = self.get_option('team')
try: try:
labels = terms labels = terms

View file

@ -14,23 +14,24 @@ DOCUMENTATION = '''
- Read keys from Python shelve file. - Read keys from Python shelve file.
options: options:
_terms: _terms:
description: sets of key value pairs of parameters description: Sets of key value pairs of parameters.
key: key:
description: key to query description: Key to query.
required: true required: true
file: file:
description: path to shelve file description: Path to shelve file.
required: true required: true
''' '''
EXAMPLES = """ EXAMPLES = """
- name: retrieve a string value corresponding to a key inside a Python shelve file - name: Retrieve a string value corresponding to a key inside a Python shelve file
ansible.builtin.debug: msg="{{ lookup('community.general.shelvefile', 'file=path_to_some_shelve_file.db key=key_to_retrieve') }} ansible.builtin.debug:
msg: "{{ lookup('community.general.shelvefile', 'file=path_to_some_shelve_file.db key=key_to_retrieve') }}"
""" """
RETURN = """ RETURN = """
_list: _list:
description: value(s) of key(s) in shelve file(s) description: Value(s) of key(s) in shelve file(s).
type: list type: list
elements: str elements: str
""" """
@ -53,7 +54,6 @@ class LookupModule(LookupBase):
return res return res
def run(self, terms, variables=None, **kwargs): def run(self, terms, variables=None, **kwargs):
if not isinstance(terms, list): if not isinstance(terms, list):
terms = [terms] terms = [terms]

View file

@ -6,10 +6,10 @@
- hosts: localhost - hosts: localhost
tasks: tasks:
- debug: - debug:
msg: '{{ query(''community.general.lmdb_kv'', ''nl'', ''be'', ''lu'', db=''jp.mdb'') }}' msg: '{{ query("community.general.lmdb_kv", "nl", "be", "lu", db="jp.mdb") }}'
- debug: - debug:
var: item.1 var: item.1
loop: '{{ query(''community.general.lmdb_kv'', db=''jp.mdb'') }}' loop: '{{ query("community.general.lmdb_kv", db="jp.mdb") }}'
- assert: - assert:
that: that:
- query('community.general.lmdb_kv', 'nl', 'be', 'lu', db='jp.mdb') == ['Netherlands', 'Belgium', 'Luxembourg'] - query('community.general.lmdb_kv', 'nl', 'be', 'lu', db='jp.mdb') == ['Netherlands', 'Belgium', 'Luxembourg']

View file

@ -11,8 +11,10 @@ from ansible.errors import AnsibleError
from ansible.module_utils.urls import ConnectionError, SSLValidationError from ansible.module_utils.urls import ConnectionError, SSLValidationError
from ansible.module_utils.six.moves.urllib.error import HTTPError, URLError from ansible.module_utils.six.moves.urllib.error import HTTPError, URLError
from ansible.module_utils import six from ansible.module_utils import six
from ansible.plugins.loader import lookup_loader
from ansible_collections.community.general.plugins.lookup.manifold import ManifoldApiClient, LookupModule, ApiError from ansible_collections.community.general.plugins.lookup.manifold import ManifoldApiClient, LookupModule, ApiError
import json import json
import os
API_FIXTURES = { API_FIXTURES = {
@ -375,8 +377,7 @@ class TestManifoldApiClient(unittest.TestCase):
class TestLookupModule(unittest.TestCase): class TestLookupModule(unittest.TestCase):
def setUp(self): def setUp(self):
self.lookup = LookupModule() self.lookup = lookup_loader.get('community.general.manifold')
self.lookup._load_name = "manifold"
@patch('ansible_collections.community.general.plugins.lookup.manifold.ManifoldApiClient') @patch('ansible_collections.community.general.plugins.lookup.manifold.ManifoldApiClient')
def test_get_all(self, client_mock): def test_get_all(self, client_mock):
@ -515,23 +516,22 @@ class TestLookupModule(unittest.TestCase):
self.lookup.run([], api_token='token-123') self.lookup.run([], api_token='token-123')
self.assertTrue('Exception: Unknown error' in str(context.exception)) self.assertTrue('Exception: Unknown error' in str(context.exception))
@patch('ansible_collections.community.general.plugins.lookup.manifold.os.getenv')
@patch('ansible_collections.community.general.plugins.lookup.manifold.ManifoldApiClient') @patch('ansible_collections.community.general.plugins.lookup.manifold.ManifoldApiClient')
def test_falls_back_to_env_var(self, client_mock, getenv_mock): def test_falls_back_to_env_var(self, client_mock):
getenv_mock.return_value = 'token-321'
client_mock.return_value.get_resources.return_value = [] client_mock.return_value.get_resources.return_value = []
client_mock.return_value.get_credentials.return_value = [] client_mock.return_value.get_credentials.return_value = []
self.lookup.run([]) try:
getenv_mock.assert_called_with('MANIFOLD_API_TOKEN') os.environ['MANIFOLD_API_TOKEN'] = 'token-321'
self.lookup.run([])
finally:
os.environ.pop('MANIFOLD_API_TOKEN', None)
client_mock.assert_called_with('token-321') client_mock.assert_called_with('token-321')
@patch('ansible_collections.community.general.plugins.lookup.manifold.os.getenv')
@patch('ansible_collections.community.general.plugins.lookup.manifold.ManifoldApiClient') @patch('ansible_collections.community.general.plugins.lookup.manifold.ManifoldApiClient')
def test_falls_raises_on_no_token(self, client_mock, getenv_mock): def test_falls_raises_on_no_token(self, client_mock):
getenv_mock.return_value = None
client_mock.return_value.get_resources.return_value = [] client_mock.return_value.get_resources.return_value = []
client_mock.return_value.get_credentials.return_value = [] client_mock.return_value.get_credentials.return_value = []
os.environ.pop('MANIFOLD_API_TOKEN', None)
with self.assertRaises(AnsibleError) as context: with self.assertRaises(AnsibleError) as context:
self.lookup.run([]) self.lookup.run([])
self.assertEqual('API token is required. Please set api_token parameter or MANIFOLD_API_TOKEN env var', assert 'api_token' in str(context.exception)
str(context.exception))