Merge pull request #701 from rhansen/group_hosts_by

`group_hosts_by` improvements
This commit is contained in:
Richard Hansen 2024-07-13 04:45:16 -04:00 committed by GitHub
commit 60f931e7da
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 430 additions and 514 deletions

View file

@ -80,10 +80,10 @@ jobs:
dnf --refresh install -y 'dnf-command(config-manager)' epel-release &&
dnf config-manager --set-enabled crb
- name: install dependencies
# The --skip-broken argument works around RedHat UBI's missing packages.
# (They're only used for testing, so it's OK to not install them.)
# The --skip-broken argument works around missing packages. (They're
# only used for testing, so it's OK to not install them.)
run: |
dnf --refresh --skip-broken install -y \
dnf --refresh install --skip-broken -y \
automake \
findutils \
iproute \

View file

@ -921,7 +921,7 @@ our %protocols = (
'examples' => \&nic_hetzner_examples,
'variables' => {
%{$variables{'protocol-common-defaults'}},
'login' => setv(T_LOGIN, 0, 0, 'token', undef),
'login' => undef,
'min-interval' => setv(T_DELAY, 0, 0, interval('1m'), 0),
'server' => setv(T_FQDNP, 0, 0, 'dns.hetzner.com/api/v1', undef),
'ttl' => setv(T_NUMBER, 0, 0, 60, 60),
@ -977,9 +977,7 @@ our %protocols = (
'examples' => \&nic_noip_examples,
'variables' => {
%{$variables{'protocol-common-defaults'}},
'custom' => setv(T_BOOL, 0, 1, 0, undef),
'server' => setv(T_FQDNP, 0, 0, 'dynupdate.no-ip.com', undef),
'static' => setv(T_BOOL, 0, 1, 0, undef),
},
},
'nsupdate' => {
@ -3460,17 +3458,19 @@ sub get_ipv6 {
## group_hosts_by
######################################################################
sub group_hosts_by {
my ($hosts, $attributes) = @_;
my %attrs = map({ ($_ => 1) } @$attributes);
my @attrs = sort(keys(%attrs));
my %groups = ();
my ($hosts, @attrs) = @_;
my %attrs = map({ ($_ => undef); } @attrs);
@attrs = sort(keys(%attrs));
my %groups;
my %cfgs;
my $d = Data::Dumper->new([])->Indent(0)->Sortkeys(1)->Terse(1)->Useqq(1);
for my $h (@$hosts) {
my %cfg = map({ ($_ => $config{$h}{$_}); } grep(exists($config{$h}{$_}), @attrs));
my $sig = $d->Reset()->Values([\%cfg])->Dump();
push @{$groups{$sig}}, $h;
push(@{$groups{$sig}}, $h);
$cfgs{$sig} = \%cfg;
}
return %groups;
return map({ {cfg => $cfgs{$_}, hosts => $groups{$_}}; } keys(%groups));
}
######################################################################
@ -4007,62 +4007,48 @@ Example ${program}.conf file entries:
my-toplevel-domain.com,my-other-domain.com
EoEXAMPLE
}
######################################################################
## nic_dyndns2_update
######################################################################
sub nic_dyndns2_update {
debug("\nnic_dyndns2_update -------------------");
## group hosts with identical attributes together
my %groups = group_hosts_by(\@_, [qw(login password server static custom wildcard mx backupmx wantipv4 wantipv6)]);
my @groups = group_hosts_by(\@_, qw(login password server script static custom wildcard mx backupmx wantipv4 wantipv6));
my %errors = (
'badauth' => 'Bad authorization (username or password)',
'badsys' => 'The system parameter given was not valid',
'notfqdn' => 'A Fully-Qualified Domain Name was not provided',
'nohost' => 'The hostname specified does not exist in the database',
'!yours' => 'The hostname specified exists, but not under the username currently being used',
'!donator' => 'The offline setting was set, when the user is not a donator',
'!active' => 'The hostname specified is in a Custom DNS domain which has not yet been activated.',
'abuse', => 'The hostname specified is blocked for abuse; you should receive an email notification ' .
'which provides an unblock request link. More info can be found on ' .
'https://www.dyndns.com/support/abuse.html',
'abuse', => 'The hostname specified is blocked for abuse; you should receive an email notification which provides an unblock request link. More info can be found on https://www.dyndns.com/support/abuse.html',
'numhost' => 'System error: Too many or too few hosts found. Contact support@dyndns.org',
'dnserr' => 'System error: DNS error encountered. Contact support@dyndns.org',
'nochg' => 'No update required; unnecessary attempts to change to the current address are considered abusive',
);
## update each set of hosts that had similar configurations
for my $sig (keys %groups) {
my @hosts = @{$groups{$sig}};
for my $group (@groups) {
my @hosts = @{$group->{hosts}};
my %groupcfg = %{$group->{cfg}};
my $hosts = join(',', @hosts);
my $h = $hosts[0];
my $ipv4 = $config{$h}{'wantipv4'};
my $ipv6 = $config{$h}{'wantipv6'};
my $ipv4 = $groupcfg{'wantipv4'};
my $ipv6 = $groupcfg{'wantipv6'};
delete $config{$_}{'wantipv4'} for @hosts;
delete $config{$_}{'wantipv6'} for @hosts;
info("setting IPv4 address to %s for %s", $ipv4, $hosts) if $ipv4;
info("setting IPv6 address to %s for %s", $ipv6, $hosts) if $ipv6;
verbose("UPDATE:", "updating %s", $hosts);
## Select the DynDNS system to update
my $url = "http://$config{$h}{'server'}$config{$h}{'script'}?system=";
if ($config{$h}{'custom'}) {
my $url = "http://$groupcfg{'server'}$groupcfg{'script'}?system=";
if ($groupcfg{'custom'}) {
warning("updating %s: 'custom' and 'static' may not be used together. ('static' ignored)", $hosts)
if $config{$h}{'static'};
if $groupcfg{'static'};
$url .= 'custom';
} elsif ($config{$h}{'static'}) {
} elsif ($groupcfg{'static'}) {
$url .= 'statdns';
} else {
$url .= 'dyndns';
}
$url .= "&hostname=$hosts";
$url .= "&myip=";
$url .= $ipv4 if $ipv4;
@ -4070,62 +4056,50 @@ sub nic_dyndns2_update {
$url .= "," if $ipv4;
$url .= $ipv6;
}
## some args are not valid for a custom domain.
$url .= "&wildcard=ON" if ynu($config{$h}{'wildcard'}, 1, 0, 0);
if ($config{$h}{'mx'}) {
$url .= "&mx=$config{$h}{'mx'}";
$url .= "&backmx=" . ynu($config{$h}{'backupmx'}, 'YES', 'NO');
$url .= "&wildcard=ON" if ynu($groupcfg{'wildcard'}, 1, 0, 0);
if ($groupcfg{'mx'}) {
$url .= "&mx=$groupcfg{'mx'}";
$url .= "&backmx=" . ynu($groupcfg{'backupmx'}, 'YES', 'NO');
}
my $reply = geturl(
proxy => opt('proxy'),
url => $url,
login => $config{$h}{'login'},
password => $config{$h}{'password'},
login => $groupcfg{'login'},
password => $groupcfg{'password'},
) // '';
if ($reply eq '') {
failed("updating %s: Could not connect to %s.", $hosts, $config{$h}{'server'});
failed("updating %s: Could not connect to %s.", $hosts, $groupcfg{'server'});
next;
}
next if !header_ok($hosts, $reply);
my @reply = split /\n/, $reply;
my $state = 'header';
for my $line (@reply) {
if ($state eq 'header') {
$state = 'body';
} elsif ($state eq 'body') {
$state = 'results' if $line eq '';
} elsif ($state =~ /^results/) {
$state = 'results2';
# bug #10: some dyndns providers does not return the IP so
# we can't use the returned IP
my ($status, $returnedips) = split / /, lc $line;
for my $h (@hosts) {
$config{$h}{'status-ipv4'} = $status if $ipv4;
$config{$h}{'status-ipv6'} = $status if $ipv6;
}
if ($status eq 'good') {
for my $h (@hosts) {
$config{$h}{'ipv4'} = $ipv4 if $ipv4;
$config{$h}{'ipv6'} = $ipv6 if $ipv6;
$config{$h}{'mtime'} = $now;
}
success("updating %s: %s: IPv4 address set to %s", $hosts, $status, $ipv4) if $ipv4;
success("updating %s: %s: IPv6 address set to %s", $hosts, $status, $ipv6) if $ipv6;
} elsif (exists $errors{$status}) {
if ($status eq 'nochg') {
warning("updating %s: %s: %s", $hosts, $status, $errors{$status});
for my $h (@hosts) {
$config{$h}{'ipv4'} = $ipv4 if $ipv4;
$config{$h}{'ipv6'} = $ipv6 if $ipv6;
@ -4133,32 +4107,26 @@ sub nic_dyndns2_update {
$config{$h}{'status-ipv4'} = 'good' if $ipv4;
$config{$h}{'status-ipv6'} = 'good' if $ipv6;
}
} else {
failed("updating %s: %s: %s", $hosts, $status, $errors{$status});
}
} elsif ($status =~ /w(\d+)(.)/) {
my ($wait, $units) = ($1, lc $2);
my ($sec, $scale) = ($wait, 1);
($scale, $units) = (1, 'seconds') if $units eq 's';
($scale, $units) = (60, 'minutes') if $units eq 'm';
($scale, $units) = (60*60, 'hours') if $units eq 'h';
$sec = $wait * $scale;
for my $h (@hosts) {
$config{$h}{'wtime'} = $now + $sec;
}
warning("updating %s: %s: wait %s %s before further updates", $hosts, $status, $wait, $units);
} else {
failed("updating %s: unexpected status (%s)", $hosts, $line);
}
}
}
failed("updating %s: Could not connect to %s.", $hosts, $config{$h}{'server'})
failed("updating %s: Could not connect to %s.", $hosts, $groupcfg{'server'})
if $state ne 'results2';
}
}
@ -4323,10 +4291,6 @@ sub dnsexit2_update_host {
######################################################################
sub nic_noip_update {
debug("\nnic_noip_update -------------------");
## group hosts with identical attributes together
my %groups = group_hosts_by(\@_, [qw(login password server static custom wildcard mx backupmx wantipv4 wantipv6)]);
my %errors = (
'badauth' => 'Invalid username or password',
'badagent' => 'Invalid user agent',
@ -4337,14 +4301,12 @@ sub nic_noip_update {
'dnserr' => 'System error: DNS error encountered. Contact support@dyndns.org',
'nochg' => 'No update required; unnecessary attempts to change to the current address are considered abusive',
);
## update each set of hosts that had similar configurations
for my $sig (keys %groups) {
my @hosts = @{$groups{$sig}};
for my $group (group_hosts_by(\@_, qw(login password server wantipv4 wantipv6))) {
my @hosts = @{$group->{hosts}};
my %groupcfg = %{$group->{cfg}};
my $hosts = join(',', @hosts);
my $h = $hosts[0];
my $ipv4 = $config{$h}{'wantipv4'};
my $ipv6 = $config{$h}{'wantipv6'};
my $ipv4 = $groupcfg{'wantipv4'};
my $ipv6 = $groupcfg{'wantipv6'};
delete $config{$_}{'wantipv4'} for @hosts;
delete $config{$_}{'wantipv6'} for @hosts;
@ -4352,7 +4314,7 @@ sub nic_noip_update {
info("setting IPv6 address to %s for %s", $ipv6, $hosts) if $ipv6;
verbose("UPDATE:", "updating %s", $hosts);
my $url = "https://$config{$h}{'server'}/nic/update?system=noip&hostname=$hosts&myip=";
my $url = "https://$groupcfg{'server'}/nic/update?system=noip&hostname=$hosts&myip=";
$url .= $ipv4 if $ipv4;
if ($ipv6) {
$url .= "," if $ipv4;
@ -4362,11 +4324,11 @@ sub nic_noip_update {
my $reply = geturl(
proxy => opt('proxy'),
url => $url,
login => $config{$h}{'login'},
password => $config{$h}{'password'},
login => $groupcfg{'login'},
password => $groupcfg{'password'},
) // '';
if ($reply eq '') {
failed("updating %s: Could not connect to %s.", $hosts, $config{$h}{'server'});
failed("updating %s: Could not connect to %s.", $hosts, $groupcfg{'server'});
next;
}
next if !header_ok($hosts, $reply);
@ -4432,10 +4394,11 @@ sub nic_noip_update {
}
}
}
failed("updating %s: Could not connect to %s.", $hosts, $config{$h}{'server'})
failed("updating %s: Could not connect to %s.", $hosts, $groupcfg{'server'})
if $state ne 'results2';
}
}
######################################################################
## nic_noip_examples
######################################################################
@ -4670,40 +4633,38 @@ sub nic_zoneedit1_force_update {
######################################################################
sub nic_zoneedit1_update {
debug("\nnic_zoneedit1_update -------------------");
## group hosts with identical attributes together
my %groups = group_hosts_by(\@_, [qw(login password server zone wantip)]);
## update each set of hosts that had similar configurations
for my $sig (keys %groups) {
my @hosts = @{$groups{$sig}};
for my $group (group_hosts_by(\@_, qw(login password server zone wantip))) {
my @hosts = @{$group->{hosts}};
my %groupcfg = %{$group->{cfg}};
my $hosts = join(',', @hosts);
my $h = $hosts[0];
my $ip = $config{$h}{'wantip'};
my $ip = $groupcfg{'wantip'};
delete $config{$_}{'wantip'} for @hosts;
info("setting IP address to %s for %s", $ip, $hosts);
verbose("UPDATE:", "updating %s", $hosts);
my $url = '';
$url .= "https://$config{$h}{'server'}/auth/dynamic.html";
$url .= "https://$groupcfg{'server'}/auth/dynamic.html";
$url .= "?host=$hosts";
$url .= "&dnsto=$ip" if $ip;
$url .= "&zone=$config{$h}{'zone'}" if defined $config{$h}{'zone'};
$url .= "&zone=$groupcfg{'zone'}" if defined $groupcfg{'zone'};
my $reply = geturl(
proxy => opt('proxy'),
url => $url,
login => $config{$h}{'login'},
password => $config{$h}{'password'},
login => $groupcfg{'login'},
password => $groupcfg{'password'},
) // '';
if ($reply eq '') {
failed("updating %s: Could not connect to %s.", $hosts, $config{$h}{'server'});
failed("updating %s: Could not connect to %s.", $hosts, $groupcfg{'server'});
next;
}
next if !header_ok($hosts, $reply);
my @reply = split /\n/, $reply;
# TODO: This is awkward and fragile -- it assumes that each line in the response body
# corresponds with each host in @hosts (and in the same order).
my $h = $hosts[0];
for my $line (@reply) {
if ($h && $line =~ /^[^<]*<(SUCCESS|ERROR)\s+([^>]+)>(.*)/) {
my ($status, $assignments, $rest) = ($1, $2, $3);
@ -4734,10 +4695,12 @@ sub nic_zoneedit1_update {
redo if $line;
}
}
failed("updating %s: no response from %s", $hosts, $config{$h}{'server'})
# TODO: Shouldn't this log join(',' @hosts) instead of $hosts?
failed("updating %s: no response from %s", $hosts, $groupcfg{'server'})
if @hosts;
}
}
######################################################################
## nic_easydns_force_update
######################################################################
@ -4761,6 +4724,7 @@ sub nic_easydns_force_update {
}
return $update;
}
######################################################################
## nic_easydns_examples
######################################################################
@ -4805,40 +4769,30 @@ Example ${program}.conf file entries:
my-toplevel-domain.com,my-other-domain.com
EoEXAMPLE
}
######################################################################
## nic_easydns_update
######################################################################
sub nic_easydns_update {
debug("\nnic_easydns_update -------------------");
## each host is in a group by itself
my %groups = map { $_ => [ $_ ] } @_;
my %errors = (
'NOACCESS' => 'Authentication failed. This happens if the username/password OR host or domain are wrong.',
'NOSERVICE' => 'Dynamic DNS is not turned on for this domain.',
'ILLEGAL' => 'Client sent data that is not allowed in a dynamic DNS update.',
'TOOSOON' => 'Update frequency is too short.',
);
for my $h (@_) {
my $ipv4 = delete $config{$h}{'wantipv4'};
my $ipv6 = delete $config{$h}{'wantipv6'};
## update each set of hosts that had similar configurations
for my $sig (keys %groups) {
my @hosts = @{$groups{$sig}};
my $hosts = join(',', @hosts);
my $h = $hosts[0];
my $ipv4 = $config{$h}{'wantipv4'};
my $ipv6 = $config{$h}{'wantipv6'};
delete $config{$_}{'wantipv4'} for @hosts;
delete $config{$_}{'wantipv6'} for @hosts;
info("setting IP address to %s %s for %s", $ipv4, $ipv6, $hosts);
verbose("UPDATE:", "updating %s", $hosts);
info("setting IP address to %s %s for %s", $ipv4, $ipv6, $h);
verbose("UPDATE:", "updating %s", $h);
#'https://api.cp.easydns.com/dyn/generic.php?hostname=test.burry.ca&myip=10.20.30.40&wildcard=ON'
my $url;
$url = "https://$config{$h}{'server'}$config{$h}{'script'}?";
$url .= "hostname=$hosts";
$url .= "hostname=$h";
$url .= "&myip=";
$url .= $ipv4 if $ipv4;
for my $ipv6a ($ipv6) {
@ -4859,10 +4813,10 @@ sub nic_easydns_update {
password => $config{$h}{'password'},
) // '';
if ($reply eq '') {
failed("updating %s: Could not connect to %s.", $hosts, $config{$h}{'server'});
failed("updating %s: Could not connect to %s.", $h, $config{$h}{'server'});
next;
}
next if !header_ok($hosts, $reply);
next if !header_ok($h, $reply);
my @reply = split /\n/, $reply;
my $state = 'header';
@ -4877,7 +4831,6 @@ sub nic_easydns_update {
$state = 'results2';
my ($status) = $line =~ /^(\S*)\b.*/;
my $h = shift @hosts;
$config{$h}{'status-ipv4'} = $status if $ipv4;
$config{$h}{'status-ipv6'} = $status if $ipv6;
@ -4907,11 +4860,10 @@ sub nic_easydns_update {
last;
}
}
failed("updating %s: Could not connect to %s.", $hosts, $config{$h}{'server'})
failed("updating %s: Could not connect to %s.", $h, $config{$h}{'server'})
if $state ne 'results2';
}
}
######################################################################
######################################################################
## nic_namecheap_examples
@ -5746,21 +5698,13 @@ Example ${program}.conf file entries:
host1.example.com,host2.example.com
EoEXAMPLE
}
######################################################################
## nic_godaddy_update
######################################################################
sub nic_godaddy_update {
debug("\nnic_godaddy_update --------------------");
## group hosts with identical attributes together
my %groups = group_hosts_by(\@_, [qw(server login password zone wantipv4 wantipv6)]);
## update each set of hosts that had similar configurations
for my $sig (keys %groups) {
my @hosts = @{$groups{$sig}};
# Update each set configured host.
for my $host (@hosts) {
for my $host (@_) {
my $ipv4 = delete $config{$host}{'wantipv4'};
my $ipv6 = delete $config{$host}{'wantipv6'};
@ -5848,7 +5792,6 @@ sub nic_godaddy_update {
failed("%s.%s -- %s", $hostname, $zone, $msg);
}
}
}
}
######################################################################
@ -5883,29 +5826,17 @@ Example ${program}.conf file entries:
my-toplevel-domain.com,my-other-domain.com
EoEXAMPLE
}
######################################################################
## nic_googledomains_update
######################################################################
sub nic_googledomains_update {
debug("\nnic_googledomains_update -------------------");
## group hosts with identical attributes together
my %groups = group_hosts_by(\@_, [qw(server login password wantip)]);
## update each set of hosts that had similar configurations
for my $sig (keys %groups) {
my @hosts = @{$groups{$sig}};
my $key = $hosts[0];
my $ip = $config{$key}{'wantip'};
# FQDNs
for my $host (@hosts) {
delete $config{$host}{'wantip'};
for my $host (@_) {
my $ip = delete $config{$host}{'wantip'};
info("setting IP address to %s for %s", $ip, $host);
verbose("UPDATE:", "updating %s", $host);
# Update the DNS record
my $url = "https://$config{$host}{'server'}/nic/update";
$url .= "?hostname=$host";
$url .= "&myip=";
@ -5927,7 +5858,6 @@ sub nic_googledomains_update {
$config{$host}{'mtime'} = $now;
$config{$host}{'status'} = 'good';
}
}
}
######################################################################
@ -6067,23 +5997,18 @@ EoEXAMPLE
######################################################################
sub nic_nsupdate_update {
debug("\nnic_nsupdate_update -------------------");
## group hosts with identical attributes together
my %groups = group_hosts_by(\@_, [qw(login password server zone wantipv4 wantipv6)]);
## update each set of hosts that had similar configurations
for my $sig (keys %groups) {
my @hosts = @{$groups{$sig}};
for my $group (group_hosts_by(\@_, qw(login password server tcp zone wantipv4 wantipv6))) {
my @hosts = @{$group->{hosts}};
my %groupcfg = %{$group->{cfg}};
my $hosts = join(',', @hosts);
my $h = $hosts[0];
my $binary = $config{$h}{'login'};
my $keyfile = $config{$h}{'password'};
my $server = $config{$h}{'server'};
my $binary = $groupcfg{'login'};
my $keyfile = $groupcfg{'password'};
my $server = $groupcfg{'server'};
## nsupdate requires a port number to be separated by whitepace, not colon
$server =~ s/:/ /;
my $zone = $config{$h}{'zone'};
my $ipv4 = $config{$h}{'wantipv4'};
my $ipv6 = $config{$h}{'wantipv6'};
my $zone = $groupcfg{'zone'};
my $ipv4 = $groupcfg{'wantipv4'};
my $ipv6 = $groupcfg{'wantipv6'};
delete $config{$_}{'wantipv4'} for @hosts;
delete $config{$_}{'wantipv6'} for @hosts;
@ -6110,7 +6035,7 @@ EoINSTR2
send
EoINSTR4
my $command = "$binary -k $keyfile";
$command .= " -v" if ynu($config{$h}{'tcp'}, 1, 0, 0);
$command .= " -v" if ynu($groupcfg{'tcp'}, 1, 0, 0);
$command .= " -d" if (opt('debug'));
verbose("UPDATE:", "nsupdate command is: %s", $command);
verbose("UPDATE:", "nsupdate instructions are:\n%s", $instructions);
@ -6179,30 +6104,24 @@ Example ${program}.conf file entries:
my-toplevel-domain.com,my-other-domain.com
EoEXAMPLE
}
######################################################################
## nic_cloudflare_update
######################################################################
sub nic_cloudflare_update {
debug("\nnic_cloudflare_update -------------------");
## group hosts with identical attributes together
my %groups = group_hosts_by(\@_, [qw(ssh login password server wildcard mx backupmx zone wantipv4 wantipv6)]);
## update each set of hosts that had similar configurations
for my $sig (keys %groups) {
my @hosts = @{$groups{$sig}};
for my $group (group_hosts_by(\@_, qw(login password))) {
my @hosts = @{$group->{hosts}};
my %groupcfg = %{$group->{cfg}};
my $hosts = join(',', @hosts);
my $key = $hosts[0];
my $headers = "Content-Type: application/json\n";
if ($config{$key}{'login'} eq 'token') {
$headers .= "Authorization: Bearer $config{$key}{'password'}\n";
if ($groupcfg{'login'} eq 'token') {
$headers .= "Authorization: Bearer $groupcfg{'password'}\n";
} else {
$headers .= "X-Auth-Email: $config{$key}{'login'}\n";
$headers .= "X-Auth-Key: $config{$key}{'password'}\n";
$headers .= "X-Auth-Email: $groupcfg{'login'}\n";
$headers .= "X-Auth-Key: $groupcfg{'password'}\n";
}
# FQDNs
for my $domain (@hosts) {
my $ipv4 = delete $config{$domain}{'wantipv4'};
my $ipv6 = delete $config{$domain}{'wantipv6'};
@ -6210,15 +6129,15 @@ sub nic_cloudflare_update {
info("getting Cloudflare Zone ID for %s", $domain);
# Get zone ID
my $url = "https://$config{$key}{'server'}/zones/?";
$url .= "name=" . $config{$key}{'zone'};
my $url = "https://$config{$domain}{'server'}/zones/?";
$url .= "name=" . $config{$domain}{'zone'};
my $reply = geturl(proxy => opt('proxy'),
url => $url,
headers => $headers
);
unless ($reply && header_ok($domain, $reply)) {
failed("updating %s: Could not connect to %s.", $domain, $config{$key}{'server'});
failed("updating %s: Could not connect to %s.", $domain, $config{$domain}{'server'});
next;
}
@ -6231,9 +6150,9 @@ sub nic_cloudflare_update {
}
# Pull the ID out of the json, messy
my ($zone_id) = map {$_->{name} eq $config{$key}{'zone'} ? $_->{id} : ()} @{$response->{result}};
my ($zone_id) = map {$_->{name} eq $config{$domain}{'zone'} ? $_->{id} : ()} @{$response->{result}};
unless ($zone_id) {
failed("updating %s: No zone ID found.", $config{$key}{'zone'});
failed("updating %s: No zone ID found.", $config{$domain}{'zone'});
next;
}
info("Zone ID is %s", $zone_id);
@ -6249,14 +6168,14 @@ sub nic_cloudflare_update {
$config{$domain}{"status-ipv$ipv"} = 'failed';
# Get DNS 'A' or 'AAAA' record ID
$url = "https://$config{$key}{'server'}/zones/$zone_id/dns_records?";
$url = "https://$config{$domain}{'server'}/zones/$zone_id/dns_records?";
$url .= "type=$type&name=$domain";
$reply = geturl(proxy => opt('proxy'),
url => $url,
headers => $headers
);
unless ($reply && header_ok($domain, $reply)) {
failed("updating %s: Could not connect to %s.", $domain, $config{$key}{'server'});
failed("updating %s: Could not connect to %s.", $domain, $config{$domain}{'server'});
next;
}
# Strip header
@ -6274,7 +6193,7 @@ sub nic_cloudflare_update {
}
debug("updating %s: DNS '$type' record ID: $dns_rec_id", $domain);
# Set domain
$url = "https://$config{$key}{'server'}/zones/$zone_id/dns_records/$dns_rec_id";
$url = "https://$config{$domain}{'server'}/zones/$zone_id/dns_records/$dns_rec_id";
my $data = "{\"content\":\"$ip\"}";
$reply = geturl(proxy => opt('proxy'),
url => $url,
@ -6327,41 +6246,31 @@ Example ${program}.conf file entries:
my-toplevel-domain.com,my-other-domain.com
EoEXAMPLE
}
######################################################################
## nic_hetzner_update
######################################################################
sub nic_hetzner_update {
debug("\nnic_hetzner_update -------------------");
## group hosts with identical attributes together
my %groups = group_hosts_by(\@_, [qw(ssh login password server wildcard mx backupmx zone wantipv4 wantipv6)]);
## update each set of hosts that had similar configurations
for my $sig (keys %groups) {
my @hosts = @{$groups{$sig}};
my $hosts = join(',', @hosts);
my $key = $hosts[0];
my $headers = "Auth-API-Token: $config{$key}{'password'}\n";
for my $domain (@_) {
my $headers = "Auth-API-Token: $config{$domain}{'password'}\n";
$headers .= "Content-Type: application/json";
# FQDNs
for my $domain (@hosts) {
(my $hostname = $domain) =~ s/\.$config{$key}{zone}$//;
(my $hostname = $domain) =~ s/\.$config{$domain}{zone}$//;
my $ipv4 = delete $config{$domain}{'wantipv4'};
my $ipv6 = delete $config{$domain}{'wantipv6'};
info("getting Hetzner Zone ID for %s", $domain);
# Get zone ID
my $url = "https://$config{$key}{'server'}/zones?name=" . $config{$key}{'zone'};
my $url = "https://$config{$domain}{'server'}/zones?name=" . $config{$domain}{'zone'};
my $reply = geturl(proxy => opt('proxy'),
url => $url,
headers => $headers
);
unless ($reply && header_ok($domain, $reply)) {
failed("updating %s: Could not connect to %s.", $domain, $config{$key}{'server'});
failed("updating %s: Could not connect to %s.", $domain, $config{$domain}{'server'});
next;
}
@ -6374,14 +6283,13 @@ sub nic_hetzner_update {
}
# Pull the ID out of the json, messy
my ($zone_id) = map {$_->{name} eq $config{$key}{'zone'} ? $_->{id} : ()} @{$response->{zones}};
my ($zone_id) = map {$_->{name} eq $config{$domain}{'zone'} ? $_->{id} : ()} @{$response->{zones}};
unless ($zone_id) {
failed("updating %s: No zone ID found.", $config{$key}{'zone'});
failed("updating %s: No zone ID found.", $config{$domain}{'zone'});
next;
}
info("Zone ID is %s", $zone_id);
# IPv4 and IPv6 handling are similar enough to do in a loop...
for my $ip ($ipv4, $ipv6) {
next if (!$ip);
@ -6392,13 +6300,13 @@ sub nic_hetzner_update {
$config{$domain}{"status-ipv$ipv"} = 'failed';
# Get DNS 'A' or 'AAAA' record ID
$url = "https://$config{$key}{'server'}/records?zone_id=$zone_id";
$url = "https://$config{$domain}{'server'}/records?zone_id=$zone_id";
$reply = geturl(proxy => opt('proxy'),
url => $url,
headers => $headers
);
unless ($reply && header_ok($domain, $reply)) {
failed("updating %s: Could not connect to %s.", $domain, $config{$key}{'server'});
failed("updating %s: Could not connect to %s.", $domain, $config{$domain}{'server'});
next;
}
# Strip header
@ -6416,11 +6324,11 @@ sub nic_hetzner_update {
if ($dns_rec_id)
{
debug("updating %s: DNS '$type' record ID: $dns_rec_id", $domain);
$url = "https://$config{$key}{'server'}/records/$dns_rec_id";
$url = "https://$config{$domain}{'server'}/records/$dns_rec_id";
$http_method = "PUT";
} else {
debug("creating %s: DNS '$type'", $domain);
$url = "https://$config{$key}{'server'}/records";
$url = "https://$config{$domain}{'server'}/records";
$http_method = "POST";
}
my $data = "{\"zone_id\":\"$zone_id\", \"name\": \"$hostname\", \"value\": \"$ip\", \"type\": \"$type\", \"ttl\": $config{$domain}{'ttl'}}";
@ -6448,7 +6356,6 @@ sub nic_hetzner_update {
}
}
}
}
}
######################################################################
@ -6481,6 +6388,7 @@ Example ${program}.conf file entries:
record.myhost.com,other.myhost.com
EoEXAMPLE
}
######################################################################
## nic_yandex_update
##
@ -6489,20 +6397,9 @@ EoEXAMPLE
######################################################################
sub nic_yandex_update {
debug("\nnic_yandex_update -------------------");
## group hosts with identical attributes together
my %groups = group_hosts_by(\@_, [qw(server login pasword wantip)]);
## update each set of hosts that had similar configurations
for my $sig (keys %groups) {
my @hosts = @{$groups{$sig}};
my $key = $hosts[0];
my $ip = $config{$key}{'wantip'};
my $headers = "PddToken: $config{$key}{'password'}\n";
# FQDNs
for my $host (@hosts) {
delete $config{$host}{'wantip'};
for my $host (@_) {
my $ip = delete $config{$host}{'wantip'};
my $headers = "PddToken: $config{$host}{'password'}\n";
info("setting IP address to %s for %s", $ip, $host);
verbose("UPDATE:", "updating %s", $host);
@ -6510,10 +6407,10 @@ sub nic_yandex_update {
# Get record ID for host
my $url = "https://$config{$host}{'server'}/api2/admin/dns/list?";
$url .= "domain=";
$url .= $config{$key}{'login'};
$url .= $config{$host}{'login'};
my $reply = geturl(proxy => opt('proxy'), url => $url, headers => $headers);
unless ($reply) {
failed("updating %s: Could not connect to %s.", $host, $config{$key}{'server'});
failed("updating %s: Could not connect to %s.", $host, $config{$host}{'server'});
next;
}
next if !header_ok($host, $reply);
@ -6536,7 +6433,7 @@ sub nic_yandex_update {
# Update the DNS record
$url = "https://$config{$host}{'server'}/api2/admin/dns/edit";
my $data = "domain=";
$data .= $config{$key}{'login'};
$data .= $config{$host}{'login'};
$data .= "&record_id=";
$data .= $id;
$data .= "&content=";
@ -6568,7 +6465,6 @@ sub nic_yandex_update {
$config{$host}{'mtime'} = $now;
$config{$host}{'status'} = 'good';
}
}
}
######################################################################
@ -7381,12 +7277,12 @@ EoEXAMPLE
}
sub nic_cloudns_update {
my %groups = group_hosts_by(\@_, [qw(dynurl wantip)]);
for my $hr (values(%groups)) {
my @hosts = @$hr;
for my $group (group_hosts_by(\@_, qw(dynurl wantip))) {
my @hosts = @{$group->{hosts}};
my %groupcfg = %{$group->{cfg}};
my $hosts = join(',', @hosts);
my $ip = $config{$hosts[0]}{'wantip'};
my $dynurl = $config{$hosts[0]}{'dynurl'};
my $ip = $groupcfg{'wantip'};
my $dynurl = $groupcfg{'dynurl'};
delete $config{$_}{'wantip'} for @hosts;
# https://www.cloudns.net/wiki/article/36/ says, "If you are behind a proxy and your real
# IP is set in the header X-Forwarded-For you need to add &proxy=1 at the end of the

View file

@ -34,57 +34,77 @@ my @test_cases = (
{
desc => 'empty attribute set yields single group with all hosts',
groupby => [qw()],
want => [[$h1, $h2, $h3]],
want => [{cfg => {}, hosts => [$h1, $h2, $h3]}],
},
{
desc => 'common attribute yields single group with all hosts',
groupby => [qw(common)],
want => [[$h1, $h2, $h3]],
want => [{cfg => {common => 'common'}, hosts => [$h1, $h2, $h3]}],
},
{
desc => 'subset share a value',
groupby => [qw(h1h2)],
want => [[$h1, $h2], [$h3]],
want => [
{cfg => {h1h2 => 'h1 and h2'}, hosts => [$h1, $h2]},
{cfg => {h1h2 => 'unique'}, hosts => [$h3]},
],
},
{
desc => 'all unique',
groupby => [qw(unique)],
want => [[$h1], [$h2], [$h3]],
want => [
{cfg => {unique => 'h1'}, hosts => [$h1]},
{cfg => {unique => 'h2'}, hosts => [$h2]},
{cfg => {unique => 'h3'}, hosts => [$h3]},
],
},
{
desc => 'combination',
groupby => [qw(common h1h2)],
want => [[$h1, $h2], [$h3]],
want => [
{cfg => {common => 'common', h1h2 => 'h1 and h2'}, hosts => [$h1, $h2]},
{cfg => {common => 'common', h1h2 => 'unique'}, hosts => [$h3]},
],
},
{
desc => 'falsy values',
groupby => [qw(falsy)],
want => [[$h1], [$h2], [$h3]],
want => [
{cfg => {falsy => 0}, hosts => [$h1]},
{cfg => {falsy => ''}, hosts => [$h2]},
{cfg => {falsy => undef}, hosts => [$h3]},
],
},
{
desc => 'set, unset, undef',
groupby => [qw(maybeunset)],
want => [[$h1], [$h2], [$h3]],
want => [
{cfg => {maybeunset => 'unique'}, hosts => [$h1]},
{cfg => {maybeunset => undef}, hosts => [$h2]},
{cfg => {}, hosts => [$h3]},
],
},
{
desc => 'missing attribute',
groupby => [qw(thisdoesnotexist)],
want => [[$h1, $h2, $h3]],
want => [{cfg => {}, hosts => [$h1, $h2, $h3]}],
},
);
for my $tc (@test_cases) {
my %got = ddclient::group_hosts_by([$h1, $h2, $h3], $tc->{groupby});
# %got is used as a set of sets. Sort everything to make comparison easier.
my @got = sort({
for (my $i = 0; $i < @$a && $i < @$b; ++$i) {
my $x = $a->[$i] cmp $b->[$i];
my @got = ddclient::group_hosts_by([$h1, $h2, $h3], @{$tc->{groupby}});
# @got is used as a set of sets. Sort everything to make comparison easier.
$_->{hosts} = [sort(@{$_->{hosts}})] for @got;
@got = sort({
for (my $i = 0; $i < @{$a->{hosts}} && $i < @{$b->{hosts}}; ++$i) {
my $x = $a->{hosts}[$i] cmp $b->{hosts}[$i];
return $x if $x != 0;
}
return @$a <=> @$b;
} map({ [sort(@$_)]; } values(%got)));
return @{$a->{hosts}} <=> @{$b->{hosts}};
} @got);
is_deeply(\@got, $tc->{want}, $tc->{desc})
or diag(Data::Dumper->Dump([\@got, $tc->{want}], [qw(got want)]));
or diag(Data::Dumper->new([\@got, $tc->{want}],
[qw(got want)])->Sortkeys(1)->Useqq(1)->Dump());
}
done_testing();