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.
[#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
### Breaking changes

View file

@ -4097,54 +4097,55 @@ EoEXAMPLE
######################################################################
sub nic_dnsexit2_update {
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 (@_) {
$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 {
my ($h) = @_;
local $_l = pushlogctx($h);
my $name = $h;
# 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
# remove the zone even if it means $name becomes the empty string.
my $zone = opt('zone', $h);
if ($name =~ s/(?:^|\.)\Q$zone\E$//) {
# The zone was successfully trimmed from $name.
} else {
fatal("hostname does not end with the zone: " . opt('zone', $h));
}
# The IPv4 and IPv6 addresses must be updated together in a single API call.
my %ips;
sub dnsexit2_update_hostgroup {
my ($group) = @_;
return unless @{$group->{hosts}} > 0;
local $_l = pushlogctx(join(', ', @{$group->{hosts}}));
my %hostips;
my @updates;
for my $ipv ('4', '6') {
my $ip = delete($config{$h}{"wantipv$ipv"}) or next;
$ips{$ipv} = $ip;
info("updating IPv$ipv address to $ip");
$recap{$h}{"status-ipv$ipv"} = 'failed';
push(@updates, {
name => $name,
type => ($ipv eq '6') ? 'AAAA' : 'A',
content => $ip,
ttl => opt('ttl', $h),
});
};
my $url = opt('server', $h) . opt('path', $h);
for my $h (@{$group->{hosts}}) {
local $_l = pushlogctx($h) if @{$group->{hosts}} > 1;
my $name = $h;
# 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
# remove the zone even if it means $name becomes the empty string.
if ($name =~ s/(?:^|\.)\Q$group->{cfg}{'zone'}\E$//) {
# The zone was successfully trimmed from $name.
} else {
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.
for my $ipv ('4', '6') {
my $ip = delete($config{$h}{"wantipv$ipv"}) or next;
$hostips{$h}{$ipv} = $ip;
info("updating IPv$ipv address to $ip");
$recap{$h}{"status-ipv$ipv"} = 'failed';
push(@updates, {
name => $name,
type => ($ipv eq '6') ? 'AAAA' : 'A',
content => $ip,
ttl => opt('ttl', $h),
});
}
}
return unless @updates > 0;
my $reply = geturl(
proxy => opt('proxy'),
url => $url,
url => $group->{cfg}{'server'} . $group->{cfg}{'path'},
headers => [
'Content-Type: application/json',
'Accept: application/json',
],
method => 'POST',
data => encode_json({
apikey => opt('password', $h),
domain => $zone,
apikey => $group->{cfg}{'password'},
domain => $group->{cfg}{'zone'},
update => \@updates,
}),
);
@ -4191,12 +4192,15 @@ sub dnsexit2_update_host {
return;
}
success($message);
$recap{$h}{'mtime'} = $now;
keys(%ips); # Reset internal iterator.
while (my ($ipv, $ip) = each(%ips)) {
$recap{$h}{"ipv$ipv"} = $ip;
$recap{$h}{"status-ipv$ipv"} = 'good';
success("updated IPv$ipv address to $ip");
keys(%hostips); # Reset internal iterator.
while (my ($h, $ips) = each(%hostips)) {
$recap{$h}{'mtime'} = $now;
keys(%$ips); # Reset internal iterator.
while (my ($ipv, $ip) = each(%$ips)) {
$recap{$h}{"ipv$ipv"} = $ip;
$recap{$h}{"status-ipv$ipv"} = 'good';
success("updated IPv$ipv address to $ip");
}
}
}

View file

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