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
|
#!/usr/bin/env ruby
require 'puppet/application'
module Puppet::Lastcheck
module Puppet::Lastcheck::Tests
def self.included(klass)
klass.extend ClassMethods
end
def self.tests
@tests ||= {}
end
module ClassMethods
def add_test(name, options={})
include Puppet::Lastcheck::Tests.const_get(name.to_s.split('_').collect{|s| s.capitalize }.join(''))
Puppet::Lastcheck::Tests.tests[name] = options
attr_accessor "ignore_#{name}".to_sym
option("--ignore-#{name.to_s.gsub(/_/,'-')}") do
self.send("ignore_#{name}=", true)
end
end
end
module Util
def facts_hosts
return @facts_hosts if @facts_hosts
require 'puppet/indirector/facts/yaml'
@facts_hosts = Puppet::Node::Facts.search("*").collect do |node|
{ :hostname => node.name, :expired => node.expired?, :timestamp => node.values[:_timestamp], :expiration => node.expiration }
end
end
end
end
module Puppet::Lastcheck::Reports
def self.included(klass)
klass.extend ClassMethods
end
def ordered_reports
@ordered_reports ||= Puppet::Lastcheck::Reports.reports.keys.sort{|a,b| Puppet::Lastcheck::Reports.reports[a][:priority] <=> Puppet::Lastcheck::Reports.reports[b][:priority] }
end
def self.reports
@reports ||= {}
end
module ClassMethods
def add_report(name, options={})
include Puppet::Lastcheck::Reports.const_get(name.to_s.split('_').collect{|s| s.capitalize }.join(''))
Puppet::Lastcheck::Reports.reports[name] = options
Puppet::Lastcheck::Reports.reports[name][:priority] ||= 100
attr_accessor "report_to_#{name}".to_sym
option("--report-to-#{name.to_s.gsub(/_/,'-')}") do
self.send("report_to_#{name}=", true)
end
end
end
end
end
module Puppet::Lastcheck::Tests::NoFacts
def analyze_no_facts
signed_hosts.each{|host| add_failed_host(host,"No facts available") unless facts_hosts.any?{|fhost| fhost[:hostname] == host } }
end
def setup_no_facts
Puppet::SSL::Host.ca_location = :only
end
private
def signed_hosts
ca.list
end
def ca
@ca ||= Puppet::SSL::CertificateAuthority.new
end
end
module Puppet::Lastcheck::Tests::ExpiredFacts
include Puppet::Lastcheck::Tests::Util
def analyze_expired_facts
facts_hosts.each{|host| add_failed_host(host[:hostname],"Expired at #{host[:expiration]}") if host[:expired] }
end
end
module Puppet::Lastcheck::Tests::TimedOutFacts
include Puppet::Lastcheck::Tests::Util
def analyze_timed_out_facts
require 'time'
facts_hosts.each{|host| add_failed_host(host[:hostname], "Last facts save at #{host[:timestamp]}") if Time.parse(host[:timestamp].to_s) < (Time.now - @timeout) }
end
def setup_timed_out_facts
if @timeout
ignore_expired_facts ||= true
end
end
end
module Puppet::Lastcheck::Tests::Storedconfigs
def analyze_storedconfigs
storedconfigs_hosts.each do |host|
if !facts_hosts.any?{|fact_host| fact_host[:hostname] == host.name }
add_failed_host(host.name, "In storedconfigs but no facts available!")
elsif host.last_compile.nil?
add_failed_host(host.name, "No entry in storedconfigs")
elsif host.last_compile < (Time.now - @timeout)
add_failed_host(host.name, "Last compile time in storedconfigs at #{host.last_compile}")
end
end
end
private
def storedconfigs_hosts
return @storedconfigs_hosts if @storedconfigs_hosts
Puppet::Rails.connect
@storedconfigs_hosts = Puppet::Rails::Host.all
end
end
module Puppet::Lastcheck::Reports::Console
def deliver_report_to_console(failing_hosts)
unless failing_hosts.empty?
puts 'The following hosts are out of date:'
puts '------------------------------------'
host_length = 0
failing_hosts.keys.each{|host| host_length = host.length if host.length > host_length }
failing_hosts.keys.each{ |host| puts "#{pretty_puts(host,host_length)} - Reason: #{failing_hosts[host][:reason]}" }
1
else
0
end
end
end
module Puppet::Lastcheck::Reports::Nagios
def deliver_report_to_nagios(failing_hosts)
unless failing_hosts.empty?
puts "PUPPETLAST CRITICAL: #{failing_hosts.size} outdated hosts: #{failing_hosts.keys.join(',')}"
2
else
puts "PUPPETLAST OK: No outdated hosts"
0
end
end
end
#
# = Synopsis
#
# Verifiying your puppet runs. Check different places to verify
# whether your clients actually still runs successfully.
# Also checks for left overs of legacy hosts.
#
# = Usage
#
# puppet lastcheck [-h|--help]
class Puppet::Application::Lastcheck < Puppet::Application
should_parse_config
run_mode :master
include Puppet::Lastcheck::Tests
add_test :no_facts
add_test :expired_facts, :ignore_by_default => true
add_test :timed_out_facts
add_test :storedconfigs
include Puppet::Lastcheck::Reports
add_report :console, :priority => 50
add_report :nagios
option("--timeout TIMEOUT") do |v|
@timeout = v.to_i
end
option("--ignore-hosts HOSTS") do |v|
@ignore_hosts = v.split(',')
end
def main
Puppet::Lastcheck::Tests.tests.keys.each do |test|
self.send("analyze_#{test}") unless self.send("ignore_#{test}")
end
exitcode = 0
ordered_reports.each do |report|
if self.send("report_to_#{report}")
tmpexitcode = self.send("deliver_report_to_#{report}",@failing_hosts)
exitcode = tmpexitcode unless exitcode > 0
end
end
exit(exitcode)
end
def setup
exit(Puppet.settings.print_configs ? 0 : 1) if Puppet.settings.print_configs?
Puppet::Util::Log.newdestination :console
Puppet::Node::Facts.terminus_class = :yaml
Puppet::Lastcheck::Tests.tests.keys.each do |test|
self.send("ignore_#{test}=", Puppet::Lastcheck::Tests.tests[test][:ignore_by_default]||false) unless self.send("ignore_#{test}")
self.send("setup_#{test}") if self.respond_to?("setup_#{test}") and !self.send("ignore_#{test}")
end
report = nil
report_activated = false
ordered_reports.each do |report|
report_activated ||= self.send("report_to_#{report}")
end
self.report_to_console = true unless report_activated
@ignore_hosts = [] unless @ignore_hosts
@failing_hosts = {}
unless @timeout
@timeout = Puppet[:runinterval]
end
end
private
def add_failed_host(hostname,reason)
@failing_hosts[hostname] = { :reason => reason } unless (@failing_hosts[hostname] || @ignore_hosts.include?(hostname))
end
def pretty_puts(str,length)
sprintf("%0-#{length}s",str)
end
end
Puppet::Application.find('lastcheck').new.run
|