File Coverage

blib/lib/Monitoring/TT.pm
Criterion Covered Total %
statement 94 456 20.6
branch 21 200 10.5
condition 2 33 6.0
subroutine 16 35 45.7
pod 3 3 100.0
total 136 727 18.7


line stmt bran cond sub pod time code
1             package Monitoring::TT;
2              
3 4     4   1917 use strict;
  4         4  
  4         89  
4 4     4   12 use warnings;
  4         3  
  4         99  
5 4     4   1927 use utf8;
  4         30  
  4         20  
6 4     4   1879 use Pod::Usage;
  4         133044  
  4         421  
7 4     4   2762 use Getopt::Long;
  4         28643  
  4         14  
8 4     4   2335 use Template;
  4         54015  
  4         102  
9 4     4   1425 use Monitoring::TT::Identifier;
  4         7  
  4         119  
10 4     4   1269 use Monitoring::TT::Log qw/error warn info debug trace log/;
  4         6  
  4         267  
11 4     4   1316 use Monitoring::TT::Object;
  4         7  
  4         86  
12 4     4   1219 use Monitoring::TT::Render;
  4         5  
  4         91  
13 4     4   1217 use Monitoring::TT::Utils;
  4         6  
  4         13725  
14              
15             our $VERSION = '1.0.1';
16              
17             #####################################################################
18              
19             =head1 NAME
20              
21             Monitoring::TT - Generic Monitoring Config based on Template Toolkit Templates
22              
23             =head1 DESCRIPTION
24              
25             Generic Monitoring Config based on Template Toolkit Templates
26              
27             =cut
28              
29             #####################################################################
30              
31             =head1 CONSTRUCTOR
32              
33             =head2 new
34              
35             new(%options)
36              
37             =cut
38              
39             sub new {
40 3     3 1 1307 my($class, %options) = @_;
41 3         12 my $self = {
42             tt_opts => {
43             TRIM => 1,
44             RELATIVE => 1,
45             STAT_TTL => 60,
46             STRICT => 1,
47             }
48             };
49 3         6 bless $self, $class;
50              
51 3 50       10 $self->{'tt_opts'}->{'STRICT'} = 1 if $ENV{'TEST_AUTHOR'};
52 3 50       36 $self->{'tt_opts'}->{'STRICT'} = 1 if -f '.author';
53 3         3 for my $s (@{Monitoring::TT::Identifier::functions('Monitoring::TT::Render')}) {
  3         11  
54 63         38 $self->{'tt_opts'}->{'PRE_DEFINE'}->{$s} = \&{'Monitoring::TT::Render::'.$s};
  63         141  
55             }
56              
57 3         10 return $self;
58             }
59              
60             #####################################################################
61              
62             =head1 METHODS
63              
64             =head2 run
65              
66             run config generator and write it to the output folder
67              
68             =cut
69              
70             sub run {
71 0     0 1 0 my( $self ) = @_;
72 0 0       0 return unless $self->_get_options();
73 0         0 info('generating config from '.join(', ', @{$self->{'in'}}));
  0         0  
74 0         0 info('into '.$self->{'out'});
75 0         0 for my $in (@{$self->{'in'}}) {
  0         0  
76 0 0       0 if(! -d $in.'/.') {
77 0         0 error($in.': '.$!);
78 0         0 exit 1;
79             }
80             }
81 0         0 $self->_run_hook('pre', join(',', @{$self->{'in'}}));
  0         0  
82              
83             # die if output directory already exists
84 0 0 0     0 if(-e $self->{'out'} and !$self->{'opt'}->{'force'}) {
85 0         0 my @files = glob($self->{'out'}.'/*');
86 0 0       0 if(scalar @files > 0) {
87 0         0 error($self->{'out'}.' does already exist and is not empty. (use --force to overwrite contents)');
88 0         0 exit 1;
89             }
90             }
91 0         0 $self->_mkdir_r($self->{'out'});
92              
93 0 0       0 info('using template filter: '.$self->{'opt'}->{'templatefilter'}) if $self->{'opt'}->{'templatefilter'};
94 0 0       0 info('using contact filter: '.$self->{'opt'}->{'contactfilter'}) if $self->{'opt'}->{'contactfilter'};
95 0 0       0 info('using host filter: '.$self->{'opt'}->{'hostfilter'}) if $self->{'opt'}->{'hostfilter'};
96              
97             # reset counter
98 0         0 $self->{'possible_types'} = {};
99 0         0 $self->{'possible_tags'} = {};
100 0         0 $self->{'possible_apps'} = {};
101              
102 0         0 $self->_copy_static_files();
103 0         0 $self->_build_dynamic_config();
104 0 0       0 $self->_check_typos() unless $self->{'opt'}->{'templatefilter'};
105 0 0       0 $self->_print_stats() if $Monitoring::TT::Log::Verbose >= 2;
106 0         0 $self->_run_hook('post', join(',', @{$self->{'in'}}));
  0         0  
107 0         0 info('done');
108 0         0 return 0;
109             }
110              
111             #####################################################################
112              
113             =head2 tt
114              
115             return template toolkit object
116              
117             =cut
118              
119             sub tt {
120 6     6 1 7 my($self) = @_;
121              
122 6 100       30 return $self->{'_tt'} if $self->{'_tt'};
123              
124             # make some globals available in TT stash
125 2         3 $self->{'tt_opts'}->{'PRE_DEFINE'}->{'src'} = $self->{'in'};
126              
127 2         21 $self->{'_tt'} = Template->new($self->{'tt_opts'});
128 2         32760 $Template::Stash::PRIVATE = undef;
129              
130 2         15 return $self->{'_tt'};
131             }
132              
133             #####################################################################
134             # INTERNAL SUBS
135             #####################################################################
136             sub _get_options {
137 0     0   0 my($self) = @_;
138 0         0 Getopt::Long::Configure('no_ignore_case');
139 0         0 Getopt::Long::Configure('bundling');
140 0         0 $self->{'opt'} = {
141             files => [],
142             verbose => 1,
143             force => 0,
144             dryrun => 0,
145             };
146             GetOptions (
147             'h|help' => \$self->{'opt'}->{'help'},
148 0     0   0 'v|verbose' => sub { $self->{'opt'}->{'verbose'}++ },
149             'q|quiet' => \$self->{'opt'}->{'quiet'},
150             'V|version' => \$self->{'opt'}->{'version'},
151             'f|force' => \$self->{'opt'}->{'force'},
152             'cf|contactfilter=s' => \$self->{'opt'}->{'contactfilter'},
153             'hf|hostfilter=s' => \$self->{'opt'}->{'hostfilter'},
154             'tf|templatefilter=s' => \$self->{'opt'}->{'templatefilter'},
155             'n|dry-run' => \$self->{'opt'}->{'dryrun'},
156 0     0   0 '<>' => sub { push @{$self->{'opt'}->{'files'}}, $_[0] },
  0         0  
157 0 0       0 ) or $self->_usage();
158 0 0       0 if($self->{'opt'}->{'version'}) { print 'Version ', $VERSION,"\n"; exit 0; }
  0         0  
  0         0  
159 0 0       0 pod2usage({ -verbose => 2, -exit => 3 } ) if $self->{'opt'}->{'help'};
160 0 0       0 $self->_usage('please specify at least one input and output folder!') if scalar @{$self->{'opt'}->{'files'}} <= 1;
  0         0  
161 0         0 for my $f (@{$self->{'opt'}->{'files'}}) { $f =~ s/\/*$//gmx; }
  0         0  
  0         0  
162 0         0 $self->{'out'} = pop @{$self->{'opt'}->{'files'}};
  0         0  
163 0         0 $self->{'in'} = $self->{'opt'}->{'files'};
164 0 0       0 $self->{'opt'}->{'verbose'} = 0 if $self->{'opt'}->{'quiet'};
165 0 0       0 $self->{'opt'}->{'dryrun'} = 1 if $self->{'opt'}->{'contactfilter'};
166 0 0       0 $self->{'opt'}->{'dryrun'} = 1 if $self->{'opt'}->{'hostfilter'};
167 0 0       0 $self->{'opt'}->{'dryrun'} = 1 if $self->{'opt'}->{'templatefilter'};
168 0         0 $Monitoring::TT::Log::Verbose = $self->{'opt'}->{'verbose'};
169 0 0       0 info('Dry Run, Hooks won\'t be executed') if $self->{'opt'}->{'dryrun'};
170 0         0 return 1;
171             }
172              
173             #####################################################################
174             sub _usage {
175 0     0   0 my($self, $msg) = @_;
176 0 0       0 print $msg, "\n\n" if $msg;
177 0         0 print "usage: $0 [options] [...] \ndetailed help available with --help\n";
178 0         0 exit 3;
179             }
180              
181             #####################################################################
182             sub _copy_static_files {
183 0     0   0 my($self) = @_;
184 0         0 for my $in (@{$self->{'in'}}) {
  0         0  
185 0 0       0 if(-d $in.'/static/.') {
186 0         0 my $cmd = 'cp -LR '.$in.'/static/* '.$self->{'out'}.'/';
187 0         0 debug($cmd);
188 0         0 `$cmd`;
189             }
190             }
191 0         0 return;
192             }
193              
194             #####################################################################
195             sub _build_dynamic_config {
196 0     0   0 my($self) = @_;
197             # main work block, dynamic object configuration
198 0         0 $self->_build_dynamic_object_config();
199              
200             # other files
201 0         0 for my $in (@{$self->{'in'}}) {
  0         0  
202 0         0 for my $file (sort glob($in.'/*.cfg')) {
203 0 0 0     0 next if defined $self->{'opt'}->{'templatefilter'} and $file !~ m/$self->{'opt'}->{'templatefilter'}/mx;
204 0         0 info('processing non object: '.$file);
205 0         0 my $outfile = $file;
206 0         0 $outfile =~ s/.*\///mx;
207 0 0       0 next if $outfile =~ m/^hosts.*\.cfg/gmx;
208 0 0       0 next if $outfile =~ m/^contacts.*\.cfg/gmx;
209 0         0 $outfile = $self->{'out'}.'/'.$outfile;
210 0         0 debug('writing: '.$outfile);
211 0 0       0 open(my $fh, '>', $outfile) or die('cannot write '.$outfile.': '.$!);
212 0         0 print $fh $self->_process_template($self->_read_replaced_template($file), {});
213 0         0 print $fh "\n";
214 0         0 close($fh);
215             }
216             }
217              
218 0         0 return;
219             }
220              
221             #####################################################################
222             # do the main work, this block is essential for maximum performance
223             sub _build_dynamic_object_config {
224 0     0   0 my($self) = @_;
225              
226             # detect input type
227 0         0 my $input_types = $self->_get_input_types($self->{'in'});
228              
229             # no dynamic config at all?
230 0 0       0 return unless scalar keys %{$input_types} > 0;
  0         0  
231              
232             # build templates
233 0         0 my $templates = {
234             contacts => $self->_build_template('conf.d', 'contacts'),
235             hosts => $self->_build_template('conf.d', 'hosts', [ 'conf.d/apps', 'conf.d/apps.cfg' ]),
236             };
237              
238 0         0 mkdir($self->{'out'}.'/conf.d');
239              
240 0         0 my $data = { hosts => [], contacts => []};
241 0         0 for my $type (keys %{$input_types}) {
  0         0  
242 0         0 my $typefilter = $self->{'opt'}->{substr($type,0,-1).'filter'};
243 0         0 my $obj_list = [];
244 0 0       0 trace('fetching data for '.$type) if $Monitoring::TT::Log::Verbose >= 4;
245 0         0 for my $cls (@{$input_types->{$type}}) {
  0         0  
246 0         0 for my $in (@{$self->{'in'}}) {
  0         0  
247 0         0 my $data = $cls->read($in, $type);
248 0         0 for my $d (@{$data}) {
  0         0  
249 0         0 $d->{'montt'} = $self;
250 0         0 my $o = Monitoring::TT::Object->new($type, $d);
251 0 0       0 die('got no object') unless defined $o;
252 0 0 0     0 next if defined $typefilter and join(',', values %{$o}) !~ m/$typefilter/mx;
  0         0  
253 0 0       0 trace($o) if $Monitoring::TT::Log::Verbose >= 5;
254 0         0 push @{$obj_list}, $o;
  0         0  
255             }
256             }
257             }
258             # sort objects by name
259 0         0 @{$obj_list} = sort {$a->{'name'} cmp $b->{'name'}} @{$obj_list};
  0         0  
  0         0  
  0         0  
260 0         0 $data->{$type} = $obj_list;
261              
262 0         0 my $outfile = $self->{'out'}.'/conf.d/'.$type.'.cfg';
263 0         0 info('writing: '.$outfile);
264 0 0       0 open(my $fh, '>', $outfile) or die('cannot write '.$outfile.': '.$!);
265 0         0 print $fh $self->_process_template($templates->{$type}, { type => $type, data => $obj_list });
266 0         0 print $fh "\n";
267 0         0 close($fh);
268             }
269              
270 0         0 for my $in (@{$self->{'in'}}) {
  0         0  
271 0         0 for my $file (reverse sort @{$self->_get_files($in.'/conf.d', '\.cfg')}) {
  0         0  
272 0 0 0     0 next if defined $self->{'opt'}->{'templatefilter'} and $file !~ m/$self->{'opt'}->{'templatefilter'}/mx;
273 0 0       0 next if $file =~ m/^$in\/conf\.d\/apps/mx;
274 0 0       0 next if $file =~ m/^$in\/conf\.d\/contacts/mx;
275 0 0       0 next if $file =~ m/^$in\/conf\.d\/hosts/mx;
276 0         0 info('processing object file: '.$file);
277 0         0 my $outfile = $file;
278 0         0 $outfile =~ s/.*\///mx;
279 0         0 $outfile = $self->{'out'}.'/conf.d/'.$outfile;
280 0         0 debug('writing: '.$outfile);
281 0 0       0 open(my $fh, '>', $outfile) or die('cannot write '.$outfile.': '.$!);
282 0         0 print $fh $self->_process_template($self->_read_replaced_template($file), $data);
283 0         0 print $fh "\n";
284 0         0 close($fh);
285             }
286             }
287              
288 0         0 $self->{'data'} = $data;
289              
290 0         0 return;
291             }
292              
293             #####################################################################
294             sub _print_stats {
295 0     0   0 my($self) = @_;
296 0         0 my $out = $self->{'out'};
297 0         0 info('written:');
298 0         0 for my $type (qw/host hostgroup hostdependency hostextinfo hostescalation
299             service servicegroup servicedependency serviceextinfo serviceescalation
300             contact contactgroup command timeperiod
301             /) {
302 0         0 my $num = $self->_grep_count($out, '^\s*define\s*'.$type.'\( \|{\)');
303 0 0       0 next if $num == 0;
304 0         0 info(sprintf('# %-15s %6s', $type, $num));
305             }
306 0         0 return;
307             }
308              
309             #####################################################################
310             sub _grep_count {
311 0     0   0 my($self, $dir, $pattern) = @_;
312 0         0 my $txt = `grep -r -c '$pattern' $dir 2>&1`;
313 0         0 my $total = 0;
314 0         0 for my $line (split/\n/mx, $txt) {
315 0 0       0 if($line =~ m/:(\d+)$/mx) {
316 0         0 $total += $1;
317             }
318             }
319 0         0 return $total;
320             }
321              
322             #####################################################################
323             sub _build_template {
324 0     0   0 my($self, $dir, $type, $appdirs) = @_;
325 0         0 my $shorttype = substr($type, 0, -1);
326 0         0 my $template = "[% FOREACH d = data %][% ".$shorttype." = d %]\n";
327 0         0 my $found = 0;
328 0         0 for my $in (@{$self->{'in'}}) {
  0         0  
329 0         0 for my $path (glob($in.'/'.$dir.'/'.$type.'/ '.
330             $in.'/'.$dir.'/'.$type.'*.cfg')
331             ) {
332 0 0       0 trace('looking for '.$type.' templates in '.$path) if $Monitoring::TT::Log::Verbose >= 4;
333 0 0       0 if(-e $path) {
334 0         0 my $templates = $self->_get_files($path, '\.cfg');
335 0         0 for my $t (reverse sort @{$templates}) {
  0         0  
336 0 0 0     0 next if defined $self->{'opt'}->{'templatefilter'} and $t !~ m|$self->{'opt'}->{'templatefilter'}|mx;
337 0         0 my $tags = $self->_get_tags_for_path($t, $path);
338 0         0 my $required_type = shift @{$tags};
  0         0  
339 0 0       0 info('adding '.$type.' template: '.$t.($required_type ? ' for type '.$required_type : '').(scalar @{$tags} > 0 ? ' with tags: '.join(' & ', @{$tags}) : ''));
  0 0       0  
  0         0  
340 0 0       0 if($required_type) {
341 0         0 $self->{$type.'possible_types'}->{$required_type} = 1;
342 0         0 $template .= "[% IF d.type == '$required_type' %]";
343             }
344 0         0 for my $tag (@{$tags}) {
  0         0  
345 0         0 $self->{$type.'possible_tags'}->{$tag} = 1;
346 0         0 $template .= "[% IF d.has_tag('$tag') %]";
347             }
348 0         0 $template .= $self->_read_replaced_template($t);
349 0         0 for my $tag (@{$tags}) {
  0         0  
350 0         0 $template .= "[% END %]";
351             }
352 0 0       0 $template .= "[% END %]" if $required_type;
353 0         0 $found++;
354 0         0 $template .= "\n";
355             }
356             }
357             }
358             }
359              
360             # add apps for hosts
361 0 0 0     0 if(defined $appdirs and scalar @{$appdirs} > 0) {
  0         0  
362 0         0 for my $in (@{$self->{'in'}}) {
  0         0  
363 0         0 for my $path (@{$appdirs}) {
  0         0  
364 0         0 $path = $in.'/'.$path;
365 0 0       0 trace('looking for '.$type.' apps in '.$path) if $Monitoring::TT::Log::Verbose >= 4;
366 0 0       0 if(-e $path) {
367 0         0 my $templates = $self->_get_files($path, '\.cfg');
368 0         0 for my $t (reverse sort @{$templates}) {
  0         0  
369 0 0 0     0 next if defined $self->{'opt'}->{'templatefilter'} and $t !~ m|$self->{'opt'}->{'templatefilter'}|mx;
370 0         0 my $apps = $self->_get_tags_for_path($t, $path);
371 0 0       0 info('adding apps template: '.$t.(scalar @{$apps} > 0 ? ' for apps: '.join(' & ', @{$apps}) : ''));
  0         0  
  0         0  
372 0         0 for my $app (@{$apps}) {
  0         0  
373 0         0 $self->{'possible_apps'}->{$app} = 1;
374 0         0 $template .= "[% IF d.has_app('$app') %]";
375             }
376 0         0 $template .= $self->_read_replaced_template($t);
377 0         0 for my $app (@{$apps}) {
  0         0  
378 0         0 $template .= "[% END %]";
379             }
380 0         0 $found++;
381 0         0 $template .= "\n";
382             }
383             }
384             }
385             }
386             }
387              
388 0 0       0 if($found == 0) {
389 0         0 debug('no templates for type '.$type.' found');
390 0         0 return '';
391             }
392 0         0 $template .= "[% END %]\n";
393 0 0       0 if($Monitoring::TT::Log::Verbose >= 4) {
394 0         0 trace('created template:');
395 0         0 trace($template);
396             }
397 0         0 return $template;
398             }
399              
400             #####################################################################
401             sub _get_files {
402 0     0   0 my($self, $dir, $pattern) = @_;
403 0 0 0     0 if(!-d $dir and $dir =~ m/$pattern/mx) {
404 0         0 return([$dir]);
405             }
406 0         0 my @files;
407 0 0       0 return \@files unless -d $dir;
408 0 0       0 opendir(my $d, $dir) or die("cannot read directory $dir: $!");
409 0         0 while(my $file = readdir($d)) {
410 0 0       0 next if substr($file,0,1) eq '.';
411 0 0       0 if(-d $dir.'/'.$file.'/.') {
412 0         0 push @files, @{$self->_get_files($dir.'/'.$file, $pattern)};
  0         0  
413             } else {
414 0 0       0 next if $file !~ m/$pattern/mx;
415 0         0 push @files, $dir."/".$file;
416             }
417             }
418 0         0 return \@files;
419             }
420              
421             #####################################################################
422             sub _process_template {
423 3     3   3 my($self, $template, $data) = @_;
424              
425 3 100       9 if(!defined $self->{'_config_template'}) {
426 2         3 $self->{'_config_template'} = "";
427 2         2 for my $in (@{$self->{'in'}}) {
  2         5  
428 0         0 debug('looking for a '.$in.'/config.cfg');
429 0 0       0 if(-e $in.'/config.cfg') {
430 0         0 debug('added config template '.$in.'/config.cfg');
431 0         0 $self->{'_config_template'} .= $self->_read_replaced_template($in.'/config.cfg')."\n";
432             }
433             }
434             }
435 3         8 $template = $self->{'_config_template'}.$template;
436              
437 3 50       9 if($Monitoring::TT::Log::Verbose >= 4) {
438 0         0 trace('template:');
439 0         0 trace('==========================');
440 0         0 trace($template);
441 0         0 trace('==========================');
442             }
443              
444 3         1 my $output;
445 3 50       8 $self->tt->process(\$template, $data, \$output) or $self->_template_process_die($template, $data);
446              
447             # clean up result
448 3         41996 $output =~ s/^\s*$//sgmxo;
449 3         11 $output =~ s/^\n//gmxo;
450              
451 3         11 return $output;
452             }
453              
454             #####################################################################
455             sub _get_input_classes {
456 1     1   4 my($self, $folders) = @_;
457 1         1 my $types = [];
458              
459 1         1 for my $dir (@{$folders}) {
  1         2  
460 0 0       0 next unless -d $dir.'/lib/.';
461 0         0 unshift @INC, "$dir/lib";
462 0 0       0 trace('added '.$dir.'/lib to @INC') if $Monitoring::TT::Log::Verbose >= 4;
463             }
464              
465 1 50       2 if($Monitoring::TT::Log::Verbose >= 4) {
466 0         0 trace('@INC:');
467 0         0 trace(\@INC);
468             }
469              
470 1         1 my $uniq_types = {};
471 1         1 my $uniq_libs = {};
472 1         1 for my $inc (@INC) {
473 12 100       26 next if defined $uniq_libs->{$inc};
474 10         13 $uniq_libs->{$inc} = 1;
475 10         319 my @files = glob($inc.'/Monitoring/TT/Input/*.pm');
476 10         162 for my $file (glob($inc.'/Monitoring/TT/Input/*.pm')) {
477 4 50       8 trace('found input class: '.$file) if $Monitoring::TT::Log::Verbose >= 4;
478 4         46 $file =~ s|^$inc/Monitoring/TT/Input/||mx;
479 4         11 $file =~ s|\.pm$||mx;
480 4 100       11 push @{$types}, $file unless defined $uniq_types->{$types}->{$file};
  2         4  
481 4         11 $uniq_types->{$types}->{$file} = 1;
482             }
483             }
484 1         5 return $types;
485             }
486              
487             #####################################################################
488             sub _get_input_types {
489 0     0   0 my($self, $folders) = @_;
490 0         0 my $input_types = {};
491 0         0 my $input_classes = $self->_get_input_classes($folders);
492 0         0 for my $t (@{$input_classes}) {
  0         0  
493 0         0 debug('requesting input files from: '.$t);
494 0         0 my $objclass = 'Monitoring::TT::Input::'.$t;
495             ## no critic
496 0         0 eval "require $objclass;";
497             ## use critic
498 0 0       0 error($@) if $@;
499 0         0 my $obj = \&{$objclass."::new"};
  0         0  
500 0         0 my $it = &$obj($objclass, montt => $self);
501 0         0 my $types = $it->get_types($folders);
502 0 0       0 trace('input \''.$t.'\' supports: '.join(', ', @{$types})) if $Monitoring::TT::Log::Verbose >= 4;
  0         0  
503 0         0 for my $type (@{$types}) {
  0         0  
504 0 0       0 $input_types->{$type} = [] unless defined $input_types->{$type};
505 0         0 push @{$input_types->{$type}}, $it;
  0         0  
506             }
507             }
508 0         0 return $input_types;
509             }
510              
511             #####################################################################
512             sub _run_hook {
513 0     0   0 my($self, $name, $args) = @_;
514 0 0       0 return if $self->{'opt'}->{'dryrun'};
515 0         0 for my $in (@{$self->{'in'}}) {
  0         0  
516 0         0 my $hook = $in.'/hooks/'.$name;
517 0 0       0 trace("hook: looking for ".$hook) if $Monitoring::TT::Log::Verbose >= 4;
518 0 0       0 if(-x $hook) {
519 0         0 my $cmd = $hook;
520 0 0       0 $cmd = $cmd." ".$args if defined $args;
521 0         0 debug($cmd);
522 0         0 open(my $ph, '-|', $cmd);
523 0         0 while(my $line = <$ph>) {
524 0         0 log($line);
525             }
526 0         0 close($ph);
527 0         0 my $rc = $?>>8;
528 0         0 debug('hook returned: '.$rc);
529 0 0       0 if($rc) {
530 0         0 debug(' -> exiting');
531 0         0 exit $rc;
532             }
533             }
534             }
535 0         0 return;
536             }
537              
538             #####################################################################
539             sub _read_replaced_template {
540 3     3   5 my($self, $template) = @_;
541 3         7 $template =~ s|//|/|gmxo;
542 3         3 my $text = '[%# SRC '.$template.':1 #%]';
543 3 50       87 open(my $fh, '<', $template) or die("cannot read: ".$template.': '.$!);
544 3         28 while(my $line = <$fh>) {
545             # remove utf8 file bom
546 25 100       37 if($. == 1) {
547 3         5 my $bom = pack("CCC", 0xef, 0xbb, 0xbf);
548 3 50       9 if(substr($line,0,3) eq $bom) {
549 0         0 $line = substr($line, 3);
550             }
551             }
552 25         18 $text .= $line;
553 25 100       67 if($line =~ m/^define\s+(\w+)/mxo) {
554 3 50 33     22 if($1 eq 'service' or $1 eq 'host' or $1 eq 'contact') {
      33        
555 3         15 $text .= ' _SRC '.$template.':'.$.."\n";
556             } else {
557 0         0 $text .= '# SRC '.$template.':'.$.."\n";
558             }
559             }
560             }
561 3         16 close($fh);
562 3         15 return $text;
563             }
564              
565             #####################################################################
566             sub _get_tags_for_path {
567 0     0     my($self, $path, $basepath) = @_;
568 0           my $tmppath = lc $path;
569 0           $tmppath =~ s|^$basepath||mx;
570 0           $tmppath =~ s|\.cfg$||mx;
571 0           $tmppath =~ s|^/||mx;
572 0           my @tags = split(/\//mx, $tmppath);
573 0           return \@tags;
574             }
575              
576             #####################################################################
577             sub _template_process_die {
578 0     0     my($self, $template, $data) = @_;
579 0           my $tterror = "".$self->tt->error();
580 0           my $already_printed = 0;
581              
582             # try to find file / line
583 0 0         if($tterror =~ m/input\s+text\s+line\s+(\d+)/mx) {
584 0           my $linenr = $1;
585 0           my @lines = split/\n/mx, $template;
586 0           my($realfile, $realline) = $self->_get_file_and_line_for_error(\@lines, $linenr);
587 0 0         if($realfile) {
588 0           my $newloc = $realfile.' line '.$realline;
589 0           $tterror =~ s|input\s+text\s+line\s+\d+|$newloc|gmx;
590             }
591             }
592              
593             # var.undef error - undefined variable: host.tag('contact_groups')
594 0 0         if($tterror =~ m/var\.undef\ error\ -\ undefined\ variable:\s+(.*)$/mx) {
595 0           my $err = $1;
596 0           my $linenr = 0;
597 0           error($tterror);
598 0           $already_printed = 1;
599 0           my @lines = split/\n/mx, $template;
600 0           for my $line (@lines) {
601 0           $linenr++;
602 0 0         if($line =~ m/\Q$err\E/mx) {
603 0           my($realfile, $realline) = $self->_get_file_and_line_for_error(\@lines, $linenr);
604 0 0         if($realfile) {
605 0           error('occurs in: '.$realfile.':'.$realline);
606             }
607             }
608             }
609             }
610              
611 0 0         error($tterror) unless $already_printed;
612 0           debug('in template:');
613 0           debug($template);
614 0 0         trace($data) if $Monitoring::TT::Log::Verbose >= 4;
615 0           exit 1;
616             }
617              
618             #####################################################################
619             sub _get_file_and_line_for_error {
620 0     0     my($self, $lines, $linenr) = @_;
621 0           for(my $x = $linenr; $x >= 0; $x--) {
622 0 0 0       if(defined $lines->[$x] and $lines->[$x] =~ m/SRC\s+(.*):(\d+)/mx) {
623 0           my $diff = $x - $2 + 1;
624 0           return($1, ($linenr - $diff))
625             }
626             }
627 0           return(undef, undef);
628             }
629              
630             #####################################################################
631             sub _mkdir_r {
632 0     0     my($self, $dir) = @_;
633 0           my $path = '';
634 0           for my $part (split/(\/)/mx, $dir) {
635 0           $path .= $part;
636 0 0         next if $path eq '';
637 0 0         mkdir($path) unless -d $path;
638             }
639 0           return;
640             }
641              
642             #####################################################################
643             sub _check_typos {
644 0     0     my($self) = @_;
645 0           for my $type (qw/hosts contacts/) {
646 0           for my $o (@{$self->{'data'}->{$type}}) {
  0            
647 0 0         if($o->{'type'}) {
648 0 0         warn('unused type \''.$o->{'type'}.'\' defined in '.$o->{'file'}.':'.$o->{'line'}) unless defined $self->{$type.'possible_types'}->{$o->{'type'}};
649             }
650 0 0         if($o->{'tags'}) {
651 0           for my $t (keys %{$o->{'tags'}}) {
  0            
652 0 0         next if substr($t,0,1) eq '_';
653 0 0         warn('unused tag \''.$t.'\' defined in '.$o->{'file'}.':'.$o->{'line'}) unless defined $self->{$type.'possible_tags'}->{$t};
654             }
655             }
656 0 0         if($o->{'apps'}) {
657 0           for my $a (keys %{$o->{'apps'}}) {
  0            
658 0 0         warn('unused app \''.$a.'\' defined in '.$o->{'file'}.':'.$o->{'line'}) unless defined $self->{$type.'possible_apps'}->{$a};
659             }
660             }
661             }
662             }
663 0           return;
664             }
665              
666             =head1 AUTHOR
667              
668             Sven Nierlein, 2013,
669              
670             =cut
671              
672             1;