2 # This file was modified from its original version at
3 # https://github.com/ripienaar/puppet-reportprint/
5 # Copyright 2013-2016 R.I.Pienaar and contributors
7 # Licensed under the Apache License, Version 2.0 (the "License");
8 # you may not use this file except in compliance with the License.
9 # You may obtain a copy of the License at
11 # http://www.apache.org/licenses/LICENSE-2.0
13 # Unless required by applicable law or agreed to in writing, software
14 # distributed under the License is distributed on an "AS IS" BASIS,
15 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 # See the License for the specific language governing permissions and
17 # limitations under the License.
23 def get_server_reports_dir
24 Puppet
.settings
[:reportdir]
29 # Prevent nonsense values being returned for fractions
31 units
= ['B', 'KB', 'MB' ,'GB' ,'TB']
32 e
= (Math
.log(self)/Math
.log(1024)).floor
35 s
= "%.2f " % (to_f
/ 1024**e
)
36 s
.sub(/\.?0*$/, units
[e
])
47 def report_resources(report
)
48 report
.resource_statuses
51 def resource_with_evaluation_time(report
)
52 report_resources(report
).select
{|r_name
, r
| !r
.evaluation_time
.nil? }
55 def resource_by_eval_time(report
)
56 report_resources(report
).reject
{|r_name
, r
| r
.evaluation_time
.nil? }.sort_by
{|r_name
, r
| r
.evaluation_time
rescue 0}
59 def resources_of_type(report
, type
)
60 report_resources(report
).select
{|r_name
, r
| r
.resource_type
== type
}
63 def color(code
, msg
, reset
=false)
70 :underline => "\e[4m",
75 :changed => colors
[:yellow],
76 :unchanged => colors
[:green],
77 :failed => colors
[:red]
80 return "%s%s%s%s" % [colors
.fetch(code
, ""), msg
, colors
[:reset], reset
? colors
.fetch(reset
, "") : ""] if @options[:color]
85 def print_report_summary(report
)
86 puts
color(:bold, "Report for %s in environment %s at %s" % [color(:underline, report
.host
, :bold), color(:underline, report
.environment
, :bold), color(:underline, report
.time
, :bold)])
88 puts
" Report File: %s" % @options[:report]
89 puts
" Report Status: %s" % report
.status
90 puts
" Puppet Version: %s" % report
.puppet_version
91 puts
" Report Format: %s" % report
.report_format
92 puts
" Configuration Version: %s" % report
.configuration_version
93 puts
" UUID: %s" % report
.transaction_uuid
rescue nil
94 puts
" Log Lines: %s %s" % [report
.logs
.size
, @options[:logs] ? "" : "(show with --log)"]
99 def print_report_motd(report
, motd_path
)
101 header
= "# #{report.host} #"
102 headline
= "#" * header
.size
103 motd
<< headline
<< header
<< headline
<< ''
105 motd
<< "Last puppet run happened at %s in environment %s." % [report
.time
, report
.environment
]
107 motd
<< "The result of this puppet run was %s." % color(report
.status
.to_sym
, report
.status
)
109 if report
.metrics
.empty
? or report
.metrics
["events"].nil?
110 motd
<< 'No Report Metrics.'
113 report
.metrics
["events"].values
.each
do |metric
|
115 motd
.last
<< ' ' << [m
, v
].join(': ') << '.'
121 File
.write(motd_path
, motd
.join("\n"))
124 def print_report_metrics(report
)
125 if report
.metrics
.empty
?
126 puts
color(:bold, "No Report Metrics")
131 puts
color(:bold, "Report Metrics:")
134 padding
= report
.metrics
.map
{|i
, m
| m
.values
}.flatten(1).map
{|i
, m
, v
| m
.size
}.sort
[-1] +
6
136 report
.metrics
.sort_by
{|i
, m
| m
.label
}.each
do |i
, metric
|
137 puts
" %s:" % metric
.label
139 metric
.values
.sort_by
{|j
, m
, v
| v
}.reverse
.each
do |j
, m
, v
|
140 puts
"%#{padding}s: %s" % [m
, v
]
149 def print_summary_by_type(report
)
152 report_resources(report
).each
do |resource
|
153 if resource
[0] =~
/^(.+?)\[/
159 STDERR.puts
"ERROR: Cannot parse type %s" % resource
[0]
163 puts
color(:bold, "Resources by resource type:")
166 summary
.sort_by
{|k
, v
| v
}.reverse
.each
do |type
, count
|
167 puts
" %4d %s" % [count
, type
]
173 def print_slow_resources(report
, number
=20)
174 if report
.report_format
< 4
175 puts
color(:red, " Cannot print slow resources for report versions %d" % report
.report_format
)
180 resources
= resource_by_eval_time(report
)
182 number
= resources
.size
if resources
.size
< number
184 puts
color(:bold, "Slowest %d resources by evaluation time:" % number
)
187 resources
[(0-number
)..-1].reverse
.each
do |r_name
, r
|
188 puts
" %7.2f %s" % [r
.evaluation_time
, r_name
]
194 def print_logs(report
)
195 puts
color(:bold, "%d Log lines:" % report
.logs
.size
)
198 report
.logs
.each
do |log
|
199 puts
" %s" % log
.to_report
205 def print_summary_by_containment_path(report
, number
=20)
206 resources
= resource_with_evaluation_time(report
)
208 containment
= Hash
.new(0)
210 resources
.each
do |r_name
, r
|
211 r
.containment_path
.each
do |containment_path
|
212 #if containment_path !~ /\[/
213 containment
[containment_path
] +
= r
.evaluation_time
218 number
= containment
.size
if containment
.size
< number
220 puts
color(:bold, "%d most time consuming containment" % number
)
223 containment
.sort_by
{|c
, s
| s
}[(0-number
)..-1].reverse
.each
do |c_name
, evaluation_time
|
224 puts
" %7.2f %s" % [evaluation_time
, c_name
]
230 def print_files(report
, number
=20)
231 resources
= resources_of_type(report
, "File")
235 resources
.each
do |r_name
, r
|
236 if r_name
=~
/^File\[(.+)\]$/
239 if File
.exist
?(file
) && File
.readable
?(file
) && File
.file
?(file
) && !File
.symlink
?(file
)
240 files
[file
] = File
.size
?(file
) || 0
245 number
= files
.size
if files
.size
< number
247 puts
color(:bold, "%d largest managed files" % number
) +
" (only those with full path as resource name that are readable)"
250 files
.sort_by
{|f
, s
| s
}[(0-number
)..-1].reverse
.each
do |f_name
, size
|
251 puts
" %9s %s" % [size
.bytes_to_human
, f_name
]
257 def get_reports_for_node(nodename
)
258 Dir
.glob("%s/%s/*.yaml" % [get_server_reports_dir
, nodename
]).sort_by
{|p
|File
.basename(p
, ".*")}
261 def load_report_for_node(nodename
, report
)
262 report_path
= "%s/%s/%s.yaml" % [get_server_reports_dir
, nodename
, report
]
264 load_report(report_path
) unless report_path
.nil?
267 def load_report_by_id(report
)
268 report_glob
= "%s/*/%s.yaml" % [get_server_reports_dir
, report
]
269 Dir
.glob(report_glob
).map
do |report_path
|
271 load_report(report_path
) unless report_path
.nil?
275 def load_last_report_for_node(nodename
)
276 report_path
= get_reports_for_node(nodename
).last
277 load_report(report_path
) unless report_path
.nil?
280 def print_reports_for_node(nodename
)
281 puts
color(:bold, "Reports for %s" % nodename
)
282 get_reports_for_node(nodename
).each
do |report_path
|
283 prefix
= File
.basename(report_path
, ".*")
284 report
= load_report(report_path
)
285 print_report_oneliner(report
, prefix
)
289 def print_report_oneliner(report
, prefix
)
290 puts
"%s: %s" % [prefix
, color(report
.status
.to_sym
, report
.status
)]
293 def print_node_oneliner(nodename
)
294 report
= load_last_report_for_node(nodename
)
295 print_report_oneliner(report
, report
.name
) unless report
.nil?
298 def print_server_nodes_status
299 puts
color(:bold, 'Nodes list')
300 dir
= get_server_reports_dir
301 puts
color(:bold, 'No nodes found!') unless Puppet::FileSystem.exist?(dir)
302 Dir.glob("%s/*/" % dir).each do |node_path|
303 print_node_oneliner(File.basename(node_path))
307 def initialize_puppet
308 require 'puppet
/util/run_mode
'
309 Puppet.settings.preferred_run_mode = :agent
310 Puppet.settings.initialize_global_settings([])
311 Puppet.settings.initialize_app_defaults(Puppet::Settings.app_defaults_for_run_mode(Puppet.run_mode))
316 opt = OptionParser.new
324 :motd_path => '/etc/motd
',
326 :report => Puppet[:lastrunreport],
328 :color => STDOUT.tty?}
330 opt.on("--logs", "Show logs") do |val|
331 @options[:logs] = val
334 opt.on("--nodelist", "(Puppet Server) List Puppet nodes and the status of their last report") do |val|
335 @options[:server] = val
338 opt.on("--node [NODE]", "(Puppet Server) Use last report of a node") do |val|
339 @options[:node] = val
342 opt.on("--history", "(with --node) Print the reports history for a node") do |val|
343 @options[:history] = val
346 opt.on("--motd", "Produce an output suitable for MOTD") do |val|
347 @options[:motd] = val
350 opt.on("--motd-path [PATH]", "Path to the MOTD file to overwrite with the --motd option") do |val|
351 @options[:motd_path] = val
354 opt.on("--count [RESOURCES]", Integer, "Number of resources to show evaluation times for") do |val|
355 @options[:count] = val
358 opt.on("--report [REPORT]", "Path to the Puppet last run report") do |val|
359 abort("Could not find report %s" % val) unless File.readable?(val)
360 @options[:report] = val
363 opt.on("--report-id [REPORTID]", "(with --node) ID of the report to load") do |val|
364 @options[:reportid] = val
367 opt.on("--[no-]color", "Colorize the report") do |val|
368 @options[:color] = val
373 report
= load_report(@options[:report]) unless @options[:server] or @options[:node]
374 if @options[:node] and not @options[:history] and not @options[:reportid]
375 report
= load_last_report_for_node(@options[:node])
376 elsif @options[:node] and @options[:reportid]
377 report
= load_report_for_node(@options[:node], @options[:reportid])
378 elsif @options[:reportid]
379 report
= load_report_by_id(@options[:reportid])
383 print_server_nodes_status
384 elsif @options[:node] and @options[:history]
385 print_reports_for_node(@options[:node])
386 elsif @options[:motd]
387 print_report_motd(report
, @options[:motd_path])
389 print_report_summary(report
)
390 print_report_metrics(report
)
391 print_summary_by_type(report
)
392 print_slow_resources(report
, @options[:count])
393 print_files(report
, @options[:count])
394 print_summary_by_containment_path(report
, @options[:count])
395 print_logs(report
) if @options[:logs]