#!/usr/bin/env perl use strict; use warnings; our $home; BEGIN { use FindBin; FindBin::again(); $home = ($ENV{NETDISCO_HOME} || $ENV{HOME}); # try to find a localenv if one isn't already in place. if (!exists $ENV{PERL_LOCAL_LIB_ROOT}) { use File::Spec; my $localenv = File::Spec->catfile($FindBin::RealBin, 'localenv'); exec($localenv, $0, @ARGV) if -f $localenv; $localenv = File::Spec->catfile($home, 'perl5', 'bin', 'localenv'); exec($localenv, $0, @ARGV) if -f $localenv; die "Sorry, can't find libs required for App::Netdisco.\n" if !exists $ENV{PERLBREW_PERL}; } } BEGIN { use Path::Class; # stuff useful locations into @INC and $PATH unshift @INC, dir($FindBin::RealBin)->parent->subdir('lib')->stringify, dir($FindBin::RealBin, 'lib')->stringify; } # for netdisco app config use App::Netdisco; use Dancer qw/:moose :script/; info "App::Netdisco version $App::Netdisco::VERSION loaded."; use Try::Tiny; use Pod::Usage; use Scalar::Util 'blessed'; use NetAddr::IP qw/:rfc3021 :lower/; use App::Netdisco::Backend::Job; use App::Netdisco::Util::Device 'get_device'; use Getopt::Long; Getopt::Long::Configure ("bundling"); my ($device, $port, $extra, $debug, $rollback); my ($infotrace, $snmptrace, $sqltrace) = (0, 0, 0); my $result = GetOptions( 'device|d=s' => \$device, 'port|p=s' => \$port, 'extra|e=s' => \$extra, 'debug|D' => \$debug, 'rollback|R' => \$rollback, 'infotrace|I+' => \$infotrace, 'snmptrace|S+' => \$snmptrace, 'sqltrace|Q+' => \$sqltrace, ) or pod2usage( -msg => 'error: bad options', -verbose => 0, -exitval => 1, ); my $CONFIG = config(); $CONFIG->{logger} = 'console'; $CONFIG->{log} = ($debug ? 'debug' : 'info'); $ENV{INFO_TRACE} ||= $infotrace; $ENV{SNMP_TRACE} ||= $snmptrace; $ENV{DBIC_TRACE} ||= $sqltrace; $ENV{ND2_DB_ROLLBACK} ||= $rollback; # reconfigure logging to force console output Dancer::Logger->init('console', $CONFIG); # get requested action (my $action = shift @ARGV) =~ s/^set_// if scalar @ARGV; unless ($action) { pod2usage( -msg => 'error: missing action!', -verbose => 2, -exitval => 2, ); } # create worker (placeholder object for the action runner) { package MyWorker; use Moo; with 'App::Netdisco::Worker::Runner'; } my $worker = MyWorker->new(); my $net = NetAddr::IP->new($device); if ($device and (!$net or $net->num == 0 or $net->addr eq '0.0.0.0')) { info sprintf '%s: error - Bad host, IP or prefix: %s', $action, $device; exit 1; } my @hostlist = defined $device ? ($net->hostenum) : (undef); my $exitstatus = 0; foreach my $host (@hostlist) { my $dev = $host ? get_device($host->addr) : undef; if ($dev and not (blessed $dev and $dev->in_storage) and $action !~ m/^discover/) { info sprintf "%s: error - Don't know device: %s", $action, $host->addr; next; } # what job are we asked to do? my $job = App::Netdisco::Backend::Job->new({ job => 0, action => $action, device => $dev, port => $port, subaction => $extra, }); my $actiontext = ( ($job->device ? ('['.$job->device->ip.']') : '') . ($job->action eq 'show' ? ('/'. ($job->subaction || 'interfaces')) : '') ); # do job try { info sprintf '%s: %s started at %s', $action, $actiontext, scalar localtime; $worker->run($job); } catch { $job->status('error'); $job->log("error running job: $_"); }; if ($job->log eq 'failed to report from any worker!' and not $job->only_namespace) { pod2usage( -msg => (sprintf 'error: %s is not a valid action', $action), -verbose => 2, -exitval => 3, ); } info sprintf '%s: finished at %s', $action, scalar localtime; info sprintf '%s: status %s: %s', $action, $job->status, $job->log; $exitstatus = 1 if !$exitstatus and $job->status ne 'done'; } exit $exitstatus; =head1 NAME netdisco-do - Run any Netdisco job from the command-line. =head1 SYNOPSIS ~/bin/netdisco-do <action> [-DISQR] [-d <device> [-p <port>] [-e <extra>]] =head1 DESCRIPTION This program allows you to run any Netdisco poller job from the command-line. =head1 ACTIONS Note that some jobs (C<discoverall>, C<macwalk>, C<arpwalk>, C<nbtwalk>) simply add entries to the Netdisco job queue for other jobs, so won't seem to do much when you trigger them. For any action, if you wish to run one of its individual worker stages, then pass C<action::stage> as the first argument to C<netdisco-do>, for example C<discover::neighbors>. Any action taking a C<device> parameter can be passed either a hostname or IP of any interface on a known or unknown device, or an IP prefix (subnet) which will cause C<netdisco-do> to run the action for all addresses in that range. =head2 discover Run a discover on the device (specified with C<-d>). ~netdisco/bin/netdisco-do discover -d 192.0.2.1 =head2 discoverall Queue a discover for all known devices. =head2 macsuck Run a macsuck on the device (specified with C<-d>). ~netdisco/bin/netdisco-do macsuck -d 192.0.2.1 =head2 macwalk Queue a macsuck for all known devices. =head2 arpnip Run an arpnip on the device (specified with C<-d>). ~netdisco/bin/netdisco-do arpnip -d 192.0.2.1 =head2 arpwalk Queue an arpnip for all known devices. =head2 delete Delete a device (specified with C<-d>). Pass a log message for the action in the C<-e> parameter. Optionally request for associated nodes to be archived (rather than deleted) by setting the C<-p> parameter to "C<yes>" (mnemonic: B<p>reserve). ~netdisco/bin/netdisco-do delete -d 192.0.2.1 ~netdisco/bin/netdisco-do delete -d 192.0.2.1 -e 'older than the sun' ~netdisco/bin/netdisco-do delete -d 192.0.2.1 -e 'older than the sun' -p yes =head2 renumber Change the canonical IP address of a device (specified with C<-d>). Pass the new IP address in the C<-e> parameter. All related records such as topology, log and node information will also be updated to refer to the new device. Note that I<no> check is made as to whether the new IP is reachable for future polling. ~netdisco/bin/netdisco-do renumber -d 192.0.2.1 -e 192.0.2.254 =head2 nbtstat Run an nbtstat on the node (specified with C<-d>). ~netdisco/bin/netdisco-do nbtstat -d 192.0.2.2 =head2 nbtwalk Queue an nbtstat for all known nodes. =head2 expire Run Device and Node expiry actions according to configuration. =head2 expirenodes Archive nodes on the specified device. If you want to delete nodes, set the C<-e> parameter to "C<no>" (mnemonic: B<e>xpire). If you want to perform the action on a specific port, set the C<-p> parameter. ~netdisco/bin/netdisco-do expirenodes -d 192.0.2.1 ~netdisco/bin/netdisco-do expirenodes -d 192.0.2.1 -p FastEthernet0/1 -e no =head2 graph Generate GraphViz graphs for the largest cluster of devices. You'll need to install the L<Graph::Undirected> and L<GraphViz> Perl modules, and possibly also the C<graphviz> utility for your operating system. Also create a directory for the output files. mkdir ~netdisco/graph ~netdisco/bin/localenv cpanm Graph::Undirected ~netdisco/bin/localenv cpanm GraphViz =head2 show Dump the content of an SNMP MIB leaf, which is useful for diagnostics and troubleshooting. You should provide the "C<-e>" option which is the name of the leaf (such as C<interfaces> or C<uptime>). If you wish to test with a device class other than that discovered, prefix the leaf with the class short name, for example "C<Layer3::C3550::interfaces>" or "C<Layer2::HP::uptime>". ~netdisco/bin/netdisco-do show -d 192.0.2.1 -e interfaces ~netdisco/bin/netdisco-do show -d 192.0.2.1 -e Layer2::HP::interfaces A paramter may be passed to the C<SNMP::Info> method in the C<-p> parameter: ~netdisco/bin/netdisco-do show -d 192.0.2.1 -e has_layer -p 3 =head2 psql Start an interactive terminal with the Netdisco PostgreSQL database. If you pass an SQL statement in the C<-e> option then it will be executed. ~netdisco/bin/netdisco-do psql ~netdisco/bin/netdisco-do psql -e 'SELECT ip, dns FROM device' ~netdisco/bin/netdisco-do psql -e 'COPY (SELECT ip, dns FROM device) TO STDOUT WITH CSV HEADER' =head2 stats Updates Netdisco's statistics on number of devices, nodes, etc, for today. =head2 location Set the SNMP location field on the device (specified with C<-d>). Pass the location string in the C<-e> extra parameter. ~netdisco/bin/netdisco-do location -d 192.0.2.1 -e 'wiring closet' =head2 contact Set the SNMP contact field on the device (specified with C<-d>). Pass the contact name in the C<-e> extra parameter. ~netdisco/bin/netdisco-do contact -d 192.0.2.1 -e 'tel: 555-2453' =head2 portname Set the description on a device port. Requires the C<-d> parameter (device), C<-p> parameter (port), and C<-e> parameter (description). ~netdisco/bin/netdisco-do portname -d 192.0.2.1 -p FastEthernet0/1 -e 'Web Server' =head2 portcontrol Set the up/down status on a device port. Requires the C<-d> parameter (device), C<-p> parameter (port), and C<-e> parameter ("up" or "down"). ~netdisco/bin/netdisco-do portcontrol -d 192.0.2.1 -p FastEthernet0/1 -e up ~netdisco/bin/netdisco-do portcontrol -d 192.0.2.1 -p FastEthernet0/1 -e down =head2 vlan Set the native VLAN on a device port. Requires the C<-d> parameter (device), C<-p> parameter (port), and C<-e> parameter (VLAN number). ~netdisco/bin/netdisco-do vlan -d 192.0.2.1 -p FastEthernet0/1 -e 102 =head2 power Set the PoE on/off status on a device port. Requires the C<-d> parameter (device), C<-p> parameter (port), and C<-e> parameter ("on" or "off"). ~netdisco/bin/netdisco-do power -d 192.0.2.1 -p FastEthernet0/1 -e on ~netdisco/bin/netdisco-do power -d 192.0.2.1 -p FastEthernet0/1 -e off =head2 dumpconfig Will dump the loaded and parsed configuration for the application. Pass a specific configuration setting name to the C<-e> parameter to dump only that. =head1 DEBUG OPTIONS The flag "C<-R>" will cause any changes to the database to be rolled back at the end of the action. The flags "C<-DISQ>" can be specified, multiple times, and enable the following items in order: =over 4 =item C<-D> Netdisco debug log level =item C<-I> or C<-II> L<SNMP::Info> trace level (1 or 2). =item C<-S> or C<-SS> or C<-SSS> L<SNMP> (net-snmp) trace level (1, 2 or 3). =item C<-Q> L<DBIx::Class> trace enabled =back =cut