1package App::Netdisco::Configuration; 2 3use App::Netdisco::Environment; 4use App::Netdisco::Util::DeviceAuth (); 5use Dancer ':script'; 6 7use Path::Class 'dir'; 8use Net::Domain 'hostdomain'; 9use File::ShareDir 'dist_dir'; 10use URI::Based; 11 12BEGIN { 13 if (setting('include_paths') and ref [] eq ref setting('include_paths')) { 14 # stuff useful locations into @INC 15 push @{setting('include_paths')}, 16 dir(($ENV{NETDISCO_HOME} || $ENV{HOME}), 'nd-site-local', 'lib')->stringify 17 if (setting('site_local_files')); 18 unshift @INC, @{setting('include_paths')}; 19 } 20} 21 22# set up database schema config from simple config vars 23if (ref {} eq ref setting('database')) { 24 # override from env for docker 25 26 setting('database')->{name} = 27 ($ENV{NETDISCO_DB_NAME} || $ENV{NETDISCO_DBNAME} || setting('database')->{name}); 28 29 setting('database')->{host} = 30 ($ENV{NETDISCO_DB_HOST} || setting('database')->{host}); 31 32 setting('database')->{host} .= (';'. $ENV{NETDISCO_DB_PORT}) 33 if (setting('database')->{host} and $ENV{NETDISCO_DB_PORT}); 34 35 setting('database')->{user} = 36 ($ENV{NETDISCO_DB_USER} || setting('database')->{user}); 37 38 setting('database')->{pass} = 39 ($ENV{NETDISCO_DB_PASS} || setting('database')->{pass}); 40 41 my $name = setting('database')->{name}; 42 my $host = setting('database')->{host}; 43 my $user = setting('database')->{user}; 44 my $pass = setting('database')->{pass}; 45 46 my $dsn = "dbi:Pg:dbname=${name}"; 47 $dsn .= ";host=${host}" if $host; 48 49 # set up the netdisco schema now we have access to the config 50 # but only if it doesn't exist from an earlier config style 51 setting('plugins')->{DBIC}->{netdisco} ||= { 52 dsn => $dsn, 53 user => $user, 54 password => $pass, 55 options => { 56 AutoCommit => 1, 57 RaiseError => 1, 58 auto_savepoint => 1, 59 pg_enable_utf8 => 1, 60 }, 61 schema_class => 'App::Netdisco::DB', 62 }; 63 64 foreach my $c (@{setting('external_databases')}) { 65 my $schema = delete $c->{tag} or next; 66 next if $schema eq 'netdisco'; 67 setting('plugins')->{DBIC}->{$schema} = $c; 68 setting('plugins')->{DBIC}->{$schema}->{schema_class} 69 ||= 'App::Netdisco::GenericDB'; 70 } 71} 72 73# always set this 74$ENV{DBIC_TRACE_PROFILE} = 'console'; 75 76# override from env for docker 77config->{'community'} = ($ENV{NETDISCO_RO_COMMUNITY} ? 78 [split ',', $ENV{NETDISCO_RO_COMMUNITY}] : config->{'community'}); 79config->{'community_rw'} = ($ENV{NETDISCO_RW_COMMUNITY} ? 80 [split ',', $ENV{NETDISCO_RW_COMMUNITY}] : config->{'community_rw'}); 81 82# if snmp_auth and device_auth not set, add defaults to community{_rw} 83if ((setting('snmp_auth') and 0 == scalar @{ setting('snmp_auth') }) 84 and (setting('device_auth') and 0 == scalar @{ setting('device_auth') })) { 85 config->{'community'} = [ @{setting('community')}, 'public' ]; 86 config->{'community_rw'} = [ @{setting('community_rw')}, 'private' ]; 87} 88# fix up device_auth (or create it from old snmp_auth and community settings) 89# also imports legacy sshcollector config 90config->{'device_auth'} 91 = [ App::Netdisco::Util::DeviceAuth::fixup_device_auth() ]; 92 93# defaults for workers 94setting('workers')->{queue} ||= 'PostgreSQL'; 95if ($ENV{ND2_SINGLE_WORKER}) { 96 setting('workers')->{tasks} = 1; 97 delete config->{'schedule'}; 98} 99 100# force skipped DNS resolution, if unset 101setting('dns')->{hosts_file} ||= '/etc/hosts'; 102setting('dns')->{no} ||= ['fe80::/64','169.254.0.0/16']; 103 104# set max outstanding requests for AnyEvent::DNS 105$ENV{'PERL_ANYEVENT_MAX_OUTSTANDING_DNS'} 106 = setting('dns')->{max_outstanding} || 50; 107$ENV{'PERL_ANYEVENT_HOSTS'} = setting('dns')->{hosts_file}; 108 109# load /etc/hosts 110setting('dns')->{'ETCHOSTS'} = {}; 111{ 112 # AE::DNS::EtcHosts only works for A/AAAA/SRV, but we want PTR. 113 # this loads+parses /etc/hosts file using AE. dirty hack. 114 use AnyEvent::Socket 'format_address'; 115 use AnyEvent::DNS::EtcHosts; 116 AnyEvent::DNS::EtcHosts::_load_hosts_unless(sub{},AE::cv); 117 no AnyEvent::DNS::EtcHosts; # unimport 118 119 setting('dns')->{'ETCHOSTS'}->{$_} = 120 [ map { [ $_ ? (format_address $_->[0]) : '' ] } 121 @{ $AnyEvent::DNS::EtcHosts::HOSTS{ $_ } } ] 122 for keys %AnyEvent::DNS::EtcHosts::HOSTS; 123} 124 125# override from env for docker 126if ($ENV{NETDISCO_DOMAIN}) { 127 if ($ENV{NETDISCO_DOMAIN} eq 'discover') { 128 delete $ENV{NETDISCO_DOMAIN}; 129 if (! setting('domain_suffix')) { 130 info 'resolving domain name...'; 131 config->{'domain_suffix'} = hostdomain; 132 } 133 } 134 else { 135 config->{'domain_suffix'} = $ENV{NETDISCO_DOMAIN}; 136 } 137} 138 139# convert domain_suffix from scalar or list to regexp 140 141config->{'domain_suffix'} = [setting('domain_suffix')] 142 if ref [] ne ref setting('domain_suffix'); 143 144if (scalar @{ setting('domain_suffix') }) { 145 my @suffixes = map { (ref qr// eq ref $_) ? $_ : quotemeta } 146 @{ setting('domain_suffix') }; 147 my $buildref = '(?:'. (join '|', @suffixes) .')$'; 148 config->{'domain_suffix'} = qr/$buildref/; 149} 150else { 151 config->{'domain_suffix'} = qr//; 152} 153 154# convert radius and tacacs from single to lists 155 156if (ref {} eq ref setting('radius') 157 and exists setting('radius')->{'secret'}) { 158 159 my $servers = (ref [] eq ref setting('radius')->{'server'} 160 ? setting('radius')->{'server'} : [setting('radius')->{'server'}]); 161 config->{'radius'} = [ 162 Secret => setting('radius')->{'secret'}, 163 NodeList => $servers, 164 ]; 165} 166 167if (ref {} eq ref setting('tacacs') 168 and exists setting('tacacs')->{'key'}) { 169 170 config->{'tacacs'} = [ 171 Host => setting('tacacs')->{'server'}, 172 Key => setting('tacacs')->{'key'} || setting('tacacs')->{'secret'}, 173 Port => (setting('tacacs')->{'port'} || 'tacacs'), 174 Timeout => (setting('tacacs')->{'timeout'} || 15), 175 ]; 176} 177elsif (ref [] eq ref setting('tacacs')) { 178 my @newservers = (); 179 foreach my $server (@{ setting('tacacs') }) { 180 push @newservers, [ 181 Host => $server->{'server'}, 182 Key => $server->{'key'} || $server->{'secret'}, 183 Port => ($server->{'port'} || 'tacacs'), 184 Timeout => ($server->{'timeout'} || 15), 185 ]; 186 } 187 config->{'tacacs'} = [ @newservers ]; 188} 189 190# support unordered dictionary as if it were a single item list 191if (ref {} eq ref setting('device_identity')) { 192 config->{'device_identity'} = [ setting('device_identity') ]; 193} 194else { config->{'device_identity'} ||= [] } 195 196# copy devices_no and devices_only into others 197foreach my $name (qw/devices_no devices_only 198 discover_no macsuck_no arpnip_no nbtstat_no 199 discover_only macsuck_only arpnip_only nbtstat_only/) { 200 config->{$name} ||= []; 201 config->{$name} = [setting($name)] if ref [] ne ref setting($name); 202} 203foreach my $name (qw/discover_no macsuck_no arpnip_no nbtstat_no/) { 204 push @{setting($name)}, @{ setting('devices_no') }; 205} 206foreach my $name (qw/discover_only macsuck_only arpnip_only nbtstat_only/) { 207 push @{setting($name)}, @{ setting('devices_only') }; 208} 209 210# legacy config item names 211 212config->{'devport_vlan_limit'} = 213 config->{'deviceport_vlan_membership_threshold'} 214 if setting('deviceport_vlan_membership_threshold') 215 and not setting('devport_vlan_limit'); 216delete config->{'deviceport_vlan_membership_threshold'}; 217 218config->{'schedule'} = config->{'housekeeping'} 219 if setting('housekeeping') and not setting('schedule'); 220delete config->{'housekeeping'}; 221 222# used to have separate types of worker 223if (exists setting('workers')->{interactives} 224 or exists setting('workers')->{pollers}) { 225 226 setting('workers')->{tasks} ||= 227 (setting('workers')->{pollers} || 0) 228 + (setting('workers')->{interactives} || 0); 229 230 delete setting('workers')->{pollers}; 231 delete setting('workers')->{interactives}; 232} 233 234# moved the timeout setting 235setting('workers')->{'timeout'} = setting('timeout') 236 if defined setting('timeout') 237 and !defined setting('workers')->{'timeout'}; 238 239# 0 for workers max_deferrals and retry_after is like disabling 240# but we need to fake it with special values 241setting('workers')->{'max_deferrals'} ||= (2**30); 242setting('workers')->{'retry_after'} ||= '100 years'; 243 244# schedule expire used to be called expiry 245setting('schedule')->{expire} ||= setting('schedule')->{expiry} 246 if setting('schedule') and exists setting('schedule')->{expiry}; 247delete config->{'schedule'}->{'expiry'} if setting('schedule'); 248 249# upgrade reports config from hash to list 250if (setting('reports') and ref {} eq ref setting('reports')) { 251 config->{'reports'} = [ map {{ 252 tag => $_, 253 %{ setting('reports')->{$_} } 254 }} keys %{ setting('reports') } ]; 255} 256 257# add system_reports onto reports 258config->{'reports'} = [ @{setting('system_reports')}, @{setting('reports')} ]; 259 260# set swagger ui location 261#config->{plugins}->{Swagger}->{ui_dir} = 262 #dir(dist_dir('App-Netdisco'), 'share', 'public', 'swagger-ui')->absolute; 263 264# setup helpers for when request->uri_for() isn't available 265# (for example when inside swagger_path()) 266config->{url_base} 267 = URI::Based->new((config->{path} eq '/') ? '' : config->{path}); 268config->{api_base} 269 = config->{url_base}->with('/api/v1')->path; 270 271true; 272