App-Padadoy

 view release on metacpan or  search on metacpan

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

use strict;
use warnings;
package App::Padadoy;
{
  $App::Padadoy::VERSION = '0.125';
}
#ABSTRACT: Simply deploy PSGI applications

use 5.010;
use autodie;
use Try::Tiny;
use IPC::System::Simple qw(run capture $EXITVAL);
use File::Slurp;
use List::Util qw(max);
use File::ShareDir qw(dist_file);
use File::Path qw(make_path);
use File::Basename qw(dirname);
use File::Spec::Functions qw(catdir catfile rel2abs);
use Git::Repository;
use Sys::Hostname;
use YAML::Any qw(LoadFile Dump);
use Cwd;

# required for deployment
use Plack::Handler::Starman qw();
use Carton qw(0.9.4);

# required for testing
use Plack::Test qw();
use HTTP::Request::Common qw();

our @commands = qw(init start stop restart config status create checkout
        deplist cartontest remote version update enable logs);
our @remote_commands = qw(init start stop restart config status version); # TODO: create deplist checkout cartontest
our @configs = qw(user base repository port pidfile quiet remote);

# _msg( $fh, [\$caller], $msg [@args] )
sub _msg (@) { 
    my $fh = shift;
    my $caller = ref($_[0]) ? ${(shift)} :
            ((caller(2))[3] =~ /^App::Padadoy::(.+)/ ? $1 : '');
    my $text  = shift;
    say $fh (($caller ? "[$caller] " : "") 
        . (@_ ? sprintf($text, @_) : $text));
}

sub fail (@) {
    _msg(*STDERR, @_);
    exit 1;
}

sub msg {
    my $self = shift;
    _msg( *STDOUT, @_ ) unless $self->{quiet};
}


sub new {
    my ($class, $config, %values) = @_;

    my $self = bless { }, $class;
    my $yaml = { };

    if ($config) {
        # $self->msg("Reading configuration from $config");
        try {
            $yaml = LoadFile( $config );
        } catch {
            fail $_;
        };
        $self->{base} = rel2abs(dirname($config));
    } else {
        $self->{base} = $values{base} // cwd;
    }

    foreach (@configs) {
        $yaml->{$_} = $values{$_} if defined $values{$_};
    }

    $self->{user}       = $yaml->{user} || getlogin || getpwuid($<);
    $self->{repository} = $yaml->{repository} || catdir($self->{base},'repository');
    $self->{port}       = $yaml->{port} || 6000;
    $self->{pidfile}    = $yaml->{pidfile} || catfile($self->{base},'starman.pid');
    $self->{remote}     = $yaml->{remote};

    # config file
    $self->{config} = $config;

    # TODO: validate config values

    fail "Invalid remote value: ".$self->{remote} 
        if $self->{remote} and $self->{remote} !~ qr{^[^@]+@[^:]+:[~/].*$};

    $self;
}


sub create {
    my $self   = shift;
    my $module = shift;

    $self->{module} = $module;
    fail("Invalid module name: $module") 

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

    # .openshift/      - hooks for OpenShift (o)
    #   action_hooks/  - scripts that get run every git push (o)
}


sub deplist {
    my $self = shift;

    eval "use Perl::PrereqScanner";
    fail "Perl::PrereqScanner required" if $@;

    fail "not implemented yet";

    # TODO: dependencies should be detectable automatically
    # with Perl::PrereqScanner::App

    $self->msg("You must initialize a git repository and add remotes");
}


sub init {
    my $self = shift;
    $self->msg("Initializing environment");

    fail "Expected to run in ".$self->{base} 
        unless cwd eq $self->{base};
    fail 'Expected to run in an EMPTY base directory' 
        if grep { $_ ne $0 and $_ ne 'padadoy.yml' } <*>;

    $self->_provide_config('init');

    try { 
        my $out = capture('git', 'init', '--bare', $self->{repository});
        $self->msg(\'init',$_) for split "\n", $out;
    } catch {
        fail 'Failed to init git repository in ' . $self->{repository};
    };

    my $file = $self->{repository}.'/hooks/update';
    $self->msg("$file as executable");
    write_file($file, read_file(dist_file('App-Padadoy','update')));
    chmod 0755,$file;

    $file = $self->{repository}.'/hooks/post-receive';
    $self->msg("$file as executable");
    write_file($file, read_file(dist_file('App-Padadoy','post-receive')));
    chmod 0755,$file;

    $self->msg("logs/");
    mkdir 'logs';
 
    $self->msg("app -> current/app");
    symlink 'current/app','app';

    $self->msg("Pushing to git repository %s@%s:%s will update", 
        $self->{user}, hostname, $self->{repository});
}


sub config {
    say shift->_config;
}

sub _config {
    my $self = shift;
    Dump( { map { $_ => $self->{$_} // '' } @configs } );
}


sub restart {
    my $self = shift;

    my $pid = $self->_pid;
    if ($pid) {
        $self->msg("Gracefully restarting starman as deamon on port %d (pid in %s)",
            $self->{port}, $self->{pidfile});
        run('kill','-HUP',$pid);
    } else {
        $self->start;
    }
}


sub start {
    my $self = shift;

    fail "No configuration file found" unless $self->{config};

    chdir $self->{base}.'/app';


if (0) { # FIXME
    # check whether dependencies are satisfied
    my @out = split "\n", capture('carton check --nocolor 2>&1');
    if (@out > 1) { # carton check always seems to exit with zero (?!)
        $out[0] = 
        _msg( *STDERR, \'start', $_) for @out;
        exit 1;
    }
}

    # make sure log files exist
    my $logs = catdir($self->{base},'logs');
    make_path($logs) unless -d $logs;

    foreach ( grep { ! -e $_ } 
              map { catfile($logs,$_) } qw(error.log access.log) ) {
        open (my $fh, '>>', $_); 
        close $fh;
    }

    $self->msg("Starting starman as deamon on port %d (pid in %s)",
        $self->{port}, $self->{pidfile});

    # TODO: refactor after release of carton 1.0
    $ENV{PLACK_ENV} = 'production';
    my @opt = (
        'starman','--port' => $self->{port},
        '-D','--pid'   => $self->{pidfile},
        '--error-log'  => catfile($logs,'error.log'),
        '--access-log' => catfile($logs,'access.log'),

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

    $self->msg("revision $revision checked out and tested at $newdir");
}


sub enable {
    my $self = shift;

    fail "Missing directory ".$self->{base} unless -d $self->{base};
    chdir $self->{base};

    my $new     = catdir($self->{base},'new');
    my $current = catdir($self->{base},'current');

    fail "Missing directory $new" unless -d $new;
 
    $self->msg("$new -> current");
    run('rm','-f','current');
    run('mv','new','current');

    chdir $current;

    # TODO: re-read full configuration (?)
    $self->{base} = $current;

    # graceful restart seems broken
    $self->stop;
    $self->start;

    # TODO: cleanup old revisions?
}


sub remote {
    my $self = shift;
    my $command = shift;

    fail 'no remote configured' unless $self->{remote};
    fail 'missing remote command' unless $command;

    fail "command $command not supported on remote"
        unless grep { $_ eq $command } @remote_commands;
    
    $self->{remote} =~ /^(.+):(.+)$/ or fail 'invalid remote value: '.$self->{remote};
    my ($userhost,$dir) = ($1,$2);
    fail 'remote directory should not contain spaces' if $dir =~ /\s/;

    $self->msg("running padadoy on ".$self->{remote});

    run('ssh',$userhost,"cd $dir && padadoy $command ".join ' ', @_);
}


sub logs {
    my $self = shift;
    my $logs = catdir($self->{base},'logs');
    run('tail','-F', map { catfile($logs,$_) } qw(error.log access.log));
}


sub version {
    say 'This is padadoy version '.($App::Padadoy::VERSION || '??');
    exit;
}

1;


__END__
=pod

=head1 NAME

App::Padadoy - Simply deploy PSGI applications

=head1 VERSION

version 0.125

=head1 SYNOPSIS

Create a new application and start it locally on your development machine:

  $ padadoy create Your::Module
  $ plackup app/app.psgi

Start application locally as deamon with bundled dependencies:

  $ padadoy cartontest
  $ padadoy start

Show status of your running application and stop it:

  $ padadoy status
  $ padadoy stop

Manage your application files in a git repository:

  $ git add *
  $ git commit -m "inial commit"

Deploy the application at dotCloud

  $ dotcloud create nameoryourapp
  $ dotcloud push nameofyourapp

Prepare your own deployment machine (as C<remote> in C<padadoy.yml>):

  $ padadoy remote init

Add your deployment machine as git remote and deploy:

  $ git remote add prod ...
  $ git push prod master

=head1 DESCRIPTION

I<This is an early preview release, be warned! Design changes are likely,
at least until a stable carton 1.0 has been released!>

L<Padadoy|padadoy> is a command line application to facilitate deployment of
L<PSGI> applications, inspired by L<http://dotcloud.com>. Padadoy is based on



( run in 2.059 seconds using v1.01-cache-2.11-cpan-ad19def0cd9 )