summaryrefslogtreecommitdiffstats
path: root/ansible_collections/community/general/plugins/lookup/etcd3.py
blob: 7f0a0cf90ef676487b75aa93268388bcd5a9559f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
# -*- coding: utf-8 -*-
#
# Copyright (c) 2020, SCC France, Eric Belhomme <ebelhomme@fr.scc.com>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later

from __future__ import absolute_import, division, print_function
__metaclass__ = type

DOCUMENTATION = '''
    author:
    - Eric Belhomme (@eric-belhomme) <ebelhomme@fr.scc.com>
    version_added: '0.2.0'
    name: etcd3
    short_description: Get key values from etcd3 server
    description:
    - Retrieves key values and/or key prefixes from etcd3 server using its native gRPC API.
    - Try to reuse M(community.general.etcd3) options for connection parameters, but add support for some C(ETCDCTL_*) environment variables.
    - See U(https://github.com/etcd-io/etcd/tree/master/Documentation/op-guide) for etcd overview.

    options:
        _terms:
            description:
            - The list of keys (or key prefixes) to look up on the etcd3 server.
            type: list
            elements: str
            required: true
        prefix:
            description:
            - Look for key or prefix key.
            type: bool
            default: false
        endpoints:
            description:
            - Counterpart of C(ETCDCTL_ENDPOINTS) environment variable.
              Specify the etcd3 connection with and URL form eg. C(https://hostname:2379)  or C(<host>:<port>) form.
            - The C(host) part is overwritten by I(host) option, if defined.
            - The C(port) part is overwritten by I(port) option, if defined.
            env:
            - name: ETCDCTL_ENDPOINTS
            default: '127.0.0.1:2379'
            type: str
        host:
            description:
            - etcd3 listening client host.
            - Takes precedence over I(endpoints).
            type: str
        port:
            description:
            - etcd3 listening client port.
            - Takes precedence over I(endpoints).
            type: int
        ca_cert:
            description:
            - etcd3 CA authority.
            env:
            - name: ETCDCTL_CACERT
            type: str
        cert_cert:
            description:
            - etcd3 client certificate.
            env:
            - name: ETCDCTL_CERT
            type: str
        cert_key:
            description:
            - etcd3 client private key.
            env:
            - name: ETCDCTL_KEY
            type: str
        timeout:
            description:
            - Client timeout.
            default: 60
            env:
            - name: ETCDCTL_DIAL_TIMEOUT
            type: int
        user:
            description:
            - Authenticated user name.
            env:
            - name: ETCDCTL_USER
            type: str
        password:
            description:
            - Authenticated user password.
            env:
            - name: ETCDCTL_PASSWORD
            type: str

    notes:
    - I(host) and I(port) options take precedence over (endpoints) option.
    - The recommended way to connect to etcd3 server is using C(ETCDCTL_ENDPOINT)
      environment variable and keep I(endpoints), I(host), and I(port) unused.
    seealso:
    - module: community.general.etcd3
    - ref: ansible_collections.community.general.etcd_lookup
      description: The etcd v2 lookup.

    requirements:
    - "etcd3 >= 0.10"
'''

EXAMPLES = '''
- name: "a value from a locally running etcd"
  ansible.builtin.debug:
    msg: "{{ lookup('community.general.etcd3', 'foo/bar') }}"

- name: "values from multiple folders on a locally running etcd"
  ansible.builtin.debug:
    msg: "{{ lookup('community.general.etcd3', 'foo', 'bar', 'baz') }}"

- name: "look for a key prefix"
  ansible.builtin.debug:
    msg: "{{ lookup('community.general.etcd3', '/foo/bar', prefix=True) }}"

- name: "connect to etcd3 with a client certificate"
  ansible.builtin.debug:
    msg: "{{ lookup('community.general.etcd3', 'foo/bar', cert_cert='/etc/ssl/etcd/client.pem', cert_key='/etc/ssl/etcd/client.key') }}"
'''

RETURN = '''
    _raw:
        description:
        - List of keys and associated values.
        type: list
        elements: dict
        contains:
            key:
                description: The element's key.
                type: str
            value:
                description: The element's value.
                type: str
'''

import re

from ansible.errors import AnsibleLookupError
from ansible.module_utils.basic import missing_required_lib
from ansible.module_utils.common.text.converters import to_native
from ansible.plugins.lookup import LookupBase
from ansible.utils.display import Display

try:
    import etcd3
    HAS_ETCD = True
except ImportError:
    HAS_ETCD = False

display = Display()

etcd3_cnx_opts = (
    'host',
    'port',
    'ca_cert',
    'cert_key',
    'cert_cert',
    'timeout',
    'user',
    'password',
    # 'grpc_options' Etcd3Client() option currently not supported by lookup module (maybe in future ?)
)


def etcd3_client(client_params):
    try:
        etcd = etcd3.client(**client_params)
        etcd.status()
    except Exception as exp:
        raise AnsibleLookupError('Cannot connect to etcd cluster: %s' % (to_native(exp)))
    return etcd


class LookupModule(LookupBase):

    def run(self, terms, variables, **kwargs):

        self.set_options(var_options=variables, direct=kwargs)

        if not HAS_ETCD:
            display.error(missing_required_lib('etcd3'))
            return None

        # create the etcd3 connection parameters dict to pass to etcd3 class
        client_params = {}

        # etcd3 class expects host and port as connection parameters, so endpoints
        # must be mangled a bit to fit in this scheme.
        # so here we use a regex to extract server and port
        match = re.compile(
            r'^(https?://)?(?P<host>(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})|([-_\d\w\.]+))(:(?P<port>\d{1,5}))?/?$'
        ).match(self.get_option('endpoints'))
        if match:
            if match.group('host'):
                client_params['host'] = match.group('host')
            if match.group('port'):
                client_params['port'] = match.group('port')

        for opt in etcd3_cnx_opts:
            if self.get_option(opt):
                client_params[opt] = self.get_option(opt)

        cnx_log = dict(client_params)
        if 'password' in cnx_log:
            cnx_log['password'] = '<redacted>'
        display.verbose("etcd3 connection parameters: %s" % cnx_log)

        # connect to etcd3 server
        etcd = etcd3_client(client_params)

        ret = []
        # we can pass many keys to lookup
        for term in terms:
            if self.get_option('prefix'):
                try:
                    for val, meta in etcd.get_prefix(term):
                        if val and meta:
                            ret.append({'key': to_native(meta.key), 'value': to_native(val)})
                except Exception as exp:
                    display.warning('Caught except during etcd3.get_prefix: %s' % (to_native(exp)))
            else:
                try:
                    val, meta = etcd.get(term)
                    if val and meta:
                        ret.append({'key': to_native(meta.key), 'value': to_native(val)})
                except Exception as exp:
                    display.warning('Caught except during etcd3.get: %s' % (to_native(exp)))
        return ret