File Coverage

blib/lib/Metabrik/Client/Ssh.pm
Criterion Covered Total %
statement 9 166 5.4
branch 0 98 0.0
condition 0 31 0.0
subroutine 3 16 18.7
pod 2 13 15.3
total 14 324 4.3


line stmt bran cond sub pod time code
1             #
2             # $Id$
3             #
4             # client::ssh Brik
5             #
6             package Metabrik::Client::Ssh;
7 2     2   732 use strict;
  2         4  
  2         56  
8 2     2   10 use warnings;
  2         5  
  2         52  
9              
10 2     2   10 use base qw(Metabrik::System::Package);
  2         4  
  2         2344  
11              
12             sub brik_properties {
13             return {
14 0     0 1   revision => '$Revision$',
15             tags => [ qw(unstable) ],
16             author => 'GomoR ',
17             license => 'http://opensource.org/licenses/BSD-3-Clause',
18             attributes => {
19             hostname => [ qw(hostname) ],
20             port => [ qw(integer) ],
21             username => [ qw(username) ],
22             password => [ qw(password) ],
23             publickey => [ qw(file) ],
24             privatekey => [ qw(file) ],
25             ssh2 => [ qw(Net::SSH2) ],
26             use_publickey => [ qw(0|1) ],
27             _channel => [ qw(INTERNAL) ],
28             },
29             attributes_default => {
30             username => 'root',
31             port => 22,
32             use_publickey => 1,
33             },
34             commands => {
35             install => [ ], # Inherited
36             connect => [ qw(hostname|OPTIONAL port|OPTIONAL username|OPTIONAL) ],
37             execute => [ qw(command) ],
38             read => [ qw(channel|OPTIONAL) ],
39             read_line => [ qw(channel|OPTIONAL) ],
40             read_line_all => [ qw(channel|OPTIONAL) ],
41             load => [ qw(file) ],
42             disconnect => [ ],
43             create_channel => [ ],
44             close_channel => [ ],
45             execute_in_background => [ qw(command stdout_file|OPTIONAL stderr_file|OPTIONAL stdin_file|OPTIONAL) ],
46             capture => [ qw(command) ],
47             },
48             require_modules => {
49             'IO::Scalar' => [ ],
50             'Net::SSH2' => [ ],
51             'Metabrik::String::Password' => [ ],
52             },
53             need_packages => {
54             ubuntu => [ qw(libssh2-1-dev) ],
55             debian => [ qw(libssh2-1-dev) ],
56             kali => [ qw(libssh2-1-dev) ],
57             },
58             };
59             }
60              
61             #
62             # With help from http://www.perlmonks.org/?node_id=569657
63             #
64              
65             sub connect {
66 0     0 0   my $self = shift;
67 0           my ($hostname, $port, $username, $password) = @_;
68              
69 0 0         if (defined($self->ssh2)) {
70 0           return $self->log->verbose("connect: already connected");
71             }
72              
73 0   0       $hostname ||= $self->hostname;
74 0   0       $port ||= $self->port;
75 0   0       $username ||= $self->username;
76 0   0       $password ||= $self->password;
77 0 0         $self->brik_help_run_undef_arg('connect', $hostname) or return;
78 0 0         $self->brik_help_run_undef_arg('connect', $port) or return;
79 0 0         $self->brik_help_run_undef_arg('connect', $username) or return;
80              
81 0           my $publickey = $self->publickey;
82 0           my $privatekey = $self->privatekey;
83 0 0 0       if ($self->use_publickey && ! $publickey) {
84 0           return $self->log->error($self->brik_help_set('publickey'));
85             }
86 0 0 0       if ($self->use_publickey && ! $privatekey) {
87 0           return $self->log->error($self->brik_help_set('privatekey'));
88             }
89              
90 0           my $ssh2 = Net::SSH2->new;
91 0 0         if (! defined($ssh2)) {
92 0           return $self->log->error("connect: cannot create Net::SSH2 object");
93             }
94              
95 0           my $ret = $ssh2->connect($hostname, $port);
96 0 0         if (! $ret) {
97 0           return $self->log->error("connect: can't connect via SSH2: $!");
98             }
99              
100 0 0         if ($self->use_publickey) {
101 0           $ret = $ssh2->auth(
102             username => $username,
103             publickey => $publickey,
104             privatekey => $privatekey,
105             );
106 0 0         if (! $ret) {
107 0           return $self->log->error("connect: authentication failed with publickey: $!");
108             }
109             }
110             else {
111             # Prompt for password if not given
112 0 0         if (! defined($password)) {
113 0 0         my $sp = Metabrik::String::Password->new_from_brik_init($self) or return;
114 0           $password = $sp->prompt;
115             }
116              
117 0           $ret = $ssh2->auth_password($username, $password);
118 0 0         if (! $ret) {
119 0           return $self->log->error("connect: authentication failed with password: $!");
120             }
121             }
122              
123 0           $self->log->verbose("connect: ssh2 connected to [$hostname]:$port");
124              
125 0           return $self->ssh2($ssh2);
126             }
127              
128             sub disconnect {
129 0     0 0   my $self = shift;
130              
131 0           my $ssh2 = $self->ssh2;
132 0 0         if (! defined($ssh2)) {
133 0           return $self->log->verbose("disconnect: not connected");
134             }
135              
136 0           my $r = $ssh2->disconnect;
137              
138 0           $self->ssh2(undef);
139 0           $self->close_channel;
140              
141 0           return $r;
142             }
143              
144             sub execute {
145 0     0 0   my $self = shift;
146 0           my ($cmd) = @_;
147              
148 0           my $ssh2 = $self->ssh2;
149 0 0         $self->brik_help_run_undef_arg('connect', $ssh2) or return;
150 0 0         $self->brik_help_run_undef_arg('execute', $cmd) or return;
151              
152 0           $self->log->debug("execute: cmd [$cmd]");
153              
154 0 0         my $channel = $self->create_channel or return;
155              
156 0 0         $channel->process('exec', $cmd)
157             or return $self->log->error("execute: can't execute command [$cmd]: $!");
158              
159 0           return $self->_channel($channel);
160             }
161              
162             sub create_channel {
163 0     0 0   my $self = shift;
164              
165 0           my $ssh2 = $self->ssh2;
166 0 0         $self->brik_help_run_undef_arg('connect', $ssh2) or return;
167              
168 0           my $channel = $ssh2->channel;
169 0 0         if (! defined($channel)) {
170 0           return $self->log->error("create_channel: creation failed: [$!]");
171             }
172              
173 0           return $self->_channel($channel);
174             }
175              
176             sub close_channel {
177 0     0 0   my $self = shift;
178              
179 0           my $channel = $self->_channel;
180 0 0         if (defined($channel)) {
181 0           $channel->close;
182 0           $self->_channel(undef);
183             }
184              
185 0           return 1;
186             }
187              
188             sub execute_in_background {
189 0     0 0   my $self = shift;
190 0           my ($cmd, $stdout_file, $stderr_file, $stdin_file) = @_;
191              
192 0           my $ssh2 = $self->ssh2;
193 0 0         $self->brik_help_run_undef_arg('connect', $ssh2) or return;
194 0 0         $self->brik_help_run_undef_arg('execute_in_background', $cmd) or return;
195              
196 0   0       $stdout_file ||= '/dev/null';
197 0   0       $stderr_file ||= '/dev/null';
198              
199 0 0         my $channel = $self->create_channel or return;
200              
201 0           $cmd .= " > $stdout_file 2> $stderr_file";
202 0 0         if (defined($stdin_file)) {
203 0           $cmd .= " < $stdin_file";
204             }
205 0           $cmd .= " &";
206              
207 0           $self->log->debug("execute_in_background: cmd [$cmd]");
208              
209 0 0         $channel->process('exec', $cmd)
210             or return $self->log->error("execute_in_background: process failed: [$!]");
211 0 0         $channel->send_eof
212             or return $self->log->error("execute_in_background: send_eof failed: [$!]");
213              
214 0 0         $self->close_channel or return;
215              
216 0           return 1;
217             }
218              
219             sub read_line {
220 0     0 0   my $self = shift;
221 0           my ($channel) = @_;
222              
223 0   0       $channel ||= $self->_channel;
224 0 0         $self->brik_help_run_undef_arg('create_channel', $channel) or return;
225              
226 0           my $read = '';
227 0           my $count = 1;
228 0           while (1) {
229 0           my $char = '';
230 0           my $rc = $channel->read($char, $count);
231 0 0         if (! defined($rc)) {
232 0           return $self->log->error("read_line: read failed: [$!]");
233             }
234 0 0         if ($rc > 0) {
    0          
235             #print "read[$char]\n";
236             #print "returned[$c]\n";
237 0           $read .= $char;
238              
239 0 0         last if $char eq "\n";
240             }
241             elsif ($rc < 0) {
242 0           return $self->log->error("read_line: error [$rc]");
243             }
244             else {
245 0           last;
246             }
247             }
248              
249 0           return $read;
250             }
251              
252             sub read_line_all {
253 0     0 0   my $self = shift;
254 0           my ($channel) = @_;
255              
256 0   0       $channel ||= $self->_channel;
257 0 0         $self->brik_help_run_undef_arg('create_channel', $channel) or return;
258              
259 0 0         my $read = $self->read($channel) or return;
260              
261 0           my @lines = split(/\n/, $read);
262              
263 0           $self->close_channel;
264              
265 0           return \@lines;
266             }
267              
268             sub read {
269 0     0 0   my $self = shift;
270 0           my ($channel) = @_;
271              
272 0   0       $channel ||= $self->_channel;
273 0 0         $self->brik_help_run_undef_arg('create_channel', $channel) or return;
274              
275 0           $self->log->verbose("read: channel[$channel]");
276              
277 0           my $read = '';
278 0           my $count = 1024;
279 0           while (1) {
280 0           my $buf = '';
281 0           my $rc = $channel->read($buf, $count);
282 0 0         if (! defined($rc)) {
283 0           return $self->log->error("read: read failed: [$!]");
284             }
285 0 0         if ($rc > 0) {
    0          
286             #print "read[$buf]\n";
287             #print "returned[$c]\n";
288 0           $read .= $buf;
289              
290 0 0         last if $rc < $count;
291             }
292             elsif ($rc < 0) {
293 0           return $self->log->error("read: error [$rc]");
294             }
295             else {
296 0           last;
297             }
298             }
299              
300 0           $self->close_channel;
301              
302 0           return $read;
303             }
304              
305             sub load {
306 0     0 0   my $self = shift;
307 0           my ($file) = @_;
308              
309 0           my $ssh2 = $self->ssh2;
310 0 0         $self->brik_help_run_undef_arg('connect', $ssh2) or return;
311 0 0         $self->brik_help_run_undef_arg('load', $file) or return;
312              
313 0           my $io = IO::Scalar->new;
314              
315 0 0         $ssh2->scp_get($file, $io)
316             or return $self->log->error("load: scp_get: $file");
317              
318 0           $io->seek(0, 0);
319              
320 0           my $buf = '';
321 0           while (<$io>) {
322 0           $buf .= $_;
323             }
324              
325 0           return $buf;
326             }
327              
328             sub capture {
329 0     0 0   my $self = shift;
330 0           my ($cmd) = @_;
331              
332 0           my $ssh2 = $self->ssh2;
333 0 0         $self->brik_help_run_undef_arg('connect', $ssh2) or return;
334 0 0         $self->brik_help_run_undef_arg('capture', $cmd) or return;
335              
336 0 0         my $channel = $self->create_channel or return;
337              
338 0           $self->log->debug("capture: cmd [$cmd]");
339              
340 0 0         $channel->process('exec', $cmd)
341             or return $self->log->error("capture: process failed: [$!]");
342              
343 0 0         my $lines = $self->read_line_all or return;
344 0           $self->close_channel;
345              
346 0           return $lines;
347             }
348              
349             sub brik_fini {
350 0     0 1   my $self = shift;
351              
352 0           my $ssh2 = $self->ssh2;
353 0 0         if (defined($ssh2)) {
354 0           $ssh2->disconnect;
355 0           $self->ssh2(undef);
356 0           $self->_channel(undef);
357             }
358              
359 0           return 1;
360             }
361              
362             1;
363              
364             __END__