Initial commit

This commit is contained in:
Ansible Core Team 2020-03-09 09:11:07 +00:00
commit aebc1b03fd
4861 changed files with 812621 additions and 0 deletions

View file

127
plugins/lookup/avi.py Normal file
View file

@ -0,0 +1,127 @@
# python 3 headers, required if submitting to Ansible
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = '''
lookup: avi
author: Sandeep Bandi <sandeepb@avinetworks.com>
short_description: Look up ``Avi`` objects.
description:
- Given an object_type, fetch all the objects of that type or fetch
the specific object that matches the name/uuid given via options.
- For single object lookup. If you want the output to be a list, you may
want to pass option wantlist=True to the plugin.
options:
obj_type:
description:
- type of object to query
required: True
obj_name:
description:
- name of the object to query
obj_uuid:
description:
- UUID of the object to query
extends_documentation_fragment:
- community.general.avi
'''
EXAMPLES = """
# Lookup query for all the objects of a specific type.
- debug: msg="{{ lookup('avi', avi_credentials=avi_credentials, obj_type='virtualservice') }}"
# Lookup query for an object with the given name and type.
- debug: msg="{{ lookup('avi', avi_credentials=avi_credentials, obj_name='vs1', obj_type='virtualservice', wantlist=True) }}"
# Lookup query for an object with the given UUID and type.
- debug: msg="{{ lookup('avi', obj_uuid='virtualservice-5c0e183a-690a-45d8-8d6f-88c30a52550d', obj_type='virtualservice') }}"
# We can replace lookup with query function to always the get the output as list.
# This is helpful for looping.
- debug: msg="{{ query('avi', obj_uuid='virtualservice-5c0e183a-690a-45d8-8d6f-88c30a52550d', obj_type='virtualservice') }}"
"""
RETURN = """
_raw:
description:
- One ore more objects returned from ``Avi`` API.
type: list
elements: dictionary
"""
from ansible.module_utils._text import to_native
from ansible.errors import AnsibleError, AnsibleParserError
from ansible.plugins.lookup import LookupBase
from ansible.utils.display import Display
from ansible_collections.community.general.plugins.module_utils.network.avi.avi_api import (ApiSession,
AviCredentials,
AviServerError,
ObjectNotFound,
APIError)
display = Display()
def _api(avi_session, path, **kwargs):
'''
Generic function to handle both /<obj_type>/<obj_uuid> and /<obj_type>
API resource endpoints.
'''
rsp = []
try:
rsp_data = avi_session.get(path, **kwargs).json()
if 'results' in rsp_data:
rsp = rsp_data['results']
else:
rsp.append(rsp_data)
except ObjectNotFound as e:
display.warning('Resource not found. Please check obj_name/'
'obj_uuid/obj_type are spelled correctly.')
display.v(to_native(e))
except (AviServerError, APIError) as e:
raise AnsibleError(to_native(e))
except Exception as e:
# Generic excption handling for connection failures
raise AnsibleError('Unable to communicate with controller'
'due to error: %s' % to_native(e))
return rsp
class LookupModule(LookupBase):
def run(self, terms, variables=None, avi_credentials=None, **kwargs):
api_creds = AviCredentials(**avi_credentials)
# Create the session using avi_credentials
try:
avi = ApiSession(avi_credentials=api_creds)
except Exception as e:
raise AnsibleError(to_native(e))
# Return an empty list if the object is not found
rsp = []
try:
path = kwargs.pop('obj_type')
except KeyError:
raise AnsibleError("Please pass the obj_type for lookup")
if kwargs.get('obj_name', None):
name = kwargs.pop('obj_name')
try:
display.v("Fetching obj: %s of type: %s" % (name, path))
rsp_data = avi.get_object_by_name(path, name, **kwargs)
if rsp_data:
# Append the return data only if it is not None. i.e object
# with specified name is present
rsp.append(rsp_data)
except AviServerError as e:
raise AnsibleError(to_native(e))
elif kwargs.get('obj_uuid', None):
obj_uuid = kwargs.pop('obj_uuid')
obj_path = "%s/%s" % (path, obj_uuid)
display.v("Fetching obj: %s of type: %s" % (obj_uuid, path))
rsp = _api(avi, obj_path, **kwargs)
else:
display.v("Fetching all objects of type: %s" % path)
rsp = _api(avi, path, **kwargs)
return rsp

View file

@ -0,0 +1,73 @@
# (c) 2013, Bradley Young <young.bradley@gmail.com>
# (c) 2017 Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = '''
lookup: cartesian
short_description: returns the cartesian product of lists
description:
- Takes the input lists and returns a list that represents the product of the input lists.
- 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.
options:
_raw:
description:
- a set of lists
required: True
'''
EXAMPLES = """
- name: Example of the change in the description
debug: msg="{{ [1,2,3]|lookup('cartesian', [a, b])}}"
- name: loops over the cartesian product of the supplied lists
debug: msg="{{item}}"
with_cartesian:
- "{{list1}}"
- "{{list2}}"
- [1,2,3,4,5,6]
"""
RETURN = """
_list:
description:
- list of lists composed of elements of the input lists
type: lists
"""
from itertools import product
from ansible.errors import AnsibleError
from ansible.plugins.lookup import LookupBase
from ansible.utils.listify import listify_lookup_plugin_terms
class LookupModule(LookupBase):
"""
Create the cartesian product of lists
"""
def _lookup_variables(self, terms):
"""
Turn this:
terms == ["1,2,3", "a,b"]
into this:
terms == [[1,2,3], [a, b]]
"""
results = []
for x in terms:
intermediate = listify_lookup_plugin_terms(x, templar=self._templar, loader=self._loader)
results.append(intermediate)
return results
def run(self, terms, variables=None, **kwargs):
terms = self._lookup_variables(terms)
my_list = terms[:]
if len(my_list) == 0:
raise AnsibleError("with_cartesian requires at least one element in each list")
return [self._flatten(x) for x in product(*my_list)]

View file

@ -0,0 +1,101 @@
# (c) 2016, Josh Bradley <jbradley(at)digitalocean.com>
# (c) 2017 Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = '''
lookup: chef_databag
short_description: fetches data from a Chef Databag
description:
- "This is a lookup plugin to provide access to chef data bags using the pychef package.
It interfaces with the chef server api using the same methods to find a knife or chef-client config file to load parameters from,
starting from either the given base path or the current working directory.
The lookup order mirrors the one from Chef, all folders in the base path are walked back looking for the following configuration
file in order : .chef/knife.rb, ~/.chef/knife.rb, /etc/chef/client.rb"
requirements:
- "pychef (python library https://pychef.readthedocs.io `pip install pychef`)"
options:
name:
description:
- Name of the databag
required: True
item:
description:
- Item to fetch
required: True
'''
EXAMPLES = """
- debug:
msg: "{{ lookup('chef_databag', 'name=data_bag_name item=data_bag_item') }}"
"""
RETURN = """
_raw:
description:
- The value from the databag
"""
from ansible.errors import AnsibleError
from ansible.plugins.lookup import LookupBase
from ansible.parsing.splitter import parse_kv
try:
import chef
HAS_CHEF = True
except ImportError as missing_module:
HAS_CHEF = False
class LookupModule(LookupBase):
"""
Chef data bag lookup module
"""
def __init__(self, loader=None, templar=None, **kwargs):
super(LookupModule, self).__init__(loader, templar, **kwargs)
# setup vars for data bag name and data bag item
self.name = None
self.item = None
def parse_kv_args(self, args):
"""
parse key-value style arguments
"""
for arg in ["name", "item"]:
try:
arg_raw = args.pop(arg, None)
if arg_raw is None:
continue
parsed = str(arg_raw)
setattr(self, arg, parsed)
except ValueError:
raise AnsibleError(
"can't parse arg {0}={1} as string".format(arg, arg_raw)
)
if args:
raise AnsibleError(
"unrecognized arguments to with_sequence: %r" % args.keys()
)
def run(self, terms, variables=None, **kwargs):
# Ensure pychef has been loaded
if not HAS_CHEF:
raise AnsibleError('PyChef needed for lookup plugin, try `pip install pychef`')
for term in terms:
self.parse_kv_args(parse_kv(term))
api_object = chef.autoconfigure()
if not isinstance(api_object, chef.api.ChefAPI):
raise AnsibleError('Unable to connect to Chef Server API.')
data_bag_object = chef.DataBag(self.name)
data_bag_item = data_bag_object[self.item]
return [dict(data_bag_item)]

View file

@ -0,0 +1,163 @@
# (c) 2018, Jason Vanderhoof <jason.vanderhoof@cyberark.com>, Oren Ben Meir <oren.benmeir@cyberark.com>
# (c) 2018 Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
lookup: conjur_variable
short_description: Fetch credentials from CyberArk Conjur.
description:
- "Retrieves credentials from Conjur using the controlling host's Conjur identity. Conjur info: U(https://www.conjur.org/)."
requirements:
- 'The controlling host running Ansible has a Conjur identity.
(More: U(https://docs.conjur.org/Latest/en/Content/Get%20Started/key_concepts/machine_identity.html))'
options:
_term:
description: Variable path
required: True
identity_file:
description: Path to the Conjur identity file. The identity file follows the netrc file format convention.
type: path
default: /etc/conjur.identity
required: False
ini:
- section: conjur,
key: identity_file_path
env:
- name: CONJUR_IDENTITY_FILE
config_file:
description: Path to the Conjur configuration file. The configuration file is a YAML file.
type: path
default: /etc/conjur.conf
required: False
ini:
- section: conjur,
key: config_file_path
env:
- name: CONJUR_CONFIG_FILE
'''
EXAMPLES = """
- debug:
msg: "{{ lookup('conjur_variable', '/path/to/secret') }}"
"""
RETURN = """
_raw:
description:
- Value stored in Conjur.
"""
import os.path
from ansible.errors import AnsibleError
from ansible.plugins.lookup import LookupBase
from base64 import b64encode
from netrc import netrc
from os import environ
from time import time
from ansible.module_utils.six.moves.urllib.parse import quote_plus
import yaml
from ansible.module_utils.urls import open_url
from ansible.utils.display import Display
display = Display()
# Load configuration and return as dictionary if file is present on file system
def _load_conf_from_file(conf_path):
display.vvv('conf file: {0}'.format(conf_path))
if not os.path.exists(conf_path):
raise AnsibleError('Conjur configuration file `{0}` was not found on the controlling host'
.format(conf_path))
display.vvvv('Loading configuration from: {0}'.format(conf_path))
with open(conf_path) as f:
config = yaml.safe_load(f.read())
if 'account' not in config or 'appliance_url' not in config:
raise AnsibleError('{0} on the controlling host must contain an `account` and `appliance_url` entry'
.format(conf_path))
return config
# Load identity and return as dictionary if file is present on file system
def _load_identity_from_file(identity_path, appliance_url):
display.vvvv('identity file: {0}'.format(identity_path))
if not os.path.exists(identity_path):
raise AnsibleError('Conjur identity file `{0}` was not found on the controlling host'
.format(identity_path))
display.vvvv('Loading identity from: {0} for {1}'.format(identity_path, appliance_url))
conjur_authn_url = '{0}/authn'.format(appliance_url)
identity = netrc(identity_path)
if identity.authenticators(conjur_authn_url) is None:
raise AnsibleError('The netrc file on the controlling host does not contain an entry for: {0}'
.format(conjur_authn_url))
id, account, api_key = identity.authenticators(conjur_authn_url)
if not id or not api_key:
raise AnsibleError('{0} on the controlling host must contain a `login` and `password` entry for {1}'
.format(identity_path, appliance_url))
return {'id': id, 'api_key': api_key}
# Use credentials to retrieve temporary authorization token
def _fetch_conjur_token(conjur_url, account, username, api_key):
conjur_url = '{0}/authn/{1}/{2}/authenticate'.format(conjur_url, account, username)
display.vvvv('Authentication request to Conjur at: {0}, with user: {1}'.format(conjur_url, username))
response = open_url(conjur_url, data=api_key, method='POST')
code = response.getcode()
if code != 200:
raise AnsibleError('Failed to authenticate as \'{0}\' (got {1} response)'
.format(username, code))
return response.read()
# Retrieve Conjur variable using the temporary token
def _fetch_conjur_variable(conjur_variable, token, conjur_url, account):
token = b64encode(token)
headers = {'Authorization': 'Token token="{0}"'.format(token)}
display.vvvv('Header: {0}'.format(headers))
url = '{0}/secrets/{1}/variable/{2}'.format(conjur_url, account, quote_plus(conjur_variable))
display.vvvv('Conjur Variable URL: {0}'.format(url))
response = open_url(url, headers=headers, method='GET')
if response.getcode() == 200:
display.vvvv('Conjur variable {0} was successfully retrieved'.format(conjur_variable))
return [response.read()]
if response.getcode() == 401:
raise AnsibleError('Conjur request has invalid authorization credentials')
if response.getcode() == 403:
raise AnsibleError('The controlling host\'s Conjur identity does not have authorization to retrieve {0}'
.format(conjur_variable))
if response.getcode() == 404:
raise AnsibleError('The variable {0} does not exist'.format(conjur_variable))
return {}
class LookupModule(LookupBase):
def run(self, terms, variables=None, **kwargs):
conf_file = self.get_option('config_file')
conf = _load_conf_from_file(conf_file)
identity_file = self.get_option('identity_file')
identity = _load_identity_from_file(identity_file, conf['appliance_url'])
token = _fetch_conjur_token(conf['appliance_url'], conf['account'], identity['id'], identity['api_key'])
return _fetch_conjur_variable(terms[0], token, conf['appliance_url'], conf['account'])

181
plugins/lookup/consul_kv.py Normal file
View file

@ -0,0 +1,181 @@
# (c) 2015, Steve Gargan <steve.gargan@gmail.com>
# (c) 2017 Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
lookup: consul_kv
short_description: Fetch metadata from a Consul key value store.
description:
- Lookup metadata for a playbook from the key value store in a Consul cluster.
Values can be easily set in the kv store with simple rest commands
- C(curl -X PUT -d 'some-value' http://localhost:8500/v1/kv/ansible/somedata)
requirements:
- 'python-consul python library U(https://python-consul.readthedocs.io/en/latest/#installation)'
options:
_raw:
description: List of key(s) to retrieve.
type: list
required: True
recurse:
type: boolean
description: If true, will retrieve all the values that have the given key as prefix.
default: False
index:
description:
- If the key has a value with the specified index then this is returned allowing access to historical values.
datacenter:
description:
- Retrieve the key from a consul datatacenter other than the default for the consul host.
token:
description: The acl token to allow access to restricted values.
host:
default: localhost
description:
- The target to connect to, must be a resolvable address.
Will be determined from C(ANSIBLE_CONSUL_URL) if that is set.
- "C(ANSIBLE_CONSUL_URL) should look like this: C(https://my.consul.server:8500)"
env:
- name: ANSIBLE_CONSUL_URL
ini:
- section: lookup_consul
key: host
port:
description:
- The port of the target host to connect to.
- If you use C(ANSIBLE_CONSUL_URL) this value will be used from there.
default: 8500
scheme:
default: http
description:
- Whether to use http or https.
- If you use C(ANSIBLE_CONSUL_URL) this value will be used from there.
validate_certs:
default: True
description: Whether to verify the ssl connection or not.
env:
- name: ANSIBLE_CONSUL_VALIDATE_CERTS
ini:
- section: lookup_consul
key: validate_certs
client_cert:
description: The client cert to verify the ssl connection.
env:
- name: ANSIBLE_CONSUL_CLIENT_CERT
ini:
- section: lookup_consul
key: client_cert
'''
EXAMPLES = """
- debug:
msg: 'key contains {{item}}'
with_consul_kv:
- 'key/to/retrieve'
- name: Parameters can be provided after the key be more specific about what to retrieve
debug:
msg: 'key contains {{item}}'
with_consul_kv:
- 'key/to recurse=true token=E6C060A9-26FB-407A-B83E-12DDAFCB4D98'
- name: retrieving a KV from a remote cluster on non default port
debug:
msg: "{{ lookup('consul_kv', 'my/key', host='10.10.10.10', port='2000') }}"
"""
RETURN = """
_raw:
description:
- Value(s) stored in consul.
"""
import os
from ansible.module_utils.six.moves.urllib.parse import urlparse
from ansible.errors import AnsibleError, AnsibleAssertionError
from ansible.plugins.lookup import LookupBase
from ansible.module_utils._text import to_text
try:
import consul
HAS_CONSUL = True
except ImportError as e:
HAS_CONSUL = False
class LookupModule(LookupBase):
def run(self, terms, variables=None, **kwargs):
if not HAS_CONSUL:
raise AnsibleError(
'python-consul is required for consul_kv lookup. see http://python-consul.readthedocs.org/en/latest/#installation')
values = []
try:
for term in terms:
params = self.parse_params(term)
try:
url = os.environ['ANSIBLE_CONSUL_URL']
validate_certs = os.environ['ANSIBLE_CONSUL_VALIDATE_CERTS'] or True
client_cert = os.environ['ANSIBLE_CONSUL_CLIENT_CERT'] or None
u = urlparse(url)
consul_api = consul.Consul(host=u.hostname, port=u.port, scheme=u.scheme, verify=validate_certs,
cert=client_cert)
except KeyError:
port = kwargs.get('port', '8500')
host = kwargs.get('host', 'localhost')
scheme = kwargs.get('scheme', 'http')
validate_certs = kwargs.get('validate_certs', True)
client_cert = kwargs.get('client_cert', None)
consul_api = consul.Consul(host=host, port=port, scheme=scheme, verify=validate_certs,
cert=client_cert)
results = consul_api.kv.get(params['key'],
token=params['token'],
index=params['index'],
recurse=params['recurse'],
dc=params['datacenter'])
if results[1]:
# responds with a single or list of result maps
if isinstance(results[1], list):
for r in results[1]:
values.append(to_text(r['Value']))
else:
values.append(to_text(results[1]['Value']))
except Exception as e:
raise AnsibleError(
"Error locating '%s' in kv store. Error was %s" % (term, e))
return values
def parse_params(self, term):
params = term.split(' ')
paramvals = {
'key': params[0],
'token': None,
'recurse': False,
'index': None,
'datacenter': None
}
# parameters specified?
try:
for param in params[1:]:
if param and len(param) > 0:
name, value = param.split('=')
if name not in paramvals:
raise AnsibleAssertionError("%s not a valid consul lookup parameter" % name)
paramvals[name] = value
except (ValueError, AssertionError) as e:
raise AnsibleError(e)
return paramvals

118
plugins/lookup/credstash.py Normal file
View file

@ -0,0 +1,118 @@
# (c) 2015, Ensighten <infra@ensighten.com>
# (c) 2017 Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = '''
lookup: credstash
short_description: retrieve secrets from Credstash on AWS
requirements:
- credstash (python library)
description:
- "Credstash is a small utility for managing secrets using AWS's KMS and DynamoDB: https://github.com/fugue/credstash"
options:
_terms:
description: term or list of terms to lookup in the credit store
type: list
required: True
table:
description: name of the credstash table to query
default: 'credential-store'
required: True
version:
description: Credstash version
region:
description: AWS region
profile_name:
description: AWS profile to use for authentication
env:
- name: AWS_PROFILE
aws_access_key_id:
description: AWS access key ID
env:
- name: AWS_ACCESS_KEY_ID
aws_secret_access_key:
description: AWS access key
env:
- name: AWS_SECRET_ACCESS_KEY
aws_session_token:
description: AWS session token
env:
- name: AWS_SESSION_TOKEN
'''
EXAMPLES = """
- name: first use credstash to store your secrets
shell: credstash put my-github-password secure123
- name: "Test credstash lookup plugin -- get my github password"
debug: msg="Credstash lookup! {{ lookup('credstash', 'my-github-password') }}"
- name: "Test credstash lookup plugin -- get my other password from us-west-1"
debug: msg="Credstash lookup! {{ lookup('credstash', 'my-other-password', region='us-west-1') }}"
- name: "Test credstash lookup plugin -- get the company's github password"
debug: msg="Credstash lookup! {{ lookup('credstash', 'company-github-password', table='company-passwords') }}"
- name: Example play using the 'context' feature
hosts: localhost
vars:
context:
app: my_app
environment: production
tasks:
- name: "Test credstash lookup plugin -- get the password with a context passed as a variable"
debug: msg="{{ lookup('credstash', 'some-password', context=context) }}"
- name: "Test credstash lookup plugin -- get the password with a context defined here"
debug: msg="{{ lookup('credstash', 'some-password', context=dict(app='my_app', environment='production')) }}"
"""
RETURN = """
_raw:
description:
- value(s) stored in Credstash
"""
import os
from ansible.errors import AnsibleError
from ansible.plugins.lookup import LookupBase
CREDSTASH_INSTALLED = False
try:
import credstash
CREDSTASH_INSTALLED = True
except ImportError:
CREDSTASH_INSTALLED = False
class LookupModule(LookupBase):
def run(self, terms, variables, **kwargs):
if not CREDSTASH_INSTALLED:
raise AnsibleError('The credstash lookup plugin requires credstash to be installed.')
ret = []
for term in terms:
try:
version = kwargs.pop('version', '')
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:
raise AnsibleError('Key {0} not found'.format(term))
except Exception as e:
raise AnsibleError('Encountered exception while fetching {0}: {1}'.format(term, e))
ret.append(val)
return ret

View file

@ -0,0 +1,179 @@
# (c) 2017, Edward Nunez <edward.nunez@cyberark.com>
# (c) 2017 Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = '''
lookup: cyberarkpassword
short_description: get secrets from CyberArk AIM
requirements:
- CyberArk AIM tool installed
description:
- Get secrets from CyberArk AIM.
options :
_command:
description: Cyberark CLI utility.
env:
- name: AIM_CLIPASSWORDSDK_CMD
default: '/opt/CARKaim/sdk/clipasswordsdk'
appid:
description: Defines the unique ID of the application that is issuing the password request.
required: True
query:
description: Describes the filter criteria for the password retrieval.
required: True
output:
description:
- Specifies the desired output fields separated by commas.
- "They could be: Password, PassProps.<property>, PasswordChangeInProcess"
default: 'password'
_extra:
description: for extra_parms values please check parameters for clipasswordsdk in CyberArk's "Credential Provider and ASCP Implementation Guide"
note:
- For Ansible on windows, please change the -parameters (-p, -d, and -o) to /parameters (/p, /d, and /o) and change the location of CLIPasswordSDK.exe
'''
EXAMPLES = """
- name: passing options to the lookup
debug: msg={{ lookup("cyberarkpassword", cyquery)}}
vars:
cyquery:
appid: "app_ansible"
query: "safe=CyberArk_Passwords;folder=root;object=AdminPass"
output: "Password,PassProps.UserName,PassProps.Address,PasswordChangeInProcess"
- name: used in a loop
debug: msg={{item}}
with_cyberarkpassword:
appid: 'app_ansible'
query: 'safe=CyberArk_Passwords;folder=root;object=AdminPass'
output: 'Password,PassProps.UserName,PassProps.Address,PasswordChangeInProcess'
"""
RETURN = """
password:
description:
- The actual value stored
passprops:
description: properties assigned to the entry
type: dictionary
passwordchangeinprocess:
description: did the password change?
"""
import os
import subprocess
from subprocess import PIPE
from subprocess import Popen
from ansible.errors import AnsibleError
from ansible.plugins.lookup import LookupBase
from ansible.parsing.splitter import parse_kv
from ansible.module_utils._text import to_bytes, to_text, to_native
from ansible.utils.display import Display
display = Display()
CLIPASSWORDSDK_CMD = os.getenv('AIM_CLIPASSWORDSDK_CMD', '/opt/CARKaim/sdk/clipasswordsdk')
class CyberarkPassword:
def __init__(self, appid=None, query=None, output=None, **kwargs):
self.appid = appid
self.query = query
self.output = output
# Support for Generic parameters to be able to specify
# FailRequestOnPasswordChange, Queryformat, Reason, etc.
self.extra_parms = []
for key, value in kwargs.items():
self.extra_parms.append('-p')
self.extra_parms.append("%s=%s" % (key, value))
if self.appid is None:
raise AnsibleError("CyberArk Error: No Application ID specified")
if self.query is None:
raise AnsibleError("CyberArk Error: No Vault query specified")
if self.output is None:
# If no output is specified, return at least the password
self.output = "password"
else:
# To avoid reference issues/confusion to values, all
# output 'keys' will be in lowercase.
self.output = self.output.lower()
self.b_delimiter = b"@#@" # Known delimiter to split output results
def get(self):
result_dict = {}
try:
all_parms = [
CLIPASSWORDSDK_CMD,
'GetPassword',
'-p', 'AppDescs.AppID=%s' % self.appid,
'-p', 'Query=%s' % self.query,
'-o', self.output,
'-d', self.b_delimiter]
all_parms.extend(self.extra_parms)
b_credential = b""
b_all_params = [to_bytes(v) for v in all_parms]
tmp_output, tmp_error = Popen(b_all_params, stdout=PIPE, stderr=PIPE, stdin=PIPE).communicate()
if tmp_output:
b_credential = to_bytes(tmp_output)
if tmp_error:
raise AnsibleError("ERROR => %s " % (tmp_error))
if b_credential and b_credential.endswith(b'\n'):
b_credential = b_credential[:-1]
output_names = self.output.split(",")
output_values = b_credential.split(self.b_delimiter)
for i in range(len(output_names)):
if output_names[i].startswith("passprops."):
if "passprops" not in result_dict:
result_dict["passprops"] = {}
output_prop_name = output_names[i][10:]
result_dict["passprops"][output_prop_name] = to_native(output_values[i])
else:
result_dict[output_names[i]] = to_native(output_values[i])
except subprocess.CalledProcessError as e:
raise AnsibleError(e.output)
except OSError as e:
raise AnsibleError("ERROR - AIM not installed or clipasswordsdk not in standard location. ERROR=(%s) => %s " % (to_text(e.errno), e.strerror))
return [result_dict]
class LookupModule(LookupBase):
"""
USAGE:
"""
def run(self, terms, variables=None, **kwargs):
display.vvvv("%s" % terms)
if isinstance(terms, list):
return_values = []
for term in terms:
display.vvvv("Term: %s" % term)
cyberark_conn = CyberarkPassword(**term)
return_values.append(cyberark_conn.get())
return return_values
else:
cyberark_conn = CyberarkPassword(**terms)
result = cyberark_conn.get()
return result

301
plugins/lookup/dig.py Normal file
View file

@ -0,0 +1,301 @@
# (c) 2015, Jan-Piet Mens <jpmens(at)gmail.com>
# (c) 2017 Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = '''
lookup: dig
author: Jan-Piet Mens (@jpmens) <jpmens(at)gmail.com>
short_description: query DNS using the dnspython library
requirements:
- dnspython (python library, http://www.dnspython.org/)
description:
- The dig lookup runs queries against DNS servers to retrieve DNS records for a specific name (FQDN - fully qualified domain name).
It is possible to lookup any DNS record in this manner.
- There is a couple of different syntaxes that can be used to specify what record should be retrieved, and for which name.
It is also possible to explicitly specify the DNS server(s) to use for lookups.
- In its simplest form, the dig lookup plugin can be used to retrieve an IPv4 address (DNS A record) associated with FQDN
- 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.
- 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
over which you can iterate later on.
- 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.
This needs to be passed-in as an additional parameter to the lookup
options:
_terms:
description: domain(s) to query
qtype:
description: record type to query
default: 'A'
choices: [A, ALL, AAAA, CNAME, DNAME, DLV, DNSKEY, DS, HINFO, LOC, MX, NAPTR, NS, NSEC3PARAM, PTR, RP, RRSIG, SOA, SPF, SRV, SSHFP, TLSA, TXT]
flat:
description: If 0 each record is returned as a dictionary, otherwise a string
default: 1
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.
- While the 'dig' lookup plugin supports anything which dnspython supports out of the box, only a subset can be converted into a dictionary.
- If you need to obtain the AAAA record (IPv6 address), you must specify the record type explicitly.
Syntax for specifying the record type is shown in the examples below.
- The trailing dot in most of the examples listed is purely optional, but is specified for completeness/correctness sake.
'''
EXAMPLES = """
- name: Simple A record (IPV4 address) lookup for example.com
debug: msg="{{ lookup('dig', 'example.com.')}}"
- name: "The TXT record for example.org."
debug: msg="{{ lookup('dig', 'example.org.', 'qtype=TXT') }}"
- name: "The TXT record for example.org, alternative syntax."
debug: msg="{{ lookup('dig', 'example.org./TXT') }}"
- name: use in a loop
debug: msg="MX record for gmail.com {{ item }}"
with_items: "{{ lookup('dig', 'gmail.com./MX', wantlist=True) }}"
- debug: msg="Reverse DNS for 192.0.2.5 is {{ lookup('dig', '192.0.2.5/PTR') }}"
- debug: msg="Reverse DNS for 192.0.2.5 is {{ lookup('dig', '5.2.0.192.in-addr.arpa./PTR') }}"
- debug: msg="Reverse DNS for 192.0.2.5 is {{ lookup('dig', '5.2.0.192.in-addr.arpa.', 'qtype=PTR') }}"
- debug: msg="Querying 198.51.100.23 for IPv4 address for example.com. produces {{ lookup('dig', 'example.com', '@198.51.100.23') }}"
- debug: msg="XMPP service for gmail.com. is available at {{ item.target }} on port {{ item.port }}"
with_items: "{{ lookup('dig', '_xmpp-server._tcp.gmail.com./SRV', 'flat=0', wantlist=True) }}"
"""
RETURN = """
_list:
description:
- list of composed strings or dictonaries with key and value
If a dictionary, fields shows the keys returned depending on query type
fields:
ALL: owner, ttl, type
A: address
AAAA: address
CNAME: target
DNAME: target
DLV: algorithm, digest_type, key_tag, digest
DNSKEY: flags, algorithm, protocol, key
DS: algorithm, digest_type, key_tag, digest
HINFO: cpu, os
LOC: latitude, longitude, altitude, size, horizontal_precision, vertical_precision
MX: preference, exchange
NAPTR: order, preference, flags, service, regexp, replacement
NS: target
NSEC3PARAM: algorithm, flags, iterations, salt
PTR: target
RP: mbox, txt
SOA: mname, rname, serial, refresh, retry, expire, minimum
SPF: strings
SRV: priority, weight, port, target
SSHFP: algorithm, fp_type, fingerprint
TLSA: usage, selector, mtype, cert
TXT: strings
"""
from ansible.errors import AnsibleError
from ansible.plugins.lookup import LookupBase
from ansible.module_utils._text import to_native
import socket
try:
import dns.exception
import dns.name
import dns.resolver
import dns.reversename
import dns.rdataclass
from dns.rdatatype import (A, AAAA, CNAME, DLV, DNAME, DNSKEY, DS, HINFO, LOC,
MX, NAPTR, NS, NSEC3PARAM, PTR, RP, SOA, SPF, SRV, SSHFP, TLSA, TXT)
HAVE_DNS = True
except ImportError:
HAVE_DNS = False
def make_rdata_dict(rdata):
''' While the 'dig' lookup plugin supports anything which dnspython supports
out of the box, the following supported_types list describes which
DNS query types we can convert to a dict.
Note: adding support for RRSIG is hard work. :)
'''
supported_types = {
A: ['address'],
AAAA: ['address'],
CNAME: ['target'],
DNAME: ['target'],
DLV: ['algorithm', 'digest_type', 'key_tag', 'digest'],
DNSKEY: ['flags', 'algorithm', 'protocol', 'key'],
DS: ['algorithm', 'digest_type', 'key_tag', 'digest'],
HINFO: ['cpu', 'os'],
LOC: ['latitude', 'longitude', 'altitude', 'size', 'horizontal_precision', 'vertical_precision'],
MX: ['preference', 'exchange'],
NAPTR: ['order', 'preference', 'flags', 'service', 'regexp', 'replacement'],
NS: ['target'],
NSEC3PARAM: ['algorithm', 'flags', 'iterations', 'salt'],
PTR: ['target'],
RP: ['mbox', 'txt'],
# RRSIG: ['algorithm', 'labels', 'original_ttl', 'expiration', 'inception', 'signature'],
SOA: ['mname', 'rname', 'serial', 'refresh', 'retry', 'expire', 'minimum'],
SPF: ['strings'],
SRV: ['priority', 'weight', 'port', 'target'],
SSHFP: ['algorithm', 'fp_type', 'fingerprint'],
TLSA: ['usage', 'selector', 'mtype', 'cert'],
TXT: ['strings'],
}
rd = {}
if rdata.rdtype in supported_types:
fields = supported_types[rdata.rdtype]
for f in fields:
val = rdata.__getattribute__(f)
if isinstance(val, dns.name.Name):
val = dns.name.Name.to_text(val)
if rdata.rdtype == DLV and f == 'digest':
val = dns.rdata._hexify(rdata.digest).replace(' ', '')
if rdata.rdtype == DS and f == 'digest':
val = dns.rdata._hexify(rdata.digest).replace(' ', '')
if rdata.rdtype == DNSKEY and f == 'key':
val = dns.rdata._base64ify(rdata.key).replace(' ', '')
if rdata.rdtype == NSEC3PARAM and f == 'salt':
val = dns.rdata._hexify(rdata.salt).replace(' ', '')
if rdata.rdtype == SSHFP and f == 'fingerprint':
val = dns.rdata._hexify(rdata.fingerprint).replace(' ', '')
if rdata.rdtype == TLSA and f == 'cert':
val = dns.rdata._hexify(rdata.cert).replace(' ', '')
rd[f] = val
return rd
# ==============================================================
# dig: Lookup DNS records
#
# --------------------------------------------------------------
class LookupModule(LookupBase):
def run(self, terms, variables=None, **kwargs):
'''
terms contains a string with things to `dig' for. We support the
following formats:
example.com # A record
example.com qtype=A # same
example.com/TXT # specific qtype
example.com qtype=txt # same
192.0.2.23/PTR # reverse PTR
^^ shortcut for 23.2.0.192.in-addr.arpa/PTR
example.net/AAAA @nameserver # query specified server
^^^ can be comma-sep list of names/addresses
... flat=0 # returns a dict; default is 1 == string
'''
if HAVE_DNS is False:
raise AnsibleError("The dig lookup requires the python 'dnspython' library and it is not installed")
# Create Resolver object so that we can set NS if necessary
myres = dns.resolver.Resolver(configure=True)
edns_size = 4096
myres.use_edns(0, ednsflags=dns.flags.DO, payload=edns_size)
domain = None
qtype = 'A'
flat = True
rdclass = dns.rdataclass.from_text('IN')
for t in terms:
if t.startswith('@'): # e.g. "@10.0.1.2,192.0.2.1" is ok.
nsset = t[1:].split(',')
for ns in nsset:
nameservers = []
# Check if we have a valid IP address. If so, use that, otherwise
# try to resolve name to address using system's resolver. If that
# fails we bail out.
try:
socket.inet_aton(ns)
nameservers.append(ns)
except Exception:
try:
nsaddr = dns.resolver.query(ns)[0].address
nameservers.append(nsaddr)
except Exception as e:
raise AnsibleError("dns lookup NS: %s" % to_native(e))
myres.nameservers = nameservers
continue
if '=' in t:
try:
opt, arg = t.split('=')
except Exception:
pass
if opt == 'qtype':
qtype = arg.upper()
elif opt == 'flat':
flat = int(arg)
elif opt == 'class':
try:
rdclass = dns.rdataclass.from_text(arg)
except Exception as e:
raise AnsibleError("dns lookup illegal CLASS: %s" % to_native(e))
continue
if '/' in t:
try:
domain, qtype = t.split('/')
except Exception:
domain = t
else:
domain = t
# print "--- domain = {0} qtype={1} rdclass={2}".format(domain, qtype, rdclass)
ret = []
if qtype.upper() == 'PTR':
try:
n = dns.reversename.from_address(domain)
domain = n.to_text()
except dns.exception.SyntaxError:
pass
except Exception as e:
raise AnsibleError("dns.reversename unhandled exception %s" % to_native(e))
try:
answers = myres.query(domain, qtype, rdclass=rdclass)
for rdata in answers:
s = rdata.to_text()
if qtype.upper() == 'TXT':
s = s[1:-1] # Strip outside quotes on TXT rdata
if flat:
ret.append(s)
else:
try:
rd = make_rdata_dict(rdata)
rd['owner'] = answers.canonical_name.to_text()
rd['type'] = dns.rdatatype.to_text(rdata.rdtype)
rd['ttl'] = answers.rrset.ttl
rd['class'] = dns.rdataclass.to_text(rdata.rdclass)
ret.append(rd)
except Exception as e:
ret.append(str(e))
except dns.resolver.NXDOMAIN:
ret.append('NXDOMAIN')
except dns.resolver.NoAnswer:
ret.append("")
except dns.resolver.Timeout:
ret.append('')
except dns.exception.DNSException as e:
raise AnsibleError("dns.resolver unhandled exception %s" % to_native(e))
return ret

93
plugins/lookup/dnstxt.py Normal file
View file

@ -0,0 +1,93 @@
# (c) 2012, Jan-Piet Mens <jpmens(at)gmail.com>
# (c) 2017 Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = '''
lookup: dnstxt
author: Jan-Piet Mens (@jpmens) <jpmens(at)gmail.com>
short_description: query a domain(s)'s DNS txt fields
requirements:
- dns/dns.resolver (python library)
description:
- Uses a python library to return the DNS TXT record for a domain.
options:
_terms:
description: domain or list of domains to query TXT records from
required: True
type: list
'''
EXAMPLES = """
- name: show txt entry
debug: msg="{{lookup('dnstxt', ['test.example.com'])}}"
- name: iterate over txt entries
debug: msg="{{item}}"
with_dnstxt:
- 'test.example.com'
- 'other.example.com'
- 'last.example.com'
- name: iterate of a comma delimited DNS TXT entry
debug: msg="{{item}}"
with_dnstxt: "{{lookup('dnstxt', ['test.example.com']).split(',')}}"
"""
RETURN = """
_list:
description:
- values returned by the DNS TXT record.
type: list
"""
HAVE_DNS = False
try:
import dns.resolver
from dns.exception import DNSException
HAVE_DNS = True
except ImportError:
pass
from ansible.errors import AnsibleError
from ansible.module_utils._text import to_native
from ansible.plugins.lookup import LookupBase
# ==============================================================
# DNSTXT: DNS TXT records
#
# key=domainname
# TODO: configurable resolver IPs
# --------------------------------------------------------------
class LookupModule(LookupBase):
def run(self, terms, variables=None, **kwargs):
if HAVE_DNS is False:
raise AnsibleError("Can't LOOKUP(dnstxt): module dns.resolver is not installed")
ret = []
for term in terms:
domain = term.split()[0]
string = []
try:
answers = dns.resolver.query(domain, 'TXT')
for rdata in answers:
s = rdata.to_text()
string.append(s[1:-1]) # Strip outside quotes on TXT rdata
except dns.resolver.NXDOMAIN:
string = 'NXDOMAIN'
except dns.resolver.Timeout:
string = ''
except dns.resolver.NoAnswer:
string = ''
except DNSException as e:
raise AnsibleError("dns.resolver unhandled exception %s" % to_native(e))
ret.append(''.join(string))
return ret

177
plugins/lookup/etcd.py Normal file
View file

@ -0,0 +1,177 @@
# (c) 2013, Jan-Piet Mens <jpmens(at)gmail.com>
# (m) 2016, Mihai Moldovanu <mihaim@tfm.ro>
# (m) 2017, Juan Manuel Parrilla <jparrill@redhat.com>
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = '''
author:
- Jan-Piet Mens (@jpmens)
lookup: etcd
short_description: get info from an etcd server
description:
- Retrieves data from an etcd server
options:
_terms:
description:
- the list of keys to lookup on the etcd server
type: list
elements: string
required: True
url:
description:
- Environment variable with the url for the etcd server
default: 'http://127.0.0.1:4001'
env:
- name: ANSIBLE_ETCD_URL
version:
description:
- Environment variable with the etcd protocol version
default: 'v1'
env:
- name: ANSIBLE_ETCD_VERSION
validate_certs:
description:
- toggle checking that the ssl certificates are valid, you normally only want to turn this off with self-signed certs.
default: True
type: boolean
'''
EXAMPLES = '''
- name: "a value from a locally running etcd"
debug: msg={{ lookup('etcd', 'foo/bar') }}
- name: "values from multiple folders on a locally running etcd"
debug: msg={{ lookup('etcd', 'foo', 'bar', 'baz') }}
- name: "since Ansible 2.5 you can set server options inline"
debug: msg="{{ lookup('etcd', 'foo', version='v2', url='http://192.168.0.27:4001') }}"
'''
RETURN = '''
_raw:
description:
- list of values associated with input keys
type: list
elements: strings
'''
import json
from ansible.plugins.lookup import LookupBase
from ansible.module_utils.urls import open_url
# this can be made configurable, not should not use ansible.cfg
#
# Made module configurable from playbooks:
# If etcd v2 running on host 192.168.1.21 on port 2379
# we can use the following in a playbook to retrieve /tfm/network/config key
#
# - debug: msg={{lookup('etcd','/tfm/network/config', url='http://192.168.1.21:2379' , version='v2')}}
#
# Example Output:
#
# TASK [debug] *******************************************************************
# ok: [localhost] => {
# "msg": {
# "Backend": {
# "Type": "vxlan"
# },
# "Network": "172.30.0.0/16",
# "SubnetLen": 24
# }
# }
#
#
#
#
class Etcd:
def __init__(self, url, version, validate_certs):
self.url = url
self.version = version
self.baseurl = '%s/%s/keys' % (self.url, self.version)
self.validate_certs = validate_certs
def _parse_node(self, node):
# This function will receive all etcd tree,
# if the level requested has any node, the recursion starts
# create a list in the dir variable and it is passed to the
# recursive function, and so on, if we get a variable,
# the function will create a key-value at this level and
# undoing the loop.
path = {}
if node.get('dir', False):
for n in node.get('nodes', []):
path[n['key'].split('/')[-1]] = self._parse_node(n)
else:
path = node['value']
return path
def get(self, key):
url = "%s/%s?recursive=true" % (self.baseurl, key)
data = None
value = {}
try:
r = open_url(url, validate_certs=self.validate_certs)
data = r.read()
except Exception:
return None
try:
# I will not support Version 1 of etcd for folder parsing
item = json.loads(data)
if self.version == 'v1':
# When ETCD are working with just v1
if 'value' in item:
value = item['value']
else:
if 'node' in item:
# When a usual result from ETCD
value = self._parse_node(item['node'])
if 'errorCode' in item:
# Here return an error when an unknown entry responds
value = "ENOENT"
except Exception:
raise
return value
class LookupModule(LookupBase):
def run(self, terms, variables, **kwargs):
self.set_options(var_options=variables, direct=kwargs)
validate_certs = self.get_option('validate_certs')
url = self.get_option('url')
version = self.get_option('version')
etcd = Etcd(url=url, version=version, validate_certs=validate_certs)
ret = []
for term in terms:
key = term.split()[0]
value = etcd.get(key)
ret.append(value)
return ret

196
plugins/lookup/filetree.py Normal file
View file

@ -0,0 +1,196 @@
# (c) 2016 Dag Wieers <dag@wieers.com>
# (c) 2017 Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = '''
lookup: filetree
author: Dag Wieers (@dagwieers) <dag@wieers.com>
short_description: recursively match all files in a directory tree
description:
- This lookup enables you to template a complete tree of files on a target system while retaining permissions and ownership.
- Supports directories, files and symlinks, including SELinux and other file properties.
- If you provide more than one path, it will implement a first_found logic, and will not process entries it already processed in previous paths.
This enables merging different trees in order of importance, or add role_vars to specific paths to influence different instances of the same role.
options:
_terms:
description: path(s) of files to read
required: True
'''
EXAMPLES = """
- name: Create directories
file:
path: /web/{{ item.path }}
state: directory
mode: '{{ item.mode }}'
with_filetree: web/
when: item.state == 'directory'
- name: Template files (explicitly skip directories in order to use the 'src' attribute)
template:
src: '{{ item.src }}'
dest: /web/{{ item.path }}
mode: '{{ item.mode }}'
with_filetree: web/
when: item.state == 'file'
- name: Recreate symlinks
file:
src: '{{ item.src }}'
dest: /web/{{ item.path }}
state: link
force: yes
mode: '{{ item.mode }}'
with_filetree: web/
when: item.state == 'link'
"""
RETURN = """
_raw:
description: list of dictionaries with file information
contains:
src:
description:
- full path to file
- not returned when C(item.state) is set to C(directory)
root:
description: allows filtering by original location
path:
description: contains the relative path to root
mode:
description: TODO
state:
description: TODO
owner:
description: TODO
group:
description: TODO
seuser:
description: TODO
serole:
description: TODO
setype:
description: TODO
selevel:
description: TODO
uid:
description: TODO
gid:
description: TODO
size:
description: TODO
mtime:
description: TODO
ctime:
description: TODO
"""
import os
import pwd
import grp
import stat
HAVE_SELINUX = False
try:
import selinux
HAVE_SELINUX = True
except ImportError:
pass
from ansible.plugins.lookup import LookupBase
from ansible.module_utils._text import to_native, to_text
from ansible.utils.display import Display
display = Display()
# If selinux fails to find a default, return an array of None
def selinux_context(path):
context = [None, None, None, None]
if HAVE_SELINUX and selinux.is_selinux_enabled():
try:
# note: the selinux module uses byte strings on python2 and text
# strings on python3
ret = selinux.lgetfilecon_raw(to_native(path))
except OSError:
return context
if ret[0] != -1:
# Limit split to 4 because the selevel, the last in the list,
# may contain ':' characters
context = ret[1].split(':', 3)
return context
def file_props(root, path):
''' Returns dictionary with file properties, or return None on failure '''
abspath = os.path.join(root, path)
try:
st = os.lstat(abspath)
except OSError as e:
display.warning('filetree: Error using stat() on path %s (%s)' % (abspath, e))
return None
ret = dict(root=root, path=path)
if stat.S_ISLNK(st.st_mode):
ret['state'] = 'link'
ret['src'] = os.readlink(abspath)
elif stat.S_ISDIR(st.st_mode):
ret['state'] = 'directory'
elif stat.S_ISREG(st.st_mode):
ret['state'] = 'file'
ret['src'] = abspath
else:
display.warning('filetree: Error file type of %s is not supported' % abspath)
return None
ret['uid'] = st.st_uid
ret['gid'] = st.st_gid
try:
ret['owner'] = pwd.getpwuid(st.st_uid).pw_name
except KeyError:
ret['owner'] = st.st_uid
try:
ret['group'] = to_text(grp.getgrgid(st.st_gid).gr_name)
except KeyError:
ret['group'] = st.st_gid
ret['mode'] = '0%03o' % (stat.S_IMODE(st.st_mode))
ret['size'] = st.st_size
ret['mtime'] = st.st_mtime
ret['ctime'] = st.st_ctime
if HAVE_SELINUX and selinux.is_selinux_enabled() == 1:
context = selinux_context(abspath)
ret['seuser'] = context[0]
ret['serole'] = context[1]
ret['setype'] = context[2]
ret['selevel'] = context[3]
return ret
class LookupModule(LookupBase):
def run(self, terms, variables=None, **kwargs):
basedir = self.get_basedir(variables)
ret = []
for term in terms:
term_file = os.path.basename(term)
dwimmed_path = self._loader.path_dwim_relative(basedir, 'files', os.path.dirname(term))
path = os.path.join(dwimmed_path, term_file)
display.debug("Walking '{0}'".format(path))
for root, dirs, files in os.walk(path, topdown=True):
for entry in dirs + files:
relpath = os.path.relpath(os.path.join(root, entry), path)
# Skip if relpath was already processed (from another root)
if relpath not in [entry['path'] for entry in ret]:
props = file_props(path, relpath)
if props is not None:
display.debug(" found '{0}'".format(os.path.join(path, relpath)))
ret.append(props)
return ret

View file

@ -0,0 +1,83 @@
# (c) 2013, Serge van Ginderachter <serge@vanginderachter.be>
# (c) 2017 Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = '''
lookup: flattened
author: Serge van Ginderachter <serge@vanginderachter.be>
short_description: return single list completely flattened
description:
- given one or more lists, this lookup will flatten any list elements found recursively until only 1 list is left.
options:
_terms:
description: lists to flatten
required: True
notes:
- unlike 'items' which only flattens 1 level, this plugin will continue to flatten until it cannot find lists anymore.
- aka highlander plugin, there can only be one (list).
'''
EXAMPLES = """
- name: "'unnest' all elements into single list"
debug: msg="all in one list {{lookup('flattened', [1,2,3,[5,6]], [a,b,c], [[5,6,1,3], [34,a,b,c]])}}"
"""
RETURN = """
_raw:
description:
- flattened list
type: list
"""
from ansible.errors import AnsibleError
from ansible.module_utils.six import string_types
from ansible.plugins.lookup import LookupBase
from ansible.utils.listify import listify_lookup_plugin_terms
class LookupModule(LookupBase):
def _check_list_of_one_list(self, term):
# make sure term is not a list of one (list of one..) item
# return the final non list item if so
if isinstance(term, list) and len(term) == 1:
term = term[0]
if isinstance(term, list):
term = self._check_list_of_one_list(term)
return term
def _do_flatten(self, terms, variables):
ret = []
for term in terms:
term = self._check_list_of_one_list(term)
if term == 'None' or term == 'null':
# ignore undefined items
break
if isinstance(term, string_types):
# convert a variable to a list
term2 = listify_lookup_plugin_terms(term, templar=self._templar, loader=self._loader)
# but avoid converting a plain string to a list of one string
if term2 != [term]:
term = term2
if isinstance(term, list):
# if it's a list, check recursively for items that are a list
term = self._do_flatten(term, variables)
ret.extend(term)
else:
ret.append(term)
return ret
def run(self, terms, variables, **kwargs):
if not isinstance(terms, list):
raise AnsibleError("with_flattened expects a list")
return self._do_flatten(terms, variables)

View file

@ -0,0 +1,138 @@
# (c) 2019, Eric Anderson <eric.sysmin@gmail.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
lookup: gcp_storage_file
description:
- This lookup returns the contents from a file residing on Google Cloud Storage
short_description: Return GC Storage content
author: Eric Anderson <eanderson@avinetworks.com>
requirements:
- python >= 2.6
- requests >= 2.18.4
- google-auth >= 1.3.0
options:
src:
description:
- Source location of file (may be local machine or cloud depending on action).
required: false
bucket:
description:
- The name of the bucket.
required: false
extends_documentation_fragment:
- community.general.gcp
'''
EXAMPLES = '''
- debug: msg="the value of foo.txt is {{ lookup('gcp_storage_file',
bucket='gcp-bucket', src='mydir/foo.txt', project='project-name',
auth_kind='serviceaccount', service_account_file='/tmp/myserviceaccountfile.json') }}"
'''
RETURN = '''
_raw:
description:
- base64 encoded file content
'''
import base64
import json
import mimetypes
import os
import requests
from ansible.errors import AnsibleError
from ansible.plugins.lookup import LookupBase
from ansible.utils.display import Display
from ansible_collections.google.cloud.plugins.module_utils.gcp_utils import navigate_hash, GcpSession
display = Display()
class GcpMockModule(object):
def __init__(self, params):
self.params = params
def fail_json(self, *args, **kwargs):
raise AnsibleError(kwargs['msg'])
def raise_for_status(self, response):
try:
response.raise_for_status()
except getattr(requests.exceptions, 'RequestException'):
self.fail_json(msg="GCP returned error: %s" % response.json())
class GcpFileLookup():
def get_file_contents(self, module):
auth = GcpSession(module, 'storage')
data = auth.get(self.media_link(module))
return base64.b64encode(data.content.rstrip())
def fetch_resource(self, module, link, allow_not_found=True):
auth = GcpSession(module, 'storage')
return self.return_if_object(module, auth.get(link), allow_not_found)
def self_link(self, module):
return "https://www.googleapis.com/storage/v1/b/{bucket}/o/{src}".format(**module.params)
def media_link(self, module):
return "https://www.googleapis.com/storage/v1/b/{bucket}/o/{src}?alt=media".format(**module.params)
def return_if_object(self, module, response, allow_not_found=False):
# If not found, return nothing.
if allow_not_found and response.status_code == 404:
return None
# If no content, return nothing.
if response.status_code == 204:
return None
try:
module.raise_for_status(response)
result = response.json()
except getattr(json.decoder, 'JSONDecodeError', ValueError) as inst:
raise AnsibleError("Invalid JSON response with error: %s" % inst)
if navigate_hash(result, ['error', 'errors']):
raise AnsibleError(navigate_hash(result, ['error', 'errors']))
return result
def object_headers(self, module):
return {
"name": module.params['src'],
"Content-Type": mimetypes.guess_type(module.params['src'])[0],
"Content-Length": str(os.path.getsize(module.params['src'])),
}
def run(self, terms, variables=None, **kwargs):
params = {
'bucket': kwargs.get('bucket', None),
'src': kwargs.get('src', None),
'projects': kwargs.get('projects', None),
'scopes': kwargs.get('scopes', None),
'zones': kwargs.get('zones', None),
'auth_kind': kwargs.get('auth_kind', None),
'service_account_file': kwargs.get('service_account_file', None),
'service_account_email': kwargs.get('service_account_email', None),
}
if not params['scopes']:
params['scopes'] = ['https://www.googleapis.com/auth/devstorage.full_control']
fake_module = GcpMockModule(params)
# Check if files exist.
remote_object = self.fetch_resource(fake_module, self.self_link(fake_module))
if not remote_object:
raise AnsibleError("File does not exist in bucket")
result = self.get_file_contents(fake_module)
return [result]
class LookupModule(LookupBase):
def run(self, terms, variables=None, **kwargs):
return GcpFileLookup().run(terms, variables=variables, **kwargs)

View file

@ -0,0 +1,300 @@
# (c) 2015, Jonathan Davila <jonathan(at)davila.io>
# (c) 2017 Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = '''
lookup: hashi_vault
author: Jonathan Davila <jdavila(at)ansible.com>
short_description: retrieve secrets from HashiCorp's vault
requirements:
- hvac (python library)
description:
- retrieve secrets from HashiCorp's vault
notes:
- Due to a current limitation in the HVAC library there won't necessarily be an error if a bad endpoint is specified.
- As of Ansible 2.10, only the latest secret is returned when specifying a KV v2 path.
options:
secret:
description: query you are making.
required: True
token:
description: vault token.
env:
- name: VAULT_TOKEN
url:
description: URL to vault service.
env:
- name: VAULT_ADDR
default: 'http://127.0.0.1:8200'
username:
description: Authentication user name.
password:
description: Authentication password.
role_id:
description: Role id for a vault AppRole auth.
env:
- name: VAULT_ROLE_ID
secret_id:
description: Secret id for a vault AppRole auth.
env:
- name: VAULT_SECRET_ID
auth_method:
description:
- Authentication method to be used.
- C(userpass) is added in version 2.8.
env:
- name: VAULT_AUTH_METHOD
choices:
- userpass
- ldap
- approle
mount_point:
description: vault mount point, only required if you have a custom mount point.
default: ldap
ca_cert:
description: path to certificate to use for authentication.
aliases: [ cacert ]
validate_certs:
description: controls verification and validation of SSL certificates, mostly you only want to turn off with self signed ones.
type: boolean
default: True
namespace:
description: namespace where secrets reside. requires HVAC 0.7.0+ and Vault 0.11+.
'''
EXAMPLES = """
- debug:
msg: "{{ lookup('hashi_vault', 'secret=secret/hello:value token=c975b780-d1be-8016-866b-01d0f9b688a5 url=http://myvault:8200')}}"
- name: Return all secrets from a path
debug:
msg: "{{ lookup('hashi_vault', 'secret=secret/hello token=c975b780-d1be-8016-866b-01d0f9b688a5 url=http://myvault:8200')}}"
- name: Vault that requires authentication via LDAP
debug:
msg: "{{ lookup('hashi_vault', 'secret=secret/hello:value auth_method=ldap mount_point=ldap username=myuser password=mypas url=http://myvault:8200')}}"
- name: Vault that requires authentication via username and password
debug:
msg: "{{ lookup('hashi_vault', 'secret=secret/hello:value auth_method=userpass username=myuser password=mypas url=http://myvault:8200')}}"
- name: Using an ssl vault
debug:
msg: "{{ lookup('hashi_vault', 'secret=secret/hola:value token=c975b780-d1be-8016-866b-01d0f9b688a5 url=https://myvault:8200 validate_certs=False')}}"
- name: using certificate auth
debug:
msg: "{{ lookup('hashi_vault', 'secret=secret/hi:value token=xxxx-xxx-xxx url=https://myvault:8200 validate_certs=True cacert=/cacert/path/ca.pem')}}"
- name: authenticate with a Vault app role
debug:
msg: "{{ lookup('hashi_vault', 'secret=secret/hello:value auth_method=approle role_id=myroleid secret_id=mysecretid url=http://myvault:8200')}}"
- name: Return all secrets from a path in a namespace
debug:
msg: "{{ lookup('hashi_vault', 'secret=secret/hello token=c975b780-d1be-8016-866b-01d0f9b688a5 url=http://myvault:8200 namespace=teama/admins')}}"
# When using KV v2 the PATH should include "data" between the secret engine mount and path (e.g. "secret/data/:path")
# see: https://www.vaultproject.io/api/secret/kv/kv-v2.html#read-secret-version
- name: Return latest KV v2 secret from path
debug:
msg: "{{ lookup('hashi_vault', 'secret=secret/data/hello token=my_vault_token url=http://myvault_url:8200') }}"
"""
RETURN = """
_raw:
description:
- secrets(s) requested
"""
import os
from ansible.errors import AnsibleError
from ansible.module_utils.parsing.convert_bool import boolean
from ansible.plugins.lookup import LookupBase
HAS_HVAC = False
try:
import hvac
HAS_HVAC = True
except ImportError:
HAS_HVAC = False
ANSIBLE_HASHI_VAULT_ADDR = 'http://127.0.0.1:8200'
if os.getenv('VAULT_ADDR') is not None:
ANSIBLE_HASHI_VAULT_ADDR = os.environ['VAULT_ADDR']
class HashiVault:
def __init__(self, **kwargs):
self.url = kwargs.get('url', ANSIBLE_HASHI_VAULT_ADDR)
self.namespace = kwargs.get('namespace', None)
self.avail_auth_method = ['approle', 'userpass', 'ldap']
# split secret arg, which has format 'secret/hello:value' into secret='secret/hello' and secret_field='value'
s = kwargs.get('secret')
if s is None:
raise AnsibleError("No secret specified for hashi_vault lookup")
s_f = s.rsplit(':', 1)
self.secret = s_f[0]
if len(s_f) >= 2:
self.secret_field = s_f[1]
else:
self.secret_field = ''
self.verify = self.boolean_or_cacert(kwargs.get('validate_certs', True), kwargs.get('cacert', ''))
# If a particular backend is asked for (and its method exists) we call it, otherwise drop through to using
# token auth. This means if a particular auth backend is requested and a token is also given, then we
# ignore the token and attempt authentication against the specified backend.
#
# to enable a new auth backend, simply add a new 'def auth_<type>' method below.
#
self.auth_method = kwargs.get('auth_method', os.environ.get('VAULT_AUTH_METHOD'))
self.verify = self.boolean_or_cacert(kwargs.get('validate_certs', True), kwargs.get('cacert', ''))
if self.auth_method and self.auth_method != 'token':
try:
if self.namespace is not None:
self.client = hvac.Client(url=self.url, verify=self.verify, namespace=self.namespace)
else:
self.client = hvac.Client(url=self.url, verify=self.verify)
# prefixing with auth_ to limit which methods can be accessed
getattr(self, 'auth_' + self.auth_method)(**kwargs)
except AttributeError:
raise AnsibleError("Authentication method '%s' not supported."
" Available options are %r" % (self.auth_method, self.avail_auth_method))
else:
self.token = kwargs.get('token', os.environ.get('VAULT_TOKEN', None))
if self.token is None and os.environ.get('HOME'):
token_filename = os.path.join(
os.environ.get('HOME'),
'.vault-token'
)
if os.path.exists(token_filename):
with open(token_filename) as token_file:
self.token = token_file.read().strip()
if self.token is None:
raise AnsibleError("No Vault Token specified")
if self.namespace is not None:
self.client = hvac.Client(url=self.url, token=self.token, verify=self.verify, namespace=self.namespace)
else:
self.client = hvac.Client(url=self.url, token=self.token, verify=self.verify)
if not self.client.is_authenticated():
raise AnsibleError("Invalid Hashicorp Vault Token Specified for hashi_vault lookup")
def get(self):
data = self.client.read(self.secret)
# Check response for KV v2 fields and flatten nested secret data.
#
# https://vaultproject.io/api/secret/kv/kv-v2.html#sample-response-1
try:
# sentinel field checks
check_dd = data['data']['data']
check_md = data['data']['metadata']
# unwrap nested data
data = data['data']
except KeyError:
pass
if data is None:
raise AnsibleError("The secret %s doesn't seem to exist for hashi_vault lookup" % self.secret)
if self.secret_field == '':
return data['data']
if self.secret_field not in data['data']:
raise AnsibleError("The secret %s does not contain the field '%s'. for hashi_vault lookup" % (self.secret, self.secret_field))
return data['data'][self.secret_field]
def check_params(self, **kwargs):
username = kwargs.get('username')
if username is None:
raise AnsibleError("Authentication method %s requires a username" % self.auth_method)
password = kwargs.get('password')
if password is None:
raise AnsibleError("Authentication method %s requires a password" % self.auth_method)
mount_point = kwargs.get('mount_point')
return username, password, mount_point
def auth_userpass(self, **kwargs):
username, password, mount_point = self.check_params(**kwargs)
if mount_point is None:
mount_point = 'userpass'
self.client.auth_userpass(username, password, mount_point=mount_point)
def auth_ldap(self, **kwargs):
username, password, mount_point = self.check_params(**kwargs)
if mount_point is None:
mount_point = 'ldap'
self.client.auth.ldap.login(username, password, mount_point=mount_point)
def boolean_or_cacert(self, validate_certs, cacert):
validate_certs = boolean(validate_certs, strict=False)
'''' return a bool or cacert '''
if validate_certs is True:
if cacert != '':
return cacert
else:
return True
else:
return False
def auth_approle(self, **kwargs):
role_id = kwargs.get('role_id', os.environ.get('VAULT_ROLE_ID', None))
if role_id is None:
raise AnsibleError("Authentication method app role requires a role_id")
secret_id = kwargs.get('secret_id', os.environ.get('VAULT_SECRET_ID', None))
if secret_id is None:
raise AnsibleError("Authentication method app role requires a secret_id")
self.client.auth_approle(role_id, secret_id)
class LookupModule(LookupBase):
def run(self, terms, variables=None, **kwargs):
if not HAS_HVAC:
raise AnsibleError("Please pip install hvac to use the hashi_vault lookup module.")
vault_args = terms[0].split()
vault_dict = {}
ret = []
for param in vault_args:
try:
key, value = param.split('=')
except ValueError:
raise AnsibleError("hashi_vault lookup plugin needs key=value pairs, but received %s" % terms)
vault_dict[key] = value
if 'ca_cert' in vault_dict.keys():
vault_dict['cacert'] = vault_dict['ca_cert']
vault_dict.pop('ca_cert', None)
vault_conn = HashiVault(**vault_dict)
for term in terms:
key = term.split()[0]
value = vault_conn.get()
ret.append(value)
return ret

86
plugins/lookup/hiera.py Normal file
View file

@ -0,0 +1,86 @@
# (c) 2017, Juan Manuel Parrilla <jparrill@redhat.com>
# (c) 2012-17 Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = '''
author:
- Juan Manuel Parrilla (@jparrill)
lookup: hiera
short_description: get info from hiera data
requirements:
- hiera (command line utility)
description:
- Retrieves data from an Puppetmaster node using Hiera as ENC
options:
_hiera_key:
description:
- The list of keys to lookup on the Puppetmaster
type: list
element_type: string
required: True
_bin_file:
description:
- Binary file to execute Hiera
default: '/usr/bin/hiera'
env:
- name: ANSIBLE_HIERA_BIN
_hierarchy_file:
description:
- File that describes the hierarchy of Hiera
default: '/etc/hiera.yaml'
env:
- name: ANSIBLE_HIERA_CFG
# FIXME: incomplete options .. _terms? environment/fqdn?
'''
EXAMPLES = """
# All this examples depends on hiera.yml that describes the hierarchy
- name: "a value from Hiera 'DB'"
debug: msg={{ lookup('hiera', 'foo') }}
- name: "a value from a Hiera 'DB' on other environment"
debug: msg={{ lookup('hiera', 'foo environment=production') }}
- name: "a value from a Hiera 'DB' for a concrete node"
debug: msg={{ lookup('hiera', 'foo fqdn=puppet01.localdomain') }}
"""
RETURN = """
_raw:
description:
- a value associated with input key
type: strings
"""
import os
from ansible.plugins.lookup import LookupBase
from ansible.utils.cmd_functions import run_cmd
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):
def get(self, hiera_key):
pargs = [ANSIBLE_HIERA_BIN]
pargs.extend(['-c', ANSIBLE_HIERA_CFG])
pargs.extend(hiera_key)
rc, output, err = run_cmd("{0} -c {1} {2}".format(
ANSIBLE_HIERA_BIN, ANSIBLE_HIERA_CFG, hiera_key[0]))
return output.strip()
class LookupModule(LookupBase):
def run(self, terms, variables=''):
hiera = Hiera()
ret = []
ret.append(hiera.get(terms))
return ret

65
plugins/lookup/keyring.py Normal file
View file

@ -0,0 +1,65 @@
# (c) 2016, Samuel Boucher <boucher.samuel.c@gmail.com>
# (c) 2017 Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = '''
lookup: keyring
author:
- Samuel Boucher <boucher.samuel.c@gmail.com>
requirements:
- keyring (python library)
short_description: grab secrets from the OS keyring
description:
- Allows you to access data stored in the OS provided keyring/keychain.
'''
EXAMPLES = """
- name : output secrets to screen (BAD IDEA)
debug:
msg: "Password: {{item}}"
with_keyring:
- 'servicename username'
- name: access mysql with password from keyring
mysql_db: login_password={{lookup('keyring','mysql joe')}} login_user=joe
"""
RETURN = """
_raw:
description: secrets stored
"""
HAS_KEYRING = True
from ansible.errors import AnsibleError
from ansible.utils.display import Display
try:
import keyring
except ImportError:
HAS_KEYRING = False
from ansible.plugins.lookup import LookupBase
display = Display()
class LookupModule(LookupBase):
def run(self, terms, **kwargs):
if not HAS_KEYRING:
raise AnsibleError(u"Can't LOOKUP(keyring): missing required python library 'keyring'")
display.vvvv(u"keyring: %s" % keyring.get_keyring())
ret = []
for term in terms:
(servicename, username) = (term.split()[0], term.split()[1])
display.vvvv(u"username: %s, servicename: %s " % (username, servicename))
password = keyring.get_password(servicename, username)
if password is None:
raise AnsibleError(u"servicename: %s for user %s not found" % (servicename, username))
ret.append(password.rstrip())
return ret

View file

@ -0,0 +1,97 @@
# (c) 2016, Andrew Zenk <azenk@umn.edu>
# (c) 2017 Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = '''
lookup: lastpass
author:
- Andrew Zenk <azenk@umn.edu>
requirements:
- lpass (command line utility)
- must have already logged into lastpass
short_description: fetch data from lastpass
description:
- use the lpass command line utility to fetch specific fields from lastpass
options:
_terms:
description: key from which you want to retrieve the field
required: True
field:
description: field to return from lastpass
default: 'password'
'''
EXAMPLES = """
- name: get 'custom_field' from lastpass entry 'entry-name'
debug:
msg: "{{ lookup('lastpass', 'entry-name', field='custom_field') }}"
"""
RETURN = """
_raw:
description: secrets stored
"""
from subprocess import Popen, PIPE
from ansible.errors import AnsibleError
from ansible.module_utils._text import to_bytes, to_text
from ansible.plugins.lookup import LookupBase
class LPassException(AnsibleError):
pass
class LPass(object):
def __init__(self, path='lpass'):
self._cli_path = path
@property
def cli_path(self):
return self._cli_path
@property
def logged_in(self):
out, err = self._run(self._build_args("logout"), stdin="n\n", expected_rc=1)
return err.startswith("Are you sure you would like to log out?")
def _run(self, args, stdin=None, expected_rc=0):
p = Popen([self.cli_path] + args, stdout=PIPE, stderr=PIPE, stdin=PIPE)
out, err = p.communicate(to_bytes(stdin))
rc = p.wait()
if rc != expected_rc:
raise LPassException(err)
return to_text(out, errors='surrogate_or_strict'), to_text(err, errors='surrogate_or_strict')
def _build_args(self, command, args=None):
if args is None:
args = []
args = [command] + args
args += ["--color=never"]
return args
def get_field(self, key, field):
if field in ['username', 'password', 'url', 'notes', 'id', 'name']:
out, err = self._run(self._build_args("show", ["--{0}".format(field), key]))
else:
out, err = self._run(self._build_args("show", ["--field={0}".format(field), key]))
return out.strip()
class LookupModule(LookupBase):
def run(self, terms, variables=None, **kwargs):
lp = LPass()
if not lp.logged_in:
raise AnsibleError("Not logged into lastpass: please run 'lpass login' first")
field = kwargs.get('field', 'password')
values = []
for term in terms:
values.append(lp.get_field(term, field))
return values

117
plugins/lookup/lmdb_kv.py Normal file
View file

@ -0,0 +1,117 @@
# (c) 2017-2018, Jan-Piet Mens <jpmens(at)gmail.com>
# (c) 2018 Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = '''
lookup: lmdb_kv
author:
- Jan-Piet Mens (@jpmens)
short_description: fetch data from LMDB
description:
- This lookup returns a list of results from an LMDB DB corresponding to a list of items given to it
requirements:
- lmdb (python library https://lmdb.readthedocs.io/en/release/)
options:
_terms:
description: list of keys to query
db:
description: path to LMDB database
default: 'ansible.mdb'
'''
EXAMPLES = """
- name: query LMDB for a list of country codes
debug:
msg: "{{ query('lmdb_kv', 'nl', 'be', 'lu', db='jp.mdb') }}"
- name: use list of values in a loop by key wildcard
debug:
msg: "Hello from {{ item.0 }} a.k.a. {{ item.1 }}"
vars:
- lmdb_kv_db: jp.mdb
with_lmdb_kv:
- "n*"
- name: get an item by key
assert:
that:
- item == 'Belgium'
vars:
- lmdb_kv_db: jp.mdb
with_lmdb_kv:
- be
"""
RETURN = """
_raw:
description: value(s) stored in LMDB
"""
from ansible.errors import AnsibleError
from ansible.plugins.lookup import LookupBase
from ansible.module_utils._text import to_native, to_text
HAVE_LMDB = True
try:
import lmdb
except ImportError:
HAVE_LMDB = False
class LookupModule(LookupBase):
def run(self, terms, variables, **kwargs):
'''
terms contain any number of keys to be retrieved.
If terms is None, all keys from the database are returned
with their values, and if term ends in an asterisk, we
start searching there
The LMDB database defaults to 'ansible.mdb' if Ansible's
variable 'lmdb_kv_db' is not set:
vars:
- lmdb_kv_db: "jp.mdb"
'''
if HAVE_LMDB is False:
raise AnsibleError("Can't LOOKUP(lmdb_kv): this module requires lmdb to be installed")
db = variables.get('lmdb_kv_db', None)
if db is None:
db = kwargs.get('db', 'ansible.mdb')
db = str(db)
try:
env = lmdb.open(db, readonly=True)
except Exception as e:
raise AnsibleError("LMDB can't open database %s: %s" % (db, to_native(e)))
ret = []
if len(terms) == 0:
with env.begin() as txn:
cursor = txn.cursor()
cursor.first()
for key, value in cursor:
ret.append((to_text(key), to_native(value)))
else:
for term in terms:
with env.begin() as txn:
if term.endswith('*'):
cursor = txn.cursor()
prefix = term[:-1] # strip asterisk
cursor.set_range(to_text(term).encode())
while cursor.key().startswith(to_text(prefix).encode()):
for key, value in cursor:
ret.append((to_text(key), to_native(value)))
cursor.next()
else:
value = txn.get(to_text(term).encode())
if value is not None:
ret.append(to_native(value))
return ret

275
plugins/lookup/manifold.py Normal file
View file

@ -0,0 +1,275 @@
# (c) 2018, Arigato Machine Inc.
# (c) 2018, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = '''
author:
- Kyrylo Galanov (galanoff@gmail.com)
lookup: manifold
short_description: get credentials from Manifold.co
description:
- Retrieves resources' credentials from Manifold.co
options:
_terms:
description:
- Optional list of resource labels to lookup on Manifold.co. If no resources are specified, all
matched resources will be returned.
type: list
elements: string
required: False
api_token:
description:
- manifold API token
type: string
required: True
env:
- name: MANIFOLD_API_TOKEN
project:
description:
- The project label you want to get the resource for.
type: string
required: False
team:
description:
- The team label you want to get the resource for.
type: string
required: False
'''
EXAMPLES = '''
- name: all available resources
debug: msg="{{ lookup('manifold', api_token='SecretToken') }}"
- name: all available resources for a specific project in specific team
debug: msg="{{ lookup('manifold', api_token='SecretToken', project='poject-1', team='team-2') }}"
- name: two specific resources
debug: msg="{{ lookup('manifold', 'resource-1', 'resource-2') }}"
'''
RETURN = '''
_raw:
description:
- dictionary of credentials ready to be consumed as environment variables. If multiple resources define
the same environment variable(s), the last one returned by the Manifold API will take precedence.
type: dict
'''
from ansible.errors import AnsibleError
from ansible.plugins.lookup import LookupBase
from ansible.module_utils.urls import open_url, ConnectionError, SSLValidationError
from ansible.module_utils.six.moves.urllib.error import HTTPError, URLError
from ansible.module_utils.six.moves.urllib.parse import urlencode
from ansible.module_utils import six
from ansible.utils.display import Display
from traceback import format_exception
import json
import sys
import os
display = Display()
class ApiError(Exception):
pass
class ManifoldApiClient(object):
base_url = 'https://api.{api}.manifold.co/v1/{endpoint}'
http_agent = 'python-manifold-ansible-1.0.0'
def __init__(self, token):
self._token = token
def request(self, api, endpoint, *args, **kwargs):
"""
Send a request to API backend and pre-process a response.
:param api: API to send a request to
:type api: str
:param endpoint: API endpoint to fetch data from
:type endpoint: str
:param args: other args for open_url
:param kwargs: other kwargs for open_url
:return: server response. JSON response is automatically deserialized.
:rtype: dict | list | str
"""
default_headers = {
'Authorization': "Bearer {0}".format(self._token),
'Accept': "*/*" # Otherwise server doesn't set content-type header
}
url = self.base_url.format(api=api, endpoint=endpoint)
headers = default_headers
arg_headers = kwargs.pop('headers', None)
if arg_headers:
headers.update(arg_headers)
try:
display.vvvv('manifold lookup connecting to {0}'.format(url))
response = open_url(url, headers=headers, http_agent=self.http_agent, *args, **kwargs)
data = response.read()
if response.headers.get('content-type') == 'application/json':
data = json.loads(data)
return data
except ValueError:
raise ApiError('JSON response can\'t be parsed while requesting {url}:\n{json}'.format(json=data, url=url))
except HTTPError as e:
raise ApiError('Server returned: {err} while requesting {url}:\n{response}'.format(
err=str(e), url=url, response=e.read()))
except URLError as e:
raise ApiError('Failed lookup url for {url} : {err}'.format(url=url, err=str(e)))
except SSLValidationError as e:
raise ApiError('Error validating the server\'s certificate for {url}: {err}'.format(url=url, err=str(e)))
except ConnectionError as e:
raise ApiError('Error connecting to {url}: {err}'.format(url=url, err=str(e)))
def get_resources(self, team_id=None, project_id=None, label=None):
"""
Get resources list
:param team_id: ID of the Team to filter resources by
:type team_id: str
:param project_id: ID of the project to filter resources by
:type project_id: str
:param label: filter resources by a label, returns a list with one or zero elements
:type label: str
:return: list of resources
:rtype: list
"""
api = 'marketplace'
endpoint = 'resources'
query_params = {}
if team_id:
query_params['team_id'] = team_id
if project_id:
query_params['project_id'] = project_id
if label:
query_params['label'] = label
if query_params:
endpoint += '?' + urlencode(query_params)
return self.request(api, endpoint)
def get_teams(self, label=None):
"""
Get teams list
:param label: filter teams by a label, returns a list with one or zero elements
:type label: str
:return: list of teams
:rtype: list
"""
api = 'identity'
endpoint = 'teams'
data = self.request(api, endpoint)
# Label filtering is not supported by API, however this function provides uniform interface
if label:
data = list(filter(lambda x: x['body']['label'] == label, data))
return data
def get_projects(self, label=None):
"""
Get projects list
:param label: filter projects by a label, returns a list with one or zero elements
:type label: str
:return: list of projects
:rtype: list
"""
api = 'marketplace'
endpoint = 'projects'
query_params = {}
if label:
query_params['label'] = label
if query_params:
endpoint += '?' + urlencode(query_params)
return self.request(api, endpoint)
def get_credentials(self, resource_id):
"""
Get resource credentials
:param resource_id: ID of the resource to filter credentials by
:type resource_id: str
:return:
"""
api = 'marketplace'
endpoint = 'credentials?' + urlencode({'resource_id': resource_id})
return self.request(api, endpoint)
class LookupModule(LookupBase):
def run(self, terms, variables=None, api_token=None, project=None, team=None):
"""
:param terms: a list of resources lookups to run.
:param variables: ansible variables active at the time of the lookup
:param api_token: API token
:param project: optional project label
:param team: optional team label
:return: a dictionary of resources credentials
"""
if not api_token:
api_token = os.getenv('MANIFOLD_API_TOKEN')
if not api_token:
raise AnsibleError('API token is required. Please set api_token parameter or MANIFOLD_API_TOKEN env var')
try:
labels = terms
client = ManifoldApiClient(api_token)
if team:
team_data = client.get_teams(team)
if len(team_data) == 0:
raise AnsibleError("Team '{0}' does not exist".format(team))
team_id = team_data[0]['id']
else:
team_id = None
if project:
project_data = client.get_projects(project)
if len(project_data) == 0:
raise AnsibleError("Project '{0}' does not exist".format(project))
project_id = project_data[0]['id']
else:
project_id = None
if len(labels) == 1: # Use server-side filtering if one resource is requested
resources_data = client.get_resources(team_id=team_id, project_id=project_id, label=labels[0])
else: # Get all resources and optionally filter labels
resources_data = client.get_resources(team_id=team_id, project_id=project_id)
if labels:
resources_data = list(filter(lambda x: x['body']['label'] in labels, resources_data))
if labels and len(resources_data) < len(labels):
fetched_labels = [r['body']['label'] for r in resources_data]
not_found_labels = [label for label in labels if label not in fetched_labels]
raise AnsibleError("Resource(s) {0} do not exist".format(', '.join(not_found_labels)))
credentials = {}
cred_map = {}
for resource in resources_data:
resource_credentials = client.get_credentials(resource['id'])
if len(resource_credentials) and resource_credentials[0]['body']['values']:
for cred_key, cred_val in six.iteritems(resource_credentials[0]['body']['values']):
label = resource['body']['label']
if cred_key in credentials:
display.warning("'{cred_key}' with label '{old_label}' was replaced by resource data "
"with label '{new_label}'".format(cred_key=cred_key,
old_label=cred_map[cred_key],
new_label=label))
credentials[cred_key] = cred_val
cred_map[cred_key] = label
ret = [credentials]
return ret
except ApiError as e:
raise AnsibleError('API Error: {0}'.format(str(e)))
except AnsibleError as e:
raise e
except Exception:
exc_type, exc_value, exc_traceback = sys.exc_info()
raise AnsibleError(format_exception(exc_type, exc_value, exc_traceback))

118
plugins/lookup/nios.py Normal file
View file

@ -0,0 +1,118 @@
#
# Copyright 2018 Red Hat | Ansible
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = '''
---
lookup: nios
short_description: Query Infoblox NIOS objects
description:
- Uses the Infoblox WAPI API to fetch NIOS specified objects. This lookup
supports adding additional keywords to filter the return data and specify
the desired set of returned fields.
requirements:
- infoblox-client
extends_documentation_fragment:
- community.general.nios
options:
_terms:
description: The name of the object to return from NIOS
required: True
return_fields:
description: The list of field names to return for the specified object.
filter:
description: a dict object that is used to filter the return objects
extattrs:
description: a dict object that is used to filter on extattrs
'''
EXAMPLES = """
- name: fetch all networkview objects
set_fact:
networkviews: "{{ lookup('nios', 'networkview', provider={'host': 'nios01', 'username': 'admin', 'password': 'password'}) }}"
- name: fetch the default dns view
set_fact:
dns_views: "{{ lookup('nios', 'view', filter={'name': 'default'}, provider={'host': 'nios01', 'username': 'admin', 'password': 'password'}) }}"
# all of the examples below use credentials that are set using env variables
# export INFOBLOX_HOST=nios01
# export INFOBLOX_USERNAME=admin
# export INFOBLOX_PASSWORD=admin
- name: fetch all host records and include extended attributes
set_fact:
host_records: "{{ lookup('nios', 'record:host', return_fields=['extattrs', 'name', 'view', 'comment']}) }}"
- name: use env variables to pass credentials
set_fact:
networkviews: "{{ lookup('nios', 'networkview') }}"
- name: get a host record
set_fact:
host: "{{ lookup('nios', 'record:host', filter={'name': 'hostname.ansible.com'}) }}"
- name: get the authoritative zone from a non default dns view
set_fact:
host: "{{ lookup('nios', 'zone_auth', filter={'fqdn': 'ansible.com', 'view': 'ansible-dns'}) }}"
"""
RETURN = """
obj_type:
description:
- The object type specified in the terms argument
returned: always
type: complex
contains:
obj_field:
- One or more obj_type fields as specified by return_fields argument or
the default set of fields as per the object type
"""
from ansible.plugins.lookup import LookupBase
from ansible_collections.community.general.plugins.module_utils.net_tools.nios.api import WapiLookup
from ansible_collections.community.general.plugins.module_utils.net_tools.nios.api import normalize_extattrs, flatten_extattrs
from ansible.errors import AnsibleError
class LookupModule(LookupBase):
def run(self, terms, variables=None, **kwargs):
try:
obj_type = terms[0]
except IndexError:
raise AnsibleError('the object_type must be specified')
return_fields = kwargs.pop('return_fields', None)
filter_data = kwargs.pop('filter', {})
extattrs = normalize_extattrs(kwargs.pop('extattrs', {}))
provider = kwargs.pop('provider', {})
wapi = WapiLookup(provider)
res = wapi.get_object(obj_type, filter_data, return_fields=return_fields, extattrs=extattrs)
if res is not None:
for obj in res:
if 'extattrs' in obj:
obj['extattrs'] = flatten_extattrs(obj['extattrs'])
else:
res = []
return res

View file

@ -0,0 +1,100 @@
#
# Copyright 2018 Red Hat | Ansible
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = '''
---
lookup: nios_next_ip
short_description: Return the next available IP address for a network
description:
- Uses the Infoblox WAPI API to return the next available IP addresses
for a given network CIDR
requirements:
- infoblox-client
extends_documentation_fragment:
- community.general.nios
options:
_terms:
description: The CIDR network to retrieve the next addresses from
required: True
num:
description: The number of IP addresses to return
required: false
default: 1
exclude:
description: List of IP's that need to be excluded from returned IP addresses
required: false
'''
EXAMPLES = """
- name: return next available IP address for network 192.168.10.0/24
set_fact:
ipaddr: "{{ lookup('nios_next_ip', '192.168.10.0/24', provider={'host': 'nios01', 'username': 'admin', 'password': 'password'}) }}"
- name: return the next 3 available IP addresses for network 192.168.10.0/24
set_fact:
ipaddr: "{{ lookup('nios_next_ip', '192.168.10.0/24', num=3, provider={'host': 'nios01', 'username': 'admin', 'password': 'password'}) }}"
- name: return the next 3 available IP addresses for network 192.168.10.0/24 excluding ip addresses - ['192.168.10.1', '192.168.10.2']
set_fact:
ipaddr: "{{ lookup('nios_next_ip', '192.168.10.0/24', num=3, exclude=['192.168.10.1', '192.168.10.2'],
provider={'host': 'nios01', 'username': 'admin', 'password': 'password'}) }}"
"""
RETURN = """
_list:
description:
- The list of next IP addresses available
returned: always
type: list
"""
from ansible.plugins.lookup import LookupBase
from ansible_collections.community.general.plugins.module_utils.net_tools.nios.api import WapiLookup
from ansible.module_utils._text import to_text
from ansible.errors import AnsibleError
class LookupModule(LookupBase):
def run(self, terms, variables=None, **kwargs):
try:
network = terms[0]
except IndexError:
raise AnsibleError('missing argument in the form of A.B.C.D/E')
provider = kwargs.pop('provider', {})
wapi = WapiLookup(provider)
network_obj = wapi.get_object('network', {'network': network})
if network_obj is None:
raise AnsibleError('unable to find network object %s' % network)
num = kwargs.get('num', 1)
exclude_ip = kwargs.get('exclude', [])
try:
ref = network_obj[0]['_ref']
avail_ips = wapi.call_func('next_available_ip', ref, {'num': num, 'exclude': exclude_ip})
return [avail_ips['ips']]
except Exception as exc:
raise AnsibleError(to_text(exc))

View file

@ -0,0 +1,112 @@
#
# Copyright 2018 Red Hat | Ansible
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = '''
---
lookup: nios_next_network
short_description: Return the next available network range for a network-container
description:
- Uses the Infoblox WAPI API to return the next available network addresses for
a given network CIDR
requirements:
- infoblox_client
extends_documentation_fragment:
- community.general.nios
options:
_terms:
description: The CIDR network to retrieve the next network from next available network within the specified
container.
required: True
cidr:
description:
- The CIDR of the network to retrieve the next network from next available network within the
specified container. Also, Requested CIDR must be specified and greater than the parent CIDR.
required: True
default: 24
num:
description: The number of network addresses to return from network-container
required: false
default: 1
exclude:
description: Network addresses returned from network-container excluding list of user's input network range
required: false
default: ''
'''
EXAMPLES = """
- name: return next available network for network-container 192.168.10.0/24
set_fact:
networkaddr: "{{ lookup('nios_next_network', '192.168.10.0/24', cidr=25, provider={'host': 'nios01', 'username': 'admin', 'password': 'password'}) }}"
- name: return the next 2 available network addresses for network-container 192.168.10.0/24
set_fact:
networkaddr: "{{ lookup('nios_next_network', '192.168.10.0/24', cidr=25, num=2,
provider={'host': 'nios01', 'username': 'admin', 'password': 'password'}) }}"
- name: return the available network addresses for network-container 192.168.10.0/24 excluding network range '192.168.10.0/25'
set_fact:
networkaddr: "{{ lookup('nios_next_network', '192.168.10.0/24', cidr=25, exclude=['192.168.10.0/25'],
provider={'host': 'nios01', 'username': 'admin', 'password': 'password'}) }}"
"""
RETURN = """
_list:
description:
- The list of next network addresses available
returned: always
type: list
"""
from ansible.plugins.lookup import LookupBase
from ansible_collections.community.general.plugins.module_utils.net_tools.nios.api import WapiLookup
from ansible.module_utils._text import to_text
from ansible.errors import AnsibleError
class LookupModule(LookupBase):
def run(self, terms, variables=None, **kwargs):
try:
network = terms[0]
except IndexError:
raise AnsibleError('missing network argument in the form of A.B.C.D/E')
try:
cidr = kwargs.get('cidr', 24)
except IndexError:
raise AnsibleError('missing CIDR argument in the form of xx')
provider = kwargs.pop('provider', {})
wapi = WapiLookup(provider)
network_obj = wapi.get_object('networkcontainer', {'network': network})
if network_obj is None:
raise AnsibleError('unable to find network-container object %s' % network)
num = kwargs.get('num', 1)
exclude_ip = kwargs.get('exclude', [])
try:
ref = network_obj[0]['_ref']
avail_nets = wapi.call_func('next_available_network', ref, {'cidr': cidr, 'num': num, 'exclude': exclude_ip})
return [avail_nets['networks']]
except Exception as exc:
raise AnsibleError(to_text(exc))

View file

@ -0,0 +1,226 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2018, Scott Buchanan <sbuchanan@ri.pn>
# Copyright: (c) 2016, Andrew Zenk <azenk@umn.edu> (lastpass.py used as starting point)
# Copyright: (c) 2018, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
ANSIBLE_METADATA = {
'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'
}
DOCUMENTATION = '''
lookup: onepassword
author:
- Scott Buchanan (@scottsb)
- Andrew Zenk (@azenk)
- Sam Doran (@samdoran)
requirements:
- C(op) 1Password command line utility. See U(https://support.1password.com/command-line/)
short_description: fetch field values from 1Password
description:
- C(onepassword) wraps the C(op) command line utility to fetch specific field values from 1Password.
options:
_terms:
description: identifier(s) (UUID, name, or subdomain; case-insensitive) of item(s) to retrieve.
required: True
field:
description: field to return from each matching item (case-insensitive).
default: 'password'
master_password:
description: The password used to unlock the specified vault.
aliases: ['vault_password']
section:
description: Item section containing the field to retrieve (case-insensitive). If absent will return first match from any section.
subdomain:
description: The 1Password subdomain to authenticate against.
username:
description: The username used to sign in.
secret_key:
description: The secret key used when performing an initial sign in.
vault:
description: Vault containing the item to retrieve (case-insensitive). If absent will search all vaults.
notes:
- This lookup will use an existing 1Password session if one exists. If not, and you have already
performed an initial sign in (meaning C(~/.op/config exists)), then only the C(master_password) is required.
You may optionally specify C(subdomain) in this scenario, otherwise the last used subdomain will be used by C(op).
- This lookup can perform an initial login by providing C(subdomain), C(username), C(secret_key), and C(master_password).
- Due to the B(very) sensitive nature of these credentials, it is B(highly) recommended that you only pass in the minimal credentials
needed at any given time. Also, store these credentials in an Ansible Vault using a key that is equal to or greater in strength
to the 1Password master password.
- This lookup stores potentially sensitive data from 1Password as Ansible facts.
Facts are subject to caching if enabled, which means this data could be stored in clear text
on disk or in a database.
- Tested with C(op) version 0.5.3
'''
EXAMPLES = """
# These examples only work when already signed in to 1Password
- name: Retrieve password for KITT when already signed in to 1Password
debug:
var: lookup('onepassword', 'KITT')
- name: Retrieve password for Wintermute when already signed in to 1Password
debug:
var: lookup('onepassword', 'Tessier-Ashpool', section='Wintermute')
- name: Retrieve username for HAL when already signed in to 1Password
debug:
var: lookup('onepassword', 'HAL 9000', field='username', vault='Discovery')
- name: Retrieve password for HAL when not signed in to 1Password
debug:
var: lookup('onepassword'
'HAL 9000'
subdomain='Discovery'
master_password=vault_master_password)
- name: Retrieve password for HAL when never signed in to 1Password
debug:
var: lookup('onepassword'
'HAL 9000'
subdomain='Discovery'
master_password=vault_master_password
username='tweety@acme.com'
secret_key=vault_secret_key)
"""
RETURN = """
_raw:
description: field data requested
"""
import errno
import json
import os
from subprocess import Popen, PIPE
from ansible.plugins.lookup import LookupBase
from ansible.errors import AnsibleLookupError
from ansible.module_utils._text import to_bytes, to_text
class OnePass(object):
def __init__(self, path='op'):
self.cli_path = path
self.config_file_path = os.path.expanduser('~/.op/config')
self.logged_in = False
self.token = None
self.subdomain = None
self.username = None
self.secret_key = None
self.master_password = None
def get_token(self):
# If the config file exists, assume an initial signin has taken place and try basic sign in
if os.path.isfile(self.config_file_path):
if not self.master_password:
raise AnsibleLookupError('Unable to sign in to 1Password. master_password is required.')
try:
args = ['signin', '--output=raw']
if self.subdomain:
args = ['signin', self.subdomain, '--output=raw']
rc, out, err = self._run(args, command_input=to_bytes(self.master_password))
self.token = out.strip()
except AnsibleLookupError:
self.full_login()
else:
# Attempt a full sign in since there appears to be no existing sign in
self.full_login()
def assert_logged_in(self):
try:
rc, out, err = self._run(['get', 'account'], ignore_errors=True)
if rc == 0:
self.logged_in = True
if not self.logged_in:
self.get_token()
except OSError as e:
if e.errno == errno.ENOENT:
raise AnsibleLookupError("1Password CLI tool '%s' not installed in path on control machine" % self.cli_path)
raise e
def get_raw(self, item_id, vault=None):
args = ["get", "item", item_id]
if vault is not None:
args += ['--vault={0}'.format(vault)]
if not self.logged_in:
args += [to_bytes('--session=') + self.token]
rc, output, dummy = self._run(args)
return output
def get_field(self, item_id, field, section=None, vault=None):
output = self.get_raw(item_id, vault)
return self._parse_field(output, field, section) if output != '' else ''
def full_login(self):
if None in [self.subdomain, self.username, self.secret_key, self.master_password]:
raise AnsibleLookupError('Unable to perform initial sign in to 1Password. '
'subdomain, username, secret_key, and master_password are required to perform initial sign in.')
args = [
'signin',
'{0}.1password.com'.format(self.subdomain),
to_bytes(self.username),
to_bytes(self.secret_key),
'--output=raw',
]
rc, out, err = self._run(args, command_input=to_bytes(self.master_password))
self.token = out.strip()
def _run(self, args, expected_rc=0, command_input=None, ignore_errors=False):
command = [self.cli_path] + args
p = Popen(command, stdout=PIPE, stderr=PIPE, stdin=PIPE)
out, err = p.communicate(input=command_input)
rc = p.wait()
if not ignore_errors and rc != expected_rc:
raise AnsibleLookupError(to_text(err))
return rc, out, err
def _parse_field(self, data_json, field_name, section_title=None):
data = json.loads(data_json)
if section_title is None:
for field_data in data['details'].get('fields', []):
if field_data.get('name', '').lower() == field_name.lower():
return field_data.get('value', '')
for section_data in data['details'].get('sections', []):
if section_title is not None and section_title.lower() != section_data['title'].lower():
continue
for field_data in section_data.get('fields', []):
if field_data.get('t', '').lower() == field_name.lower():
return field_data.get('v', '')
return ''
class LookupModule(LookupBase):
def run(self, terms, variables=None, **kwargs):
op = OnePass()
field = kwargs.get('field', 'password')
section = kwargs.get('section')
vault = kwargs.get('vault')
op.subdomain = kwargs.get('subdomain')
op.username = kwargs.get('username')
op.secret_key = kwargs.get('secret_key')
op.master_password = kwargs.get('master_password', kwargs.get('vault_password'))
op.assert_logged_in()
values = []
for term in terms:
values.append(op.get_field(term, field, section, vault))
return values

View file

@ -0,0 +1,94 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2018, Scott Buchanan <sbuchanan@ri.pn>
# Copyright: (c) 2016, Andrew Zenk <azenk@umn.edu> (lastpass.py used as starting point)
# Copyright: (c) 2018, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
lookup: onepassword_raw
author:
- Scott Buchanan (@scottsb)
- Andrew Zenk (@azenk)
- Sam Doran (@samdoran)
requirements:
- C(op) 1Password command line utility. See U(https://support.1password.com/command-line/)
short_description: fetch an entire item from 1Password
description:
- C(onepassword_raw) wraps C(op) command line utility to fetch an entire item from 1Password
options:
_terms:
description: identifier(s) (UUID, name, or domain; case-insensitive) of item(s) to retrieve.
required: True
master_password:
description: The password used to unlock the specified vault.
aliases: ['vault_password']
section:
description: Item section containing the field to retrieve (case-insensitive). If absent will return first match from any section.
subdomain:
description: The 1Password subdomain to authenticate against.
username:
description: The username used to sign in.
secret_key:
description: The secret key used when performing an initial sign in.
vault:
description: Vault containing the item to retrieve (case-insensitive). If absent will search all vaults.
notes:
- This lookup will use an existing 1Password session if one exists. If not, and you have already
performed an initial sign in (meaning C(~/.op/config exists)), then only the C(master_password) is required.
You may optionally specify C(subdomain) in this scenario, otherwise the last used subdomain will be used by C(op).
- This lookup can perform an initial login by providing C(subdomain), C(username), C(secret_key), and C(master_password).
- Due to the B(very) sensitive nature of these credentials, it is B(highly) recommended that you only pass in the minimal credentials
needed at any given time. Also, store these credentials in an Ansible Vault using a key that is equal to or greater in strength
to the 1Password master password.
- This lookup stores potentially sensitive data from 1Password as Ansible facts.
Facts are subject to caching if enabled, which means this data could be stored in clear text
on disk or in a database.
- Tested with C(op) version 0.5.3
'''
EXAMPLES = """
- name: Retrieve all data about Wintermute
debug:
var: lookup('onepassword_raw', 'Wintermute')
- name: Retrieve all data about Wintermute when not signed in to 1Password
debug:
var: lookup('onepassword_raw', 'Wintermute', subdomain='Turing', vault_password='DmbslfLvasjdl')
"""
RETURN = """
_raw:
description: field data requested
"""
import json
from ansible_collections.community.general.plugins.lookup.onepassword import OnePass
from ansible.plugins.lookup import LookupBase
class LookupModule(LookupBase):
def run(self, terms, variables=None, **kwargs):
op = OnePass()
vault = kwargs.get('vault')
op.subdomain = kwargs.get('subdomain')
op.username = kwargs.get('username')
op.secret_key = kwargs.get('secret_key')
op.master_password = kwargs.get('master_password', kwargs.get('vault_password'))
op.assert_logged_in()
values = []
for term in terms:
data = json.loads(op.get_raw(term, vault))
values.append(data)
return values

View file

@ -0,0 +1,280 @@
# (c) 2017, Patrick Deelman <patrick@patrickdeelman.nl>
# (c) 2017 Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = '''
lookup: passwordstore
author:
- Patrick Deelman <patrick@patrickdeelman.nl>
short_description: manage passwords with passwordstore.org's pass utility
description:
- Enables Ansible to retrieve, create or update passwords from the passwordstore.org pass utility.
It also retrieves YAML style keys stored as multilines in the passwordfile.
options:
_terms:
description: query key
required: True
passwordstore:
description: location of the password store
default: '~/.password-store'
directory:
description: The directory of the password store.
env:
- name: PASSWORD_STORE_DIR
create:
description: Create the password if it does not already exist.
type: bool
default: 'no'
overwrite:
description: Overwrite the password if it does already exist.
type: bool
default: 'no'
returnall:
description: Return all the content of the password, not only the first line.
type: bool
default: 'no'
subkey:
description: Return a specific subkey of the password. When set to C(password), always returns the first line.
default: password
userpass:
description: Specify a password to save, instead of a generated one.
length:
description: The length of the generated password
type: integer
default: 16
backup:
description: Used with C(overwrite=yes). Backup the previous password in a subkey.
type: bool
default: 'no'
nosymbols:
description: use alphanumeric characters
type: bool
default: 'no'
'''
EXAMPLES = """
# Debug is used for examples, BAD IDEA to show passwords on screen
- name: Basic lookup. Fails if example/test doesn't exist
debug:
msg: "{{ lookup('passwordstore', 'example/test')}}"
- name: Create pass with random 16 character password. If password exists just give the password
debug:
var: mypassword
vars:
mypassword: "{{ lookup('passwordstore', 'example/test create=true')}}"
- name: Different size password
debug:
msg: "{{ lookup('passwordstore', 'example/test create=true length=42')}}"
- name: Create password and overwrite the password if it exists. As a bonus, this module includes the old password inside the pass file
debug:
msg: "{{ lookup('passwordstore', 'example/test create=true overwrite=true')}}"
- name: Create an alphanumeric password
debug: msg="{{ lookup('passwordstore', 'example/test create=true nosymbols=true') }}"
- name: Return the value for user in the KV pair user, username
debug:
msg: "{{ lookup('passwordstore', 'example/test subkey=user')}}"
- name: Return the entire password file content
set_fact:
passfilecontent: "{{ lookup('passwordstore', 'example/test returnall=true')}}"
"""
RETURN = """
_raw:
description:
- a password
"""
import os
import subprocess
import time
from distutils import util
from ansible.errors import AnsibleError, AnsibleAssertionError
from ansible.module_utils._text import to_bytes, to_native, to_text
from ansible.utils.encrypt import random_password
from ansible.plugins.lookup import LookupBase
from ansible import constants as C
# backhacked check_output with input for python 2.7
# http://stackoverflow.com/questions/10103551/passing-data-to-subprocess-check-output
def check_output2(*popenargs, **kwargs):
if 'stdout' in kwargs:
raise ValueError('stdout argument not allowed, it will be overridden.')
if 'stderr' in kwargs:
raise ValueError('stderr argument not allowed, it will be overridden.')
if 'input' in kwargs:
if 'stdin' in kwargs:
raise ValueError('stdin and input arguments may not both be used.')
b_inputdata = to_bytes(kwargs['input'], errors='surrogate_or_strict')
del kwargs['input']
kwargs['stdin'] = subprocess.PIPE
else:
b_inputdata = None
process = subprocess.Popen(*popenargs, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs)
try:
b_out, b_err = process.communicate(b_inputdata)
except Exception:
process.kill()
process.wait()
raise
retcode = process.poll()
if retcode != 0 or \
b'encryption failed: Unusable public key' in b_out or \
b'encryption failed: Unusable public key' in b_err:
cmd = kwargs.get("args")
if cmd is None:
cmd = popenargs[0]
raise subprocess.CalledProcessError(
retcode,
cmd,
to_native(b_out + b_err, errors='surrogate_or_strict')
)
return b_out
class LookupModule(LookupBase):
def parse_params(self, term):
# I went with the "traditional" param followed with space separated KV pairs.
# Waiting for final implementation of lookup parameter parsing.
# See: https://github.com/ansible/ansible/issues/12255
params = term.split()
if len(params) > 0:
# the first param is the pass-name
self.passname = params[0]
# next parse the optional parameters in keyvalue pairs
try:
for param in params[1:]:
name, value = param.split('=')
if name not in self.paramvals:
raise AnsibleAssertionError('%s not in paramvals' % name)
self.paramvals[name] = value
except (ValueError, AssertionError) as e:
raise AnsibleError(e)
# check and convert values
try:
for key in ['create', 'returnall', 'overwrite', 'backup', 'nosymbols']:
if not isinstance(self.paramvals[key], bool):
self.paramvals[key] = util.strtobool(self.paramvals[key])
except (ValueError, AssertionError) as e:
raise AnsibleError(e)
if not isinstance(self.paramvals['length'], int):
if self.paramvals['length'].isdigit():
self.paramvals['length'] = int(self.paramvals['length'])
else:
raise AnsibleError("{0} is not a correct value for length".format(self.paramvals['length']))
# Set PASSWORD_STORE_DIR if directory is set
if self.paramvals['directory']:
if os.path.isdir(self.paramvals['directory']):
os.environ['PASSWORD_STORE_DIR'] = self.paramvals['directory']
else:
raise AnsibleError('Passwordstore directory \'{0}\' does not exist'.format(self.paramvals['directory']))
def check_pass(self):
try:
self.passoutput = to_text(
check_output2(["pass", self.passname]),
errors='surrogate_or_strict'
).splitlines()
self.password = self.passoutput[0]
self.passdict = {}
for line in self.passoutput[1:]:
if ':' in line:
name, value = line.split(':', 1)
self.passdict[name.strip()] = value.strip()
except (subprocess.CalledProcessError) as e:
if e.returncode == 1 and 'not in the password store' in e.output:
# if pass returns 1 and return string contains 'is not in the password store.'
# We need to determine if this is valid or Error.
if not self.paramvals['create']:
raise AnsibleError('passname: {0} not found, use create=True'.format(self.passname))
else:
return False
else:
raise AnsibleError(e)
return True
def get_newpass(self):
if self.paramvals['nosymbols']:
chars = C.DEFAULT_PASSWORD_CHARS[:62]
else:
chars = C.DEFAULT_PASSWORD_CHARS
if self.paramvals['userpass']:
newpass = self.paramvals['userpass']
else:
newpass = random_password(length=self.paramvals['length'], chars=chars)
return newpass
def update_password(self):
# generate new password, insert old lines from current result and return new password
newpass = self.get_newpass()
datetime = time.strftime("%d/%m/%Y %H:%M:%S")
msg = newpass + '\n'
if self.passoutput[1:]:
msg += '\n'.join(self.passoutput[1:]) + '\n'
if self.paramvals['backup']:
msg += "lookup_pass: old password was {0} (Updated on {1})\n".format(self.password, datetime)
try:
check_output2(['pass', 'insert', '-f', '-m', self.passname], input=msg)
except (subprocess.CalledProcessError) as e:
raise AnsibleError(e)
return newpass
def generate_password(self):
# generate new file and insert lookup_pass: Generated by Ansible on {date}
# use pwgen to generate the password and insert values with pass -m
newpass = self.get_newpass()
datetime = time.strftime("%d/%m/%Y %H:%M:%S")
msg = newpass + '\n' + "lookup_pass: First generated by ansible on {0}\n".format(datetime)
try:
check_output2(['pass', 'insert', '-f', '-m', self.passname], input=msg)
except (subprocess.CalledProcessError) as e:
raise AnsibleError(e)
return newpass
def get_passresult(self):
if self.paramvals['returnall']:
return os.linesep.join(self.passoutput)
if self.paramvals['subkey'] == 'password':
return self.password
else:
if self.paramvals['subkey'] in self.passdict:
return self.passdict[self.paramvals['subkey']]
else:
return None
def run(self, terms, variables, **kwargs):
result = []
self.paramvals = {
'subkey': 'password',
'directory': variables.get('passwordstore'),
'create': False,
'returnall': False,
'overwrite': False,
'nosymbols': False,
'userpass': '',
'length': 16,
'backup': False,
}
for term in terms:
self.parse_params(term) # parse the input into paramvals
if self.check_pass(): # password exists
if self.paramvals['overwrite'] and self.paramvals['subkey'] == 'password':
result.append(self.update_password())
else:
result.append(self.get_passresult())
else: # password does not exist
if self.paramvals['create']:
result.append(self.generate_password())
return result

189
plugins/lookup/rabbitmq.py Normal file
View file

@ -0,0 +1,189 @@
# (c) 2018, John Imison <john+github@imison.net>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = '''
lookup: rabbitmq
author: John Imison <@Im0>
short_description: Retrieve messages from an AMQP/AMQPS RabbitMQ queue.
description:
- This lookup uses a basic get to retrieve all, or a limited number C(count), messages from a RabbitMQ queue.
options:
url:
description:
- An URI connection string to connect to the AMQP/AMQPS RabbitMQ server.
- For more information refer to the URI spec U(https://www.rabbitmq.com/uri-spec.html).
required: True
queue:
description:
- The queue to get messages from.
required: True
count:
description:
- How many messages to collect from the queue.
- If not set, defaults to retrieving all the messages from the queue.
requirements:
- The python pika package U(https://pypi.org/project/pika/).
notes:
- This lookup implements BlockingChannel.basic_get to get messages from a RabbitMQ server.
- After retrieving a message from the server, receipt of the message is acknowledged and the message on the server is deleted.
- Pika is a pure-Python implementation of the AMQP 0-9-1 protocol that tries to stay fairly independent of the underlying network support library.
- More information about pika can be found at U(https://pika.readthedocs.io/en/stable/).
- This plugin is tested against RabbitMQ. Other AMQP 0.9.1 protocol based servers may work but not tested/guaranteed.
- Assigning the return messages to a variable under C(vars) may result in unexpected results as the lookup is evaluated every time the
variable is referenced.
- Currently this plugin only handles text based messages from a queue. Unexpected results may occur when retrieving binary data.
'''
EXAMPLES = """
- name: Get all messages off a queue
debug:
msg: "{{ lookup('rabbitmq', url='amqp://guest:guest@192.168.0.10:5672/%2F', queue='hello') }}"
# If you are intending on using the returned messages as a variable in more than
# one task (eg. debug, template), it is recommended to set_fact.
- name: Get 2 messages off a queue and set a fact for re-use
set_fact:
messages: "{{ lookup('rabbitmq', url='amqp://guest:guest@192.168.0.10:5672/%2F', queue='hello', count=2) }}"
- name: Dump out contents of the messages
debug:
var: messages
"""
RETURN = """
_list:
description:
- A list of dictionaries with keys and value from the queue.
type: list
contains:
content_type:
description: The content_type on the message in the queue.
type: str
delivery_mode:
description: The delivery_mode on the message in the queue.
type: str
delivery_tag:
description: The delivery_tag on the message in the queue.
type: str
exchange:
description: The exchange the message came from.
type: str
message_count:
description: The message_count for the message on the queue.
type: str
msg:
description: The content of the message.
type: str
redelivered:
description: The redelivered flag. True if the message has been delivered before.
type: bool
routing_key:
description: The routing_key on the message in the queue.
type: str
headers:
description: The headers for the message returned from the queue.
type: dict
json:
description: If application/json is specified in content_type, json will be loaded into variables.
type: dict
"""
import json
from ansible.errors import AnsibleError, AnsibleParserError
from ansible.plugins.lookup import LookupBase
from ansible.module_utils._text import to_native, to_text
from ansible.utils.display import Display
try:
import pika
from pika import spec
HAS_PIKA = True
except ImportError:
HAS_PIKA = False
display = Display()
class LookupModule(LookupBase):
def run(self, terms, variables=None, url=None, queue=None, count=None):
if not HAS_PIKA:
raise AnsibleError('pika python package is required for rabbitmq lookup.')
if not url:
raise AnsibleError('URL is required for rabbitmq lookup.')
if not queue:
raise AnsibleError('Queue is required for rabbitmq lookup.')
display.vvv(u"terms:%s : variables:%s url:%s queue:%s count:%s" % (terms, variables, url, queue, count))
try:
parameters = pika.URLParameters(url)
except Exception as e:
raise AnsibleError("URL malformed: %s" % to_native(e))
try:
connection = pika.BlockingConnection(parameters)
except Exception as e:
raise AnsibleError("Connection issue: %s" % to_native(e))
try:
conn_channel = connection.channel()
except pika.exceptions.AMQPChannelError as e:
try:
connection.close()
except pika.exceptions.AMQPConnectionError as ie:
raise AnsibleError("Channel and connection closing issues: %s / %s" % to_native(e), to_native(ie))
raise AnsibleError("Channel issue: %s" % to_native(e))
ret = []
idx = 0
while True:
method_frame, properties, body = conn_channel.basic_get(queue=queue)
if method_frame:
display.vvv(u"%s, %s, %s " % (method_frame, properties, to_text(body)))
# TODO: In the future consider checking content_type and handle text/binary data differently.
msg_details = dict({
'msg': to_text(body),
'message_count': method_frame.message_count,
'routing_key': method_frame.routing_key,
'delivery_tag': method_frame.delivery_tag,
'redelivered': method_frame.redelivered,
'exchange': method_frame.exchange,
'delivery_mode': properties.delivery_mode,
'content_type': properties.content_type,
'headers': properties.headers
})
if properties.content_type == 'application/json':
try:
msg_details['json'] = json.loads(msg_details['msg'])
except ValueError as e:
raise AnsibleError("Unable to decode JSON for message %s: %s" % (method_frame.delivery_tag, to_native(e)))
ret.append(msg_details)
conn_channel.basic_ack(method_frame.delivery_tag)
idx += 1
if method_frame.message_count == 0 or idx == count:
break
# If we didn't get a method_frame, exit.
else:
break
if connection.is_closed:
return [ret]
else:
try:
connection.close()
except pika.exceptions.AMQPConnectionError:
pass
return [ret]

111
plugins/lookup/redis.py Normal file
View file

@ -0,0 +1,111 @@
# (c) 2012, Jan-Piet Mens <jpmens(at)gmail.com>
# (c) 2017 Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = '''
lookup: redis
author:
- Jan-Piet Mens (@jpmens) <jpmens(at)gmail.com>
- Ansible Core
short_description: fetch data from Redis
description:
- This lookup returns a list of results from a Redis DB corresponding to a list of items given to it
requirements:
- redis (python library https://github.com/andymccurdy/redis-py/)
options:
_terms:
description: list of keys to query
host:
description: location of Redis host
default: '127.0.0.1'
env:
- name: ANSIBLE_REDIS_HOST
ini:
- section: lookup_redis
key: host
port:
description: port on which Redis is listening on
default: 6379
type: int
env:
- name: ANSIBLE_REDIS_PORT
ini:
- section: lookup_redis
key: port
socket:
description: path to socket on which to query Redis, this option overrides host and port options when set.
type: path
env:
- name: ANSIBLE_REDIS_SOCKET
ini:
- section: lookup_redis
key: socket
'''
EXAMPLES = """
- name: query redis for somekey (default or configured settings used)
debug: msg="{{ lookup('redis', 'somekey') }}"
- name: query redis for list of keys and non-default host and port
debug: msg="{{ lookup('redis', item, host='myredis.internal.com', port=2121) }}"
loop: '{{list_of_redis_keys}}'
- name: use list directly
debug: msg="{{ lookup('redis', 'key1', 'key2', 'key3') }}"
- name: use list directly with a socket
debug: msg="{{ lookup('redis', 'key1', 'key2', socket='/var/tmp/redis.sock') }}"
"""
RETURN = """
_raw:
description: value(s) stored in Redis
"""
import os
HAVE_REDIS = False
try:
import redis
HAVE_REDIS = True
except ImportError:
pass
from ansible.module_utils._text import to_text
from ansible.errors import AnsibleError
from ansible.plugins.lookup import LookupBase
class LookupModule(LookupBase):
def run(self, terms, variables, **kwargs):
if not HAVE_REDIS:
raise AnsibleError("Can't LOOKUP(redis_kv): module redis is not installed")
# get options
self.set_options(direct=kwargs)
# setup connection
host = self.get_option('host')
port = self.get_option('port')
socket = self.get_option('socket')
if socket is None:
conn = redis.Redis(host=host, port=port)
else:
conn = redis.Redis(unix_socket_path=socket)
ret = []
for term in terms:
try:
res = conn.get(term)
if res is None:
res = ""
ret.append(to_text(res))
except Exception as e:
# connection failed or key not found
raise AnsibleError('Encountered exception while fetching {0}: {1}'.format(term, e))
return ret

View file

@ -0,0 +1,88 @@
# (c) 2015, Alejandro Guirao <lekumberri@gmail.com>
# (c) 2012-17 Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = '''
lookup: shelvefile
author: Alejandro Guirao <lekumberri@gmail.com>
short_description: read keys from Python shelve file
description:
- Read keys from Python shelve file.
options:
_terms:
description: sets of key value pairs of parameters
key:
description: key to query
required: True
file:
description: path to shelve file
required: True
'''
EXAMPLES = """
- name: retrieve a string value corresponding to a key inside a Python shelve file
debug: msg="{{ lookup('shelvefile', 'file=path_to_some_shelve_file.db key=key_to_retrieve') }}
"""
RETURN = """
_list:
description: value(s) of key(s) in shelve file(s)
"""
import shelve
from ansible.errors import AnsibleError, AnsibleAssertionError
from ansible.plugins.lookup import LookupBase
from ansible.module_utils._text import to_bytes, to_text
class LookupModule(LookupBase):
def read_shelve(self, shelve_filename, key):
"""
Read the value of "key" from a shelve file
"""
d = shelve.open(to_bytes(shelve_filename))
res = d.get(key, None)
d.close()
return res
def run(self, terms, variables=None, **kwargs):
if not isinstance(terms, list):
terms = [terms]
ret = []
for term in terms:
paramvals = {"file": None, "key": None}
params = term.split()
try:
for param in params:
name, value = param.split('=')
if name not in paramvals:
raise AnsibleAssertionError('%s not in paramvals' % name)
paramvals[name] = value
except (ValueError, AssertionError) as e:
# In case "file" or "key" are not present
raise AnsibleError(e)
key = paramvals['key']
# Search also in the role/files directory and in the playbook directory
shelvefile = self.find_file_in_search_path(variables, 'files', paramvals['file'])
if shelvefile:
res = self.read_shelve(shelvefile, key)
if res is None:
raise AnsibleError("Key %s not found in shelve file %s" % (key, shelvefile))
# Convert the value read to string
ret.append(to_text(res))
break
else:
raise AnsibleError("Could not locate shelve file in lookup: %s" % paramvals['file'])
return ret