189
203
lines = "".join(formatted).split("\n")
190
204
if len(lines) > 0 and len(lines[-1]) == 0:
192
indented = "\n".join([" %s" % (line,) for line in lines])
193
self.output.write("%s\n" % (indented,))
197
"""Parse arguments, then build and run checks in a reactor."""
199
# We do this first because ArgumentParser won't let us mix and match
200
# non-default positional argument with a flag argument
206
indented = "\n".join([" {}".format(line) for line in lines])
207
self.output.write("{}\n".format(indented))
210
class Command(object):
211
"""CLI command runner for the main conn-check endpoint."""
213
def __init__(self, args):
214
self.make_arg_parser()
215
self.parse_options(args)
216
self.wrap_output(sys.stdout)
217
self.load_descriptions()
219
def make_arg_parser(self):
220
"""Set up an arg parser with our options."""
222
parser = NagiosCompatibleArgsParser()
223
parser.add_argument("config_file",
224
help="Config file specifying the checks to run.")
225
parser.add_argument("patterns", nargs='*',
226
help="Patterns to filter the checks.")
227
parser.add_argument("-v", "--verbose", dest="verbose",
228
action="store_true", default=False,
229
help="Show additional status")
230
parser.add_argument("-d", "--duration", dest="show_duration",
231
action="store_true", default=False,
232
help="Show duration")
233
parser.add_argument("-t", "--tracebacks", dest="show_tracebacks",
234
action="store_true", default=False,
235
help="Show tracebacks on failure")
236
parser.add_argument("--validate", dest="validate",
237
action="store_true", default=False,
238
help="Only validate the config file,"
239
" don't run checks.")
240
parser.add_argument("--version", dest="print_version",
241
action="store_true", default=False,
242
help="Print the currently installed version.")
243
parser.add_argument("--tls-certs-path", dest="cacerts_path",
244
action="store", default="/etc/ssl/certs/",
245
help="Path to TLS CA certificates.")
246
parser.add_argument("--max-timeout", dest="max_timeout", type=float,
247
action="store", help="Maximum execution time.")
248
parser.add_argument("--connect-timeout", dest="connect_timeout",
249
action="store", default=10, type=float,
250
help="Network connection timeout.")
251
parser.add_argument("-U", "--unbuffered-output", dest="buffer_output",
252
action="store_false", default=True,
253
help="Don't buffer output, write to STDOUT right "
255
parser.add_argument("--dry-run",
256
dest="dry_run", action="store_true",
258
help="Skip all checks, just print out"
259
" what would be run.")
260
group = parser.add_mutually_exclusive_group()
261
group.add_argument("--include-tags", dest="include_tags",
262
action="store", default="",
263
help="Comma separated list of tags to include.")
264
group.add_argument("--exclude-tags", dest="exclude_tags",
265
action="store", default="",
266
help="Comma separated list of tags to exclude.")
269
def setup_reactor(self):
270
"""Setup the Twisted reactor with required customisations."""
272
def make_daemon_thread(*args, **kw):
273
"""Create a daemon thread."""
274
thread = Thread(*args, **kw)
278
threadpool = ThreadPool(minthreads=1)
279
threadpool.threadFactory = make_daemon_thread
280
reactor.threadpool = threadpool
281
reactor.callWhenRunning(threadpool.start)
283
if self.options.max_timeout is not None:
285
# Hasta la vista, twisted
287
print('Maximum timeout reached: {}s'.format(
288
self.options.max_timeout))
290
reactor.callLater(self.options.max_timeout, terminator)
292
def parse_options(self, args):
293
"""Parse args (e.g. sys.argv) into options and set some config."""
295
options = self.parser.parse_args(list(args))
298
if options.include_tags:
299
include_tags = options.include_tags.split(',')
300
include_tags = [tag.strip() for tag in include_tags]
301
options.include_tags = include_tags
304
if options.exclude_tags:
305
exclude_tags = options.exclude_tags.split(',')
306
exclude_tags = [tag.strip() for tag in exclude_tags]
307
options.exclude_tags = exclude_tags
310
self.patterns = SumPattern(map(SimplePattern, options.patterns))
312
self.patterns = SimplePattern("*")
313
self.options = options
315
def wrap_output(self, output):
316
"""Wraps an output stream (e.g. sys.stdout) from options."""
318
if self.options.show_duration:
319
output = TimestampOutput(output)
320
if self.options.buffer_output:
321
# We buffer output so we can order it for human readable output
322
output = OrderedOutput(output)
324
results = ConsoleOutput(output=output,
325
show_tracebacks=self.options.show_tracebacks,
326
show_duration=self.options.show_duration,
327
verbose=self.options.verbose)
328
if not self.options.dry_run:
329
results = FailureCountingResultWrapper(results)
332
self.results = results
334
def load_descriptions(self):
335
"""Pre-load YAML checks file into a descriptions property."""
337
with open(self.options.config_file) as f:
338
self.descriptions = yaml.load(f)
341
"""Run/validate/dry-run the given command with options."""
343
checks = build_checks(self.descriptions,
344
self.options.connect_timeout,
345
self.options.include_tags,
346
self.options.exclude_tags,
347
self.options.dry_run)
349
if not self.options.validate:
350
if not self.options.dry_run:
351
load_tls_certs(self.options.cacerts_path)
354
reactor.callWhenRunning(run_checks, checks, self.patterns,
358
# Flush output, this really only has an effect when running
362
if not self.options.dry_run and self.results.any_failed():
368
def parse_version_arg():
369
"""Manually check for --version in args and output version info.
371
We need to do this early because ArgumentParser won't let us mix
372
and match non-default positional argument with a flag argument.
201
374
if '--version' in sys.argv:
202
375
sys.stdout.write('conn-check {}\n'.format(get_version_string()))
380
if parse_version_arg():
205
parser = NagiosCompatibleArgsParser()
206
parser.add_argument("config_file",
207
help="Config file specifying the checks to run.")
208
parser.add_argument("patterns", nargs='*',
209
help="Patterns to filter the checks.")
210
parser.add_argument("-v", "--verbose", dest="verbose",
211
action="store_true", default=False,
212
help="Show additional status")
213
parser.add_argument("-d", "--duration", dest="show_duration",
214
action="store_true", default=False,
215
help="Show duration")
216
parser.add_argument("-t", "--tracebacks", dest="show_tracebacks",
217
action="store_true", default=False,
218
help="Show tracebacks on failure")
219
parser.add_argument("--validate", dest="validate",
220
action="store_true", default=False,
221
help="Only validate the config file, don't run checks.")
222
parser.add_argument("--version", dest="print_version",
223
action="store_true", default=False,
224
help="Print the currently installed version.")
225
parser.add_argument("--tls-certs-path", dest="cacerts_path",
226
action="store", default="/etc/ssl/certs/",
227
help="Path to TLS CA certificates.")
228
parser.add_argument("--max-timeout", dest="max_timeout", type=float,
229
action="store", help="Maximum execution time.")
230
parser.add_argument("--connect-timeout", dest="connect_timeout",
231
action="store", default=10, type=float,
232
help="Network connection timeout.")
233
parser.add_argument("-U", "--unbuffered-output", dest="buffer_output",
234
action="store_false", default=True,
235
help="Don't buffer output, write to STDOUT right "
237
group = parser.add_mutually_exclusive_group()
238
group.add_argument("--include-tags", dest="include_tags",
239
action="store", default="",
240
help="Comma separated list of tags to include.")
241
group.add_argument("--exclude-tags", dest="exclude_tags",
242
action="store", default="",
243
help="Comma separated list of tags to exclude.")
244
options = parser.parse_args(list(args))
246
load_tls_certs(options.cacerts_path)
249
pattern = SumPattern(map(SimplePattern, options.patterns))
251
pattern = SimplePattern("*")
253
def make_daemon_thread(*args, **kw):
254
"""Create a daemon thread."""
255
thread = Thread(*args, **kw)
259
threadpool = ThreadPool(minthreads=1)
260
threadpool.threadFactory = make_daemon_thread
261
reactor.threadpool = threadpool
262
reactor.callWhenRunning(threadpool.start)
266
if options.show_duration:
267
output = TimestampOutput(output)
269
if options.buffer_output:
270
# We buffer output so we can order it for human readable output
271
output = OrderedOutput(output)
273
include = options.include_tags.split(',') if options.include_tags else []
274
exclude = options.exclude_tags.split(',') if options.exclude_tags else []
276
results = ConsoleOutput(output=output,
277
show_tracebacks=options.show_tracebacks,
278
show_duration=options.show_duration,
279
verbose=options.verbose)
280
results = FailureCountingResultWrapper(results)
281
with open(options.config_file) as f:
282
descriptions = yaml.load(f)
284
checks = build_checks(descriptions, options.connect_timeout, include, exclude)
286
if options.max_timeout is not None:
288
# Hasta la vista, twisted
290
print('Maximum timeout reached: {}s'.format(options.max_timeout))
292
reactor.callLater(options.max_timeout, terminator)
294
if not options.validate:
295
reactor.callWhenRunning(run_checks, checks, pattern, results)
299
# Flush output, this really only has an effect when running buffered
303
if results.any_failed():
310
exit(main(*sys.argv[1:]))
388
sys.exit(run(*sys.argv[1:]))
313
391
if __name__ == '__main__':