diff --git a/lib/App/Netdisco/Worker/Plugin/MakeRancidConf.pm b/lib/App/Netdisco/Worker/Plugin/MakeRancidConf.pm index 8f241bae..390d0962 100644 --- a/lib/App/Netdisco/Worker/Plugin/MakeRancidConf.pm +++ b/lib/App/Netdisco/Worker/Plugin/MakeRancidConf.pm @@ -1,5 +1,8 @@ package App::Netdisco::Worker::Plugin::MakeRancidConf; +use strict; +use warnings; + use Dancer ':syntax'; use Dancer::Plugin::DBIC; @@ -8,7 +11,7 @@ use aliased 'App::Netdisco::Worker::Status'; use Path::Class; use List::Util qw/pairkeys pairfirst/; -use File::Slurper 'write_text'; +use File::Slurper qw/read_lines write_text/; use App::Netdisco::Util::Permission 'check_acl_no'; register_worker({ phase => 'main' }, sub { @@ -16,14 +19,31 @@ register_worker({ phase => 'main' }, sub { my $config = setting('rancid') || {}; my $domain_suffix = setting('domain_suffix') || ''; - my $delimiter = $config->{delimiter} || ':'; + my $delimiter = $config->{delimiter} || ';'; my $down_age = $config->{down_age} || '1 day'; + my $default_group = $config->{default_group} || 'default'; - my $rancidhome = $config->{rancid_home} + my $rancidconf = $config->{rancid_conf} || '/etc/rancid'; + my $rancidcvsroot = $config->{rancid_cvsroot} || dir($ENV{NETDISCO_HOME}, 'rancid')->stringify; - mkdir $rancidhome if ! -d $rancidhome; - return Status->error("cannot create or see rancid home: $rancidhome") - if ! -d $rancidhome; + mkdir $rancidcvsroot if ! -d $rancidcvsroot; + return Status->error("cannot create or access rancid cvsroot: $rancidcvsroot") + if ! -d $rancidcvsroot; + + my $allowed_types = {}; + foreach my $type (qw/base conf/) { + my $type_file = file($rancidconf, "rancid.types.$type")->stringify; + debug sprintf("trying rancid configuration file %s\n", $type_file); + next unless -f $type_file; + my @lines = read_lines($type_file); + foreach my $line (@lines) { + next if $line =~ m/^(?:\#|\$)/; + $allowed_types->{$1} += 1 if $line =~ m/^([a-z0-9_\-]+);login;.*$/; + } + } + + return Status->error("You didn't have any device types configured in your rancid installation.") + if ! scalar keys %$allowed_types; my $devices = schema('netdisco')->resultset('Device')->search(undef, { '+columns' => { old => @@ -32,24 +52,37 @@ register_worker({ phase => 'main' }, sub { $config->{groups} ||= { default => 'any' }; $config->{vendormap} ||= {}; + $config->{excluded} ||= {}; + $config->{by_ip} ||= {}; + $config->{by_hostname} ||= {}; my $routerdb = {}; while (my $d = $devices->next) { - my $name = - check_acl_no($d, $config->{by_ip}) ? $d->ip : ($d->dns || $d->name); - $name =~ s/$domain_suffix$// - if check_acl_no($d, $config->{by_hostname}); + + if (check_acl_no($d, $config->{excluded})) { + debug " skipping $d: device excluded of export"; + next + } + + my $name = check_acl_no($d, $config->{by_ip}) ? $d->ip : ($d->dns || $d->name); + $name =~ s/$domain_suffix$// if check_acl_no($d, $config->{by_hostname}); my ($group) = - pairkeys pairfirst { check_acl_no($d, $b) } %{ $config->{groups} }; + (pairkeys pairfirst { check_acl_no($d, $b) } %{ $config->{groups} }) || $default_group; my ($vendor) = (pairkeys pairfirst { check_acl_no($d, $b) } %{ $config->{vendormap} }) || $d->vendor; - if ($vendor =~ m/(?:enterprises\.|netdisco)/) { + if (not ($name and $vendor)) { + debug " skipping $d: the name or vendor is not defined"; + next + } elsif ($vendor =~ m/(?:enterprises\.|netdisco)/) { debug " skipping $d with unresolved vendor: $vendor"; next; + } elsif (scalar keys %$allowed_types and !exists($allowed_types->{$vendor})) { + debug " skipping $d: $vendor doesn't exist in rancid's vendor list"; + next; } push @{$routerdb->{$group}}, @@ -58,12 +91,14 @@ register_worker({ phase => 'main' }, sub { } foreach my $group (keys %$routerdb) { - mkdir dir($rancidhome, $group)->stringify; - my $content = join "\n", @{$routerdb->{$group}}; - write_text(file($rancidhome, $group, 'router.db')->stringify, "${content}\n"); + mkdir dir($rancidcvsroot, $group)->stringify; + my $content = "#\n# Router list file for rancid group $group.\n"; + $content .= "# Generate automatically by App::Netdisco::Worker::Plugin::MakeRancidConf\n#\n"; + $content .= join "\n", sort @{$routerdb->{$group}}; + write_text(file($rancidcvsroot, $group, 'router.db')->stringify, "${content}\n"); } - return Status->done('Wrote RANCID configuration.'); + return Status->done('Wrote rancid configuration.'); }); true; @@ -72,17 +107,20 @@ true; =head1 NAME -MakeRancidConf - Generate RANCID Configuration +MakeRancidConf - Generate rancid Configuration =head1 INTRODUCTION -This worker will generate a RANCID configuration for all devices in Netdisco. +This worker will generate a rancid configuration for all devices in Netdisco. Optionally you can provide configuration to control the output, however the -defaults are sane, and will create one RANCID group called "C" which -contains all devices. Those devices not discovered successfully within the -past day will be marked as "down" for RANCID to skip. Configuration is saved -to the "rancid" subdirectory of Netdisco's home folder. +defaults are sane for rancid versions 3.x and will create one rancid group +called C which contains all devices. Those devices not discovered +successfully within the past day will be marked as C for rancid to skip. +Configuration is saved to the F<~/rancid> subdirectory of Netdisco's home folder. + +Note that this only generates the router.db files, you will still need to +configure rancid's F<.cloginrc> and schedule C to run. You could run this worker at 09:05 each day using the following configuration: @@ -90,95 +128,142 @@ You could run this worker at 09:05 each day using the following configuration: makerancidconf: when: '5 9 * * *' +Since MakeRancidConf is a worker module it can also be run via C: + + netdisco-do makerancidconf + =head1 CONFIGURATION Here is a complete example of the configuration, which must be called -"C". All keys are optional: +C. All keys are optional: rancid: - rancid_home: "$ENV{NETDISCO_HOME}/rancid" # default - down_age: '1 day' # default - delimiter: ':' # default + rancid_cvsroot: '$ENV{NETDISCO_HOME}/rancid' # default + rancid_conf: '/etc/rancid' # default + down_age: '1 day' # default + delimiter: ';' # default + default_group: 'default' # default + excluded: + excludegroup1: 'host_group1_acl' + excludegroup2: 'host_group2_acl' groups: - groupname1: 'host_group1_acl' - groupname2: 'host_group2_acl' + groupname1: 'host_group3_acl' + groupname2: 'host_group4_acl' vendormap: - vname1: 'host_group3_acl' - vname2: 'host_group4_acl' - by_ip: 'host_group5_acl' - by_hostname: 'host_group6_acl' + vname1: 'host_group5_acl' + vname2: 'host_group6_acl' + by_ip: 'host_group7_acl' + by_hostname: 'host_group8_acl' -Note that the default home for writing files is not "C" so -you may wish to set this (especially if migrating from the old +Note that the default directory for writing files is not F so +you may wish to set this in C, (especially if migrating from the old C script). -Any values above that are a Host Group ACL will take either a single item or -list of Network Identifiers or Device Properties. See the L -wiki page for full details. We advise you to use the "C" setting +wiki page for full details. We advise you to use the C setting and then refer to named entries in that, for example: host_groups: coredevices: '192.0.2.0/24' edgedevices: '172.16.0.0/16' - + grp-nxos: 'os:nx-os' + rancid: groups: core_devices: 'group:coredevices' edge_devices: 'group:edgedevices' + vendormap: + cisco-nx: 'group:grp-nxos' + by_ip: 'any' -=head2 C +Do not forget that rancid also needs configuring when adding a new group, +such as scheduling the group to run, adding it to F, setting up the +email config and creating the repository with C. -The location to write RANCID Group configuration files into. A subdirectory -for each Group will be created. +=head2 C + +The location where the rancid configuration (F and +F) is installed. It will be used to check the existance +of device types before exporting the devices to the rancid configuration. if no match +is found the device will not be added to rancid. + +=head2 C + +The location to write rancid group configuration files (F) into. A +subdirectory for each group will be created. =head2 C This should be the same or greater than the interval between regular discover jobs on your network. Devices which have not been discovered within this time -will be marked as "C" to RANCID. +will be marked as C to rancid. The format is any time interval known and understood by PostgreSQL, such as at -L. +L. =head2 C -Set this to the delimiter character if needed to be different from the -default. +Set this to the delimiter character for your F entries if needed to +be different from the default, the default is C<;>. + +=head2 C + +Put devices into this group if they do not match any other groups defined. + +=head2 C + +This dictionary defines a list of devices that you do not wish to export to +rancid configuration. + +The value should be a L +to select devices in the Netdisco database. =head2 C -This dictionary maps RANCID Group names with configuration which will match +This dictionary maps rancid group names with configuration which will match devices in the Netdisco database. -The left hand side (key) should be the RANCID group name, the right hand side +The left hand side (key) should be the rancid group name, the right hand side (value) should be a L to select devices in the Netdisco database. =head2 C -If the device Vendor in Netdisco is not the same as the RANCID vendor script, -configure a mapping here. +If the device vendor in Netdisco is not the same as the rancid vendor script or +device type, configure a mapping here. -The left hand side (key) should be the RANCID vendor, the right hand side +The left hand side (key) should be the rancid device type, the right hand side (value) should be a L to select devices in the Netdisco database. +Note that vendors might have a large array of operating systems which require +different rancid modules. Mapping operating systems to rancid device types is +a good solution to use the correct device type. Example: + + host_groups: + grp-ciscosb: 'os:ros' + + rancid: + vendormap: + cisco-sb: 'group:grp-ciscosb' + =head2 C L -to select devices which will be written to the RANCID config as an IP address, -instead of the DNS FQDN or SNMP host name. +to select devices which will be written to the rancid config as an IP address, +instead of the DNS FQDN or SNMP hostname. =head2 C L -to select devices which will have the unqualified host name written to the -RANCID config. This is done simply by stripping the C +to select devices which will have the unqualified hostname written to the +rancid config. This is done simply by stripping the C configuration setting from the device FQDN. =head1 SEE ALSO