#!/usr/bin/env perl package Spin::Executor ; use Moose::Role; use B::Deparse; has 'server' => ( is => 'ro', isa => 'ArrayRef', required => 0); has 'pass' => ( is => 'ro', isa => 'Str', required => 0); has 'user' => ( is => 'ro', isa => 'Str', required => 1); has '_ssh' => (is => 'ro', isa => 'Net::SSH::Expect', required => 0, writer => '_set_ssh' ); sub _connect { my $s = shift; my $server = shift; my $ssh; if ($s->pass) { $ssh = Net::SSH::Expect->new( host => $s->server, user => $s->user, password => $s->pass, ); $ssh->login(); } else { $ssh = Net::SSH::Expect->new( host => $server, user => $s->user ); $ssh->run_ssh(); } $s->_set_ssh($ssh); } sub run_server_tasks { my $s = shift; for my $server (@{$s->server}) { $s->_connect($server); $s->_ssh->exec("/bin/bash --noprofile --norc"); ### SMELLY ### The subroutines imported from our config file are associated with the ### Spin::Command::spin package but we want them to run in the Spin::Executor ### package to try to make the code associated with executing a task in ### a properly named object (Executor). I mostly just did this to see if ### it could be done. Credit to LanX on PM for help with this. my $deparse = B::Deparse->new; my $newtext = $deparse->coderef2text($s->func); $newtext =~ s/Spin::Command::spin/Spin::Executor/; my $code = eval qq( sub {$newtext} ); $deparse = B::Deparse->new; ### SMELLY ### To get the run() command below to work with the Spin::Task object, I ### did the same trick used with Spin::Command::spin module (see that package ### for more explanation). I'm not even exactly sure how this works. Credit ### to "mst" on #moose irc for helping me come up with this solution. local our $Object; $Object = $s; &$code; $s->_ssh->exec("exit"); $s->_ssh->close(); } } # here is where the run commands get executed sub run { our $Object; my $cmd = shift; my @ret = (); if ($Object->_ssh) { $Object->_ssh->send($cmd); while(defined (my $line = $Object->_ssh->read_line()) ) { $line =~ s/[\r\n]//gms; next if($line =~ m/^$/); push @ret, $line; } shift @ret; } else { push @ret, `$cmd`; chomp @ret; } if (scalar(@ret) >= 1) { print join("\n", @ret); print "\n"; } return join("\n", @ret); } 1; # Magic true value package Spin::Group ; use Moose; has 'name' => (is => 'ro', isa => 'Str', required => 1 ); has 'servers' => (is => 'ro', isa => 'ArrayRef', required => 1 ); package Spin::Task ; use Moose; use Net::SSH::Expect; with 'Spin::Executor'; has 'name' => (is => 'ro', isa => 'Str', required => 1 ); has 'descript' => (is => 'ro', isa => 'Str', required => 0 ); has 'func' => ( is => 'ro', isa => 'CodeRef', required => 1); sub execute_task { my $s = shift; $s->run_server_tasks; } package Spin::Command::spin ; use Moose; use Spin::Task; use Spin::Group; extends qw(Spin::Command MooseX::App::Cmd::Command); has '_curr_desc' => ( is => 'rw', isa => 'Str', default => ''); has '_tasks' => ( traits => [ 'Hash' ], is => 'ro', isa => 'HashRef[Spin::Task]', default => sub { {} }, handles => { set_task => 'set', get_task => 'get', task_exists => 'exists', task_keys => 'keys', no_tasks => 'is_empty'}, ); has '_user' => ( is => 'ro', isa => 'Str', required => 0, default => '', writer => '_set_user' ); has '_password' => ( is => 'ro', isa => 'Str', required => 0, default => '', writer => '_set_password' ); sub execute { my $s = shift; ### Initialize object with a config file $s->_init_command(); ### Set Spin::Task $s->get_task($ARGV[1])->execute_task; } ### SMELLY ### The following functions in this package work together to load a config file ### which consists of perl code (see below). The perl code in the config file ### calls the following functions. We load in the $Object variable so that ### these functions can modify the Spin::Command::spin object in the $Object var. sub run { } sub user { our $Object; $Object->_set_user(shift); } sub password { our $Object; $Object->_set_password(shift); } sub task { our $Object; my $name = shift; my $desc = $Object->_curr_desc; $Object->_curr_desc(''); my $next_arg = pop; my $func; if (ref($next_arg) eq 'CODE') { $func = $next_arg; } else { $func = pop; } my $group = 'ALL'; my @server = (); if (scalar(@_) >= 1) { if($_[0] eq 'group') { $group = $_[1]; if ($Object->group_exists($group)) { @server = @{$Object->get_group($group)->servers}; } else { } } else { @server = @_; } } my $task = Spin::Task->new( name => $name, func => $func, server => [ @server ], descript => $desc, user => $Object->_user, pass => $Object->_password); $Object->set_task($name => $task); } sub desc { our $Object; my $desc = shift; $Object->_curr_desc($desc); } { my $done = 0; sub _init_command { return if $done; ### Set up $Object so the function calls can modify our ### Spin::Command::spin object local our $Object; $Object = shift; ### this block is normally loaded from a file ### change user and host as appropriate to get it to work ### no password required for host if you have ssh keys eval { user "your_name"; #password "your_password"; desc "Show Unix version"; task "uname", "your_host", sub { run "export MYVAR='blah'"; run "uname -a"; run "echo 'Running: ' \$MYVAR"; run "id"; }; }; $done++; } } package Spin::Command ; use Moose; use Cwd; use File::Spec; use File::UserConfig; extends qw(MooseX::App::Cmd::Command); package Spin ; use Moose; extends qw(MooseX::App::Cmd); # set the default command if none supplied unshift @ARGV, 'spin'; Spin->run;