hostlint 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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,12 @@
1
+ :main:
2
+ :colo: cluster1
3
+ :host: mach1
4
+ :check_dir: "../checks" # "/etc/hostlint.d"
5
+
6
+ :output:
7
+ - :log:
8
+ :file: "./log/hostlint.log"
9
+ - :amqp:
10
+ host: localhost
11
+ user: guest
12
+ pass: guest
@@ -0,0 +1,5 @@
1
+ #! /usr/bin/bash
2
+ echo fail_stderr >&2
3
+ echo fail "bad prelude"
4
+ exit 123
5
+
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ echo 'OK
3
+ This check should EXEC_FAIL because of its exit status (even though the
4
+ prelude is correct).
5
+ '
6
+ # but exit badly
7
+ exit 1
@@ -0,0 +1,2 @@
1
+ #!/bin/bash
2
+ echo "--INTROIBO AD ALTARE DEI. (first line doesn't match prelude)"
@@ -0,0 +1,2 @@
1
+ #!/bin/bash
2
+ exit 1
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+ # EXEC_FAIL because there is output on the standard error
3
+
4
+ STDERR.puts "shoot"
5
+ puts "OK
6
+ --Come up, Kinch! Come up, you fearful jesuit!
7
+ "
8
+ exit 1
@@ -0,0 +1,3 @@
1
+ #!/bin/bash
2
+
3
+ echo "this should fail because the prelude is missing"
@@ -0,0 +1 @@
1
+ #!/bin/bash
File without changes
@@ -0,0 +1,3 @@
1
+ #! /bin/sh
2
+ echo "exec_fail.rename.check1: OK"
3
+ exit 1
@@ -0,0 +1,3 @@
1
+ #! /bin/sh
2
+ echo "exec_fail.rename.check2: FAIL"
3
+ exit 1
@@ -0,0 +1,5 @@
1
+ #!/bin/bash
2
+
3
+ echo 'fail.test_failure: FAIL
4
+ Buck Mulligan peeped an instant under the mirror and then covered
5
+ the bowl smartly.'
@@ -0,0 +1,2 @@
1
+ #!/bin/sh
2
+ echo "fail.renamed_check_fail: FAIL"
@@ -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
+ """,
@@ -0,0 +1,2 @@
1
+ #!/bin/sh
2
+ echo "ok.renamed_check: OK"
@@ -0,0 +1,5 @@
1
+ #!/bin/bash
2
+
3
+ echo 'OK
4
+ --Thanks, old chap, he cried briskly. That will do nicely. Switch off the
5
+ current, will you?'
@@ -0,0 +1,2 @@
1
+ #!/bin/bash
2
+ echo "OK"
data/tests/config.yml ADDED
@@ -0,0 +1,9 @@
1
+ # test configuration
2
+ :main:
3
+ :colo: cluster1
4
+ :host: mach1
5
+ :check_dir: "./checks" # "/etc/hostlint.d"
6
+
7
+ :output:
8
+ - :stdout
9
+
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: []