1#!/usr/local/bin/perl 2 3use warnings; 4use strict; 5use Data::Dumper; 6use Getopt::Long; 7use File::Basename; 8use Sys::Hostname; 9 10$|=1; # autoflush 11 12my %options = ( 13 check => 0, 14 verbose => 0, 15 veryverbose => 0, 16 help => 0, 17 cfagent => "../cf-agent/cf-agent", 18 workdir => "/tmp", 19 ); 20 21die ("Unknown options") unless GetOptions(\%options, 22 "help|h!", 23 "check|c!", 24 "cfagent=s", 25 "workdir=s", 26 "verbose|v!", 27 "veryverbose!", 28 ); 29 30if ($options{help}) 31{ 32 print <<EOHIPPUS; 33Syntax: $0 [-c|--check] [-v|--verbose] [--cfagent=PATH] [--workdir=WORKDIR] FILE1.cf FILE2.cf ... 34 35Generate the output section of CFEngine code example. 36 37With -c or --check, the script reports if the output is different but does not 38write it. 39 40With -v or --verbose, the script shows the full output of each test. Use 41--veryverbose if you REALLY want a lot of output. 42 43The --workdir path, defaulting to /tmp, is used for storing the example to be 44run. 45 46The --cfagent path, defaulting to ../cf-agent/cf-agent, is the cf-agent path. 47 48Each input .cf file is scanned for three markers: 49 501) required: a cfengine3 code block to be run 51 52#+begin_src cfengine3 53... CFEngine code to run here ... 54#+end_src 55 562) optionally, a prep block 57(each command will be run before the cfengine3 code block) 58 59#+begin_src prep 60#@ ``` 61#@ touch -t '200102031234.56' /tmp/earlier 62#@ touch -t '200202031234.56' /tmp/later 63#@ ``` 64#+end_src 65 663) required: an output code block 67 68#+begin_src example_output 69#@ ``` 70#@ 2013-12-16T20:48:24+0200 notice: /default/example: R: The secret changes have been accessed after the reference time 71#@ ``` 72#+end_src 73 74This block is rewritten if it's different, otherwise it's left alone. 75 76The "#@ " part is optional but needed if you want the file to be valid CFEngine 77policy. The "#@ ```" parts make the prep and output steps render as code in markdown. 78 79If the output is unpredictable due to, for example, random input or network 80dependencies, make sure the unpredictable line has the string RANDOM in capital 81letters somewhere. That will skip the output check for that line. 82 83EOHIPPUS 84 85 exit; 86} 87 88my $rc = 0; 89my @todo = @ARGV; 90 91foreach my $file (@todo) 92{ 93 open my $fh, '<', $file or warn "Could not open $file: $!"; 94 my $data = join '', <$fh>; 95 close $fh; 96 my $copy = $data; 97 if ($data =~ m/#\+begin_src cfengine3\n(.+?)\n#\+end_src/s) 98 { 99 my $example = $1; 100 101 my $prep; 102 if ($data =~ m/#\+begin_src prep\n(.+?)\n#\+end_src/s) 103 { 104 $prep = [split "\n", $1]; 105 } 106 107 $data =~ s/(#\+begin_src example_output( no_check)?\n)(.*?)(#\+end_src)/$1 . rewrite_output($file, $prep, $example, $3) . $4/es; 108 if (!defined($2) && $data ne $copy) 109 { 110 print "$file: output differs from original..."; 111 if ($options{check}) 112 { 113 $rc = 1; 114 print "\n"; 115 next; 116 } 117 118 open my $fh, '>', $file or warn "Could not open $file: $!"; 119 print $fh $data; 120 close $fh; 121 print "new output written!\n"; 122 } 123 } 124 else 125 { 126 warn "No example to run was found in $file, skipping"; 127 } 128} 129 130exit $rc; 131 132sub rewrite_output 133{ 134 my $file = shift @_; 135 my $prep = shift @_; 136 my $example = shift @_; 137 my $old_output = shift @_; 138 my $new_output = run_example($file, $prep, $example); 139 140 if (equal_outputs($old_output, $new_output, $file)) 141 { 142 return $old_output; 143 } 144 145 if (defined $new_output && length $new_output > 0) 146 { 147 $new_output =~ s/^/#@ /mg; 148 $new_output = "#@ ```\n$new_output#@ ```\n"; 149 } 150 151 return $new_output; 152} 153 154sub equal_outputs 155{ 156 # strip out date, e.g. '2013-12-16T20:48:24+0200' 157 my $x = shift @_; 158 my $y = shift @_; 159 my $file = shift @_; 160 161 my ($tempfile, $base) = get_tempfile($file); 162 163 $x =~ s/^#@ ```\s+//mg; 164 $y =~ s/^#@ ```\s+//mg; 165 166 $x =~ s/^(#@ )//mg; 167 $x =~ s/^[-0-9T:+]+\s+//mg; 168 $y =~ s/^(#@ )//mg; 169 $y =~ s/^[-0-9T:+]+\s+//mg; 170 171 $x =~ s/.*RANDOM.*//mg; 172 $y =~ s/.*RANDOM.*//mg; 173 174 # strip leading blanks, for example from " error:" 175 $x =~ s/^ *//mg; 176 $y =~ s/^ *//mg; 177 178 if ($x ne $y) 179 { 180 open my $fha, '>', "$tempfile.a" or die "Could not write to diff output $tempfile.a: $!"; 181 print $fha $x; 182 close $fha; 183 184 open my $fhb, '>', "$tempfile.b" or die "Could not write to diff output $tempfile.b: $!"; 185 print $fhb $y; 186 close $fhb; 187 188 system("diff -u $tempfile.a $tempfile.b") if $options{verbose}; 189 return 0; 190 } 191 192 return 1; 193} 194 195sub get_tempfile 196{ 197 my $file = shift @_; 198 199 my $base = basename($file); 200 my $tempfile = "$options{workdir}/$base"; 201 mkdir $options{workdir} unless -e $options{workdir}; 202 203 return ($tempfile, $base); 204} 205 206sub run_example 207{ 208 my $file = shift @_; 209 my $prep = shift @_ || []; 210 my $example = shift @_; 211 212 my ($tempfile, $base) = get_tempfile($file); 213 open my $fh, '>', $tempfile or die "Could not write to $tempfile: $!"; 214 print $fh $example; 215 close $fh; 216 chmod 0600, $tempfile; 217 218 foreach (@$prep) 219 { 220 s/^#@ //; 221 # skip Markdown markup like ``` 222 next unless m/^\w/; 223 s/FILE/$tempfile/g; 224 s/\$HOSTNAME/hostname()/ge; 225 print "processing $file: Running prep '$_'" 226 if $options{verbose}; 227 system($_); 228 } 229 230 $ENV{EXAMPLE} = $base; 231 $ENV{CFENGINE_COLOR} = 0; 232 my $cmd = "$options{cfagent} -D_cfe_output_testing -Kf $tempfile 2>&1"; 233 open my $ofh, '-|', $cmd; 234 my $output = join '', <$ofh>; 235 close $ofh; 236 237 print "Test file: $file\nCommand: $cmd\n\nNEW OUTPUT: [[[$output]]]\n\n\n" 238 if $options{verbose}; 239 240 return $output; 241} 242