Perl Getopt::Declare parameter action not invoked - perl

I use Getopt::Declare in a script but invoking the script and passing -get_ip "test" doesn't do anything i.e. the script executes the "my" statements and getFirstAvailableIP doesn't get called.
use Getopt::Declare;
use lib "/home/vtsingaras/NicTool/client/lib/";
use NicToolServerAPI;
use strict;
use warnings;
#debug remove
use Data::Dumper;
#NicToolServer settings, edit
my $ntconf = {
ntuser => 'censored',
ntpass => 'censored',
nthost => 'censored',
ntport => 8082,
};
my ( $zone, $fqdn, $ip, $comment );
my $options_spec = q(+g[et_ip] <zone> Get the first available IP from the provided reverse <zone>.
{getFirstAvailableIP($::zone);}
+s[et_dns] <fqdn> <ip> <comment> Create an A record for <fqdn> that points to <ip> and the associated PTR record.
{createFwdAndPtr($::fqdn, $::ip, $::comment);}
);
my $args = Getopt::Declare->new($options_spec);
#Setup NicTool
my $nt = new NicToolServerAPI;
$NicToolServerAPI::server_host = $ntconf->{nthost};
$NicToolServerAPI::server_port = $ntconf->{ntport};
$NicToolServerAPI::data_protocol = "soap";
#$NicToolServerAPI::use_https_authentication = 0;
sub nt_login {
#Login to NicTool Server
my $ntuser = $nt->send_request(
action => "login",
username => $ntconf->{ntuser},
password => $ntconf->{ntpass},
);
if ( $ntuser->{error_code} ) {
print( "Unable to log in: " . $ntuser->{error_code} . " " . $ntuser->{error_msg} . "\n" );
exit 1;
} else {
print( "Logged in as " . $ntuser->{first_name} . " " . $ntuser->{last_name} . "\n" );
}
}
sub getFirstAvailableIP {
my $fqdn = $_[0];
print $fqdn;
die "blah";
}

The problem is that you specified + instead of - in $options_spec for get_ip.
Here is a self-contained runnable example which calls getFirstAvailableIP:
use strict;
use warnings;
use Getopt::Declare;
my $zone;
my $args = Getopt::Declare->new(<<'END_OPTS');
# tab
# ||||
# vvvv
-g[et_ip] <zone> Get the first available IP from the provided reverse <zone>.
{ getFirstAvailableIP($zone); }
END_OPTS
print "hello world\n";
exit;
sub getFirstAvailableIP {
print "blah - #_\n";
}
__END__
And executed:
$ perl declare_test.pl -get_ip test
blah - test
hello world
Note that this module requires a tab character in its specification; this makes it difficult to copy'n'paste correctly.

Related

SOAP::Lite log transport request/response with custom identifier

I would like to log SOAP::Lite transport request/response contents using a custom identifier (e.g. a transaction-id or txn_id in my example below):
#!/usr/bin/perl
use strict;
use warnings;
use Data::Dumper;
use SOAP::Lite +trace => [ transport => \&log_transport, ];
sub log_transport {
my ($in) = #_;
if (ref($in) eq "HTTP::Request") {
# INSERT INTO logs ( txn_id, request ) VALUES ( $tnx_id, $in->content )
say STDERR Dumper(ref($in), $in->content);
}
elsif (ref($in) eq "HTTP::Response") {
# UPDATE logs SET response = '$in->content' WHERE txn_id = $tnx_id
say STDERR Dumper(ref($in), $in->content);
}
}
$ENV{PERL_LWP_SSL_VERIFY_HOSTNAME}=0;
my $soap = SOAP::Lite->proxy('https://www.dataaccess.com/webservicesserver/NumberConversion.wso?op=NumberToWords');
$soap->serializer()->register_ns('http://www.dataaccess.com/webservicesserver/' ,"ns");
sub getWords
{
my ($number, $txn_id) = #_ ;
my $method = SOAP::Data->name("ns:NumberToWords");
my #params = ( SOAP::Data->name("ubiNum" => $number) );
my $response = $soap->call($method => #params);
if (!$response->fault) {
say STDOUT "NumberToWords = " . $response->result;
}
else {
say STDERR "error: " . (defined $response->faultstring? $response->faultstring : $soap->transport->status);
}
}
getWords(444, '123abc');
In my example above, how can I pass the transaction-id 123abc to my logger?
P.S. I do not wish to use:
$soap->outputxml(1)->call($method => #params)
It does not seem like the SOAP::Trace transport callback supports extra argument passing. As a workaround you could use a lexical variable declared in the outer scope like this:
use strict;
use warnings;
use Data::Dumper;
my $TXN_ID;
use SOAP::Lite +trace => [ transport => \&log_transport, ];
sub log_transport {
my ($in) = #_;
say STDERR "Logging transaction id: $TXN_ID:";
if (ref($in) eq "HTTP::Request") {
# INSERT INTO logs ( txn_id, request ) VALUES ( $tnx_id, $in->content )
say STDERR Dumper(ref($in), $in->content);
}
elsif (ref($in) eq "HTTP::Response") {
# UPDATE logs SET response = '$in->content' WHERE txn_id = $tnx_id
say STDERR Dumper(ref($in), $in->content);
}
}
$ENV{PERL_LWP_SSL_VERIFY_HOSTNAME}=0;
my $soap = SOAP::Lite->proxy('https://www.dataaccess.com/webservicesserver/NumberConversion.wso?op=NumberToWords');
$soap->serializer()->register_ns('http://www.dataaccess.com/webservicesserver/' ,"ns");
sub getWords
{
my ($number, $txn_id) = #_ ;
$TXN_ID = $txn_id;
my $method = SOAP::Data->name("ns:NumberToWords");
my #params = ( SOAP::Data->name("ubiNum" => $number) );
my $response = $soap->call($method => #params);
if (!$response->fault) {
say STDOUT "NumberToWords = " . $response->result;
}
else {
say STDERR "error: " . (defined $response->faultstring? $response->faultstring : $soap->transport->status);
}
}
getWords(444, '123abc');

Parsing command line mutual exclusion flags with their specific options in perl with Getopt::Long

I have several mutually exclusive flags witch have their own options. Lets say, if I invoke the "stop_service" flag, I want a "name" option; but if I invoke the "send_report" flag I want a "email" option. For parsing that I use "Getopt::Long". This is the code:
use Getopt::Long;
# Option vars
my $stop_service; # flag
my $send_report; # flag
my $name; # string
my $email; # string
# Get all possible options
GetOptions(
# Flag and options for stop_service
"stop_service" => \$stop_service, # Mutual Exclusion Flag
"name=s" => \$name, # option string
# Flag and options for send_report
"send_report" => \$send_report, # Mutual Exclusion Flag
"email=s" => \$email, # option string
);
# Parsing correct combinations
# --stop_service --name XXX
if (($stop_service and !$send_report) # mutual exclusion
and ($name && !$email)) # options
{
print "stop_service + name: \n";
print $stop_service, " - ", $name, "\n";
}
# --send_report --email XXX
elsif ((!$stop_service and $send_report) # mutual exclusion
and (!$name && $email)) # options
{
print "send_report + email: \n";
print $send_report, " - ", $email, "\n";
}
# HELP
else {
print <<DOC;
Help in line 1.
Help in line 2.
DOC
}
It works well:
[getopt]$ perl 06_getopt_cond_3.pl --stop_service --name jumersindo
stop_service + name:
1 - jumersindo
[getopt]$ perl 06_getopt_cond_3.pl --send_report --email jumer#jum.er
send_report + email:
1 - jumer#jum.er
[getopt]$ perl 06_getopt_cond_3.pl --send_report --name
Option name requires an argument
Help in line 1.
Help in line 2.
Are there a more "automatic" way of configuring that? or I need to specify all the option combinations with the "if" sentences?
Based on Avoiding mix of certain arguments to script, I managed to implement this solution. The idea is only accept the explicitly specified options and reject other combinations, like that:
use strict;
use Getopt::Long;
# Option vars
my %options= ();
# Get all possible options
GetOptions(
# Mutually exclusive flags
"detener_servicio|stop_service" => \$options{stop_service},
"arrancar_servicio|start_service" => \$options{start_service},
"reiniciar_servicio|restart_service" => \$options{restart_service},
"registrar_servicio|record_service" => \$options{record_service},
"enviar_informe|send_report" => \$options{send_report},
# Options
"nombre|name=s" => \$options{name},
"script=s" => \$options{script},
"ruta|path=s" => \$options{path},
"ejecutables|execs=s" => \$options{execs},
"email=s" => \$options{email},
);
if (only_specified_options(\%options, 'stop_service', 'name')) {
print "Stoping service: ", $options{name}, "\n";
}
elsif (only_specified_options(\%options, 'start_service', 'name')) {
print "Starting service: ", $options{name}, "\n";
}
elsif (only_specified_options(\%options, 'restart_service', 'name')) {
print "Restarting service: ", $options{name}, "\n";
}
elsif (only_specified_options(\%options, 'record_service', 'name',
'script','path','execs')) {
print "Recording service: ", $options{name}, " ",
$options{script}, " ",
$options{path}, " ",
$options{execs} , " ", "\n";
}
elsif (only_specified_options(\%options, 'send_report', 'email')) {
print "Sending report: ", $options{email}, "\n";
}
else {
print <<DOC;
Usage:
script --stop_service --name <NAME>
script --start_service --name <NAME>
script --restart_service --name <NAME>
script --record_service --name <NAME> --script <SCRIPT>
--path <PATH> --execs <EXECS>
script --send_report --email <EMAIL>
DOC
}
# only_specified_options(\%options, 'option_1', 'option_2',..., 'option_n')
# If only specified options are present => return true
# otherwise => false
sub only_specified_options {
my $opt_ref = shift;
my %must_params = map { $_ => 1 } #_;
my $result_bool = 1;
while ((my $key, my $value) = each (%$opt_ref)) {
$result_bool &&= (exists($must_params{$key})?$value:!$value);
}
return $result_bool;
}

Perl SNMP trap generator for scale testing?

I've hacked the script below together to let me generate traps to a test server. What I really need is something that will generate traps at a large scale so that I can check my tools on the receiving end to find out where the bottleneck is, such as UDP, Net::SNMP, Perl, etc.
I had hoped this script would let me generate something like 10k events/second but I am sadly mistaken.
Does anyone know if I can do this in Perl or have a suggestion of another way to do it?
#! /usr/bin/perl
use strict;
use warnings;
use Log::Fast;
use FindBin;
use Getopt::Long;
use File::Basename;
use Cwd qw(abs_path);
my $ROOT_DIR = abs_path("$FindBin::Bin/..");
use POSIX qw/strftime/;
use Net::SNMP qw(:ALL);
use Time::HiRes qw( time sleep );
#FIXME - I had to add below for Perl 5.10 users.
# on Perl 5.10, I would get the following when running:
# perl -e"autoflush STDOUT, 1;"
# Can't locate object method "autoflush" via package "IO::Handle" at -e line 1.
use FileHandle;
# Create default logger, will reconfigure it as soon as we read configuration from database
my $log = Log::Fast->global();
my $myname = $0;
$myname =~ s{.*/}{}; # leave just program name without path
# Command line options
my $options = {
debug => 0,
verbose => 0,
logfile => "./$myname.log",
help => 0,
community => "public",
trapsource => "127.0.0.1",
timelimit => 1,
};
sub usage_and_exit {
my ($exit_code) = #_;
print STDERR qq{
This program is used to generate SNMP traps to a specified host at a specified rate
Usage: $myname [-o --option]
-h : this (help) message
-d : debug level (0-5) (0 = disabled [default])
-v : Also print results to STDERR
-l : log file (defaults to local dir
-r : Rate (events/sec)
-ts : host to generate messages FROM
-td : host to generate messages TO
-tl : Run for this many seconds (default 1)
-c : community
Example: $myname -td 192.168.28.29 -r 1 -tl 5 -v
};
exit($exit_code);
}
GetOptions(
'debug|d=i' => \$options->{debug},
'help|h!' => \$options->{help},
'verbose|v!' => \$options->{verbose},
'logfile|l=s' => \$options->{logfile},
'rate|r=i' => \$options->{rate},
'trapsource|ts=s' => \$options->{trapsource},
'trapdest|td=s' => \$options->{trapdest},
'community|c=s' => \$options->{community},
'timelimit|tl=i' => \$options->{timelimit},
) or usage_and_exit(1); # got some invalid options
if ( $options->{help} ) {
usage_and_exit(0);
}
# Reconfigure log to use logfile (as we finally got it from $settings), also
# set proper level and output based on $options{verbose} and $options{debug}
setup_log();
# Finally we are initialized, announce this to the world :-)
$log->INFO("Program initialized successfully");
my $date = strftime "%Y-%m-%d %H:%M:%S", localtime;
# start func
my $period = 1 / $options->{rate};
my $start = time();
my $limit = time() + $options->{timelimit};
my $total = $options->{rate} * $options->{timelimit};
$log->INFO("Generating $options->{rate} trap(s) every second for $options->{timelimit} seconds (1 every $period seconds, $total total events)");
while($start < $limit) {
my $elapsed = time() - $start;
if ($elapsed < $period) {
sleep($period - $elapsed);
my ($session, $error) = Net::SNMP->session(
-hostname => $options->{trapdest},
-community => $options->{community},
-port => SNMP_TRAP_PORT, # Need to use port 162
-version => 'snmpv2c'
);
if (!defined($session)) {
$log->INFO("ERROR: %s.", $error);
exit 1;
}
my $result = $session->snmpv2_trap(
-varbindlist => [
'1.3.6.1.2.1.1.3.0', TIMETICKS, 600,
'1.3.6.1.6.3.1.1.4.1.0', OBJECT_IDENTIFIER, '1.3.6.1.4.1.326',
'1.3.6.1.6.3.18.1.3.0', IPADDRESS, $options->{trapsource}
]
);
if (!defined($result)) {
$log->INFO("ERROR: %s.", $session->error());
} else {
$log->INFO("SNMPv2-Trap-PDU sent from $options->{trapsource} to $options->{trapdest}.");
}
} else {
$start = time();
}
}
#-------------------------------------------
# There should only be subs from here down
#-------------------------------------------
# =================================================================================================
# Helper functions
# =================================================================================================
# commify not used yet
sub commify {
my $text = reverse $_[0];
$text =~ s/(\d\d\d)(?=\d)(?!\d*\.)/$1,/g;
return scalar reverse $text;
}
sub setup_log {
my $log_dir = dirname($options->{logfile});
# Create log dir, and build log path if not provided by command line option
if ( !-d $log_dir ) {
mkdir( $log_dir, 0755 ) or die("mkdir $log_dir: $!");
}
if ( !$options->{logfile} ) {
$options->{logfile} = $log_dir . "/" . basename( $0, '.pl' ) . '.log';
}
my $log_options = {};
# Set up output to file or both file and stderr
if ( $options->{verbose} ) {
# make multiplexer FH sending data both to file and STDERR
open( my $fh, '>>:tee', $options->{logfile}, \*STDERR )
or die("$options->{logfile}: $!");
$fh->autoflush(1);
$log_options->{fh} = $fh;
}
else {
open( my $fh, '>>', $options->{logfile} ) or die("$options->{logfile}: $!");
$log_options->{fh} = $fh;
}
# Setup extra information to put in every log line, depending on debug level
if ( $options->{debug} > 1 ) {
$log_options->{prefix} = "%D %T %S [%L] ";
}
else {
$log_options->{prefix} = "%D %T [%L] ";
}
$log_options->{level} = $options->{debug} > 0 ? 'DEBUG' : 'INFO';
$log->config($log_options);
$SIG{__WARN__} = sub {
my $msg = shift;
$msg =~ s/\n//;
$log->WARN($msg);
};
$log->INFO("Starting logging to $options->{logfile} with pid $$");
}
sub DEBUG {
my ( $level, #log_args ) = #_;
if ( $options->{debug} >= $level ) {
$log->DEBUG(#log_args);
}
}
Perhaps use something like Parallel::ForkManager ? In addition, with specific regard to testing scalability of your SNMP collector, you'll probably be interested in the use case of receiving the traps from many HOSTS, not just a single host sending traps at a high rate. For that, you might want to look at using pssh.
One problem might be the slowness of Net::SNMP in pure-perl - perhaps exectuting snmptest or snmptrap via the shell might be faster ? Worth a try.

How to properly call a sub by referencing in Perl

I'm working on a dispatching script. It takes a string with a command, does some cooking to it, and then parses it. But I can't grab a hold into the referencing:
Use::strict;
Use:warnings;
my($contexto, $cmd, $target, $ultpos, #params);
my $do = "echo5 sample string that says stuff ";
$target = "";
$cmd = "";
$_ = "";
# I do some cumbersome string parsing to get the array with
# the exploded string and then call parsear(#command)
sub parsear {
my %operations = (
'echo' => \&echo,
'status' => \&status,
'echo5' => \&echo5,
);
my $op = $_[0];
if ($operations{$op}){
$operations{$op}->(#_);
print "it exists\n";
}
else{
print "incorrect command.\n";
}
}
sub status {
print "correct status.\n";
}
sub echo {
shift(#_);
print join(' ',#_) . "\n";
}
sub echo5 {
shift(#_);
print join(' ',#_) . "\n" x 5;
}
I don't really know what the problem is. If the sub does not exist, it never says "incorrect command", and if I call for example "echo5 hello" it should print out:
hello
hello
hello
hello
hello
But it does nothing.
And when I call echo, it works as expected. What is the explanation?
Note: I'm on the latest version of Strawberry Perl
use strict; # 'use' is a keyword
use warnings;
# All these variables are not needed
sub parsear { # Learn to indent correctly
my %operations = (
'echo' => \&echo,
'status' => \&status,
'echo5' => \&echo5,
);
my $op = shift; # take first element off #_
if ($operations{$op}) {
print "$op exists\n"; # Make your status message useful
$operations{$op}->(#_);
} else {
print "incorrect command: $op\n"; # And your error message
}
}
sub status {
print "correct status.\n";
}
sub echo {
# shift(#_); # This is no longer needed, and now echo can be used as a
# normal subroutine as well as a dispatch target
print join(' ',#_) . "\n";
}
sub echo5 {
# shift(#_); # This is no longer needed
print +(join(' ',#_) . "\n") x 5; # Parentheses are needed since x binds tightly
}
Then running:
parsear 'status';
parsear 'echo', 'hello';
parsear 'echo5', 'hello';
parsear 'an error';
results in:
status exists
correct status.
echo exists
hello
echo5 exists
hello
hello
hello
hello
hello
incorrect command: an error
I am not sure what "cumbersome string parsing" you are doing since you did not include it, but if you are parsing a string like
my $do = "echo5 sample string that says stuff ";
where the command is the first word, and the arguments are the rest, you can either split everything:
parsear split /\s+/, $do;
Or use a regex to cut the first word off:
my ($cmd, $arg) = $do =~ /^(\w+)\s*(.*)/;
parsear $cmd => $arg;
You don’t even need the variables:
parsear $do =~ /^(\w+)\s*(.*)/;
Finally, the echo5 subroutine is a bit more complicated than it needs to be. It could be written as:
sub echo5 {
print "#_\n" x 5; # "#_" means join($", #_) and $" defaults to ' '
}
The x command binds differently from how you were expecting; you probably wanted:
print ((join(' ', #_) . "\n") x 5);
Both extra sets of parentheses seemed to be necessary.

How can I read messages in a Gmail account from Perl?

I have used the module Mail::Webmail::Gmail to read the new messages in my Gmail account.
I have written the following code for this purpose:
use strict;
use warnings;
use Data::Dumper;
use Mail::Webmail::Gmail;
my $gmail = Mail::Webmail::Gmail->new(
username => 'username', password => 'password',
);
my $messages = $gmail->get_messages( label => $Mail::Webmail::Gmail::FOLDERS{ 'INBOX' } );
foreach ( #{ $messages } ) {
if ( $_->{ 'new' } ) {
print "Subject: " . $_->{ 'subject' } . " / Blurb: " . $_->{ 'blurb' } . "\n";
}
}
But it didn't print anything.
Can anyone help me in this or suggest any other module for this?
Thanks in advance.
This is taken almost word from word from the Net::IMAP::Simple POD:
use strict;
use warnings;
# required modules
use Net::IMAP::Simple;
use Email::Simple;
use IO::Socket::SSL;
# fill in your details here
my $username = 'user#example.com';
my $password = 'secret';
my $mailhost = 'pop.gmail.com';
# Connect
my $imap = Net::IMAP::Simple->new(
$mailhost,
port => 993,
use_ssl => 1,
) || die "Unable to connect to IMAP: $Net::IMAP::Simple::errstr\n";
# Log in
if ( !$imap->login( $username, $password ) ) {
print STDERR "Login failed: " . $imap->errstr . "\n";
exit(64);
}
# Look in the INBOX
my $nm = $imap->select('INBOX');
# How many messages are there?
my ($unseen, $recent, $num_messages) = $imap->status();
print "unseen: $unseen, recent: $recent, total: $num_messages\n\n";
## Iterate through unseen messages
for ( my $i = 1 ; $i <= $nm ; $i++ ) {
if ( $imap->seen($i) ) {
next;
}
else {
my $es = Email::Simple->new( join '', #{ $imap->top($i) } );
printf( "[%03d] %s\n\t%s\n", $i, $es->header('From'), $es->header('Subject') );
}
}
# Disconnect
$imap->quit;
exit;
You can use the Mail::POP3Client module. It is used to get the message from the Gmail account.
Have you tried doing some error checking with after you try an operation
if ($gmail->error())
{
print $gmail->error_msg();
}
I found that when I do it it results in:
Error: Could not login with those
credentials - could not find final URL
Additionally, HTTP error: 200 OK
Error: Could not Login.
I believe it may be because this module was last updated in 2006 and gmail may have changed the way the logins work so it may no longer be able to access it.
What you could do if you don't just want to download new messages with pop3 is you can use
Net::IMAP::Simple to access a gmail account via IMAP