summaryrefslogtreecommitdiff
path: root/files/master/lastruncheck
blob: fe67d2100de9b351b856c7d58307f6e29ad147a2 (plain)
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
#!/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 |fqdn|
          if node = Puppet::Node::Facts.find(fqdn)
            { :hostname => node.name, :expired => node.expired?, :timestamp => node.values[:_timestamp], :expiration => node.expiration }
          end
        end.compact
      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