File Coverage

lib/DBIx/Deployer.pm
Criterion Covered Total %
statement 421 447 94.1
branch 106 202 52.4
condition 10 15 66.6
subroutine 48 49 97.9
pod n/a
total 585 713 82.0


line stmt bran cond sub pod time code
1 1     1   96612 use 5.14.2;
  2         290  
2 2     1   1286 use Modern::Perl;
  2         526  
  2         145  
3 2     1   1164 use Moops;
  2         34956  
  2         133  
4              
5              
6 2     1   167950 class DBIx::Deployer::Patch 1.2.0 {
  2     1   218  
  2         236  
  1         2  
  1         67  
  1         360  
  1         1592  
  1         4  
  1         1291  
  1         2  
  1         7  
  1         58  
  1         2  
  1         47  
  1         6  
  1         2  
  1         97  
  1         30  
  1         9  
  1         2  
  1         10  
  1         3969  
  1         3  
  1         7  
  1         787  
  1         3386  
  1         5  
  1         156  
  1         2  
  1         9  
  1         684  
  1         6262  
  1         11  
  1         712  
  1         2246  
  1         6  
  1         1148  
  1         2174  
  1         8  
  1         121861  
  1         3  
  1         5  
  1         2  
  1         21  
  1         5  
  1         2  
  1         55  
  1         10  
  1         3  
  1         116  
  1         5468  
  1         6  
7 1     1   6 use Digest::MD5;
  1         1  
  1         55  
8 1     1   504 use Term::ANSIColor qw(colored);
  1         7924  
  1         766  
9 1     1   567 use Data::Printer colored => 1;
  1         20202  
  1         12  
10              
11 1         24 has deployed => ( is => 'rw', isa => Bool, default => 0 );
12 1         3536 has verified => ( is => 'rw', isa => Bool, default => 0 );
13 1         1153 has name => ( is => 'ro', isa => Str, required => true );
14 1         630 has supports_transactions => ( is => 'ro', isa => Bool, default => true );
15 1         411 has dependencies => ( is => 'ro', isa => Maybe[ArrayRef] );
16 1         1111 has deploy_sql => ( is => 'ro', isa => Maybe[Str] );
17 1         985 has deploy_sql_args => ( is => 'ro', isa => Maybe[ArrayRef] );
18 1         625 has deploy_script => ( is => 'ro', isa => Maybe[Str] );
19 1         662 has deploy_script_args => ( is => 'rw', isa => Maybe[ArrayRef] );
20 1         1327 has no_verify => ( is => 'ro', isa => Bool, default => false );
21 1         448 has verify_sql => ( is => 'ro', isa => Str );
22 1         553 has verify_sql_args => ( is => 'ro', isa => ArrayRef );
23 1         596 has verify_expects => ( is => 'ro', isa => ArrayRef );
24 1         487 has db => ( is => 'ro', isa => InstanceOf['DBI::db'], required => true );
25              
26 1 50   1   4006 method deploy {
  1     15   3  
  1         404  
  1         3637  
  0         0  
  15         1747  
27 15 50 66     35 if($self->deploy_sql && $self->deploy_script) {
    100          
    50          
28 15         350 $self->handle_error('Patch cannot have both deploy_sql and deploy_script.');
29             }
30             elsif($self->deploy_sql) {
31 15 100       327 if($self->deploy_sql_args){
32 13 50       169 $self->db->do($self->deploy_sql, {}, @{ $self->deploy_sql_args })
  13         100301  
33             or $self->handle_error($self->db->errstr);
34             }
35             else{
36 13 100       44 $self->db->do($self->deploy_sql) or $self->handle_error($self->db->errstr);
37             }
38             }
39             elsif($self->deploy_script) {
40 13 50       387 if( my $status = system $self->deploy_script, @{ $self->deploy_script_args || [] } ) {
  13 100       1094  
41 13         226 $self->handle_error("Exited with status $status.");
42             }
43             }
44             else {
45 13         34 $self->handle_error('Patch has neither deploy_sql nor deploy_script.');
46             }
47             }
48              
49 1 50   1   1514 before deploy {
  1     15   3  
  1         189  
  1         1584  
  13         82  
  1         28  
50 1 50       67 die colored(['red'], 'Patch "' . $self->name . '" is already deployed') if $self->deployed;
51 12 100 66     95 if($self->supports_transactions && !$self->deploy_script){ $self->db->begin_work; }
  12         112  
52             }
53              
54 1 50   1   1474 after deploy {
  1     13   3  
  1         126  
  1         29  
  1         9  
  11         39  
55 11         62 $self->deployed(1);
56 1         11 $self->verify;
57             }
58              
59 1 50   1   1220 method verify {
  1     13   2  
  1         264  
  1         8  
  1         21  
  10         198  
60 11 100       2300 if($self->no_verify){
61 12         1152 $self->verified(1);
62 12         34 return;
63             }
64              
65 12 100 66     336 unless($self->verify_sql && @{ $self->verify_expects || [] }){
  11 100       245  
66 10         137652 $self->handle_error('Patch is missing verification attributes');
67             }
68              
69 11         386 my $result;
70              
71 1 100       14 if($self->verify_sql_args){
72 4 50       391 $result = $self->db->selectall_arrayref($self->verify_sql, {}, @{ $self->verify_sql_args })
  4         26  
73             or $self->handle_error($self->db->errstr);
74             }
75             else{
76 4 50       35 $result = $self->db->selectall_arrayref($self->verify_sql)
77             or $self->handle_error($self->db->errstr);
78             }
79              
80 4         22 $self->verified($self->_check_signature($result));
81             }
82              
83 1 50   1   933 after verify {
  1     12   2  
  1         240  
  1         2  
  4         11  
  4         25  
84 4 100       12 if($self->verified){
85 4 100 66     51 if($self->supports_transactions && !$self->deploy_script){
86 3         89 $self->db->commit;
87             }
88 3         600 say colored(['green'], 'Patch "' . $self->name . '" completed successfully');
89             }
90             else{
91 4         82 $self->handle_error('Failed verification');
92             }
93             }
94              
95 1 50   1   15364 method handle_error ( Str $error ){
  1 50   4   3  
  1 50       152  
  1 50       6  
  1 50       3  
  1         166  
  1         63  
  11         141  
  11         58  
  11         48  
  11         60  
  11         35  
  11         48  
  11         32  
96 11 100 66     353 if($self->supports_transactions && !$self->deploy_script){
97 11         59 $self->deployed(0);
98 1 50       110 $self->db->rollback or die $self->name . ': ' . $self->db->errstr;
99             }
100 1         14 die colored(['red'], 'Patch "' . $self->name . '" failed: ' . $error);
101             }
102            
103 1 50   1   2842 method _check_signature ( ArrayRef $result ){
  1 50   11   11  
  1 50       137  
  1 50       7  
  1 50       1  
  1         177  
  1         6  
  1         3181  
  1         66  
  11         3666  
  0         0  
  0         0  
  0         0  
  0         0  
104 0         0 my $is_equal = $self->_signature($result) eq $self->_signature($self->verify_expects);
105 0 100       0 unless ( $is_equal ) {
106 0         0 say 'Expected:';
107 0         0 say p( $self->verify_expects );
108 0         0 say "\nReceived:";
109 0         0 say p( $result );
110             }
111 0         0 return $is_equal;
112             }
113              
114 1 0   1   2203 multi method _signature( HashRef $params ) {
  1 0   0   3  
  1 0       199  
  1 0       6  
  1 0       2  
  1 0       144  
  1 0       3  
  0 0       0  
  0         0  
  59         35734  
  59         187  
  59         196  
  59         196  
  59         199  
  59         189  
  59         197  
  0         0  
  0         0  
115             return Digest::MD5::md5_base64(
116             join(
117             '',
118             map {
119 0         0 $self->_signature($_)
120 59         90 . $self->_signature( $params->{$_} )
121             } sort keys %$params
122             )
123             );
124             }
125              
126 1 50   1   2201 multi method _signature( ArrayRef $params ) {
  1 50   59   2  
  1 50       194  
  1 50       5  
  1 50       2  
  1 0       124  
  1 0       9  
  59 50       167  
  236         6337  
  167         95004  
  167         473  
  167         469  
  167         442  
  167         422  
  167         448  
  167         397  
  0         0  
  0         0  
127             return Digest::MD5::md5_base64(
128 0         0 join( '', map { $self->_signature($_) } @$params )
  167         278  
129             );
130             }
131              
132 1 50   1   2047 multi method _signature( Str $params ) {
  1 50   167   2  
  1 50       194  
  1 50       6  
  1 50       2  
  1 0       87  
  1 0       375  
  167 50       1864  
  32         26131  
  32         102  
  32         123  
  32         109  
  32         103  
  32         118  
  32         105  
  0         0  
  0         0  
  0         0  
133 32         64 return Digest::MD5::md5_base64($params);
134             };
135              
136 1 50   1   2462 multi method _signature ( Undef $params ) {
  1 50   32   2  
  1 50       204  
  1 50       6  
  1 50       2  
  1 0       95  
  1 0       3  
  32 50       99  
  20         2862  
  20         47  
  20         54  
  20         607  
  11         370  
  11         179  
  11         2948  
  11         86  
  11         1810  
  11         41  
137 13         575 return;
138             }
139             }
140              
141 1     1   59 class DBIx::Deployer 1.2.0 {
  1     1   2  
  1         43  
  1         5  
  1         2  
  1         74  
  1         30  
  1         5  
  1         1  
  1         8  
  1         4923  
  1         3  
  1         9  
  1         522  
  1         2  
  1         10  
  1         155  
  1         2  
  1         11  
  1         133  
  1         2  
  1         9  
  1         225  
  1         2  
  1         9  
  1         897  
  1         2  
  1         6  
  1         1911  
  1         3  
  1         5  
  1         2  
  1         28  
  1         8  
  1         2  
  1         90  
  1         10  
  1         3  
  1         143  
  1         8  
  1         3  
  1         49  
  1         630  
  1         8320  
  1         69  
  1         483  
  1         4156  
  1         71  
  1         7  
  1         3  
  1         1412  
  1         82260  
142 1     1   56 use DBI;
  1         327  
  1         16168  
143 1     1   4 use DBD::SQLite;
  1         9337  
  1         3  
144 1     1   594 use JSON::XS;
  1         2136  
  1         2  
145 1     1   167 use Term::ANSIColor;
  1         6  
  1         19  
146 1     1   109 use autodie;
  1         3842  
  1         2  
147              
148             has target_db => ( is => 'lazy', isa => InstanceOf['DBI::db'],
149             builder => method {
150             die 'Missing attribute target_dsn. Optionally, you may pass a DBI::db as target_db' unless $self->target_dsn;
151             DBI->connect(
152             $self->target_dsn,
153             $self->target_username,
154             $self->target_password
155             ) or die $@;
156 1         452 }
157             );
158              
159 1         408 has target_dsn => ( is => 'ro', isa => Str );
160 1         211 has target_username => ( is => 'ro', isa => Str );
161 1         188 has target_password => ( is => 'ro', isa => Str );
162              
163 1         218 has patch_path => ( is => 'ro', isa => Str, required => true );
164 1         135 has deployer_db_file => ( is => 'ro', isa => Str );
165              
166             has deployer_db => ( is => 'lazy', isa => InstanceOf['DBI::db'],
167             builder => method {
168             die 'Missing attribute deployer_db_file if using SQLite for patch management' unless $self->deployer_db_file;
169             my $db = DBI->connect('dbi:SQLite:dbname=' . $self->deployer_db_file) or die $@;
170             my $tables = $db->selectall_arrayref('SELECT name FROM sqlite_master WHERE type = "table"') || [];
171              
172             unless(@$tables){
173             $self->_init($db);
174             }
175             return $db;
176 1         130 }
177             );
178              
179 0         0 has deployer_patch_table => ( is => 'ro', isa => Str, default => 'patches' );
180              
181             has _patches_hashref => (
182             is => 'rw',
183             isa => HashRef,
184 15         46 default => sub{ {} }
185 15         980 );
186            
187 15         182 has supports_transactions => ( is => 'ro', isa => Bool, default => true );
188 0         0 has keep_newlines => ( is => 'ro', isa => Bool, default => false );
189              
190 1 50   1   145 method patches {
  1     20   6  
  1         2  
  13         69  
  13         35  
  13         82  
191 13 100       143 return $self->_patches_hashref if %{ $self->_patches_hashref };
  13         4775  
192              
193 13         237 my $patches = $self->_patches_hashref;
194              
195 13         5109 opendir(my $dh, $self->patch_path);
196 13         298 my @patch_files = sort readdir($dh);
197 13         60 closedir($dh);
198              
199 15         1265 shift @patch_files for 1..2; # Throw away "." and ".."
200              
201 15         6762 foreach my $file (@patch_files){
202 15         190839 my $json;
203             {
204 0         0 local $/ = undef;
  15         1132  
205 15         9684 open(my $fh, '<', $self->patch_path . '/' . $file);
206 15         604 $json = <$fh>;
207 11         13409 close($fh);
208             }
209 11 50       687 $json=~s/\n|\r\n/ /gm unless $self->keep_newlines;
210 15         107 my $patch_array = JSON::XS::decode_json($json);
211 15         80 foreach my $patch (@$patch_array) {
212              
213             my $status = $self->deployer_db->selectrow_hashref(
214 15         85 (sprintf q|SELECT * FROM %s WHERE name = ?|, $self->deployer_patch_table),{},$patch->{name});
215              
216 15 50       70 $self->record_patch($patch->{name}) unless $status;
217              
218 15         32 foreach (keys %$status) {
219 15         84 $patch->{$_} = $status->{$_};
220             }
221 15         39 $patch->{db} = $self->target_db;
222 15         511 $patch->{supports_transactions} = $self->supports_transactions;
223 10         68 $patches->{ $patch->{name} } = DBIx::Deployer::Patch->new( %$patch );
224             }
225             }
226 10         66 $self->_patches_hashref($patches);
227 10         58 return $self->_patches_hashref;
228             }
229              
230 1 50   1   165 method record_patch (Str $name) {
  1 50   15   1111  
  1 50       3  
  1 50       137  
  1 50       2662  
  1         1  
  1         11  
  10         54  
  10         28  
  10         152  
  10         32  
  10         328  
  30         1733  
  8         1814  
231 8 50       21 $self->deployer_db->do(
232             (sprintf q|INSERT INTO %s (name, deployed, verified) VALUES (?, ?, ?)|, $self->deployer_patch_table),
233             {}, $name, 0, 0) or die $@;
234             }
235              
236 1 50   1   182 method update_patch (InstanceOf['DBIx::Deployer::Patch'] $patch) {
  1 50   10   9  
  1 50       3  
  1 50       513  
  1 50       2600  
  1         2  
  1         19  
  8         44  
  8         259  
  9         63  
  7         82  
  13         186  
  13         61  
  13         61  
237             $self->deployer_db->do(
238             (sprintf q|UPDATE %s SET deployed = ?, verified = ? WHERE name = ?|, $self->deployer_patch_table),
239 13 50       358 {}, map{ $patch->$_ } qw(deployed verified name)
  13         30  
240             ) or die $@;
241             }
242              
243 1 50   1   133 method deploy_all {
  1     8   8  
  1         5  
  12         122  
  13         121  
  13         38  
244 13         610 my $patches = $self->patches;
245 11         137 foreach my $name (keys %$patches){
246 11         109 $self->deploy($patches->{$name});
247             }
248 11         54 return true;
249             }
250              
251 1 50   1   110 method deploy (InstanceOf['DBIx::Deployer::Patch'] $patch) {
  1 50   13   10605  
  1 50       16  
  1 50       2606  
  1 50       600  
  1         409  
  2         6  
  3         19  
  3         125  
  3         21  
  2         20  
  1         18  
  10         31  
  10         394  
252 10 100       2395 return if $patch->deployed;
253              
254 10 100       106 my @dependencies = @{ $patch->dependencies || [] };
  10         140271  
255              
256 1 100       17 if(@dependencies){
257 11         61 my $patches = $self->patches;
258 11         56 foreach my $name (@dependencies){
259 11 100       64 if($patches->{$name}){
260 11         55 $self->deploy($patches->{$name});
261             }
262             else{
263 11         24 die colored(['red'], q|Patch "| . $patch->name . qq|" failed: Patch dependency "$name" is not defined.|);
264             }
265             }
266             }
267              
268 11         91 eval{ $patch->deploy };
  11         32  
269 11         182 my $error = $@;
270             $self->update_patch($patch);
271   100         if($error){ die $error; }
272             }
273              
274 1 50   1   481 method _init (InstanceOf['DBI::db'] $db){
  1 50   11   645  
  1 50       590  
  1 50       2288  
  1 50       469  
  13         76860  
  2         29  
275   50         $db->do(
276             (sprintf q|CREATE TABLE %s (name VARCHAR UNIQUE, deployed INT, verified INT)|, $self->deployer_patch_table)
277             ) or die $@;
278             }
279             }
280              
281             # ABSTRACT: Light-weight database patch utility
282             # PODNAME: DBIx::Deployer
283              
284             __END__
285              
286             =pod
287              
288             =encoding UTF-8
289              
290             =head1 NAME
291              
292             DBIx::Deployer - Light-weight database patch utility
293              
294             =head1 VERSION
295              
296             version v1.2.0
297              
298             =head1 SYNOPSIS
299              
300             use DBIx::Deployer;
301             my $d = DBIx::Deployer->new(
302             target_dsn => 'dbi:Sybase:server=foo;database=bar;',
303             target_username => 'sa',
304             target_password => '1234',
305             patch_path => '../patches/',
306             deployer_db_file => 'deployer.db',
307             );
308              
309             # Run all patches (skipping over those already deployed)
310             $d->deploy_all;
311              
312             # Run one patch (and its dependencies)
313             my $patches = $d->patches;
314             $d->deploy( $patches->{'the patch name'} );
315              
316             =head1 DESCRIPTION
317              
318             Stop here. Go read about L<App::Sqitch> instead.
319              
320             Still here? That's probably because your database isn't supported by Sqitch :(. This module is a super-lightweight patch management tool that uses SQLite (see L<DBD::SQLite>) to store whether a patch has been deployed and verified.
321              
322             If you're wondering why I authored this and did not contribute to Sqitch, the answer is that I needed a quick and dirty solution to hold me over until I can use Sqitch after a database migration.
323              
324             =head1 VERSIONING
325              
326             Semantic versioning is adopted by this module. See L<http://semver.org/>.
327              
328             =head1 ATTRIBUTES
329              
330             =head2 target_db (DBI::db)
331              
332             This is the database handle where patches will be deployed. You may optionally pass C<target_dsn>, C<target_username>, and C<target_password> as an alternative to C<target_db>.
333              
334             =head2 target_dsn (Str)
335              
336             This is the dsn for your database that you will be performing patch deployments upon. See L<DBI> for more information on dsn strings.
337              
338             =head2 target_username (Str)
339              
340             The username for your database.
341              
342             =head2 target_password (Str)
343              
344             The password for your database.
345              
346             =head2 patch_path (Str REQUIRED)
347              
348             The directory path where you will store your patch files. PLEASE NOTE: DBIx::Deployer will attempt to process *all* files in this directory as patches regardless of extension or naming convention.
349              
350             =head2 deployer_db_file (Str)
351              
352             This is the file path where you would like your DBIx::Deployer SQLite database to be stored. This is required if using SQLite to manage your patch information.
353              
354             =head2 deployer_db (DBI::db)
355              
356             If you want your patch status information to live in a database other than SQLite, pass a DBI::db object during instantiation. Your database storing the patches must have a table conforming to the following structure:
357              
358             =over 4
359              
360             =item
361             * Table name: patches (you may specify a different table name by using the C<deployer_patch_table> attribute)
362              
363             =item
364             * Column: name VARCHAR (of acceptable length for patch names, recommended to be UNIQUE)
365              
366             =item
367             * Column: deployed BOOL/INT
368              
369             =item
370             * Column: verified BOOL/INT
371              
372             =back
373              
374             =head2 deployer_patch_table (Str OPTIONAL defaults to 'patches')
375              
376             Set this attribute if you want patch data to be recorded in a table other than 'patches'. See C<deployer_db>.
377              
378             =head2 supports_transactions (Bool OPTIONAL defaults to true)
379              
380             If your database supports transactions, C<deploy_sql> will be rolled back if verification fails, or if other errors occur during deployment of individual patches. If your database does not support transactions, you will need to set this attribute to false. Please be aware that without transactions, patches may find themselves in a state of being deployed but not verified... however, if that happens you'll likely have bigger fish to fry like figuring out how to repair your database. :)
381              
382             =head2 keep_newlines (Bool OPTIONAL defaults to false)
383              
384             For convenience and SQL readability, newlines are allowed in the SQL string values in the JSON patch files contrary to the JSON specification. By default, these newlines will be converted to spaces before being passed to the parser. If for some reason these transformations must not be done, set this attribute to true.
385              
386             =head1 METHODS
387              
388             =head2 deploy_all
389              
390             This will process all patches in the C<patch_path>. Some things to note:
391              
392             =over 4
393              
394             =item
395             * C<deploy> is idempotent. It will not run patch files that have already been deployed.
396              
397             =item
398             * If your database supports transactions, failed patches will be rolled back. Please be aware that an entire patch file (think multiple SQL statements) will not be rolled back if a patch (think single SQL statement) within the file fails.
399              
400             =back
401              
402             =head2 patches
403              
404             This returns an array of DBIx::Deployer::Patch objects. This is only useful if your intent is to use these objects in conjunction with C<deploy>.
405              
406             =head2 deploy (DBIx::Deployer::Patch REQUIRED)
407              
408             This method deploys the patch passed as an argument AND its corresponding dependencies.
409              
410             =head1 PATCH FILES
411              
412             Patches are written as JSON arrays, and stored in the C<patch_path> directory. These files must be able to be parsed by JSON::XS.
413              
414             # Patch Example
415             [
416             {
417             "name":"insert into foo",
418             "deploy_sql":"INSERT INTO foo VALUES (1, 2)",
419             "verify_sql":"SELECT COUNT(*) FROM foo",
420             "verify_expects":[ [1] ],
421             "dependencies": [ "create table foo" ]
422             },
423             {
424             "name":"create table foo",
425             "deploy_sql":"CREATE TABLE foo(a,b)",
426             "verify_sql":"PRAGMA table_info(foo)",
427             "verify_expects":[ [ 0, "a", "", 0, null, 0 ], [ 1, "b", "", 0, null, 0 ] ]
428             }
429             ]
430              
431             =head2 Patch Attributes
432              
433             =head3 name (Str REQUIRED)
434              
435             The name of the patch must be unique. It will be used as the primary key for the patch, and is how you will declare it as a dependency for other patches.
436              
437             =head3 dependencies (ArrayRef)
438              
439             Dependencies are listed by name. Take care not to create circular dependencies as I have no intentions of protecting against them.
440              
441             =head3 deploy_sql (Str)
442              
443             Patch files may contain multiple patches, but a single patch within a patch file may not contain more than one SQL statement to deploy.
444              
445             =head3 deploy_sql_args (ArrayRef)
446              
447             If using bind parameters in your C<deploy_sql> statement, the values in C<deploy_sql_args> will be used for those parameters. See L<DBI> and L<http://www.bobby-tables.com> for more information about bind parameters.
448              
449             =head3 deploy_script (Str)
450              
451             The C<deploy_script> will be passed as an argument to the C<system> command. Scripts are expected to handle transactions on their own. A non-zero exit status is reported as a failure.
452              
453             =head3 deploy_script_args (ArrayRef)
454              
455             The C<deploy_script_args> are passed to the C<system> command with the C<deploy_script> in PROGRAM LIST syntax. It may be useful to manipulate this attribute at runtime to pass environment-specific arguments.
456              
457             =head3 verify_sql (Str)
458              
459             This is a single query used to sanity check that your C<deploy_sql> was successful. By default, this parameter is required. See C<no_verify> if your use case requires deployment without verification.
460              
461             =head3 verify_sql_args (ArrayRef)
462              
463             If using bind parameters in your C<verify_sql> statement, the values in C<verify_sql_args> will be used for those parameters. See L<DBI> and L<http://www.bobby-tables.com> for more information about bind parameters.
464              
465             =head3 verify_expects (ArrayRef)
466              
467             The C<verify_sql> is selected using C<selectall_arrayref> (see L<DBI>). The C<verify_expects> attribute is a representation of the query result you would anticipate from the C<selectall_arrayref> method.
468              
469             =head3 no_verify (Bool)
470              
471             If set to true, patches will be marked verified WITHOUT having any tests run.
472              
473             =head1 REPOSITORY
474              
475             L<https://github.com/Camspi/DBIx-Deployer>
476              
477             =head1 SEE ALSO
478              
479             =over 4
480              
481             =item
482             * L<App::Sqitch> - seriously, use this module instead
483              
484             =item
485             * L<DBD::SQLite>
486              
487             =item
488             * L<DBI>
489              
490             =back
491              
492             =head1 CREDITS
493              
494             =over 4
495              
496             =item
497             * eMortgage Logic, LLC., for allowing me to publish this module to CPAN
498              
499             =back
500              
501             =head1 AUTHOR
502              
503             Chris Tijerina
504              
505             =head1 COPYRIGHT AND LICENSE
506              
507             This software is copyright (c) 2014-2017 by eMortgage Logic LLC.
508              
509             This is free software; you can redistribute it and/or modify it under
510             the same terms as the Perl 5 programming language system itself.
511              
512             =cut