File Coverage

blib/lib/DBIx/Class/DeploymentHandler/CLI.pm
Criterion Covered Total %
statement 32 131 24.4
branch 0 22 0.0
condition 0 3 0.0
subroutine 11 25 44.0
pod 11 11 100.0
total 54 192 28.1


line stmt bran cond sub pod time code
1             package DBIx::Class::DeploymentHandler::CLI;
2              
3 1     1   104306 use 5.006;
  1         5  
4 1     1   9 use strict;
  1         3  
  1         41  
5 1     1   8 use warnings;
  1         3  
  1         38  
6              
7 1     1   655 use FindBin qw($Script);
  1         1419  
  1         152  
8 1     1   698 use Moo;
  1         13055  
  1         5  
9 1     1   4883 use Types::Standard qw/ArrayRef HashRef InstanceOf Str/;
  1         92246  
  1         11  
10 1     1   3034 use DBIx::Class::DeploymentHandler;
  1         1906788  
  1         45  
11 1     1   1514 use DBIx::Class::DeploymentHandler::CLI::ConfigReader;
  1         4  
  1         42  
12 1     1   8 use Module::Runtime 'require_module';
  1         3  
  1         7  
13 1     1   3125 use Path::Tiny qw/path/;
  1         16828  
  1         78  
14              
15 1     1   11 use namespace::clean;
  1         3  
  1         12  
16              
17             =head1 NAME
18              
19             DBIx::Class::DeploymentHandler::CLI - Command line interface for deployment handler
20              
21             =head1 VERSION
22              
23             Version 0.3.0
24              
25             =cut
26              
27             our $VERSION = '0.3.0';
28              
29              
30             =head1 SYNOPSIS
31              
32             This module provides an command line interface for
33             L<DBIx::Class::DeploymentHandler>.
34              
35             A sample script using this module looks like:
36              
37             #! /usr/bin/env perl
38              
39             use strict;
40             use warnings;
41              
42             use PerlDance::Schema;
43             use DBIx::Class::DeploymentHandler::CLI;
44              
45             my $schema = PerlDance::Schema->connect('perldance');
46              
47             my $dh_cli = DBIx::Class::DeploymentHandler::CLI->new(
48             schema => $schema,
49             databases => 'MySQL',
50             args => \@ARGV,
51             );
52              
53             if (my $ret = $dh_cli->run) {
54             print $ret, "\n";
55             }
56              
57             Let's assume that you name the script C<dh-cli>.
58              
59             Now you can call the module's methods through commandline
60             parameters:
61              
62             ./dh-cli prepare-version-storage
63             ./dh-cli install-version-storage
64              
65             It doesn't matter whether you use dashes (C< - >) or
66             underscores (C< _ >) in the function name.
67              
68             =head1 BEST PRACTICES
69              
70             It is often desirable to follow what DeploymentHandler is
71             doing, thus we recommendend to turn on debugging in your
72             C<dh-cli> script:
73              
74             BEGIN {
75             $ENV{DBICDH_DEBUG} = 1;
76             }
77              
78             =head1 Configuration
79              
80             We are showing examples for YAML configuration files, but
81             you can also use any other format supported by L<Config::Any>.
82              
83             =head2 Values
84              
85             =over 4
86              
87             =item schema_class
88              
89             =item connection
90              
91             =item databases
92              
93             =back
94              
95             =head2 Files
96              
97             =over 4
98              
99             =item F<dh-cli.yaml>
100              
101             =item F<~/.dh-cli.yaml>
102              
103             =item F</etc/dh-cli.yaml>
104              
105             =back
106              
107             =head1 ATTRIBUTES
108              
109             =head2 schema
110              
111             L<DBIx::Class::Schema> object. This parameter is B<required>.
112              
113             =cut
114              
115             has schema => (
116             is => 'ro',
117             isa => InstanceOf['DBIx::Class::Schema'],
118             required => 1,
119             );
120              
121             =head2 databases
122              
123             Array reference with database names or single database
124             name as a string. This parameter is B<required>.
125              
126             It is passed directly to L<DBIx::Class::DeploymentHandler>.
127              
128             =cut
129              
130             has databases => (
131             isa => Str | ArrayRef,
132             is => 'ro',
133             default => sub { [qw( MySQL SQLite PostgreSQL )] },
134             required => 1,
135             );
136              
137             =head2 sql_translator_args
138              
139             Hash reference with parameters for L<SQL::Translator>.
140              
141             Defaults to:
142              
143             {
144             add_drop_table => 0,
145             producer_args => {
146             mysql_version => 5,
147             },
148             }
149              
150             L<SQL::Translator> defaults to use data types for totally
151             outdated versions of MySQL, thus we force the major version
152             almost all running instances are using.
153              
154             This prevents you from ending up with an C<enum> MySQL data type
155             for boolean columns in the schema instead of the C<boolean> one
156             supported by MySQL 5.
157              
158             It is passed directly to L<DBIx::Class::DeploymentHandler>.
159              
160             =cut
161              
162             has sql_translator_args => (
163             isa => HashRef,
164             is => 'ro',
165             default => sub { {
166             add_drop_table => 0,
167             producer_args => {
168             mysql_version => 5,
169             },
170             } },
171             );
172              
173             =head2 args
174              
175             Array reference with commandline parameters.
176              
177             =cut
178              
179             has args => (
180             isa => ArrayRef,
181             is => 'ro',
182             default => sub {[]},
183             );
184              
185             =head2 config
186              
187             Configuration object C<DBIx::Class::DeploymentHandler::CLI::ConfigReader>.
188             Created automatically.
189              
190             =cut
191              
192             has config => (
193             isa => InstanceOf['DBIx::Class::DeploymentHandler::CLI::ConfigReader'],
194             is => 'ro',
195             builder => '_config_builder',
196             );
197              
198             sub _config_builder {
199 0     0     my $config = DBIx::Class::DeploymentHandler::CLI::ConfigReader->new;
200             }
201              
202             =head2 config_files
203              
204             Candidates for configuration files to be used instead of the default ones.
205              
206             Type: array reference.
207              
208             =cut
209              
210             has config_files => (
211             isa => ArrayRef,
212             is => 'ro',
213             );
214              
215             =head2 run
216              
217             Determines method to be run.
218              
219             =cut
220              
221             sub run {
222 0     0 1   my $self = shift;
223 0           my $cmd;
224 0           my @params = @{$self->args};
  0            
225              
226             # check first whether we are using an alias
227 0           $Script =~ /^dh-(.*?)$/;
228              
229 0 0 0       if (defined $1 && $1 ne 'cli') {
    0          
230 0           $cmd = $1;
231             }
232             # if we have commandline arguments
233             elsif (@params) {
234 0           $cmd = shift @params;
235             }
236             else {
237 0           die "Missing command.\n";
238             }
239              
240 0           $cmd =~ s/(\w)-/$1_/g;
241              
242 0 0         if ($self->can($cmd)) {
243 0           return $self->$cmd( @params );
244             }
245              
246 0           die "No method for command $cmd";
247             }
248              
249             =head2 version
250              
251             Prints database and schema version.
252              
253             =cut
254              
255             sub version {
256 0     0 1   my $self = shift;
257 0           my $database_version = $self->database_version;
258 0           my $schema_version = $self->schema_version;
259              
260 0           return qq{Database version: $database_version
261             Schema version: $schema_version};
262             }
263              
264             =head2 database_version
265              
266             Retrieves schema version from database.
267              
268             Dies if version storage is missing from database.
269              
270             Returns 0 if version storage is present, but doesn't
271             contain any records.
272              
273             =cut
274              
275             sub database_version {
276 0     0 1   my $self = shift;
277 0           my $dh = $self->_dh_object;
278              
279             # check if version is present in the database
280 0 0         unless ($dh->version_storage_is_installed) {
281 0           die "Version storage isn't present in the database.";
282             }
283              
284 0           my $version = $dh->database_version;
285              
286 0 0         unless (defined $version) {
287 0           $version = 0;
288             }
289              
290 0           return $version;
291             }
292              
293             =head2 schema_version
294              
295             Retrieves schema version from schema.
296              
297             =cut
298              
299             sub schema_version {
300 0     0 1   my $self = shift;
301 0           my $dh = $self->_dh_object;
302              
303 0           return $dh->schema_version;
304             }
305              
306             =head2 custom_upgrade_directory
307              
308             Returns custom upgrade directory if possible.
309              
310             =cut
311              
312             sub custom_upgrade_directory {
313 0     0 1   my $self = shift;
314 0           my $dh = $self->_dh_object;
315              
316 0           my $db_version = $self->database_version;
317 0           my $schema_version = $self->schema_version;
318              
319 0 0         unless ($schema_version == $db_version + 1) {
320 0           die "Schema version $schema_version needs to be one version ahead of database version $db_version.";
321             }
322              
323 0           return "sql/_common/upgrade/${db_version}-${schema_version}";
324             }
325              
326             =head2 run_custom
327              
328             Runs a custom upgrade script.
329              
330             =cut
331              
332             sub run_custom {
333 0     0 1   my ($self, $module_name) = @_;
334              
335 0           my $module_upgrade = $self->_load_custom_upgrade_module($module_name);
336              
337 0           my $upgrade = $module_upgrade->new( schema => $self->schema );
338              
339 0           $upgrade->clear;
340 0           $upgrade->upgrade;
341             }
342              
343             =head2 install_custom
344              
345             Installs a custom upgrade script.
346              
347             =cut
348              
349             sub install_custom {
350 0     0 1   my ($self, $module_name, $before_sql) = @_;
351              
352 0           my $module_upgrade = $self->_load_custom_upgrade_module( $module_name );
353 0           my $custom_lib = $self->custom_upgrade_directory . '/lib';
354              
355             # create directory
356 0           my $module_path = $module_upgrade;
357 0           $module_path =~ s%::%/%g;
358              
359 0           my $po = path("$custom_lib/$module_path")->parent;
360 0           my @dirs = $po->mkpath;
361              
362             # copy current module there - Path::Tiny 0.070 required
363 0           my $lib_path = path("lib/${module_path}.pm");
364              
365 0           $lib_path->copy("$custom_lib/${module_path}.pm");
366              
367             # now we are creating the DH custom script
368 0           my $custom_script = <<EOF;
369             #! /usr/bin/env perl
370              
371             use strict;
372             use warnings;
373              
374             use FindBin;
375             use lib "$custom_lib";
376              
377             use $module_upgrade;
378              
379             sub {
380             my \$schema = shift;
381             my \$upgrade = $module_upgrade->new(
382             schema => \$schema
383             );
384              
385             \$upgrade->upgrade;
386             };
387              
388             EOF
389              
390             # prefix
391 0           my $script_prefix;
392              
393 0 0         if ($before_sql) {
394 0           $script_prefix = '000';
395             }
396             else {
397 0           $script_prefix = '002';
398             }
399              
400             # determine script name
401 0           my $custom_script_name = $self->custom_upgrade_directory . "/$script_prefix-"
402             . lc(path($module_path)->basename) . '.pl';
403              
404 0           path($custom_script_name)->spew($custom_script);
405              
406 0           return;
407             }
408              
409             sub _load_custom_upgrade_module {
410 0     0     my ($self, $module_name) = @_;
411              
412 0 0         unless ($module_name) {
413 0           die "Need name of upgrade module.";
414             }
415              
416 0           my $module_upgrade = $module_name;
417              
418             # determine module name
419 0           my $schema_class = ref($self->schema);
420              
421 0 0         unless ($module_upgrade =~ /::/) {
422             # prefix with proper namespace
423 0           $module_upgrade = "${schema_class}::Upgrades::$module_name";
424             }
425              
426 0           require_module( $module_upgrade );
427              
428 0           return $module_upgrade;
429             }
430              
431             =head2 prepare_version_storage
432              
433             =cut
434              
435             sub prepare_version_storage {
436 0     0 1   my $self = shift;
437              
438 0           my $dh = $self->_dh_object;
439              
440 0           $dh->prepare_version_storage_install;
441 0           $dh->prepare_deploy;
442              
443 0           return;
444             }
445              
446             =head2 install_version_storage
447              
448             =cut
449              
450             sub install_version_storage {
451 0     0 1   my $self = shift;
452              
453 0           my $dh = $self->_dh_object;
454              
455 0           $dh->install_version_storage( { version => 1 } );
456 0           $dh->add_database_version( { version => 1 } );
457              
458 0           return;
459             }
460              
461             =head2 prepare_upgrade
462              
463             =cut
464              
465             sub prepare_upgrade {
466 0     0 1   my $self = shift;
467              
468 0           my $dh = $self->_dh_object;
469              
470 0           my $db_version = $self->database_version;
471 0           my $schema_version = $self->schema_version;
472              
473 0 0         unless ($schema_version == $db_version + 1) {
474 0           die "Schema version $schema_version needs to be one version ahead of database version $db_version for preparing upgrades.";
475             }
476              
477 0           $dh->prepare_deploy;
478 0           $dh->prepare_upgrade(
479             {
480             from_version => $db_version,
481             to_version => $schema_version,
482             }
483             );
484              
485 0           return;
486             }
487              
488             =head2 upgrade
489              
490             =cut
491              
492             sub upgrade {
493 0     0 1   my $self = shift;
494              
495 0           my $dh = $self->_dh_object;
496              
497 0           my $db_version = $self->database_version;
498 0           my $schema_version = $self->schema_version;
499              
500 0           $dh->upgrade(
501             {
502             from_version => $db_version,
503             to_version => $schema_version,
504             }
505             );
506              
507 0           return;
508             }
509              
510             sub _dh_object {
511 0     0     my ($self, $schema_version) = @_;
512 0           my $dh;
513              
514 0           my %params = (
515             schema => $self->schema,
516             databases => $self->databases,
517             sql_translator_args => $self->sql_translator_args,
518             );
519              
520 0 0         if ($schema_version) {
521 0           $params{schema_version} = $schema_version;
522             }
523              
524 0           $dh = DBIx::Class::DeploymentHandler->new(\%params);
525              
526 0           return $dh;
527             }
528              
529             around BUILDARGS => sub {
530             my ( $orig, $class, @args ) = @_;
531             my $arghash = { @args };
532             my $config;
533             my $config_reader;
534              
535             if ( exists $arghash->{config} ) {
536             $config = $arghash->{config};
537             }
538              
539             unless ( $arghash->{schema} ) {
540             # build schema based on configuration
541             unless ( $config ) {
542             $config_reader = DBIx::Class::DeploymentHandler::CLI::ConfigReader->new;
543             $config = $config_reader->config;
544             push @args, ( config => $config_reader );
545             }
546              
547             if ( exists $config->{schema_class} && exists $config->{connection} ) {
548             my $schema_module = $config->{schema_class};
549             unless (require_module( $schema_module )) {
550             die "Module $schema_module failed to load."
551             };
552             my $schema = $schema_module->connect( $config->{connection} );
553             push @args, ( schema => $schema );
554             }
555             }
556              
557             unless ( $arghash->{databases} ) {
558             # build list of database based on configuration
559             unless ( $config ) {
560             $config_reader = DBIx::Class::DeploymentHandler::CLI::ConfigReader->new;
561             $config = $config_reader->config;
562             push @args, ( config => $config_reader );
563             }
564              
565             if ( exists $config->{databases} ) {
566             warn "Databases are: ", $config->{databases}, "\n";
567             push @args, ( databases => $config->{databases} );
568             }
569             }
570              
571             return $class->$orig ( @args );
572             };
573              
574             =head1 AUTHOR
575              
576             Stefan Hornburg (Racke), C<< <racke at linuxia.de> >>
577              
578             =head1 BUGS
579              
580             Please report any bugs or feature requests at
581             L<https://github.com/interchange/DBIx-Class-DeploymentHandler-CLI/issues>
582              
583             I will be notified, and then you'll automatically be notified of progress
584             on your bug as I make changes.
585              
586             =head1 ACKNOWLEDGEMENTS
587              
588             None so far.
589              
590             =head1 LICENSE AND COPYRIGHT
591              
592             Copyright 2016-2022 Stefan Hornburg (Racke).
593              
594             This program is free software; you can redistribute it and/or modify it
595             under the terms of the the Artistic License (2.0). You may obtain a
596             copy of the full license at:
597              
598             L<http://www.perlfoundation.org/artistic_license_2_0>
599              
600             Any use, modification, and distribution of the Standard or Modified
601             Versions is governed by this Artistic License. By using, modifying or
602             distributing the Package, you accept this license. Do not use, modify,
603             or distribute the Package, if you do not accept this license.
604              
605             If your Modified Version has been derived from a Modified Version made
606             by someone other than you, you are nevertheless required to ensure that
607             your Modified Version complies with the requirements of this license.
608              
609             This license does not grant you the right to use any trademark, service
610             mark, tradename, or logo of the Copyright Holder.
611              
612             This license includes the non-exclusive, worldwide, free-of-charge
613             patent license to make, have made, use, offer to sell, sell, import and
614             otherwise transfer the Package with respect to any patent claims
615             licensable by the Copyright Holder that are necessarily infringed by the
616             Package. If you institute patent litigation (including a cross-claim or
617             counterclaim) against any party alleging that the Package constitutes
618             direct or contributory patent infringement, then this Artistic License
619             to you shall terminate on the date that such litigation is filed.
620              
621             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
622             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
623             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
624             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
625             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
626             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
627             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
628             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
629              
630              
631             =cut
632              
633             1; # End of DBIx::Class::DeploymentHandler::CLI