App-JESP

 view release on metacpan or  search on metacpan

LICENSE  view on Meta::CPAN

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


    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

use DBIx::Simple;
use File::Spec;
use IO::Interactive;
use Log::Any qw/$log/;
use String::Truncate;

# 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


  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

              "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

    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

    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

    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

#! perl -w

use Test::Most;
use App::JESP;

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

#! perl -w

use Test::Most;
use App::JESP;

use Term::ANSIColor;
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

use Test::Most;
use App::JESP;

# 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

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

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

    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 $@;
}

use App::JESP;

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

    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

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


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

#! perl -w

use Test::Most;
use App::JESP;

use Carp::Always;

# 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

}

use App::JESP;
use File::Spec;

# 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


use Test::Most;
use App::JESP;

# 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

# 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.403 second using v1.01-cache-2.11-cpan-9b1e4054eb1 )