The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/usr/bin/env perl

=head1 NAME

clad -- A simple parallel ssh client.

=head1 SYNOPSIS

    clad [log options] [-n] [-a] [-l login] <cluster> <command>

=head1 DESCRIPTION

clad provides the ability to run the same command on several
hosts at once.  The output is displayed unbuffered
as the various hosts run the command.  The list of hosts
is determined by reading a configuration file which associates
names of clusters with lists of hosts.  The configuration file
may also contain command aliases and environment settings.

The command(s) will be executed under '/bin/sh -e' regardless
of the login shell for the remote user.

=head1 FEATURES

=over

=item * Cluster-specific and global environment settings

=item * Event-driven unbuffered output (using L<Mojo::Reactor>)

=item * Host chaining support

=item * Perlish configuration for dynamic cluster settings

=item * Filtering of banners

=item * Command aliases

=item * Command macros

=back

=head1 OPTIONS

=over

=item B<-n>

Dry run, just show the command that would be executed
and each host.

=item B<-a>

Don't colorize the host names in the output.

=item B<-l>

Specify a login name for all ssh connections (proxies, too).

=back

=head1 EXAMPLES

 clad -n mailhosts uname -a
 clad webservers df -kh
 clad --trace root webservers ping -i 1 localhost

=head1 CONFIGURATION

The configuration file is a L<Clustericious::Config>
file (YAML or JSON L<Mojo::Template>'s ) and has three
sections :

env : environment settings for all commands
run on all hosts.

clusters : specifies the clusters.  A cluster
is a label and a list of hosts. Each host
can be either a hostname or an array of hostnames.
If it is an array of hostnames it will be treated
as a sequence of hosts through which to proxy, e.g.

    ssh host1 ssh host2 ssh host3...

If the first proxy host is the same for all the hosts
in a cluster, then it can be given separately (see
the example below).

aliases : command aliases.  These may be either
a single command or a list of commands.  Lists
of commands will all be run in the same ssh
session.  Environment variables in the env
section will be merged with the global
environment settings.

macros : command macros.  These are a sequence
of commands or aliases.  They will be executed
in sequence, starting a new connection with each
one.  Each command/alias may also optionally have
a login associated with it.

banners : banners which should be suppressed when
displaying stderr.

=head1 SAMPLE CONFIGURATION

A sample configuration file ~/etc/Clad.conf :

    ---
    env :
        PATH : /usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/bin
        PERL5LIB : /home/joe/perl5
    clusters :
        mailers :
            - mail1
            - mail2
            - mail3
        webservers :
            env  :
                USER : bob
            hosts :
                 - web1
                 - web2
        testweb :
            proxy : test.example.com
            hosts :
                - testweb1
                - testweb2
    aliases :
        check_ports : lsof -i -n
        startweb : nginx start
        stopweb : nginx start
        restartweb :
            - apachectl stop
            - apachectl start
        config_pull :
            - cd /usr/local/etc
            - git pull
        build :
            - cd project
            - perl Build.PL
            - ./Build
            - ./Build test
            - ./Build install
    macros :
         release :
            - login : joe
              command : build
            - login : dev
              command : restartweb
     banners :
            - text : |


                **********************************************
                This is a welcome banner that you see whenever
                you ssh to our server!
                **********************************************
                \n
                \n

Another example, using some L<Mojo::Template> features :

    ---
    env :
        PATH : /usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/bin
        PERL5LIB : /home/joe/perl5
    clusters :
        mailers    : [ <%= join ',', map "mail".$_, 0..10 %> ],
        webservers : [ <%= `/usr/local/bin/list_my_web_servers` %> ]

=head1 NOTES

Manipulating .ssh/config can also be used for proxying.
For instance :

    Host webdev1
    ProxyCommand ssh firewall.example.com nc %h %p

    Host webdev2
    ProxyCommand ssh firewall.example.com nc %h %p

=head1 SEE ALSO

L<Clustericious::Admin>,
L<Clustericious::Config>,
L<Log::Log4perl::CommandLine>,
L<SSH::Batch>,
L<Net::OpenSSH::Parallel>,
L<Net::SSH::Mechanize>,
L<helm>

=cut

use feature 'say';
use File::Basename 'dirname';
use File::Spec;
use Log::Log4perl::CommandLine (':all', ':loginit' => <<"EOT");
           log4perl.rootLogger = INFO, Screen
           log4perl.appender.Screen = Log::Log4perl::Appender::ScreenColoredLevels
           log4perl.appender.Screen.layout = Log::Log4perl::Layout::PatternLayout
           log4perl.appender.Screen.layout.ConversionPattern = @{[ $ENV{HARNESS_ACTIVE} ? '#' : '' ]} [%-5p] %d %F{1} (%L) %m %n
EOT

use lib join '/', File::Spec->splitdir(dirname(__FILE__)), 'lib';
use lib join '/', File::Spec->splitdir(dirname(__FILE__)), '..', 'lib';
use lib join '/', File::Spec->splitdir(dirname(__FILE__)), '..', 'lib', 'perl';

use Clustericious::Admin;
use Getopt::Std qw/getopts/;

unless (@ARGV >=2 ) {
    say "Not enough arguments : @ARGV";
    say "usage $0 <cluster> <command>";
    say "Available clusters : ".join ' ', Clustericious::Admin->clusters(default => []);
    say "Available aliases  : ".join ' ', Clustericious::Admin->aliases(default => []);
    exit;
}

my %opts;
getopts('nal:',\%opts);
Clustericious::Admin->run(\%opts,@ARGV);

1;