1package App::Netdisco::Transport::SSH; 2 3use Dancer qw/:syntax :script/; 4 5use App::Netdisco::Util::Device 'get_device'; 6use Module::Load (); 7use Net::OpenSSH; 8use Try::Tiny; 9 10use base 'Dancer::Object::Singleton'; 11 12=head1 NAME 13 14App::Netdisco::Transport::SSH 15 16=head1 DESCRIPTION 17 18Returns an object which has an active SSH connection which can be used 19for some actions such as arpnip. 20 21 my $cli = App::Netdisco::Transport::SSH->session_for( ... ); 22 23=cut 24 25__PACKAGE__->attributes(qw/ sessions /); 26 27sub init { 28 my ( $class, $self ) = @_; 29 $self->sessions( {} ); 30 return $self; 31} 32 33=head1 session_for( $ip ) 34 35Given an IP address, returns an object instance configured for and connected 36to that device. 37 38Returns C<undef> if the connection fails. 39 40=cut 41 42{ 43 package MySession; 44 use Moo; 45 46 has 'ssh' => ( is => 'rw' ); 47 has 'auth' => ( is => 'rw' ); 48 has 'host' => ( is => 'rw' ); 49 has 'platform' => ( is => 'rw' ); 50 51 sub arpnip { 52 my $self = shift; 53 $self->platform->arpnip(@_, $self->host, $self->ssh, $self->auth); 54 } 55} 56 57sub session_for { 58 my ($class, $ip) = @_; 59 60 my $device = get_device($ip) or return undef; 61 my $sessions = $class->instance->sessions or return undef; 62 63 return $sessions->{$device->ip} if exists $sessions->{$device->ip}; 64 debug sprintf 'cli session cache warm: [%s]', $device->ip; 65 66 my $auth = (setting('device_auth') || []); 67 if (1 != scalar @$auth) { 68 error sprintf " [%s] require only one matching auth stanza", $device->ip; 69 return undef; 70 } 71 $auth = $auth->[0]; 72 73 my @master_opts = qw(-o BatchMode=no); 74 push(@master_opts, @{$auth->{ssh_master_opts}}) 75 if $auth->{ssh_master_opts}; 76 77 $Net::OpenSSH::debug = $ENV{SSH_TRACE}; 78 my $ssh = Net::OpenSSH->new( 79 $device->ip, 80 user => $auth->{username}, 81 password => $auth->{password}, 82 timeout => 30, 83 async => 0, 84 default_stderr_file => '/dev/null', 85 master_opts => \@master_opts 86 ); 87 88 if ($ssh->error) { 89 error sprintf " [%s] ssh connection error [%s]", $device->ip, $ssh->error; 90 return undef; 91 } 92 elsif (! $ssh) { 93 error sprintf " [%s] Net::OpenSSH instantiation error", $device->ip; 94 return undef; 95 } 96 97 my $platform = "App::Netdisco::SSHCollector::Platform::" . $auth->{platform}; 98 my $happy = false; 99 try { 100 Module::Load::load $platform; 101 $happy = true; 102 } catch { error $_ }; 103 return unless $happy; 104 105 my $sess = MySession->new( 106 ssh => $ssh, 107 auth => $auth, 108 host => $device->ip, 109 platform => $platform->new(), 110 ); 111 112 return ($sessions->{$device->ip} = $sess); 113} 114 115true; 116