App-Goto2
view release on metacpan or search on metacpan
lib/App/Goto2.pm view on Meta::CPAN
use 5.10.0;
our $VERSION = '0.04';
use Data::Dumper;
use MooseX::App::Simple qw(Color ConfigHome);
option 'aws' => (
is => 'ro',
isa => 'Bool',
cmd_aliases => ['a'],
documentation => q/Retrieve AWS EC2 instances instead of using the config file/,
);
option 'list' => (
is => 'ro',
isa => 'Bool',
cmd_aliases => ['l'],
documentation => q/Just print list of known servers & exit/,
);
option 'verbose' => (
is => 'ro',
isa => 'Bool',
cmd_aliases => ['v'],
documentation => q/Verbose output/,
);
option 'iterate' => (
is => 'ro',
isa => 'Bool',
cmd_aliases => ['i'],
documentation => q/Connect to all matching hosts one after the other/,
);
option 'cmd' => (
is => 'ro',
isa => 'Str',
documentation => q/Run a command on the remote server(s)/,
);
has 'config_hosts' => (
is => 'rw',
isa => 'Maybe[HashRef]',
);
sub run {
my ($self) = @_;
# Use retrieved EC2 hosts instead of local config file entries if appropriate
$self->get_aws if $self->aws;
my $hostre = join '.*', @{ $self->extra_argv };
error("Must supply string to match host(s)") unless $hostre or $self->list;
my $hosts = $self->hosts;
my @matching_hosts = grep { m/$hostre/ } sort keys %$hosts;
error("No matching hosts found") unless @matching_hosts;
say "Hosts found: @matching_hosts" if $self->verbose;
# Just print a list of servers if appropriate
$self->print_list(\@matching_hosts) if $self->list;
# Either iterate over all matching hosts, or just use the first
for my $host ( @matching_hosts ) {
my $cmd = $self->generate_ssh_cmd($hosts->{$host});
say "Executing command: $cmd" if $self->verbose;
system( $cmd );
exit unless $self->iterate;
}
}
sub get_aws {
my ($self) = @_;
use Net::Amazon::EC2;
my $aws = $self->_config_data->{aws};
my $ec2 = Net::Amazon::EC2->new(
AWSAccessKeyId => $aws->{AWSAccessKeyId},
SecretAccessKey => $aws->{SecretAccessKey},
region => $aws->{region}
);
# Get all instances
my @instances;
my $reservations = $ec2->describe_instances;
foreach my $reservation (@$reservations) {
foreach my $instance ($reservation->instances_set) {
# Ensure only running instances
next unless $instance->instance_state->name eq 'running';
push @instances, $instance->name;
}
}
# Create new hosts hash
my %hosts = map { $_ => { hostname => $_ . $aws->{domain} } } @instances;
# Over-write the local config
$self->config_hosts(\%hosts);
}
sub print_list {
my ($self, $hostnames) = @_;
my $hosts = $self->hosts;
for my $host (@$hostnames) {
say "$host: " . $hosts->{$host}{hostname};
}
exit;
}
sub generate_ssh_cmd {
my ($self, $host) = @_;
my $cmd = 'ssh ';
$cmd .= ' -p ' . $host->{port} . ' ' if $host->{port};
$cmd .= ' -i ~/.ssh/' . $host->{ssh_key} . ' ' if $host->{ssh_key};
$cmd .= ' ' . $host->{user} . '@' if $host->{user};
$cmd .= $host->{hostname};
$cmd .= " '" . $self->{cmd} . "'" if $self->{cmd};
return $cmd;
}
sub error {
my ($msg) = @_;
say $msg;
exit 1;
}
sub hosts {
my ($self) = @_;
$self->config_hosts( $self->_config_data->{hosts} ) unless $self->config_hosts;
return $self->config_hosts
}
1;
__END__
=encoding utf-8
=head1 NAME
App::Goto2 - Easily SSH to many servers
=head1 SYNOPSIS
use App::Goto2;
=head1 DESCRIPTION
App::Goto2 is intended to take the strain out of having a large number of servers you
frequently have to access via SSH. It allows you to set up nicknames, use partial hostnames,
and auto-detect available AWS EC2 instances and work out which one(s) you actually want. It
also allows for iterating over a number of machines one after the other, and the specification
of an optional command to be run on the remote machine(s).
Written by somebody who frequently had to SSH to a vast number of machines in multiple
domains, with SSH running on numerous ports, with a variety of usernames, using a variety of
SSH keys; and got sick of trying to remember all the details when writing for loops to
remotely execute commands on them all.
You can get a lot of the same functionality (port/user/aliases/etc) just by updating your
ssh config file, but (a) it's harder to share a config file, (b) it wouldn't have the EC2
integration and (c) it doesn't lend itself to wildcards so well.
=head2 CONFIG
By default, the config file will be looked for in ~/.go2/config.yml
An example layout:
---
hosts:
foo:
hostname: fubar.example.com
user: myname
port: 2222
bar:
hostname: bar.example.com
user: myothername
aws:
AWSAccessKeyId: ASDFGHJKLZXCVBNMQWER
SecretAccessKey: kmj5jgGg7gGKJkqkohknKJHkzhk7hKJHLJKt6tw
region: eu-west-1
domain: .example.com
( run in 2.578 seconds using v1.01-cache-2.11-cpan-b16cb0d3907 )