summaryrefslogtreecommitdiffstats
path: root/ansible_collections/community/general/plugins/modules/sensu_client.py
blob: eca0804b0a6a991f8f3f9a8feb3fb24ff513b74d (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
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
#!/usr/bin/python
# -*- coding: utf-8 -*-

# Copyright (c) 2017, Red Hat Inc.
# 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 = '''
---
module: sensu_client
author: "David Moreau Simard (@dmsimard)"
short_description: Manages Sensu client configuration
description:
  - Manages Sensu client configuration.
  - 'For more information, refer to the Sensu documentation: U(https://sensuapp.org/docs/latest/reference/clients.html)'
extends_documentation_fragment:
  - community.general.attributes
attributes:
  check_mode:
    support: full
  diff_mode:
    support: none
options:
  state:
    type: str
    description:
      - Whether the client should be present or not
    choices: [ 'present', 'absent' ]
    default: present
  name:
    type: str
    description:
      - A unique name for the client. The name cannot contain special characters or spaces.
      - If not specified, it defaults to the system hostname as determined by Ruby Socket.gethostname (provided by Sensu).
  address:
    type: str
    description:
      - An address to help identify and reach the client. This is only informational, usually an IP address or hostname.
      - If not specified it defaults to non-loopback IPv4 address as determined by Ruby Socket.ip_address_list (provided by Sensu).
  subscriptions:
    type: list
    elements: str
    description:
      - An array of client subscriptions, a list of roles and/or responsibilities assigned to the system (e.g. webserver).
      - These subscriptions determine which monitoring checks are executed by the client, as check requests are sent to subscriptions.
      - The subscriptions array items must be strings.
  safe_mode:
    description:
      - If safe mode is enabled for the client. Safe mode requires local check definitions in order to accept a check request and execute the check.
    type: bool
    default: false
  redact:
    type: list
    elements: str
    description:
      - Client definition attributes to redact (values) when logging and sending client keepalives.
  socket:
    type: dict
    description:
      - The socket definition scope, used to configure the Sensu client socket.
  keepalives:
    description:
      - If Sensu should monitor keepalives for this client.
    type: bool
    default: true
  keepalive:
    type: dict
    description:
      - The keepalive definition scope, used to configure Sensu client keepalives behavior (e.g. keepalive thresholds, etc).
  registration:
    type: dict
    description:
      - The registration definition scope, used to configure Sensu registration event handlers.
  deregister:
    description:
      - If a deregistration event should be created upon Sensu client process stop.
      - Default is V(false).
    type: bool
  deregistration:
    type: dict
    description:
      - The deregistration definition scope, used to configure automated Sensu client de-registration.
  ec2:
    type: dict
    description:
      - The ec2 definition scope, used to configure the Sensu Enterprise AWS EC2 integration (Sensu Enterprise users only).
  chef:
    type: dict
    description:
      - The chef definition scope, used to configure the Sensu Enterprise Chef integration (Sensu Enterprise users only).
  puppet:
    type: dict
    description:
      - The puppet definition scope, used to configure the Sensu Enterprise Puppet integration (Sensu Enterprise users only).
  servicenow:
    type: dict
    description:
      - The servicenow definition scope, used to configure the Sensu Enterprise ServiceNow integration (Sensu Enterprise users only).
notes:
  - Check mode is supported
'''

EXAMPLES = '''
# Minimum possible configuration
- name: Configure Sensu client
  community.general.sensu_client:
    subscriptions:
      - default

# With customization
- name: Configure Sensu client
  community.general.sensu_client:
    name: "{{ ansible_fqdn }}"
    address: "{{ ansible_default_ipv4['address'] }}"
    subscriptions:
      - default
      - webserver
    redact:
      - password
    socket:
      bind: 127.0.0.1
      port: 3030
    keepalive:
      thresholds:
        warning: 180
        critical: 300
      handlers:
        - email
      custom:
        - broadcast: irc
      occurrences: 3
  register: client
  notify:
    - Restart sensu-client

- name: Secure Sensu client configuration file
  ansible.builtin.file:
    path: "{{ client['file'] }}"
    owner: "sensu"
    group: "sensu"
    mode: "0600"

- name: Delete the Sensu client configuration
  community.general.sensu_client:
    state: "absent"
'''

RETURN = '''
config:
  description: Effective client configuration, when state is present
  returned: success
  type: dict
  sample: {'name': 'client', 'subscriptions': ['default']}
file:
  description: Path to the client configuration file
  returned: success
  type: str
  sample: "/etc/sensu/conf.d/client.json"
'''

import json
import os

from ansible.module_utils.basic import AnsibleModule


def main():
    module = AnsibleModule(
        supports_check_mode=True,
        argument_spec=dict(
            state=dict(type='str', choices=['present', 'absent'], default='present'),
            name=dict(type='str', ),
            address=dict(type='str', ),
            subscriptions=dict(type='list', elements="str"),
            safe_mode=dict(type='bool', default=False),
            redact=dict(type='list', elements="str"),
            socket=dict(type='dict'),
            keepalives=dict(type='bool', default=True),
            keepalive=dict(type='dict'),
            registration=dict(type='dict'),
            deregister=dict(type='bool'),
            deregistration=dict(type='dict'),
            ec2=dict(type='dict'),
            chef=dict(type='dict'),
            puppet=dict(type='dict'),
            servicenow=dict(type='dict')
        ),
        required_if=[
            ['state', 'present', ['subscriptions']]
        ]
    )

    state = module.params['state']
    path = "/etc/sensu/conf.d/client.json"

    if state == 'absent':
        if os.path.exists(path):
            if module.check_mode:
                msg = '{path} would have been deleted'.format(path=path)
                module.exit_json(msg=msg, changed=True)
            else:
                try:
                    os.remove(path)
                    msg = '{path} deleted successfully'.format(path=path)
                    module.exit_json(msg=msg, changed=True)
                except OSError as e:
                    msg = 'Exception when trying to delete {path}: {exception}'
                    module.fail_json(
                        msg=msg.format(path=path, exception=str(e)))
        else:
            # Idempotency: it's okay if the file doesn't exist
            msg = '{path} already does not exist'.format(path=path)
            module.exit_json(msg=msg)

    # Build client configuration from module arguments
    config = {'client': {}}
    args = ['name', 'address', 'subscriptions', 'safe_mode', 'redact',
            'socket', 'keepalives', 'keepalive', 'registration', 'deregister',
            'deregistration', 'ec2', 'chef', 'puppet', 'servicenow']

    for arg in args:
        if arg in module.params and module.params[arg] is not None:
            config['client'][arg] = module.params[arg]

    # Load the current config, if there is one, so we can compare
    current_config = None
    try:
        current_config = json.load(open(path, 'r'))
    except (IOError, ValueError):
        # File either doesn't exist or it's invalid JSON
        pass

    if current_config is not None and current_config == config:
        # Config is the same, let's not change anything
        module.exit_json(msg='Client configuration is already up to date',
                         config=config['client'],
                         file=path)

    # Validate that directory exists before trying to write to it
    if not module.check_mode and not os.path.exists(os.path.dirname(path)):
        try:
            os.makedirs(os.path.dirname(path))
        except OSError as e:
            module.fail_json(msg='Unable to create {0}: {1}'.format(os.path.dirname(path),
                                                                    str(e)))

    if module.check_mode:
        module.exit_json(msg='Client configuration would have been updated',
                         changed=True,
                         config=config['client'],
                         file=path)

    try:
        with open(path, 'w') as client:
            client.write(json.dumps(config, indent=4))
            module.exit_json(msg='Client configuration updated',
                             changed=True,
                             config=config['client'],
                             file=path)
    except (OSError, IOError) as e:
        module.fail_json(msg='Unable to write file {0}: {1}'.format(path,
                                                                    str(e)))


if __name__ == '__main__':
    main()