App-JESP

 view release on metacpan or  search on metacpan

LICENSE  view on Meta::CPAN

336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed.  Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
 
  Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
 
  7. Additional Terms.
 
  "Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law.  If additional permissions
apply only to part of the Program, that part may be used separately

README.md  view on Meta::CPAN

8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
    jesp
 
Or use from your own program (in Perl):
 
    my $jesp = App::JESP->new({
        interactive => 0, # No ANSI color
        home        => 'path/to/jesphome',
        dsn         => ...,
        username    => ...,
        password    => ...
    });
 
    $jesp->install();
    $jesp->deploy();
 
# CONFIGURATION
 
All JESP configuration must live in a JESP home directory.
 
This home directory must contain a plan.json file, containing the patching

lib/App/JESP.pm  view on Meta::CPAN

11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
use Log::Any qw/$log/;
 
# Settings
## DB Connection attrbutes.
has 'dsn' => ( is => 'ro', isa => 'Str', required => 1 );
has 'username' => ( is => 'ro', isa => 'Maybe[Str]', required => 1);
has 'password' => ( is => 'ro', isa => 'Maybe[Str]', required => 1);
has 'home' => ( is => 'ro', isa => 'Str', required => 1 );
 
## JESP Attributes
has 'prefix' => ( is => 'ro', isa => 'Str', default => 'jesp_' );
has 'driver_class' => ( is => 'ro', isa => 'Str', lazy_build => 1);
 
# Operational stuff
has 'get_dbh' => ( is => 'ro', isa => 'CodeRef', default => sub{
                       my ($self) = @_;
                       return sub{
                           return DBI->connect( $self->dsn(), $self->username(), $self->password(),
                                                { RaiseError => 1,
                                                  PrintError => 0,
                                                  AutoCommit => 1,
                                              });
                       };
                   });
 
has 'dbix_simple' => ( is => 'ro', isa => 'DBIx::Simple', lazy_build => 1);
has 'patches_table_name' => ( is => 'ro', isa => 'Str' , lazy_build => 1);
has 'meta_patches' => ( is => 'ro', isa => 'ArrayRef[HashRef]',

lib/App/JESP.pm  view on Meta::CPAN

302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
  jesp
 
Or use from your own program (in Perl):
 
    my $jesp = App::JESP->new({
        interactive => 0, # No ANSI color
        home        => 'path/to/jesphome',
        dsn         => ...,
        username    => ...,
        password    => ...
    });
 
    $jesp->install();
    $jesp->deploy();
 
=cut
 
=head1 CONFIGURATION
 
All JESP configuration must live in a JESP home directory.

lib/App/JESP/Cmd/CommandJESP.pm  view on Meta::CPAN

28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
              "The DSN to connect to the DB. See https://metacpan.org/pod/DBI#parse_dsn for DSN format"
              ."\nExamples:\n"
              ."\n dbi:mysql:database=testdb;host=localhost;port=3306"
              ."\n dbi:SQLite:dbname=demo/test.db"
              ."\n dbi:Pg:dbname=testdb;host=localhost;port=5432"
              ."\n"
 
          ],
        [ 'username=s' =>
              "The username to connect to the DB", { default => undef } ],
        [ 'password=s' =>
              "The password to connect to the DB", { default => undef } ],
        [ 'prefix=s' =>
              "The prefix for all jesp metatables. Defaults to 'jesp_'" ],
        $class->options($app),
    )
}
 
=head2 options
 
Override this in subclasses to add options to opt_spec

lib/App/JESP/Cmd/CommandJESP.pm  view on Meta::CPAN

61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
    my ( $self, $opts, $args ) = @_;
    unless( $opts->dsn() ){ die "Missing 'dsn' option. Run with -h\n"; }
    unless( $opts->home() ){ die "Missing 'home' option. Run with -h\n"; }
 
    # Time to build the JESP
    $log->debug("Building App::JESP instance");
    my $jesp = App::JESP->new({
        dsn => $opts->dsn(),
        home => $opts->home(),
        ( $opts->username() ? ( username => $opts->username() ) : ( username => undef ) ),
        ( $opts->password() ? ( password => $opts->password() ) : ( password => undef ) ),
    });
    $log->debug("App::JESP instance built");
 
    # Inject __jesp in myself.
    # Yes this is a bit dirty, but it works.
    $self->{__jesp} = $jesp;
    $self->validate( $opts, $args );
}
 
=head2 validate

lib/App/JESP/Driver.pm  view on Meta::CPAN

72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
my @stderr;
my $on_stderr = sub{
    $log->warn( @_ );
    push @stderr , @_;
};
 
my $properties = {};
my ($scheme, $driver, $attr_string, $attr_hash, $driver_dsn) = DBI->parse_dsn( $self->jesp()->dsn() );
ref($self)->_OdbcParse( $driver_dsn , $properties , [] );
$properties->{user} ||= $self->jesp()->username();
$properties->{password} ||= $self->jesp()->password();
$properties = {
    %$properties,
    %{ defined( $attr_hash ) ? $attr_hash : {} },
    dsn => $self->jesp()->dsn(),
    scheme => $scheme,
    driver => $driver,
    driver_dsn => $driver_dsn,
    attr_string => $attr_string,
};
my %EXTRA_ENV = ();

lib/App/JESP/Driver/mysql.pm  view on Meta::CPAN

45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
my @cmd = ( $mysql );
 
# Time to build the command according to the dsn properties
my $properties = {};
{
    eval "require DBD::mysql" or die "Please install DBD::mysql for this to work\n";
 
    my ($scheme, $driver, $attr_string, $attr_hash, $driver_dsn) = DBI->parse_dsn( $self->jesp()->dsn() );
    DBD::mysql->_OdbcParse( $driver_dsn , $properties , [] );
    $properties->{user} ||= $self->jesp()->username();
    $properties->{password} ||= $self->jesp()->password();
    $log->trace('mysql properties: '.Dumper( $properties ));
}
 
push @cmd , '-B'; # This is a batch command. We dont want interactive at all.
 
if( my $user = $properties->{user} ){
    push @cmd , ( '-u' , String::ShellQuote::shell_quote( $user ));
}
if( my $database = $properties->{database} ){
    push @cmd , ( '-D' , String::ShellQuote::shell_quote( $database ));
}
if( my $host = $properties->{host} ){
    push @cmd , ( '-h' , String::ShellQuote::shell_quote( $host ));
}
if( my $port = $properties->{port} ){
    push @cmd , ( '-P' , String::ShellQuote::shell_quote( $port ));
}
if( my $mysql_socket = $properties->{mysql_socket} ){
    push @cmd , ( '-S' , String::ShellQuote::shell_quote( $mysql_socket ));
}
if( my $password = $properties->{password} ){
    push @cmd , ( '-p'.String::ShellQuote::shell_quote( $password ));
}
 
 
my $on_stdout = sub{
    $log->info( @_ );
};
my @stderr;
my $on_stderr = sub{
    $log->warn( @_ );
    push @stderr , @_;

t/build.t  view on Meta::CPAN

1
2
3
4
5
6
7
8
9
10
11
12
13
#! perl -w
 
 
use Log::Any::Adapter qw/Stderr/;
 
ok( my $jesp = App::JESP->new({ dsn => 'dbi:SQLite:dbname=:memory:', username => undef, password => undef, home => 'bla' }) );
ok( ! $jesp->interactive(), "Ok interactive is false");
is( $jesp->prefix() , 'jesp_' );
ok( $jesp->dbix_simple(), "Ok can get DBIx::Simple DB");
 
done_testing();

t/colors.t  view on Meta::CPAN

1
2
3
4
5
6
7
8
9
10
11
12
#! perl -w
 
 
use Log::Any::Adapter qw/Stderr/;
 
ok( my $jesp = App::JESP->new({ dsn => 'dbi:SQLite:dbname=:memory:', username => undef, password => undef, home => 'bla', interactive => 1 }) );
is( $jesp->colorizer()->colored("Foo", "blue") , Term::ANSIColor::colored("Foo", "blue") );
 
done_testing();

t/deploy.t  view on Meta::CPAN

5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
 
# Test deployment in SQLite
 
# use Log::Any::Adapter qw/Stderr/;
 
# A home that is there.
my $jesp = App::JESP->new({ dsn => 'dbi:SQLite:dbname=:memory:',
                            username => undef,
                            password => undef,
                            home => './t/home/'
                        });
throws_ok(sub{ $jesp->deploy() } , qr/ERROR querying meta/ );
 
# Time to install
$jesp->install();
 my $status = $jesp->status();
is( scalar( @{$status->{plan_patches}} ) , 4, "Ok 4 patches in plan");
is( $jesp->deploy(), 4, "Ok applied 4 patches");

t/deploy_mysql.t  view on Meta::CPAN

22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
my @mysqls = ( $mysqld );
 
if( $ENV{EXTENDED_TESTING} ){
    # Other flavours of Test::mysqld
    push @mysqls , Test::mysqld->new( my_cnf => { port => Net::EmptyPort::empty_port() } );
}
 
foreach my $mysql ( @mysqls ){
    # A home that is there.
    my $jesp = App::JESP->new({ dsn => $mysql->dsn(),
                                password => '',
                                username => '',
                                home => './t/home_mysql/'
                            });
    throws_ok(sub{ $jesp->deploy() } , qr/ERROR querying meta/ );
 
    # Time to install
    $jesp->install();
    # And deploy
    is( $jesp->deploy(), 2, "Ok applied 2 patches");
    is( $jesp->deploy(), 0, "Ok applied 0 patches on the second call");

t/deploy_mysqlpasswd.t  view on Meta::CPAN

19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
my $dbh = DBI->connect( $mysql->dsn() , '', '' , { RaiseError => 1, AutoCommit => 1 });
$dbh->do('CREATE DATABASE grotest');
$dbh->do('GRANT ALL ON grotest.* TO \'salengro\'@\'localhost\' IDENTIFIED BY \'mufflin!\'');
 
my $dsn = $mysql->dsn();
$dsn =~ s/dbname=test/dbname=grotest/;
$dsn =~ s/;user=root//;
 
# # A home that is there.
my $jesp = App::JESP->new({ dsn => $dsn,
                            password => 'mufflin!',
                            username => 'salengro',
                            home => './t/home_mysql/'
                        });
$jesp->install();
# And deploy
is( $jesp->deploy(), 2, "Ok applied 2 patches");
is( $jesp->deploy(), 0, "Ok applied 0 patches on the second call");
 
done_testing();

t/deploy_pgsql.t  view on Meta::CPAN

6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
    plan skip_all => "Test::PostgreSQL is required for this test" if $@;
    eval "use Net::EmptyPort";
    plan skip_all => "Net::EmptyPort is required for this test" if $@;
}
 
 
my $pgsql = eval{ Test::PostgreSQL->new({ port => Net::EmptyPort::empty_port() }) } or plan skip_all => $@.' - '.$Test::PostgreSQL::errstr;
 
my $jesp = App::JESP->new({ dsn => $pgsql->dsn(),
                            password => '',
                            username => 'postgres',
                            home => './t/home_pgsql/'
                        });
throws_ok(sub{ $jesp->deploy() } , qr/ERROR querying meta/ );
 
# Time to install
$jesp->install();
# And deploy
is( $jesp->deploy(), 2, "Ok applied 2 patches");
is( $jesp->deploy(), 0, "Ok applied 0 patches on the second call");

t/deploy_script.t  view on Meta::CPAN

20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
    plan skip_all => 'No sqlite3 found';
}
delete $ENV{PATH};
$ENV{WHICH_SQLITE3} = $which_sqlite3;
 
my ($fh, $dbname) = File::Temp::tempfile( EXLOCK => 0 );
 
# A home that is there.
my $jesp = App::JESP->new({ dsn => "dbi:SQLite:dbname=$dbname",
                            username => undef,
                            password => undef,
                            home => './t/homescripts/'
                        });
 
# Time to install
$jesp->install();
is( $jesp->deploy(), 3, "Ok applied 3 patches");
 
unlink $dbname;
 
done_testing();

t/home_mysql/patches/sakila1.sql  view on Meta::CPAN

284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
CREATE TABLE staff (
  staff_id TINYINT UNSIGNED NOT NULL AUTO_INCREMENT,
  first_name VARCHAR(45) NOT NULL,
  last_name VARCHAR(45) NOT NULL,
  address_id SMALLINT UNSIGNED NOT NULL,
  picture BLOB DEFAULT NULL,
  email VARCHAR(50) DEFAULT NULL,
  store_id TINYINT UNSIGNED NOT NULL,
  active BOOLEAN NOT NULL DEFAULT TRUE,
  username VARCHAR(16) NOT NULL,
  password VARCHAR(40) BINARY DEFAULT NULL,
  last_update TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY  (staff_id),
  KEY idx_fk_store_id (store_id),
  KEY idx_fk_address_id (address_id),
  CONSTRAINT fk_staff_store FOREIGN KEY (store_id) REFERENCES store (store_id) ON DELETE RESTRICT ON UPDATE CASCADE,
  CONSTRAINT fk_staff_address FOREIGN KEY (address_id) REFERENCES address (address_id) ON DELETE RESTRICT ON UPDATE CASCADE
)ENGINE=InnoDB DEFAULT CHARSET=utf8;
 
--
-- Table structure for table `store`

t/home_pgsql/patches/pagila1.sql  view on Meta::CPAN

562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
CREATE TABLE staff (
    staff_id integer DEFAULT nextval('staff_staff_id_seq'::regclass) NOT NULL,
    first_name character varying(45) NOT NULL,
    last_name character varying(45) NOT NULL,
    address_id smallint NOT NULL,
    email character varying(50),
    store_id smallint NOT NULL,
    active boolean DEFAULT true NOT NULL,
    username character varying(16) NOT NULL,
    password character varying(40),
    last_update timestamp without time zone DEFAULT now() NOT NULL,
    picture bytea
);
 
 
ALTER TABLE public.staff OWNER TO postgres;
 
--
-- Name: store_store_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres
--

t/install.t  view on Meta::CPAN

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#! perl -w
 
 
 
# use Log::Any::Adapter qw/Stderr/;
 
ok( my $jesp = App::JESP->new({ dsn => 'dbi:SQLite:dbname=:memory:', username => undef, password => undef, home => 'bla' }) );
ok( $jesp->install(), "Ok can install JESP in the given Database");
 
my @installed_patches  = $jesp->dbix_simple()->select( $jesp->patches_table_name() )->hashes();
 
is( scalar( @installed_patches ) , 1 );
is( $installed_patches[0]->{id} , $jesp->prefix().'meta_zero' , "Good zero name" );
ok( exists( $installed_patches[0]->{applied_datetime} ) , "There is an applied time" );
 
# Try pushing a patch in the meta patches, and check that it becomes applied.
push @{$jesp->meta_patches()},

t/install_dbs.t  view on Meta::CPAN

10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
}
 
 
# use Log::Any::Adapter qw/Stderr/;
 
# First something with SQLite.
my @connection_params = ({ dsn => 'dbi:SQLite:dbname=:memory:',
                           username => undef,
                           password => undef
                       });
 
# Then something with MySQL
my $mysql = Test::mysqld->new( my_cnf => {
    'skip-networking' => '1',
    socket => File::Spec->catfile( File::Spec->tmpdir() , 'socket-'.$$.'-testmysqld')
});
 
if( $mysql ){
    push @connection_params, { dsn => $mysql->dsn(),
                               password => '',
                               username => ''
                           };
}else{
    diag("Warning: could not build Test::mysqld ".$Test::mysqld::errstr);
}
 
my $pgsql = eval{ Test::PostgreSQL->new(); };
if( $pgsql ){
    push @connection_params, { dsn => $pgsql->dsn(),
                               password => undef,
                               username => 'postgres'
                           };
}else{
    diag("Warning: could not build Test::PostgreSQL: ".$@.' - '.$Test::PostgreSQL::errstr);
}
 
foreach my $connect_params ( @connection_params ){
    ok( my $jesp = App::JESP->new({ dsn => $connect_params->{dsn},
                                    username => $connect_params->{username},
                                    password => $connect_params->{postgres},
                                    home => 'bla'
                                }) );
    ok( $jesp->install(), "Ok can install JESP in the given Database");
    my @installed_patches  = $jesp->dbix_simple()->select( $jesp->patches_table_name() )->hashes();
 
    is( scalar( @installed_patches ) , 1 );
    is( $installed_patches[0]->{id} , $jesp->prefix().'meta_zero' , "Good zero name" );
    ok( exists( $installed_patches[0]->{applied_datetime} ) , "There is an applied time" );
}
ok(1);

t/plan.t  view on Meta::CPAN

2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
 
# use Log::Any::Adapter qw/Stderr/;
 
{
    # A home that is not there.
    my $jesp = App::JESP->new({ dsn => 'dbi:SQLite:dbname=:memory:',
                                username => undef,
                                password => undef,
                                home => 'bla'
                            });
    throws_ok(sub{ my $plan = $jesp->plan() } , qr/does not exists/ );
}
 
{
    # A home that is there.
    my $jesp = App::JESP->new({ dsn => 'dbi:SQLite:dbname=:memory:',
                                username => undef,
                                password => undef,
                                home => './t/home/'
                            });
    ok( my $plan = $jesp->plan() );
 
    ok( my $patches = $plan->patches() );
    is( scalar( @{$patches} ) , 4 , "4 test patches");
    foreach my $patch ( @{$patches} ){
        ok( $patch->sql() , "Ok got SQL" );
    }
}

t/plan_script.t  view on Meta::CPAN

6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# use Log::Any::Adapter qw/Stderr/;
 
if( $^O =~ /Win/ ){
    plan skip_all => 'No script test on windows please';
}
 
{
    # A home that is there.
    my $jesp = App::JESP->new({ dsn => 'dbi:SQLite:dbname=:memory:',
                                username => undef,
                                password => undef,
                                home => './t/homescripts/'
                            });
    ok( my $plan = $jesp->plan() );
 
    ok( my $patches = $plan->patches() );
    is( scalar( @{$patches} ) , 3 , "3 test patches");
    ok( ! $patches->[2]->sql() , "No sql in patch 5");
    ok( $patches->[2]->script_file(), "Patch 3 is a script" );
}



( run in 0.383 second using v1.01-cache-2.11-cpan-e9199f4ba4c )