cbeckley has asked for the wisdom of the Perl Monks concerning the following question:
I've written a module, nascent, that makes it a little easier to execute a list of commands on a group of servers. Basically I create an array of servers, which I refer to as contexts in the code, each of which has an array of commands and all the commands get executed in all the contexts and all the output is gathered and returned for processing.
I've included below an excerpt from an invoking script to give an example of how the data structure is built, and below that, the module itself.
First, a couple questions.
Has this been done before? I mean, it has to have, right? My searches haven't turned up much however. nysus seems to be working on something similar, Re: How best to validate the keys of hashref arguments?, and melezhik recently wrote about Sparrow - your own script manager, which seems to be in the neighborhood, but not quite what I'm looking for. If anybody knows of a module that already does this, I'd really appreciate it.
How do I organize my modules? Assuming I do continue this development, I plan on rewriting contexts and commands as objects, which would by my first foray into objects with Perl, and I'm not sure how they should exist with respect to each other. In general, what are the implications between Foo::Bar and Foo::Baz as opposed to Foo::Bar and Foo::Bar::Baz? And in this case, Ops::Context and Ops::Command, where Ops::Context would use Ops::Command and a calling script would have to use both, or Ops::Command and Ops::Command::Context where Contexts are extensions of Commands and the invoking script only uses one or the other, depending on the complexity of the task?
Finally, any feedback on the code below would be appreciatel, any smoking guns that would explain this hole in my foot, or anything I may have done clumsily for which Perl has a better construct.
For instance, the line where I access the array of commands:
Is there a better way to say that?for my $cmd (@{$ctxt->{commands}}) { ...
An excerpt from a script that invokes the module:
use Ops::Exec qw(ops_do_ssh_shell ops_new_context ops_new_cmd); use Ops::Config qw(ops_get_servers); my %servers = map { $_ => new_server($_) } ops_get_servers('foo'); my @contexts = map {ops_new_context({ name => $servers{$_}{name}, user => 'foo', host => $servers{$_}{host}, key => $ENV{HOME} . '/.ssh/id_rsa.foo.prod', commands => [ ops_new_cmd({ name => 'capacity', command => 'df -k /home'}), ops_new_cmd({ name => 'process_type1', command => 'ps -ef | grep process_ty +pe1 | grep -v grep | wc -l'}), ops_new_cmd({ name => 'process_type2', command => 'ps -ef | grep process_ty +pe2 | grep -v grep | wc -l'}) ] })} sort keys %servers; for my $ctxt (@contexts) { if ($local_state->{status} eq 'success') { my $ctxt_status = ops_do_ssh_shell($ctxt); for my $cmd_status (@{$ctxt_status->{commands}}) { if ( $ctxt_status->{success} ) { $servers{$ctxt_status->{name}}{$cmd_status->{name}} = $cmd +_status->{output}; } else { $local_state->{status} = 'failure'; $local_state->{stack_trace} = $cmd_status; } } } } if ($local_state->{status} eq 'success') { for my $server (keys %servers) { $servers{$server}{capacity} = parse_capacity($servers{$server}{c +apacity}); # further parsing and validation } } # statuses are evaluated to generate warnings and alerts # texts and emails are sent yadda yadda
And the module:
package Ops::Exec; use strict; use warnings; use Exporter qw(import); use Data::Dumper; use Net::OpenSSH; use Hash::Util qw(lock_ref_keys); use Clone qw(clone); our @EXPORT_OK = qw(ops_do_ssh_shell ops_do_ssh_qx ops_new_context ops +_new_cmd ops_new_qx_cmd); use constant SUCCESS => 1; use constant FAILURE => 0; sub ops_new_cmd { my ($init_hash) = @_; my $new_cmd = {}; lock_ref_keys($new_cmd, qw(name command success output std_err cmd_ +ret_code cmd_ret_msg)); $new_cmd = clone($init_hash); $new_cmd->{success} = SUCCESS; return $new_cmd } sub ops_new_context { my ($init_hash) = @_; my $new_cmd = {}; lock_ref_keys($new_cmd, qw(name user host key commands success ssh_ +retcode ssh_retmsg outputs)); $new_cmd = clone($init_hash); $new_cmd->{success} = SUCCESS; return $new_cmd } sub ops_do_ssh_shell { my ($ctxt) = @_; my $ssh; defined $ctxt->{key} or $ctxt->{key} = $ENV{HOME} . '/.ssh/id_rsa'; if ( $ctxt->{success} ) { $ssh = Net::OpenSSH->new($ctxt->{host}, user => $ctxt->{user}, k +ey_path => $ctxt->{key}); if ( $ssh->error ) { $ctxt->{success} = FAILURE; ($ctxt->{ssh_retcode}, $ctxt->{ssh_retmsg}) = (0 + $ssh->erro +r, '' . $ssh->error); } } for my $cmd (@{$ctxt->{commands}}) { if ( $ctxt->{success} ) { ($cmd->{output}, $cmd->{std_err}) = $ssh->capture2($cmd->{com +mand}); $cmd->{cmd_ret_code} = 0 + $ssh->error; if ( $cmd->{cmd_ret_code} ) { $ctxt->{success} = FAILURE; $ctxt->{cmd_ret_msg} = '' . $ssh->error; } chomp $cmd->{output}; chomp $cmd->{std_err}; } } return $ctxt; } sub ops_new_qx_cmd { my ($init_hash) = @_; my $new_cmd = {}; lock_ref_keys($new_cmd, qw(name user host command success output st +d_err cmd_ret_code cmd_ret_msg ssh_ret_code ssh_ret_msg ssh_cmd_qx)); %$new_cmd = %$init_hash; $new_cmd->{success} = SUCCESS; return $new_cmd } sub ops_do_ssh_qx { my ($cmd) = @_; $cmd->{ssh_cmd_qx} = 'ssh ' . $cmd->{user} . '\@' . $cmd->{host} . + ' \'' . $cmd->{command} . '\'' . ' 2>/dev/null'; $cmd->{output} = qx($cmd->{ssh_cmd_qx}); if ( defined $cmd->{output} ) { $cmd->{cmd_ret_code} = $?; chomp $cmd->{output}; } else { ($cmd->{ssh_ret_code}, $cmd->{ssh_ret_msg}) = (0 + $!, '' . $!); } return $cmd; } "Red Guitar, Three Chords ...";
Thank you for taking the time to look at this.
Thanks,
cbeckley
|
---|