The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Net::Amazon::HadoopEC2::SSH;
use Moose;
use Moose::Util::TypeConstraints;
use Carp;
use Path::Class::File;
use File::Spec;
use File::Basename;
use Net::SSH::Perl;
use Net::Amazon::HadoopEC2::SSH::Response;

subtype 'Net::Amazon::HadoopEC2::SSH::KeyFile'
    => as 'Path::Class::File'
    => where {
        my $st = $_[0]->stat or return;
        $st->mode == 0100600 or return;
        1;
    };

coerce 'Net::Amazon::HadoopEC2::SSH::KeyFile'
    => from 'Str'
    => via {
        return Path::Class::File->new($_[0]);
    };

has host => (
    is => 'ro',
    isa => 'Str',
    required => 1,
);

has key_file => (
    is => 'ro',
    isa => 'Net::Amazon::HadoopEC2::SSH::KeyFile',
    coerce => 1,
    required => 1,
);

has retry_max => (
    is => 'rw',
    isa => 'Int',
    required => 1,
    default => 10,
);

has _ssh => (
    is => 'ro',
    isa => 'Net::SSH::Perl',
    lazy => 1,
    default => sub {
        my $self = shift;
        my $ssh;
        my $err = 0;
        while (1) { 
            $ssh = eval {
                Net::SSH::Perl->new(
                    $self->host,
                    identity_files => [ $self->key_file ],
                );
            };
            $err++ if $@;
            $ssh and last;
            $err > $self->retry_max and last;
            sleep 1;
        }
        $ssh or die;
        $ssh->login('root');
        return $ssh;
    },
);

__PACKAGE__->meta->make_immutable;

no Moose;
no Moose::Util::TypeConstraints;

sub push_files {
    my ($self, $args) = @_;
    for my $file ( @{$args->{files}} ) {
        -r $file or return;
        my $dest = $args->{destination};
        unless ($self->execute( { command => "test -d $args->{destination}" })->code) {
            $dest = File::Spec->catfile($args->{destination}, basename($file));
        }
        open my $fh, '<', $file or croak $!;
        my $content = do { local $/; <$fh> };
        close $fh;
        my $result = $self->execute(
            {
                command => "cat - > $dest", 
                input => $content,
            }
        );
        $result->code and return;
    }
    return 1;
}

sub get_files {
    my ($self, $args) = @_;
    for my $file ( @{$args->{files}} ) {
        my $result = $self->execute(
            {
                command => "cat $file",
            }
        );
        $result->code and return;
        my $target = $args->{destination};
        if (-d $args->{destination}) {
            $target = File::Spec->catfile($args->{destination}, basename($file));
        }
        open my $fh, '>', $target or croak $!;
        print $fh $result->stdout;
        close $fh;
    }
    return 1;
}

sub execute {
    my ($self, $args) = @_;
    my ($out, $err, $code) = $self->_ssh->cmd($args->{command}, $args->{input});
    return Net::Amazon::HadoopEC2::SSH::Response->new(
        {
            stdout => $out,
            stderr => $err,
            code   => $code,
        }
    );
}

1;
__END__

=pod

=head1 NAME

Net::Amazon::HadoopEC2::SSH - Net::SSH::Perl wrapper for Net::Amazon::HadoopEC2

=head1 DESCRIPTION

This module is Net::SSH::Perl wrapper for Net::Amazon::HadoopEC2.

=head1 METHODS

=head2 new ($hashref)

Constructor. Arguments are:

=over 4

=item host (required)

Host to connect to.

=item key_file (required)

Private key file to use with ssh connection.

=item retry_max (optional)

Maximum count of retry to connect. The default is 5.

=back

=head2 execute ($hashref)

Runs command on the master instance via ssh. 
Returns L<Net::Amazon::HadoopEC2::SSH::Response> instance. 
This is only wrapper of L<Net::SSH::Perl>.
Arguments are:

=over 4

=item command (required)

The command line to pass.

=item stdin (optional)

String to pass to STDIN of the command.

=back

=head2 push_files ($hashref)

Pushes local files to hadoop-ec2 master instance via ssh. Returns true if succeeded. 
Arguments are:

=over 4

=item files (required)

files to push. Accepts string or arrayref of strings.

=item destination (required)

Destination of the files.

=back

=head2 get_files ($hashref)

Gets files on the hadoop-ec2 master instance.  Returns true if succeeded. 
Arguments are:

=over 4

=item files (required)

files to get. String and arrayref of strings is ok.

=item destination (required)

local path to place the files.

=back

=head1 AUTHOR

Nobuo Danjou L<nobuo.danjou@gmail.com>

=head1 SEE ALSO

L<Net::Amazon::HadoopEC2>

L<Net::SSH::Perl>

=cut