diff --git a/Anvil/Tools.pm b/Anvil/Tools.pm index 1011c1db..2d8bdf6f 100644 --- a/Anvil/Tools.pm +++ b/Anvil/Tools.pm @@ -674,8 +674,7 @@ sub _get_hash_reference my $parameter = shift; my $anvil = $self; - #print "$THIS_FILE ".__LINE__."; hash: [".$an."], key: [$parameter->{key}]\n"; - die "$THIS_FILE ".__LINE__."; The hash key string: [$parameter->{key}] doesn't seem to be valid. It should be a string in the format 'foo::bar::baz'.\n" if $parameter->{key} !~ /::/; + die "$THIS_FILE ".__LINE__."; The hash key string: [".$parameter->{key}."] doesn't seem to be valid. It should be a string in the format 'foo::bar::baz'.\n" if $parameter->{key} !~ /::/; # Split up the keys. my $key = $parameter->{key} ? $parameter->{key} : ""; diff --git a/Anvil/Tools/Convert.pm b/Anvil/Tools/Convert.pm index 133511d5..f795fb41 100644 --- a/Anvil/Tools/Convert.pm +++ b/Anvil/Tools/Convert.pm @@ -1011,7 +1011,7 @@ sub time } # Remote commas and verify we're left with a number. - my $time =~ s/,//g; + $time =~ s/,//g; if ($time =~ /^\d+\.\d+$/) { # Round the time @@ -1073,41 +1073,46 @@ sub time { $say_time =~ s/ sec.$/$suffix_seconds/; $say_time = sprintf("%01d", $remaining_minutes).$suffix_minutes." $say_time"; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { say_time => $say_time }}); } elsif (($hours > 0) or ($days > 0) or ($weeks > 0)) { $say_time = "0".$suffix_minutes." ".$say_time; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { say_time => $say_time }}); } if ($remaining_hours > 0) { $say_time = sprintf("%01d", $remaining_hours)."$suffix_hours $say_time"; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { say_time => $say_time }}); } elsif (($days > 0) or ($weeks > 0)) { $say_time = "0".$suffix_hours." ".$say_time; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { say_time => $say_time }}); } if ($days > 0) { $say_time = sprintf("%01d", $remaining_days).$suffix_days." ".$say_time; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { say_time => $say_time }}); } elsif ($weeks > 0) { $say_time = "0".$suffix_days." ".$say_time; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { say_time => $say_time }}); } if ($weeks > 0) { - $weeks = $an->Readable->comma($weeks); + $weeks = $anvil->Convert->add_commas({number => $weeks}); $say_time = $weeks.$suffix_weeks." ".$say_time; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + weeks => $weeks, + say_time => $say_time, + }}); } # Return an already-translated string - $say_time = $an->String->_process_string({ - string => $say_time, - language => $an->default_language, - hash => $an->data, - variables => {}, - }); - + $say_time = $anvil->Words->string({debug => $debug, string => $say_time}); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { say_time => $say_time }}); return($say_time); } diff --git a/Anvil/Tools/Remote.pm b/Anvil/Tools/Remote.pm index b6b550a0..c87e2ee6 100644 --- a/Anvil/Tools/Remote.pm +++ b/Anvil/Tools/Remote.pm @@ -7,6 +7,9 @@ use strict; use warnings; use Data::Dumper; use Scalar::Util qw(weaken isweak); +use Net::SSH2; ### TODO: Phase out. +use Net::OpenSSH; +use Capture::Tiny ':all'; our $VERSION = "3.0.0"; my $THIS_FILE = "Remote.pm"; @@ -321,6 +324,280 @@ sub call $port = $anvil->data->{hosts}{$target}{port}; } + # Break out the port, if needed. + if ($target =~ /^(.*):(\d+)$/) + { + $target = $1; + $port = $2; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + port => $port, + target => $target, + }}); + + # If the user passed a port, override this. + if ($parameter->{port} =~ /^\d+$/) + { + $port = $parameter->{port}; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { port => $port }}); + } + } + else + { + # In case the user is using ports in /etc/ssh/ssh_config, we'll want to check for an entry. + $anvil->System->read_ssh_config(); + + $anvil->data->{hosts}{$target}{port} = "" if not defined $anvil->data->{hosts}{$target}{port}; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { "hosts::${target}::port" => $anvil->data->{hosts}{$target}{port} }}); + if ($anvil->data->{hosts}{$target}{port} =~ /^\d+$/) + { + $port = $anvil->data->{hosts}{$target}{port}; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { port => $port }}); + } + } + + # Make sure the port is valid. + if ($port eq "") + { + $port = 22; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { port => $port }}); + } + elsif ($port !~ /^\d+$/) + { + $port = getservbyname($port, 'tcp'); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { port => $port }}); + } + if ((not defined $port) or (($port !~ /^\d+$/) or ($port < 0) or ($port > 65536))) + { + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0058", variables => { port => $port }}); + return("!!error!!"); + } + + # If the target is a host name, convert it to an IP. + if (not $anvil->Validate->is_ipv4({ip => $target})) + { + my $new_target = $anvil->Convert->hostname_to_ip({host_name => $target}); + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { new_target => $new_target }}); + if ($new_target) + { + $target = $new_target; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { target => $target }}); + } + } + + # If the user set 'no_cache', don't use any existing 'ssh_fh'. + if (($no_cache) && ($ssh_fh)) + { + # Close the connection. + $ssh_fh->disconnect(); + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "message_0010", variables => { target => $target }}); + + # For good measure, blank both variables. + $anvil->data->{cache}{ssh_fh}{$ssh_fh_key} = ""; + $ssh_fh = ""; + } + + # This will store the output + my $output = ""; + my $state = ""; + my $error = ""; + my $connect_output = ""; + + # If I don't already have an active SSH file handle, connect now. + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { ssh_fh => $ssh_fh }}); + if ($ssh_fh !~ /^Net::OpenSSH/) + { + my $ssh_fh = ""; + my $connected = 0; + foreach (my $i = 0; $i <= 9; $i++) + { + ($connect_output) = capture_merged { + $ssh_fh = Net::OpenSSH->new($target, + user => $remote_user, + port => $port, + batch_mode => 1, + ); + }; + $connect_output =~ s/\n$//; + $connect_output =~ s/\r$//; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + 's1:target' => $target, + 's2:port' => $port, + 's3:ssh_fh' => $ssh_fh, + 's4:ssh_fh->error' => $ssh_fh->error, + 's5:connect_output' => $connect_output, + }}); + + #print "ssh error: [".$ssh_fh->error."]\n"; + #print "output: [".$connect_."]\n"; + # print "Results:\n"; print Dumper @result; + + # If I didn't connect, try again if I have a password. + if (($ssh_fh->error) && ($password) && ($connect_output =~ /Permission denied/i)) + { + # Try again. + #print "Connection without a password failed, trying again with the password.\n"; + $connect_output = ""; + ($connect_output) = capture_merged { + $ssh_fh = Net::OpenSSH->new($target, + user => $remote_user, + port => $port, + passwd => $password, + batch_mode => 1, + ); + }; + $connect_output =~ s/\n$//; + $connect_output =~ s/\r$//; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + 's1:target' => $target, + 's2:port' => $port, + 's3:ssh_fh' => $ssh_fh, + 's4:ssh_fh->error' => $ssh_fh->error, + 's5:connect_output' => $connect_output, + }}); + } + + if (not $ssh_fh->error) + { + # Connected! + $connected = 1; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { connected => $connected }}); + last; + } + elsif ($i < 9) + { + # Sleep and then try again. + $connect_output = ""; + sleep 1; + } + } + + # Try ten times. 9 in the loop, last try after. + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + 's1:connected' => $connected, + 's2:ssh_fh->error' => $ssh_fh->error, + }}); + if ((not $connected) && ($ssh_fh->error)) + { + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 1, priority => "alert", list => { + remote_user => $remote_user, + target => $target, + port => $port, + shell_call => $shell_call, + error => $ssh_fh->error, + }}); + + # We'll now try to get a more useful message for the user and logs. + my $message_key = "message_0005"; + my $variables = { + target => $target, + error => $ssh_fh->error, + }; + if (($ssh_fh->error =~ /Bad hostname/i) or ($connect_output =~ /Bad hostname/i)) + { + $message_key = "message_0001"; + } + elsif (($ssh_fh->error =~ /Connection refused/i) or ($connect_output =~ /Connection refused/i)) + { + $message_key = "message_0002"; + $variables = { + target => $target, + port => $port, + remote_user => $remote_user, + }; + } + elsif (($ssh_fh->error =~ /No route to host/) or ($connect_output =~ /No route to host/i)) + { + $message_key = "message_0003"; + } + elsif (($ssh_fh->error =~ /timeout/) or ($connect_output =~ /timeout/i)) + { + $message_key = "message_0004"; + } + $error = $anvil->Words->string({key => $message_key, variables => $variables}); + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, priority => "alert", key => $message_key, variables => $variables}); + } + + } + + + + + + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, secure => $secure, list => { + error => $error, + ssh_fh => $ssh_fh, + output => $output, + }}); + return($error, $output); +} + +### TODO: Delete this once we finish converting to Net::OpenSSH +sub call2 +{ + my $self = shift; + my $parameter = shift; + my $anvil = $self->parent; + my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3; + + # Get the target and port so that we can create the ssh_fh key + my $port = defined $parameter->{port} ? $parameter->{port} : 22; + my $target = defined $parameter->{target} ? $parameter->{target} : ""; + my $ssh_fh_key = $target.":".$port; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + port => $port, + target => $target, + }}); + + # This will store the SSH file handle for the given target after the initial connection. + $anvil->data->{cache}{ssh_fh}{$ssh_fh_key} = defined $anvil->data->{cache}{ssh_fh}{$ssh_fh_key} ? $anvil->data->{cache}{ssh_fh}{$ssh_fh_key} : ""; + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { "cache::ssh_fh::${ssh_fh_key}" => $anvil->data->{cache}{ssh_fh}{$ssh_fh_key} }}); + + # Now pick up the rest of the variables. + my $close = defined $parameter->{'close'} ? $parameter->{'close'} : 0; + my $no_cache = defined $parameter->{no_cache} ? $parameter->{no_cache} : 0; + my $password = defined $parameter->{password} ? $parameter->{password} : $anvil->data->{sys}{root_password}; + my $secure = defined $parameter->{secure} ? $parameter->{secure} : 0; + my $shell_call = defined $parameter->{shell_call} ? $parameter->{shell_call} : ""; + my $remote_user = defined $parameter->{remote_user} ? $parameter->{remote_user} : "root"; + my $start_time = time; + my $ssh_fh = $anvil->data->{cache}{ssh_fh}{$ssh_fh_key}; + # NOTE: The shell call might contain sensitive data, so we show '--' if 'secure' is set and $anvil->Log->secure is not. + $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { + 'close' => $close, + password => $anvil->Log->secure ? $password : $anvil->Words->string({key => "log_0186"}), + secure => $secure, + shell_call => ((not $anvil->Log->secure) && ($secure)) ? $anvil->Words->string({key => "log_0186"}) : $shell_call, + ssh_fh => $ssh_fh, + start_time => $start_time, + remote_user => $remote_user, + port => $port, + target => $target, + }}); + + if (not $shell_call) + { + # No shell call + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0020", variables => { method => "Remote->call()", parameter => "shell_call" }}); + return("!!error!!"); + } + if (not $target) + { + # No target + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0020", variables => { method => "Remote->call()", parameter => "target" }}); + return("!!error!!"); + } + if (not $remote_user) + { + $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0020", variables => { method => "Remote->call()", parameter => "remote_user" }}); + return("!!error!!"); + } + + # If the user didn't pass a port, but there is an entry in 'hosts::::port', use it. + if ((not $parameter->{port}) && ($anvil->data->{hosts}{$target}{port})) + { + $port = $anvil->data->{hosts}{$target}{port}; + } + # Break out the port, if needed. my $state = ""; my $error = ""; diff --git a/Anvil/Tools/System.pm b/Anvil/Tools/System.pm index ff82fb69..a7b13dcd 100644 --- a/Anvil/Tools/System.pm +++ b/Anvil/Tools/System.pm @@ -6,7 +6,6 @@ package Anvil::Tools::System; use strict; use warnings; use Data::Dumper; -use Net::SSH2; use Scalar::Util qw(weaken isweak); use Time::HiRes qw(gettimeofday tv_interval); use Proc::Simple; diff --git a/notes b/notes index 942b4abd..cae32a9d 100644 --- a/notes +++ b/notes @@ -1,3 +1,5 @@ +============ + NEXT; - RHEL 8 package changes: @@ -1058,4 +1060,3 @@ pcs resource create dlm ocf:pacemaker:controld op monitor interval=60s on-fail=f pcs resource create lvmlock ocf:heartbeat:lvmlockd op monitor interval=60s on-fail=fence clone meta interleave=true pcs constraint order start dlm-clone then lvmlock-clone pcs constraint colocation add lvmlock-clone with dlm-clone - diff --git a/rpm/SPECS/anvil.spec b/rpm/SPECS/anvil.spec index baf48be4..59bc169b 100644 --- a/rpm/SPECS/anvil.spec +++ b/rpm/SPECS/anvil.spec @@ -3,7 +3,7 @@ %define anvilgroup admin Name: anvil Version: 3.0 -Release: 23%{?dist} +Release: 24%{?dist} Summary: Alteeve Anvil! complete package. License: GPLv2+ @@ -38,6 +38,7 @@ Requires: htop Requires: iproute Requires: lsscsi Requires: mlocate +Requires: perl-Capture-Tiny Requires: perl-Data-Dumper Requires: perl-DBD-Pg Requires: perl-DBI @@ -45,10 +46,12 @@ Requires: perl-Digest-SHA Requires: perl-File-MimeInfo Requires: perl-HTML-FromText Requires: perl-HTML-Strip +Requires: perl-IO-Tty Requires: perl-JSON Requires: perl-Log-Journald Requires: perl-Net-SSH2 Requires: perl-Net-Netmask +Requires: perl-Net-OpenSSH Requires: perl-NetAddr-IP Requires: perl-Proc-Simple Requires: perl-Sys-Syslog