Beefy Boxes and Bandwidth Generously Provided by pair Networks
"be consistent"
 
PerlMonks  

comment on

( [id://3333]=superdoc: print w/replies, xml ) Need Help??

For fun, to help improve my Perl skills, and to help understand the Rex module, I decided to clone Rex with Moose using test-driven development and MooseX::App::Cmd::Command.

Rex is a module for running remote commands on a server. It loads a simple DSL from a config file. The config file is simple Perl code. I was able to get it implemented and it works, but I'm sure there is probably a better approach than what I came up with for loading the config file. I'd be interested in hearing some better approaches to help advance my skills. Here is an SSCCE. To get it to work, just put in your user and host on lines 228 and 232. Name the file spin.pl and run it with ./spin.pl uname.

The particularly smelly bits are marked with triple hashes (###) and contain explanations for what's going on. Thanks!

#!/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, w +riter => '_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 w +ith the ### Spin::Command::spin package but we want them to run in the Spi +n::Executor ### package to try to make the code associated with executing a ta +sk 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 obj +ect, I ### did the same trick used with Spin::Command::spin module (see t +hat package ### for more explanation). I'm not even exactly sure how this work +s. Credit ### to "mst" on #moose irc for helping me come up with this soluti +on. 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]', defa +ult => sub { {} }, handles => { set_task => 'set', get_task => 'get', task_exists => 'e +xists', 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 co +nfig file ### which consists of perl code (see below). The perl code in the conf +ig 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;

$PM = "Perl Monk's";
$MCF = "Most Clueless Friar Abbot Bishop Pontiff Deacon Curate Priest Vicar";
$nysus = $PM . ' ' . $MCF;
Click here if you love Perl Monks


In reply to Destinkifying some weird Moose code by nysus

Title:
Use:  <p> text here (a paragraph) </p>
and:  <code> code here </code>
to format your post; it's "PerlMonks-approved HTML":



  • Are you posting in the right place? Check out Where do I post X? to know for sure.
  • Posts may use any of the Perl Monks Approved HTML tags. Currently these include the following:
    <code> <a> <b> <big> <blockquote> <br /> <dd> <dl> <dt> <em> <font> <h1> <h2> <h3> <h4> <h5> <h6> <hr /> <i> <li> <nbsp> <ol> <p> <small> <strike> <strong> <sub> <sup> <table> <td> <th> <tr> <tt> <u> <ul>
  • Snippets of code should be wrapped in <code> tags not <pre> tags. In fact, <pre> tags should generally be avoided. If they must be used, extreme care should be taken to ensure that their contents do not have long lines (<70 chars), in order to prevent horizontal scrolling (and possible janitor intervention).
  • Want more info? How to link or How to display code and escape characters are good places to start.
Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others examining the Monastery: (3)
As of 2024-04-20 01:29 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found