File Coverage

blib/lib/Module/Install/DiffCheck.pm
Criterion Covered Total %
statement 6 52 11.5
branch 0 12 0.0
condition n/a
subroutine 2 5 40.0
pod 1 1 100.0
total 9 70 12.8


line stmt bran cond sub pod time code
1             package Module::Install::DiffCheck;
2              
3             =head1 NAME
4              
5             Module::Install::DiffCheck - Run diff commands looking for deployment problems
6              
7             =head1 SYNOPSIS
8              
9             Add statements like these to your Module::Install generated Makefile.PL:
10              
11             diffcheck(
12             before_diff_commands => [
13             'Model/refresh_Schema.pl',
14             'Model/mysqldump.pl root SuperSecret',
15             ],
16             diff_commands => [
17             'svn diff --diff-cmd diff -x "-i -b -u" Model',
18             ],
19             ignore_lines => [
20             qr/ *#/, # Ignore comments
21             qr/^\-\-/, # Ignore comments
22             qr/AUTO_INCREMENT/, # These change all the time
23             ],
24             );
25              
26             That's it. Each C is executed, then each C
27             is executed. Any diff output lines that don't match an C regex cause
28             a fatal error.
29              
30             We use L, mysqldump, and Subversion, but you should
31             be able to use any version control system, RDBMS, and ORM(s) that make you happy.
32             And/or you could diff other files that have nothing to do with databases.
33              
34             =head1 DESCRIPTION
35              
36             If you use a version control system to deploy your applications you might find
37             this module useful.
38              
39             =head2 How we check our database schemas
40              
41             Here, I describe the specifics of how we use this where I work, in case
42             you find this practical example illustrative.
43              
44             We commit all our database schemas into our
45             version control system. Every time we deploy a specific release it is critical that
46             the RDBMS schema on that server exactly matches the schema in our version control system.
47             New tables may have been introduced, tables may have been
48             altered, or old tables may have been removed.
49             diffcheck() lists all errors and dies if it detects problems.
50              
51             We use both L C and C
52             to store our schemas to disk. We then commit those files into our Subversion
53             repository.
54              
55             (L C is slick. With 5 lines of code, you can
56             flush an entire database into a static Schema/ directory. C shows us what,
57             if anything, has changed.)
58              
59             Similarly, C output (or whatever utility dumps C SQL out of your
60             database) added to our SVN repository lets us run C and see everything that changed.
61              
62             So, assuming the DBA has already prepped the appropriate database changes (if any) for "sometag",
63             our deployment goes like this:
64              
65             svn checkout https://.../MyApp/tags/sometag MyApp
66             cd MyApp
67             perl Makefile.PL
68             make
69             make install
70              
71             All done. L has installed all our CPAN dependencies for us, all other custom
72             log directories and what-not are ready to go, and our database schema(s) have been
73             audited against the tag.
74              
75             If the DBA forgot to prep the database, then perl C dies with a report about which
76             part(s) of the C results were considered fatal.
77              
78             This module will not help you if you want to manage your schema versions down to
79             individual "ALTER TABLE" statements which transform one tag to another tag.
80             (Perhaps L could help you with that level of granularity?)
81             We don't get that fancy where I work.
82              
83             =head1 METHODS
84              
85             =cut
86              
87 1     1   25358 use strict;
  1         4  
  1         41  
88 1     1   958 use Text::Diff::Parser;
  1         16899  
  1         669  
89             our @ISA;
90             require Module::Install::Base;
91             @ISA = qw/Module::Install::Base/;
92              
93             our $VERSION = '0.02';
94              
95              
96             =head2 diffcheck
97              
98             See SYNOPSIS above.
99              
100             =cut
101              
102             sub diffcheck {
103 0     0 1   my ($self, %args) = @_;
104 0           print <
105             *** Module::Install::DiffCheck
106             EOF
107              
108 0 0         unless ($args{diff_commands}) {
109 0           die "diffcheck() requires a diff_commands argument";
110             }
111              
112 0           my $fatal = 0;
113 0 0         if ($args{before_diff_commands}) {
114 0           $fatal += $self->_run_before_diff_commands(\%args);
115             }
116 0           $fatal += $self->_run_diff_commands(\%args);
117              
118 0 0         if ($fatal) {
119 0           print "*** Module::Install::DiffCheck FATAL ERRORS\n";
120 0           exit $fatal;
121             }
122              
123 0           print <
124             *** Module::Install::DiffCheck finished.
125             EOF
126              
127 0           return 1; # Does Module::Install care?
128             }
129              
130              
131              
132             sub _run_before_diff_commands {
133 0     0     my ($self, $args) = @_;
134            
135 0           my $fatal = 0;
136 0           foreach my $cmd (@{$args->{before_diff_commands}}) {
  0            
137 0           print "running '$cmd'\n";
138 0           open(my $in, "$cmd 2>&1 |");
139 0           while (<$in>) {
140 0           chomp;
141 0           print " $_\n";
142             # $fatal++; # hmm...
143             }
144 0           close $in;
145             }
146 0           return $fatal;
147             }
148              
149              
150             sub _run_diff_commands {
151 0     0     my ($self, $args) = @_;
152            
153 0           my $fatal = 0;
154 0           foreach my $cmd (@{$args->{diff_commands}}) {
  0            
155 0           print "running '$cmd'\n";
156 0           my $diff = `$cmd`;
157            
158 0           my $parser = Text::Diff::Parser->new(
159             Simplify => 1,
160             Diff => $diff,
161             # Verbose => 1,
162             );
163            
164 0           foreach my $change ( $parser->changes ) {
165 0 0         next unless ($change->type); # How do blanks get in here?
166 0           my $msg = sprintf(
167             " CHANGE DETECTED! %s %s %s line(s) at lines %s/%s:\n",
168             $change->filename1,
169             $change->type,
170             $change->size,
171             $change->line1,
172             $change->line2,
173             );
174 0           my $size = $change->size;
175 0           my $show_change = 0;
176            
177             LINE:
178 0           foreach my $line ( 0..($size-1) ) {
179             # Huh... Only the new is available. Not the old?
180 0           foreach my $i (@{$args->{ignore_lines}}) {
  0            
181 0 0         next LINE if ($change->text( $line ) =~ $i);
182             }
183 0           $msg .= sprintf(" [%s]\n", $change->text( $line ));
184 0           $show_change = 1;
185 0           $fatal = 1;
186             }
187 0 0         if ($show_change) {
188             # Hmm... It would be nice if we could just kick out the unidiff here?
189 0           print $msg;
190             }
191             }
192             }
193 0           return $fatal;
194             }
195              
196              
197             =head1 AUTHOR
198              
199             Jay Hannah, C<< >>
200              
201             =head1 BUGS
202              
203             This module makes no attempt to work on Windows. Sorry. Patches welcome.
204              
205             Please report any bugs or feature requests to C, or through
206             the web interface at L. I will be notified, and then you'll
207             automatically be notified of progress on your bug as I make changes.
208              
209             =head1 SUPPORT
210              
211             You can find documentation for this module with the perldoc command.
212              
213             perldoc Module::Install::DiffCheck
214              
215             You can also look for information at:
216              
217             =over 4
218              
219             =item * RT: CPAN's request tracker
220              
221             L
222              
223             =item * AnnoCPAN: Annotated CPAN documentation
224              
225             L
226              
227             =item * CPAN Ratings
228              
229             L
230              
231             =item * Search CPAN
232              
233             L
234              
235             =item * Version control
236              
237             L,
238             L
239              
240             =back
241              
242             =head1 COPYRIGHT & LICENSE
243              
244             Copyright 2009-2013 Jay Hannah, all rights reserved.
245              
246             =cut
247              
248             1; # End of Module::Install::DiffCheck
249