1#!/usr/bin/env perl 2 3use strict; 4use warnings; 5 6our $home; 7 8BEGIN { 9 use FindBin; 10 FindBin::again(); 11 12 $home = ($ENV{NETDISCO_HOME} || $ENV{HOME}); 13 14 # try to find a localenv if one isn't already in place. 15 if (!exists $ENV{PERL_LOCAL_LIB_ROOT}) { 16 use File::Spec; 17 my $localenv = File::Spec->catfile($FindBin::RealBin, 'localenv'); 18 exec($localenv, $0, @ARGV) if -f $localenv; 19 $localenv = File::Spec->catfile($home, 'perl5', 'bin', 'localenv'); 20 exec($localenv, $0, @ARGV) if -f $localenv; 21 22 die "Sorry, can't find libs required for App::Netdisco.\n" 23 if !exists $ENV{PERLBREW_PERL}; 24 } 25} 26 27BEGIN { 28 use Path::Class; 29 30 # stuff useful locations into @INC and $PATH 31 unshift @INC, 32 dir($FindBin::RealBin)->parent->subdir('lib')->stringify, 33 dir($FindBin::RealBin, 'lib')->stringify; 34} 35 36# for netdisco app config 37use App::Netdisco; 38use Dancer qw/:moose :script/; 39 40use Try::Tiny; 41use Pod::Usage; 42use Scalar::Util 'blessed'; 43use NetAddr::IP qw/:rfc3021 :lower/; 44 45use App::Netdisco::Backend::Job; 46use App::Netdisco::JobQueue 'jq_insert'; 47use App::Netdisco::Util::Device 'get_device'; 48 49use Getopt::Long; 50Getopt::Long::Configure ("bundling"); 51 52my ($port, $extra, $debug, $quiet, $queue_only, $rollback); 53my ($devices, $infotrace, $snmptrace, $sqltrace) = ([], 0, 0, 0); 54 55my $result = GetOptions( 56 'device|d=s@' => \$devices, 57 'port|p=s' => \$port, 58 'extra|e=s' => \$extra, 59 'debug|D' => \$debug, 60 'enqueue' => \$queue_only, 61 'quiet' => \$quiet, 62 'rollback|R' => \$rollback, 63 'infotrace|I+' => \$infotrace, 64 'snmptrace|S+' => \$snmptrace, 65 'sqltrace|Q+' => \$sqltrace, 66) or pod2usage( 67 -msg => 'error: bad options', 68 -verbose => 0, 69 -exitval => 1, 70); 71 72my $CONFIG = config(); 73$CONFIG->{logger} = 'console'; 74$CONFIG->{log} = ($debug ? 'debug' : ($quiet ? 'error' : 'info')); 75 76$ENV{INFO_TRACE} ||= $infotrace; 77$ENV{SNMP_TRACE} ||= $snmptrace; 78$ENV{DBIC_TRACE} ||= $sqltrace; 79$ENV{ND2_DB_ROLLBACK} ||= $rollback; 80 81# reconfigure logging to force console output 82Dancer::Logger->init('console', $CONFIG); 83 84info "App::Netdisco version $App::Netdisco::VERSION loaded."; 85 86# get requested action 87(my $action = shift @ARGV) =~ s/^set_// 88 if scalar @ARGV; 89 90unless ($action) { 91 pod2usage( 92 -msg => 'error: missing action!', 93 -verbose => 2, 94 -exitval => 2, 95 ); 96} 97 98# create worker (placeholder object for the action runner) 99{ 100 package MyWorker; 101 use Moo; 102 with 'App::Netdisco::Worker::Runner'; 103} 104my @hostlist = (); 105foreach my $device (@$devices) { 106 my $net = NetAddr::IP->new($device); 107 if ($device and (!$net or $net->num == 0 or $net->addr eq '0.0.0.0')) { 108 info sprintf '%s: error - Bad host, IP or prefix: %s', $action, $device; 109 exit 1; 110 } 111 push(@hostlist,$net->hostenum) if defined $device; 112} 113 114my @job_specs = (); 115my $exitstatus = 0; 116 117if (scalar @hostlist > 512) { 118 info sprintf '%s: aborted - unwise to attempt %s jobs at once', $action, (scalar @hostlist); 119 exit 1; 120} 121 122# some actions do not take a device parameter 123@hostlist = (undef) if 0 == scalar @hostlist; 124 125foreach my $host (@hostlist) { 126 my $dev = $host ? get_device($host->addr) : undef; 127 if ($dev and not (blessed $dev and $dev->in_storage) and $action !~ m/^discover/) { 128 info sprintf "%s: error - Don't know device: %s", $action, $host->addr; 129 next; 130 } 131 132 # what job are we asked to do? 133 push @job_specs, { 134 action => $action, 135 device => $dev, 136 port => $port, 137 subaction => ($extra || (($action eq 'discover') ? 'with-nodes' : undef)), 138 username => ($ENV{USER} || 'netdisco-do'), 139 }; 140} 141 142if ($queue_only) { 143 jq_insert( \@job_specs ); 144 info sprintf '%s: queued %s jobs at %s', 145 $action, (scalar @job_specs), scalar localtime; 146} 147else { 148 foreach my $spec (@job_specs) { 149 my $worker = MyWorker->new(); 150 my $job = App::Netdisco::Backend::Job->new({ job => 0, %$spec }); 151 $CONFIG->{$1."_min_age"} = 0 if $job->action =~ m/^(arpnip|macsuck|discover)$/; 152 153 my $actiontext = ( 154 ($job->device ? ('['.$job->device->ip.']') : '') . 155 ($job->action eq 'show' ? ('/'. ($job->subaction || 'interfaces')) : '') 156 ); 157 158 # do job 159 try { 160 info sprintf '%s: %s started at %s', 161 $action, $actiontext, scalar localtime; 162 $worker->run($job); 163 } 164 catch { 165 $job->status('error'); 166 $job->log("error running job: $_"); 167 }; 168 169 if ($job->log eq 'failed to report from any worker!' and not $job->only_namespace) { 170 pod2usage( 171 -msg => (sprintf 'error: %s is not a valid action', $action), 172 -verbose => 2, 173 -exitval => 3, 174 ); 175 } 176 177 info sprintf '%s: finished at %s', $action, scalar localtime; 178 info sprintf '%s: status %s: %s', $action, $job->status, $job->log; 179 $exitstatus = 1 if !$exitstatus and $job->status ne 'done'; 180 } 181} 182 183exit $exitstatus; 184 185=head1 NAME 186 187netdisco-do - Run any Netdisco job from the command-line. 188 189=head1 SYNOPSIS 190 191 ~/bin/netdisco-do <action> [-DISQR] [--enqueue] [--quiet] [-d <device> [-p <port>] [-e <extra>]] 192 193=head1 DESCRIPTION 194 195This program allows you to run any Netdisco poller job from the command-line. 196 197=head1 ACTIONS 198 199Note that some jobs (C<discoverall>, C<macwalk>, C<arpwalk>, C<nbtwalk>) 200simply add entries to the Netdisco job queue for other jobs, so won't seem 201to do much when you trigger them. Everything else happens in real-time. 202 203However the "C<--enqueue>" option will force the queueing of the job, 204regardless of type. This may be useful for cron-driven actions, or for actions 205working across large IP spaces. 206 207For any action, if you wish to run one of its individual worker stages, then 208pass C<action::stage> as the first argument to C<netdisco-do>, for example 209C<discover::neighbors>. 210 211Any action taking a C<device> parameter can be passed either a hostname or IP 212address of any interface of a known or unknown device, or an IP prefix 213(subnet) which will cause C<netdisco-do> to run the action on all addresses in 214that range. 215 216The C<device> parameter may be passed multiple times. In this case, all 217addresses (after expanding IP Prefixes) will be handled one by one. 218 219=head2 discover 220 221Run a discover on the device (specified with C<-d>). 222 223 ~/bin/netdisco-do discover -d 192.0.2.1 224 225Run a discover on two different devices (specified with C<-d>). 226 227 ~/bin/netdisco-do discover -d 192.0.2.1 -d 192.15.2.95 228 229=head2 discoverall 230 231Queue a discover for all known devices. 232 233=head2 macsuck 234 235Run a macsuck on the device (specified with C<-d>). 236 237 ~/bin/netdisco-do macsuck -d 192.0.2.1 238 239=head2 macwalk 240 241Queue a macsuck for all known devices. 242 243=head2 arpnip 244 245Run an arpnip on the device (specified with C<-d>). 246 247 ~/bin/netdisco-do arpnip -d 192.0.2.1 248 249=head2 arpwalk 250 251Queue an arpnip for all known devices. 252 253=head2 delete 254 255Delete a device (specified with C<-d>). Pass a log message for the action in 256the C<-e> parameter. Optionally request for associated nodes to be archived 257(rather than deleted) by setting the C<-p> parameter to "C<yes>" (mnemonic: 258B<p>reserve). 259 260 ~/bin/netdisco-do delete -d 192.0.2.1 261 ~/bin/netdisco-do delete -d 192.0.2.1 -e 'older than the sun' 262 ~/bin/netdisco-do delete -d 192.0.2.1 -e 'older than the sun' -p yes 263 264=head2 renumber 265 266Change the canonical IP address of a device (specified with C<-d>). Pass the 267new IP address in the C<-e> parameter. All related records such as topology, 268log and node information will also be updated to refer to the new device. 269 270Note that I<no> check is made as to whether the new IP is reachable for future 271polling. 272 273 ~/bin/netdisco-do renumber -d 192.0.2.1 -e 192.0.2.254 274 275=head2 nbtstat 276 277Run an nbtstat on the node (specified with C<-d>). 278 279 ~/bin/netdisco-do nbtstat -d 192.0.2.2 280 281=head2 nbtwalk 282 283Queue an nbtstat for all known nodes. 284 285=head2 expire 286 287Run Device and Node expiry actions according to configuration. 288 289=head2 expirenodes 290 291Archive nodes on the specified device. If you want to delete nodes, set the 292C<-e> parameter to "C<no>" (mnemonic: B<e>xpire). If you want to perform the 293action on a specific port, set the C<-p> parameter. 294 295 ~/bin/netdisco-do expirenodes -d 192.0.2.1 296 ~/bin/netdisco-do expirenodes -d 192.0.2.1 -p FastEthernet0/1 -e no 297 298=head2 graph 299 300Generate GraphViz graphs for the largest cluster of devices. 301 302You'll need to install the L<Graph::Undirected> and L<GraphViz> Perl modules, 303and possibly also the C<graphviz> utility for your operating system. Also 304create a directory for the output files. 305 306 mkdir ~/graph 307 ~/bin/localenv cpanm Graph::Undirected 308 ~/bin/localenv cpanm GraphViz 309 310=head2 show 311 312Dump the content of an SNMP MIB leaf, which is useful for diagnostics and 313troubleshooting. You should provide the "C<-e>" option which is the name of 314the leaf (such as C<interfaces> or C<uptime>). 315 316If you wish to test with a device class other than that discovered, prefix the 317leaf with the class short name, for example "C<Layer3::C3550::interfaces>" or 318"C<Layer2::HP::uptime>". Using "C<::>" as the start of the prefix will test 319against the base "C<SNMP::Info>" class. 320 321As well, SNMP OID names can be used as an argument for "C<-e>", so you can 322use C<ifName> for example, which will use the netdisco-mibs files for 323translations. 324 325All "C<-e>" parameters are case sensitive. 326 327 ~/bin/netdisco-do show -d 192.0.2.1 -e interfaces 328 ~/bin/netdisco-do show -d 192.0.2.1 -e Layer2::HP::interfaces 329 ~/bin/netdisco-do show -d 192.0.2.1 -e ::interfaces 330 ~/bin/netdisco-do show -d 192.0.2.1 -e ifName 331 332A parameter may be passed to the C<SNMP::Info> method or SNMP object in the 333"C<-p>" parameter: 334 335 ~/bin/netdisco-do show -d 192.0.2.1 -e has_layer -p 3 336 ~/bin/netdisco-do show -d 192.0.2.1 -e ifName -p 2 337 338The "C<-e>" parameter C<specify> will show the used configuration for the 339specified device. 340 341 ~/bin/netdisco-do show -d 192.0.2.1 -e specify 342 343=head2 psql 344 345Start an interactive terminal with the Netdisco PostgreSQL database. If you 346pass an SQL statement in the C<-e> option then it will be executed. 347 348 ~/bin/netdisco-do psql 349 ~/bin/netdisco-do psql -e 'SELECT ip, dns FROM device' 350 ~/bin/netdisco-do psql -e 'COPY (SELECT ip, dns FROM device) TO STDOUT WITH CSV HEADER' 351 352=head2 stats 353 354Updates Netdisco's statistics on number of devices, nodes, etc, for today. 355 356=head2 location 357 358Set the SNMP location field on the device (specified with C<-d>). Pass the 359location string in the C<-e> extra parameter. 360 361 ~/bin/netdisco-do location -d 192.0.2.1 -e 'wiring closet' 362 363=head2 contact 364 365Set the SNMP contact field on the device (specified with C<-d>). Pass the 366contact name in the C<-e> extra parameter. 367 368 ~/bin/netdisco-do contact -d 192.0.2.1 -e 'tel: 555-2453' 369 370=head2 portname 371 372Set the description on a device port. Requires the C<-d> parameter (device), 373C<-p> parameter (port), and C<-e> parameter (description). 374 375 ~/bin/netdisco-do portname -d 192.0.2.1 -p FastEthernet0/1 -e 'Web Server' 376 377=head2 portcontrol 378 379Set the up/down status on a device port. Requires the C<-d> parameter 380(device), C<-p> parameter (port), and C<-e> parameter ("up" or "down"). 381 382 ~/bin/netdisco-do portcontrol -d 192.0.2.1 -p FastEthernet0/1 -e up 383 ~/bin/netdisco-do portcontrol -d 192.0.2.1 -p FastEthernet0/1 -e down 384 385=head2 vlan 386 387Set the native VLAN on a device port. Requires the C<-d> parameter (device), 388C<-p> parameter (port), and C<-e> parameter (VLAN number). 389 390 ~/bin/netdisco-do vlan -d 192.0.2.1 -p FastEthernet0/1 -e 102 391 392=head2 power 393 394Set the PoE on/off status on a device port. Requires the C<-d> parameter 395(device), C<-p> parameter (port), and C<-e> parameter ("on" or "off"). 396 397 ~/bin/netdisco-do power -d 192.0.2.1 -p FastEthernet0/1 -e on 398 ~/bin/netdisco-do power -d 192.0.2.1 -p FastEthernet0/1 -e off 399 400=head2 makerancidconf 401 402Generates rancid configuration for known devices. See 403L<App::Netdisco::Worker::Plugin::MakeRancidConf> for configuration needs. 404 405 ~/bin/netdisco-do makerancidconf 406 407=head2 getapikey 408 409Generates an API key for the supplied username. See the 410L<API doc|https://github.com/netdisco/netdisco/wiki/API> for further 411information. 412 413 ~/bin/netdisco-do getapikey -e the_username 414 415=head2 dumpconfig 416 417Will dump the loaded and parsed configuration for the application. Pass a 418specific configuration setting name to the C<-e> parameter to dump only that. 419 420Some configuration items like device_auth are evaluated against the ACL first. 421Pass a device in C<-d> to display them: 422 423 ~/bin/netdisco-do dumpconfig -d 192.0.2.1 -e device_auth 424 425=head1 DEBUG OPTIONS 426 427The flag "C<-R>" will cause any changes to the database to be rolled back 428at the end of the action. 429 430The flags "C<-DISQ>" can be specified, multiple times, and enable the 431following items in order: 432 433=over 4 434 435=item C<-D> 436 437Netdisco debug log level. 438 439=item C<-I> or C<-II> 440 441L<SNMP::Info> trace level (1 or 2). 442 443=item C<-S> or C<-SS> or C<-SSS> 444 445L<SNMP> (net-snmp) trace level (1, 2 or 3). 446 447=item C<-Q> 448 449L<DBIx::Class> trace enabled. 450 451=back 452 453In case of issues with the colored output, setting the environment variable 454C<ANSI_COLORS_DISABLED> can be used to suppress it. 455 456=cut 457