hostlint 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/bin/hostlint +449 -0
- data/examples/checks/network/config/bootproto_dhcp.sh +22 -0
- data/examples/checks/network/ethernet_bonded.sh +18 -0
- data/examples/checks/yum/security_updates.sh +12 -0
- data/examples/config/hostlint.yml +12 -0
- data/tests/checks/exec_fail/bad_exit_prelude +5 -0
- data/tests/checks/exec_fail/bad_exit_status.sh +7 -0
- data/tests/checks/exec_fail/bad_output.sh +2 -0
- data/tests/checks/exec_fail/empty_bad_exit_status.sh +2 -0
- data/tests/checks/exec_fail/fail_stderr_bad_exit_status.rb +8 -0
- data/tests/checks/exec_fail/missing_prelude.sh +3 -0
- data/tests/checks/exec_fail/no_output.sh +1 -0
- data/tests/checks/exec_fail/nonexec +0 -0
- data/tests/checks/exec_fail/rename_exec_fail1 +3 -0
- data/tests/checks/exec_fail/rename_exec_fail2 +3 -0
- data/tests/checks/fail/fail.sh +5 -0
- data/tests/checks/fail/rename +2 -0
- data/tests/checks/ok/python.py +8 -0
- data/tests/checks/ok/rename +2 -0
- data/tests/checks/ok/succeed_ok_body.sh +5 -0
- data/tests/checks/ok/succeed_ok_no_body.sh +2 -0
- data/tests/config.yml +9 -0
- data/tests/test.rb +20 -0
- metadata +118 -0
data/bin/hostlint
ADDED
@@ -0,0 +1,449 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require "optparse"
|
3
|
+
require "csv"
|
4
|
+
require "logger"
|
5
|
+
require "yaml"
|
6
|
+
require "pathname"
|
7
|
+
require "pp"
|
8
|
+
require "syslog"
|
9
|
+
require "open4"
|
10
|
+
require "bunny"
|
11
|
+
require "map"
|
12
|
+
|
13
|
+
# fixme jobs support
|
14
|
+
def logd(string) $logger.add(Logger::DEBUG, string, "") if $verbose; end
|
15
|
+
def logi(string) $logger.add(Logger::INFO, string, " ") ; end
|
16
|
+
def loge(string) $logger.add(Logger::ERROR, string, "") ; end
|
17
|
+
def logw(string) $logger.add(Logger::WARN, string, " ") if $verbose; end
|
18
|
+
|
19
|
+
class Main
|
20
|
+
attr_accessor :checks
|
21
|
+
|
22
|
+
def parse_inner_opts
|
23
|
+
end
|
24
|
+
def prelude
|
25
|
+
$logger = Logger.new(STDOUT)
|
26
|
+
$logger.formatter = proc do |severity, datetime, progname, msg|
|
27
|
+
"#{severity}: #{progname}#{msg}\n"
|
28
|
+
end
|
29
|
+
|
30
|
+
optparser = OptionParser.new do |o|
|
31
|
+
o.on("-c", "--run-checks C1[,C2...]",
|
32
|
+
"run specific check(s) by name") do |arg|
|
33
|
+
@checks_filter = CSV.parse_line(arg)
|
34
|
+
|
35
|
+
end
|
36
|
+
o.on("-d", "--check-dir DIR",
|
37
|
+
"Specifies the directory of checks to run") do |arg|
|
38
|
+
@check_dir = Pathname.new(arg)
|
39
|
+
end
|
40
|
+
o.on("-f", "--config-file FILE",
|
41
|
+
"Specifies the location of the config file") do |arg|
|
42
|
+
@config_file = Pathname.new(arg)
|
43
|
+
end
|
44
|
+
|
45
|
+
# fixme re-implement
|
46
|
+
o.on("-j", "--jobs N",
|
47
|
+
"Specifies the number of jobs (checks) to run simultaneously " +
|
48
|
+
"(0 for unbounded)") do |arg|
|
49
|
+
@jobs = arg.to_i
|
50
|
+
end
|
51
|
+
o.on("-o", "--output-methods M[:OPTS]",
|
52
|
+
"Output using M (see below). Default stdout, may be specified multiple times") do |arg|
|
53
|
+
@output_methods ||= []
|
54
|
+
method, options = arg.split(":")
|
55
|
+
hsh = {}
|
56
|
+
if options
|
57
|
+
CSV.parse_line(options).map {|x| x.split("=")}.each {|k,v| hsh[k] = v}
|
58
|
+
end
|
59
|
+
@output_methods << [method, hsh]
|
60
|
+
end
|
61
|
+
o.on("-v", "--verbose", "Print debugging information") do # levels
|
62
|
+
$verbose = true
|
63
|
+
end
|
64
|
+
o.on("-u", "--user-mode",
|
65
|
+
"Allow an arbitrary user to run hostlint") do
|
66
|
+
@user_run = true
|
67
|
+
end
|
68
|
+
|
69
|
+
o.on_tail('-h', '--help',
|
70
|
+
'print usage information and exit') do
|
71
|
+
puts o
|
72
|
+
puts <<FILE
|
73
|
+
|
74
|
+
Hostlint is like the lint program for source code, but for hosts.
|
75
|
+
Full documentation currently lives at https://github.com/whd/hostlint .
|
76
|
+
|
77
|
+
Output methods: (syntax for options is M[:opt1=val1[,opt2=val2...]])
|
78
|
+
|
79
|
+
stdout: print a concise summary of what checks ran and if they succeeded to the standard output (no options)
|
80
|
+
|
81
|
+
log: like stdout, but be more verbose by printing the stdout and stderr of each check and exit status if applicable
|
82
|
+
options: file (optional, defaults to '/var/log/hostlint.log', use '-' for standard out)
|
83
|
+
|
84
|
+
syslog: print failing checks to the system log
|
85
|
+
options: severity
|
86
|
+
|
87
|
+
yaml: output in yaml format
|
88
|
+
options: file (optional, defaults to '-', which is stdout)
|
89
|
+
|
90
|
+
amqp: send serialized results (to, say, a hostmon server) via amqp
|
91
|
+
options: host, port, user, pass, vhost, exchange=hostlint
|
92
|
+
|
93
|
+
terse: don't print anything except the summary
|
94
|
+
|
95
|
+
email: not implemented
|
96
|
+
|
97
|
+
post: not implemented
|
98
|
+
FILE
|
99
|
+
exit
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
|
104
|
+
optparser.parse!
|
105
|
+
abort "need to run as root (or -u)" unless @user_run || Process.euid == 0
|
106
|
+
|
107
|
+
logd "parsed command line"
|
108
|
+
|
109
|
+
@config_file ||= Pathname.new("hostlint.yml")
|
110
|
+
unless @config_file.exist? && @config_file.file? && @config_file.readable?
|
111
|
+
loge "config file #{@config_file} nonexistent or unreadable (use -f)"
|
112
|
+
abort
|
113
|
+
end
|
114
|
+
|
115
|
+
@conf = YAML.load(@config_file.read)
|
116
|
+
loge "parsing config file failed: #{@config_file}" and abort unless @conf
|
117
|
+
loge "no :main key in #{@config_file}" and abort unless @conf[:main]
|
118
|
+
@conf = Map(@conf)
|
119
|
+
|
120
|
+
# FIXME if check_dir is relative, it should resolve relative to the
|
121
|
+
# config file instead of the current working directory
|
122
|
+
# (do this without using chdir crap)
|
123
|
+
@check_dir ||= @conf[:main][:check_dir]
|
124
|
+
@host = @conf[:main][:host]
|
125
|
+
@cluster ||= @conf[:main][:colo]
|
126
|
+
unless @host && @cluster
|
127
|
+
h = %x[hostname].chomp.split(".")
|
128
|
+
@host ||= h[0]
|
129
|
+
@cluster ||= h[1]
|
130
|
+
end
|
131
|
+
|
132
|
+
if @conf[:output]
|
133
|
+
@output_methods ||= @conf[:output].map do |x|
|
134
|
+
x.respond_to?(:to_a) ? x.to_a : [x, nil]
|
135
|
+
end.map(&:first)
|
136
|
+
end
|
137
|
+
@jobs ||= 1
|
138
|
+
@output_methods ||= [["stdout", {}]]
|
139
|
+
@check_dir = Pathname.new(@check_dir)
|
140
|
+
|
141
|
+
oldpwd = Dir.pwd
|
142
|
+
Dir.chdir(@config_file.dirname)
|
143
|
+
unless @check_dir.directory? && @check_dir.readable?
|
144
|
+
loge "#{@check_dir.expand_path} not a directory or unreadable" and abort
|
145
|
+
end
|
146
|
+
|
147
|
+
@check_dir = @check_dir.realpath
|
148
|
+
Dir.chdir(oldpwd)
|
149
|
+
|
150
|
+
Check.global_info = {:host => @host, :cluster => @cluster}
|
151
|
+
logd "info:\n check_dir: #{@check_dir}" +
|
152
|
+
"\n host: #{@host}\n cluster: #{@cluster}\n jobs: #{@jobs}" +
|
153
|
+
"\n output: #{@output_methods.inspect}"
|
154
|
+
end
|
155
|
+
|
156
|
+
def check_name (f)
|
157
|
+
foo = Pathname.new(f).relative_path_from(Pathname.new(@check_dir)).to_s
|
158
|
+
foo.chomp(File.extname(f)).gsub(File::SEPARATOR, ".")
|
159
|
+
end
|
160
|
+
|
161
|
+
def theme
|
162
|
+
@checks = []
|
163
|
+
Dir.glob("#{@check_dir}/**/*").each do |f|
|
164
|
+
path = Pathname.new(f)
|
165
|
+
next unless path.file?
|
166
|
+
# FIXME add subdir support to -c (and document)
|
167
|
+
# also add a warning when checks specified with -c don't end up matching
|
168
|
+
# a file
|
169
|
+
if @checks_filter &&
|
170
|
+
!@checks_filter.find {|c| f =~ /^#{c}\.?[^\/]*$|.*\/#{c}\.?[^\/]*$/}
|
171
|
+
next
|
172
|
+
end
|
173
|
+
check = Check.new(check_name(path), path)
|
174
|
+
check.run
|
175
|
+
@checks << check
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def variations
|
180
|
+
logi "SUMMARY"
|
181
|
+
if m = @checks.map {|c| c.name.length}.max
|
182
|
+
max = m + 2
|
183
|
+
end
|
184
|
+
@output_methods.each do |m, opts|
|
185
|
+
opts ||= {}
|
186
|
+
# FIXME maxl -> global_info
|
187
|
+
opts["maxl"] = max if max
|
188
|
+
method = "output_" + m.to_s
|
189
|
+
if Check.respond_to?(method)
|
190
|
+
Check.send(method, opts)
|
191
|
+
else
|
192
|
+
logw "no output method '#{m}', ignoring"
|
193
|
+
break
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
def stats
|
199
|
+
check_ok = 0
|
200
|
+
check_failed = 0
|
201
|
+
failed = 0
|
202
|
+
@checks.each do |c|
|
203
|
+
check_ok += 1 if c.check_ok?
|
204
|
+
check_failed += 1 if c.check_failed?
|
205
|
+
failed += 1 if c.failed?
|
206
|
+
end
|
207
|
+
"#{check_ok + check_failed + failed} checks total, " +
|
208
|
+
"#{check_ok} succeeded, #{check_failed} failed," +
|
209
|
+
" #{failed} failed to run properly"
|
210
|
+
end
|
211
|
+
|
212
|
+
def run
|
213
|
+
report_begin = Time.now
|
214
|
+
prelude # parse command line and configs
|
215
|
+
theme # run the checks
|
216
|
+
variations # output in various ways
|
217
|
+
report_end = Time.now
|
218
|
+
|
219
|
+
logi "hostlint check complete (#{report_end - report_begin}s)"
|
220
|
+
logi stats
|
221
|
+
end
|
222
|
+
|
223
|
+
end
|
224
|
+
|
225
|
+
class Check
|
226
|
+
@@checks = []
|
227
|
+
@@status_map = {
|
228
|
+
:CHECK_OK => 0b000000,
|
229
|
+
:CHECK_FAILED => 0b000001,
|
230
|
+
:FAILED_MISSING_PRELUDE => 0b000100,
|
231
|
+
:FAILED_STDERR_NONEMPTY => 0b001000,
|
232
|
+
:FAILED_BAD_EXIT_STATUS => 0b010000,
|
233
|
+
:FAILED_SKIPPED => 0b100000
|
234
|
+
}
|
235
|
+
|
236
|
+
@@global_info = {}
|
237
|
+
def self.global_info= (hsh)
|
238
|
+
@@global_info = hsh
|
239
|
+
end
|
240
|
+
|
241
|
+
attr_accessor :name, :exit_status, :status, :timestamp, :program, :stdout, :stderr
|
242
|
+
def initialize (name, program)
|
243
|
+
@name = name
|
244
|
+
@program = program
|
245
|
+
@status = []
|
246
|
+
@stdout, @stderr = '', ''
|
247
|
+
@@checks << self
|
248
|
+
end
|
249
|
+
|
250
|
+
def int_status
|
251
|
+
# first legitimate reason to put :| in source code I've seen
|
252
|
+
@status.map {|x| @@status_map[x]}.inject(:|)
|
253
|
+
end
|
254
|
+
|
255
|
+
# run @program, set name from first line etc. as appropriate
|
256
|
+
def run
|
257
|
+
unless @program.executable_real?
|
258
|
+
logw "#{@program} not executable, skipping"
|
259
|
+
status << :FAILED_SKIPPED
|
260
|
+
# make sure to set a timestamp
|
261
|
+
@timestamp = Time.now.to_s
|
262
|
+
return
|
263
|
+
end
|
264
|
+
regex = /^((.*):\s+)?(OK|FAIL)$/
|
265
|
+
logd "executing #{program}"
|
266
|
+
|
267
|
+
# FIXME on bad exit status, don't remove the prelude from the output stream
|
268
|
+
_status = Open4::popen4(program.to_s) do |pid, stdin, stdout, stderr|
|
269
|
+
out = stdout.read
|
270
|
+
err = stderr.read
|
271
|
+
if match = (out.lines.first||"").match(regex)
|
272
|
+
check_name = match[2]
|
273
|
+
@name = check_name if check_name
|
274
|
+
status << (match[3] == "OK" ? :CHECK_OK : :CHECK_FAILED)
|
275
|
+
out.sub!(out.lines.first, "")
|
276
|
+
else
|
277
|
+
logw "check #{@name} missing prelude"
|
278
|
+
status << :FAILED_MISSING_PRELUDE
|
279
|
+
end
|
280
|
+
|
281
|
+
if err.size > 0
|
282
|
+
logw "standard error for check #{@name} not empty"
|
283
|
+
status << :FAILED_STDERR_NONEMPTY
|
284
|
+
end
|
285
|
+
@stdout = out
|
286
|
+
@stderr = err
|
287
|
+
end
|
288
|
+
|
289
|
+
@exit_status = _status.exitstatus
|
290
|
+
|
291
|
+
if @exit_status != 0
|
292
|
+
logw "check #{@name} had nonzero exit status"
|
293
|
+
status << :FAILED_BAD_EXIT_STATUS
|
294
|
+
end
|
295
|
+
@timestamp = Time.now.to_s
|
296
|
+
end
|
297
|
+
|
298
|
+
def to_s
|
299
|
+
inspect
|
300
|
+
end
|
301
|
+
|
302
|
+
def inspect
|
303
|
+
result.inspect
|
304
|
+
end
|
305
|
+
|
306
|
+
def to_hash
|
307
|
+
@@global_info.merge({
|
308
|
+
:name => @name,
|
309
|
+
:exit_status => @exit_status,
|
310
|
+
:status => @status,
|
311
|
+
:timestamp => @timestamp,
|
312
|
+
:stdout => @stdout,
|
313
|
+
:stderr => @stderr
|
314
|
+
})
|
315
|
+
end
|
316
|
+
|
317
|
+
alias result to_hash
|
318
|
+
|
319
|
+
def check_ok?
|
320
|
+
int_status == 0
|
321
|
+
end
|
322
|
+
|
323
|
+
def check_failed?
|
324
|
+
int_status == 1
|
325
|
+
end
|
326
|
+
|
327
|
+
def failed?
|
328
|
+
int_status > 1
|
329
|
+
end
|
330
|
+
|
331
|
+
# output formats
|
332
|
+
# FIXME make instance methods (like before) and avoid duplication
|
333
|
+
|
334
|
+
# syslog
|
335
|
+
# fixme add log_healthy=false option
|
336
|
+
def self.output_syslog (opts={})
|
337
|
+
puts "output syslog"
|
338
|
+
Syslog.open unless Syslog.opened?
|
339
|
+
@@checks.each do |c|
|
340
|
+
if c.int_status > 0
|
341
|
+
# hostlint checks shouldn't really be immediately actionable
|
342
|
+
Syslog.notice "check #{c.name} failed #{c.status.inspect.gsub(":", "")}"
|
343
|
+
end
|
344
|
+
end
|
345
|
+
Syslog.close
|
346
|
+
end
|
347
|
+
|
348
|
+
# stdout
|
349
|
+
def self.output_stdout (opts={})
|
350
|
+
n = opts["maxl"]
|
351
|
+
@@checks.each do |c|
|
352
|
+
printf "%-#{n}s %s\n" % [c.name+":", c.status.inspect.gsub(":", "")]
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
# log, more verbose than stdout
|
357
|
+
# FIXME make formatting not suck, remove code duplication
|
358
|
+
def self.output_log (opts={})
|
359
|
+
file = opts["file"] || '/var/log/hostlint.log'
|
360
|
+
h1 = "hostlint report #{Time.now}"
|
361
|
+
header = "#{'*'*h1.length}\n#{h1}\n#{'*'*h1.length}"
|
362
|
+
if file == '-'
|
363
|
+
n = opts["maxl"]
|
364
|
+
STDOUT.puts header
|
365
|
+
@@checks.each do |c|
|
366
|
+
STDOUT.printf "%-#{n}s %s\n" % [c.name+":", c.status.inspect.gsub(":", "")]
|
367
|
+
if c.exit_status && c.exit_status != 0
|
368
|
+
STDOUT.puts " EXIT_STATUS: #{c.exit_status}"
|
369
|
+
end
|
370
|
+
unless c.stderr.empty?
|
371
|
+
STDOUT.puts " STDERR:"
|
372
|
+
STDOUT.puts c.stderr.gsub(/^/, "\t")
|
373
|
+
end
|
374
|
+
unless c.stdout.empty?
|
375
|
+
STDOUT.puts " STDOUT:"
|
376
|
+
STDOUT.puts c.stdout.gsub(/^/, "\t")
|
377
|
+
end
|
378
|
+
end
|
379
|
+
else
|
380
|
+
begin
|
381
|
+
File.open(file, "a") do |f|
|
382
|
+
f.puts header
|
383
|
+
@@checks.each do |c|
|
384
|
+
f.printf "%-#{n}s %s\n" % [c.name+":", c.status.inspect.gsub(":", "")]
|
385
|
+
if c.exit_status && c.exit_status != 0
|
386
|
+
f.puts " EXIT_STATUS: #{c.exit_status}"
|
387
|
+
end
|
388
|
+
unless c.stderr.empty?
|
389
|
+
f.puts " STDERR:"
|
390
|
+
f.puts c.stderr.gsub(/^/, "\t")
|
391
|
+
end
|
392
|
+
unless c.stdout.empty?
|
393
|
+
f.puts " STDOUT:"
|
394
|
+
f.puts c.stdout.gsub(/^/, "\t")
|
395
|
+
end
|
396
|
+
end
|
397
|
+
f.puts
|
398
|
+
end
|
399
|
+
rescue
|
400
|
+
loge "error writing to log file #{file}"
|
401
|
+
end
|
402
|
+
end
|
403
|
+
end
|
404
|
+
|
405
|
+
def self.output_terse(opts={})
|
406
|
+
end
|
407
|
+
|
408
|
+
# yaml
|
409
|
+
def self.output_yaml (opts={})
|
410
|
+
file = opts["file"] || '-'
|
411
|
+
if file == '-'
|
412
|
+
@@checks.each {|c| STDOUT.puts c.result.to_yaml}
|
413
|
+
else
|
414
|
+
begin
|
415
|
+
File.open(file, "a") do |f|
|
416
|
+
@@checks.each {|c| f.puts c.result.to_yaml}
|
417
|
+
end
|
418
|
+
rescue
|
419
|
+
loge "error writing to log file #{file}"
|
420
|
+
end
|
421
|
+
end
|
422
|
+
end
|
423
|
+
|
424
|
+
# amqp
|
425
|
+
def self.output_amqp (opts)
|
426
|
+
puts "output amqp"
|
427
|
+
connection_params = {}
|
428
|
+
connection_params[:port] = opts['port']
|
429
|
+
connection_params[:host] = opts['host']
|
430
|
+
connection_params[:user] = opts['user']
|
431
|
+
connection_params[:pass] = opts['pass']
|
432
|
+
connection_params[:vhost] = opts['vhost']
|
433
|
+
begin
|
434
|
+
amqp = Bunny.new(connection_params)
|
435
|
+
amqp.start
|
436
|
+
exchange = amqp.exchange(opts['exchange'] || 'hostlint',
|
437
|
+
:type => :topic,
|
438
|
+
:durable => true)
|
439
|
+
@@checks.each do |c|
|
440
|
+
exchange.publish c.result.to_yaml
|
441
|
+
end
|
442
|
+
amqp.close
|
443
|
+
rescue
|
444
|
+
'amqp transport failed'
|
445
|
+
end
|
446
|
+
end
|
447
|
+
end
|
448
|
+
|
449
|
+
Main.new.run
|
@@ -0,0 +1,22 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
fail=""
|
4
|
+
match="BOOTPROTO=dhcp"
|
5
|
+
|
6
|
+
for file in /etc/sysconfig/networking/devices/* ; do
|
7
|
+
if [[ -f $file ]] ; then
|
8
|
+
output="$(grep -i $match $file &>/dev/null)"
|
9
|
+
if [[ $? == 0 ]]; then
|
10
|
+
fail="${fail}
|
11
|
+
${file}"
|
12
|
+
fi
|
13
|
+
fi
|
14
|
+
done
|
15
|
+
|
16
|
+
if [[ -z "$fail" ]] ; then
|
17
|
+
echo "OK"
|
18
|
+
else
|
19
|
+
echo "network.config.dhcp: [FAIL]"
|
20
|
+
echo -n "devices configured to use ${match}:"
|
21
|
+
echo "$fail"
|
22
|
+
fi
|
@@ -0,0 +1,18 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
# "verify that eth0 and eth1 are bonded"
|
4
|
+
# this should likely be generalized
|
5
|
+
|
6
|
+
if [[ -d /proc/net/bonding ]] ; then
|
7
|
+
for file in /proc/net/bonding/* ; do
|
8
|
+
if grep 'eth0' $file &>/dev/null && grep 'eth1' $file &>/dev/null ; then
|
9
|
+
echo "OK"
|
10
|
+
exit 0
|
11
|
+
fi
|
12
|
+
done
|
13
|
+
fi
|
14
|
+
|
15
|
+
# fell thru
|
16
|
+
echo "FAIL
|
17
|
+
output of ifconfig:
|
18
|
+
$(ifconfig 2>/dev/null)"
|
@@ -0,0 +1,12 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
# requirith yum-plugin-security
|
3
|
+
|
4
|
+
output="$(yum --security check-update 2>/dev/null)"
|
5
|
+
|
6
|
+
if [[ $? == 100 ]] ; then #security updates exist!
|
7
|
+
echo "FAIL"
|
8
|
+
echo "you could try \"yum --security update\""
|
9
|
+
echo "$output" | tail -n +3
|
10
|
+
else
|
11
|
+
echo "OK"
|
12
|
+
fi
|
@@ -0,0 +1 @@
|
|
1
|
+
#!/bin/bash
|
File without changes
|
@@ -0,0 +1,8 @@
|
|
1
|
+
#!/usr/bin/env python
|
2
|
+
|
3
|
+
print """ok.python.works: OK
|
4
|
+
STATELY, PLUMP BUCK MULLIGAN CAME FROM THE STAIRHEAD, bearing a bowl of
|
5
|
+
lather on which a mirror and a razor lay crossed. A yellow dressinggown,
|
6
|
+
ungirdled, was sustained gently behind him by the mild morning air. He
|
7
|
+
held the bowl aloft and intoned:
|
8
|
+
""",
|
data/tests/config.yml
ADDED
data/tests/test.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# simple test wrapper, must be run from __FILE__/.
|
3
|
+
# subdirs of checks/ indicate which checks should pass, fail, and fail to run
|
4
|
+
|
5
|
+
output = %x[../bin/hostlint -u -f config.yml]
|
6
|
+
status = 0
|
7
|
+
output.each_line do |line|
|
8
|
+
next if line =~ /^INFO/
|
9
|
+
r = /^ok.*\[CHECK_OK\]$|^fail.*\[CHECK_FAILED\]$|exec_fail.*\[.*FAILED_.*$/
|
10
|
+
unless line =~ r
|
11
|
+
puts "error: line doesn't match expectations"
|
12
|
+
puts line
|
13
|
+
status = 1
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
if status == 0
|
18
|
+
puts "all tests pass"
|
19
|
+
end
|
20
|
+
exit status
|
metadata
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: hostlint
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Wesley Dawson
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-07-03 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: open4
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: bunny
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: map
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
description: hostlint client
|
63
|
+
email:
|
64
|
+
- wdawson@mozilla.com
|
65
|
+
executables:
|
66
|
+
- hostlint
|
67
|
+
extensions: []
|
68
|
+
extra_rdoc_files: []
|
69
|
+
files:
|
70
|
+
- examples/config/hostlint.yml
|
71
|
+
- examples/checks/network/config/bootproto_dhcp.sh
|
72
|
+
- examples/checks/network/ethernet_bonded.sh
|
73
|
+
- examples/checks/yum/security_updates.sh
|
74
|
+
- tests/checks/fail/fail.sh
|
75
|
+
- tests/checks/fail/rename
|
76
|
+
- tests/checks/ok/python.py
|
77
|
+
- tests/checks/ok/succeed_ok_no_body.sh
|
78
|
+
- tests/checks/ok/rename
|
79
|
+
- tests/checks/ok/succeed_ok_body.sh
|
80
|
+
- tests/checks/exec_fail/bad_output.sh
|
81
|
+
- tests/checks/exec_fail/empty_bad_exit_status.sh
|
82
|
+
- tests/checks/exec_fail/fail_stderr_bad_exit_status.rb
|
83
|
+
- tests/checks/exec_fail/bad_exit_status.sh
|
84
|
+
- tests/checks/exec_fail/rename_exec_fail1
|
85
|
+
- tests/checks/exec_fail/rename_exec_fail2
|
86
|
+
- tests/checks/exec_fail/bad_exit_prelude
|
87
|
+
- tests/checks/exec_fail/nonexec
|
88
|
+
- tests/checks/exec_fail/missing_prelude.sh
|
89
|
+
- tests/checks/exec_fail/no_output.sh
|
90
|
+
- tests/test.rb
|
91
|
+
- tests/config.yml
|
92
|
+
- bin/hostlint
|
93
|
+
homepage: https://github.com/whd/hostlint
|
94
|
+
licenses:
|
95
|
+
- Mozilla Public License (2.0)
|
96
|
+
post_install_message:
|
97
|
+
rdoc_options: []
|
98
|
+
require_paths:
|
99
|
+
- lib
|
100
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
101
|
+
none: false
|
102
|
+
requirements:
|
103
|
+
- - ! '>='
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: '0'
|
106
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
107
|
+
none: false
|
108
|
+
requirements:
|
109
|
+
- - ! '>='
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '0'
|
112
|
+
requirements: []
|
113
|
+
rubyforge_project:
|
114
|
+
rubygems_version: 1.8.24
|
115
|
+
signing_key:
|
116
|
+
specification_version: 3
|
117
|
+
summary: hostlint -- lint for hosts
|
118
|
+
test_files: []
|