1#!/usr/local/bin/python3.8
2# -*- coding: utf-8 -*-
3
4# Copyright: (c) 2015, Steve Gargan <steve.gargan@gmail.com>
5# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
6
7from __future__ import absolute_import, division, print_function
8__metaclass__ = type
9
10DOCUMENTATION = '''
11module: consul_session
12short_description: Manipulate consul sessions
13description:
14 - Allows the addition, modification and deletion of sessions in a consul
15   cluster. These sessions can then be used in conjunction with key value pairs
16   to implement distributed locks. In depth documentation for working with
17   sessions can be found at http://www.consul.io/docs/internals/sessions.html
18requirements:
19  - python-consul
20  - requests
21author:
22- Steve Gargan (@sgargan)
23options:
24    id:
25        description:
26          - ID of the session, required when I(state) is either C(info) or
27            C(remove).
28        type: str
29    state:
30        description:
31          - Whether the session should be present i.e. created if it doesn't
32            exist, or absent, removed if present. If created, the I(id) for the
33            session is returned in the output. If C(absent), I(id) is
34            required to remove the session. Info for a single session, all the
35            sessions for a node or all available sessions can be retrieved by
36            specifying C(info), C(node) or C(list) for the I(state); for C(node)
37            or C(info), the node I(name) or session I(id) is required as parameter.
38        choices: [ absent, info, list, node, present ]
39        type: str
40        default: present
41    name:
42        description:
43          - The name that should be associated with the session. Required when
44            I(state=node) is used.
45        type: str
46    delay:
47        description:
48          - The optional lock delay that can be attached to the session when it
49            is created. Locks for invalidated sessions ar blocked from being
50            acquired until this delay has expired. Durations are in seconds.
51        type: int
52        default: 15
53    node:
54        description:
55          - The name of the node that with which the session will be associated.
56            by default this is the name of the agent.
57        type: str
58    datacenter:
59        description:
60          - The name of the datacenter in which the session exists or should be
61            created.
62        type: str
63    checks:
64        description:
65          - Checks that will be used to verify the session health. If
66            all the checks fail, the session will be invalidated and any locks
67            associated with the session will be release and can be acquired once
68            the associated lock delay has expired.
69        type: list
70        elements: str
71    host:
72        description:
73          - The host of the consul agent defaults to localhost.
74        type: str
75        default: localhost
76    port:
77        description:
78          - The port on which the consul agent is running.
79        type: int
80        default: 8500
81    scheme:
82        description:
83          - The protocol scheme on which the consul agent is running.
84        type: str
85        default: http
86    validate_certs:
87        description:
88          - Whether to verify the TLS certificate of the consul agent.
89        type: bool
90        default: True
91    behavior:
92        description:
93          - The optional behavior that can be attached to the session when it
94            is created. This controls the behavior when a session is invalidated.
95        choices: [ delete, release ]
96        type: str
97        default: release
98'''
99
100EXAMPLES = '''
101- name: Register basic session with consul
102  community.general.consul_session:
103    name: session1
104
105- name: Register a session with an existing check
106  community.general.consul_session:
107    name: session_with_check
108    checks:
109      - existing_check_name
110
111- name: Register a session with lock_delay
112  community.general.consul_session:
113    name: session_with_delay
114    delay: 20s
115
116- name: Retrieve info about session by id
117  community.general.consul_session:
118    id: session_id
119    state: info
120
121- name: Retrieve active sessions
122  community.general.consul_session:
123    state: list
124'''
125
126try:
127    import consul
128    from requests.exceptions import ConnectionError
129    python_consul_installed = True
130except ImportError:
131    python_consul_installed = False
132
133from ansible.module_utils.basic import AnsibleModule
134
135
136def execute(module):
137
138    state = module.params.get('state')
139
140    if state in ['info', 'list', 'node']:
141        lookup_sessions(module)
142    elif state == 'present':
143        update_session(module)
144    else:
145        remove_session(module)
146
147
148def lookup_sessions(module):
149
150    datacenter = module.params.get('datacenter')
151
152    state = module.params.get('state')
153    consul_client = get_consul_api(module)
154    try:
155        if state == 'list':
156            sessions_list = consul_client.session.list(dc=datacenter)
157            # Ditch the index, this can be grabbed from the results
158            if sessions_list and len(sessions_list) >= 2:
159                sessions_list = sessions_list[1]
160            module.exit_json(changed=True,
161                             sessions=sessions_list)
162        elif state == 'node':
163            node = module.params.get('node')
164            sessions = consul_client.session.node(node, dc=datacenter)
165            module.exit_json(changed=True,
166                             node=node,
167                             sessions=sessions)
168        elif state == 'info':
169            session_id = module.params.get('id')
170
171            session_by_id = consul_client.session.info(session_id, dc=datacenter)
172            module.exit_json(changed=True,
173                             session_id=session_id,
174                             sessions=session_by_id)
175
176    except Exception as e:
177        module.fail_json(msg="Could not retrieve session info %s" % e)
178
179
180def update_session(module):
181
182    name = module.params.get('name')
183    delay = module.params.get('delay')
184    checks = module.params.get('checks')
185    datacenter = module.params.get('datacenter')
186    node = module.params.get('node')
187    behavior = module.params.get('behavior')
188
189    consul_client = get_consul_api(module)
190
191    try:
192        session = consul_client.session.create(
193            name=name,
194            behavior=behavior,
195            node=node,
196            lock_delay=delay,
197            dc=datacenter,
198            checks=checks
199        )
200        module.exit_json(changed=True,
201                         session_id=session,
202                         name=name,
203                         behavior=behavior,
204                         delay=delay,
205                         checks=checks,
206                         node=node)
207    except Exception as e:
208        module.fail_json(msg="Could not create/update session %s" % e)
209
210
211def remove_session(module):
212    session_id = module.params.get('id')
213
214    consul_client = get_consul_api(module)
215
216    try:
217        consul_client.session.destroy(session_id)
218
219        module.exit_json(changed=True,
220                         session_id=session_id)
221    except Exception as e:
222        module.fail_json(msg="Could not remove session with id '%s' %s" % (
223                         session_id, e))
224
225
226def get_consul_api(module):
227    return consul.Consul(host=module.params.get('host'),
228                         port=module.params.get('port'),
229                         scheme=module.params.get('scheme'),
230                         verify=module.params.get('validate_certs'))
231
232
233def test_dependencies(module):
234    if not python_consul_installed:
235        module.fail_json(msg="python-consul required for this module. "
236                             "see https://python-consul.readthedocs.io/en/latest/#installation")
237
238
239def main():
240    argument_spec = dict(
241        checks=dict(type='list', elements='str'),
242        delay=dict(type='int', default='15'),
243        behavior=dict(type='str', default='release', choices=['release', 'delete']),
244        host=dict(type='str', default='localhost'),
245        port=dict(type='int', default=8500),
246        scheme=dict(type='str', default='http'),
247        validate_certs=dict(type='bool', default=True),
248        id=dict(type='str'),
249        name=dict(type='str'),
250        node=dict(type='str'),
251        state=dict(type='str', default='present', choices=['absent', 'info', 'list', 'node', 'present']),
252        datacenter=dict(type='str'),
253    )
254
255    module = AnsibleModule(
256        argument_spec=argument_spec,
257        required_if=[
258            ('state', 'node', ['name']),
259            ('state', 'info', ['id']),
260            ('state', 'remove', ['id']),
261        ],
262        supports_check_mode=False
263    )
264
265    test_dependencies(module)
266
267    try:
268        execute(module)
269    except ConnectionError as e:
270        module.fail_json(msg='Could not connect to consul agent at %s:%s, error was %s' % (
271            module.params.get('host'), module.params.get('port'), e))
272    except Exception as e:
273        module.fail_json(msg=str(e))
274
275
276if __name__ == '__main__':
277    main()
278