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()
|