Skip to content

Instantly share code, notes, and snippets.

@thealexbaron
Last active June 15, 2017 16:38
Show Gist options
  • Save thealexbaron/45fb567e2c1349983865adbfe6945b57 to your computer and use it in GitHub Desktop.
Save thealexbaron/45fb567e2c1349983865adbfe6945b57 to your computer and use it in GitHub Desktop.
SSH Config Helper
#!/usr/bin/perl
use strict;
use warnings;
use Scalar::Util qw(looks_like_number);
use Data::Dumper;
# update https://gist.github.com/thealexbaron/45fb567e2c1349983865adbfe6945b57 when we update this file
# TODO: List possible tags
# TODO: add support for tag tab completion
=pod
Add tags and notes to your config blocks like this:
Host euweb101
HostName 176.34.140.11
IdentityFile ~/keys/ireland.pem
User ubuntu
#tags rsis-db-schema web ireland
#note this is neato note
Then you can list and filter with tags like this:
ssh-helper.pl web virginia
=cut
my $config_path = $ENV{'HOME'} . '/.ssh/config';
my $ssh_config = read_config($config_path);
render_options($ssh_config, \@ARGV);
sub read_config {
my ($config_path) = @_;
open(my $fh, '<:encoding(UTF-8)', $config_path) or die "Could not open file '$config_path' $!";
my @ssh_config;
while (my $row = <$fh>) {
chomp $row;
my ($key, $value) = split(' ', $row, 2);
if (!$key) { next; }
if (substr($key, 0) eq '#' && $key ne '#tags' && $key ne '#note') { next; }
if ($key eq 'Host') {
my %hash = ($key => $value);
push(@ssh_config, \%hash);
}
elsif ($key eq '#tags') {
$ssh_config[scalar @ssh_config - 1]{$key} = [ split(' ', $value) ];
}
elsif ($key eq '#note') {
$ssh_config[scalar @ssh_config - 1]{$key} = $value;
}
else {
$ssh_config[scalar @ssh_config - 1]{$key} = $value;
}
}
return [ map{ $_->{'#tags'} = [] if !defined $_->{'#tags'}; $_; } @ssh_config ];
}
sub generate_table_data {
my ($ssh_config) = @_;
my @table_rows = ([' # ', 'connection aliases', 'tags', 'notes']);
my $idx = 0;
foreach my $block (@$ssh_config) {
$idx += 1;
push(@table_rows, ["[$idx]", $block->{'Host'}, join(', ', @{ $block->{'#tags'} }), $block->{'#note'}]);
}
return \@table_rows;
}
sub render_table {
my ($table_data) = @_;
# first determine the widths of the columns
my @char_widths = ();
foreach my $row (@$table_data) {
my $idx = 0;
foreach my $field (@$row) {
$field = $field || '';
my $field_length = length($field);
if (!$char_widths[$idx] || $char_widths[$idx] < $field_length) {
$char_widths[$idx] = $field_length;
}
$idx += 1;
}
}
my $rowIdx = 0;
foreach my $row (@$table_data) {
my $fieldIdx = 0;
foreach my $field (@$row) {
$field = $field || '';
my $field_length = length($field);
my $add_spaces = $char_widths[$fieldIdx] - $field_length;
print $field . (' ' x $add_spaces) . ' ';
$fieldIdx += 1;
}
if ($rowIdx == 0) {
print "\n";
foreach my $width (@char_widths) {
print '=' x $width . ' ';
}
}
$rowIdx += 1;
print "\n";
}
}
sub render_options {
my ($ssh_config, $filters) = @_;
my %unique_filtered_ssh_config = ();
foreach my $block (@$ssh_config) {
my $matches_tags = 1;
if (scalar $filters != 0) {
foreach my $tag (@$filters) {
if ((scalar grep { $_ eq $tag } @{ $block->{'#tags'} }) < 1) {
$matches_tags = 0;
}
}
}
if ($matches_tags) {
$unique_filtered_ssh_config{$block->{'Host'}} = $block;
}
}
foreach my $block (@$ssh_config) {
my $note = $block->{'#note'} || $block->{'#notes'};
if ($note) {
my $matches_tags = 0;
foreach my $word (@$filters) {
if ((scalar grep(/\Q$word/, $note)) > 0) {
$matches_tags = 1;
}
}
if ($matches_tags) {
$unique_filtered_ssh_config{$block->{'Host'}} = $block;
}
}
}
my @filtered_ssh_config = ();
foreach my $block (values %unique_filtered_ssh_config) {
push(@filtered_ssh_config, $block);
}
if (!scalar @filtered_ssh_config) {
print "No matches found with those tags or notes.\n";
exit();
}
my @host_sorted_filtered_ssh_config = sort { $a->{'Host'} cmp $b->{'Host'} } @filtered_ssh_config;
my $table_data = generate_table_data(\@host_sorted_filtered_ssh_config);
render_table($table_data);
if (scalar @ARGV == 0) { print "\nFilter results by entering tags as arguments.\n"; }
my $selected_number;
if (scalar @host_sorted_filtered_ssh_config == 1) {
print "\nOnly one option available, automatically using that one.\n";
$selected_number = 1;
}
else {
print "\nHere's a list of possibilities. Which would you like to connect to?\n";
$selected_number = <STDIN>;
chomp $selected_number;
}
if (!looks_like_number($selected_number)) {
print qq|\neh... "$selected_number" isn't a number. Maybe you wanna filter by tags?\n\n|;
exit(render_options($ssh_config, [$selected_number]));
}
elsif (!$host_sorted_filtered_ssh_config[$selected_number - 1]) {
print qq|eh... "$selected_number" isn't a number in this list.\n|;
}
else {
my $host = $host_sorted_filtered_ssh_config[$selected_number - 1]{'Host'};
print "cool, connecting to $host...\n";
exec("ssh $host") or print STDERR "couldn't exec ssh $host: $!";
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment