Perl sFTP : how to check remote file doesnt exist - perl

I am 1 day old to Perl, was going through API doc here, have few basic questions
$sftp = Net::SFTP::Foreign->new($host, autodie => 1);
my $ls = $sftp->ls("/bar");
# dies as: "Couldn't open remote dir '/bar': No such file"
Question
with autodie will the connection be auto closed ?
we see in above example how to use folder , similar syntax also works for file ?
Or something like this makes more sense ??
my $sftp = Net::SFTP::Foreign->new($host, autodie => 1);
$sftp->find("/sdfjkalshfl", # nonexistent directory
on_error => sub { print "foo!\n";sftp->disconnect();exit; });
I was trying to run following code on my windows machine
use Net::SFTP::Foreign;
my $host = "demo.wftpserver.com";
my $sftp = Net::SFTP::Foreign->new($host ,ssh_cmd => 'plink',autodie => 1);
my $ls = $sftp->ls("/bar");
But i get error
'plink' is not recognized as an internal or external command ,
however when i run plink from windows command line it works fine !!

with autodie will the connection be auto closed ?
Yes. When the program ends, everything is destroyed and connections are closed. That is also the case when the $sftp variable goes out of scope. Modules like this usually implement a DESTROY sub. Those are invoked when the object (which is just a reference in Perl) goes out of scope. There can be some cleanup in that sub. Another example that has that is DBI, and of course lexical filehandles (like $fh from a open call).
we see in above example how to use folder , similar syntax also works for file ?
No. The docs say ls is for a directory:
Fetches a listing of the remote directory $remote. If $remote is not given, the current remote working directory is listed.
But you can just do ls for the directory that the file you want is in, and use the wanted option.
my $ls = $sftp->ls( '/home/foo', wanted => qr/^filename.txt$/ );
Though with the autodie that should die, so if you don't want it to actually die here, you should wrap it in a Try::Tiny call or an eval.
use Try::Tiny
# ...
my $ls = try {
return $sftp->ls( '/home/foo', wanted => qr/^filename.txt$/ );
} catch {
return; # will return undef
};
say 'Found file "filename.txt" on remote server' if $ls;
As to plink being not found, probably the Windows PATH is different from what your Perl sees.

Related

If I declare a package with multiple levels of embedding, does the module named as the leaf node need to be in a subdirctory?

I am dealing with some legacy Perl code, which I want to change as little as possible. The main script, call it "myscript.pl" has a structure like this
use MyCompany::ABDoc::HTMLFile;
use MyCompany::ABDoc::JavaScriptFile 2014.004_015;
print "Howdy"
...
The HTMLFile.pm module looks like this
package MyCompany::AMDoc::HTMLFile;
...
I am troubleshooting my_script.pl, and wish to run it from the command line. (It normally is triggered by a Jenkins job). When I try
perl -d ./my_script.pl
I get a message about HTMLFile.pm not being found. This is because hte HTMLFile.pm actually exists at the same level as my_script.pl in the filesystem.
If this was my own script, and I had the freedom to move things around, I would create directory structure
MyCompany/AMDoc/HtmlFile.pm
and I know the script would work. But I am reluctant to do this, because somehow, this all runs fine when triggered by the Jenkins job.
So is it possible to run this code, from the command line, without moving anything? I just want to do some troubleshooting. I haven't found discussion in the Perl documentation about what kinds of command line flags, such as "-I", might help me out here.
I would create directory structure MyCompany/AMDoc/HtmlFile.pm and I know the script would work.
No, moving the file to MyCompany/AMDoc/HTMLFile.pm relative to the script would not work unless the script already takes steps to add its directory to #INC.[1]
For example, adding the following in the script would achieve that:
use FindBin qw( $RealBin );
use lib $RealBin;
This can also be done from without
perl -I "$dir" "$dir"/my_script.pl # General case
perl -I . ./my_script.pl # Specific case
So is it possible to run this code, from the command line, without moving anything?
No, not without modifying the script.[2]
According to what you gave us, it has to be accessible as MyCompany/AMDoc/HTMLFile.pm relative to a directory in #INC.
It would happen to work if the script's current work directory happened to match the directory in which the script is found in old version of Perl. But that's just a fluke. These frequently don't match.
Well, you could use something of the form
perl -e'
use FindBin qw( $RealBin );
my $s = shift;
push #INC, sub {
my ( undef, $path ) = #_;
my ( $qfn ) = $path =~ m{^MyCompany/AMDoc/(.*)}s
or return;
open( my $fh, '<', $qfn )
or return;
return $fh;
};
do( $s ) or die( $# // $! );
' ./my_script.pl
Even then, that expects the script to end in a true value.
My initial assumption was wrong. My code was actually running on a Kubernetes pod, and that pod was configured with the correct directory structure for the module. In addition, PERL5LIB is set in the pod.
PERL5LIB=/perl5lib/perl-modules:/perl5lib/perl-modules/cpan/lib64/perl5:/perl5lib/perl-modules/cpan/share/perl5
and sure enough, that very first path has the path to my module.

How can I get a value from a configuarion file using Perl's Config::Simple?

I am trying to execute the shell script inside perl script. I am able to execute the shell script if I manually assign the variable. But I would like to call variable outside the script which is in configuration file.
How to execute the script? Below are my sample commands.
my $confdir = $ENV{CFG_DIR}; ## Directory where configuration file resides
my $configfile = 'LED.cfg'; ## Configuration file
my $LEDcfg = new Config::Simple( filename => "$confdir/$conffile" ); ## assigning the configuration file to LEDcfg file
my $ApID = $LEDcfg->param(".ApID") ## passing the parameter ApID from configuration file
chomp ${ApID};
my $pwd = `(ApID=$ApID $path/CA.sh)`; ## running shell script by ApID parameterized => Failed
my $pwd = `(ApID=ACF_ERTF_DER $path/CA.sh)`; ## running shell script by ApID hardcoded => Succeeded
Value of ApID=ACF_ERTF_DER as mentioned in line 7. This value is called from different configuration file which I called from $CFG_DIR/LED.cfg.
If I comment my $ApID (line 4) and my $pwd (line 6), the job succeeds by calling the ApID directly using the last line 7 i.e. my $pwd = (ApID=ACF_ERTF_DER $path/CA.sh);.
I am calling the ApID from LED.cfg file to retrieve the password from shell script and connects to server. If I run the shell script manually, I get the password which is used to connect to the server.
It looks like your problem is that you aren't reading the configuration file, and perhaps that is why you don't get any values.
The trick in programming is to know that you are in a good, expected state at each step. Go back to your program and look at each step to check that what you wanted to happen actually did happen. I suspect your problem happens very early in the process and that nothing else can then work.
You have the filename in $configfile, but then use $conffile:
my $confdir = $ENV{CFG_DIR};
my $configfile = 'LED.cfg';
my $LEDcfg = new Config::Simple( filename => "$confdir/$conffile" );
Then, after that, you don't do anything to check that you have a valid configuration.
Start by using strict, which forces you to declare variables so you can catch those pesky instances where you use the wrong name. Then, once you construct the path, see if it actually exists:
use strict;
use warnings;
use File::Spec::Functions;
my $conf_dir = ...;
my $conf_file = ...;
my $conf_path = catfile( $conf_dir, $conf_file );
die "Config path <$conf_path> does not exist!" unless -e $conf_path;
Once you know that you have an existing file. Note that you have to call the method correctly. The first argument is the path, not filename. You can also check that you got what you expected:
my $config = Config::Simple->new( $conf_path );
my %vars = $config->vars;
use Data::Dumper;
print "Found config: ", Dumper( \%vars );
Furthermore, you say you want to get the value for ApID, but then try to access .ApID. Maybe that's right, but it looks suspicious and you haven't shown us a sample configuration file. You might add some more debugging output there to check that you get a value.
Once you sort out that part, you can continue with the rest. But, also note that Config::Simple is 15 years old. There are many better, currently supported modules that can help you.

Redirect and Restore STDERR in Dancer

When starting my http server I don't want to see >> Dancer2 v0.201000 server <pid> listening on http://0.0.0.0:<port> printed on the stderr. Thats why I added the following line before calling start()
get "/pwd" => sub {
my $pwd = cwd;
print STDERR "\n\n[PWD] : $pwd\n"; # this line is not being printed
print "\n\n[STDOUT::PWD] : $pwd\n";
my %responseHash = ( pwd => $pwd );
my $response = encode_json \%responseHash;
return $response;
};
my $dancerStartErr;
sub startServer {
open (local *STDERR, ">", \$dancerStartErr)
or die "Dup err to variable error: $!\n";
start();
}
startServer();
The problem is that later I can't print something on the STERR. How can I reopen STDERR (open(STDERR, ">", \*STDERR); doesn't help)?
If you don't want your application to log anything, you can change the logging engine to use Dancer2::Logger::Null. You do that by editing your config.yml, or in one of your environments. For example, to turn it off in producion, change # appdir/environments/production.yml.
logger: 'null'
The default is the logging engine 'console', which prints stuff to your terminal.
There are other Dancer2::Logger:: classes available bundled with Dancer2 and on CPAN in their own distributions. A better solution to just dumping everything into a black hole might be to log to a file instead. Documentation of how to configure it further can be found in Dancer2::Core::Role::Logger.
Also note that instead of printing to STDERR in your code, you should use the logging keywords with the appropriate log level.
print STDERR "\n\n[PWD] : $pwd\n"; # this line is not being printed
This is not a good idea, because you cannot distinguish if this is an error, or a warning, or just debugging output. That's why there are different log levels built into Dancer2.
core
debug
info
warning
error
All of them are available as keywords. There is documentation on it in Dancer2::Manual.
Since the working directory is probably not relevant in production, but only during development, you'd go with debug.
debug "[PWD] : $pwd";
That's it. It takes care of newlines and such for you automatically.
You could use select before redirecting to save it in a variable
my $oldfh = select(STDERR);
and then use it later
select($oldfh);
Also check out:
Capture::Tiny::Extended
How to redirect and restore STDOUT/STDERR

How do I force extraction to fail if the destination directory doesn't exist?

I’m using Perl 5.16.3 with Archive::Extract
I am extracting a zip file, but I would like Perl to fail (die) if the destination directory doesn’t exist. Currently the below code does not fail, but creates the directory before extraction takes place
use Archive::Extract;
...
my $ae = Archive::Extract->new( archive => $downloadedFile, type => 'zip' );
my $ok = $ae->extract( to => $deployDir ) or die $ae->error;
How can I force things to die if the destination directory doesn’t exist?
There's nothing in the module that allows you to disable the automatic creation of the directory, so you just check it in a separate statement prior to the extract call
-d $deployDir or die {Deployment directory "$deployDir" doesn't exist};
Or you could use autodie and try to open it as a directory. That way the system will generate a die message for you. Like this
{
use autodie;
opendir my ($dh), $deployDir;
}
This requires Perl v5.10.1 or later. The braces { ... } around the code are required. They limit the scope of autodie and automatically close the directory handle again immediately

perl ssh then read file on remote server

I need to
connect to a remote server; then
do some things, like open and read the contents of a file.
For step 1:
my $server = "remoteservername.company.com";
my $ssh = Net::SSH::Perl->new("$server", debug => 1, protocol => 2, StrictHostKeyChecking => "no") or die "Error connecting server $server";
yields msg on terminal
Connection established.
so I presume I am ssh connected to remote server, via the code.
For step 2, how do i open and read a file on the remote server using code from the local server? this is the best i can do so far:
use strict;
use warnings;
use diagnostics;
use warnings::register;
use Net::SSH::Perl;
use Net::SSH::Expect;
use Math::BigInt lib => "Calc,GMP,Pari";
my $server = "server09";
my $ssh = Net::SSH::Perl->new("$server", debug => 1, protocol => 2, StrictHostKeyChecking => "no") or die "Error connecting server $server";
#open(FILE, "/home/myid/f09.txt") || print("Unable to open test.o\n"); #works, on local, opens file[does not fail].
#open(FILE, "server09://home/myid/f09.txt") || print("Unable to open test.o\n"); #---> error: "Unable to open test.o"
my #remote_text = `this text is put into array.`;
my $remote_text = join ('',#remote_text);
open (FILE,'>/home/myid/f09.txt');
print FILE "$remote_text";
close (FILE);
exit(0);
yet, it does not add anything to existing file f09.txt; also if i delete the file, the open does not create it. no errors, but this does not seem to contact the remote file.
just a simple explanation of ssh, then read from remote file would be helpful. other examples i see aren't cutting it. of course, could be me, long day, gotta walk away from it for a while. your time is very much appreciated!
You are essentially trying to modify a file that exists on another machine over SSH.
File operations functions cannot handle that.
Is it possible for you to download the file from the other server, update on your local and re-upload the file?
You may also want to experiment with the ssh command:
my #remote_text = ('this text is put into array.');
my $remote_text = join ('',#remote_text);
my #args = ("ssh server09", "echo '$remote_text' > /home/myid/f09.txt");
system(#args) == 0 or die "system #args failed: $?"
For performing interesting things over an SSH connection to another machine, you might like to try IPC::PerlSSH. From one of its examples:
use IPC::PerlSSH;
my $ips = IPC::PerlSSH->new( Host => "over.there" );
$ips->use_library( "FS", qw( mkdir chmod writefile ) );
$ips->call( "mkdir", "/tmp/testing" );
$ips->call( "chmod", 0600, "/tmp/testing" );
$ips->call( "writefile", "/tmp/testing/secret", <<EOF );
Some secret contents of my file here
EOF