1# -*- coding: utf-8 -*- 2# (c) 2015, Steve Gargan <steve.gargan@gmail.com> 3# (c) 2017 Ansible Project 4# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 5from __future__ import (absolute_import, division, print_function) 6 7__metaclass__ = type 8 9DOCUMENTATION = ''' 10 author: Unknown (!UNKNOWN) 11 name: consul_kv 12 short_description: Fetch metadata from a Consul key value store. 13 description: 14 - Lookup metadata for a playbook from the key value store in a Consul cluster. 15 Values can be easily set in the kv store with simple rest commands 16 - C(curl -X PUT -d 'some-value' http://localhost:8500/v1/kv/ansible/somedata) 17 requirements: 18 - 'python-consul python library U(https://python-consul.readthedocs.io/en/latest/#installation)' 19 options: 20 _raw: 21 description: List of key(s) to retrieve. 22 type: list 23 recurse: 24 type: boolean 25 description: If true, will retrieve all the values that have the given key as prefix. 26 default: False 27 index: 28 description: 29 - If the key has a value with the specified index then this is returned allowing access to historical values. 30 datacenter: 31 description: 32 - Retrieve the key from a consul datacenter other than the default for the consul host. 33 token: 34 description: The acl token to allow access to restricted values. 35 host: 36 default: localhost 37 description: 38 - The target to connect to, must be a resolvable address. 39 Will be determined from C(ANSIBLE_CONSUL_URL) if that is set. 40 - "C(ANSIBLE_CONSUL_URL) should look like this: C(https://my.consul.server:8500)" 41 env: 42 - name: ANSIBLE_CONSUL_URL 43 ini: 44 - section: lookup_consul 45 key: host 46 port: 47 description: 48 - The port of the target host to connect to. 49 - If you use C(ANSIBLE_CONSUL_URL) this value will be used from there. 50 default: 8500 51 scheme: 52 default: http 53 description: 54 - Whether to use http or https. 55 - If you use C(ANSIBLE_CONSUL_URL) this value will be used from there. 56 validate_certs: 57 default: True 58 description: Whether to verify the ssl connection or not. 59 env: 60 - name: ANSIBLE_CONSUL_VALIDATE_CERTS 61 ini: 62 - section: lookup_consul 63 key: validate_certs 64 client_cert: 65 description: The client cert to verify the ssl connection. 66 env: 67 - name: ANSIBLE_CONSUL_CLIENT_CERT 68 ini: 69 - section: lookup_consul 70 key: client_cert 71 url: 72 description: "The target to connect to, should look like this: C(https://my.consul.server:8500)." 73 type: str 74 version_added: 1.0.0 75 env: 76 - name: ANSIBLE_CONSUL_URL 77 ini: 78 - section: lookup_consul 79 key: url 80''' 81 82EXAMPLES = """ 83 - ansible.builtin.debug: 84 msg: 'key contains {{item}}' 85 with_community.general.consul_kv: 86 - 'key/to/retrieve' 87 88 - name: Parameters can be provided after the key be more specific about what to retrieve 89 ansible.builtin.debug: 90 msg: 'key contains {{item}}' 91 with_community.general.consul_kv: 92 - 'key/to recurse=true token=E6C060A9-26FB-407A-B83E-12DDAFCB4D98' 93 94 - name: retrieving a KV from a remote cluster on non default port 95 ansible.builtin.debug: 96 msg: "{{ lookup('community.general.consul_kv', 'my/key', host='10.10.10.10', port='2000') }}" 97""" 98 99RETURN = """ 100 _raw: 101 description: 102 - Value(s) stored in consul. 103 type: dict 104""" 105 106import os 107from ansible.module_utils.six.moves.urllib.parse import urlparse 108from ansible.errors import AnsibleError, AnsibleAssertionError 109from ansible.plugins.lookup import LookupBase 110from ansible.module_utils.common.text.converters import to_text 111 112try: 113 import consul 114 115 HAS_CONSUL = True 116except ImportError as e: 117 HAS_CONSUL = False 118 119 120class LookupModule(LookupBase): 121 122 def run(self, terms, variables=None, **kwargs): 123 124 if not HAS_CONSUL: 125 raise AnsibleError( 126 'python-consul is required for consul_kv lookup. see http://python-consul.readthedocs.org/en/latest/#installation') 127 128 # get options 129 self.set_options(direct=kwargs) 130 131 scheme = self.get_option('scheme') 132 host = self.get_option('host') 133 port = self.get_option('port') 134 url = self.get_option('url') 135 if url is not None: 136 u = urlparse(url) 137 if u.scheme: 138 scheme = u.scheme 139 host = u.hostname 140 if u.port is not None: 141 port = u.port 142 143 validate_certs = self.get_option('validate_certs') 144 client_cert = self.get_option('client_cert') 145 146 values = [] 147 try: 148 for term in terms: 149 params = self.parse_params(term) 150 consul_api = consul.Consul(host=host, port=port, scheme=scheme, verify=validate_certs, cert=client_cert) 151 152 results = consul_api.kv.get(params['key'], 153 token=params['token'], 154 index=params['index'], 155 recurse=params['recurse'], 156 dc=params['datacenter']) 157 if results[1]: 158 # responds with a single or list of result maps 159 if isinstance(results[1], list): 160 for r in results[1]: 161 values.append(to_text(r['Value'])) 162 else: 163 values.append(to_text(results[1]['Value'])) 164 except Exception as e: 165 raise AnsibleError( 166 "Error locating '%s' in kv store. Error was %s" % (term, e)) 167 168 return values 169 170 def parse_params(self, term): 171 params = term.split(' ') 172 173 paramvals = { 174 'key': params[0], 175 'token': self.get_option('token'), 176 'recurse': self.get_option('recurse'), 177 'index': self.get_option('index'), 178 'datacenter': self.get_option('datacenter') 179 } 180 181 # parameters specified? 182 try: 183 for param in params[1:]: 184 if param and len(param) > 0: 185 name, value = param.split('=') 186 if name not in paramvals: 187 raise AnsibleAssertionError("%s not a valid consul lookup parameter" % name) 188 paramvals[name] = value 189 except (ValueError, AssertionError) as e: 190 raise AnsibleError(e) 191 192 return paramvals 193