dnsexit2: Update multiple hosts at a time when possible

This commit is contained in:
Richard Hansen 2024-05-29 16:12:30 -04:00
parent 3b10e37607
commit 63bf3512a4
3 changed files with 49 additions and 46 deletions

View file

@ -16,6 +16,11 @@ repository history](https://github.com/ddclient/ddclient/commits/main).
special characters are preserved literally. special characters are preserved literally.
[#766](https://github.com/ddclient/ddclient/pull/766) [#766](https://github.com/ddclient/ddclient/pull/766)
### New features
* `dnsexit2`: Multiple hosts are updated in a single API call when possible.
[#684](https://github.com/ddclient/ddclient/pull/684)
## 2025-01-07 v4.0.0-rc.2 ## 2025-01-07 v4.0.0-rc.2
### Breaking changes ### Breaking changes

View file

@ -4097,33 +4097,33 @@ EoEXAMPLE
###################################################################### ######################################################################
sub nic_dnsexit2_update { sub nic_dnsexit2_update {
my $self = shift; my $self = shift;
# The DNSExit API does not support updating hosts with different zones at the same time,
# handling update per host.
for my $h (@_) { for my $h (@_) {
$config{$h}{'zone'} = $h if !defined(opt('zone', $h)); $config{$h}{'zone'} = $h if !defined(opt('zone', $h));
dnsexit2_update_host($h);
} }
dnsexit2_update_hostgroup($_) for group_hosts_by(\@_, qw(password path server ssl zone));
} }
sub dnsexit2_update_host { sub dnsexit2_update_hostgroup {
my ($h) = @_; my ($group) = @_;
local $_l = pushlogctx($h); return unless @{$group->{hosts}} > 0;
local $_l = pushlogctx(join(', ', @{$group->{hosts}}));
my %hostips;
my @updates;
for my $h (@{$group->{hosts}}) {
local $_l = pushlogctx($h) if @{$group->{hosts}} > 1;
my $name = $h; my $name = $h;
# Remove the zone suffix from $name. If the zone eq $name, $name can be left alone or # Remove the zone suffix from $name. If the zone eq $name, $name can be left alone or
# set to the empty string; both have identical semantics. For consistency, always # set to the empty string; both have identical semantics. For consistency, always
# remove the zone even if it means $name becomes the empty string. # remove the zone even if it means $name becomes the empty string.
my $zone = opt('zone', $h); if ($name =~ s/(?:^|\.)\Q$group->{cfg}{'zone'}\E$//) {
if ($name =~ s/(?:^|\.)\Q$zone\E$//) {
# The zone was successfully trimmed from $name. # The zone was successfully trimmed from $name.
} else { } else {
fatal("hostname does not end with the zone: " . opt('zone', $h)); fatal("hostname does not end with the zone: $group->{cfg}{'zone'}");
} }
# The IPv4 and IPv6 addresses must be updated together in a single API call. # The IPv4 and IPv6 addresses must be updated together in a single API call.
my %ips;
my @updates;
for my $ipv ('4', '6') { for my $ipv ('4', '6') {
my $ip = delete($config{$h}{"wantipv$ipv"}) or next; my $ip = delete($config{$h}{"wantipv$ipv"}) or next;
$ips{$ipv} = $ip; $hostips{$h}{$ipv} = $ip;
info("updating IPv$ipv address to $ip"); info("updating IPv$ipv address to $ip");
$recap{$h}{"status-ipv$ipv"} = 'failed'; $recap{$h}{"status-ipv$ipv"} = 'failed';
push(@updates, { push(@updates, {
@ -4132,19 +4132,20 @@ sub dnsexit2_update_host {
content => $ip, content => $ip,
ttl => opt('ttl', $h), ttl => opt('ttl', $h),
}); });
}; }
my $url = opt('server', $h) . opt('path', $h); }
return unless @updates > 0;
my $reply = geturl( my $reply = geturl(
proxy => opt('proxy'), proxy => opt('proxy'),
url => $url, url => $group->{cfg}{'server'} . $group->{cfg}{'path'},
headers => [ headers => [
'Content-Type: application/json', 'Content-Type: application/json',
'Accept: application/json', 'Accept: application/json',
], ],
method => 'POST', method => 'POST',
data => encode_json({ data => encode_json({
apikey => opt('password', $h), apikey => $group->{cfg}{'password'},
domain => $zone, domain => $group->{cfg}{'zone'},
update => \@updates, update => \@updates,
}), }),
); );
@ -4191,13 +4192,16 @@ sub dnsexit2_update_host {
return; return;
} }
success($message); success($message);
keys(%hostips); # Reset internal iterator.
while (my ($h, $ips) = each(%hostips)) {
$recap{$h}{'mtime'} = $now; $recap{$h}{'mtime'} = $now;
keys(%ips); # Reset internal iterator. keys(%$ips); # Reset internal iterator.
while (my ($ipv, $ip) = each(%ips)) { while (my ($ipv, $ip) = each(%$ips)) {
$recap{$h}{"ipv$ipv"} = $ip; $recap{$h}{"ipv$ipv"} = $ip;
$recap{$h}{"status-ipv$ipv"} = 'good'; $recap{$h}{"status-ipv$ipv"} = 'good';
success("updated IPv$ipv address to $ip"); success("updated IPv$ipv address to $ip");
} }
}
} }
###################################################################### ######################################################################

View file

@ -165,12 +165,6 @@ my @test_cases = (
ttl => 5, ttl => 5,
type => 'A', type => 'A',
}, },
],
},
{
apikey => 'key',
domain => 'example.com',
update => [
{ {
content => '2001:db8::1', content => '2001:db8::1',
name => 'host2', name => 'host2',