summaryrefslogtreecommitdiffstats
path: root/ansible_collections/community/general/plugins/lookup/etcd.py
blob: d6a12293e3a72f561f3f72e7553a932703020688 (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
# -*- coding: utf-8 -*-
# Copyright (c) 2013, Jan-Piet Mens <jpmens(at)gmail.com>
# (m) 2016, Mihai Moldovanu <mihaim@tfm.ro>
# (m) 2017, Juan Manuel Parrilla <jparrill@redhat.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:
        - Jan-Piet Mens (@jpmens)
    name: 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"
  ansible.builtin.debug:
    msg: "{{ lookup('community.general.etcd', 'foo/bar') }}"

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

- name: "since Ansible 2.5 you can set server options inline"
  ansible.builtin.debug:
    msg: "{{ lookup('community.general.etcd', 'foo', version='v2', url='http://192.168.0.27:4001') }}"
'''

RETURN = '''
    _raw:
        description:
            - list of values associated with input keys
        type: list
        elements: string
'''

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
#
# - ansible.builtin.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