Write update status to %recap, not %config

Status is not configuration so it doesn't belong in `%config`.

`wantip`, `wantipv4`, and `wantipv6` are still passed along in
`%config` because `group_hosts_by` needs access to them like other
settings.
This commit is contained in:
Richard Hansen 2024-08-25 00:31:42 -04:00
parent 0348ded46b
commit 268369a05e
6 changed files with 254 additions and 451 deletions

View file

@ -100,6 +100,8 @@ repository history](https://github.com/ddclient/ddclient/commits/master).
### Bug fixes ### Bug fixes
* Fixed numerous bugs in cache file (recap) handling.
[#740](https://github.com/ddclient/ddclient/pull/740)
* Fixed numerous bugs in command-line option and configuration file * Fixed numerous bugs in command-line option and configuration file
processing. [#733](https://github.com/ddclient/ddclient/pull/733) processing. [#733](https://github.com/ddclient/ddclient/pull/733)
* `noip`: Fixed failure to honor IP discovery settings in some circumstances. * `noip`: Fixed failure to honor IP discovery settings in some circumstances.

View file

@ -145,15 +145,10 @@ our %config;
# host's "recap variables" -- the host's protocol's variables with a truthy `recap` property -- are # host's "recap variables" -- the host's protocol's variables with a truthy `recap` property -- are
# included. # included.
# #
# `%config` is the source of truth for recap variable values. The recap variable values in # `%recap` is independent of `%config`, although they both share the same set of variable
# `%config` are initialized with values from the cache file (via `%recap`). After initialization, # declarations. There are two classes of recap variables:
# any change to a recap variable's value is made to `%config` and later copied to `%recap` before
# being written to the cache file. Changes made directly to `%recap` are erroneous because they
# will be overwritten by the value in `%config`.
#
# There are two classes of recap variables:
# * "Status" variables: These track update success/failure, the IP address of the last successful # * "Status" variables: These track update success/failure, the IP address of the last successful
# update, etc. These do not hold configuration data. These should always be kept in sync with # update, etc. These do not hold configuration data; they are unrelated to any entries in
# `%config`. # `%config`.
# * "Configuration change detection" variables: These are used to force an update if the value in # * "Configuration change detection" variables: These are used to force an update if the value in
# the same-named entry in `%config` has changed since the previous update attempt. The value # the same-named entry in `%config` has changed since the previous update attempt. The value
@ -654,20 +649,13 @@ our %variables = (
'min-error-interval' => setv(T_DELAY, 0, 0, interval('5m'), 0), 'min-error-interval' => setv(T_DELAY, 0, 0, interval('5m'), 0),
# The desired IP address (IPv4 or IPv6, but almost always IPv4) that should be saved at the # The desired IP address (IPv4 or IPv6, but almost always IPv4) that should be saved at the
# DDNS service. # DDNS service (when `use=ip`).
# TODO: Legacy protocol implementations write the IP address most recently saved at the
# DDNS service to this variable so that it can be saved in the recap (as `ipv4` or `ipv6`).
# Update the legacy implementations to use `ipv4` or `ipv6` instead, though see the TODO
# for those variables.
'ip' => setv(T_IP, 0, 0, undef, undef), 'ip' => setv(T_IP, 0, 0, undef, undef),
# As a recap value, this is the IPv4 address most recently saved at the DDNS service. As a # As a recap value, this is the IPv4 address most recently saved at the DDNS service. As a
# setting, this is the desired IPv4 address that should be saved at the DDNS service. # setting, this is the desired IPv4 address that should be saved at the DDNS service.
# TODO: The use of `ipv4` as a recap status variable is supposed to be independent of the # TODO: The use of `ipv4` as a recap status variable is independent of the use of `ipv4` as
# use of `ipv4` as a configuration setting. Unfortunately, because the `%config` value is # a configuration setting. Rename the `%recap` status variable to something like
# synced to `%recap`, the two uses conflict which causes the bug "skipped: IP address was # `saved-ipv4` to avoid confusion with the `%config` setting variable.
# already set to a.b.c.d" when the IP was never set to a.b.c.d. Rename the `%recap` status
# variable to something like `saved-ipv4` to avoid the collision (and confusion) with the
# `%config` setting variable.
'ipv4' => setv(T_IPV4, 0, 1, undef, undef), 'ipv4' => setv(T_IPV4, 0, 1, undef, undef),
# As `ipv4`, but for an IPv6 address. # As `ipv4`, but for an IPv6 address.
'ipv6' => setv(T_IPV6, 0, 1, undef, undef), 'ipv6' => setv(T_IPV6, 0, 1, undef, undef),
@ -711,7 +699,7 @@ our %variables = (
); );
# Converts a legacy protocol update method to behave like a modern implementation by moving # Converts a legacy protocol update method to behave like a modern implementation by moving
# `$config{$h}{status}` and `$config{$h}{ip}` to the version-specific modern equivalents. # `$recap{$h}{status}` and `$recap{$h}{ip}` to the version-specific modern equivalents.
sub adapt_legacy_update { sub adapt_legacy_update {
my ($update) = @_; my ($update) = @_;
return sub { return sub {
@ -720,7 +708,7 @@ sub adapt_legacy_update {
for my $h (@hosts) { for my $h (@hosts) {
$ipv{$h} = defined($config{$h}{'wantipv4'}) ? '4' : '6'; $ipv{$h} = defined($config{$h}{'wantipv4'}) ? '4' : '6';
$config{$h}{'wantip'} = delete($config{$h}{"wantipv$ipv{$h}"}); $config{$h}{'wantip'} = delete($config{$h}{"wantipv$ipv{$h}"});
delete($config{$h}{$_}) for qw(ip status); delete($recap{$h}{$_}) for qw(ip status);
} }
$update->(@hosts); $update->(@hosts);
for my $h (@hosts) { for my $h (@hosts) {
@ -728,29 +716,8 @@ sub adapt_legacy_update {
delete($config{$h}{'wantip'}); delete($config{$h}{'wantip'});
debug( debug(
"legacy protocol; moving 'status' to 'status-ipv$ipv{$h}', 'ip' to 'ipv$ipv{$h}'"); "legacy protocol; moving 'status' to 'status-ipv$ipv{$h}', 'ip' to 'ipv$ipv{$h}'");
$config{$h}{"status-ipv$ipv{$h}"} = delete($config{$h}{'status'}); $recap{$h}{"status-ipv$ipv{$h}"} = delete($recap{$h}{'status'});
# TODO: Currently $config{$h}{'ip'} is used for two distinct purposes: it holds the $recap{$h}{"ipv$ipv{$h}"} = delete($recap{$h}{'ip'});
# value of the --ip option, and it is updated by legacy protocols to hold the new IP
# address after an update. Fortunately, the --ip option is not used very often, and if
# it is, the values for the two use cases are usually (but not always) the same. This
# boolean is an imperfect attempt to identify whether 'ip' is being used for the --ip
# option, to avoid breaking some user's configuration. Protocols should be updated to
# not set $config{$h}{'ip'} because %config is for configuration, not update status
# (same goes for 'status', 'mtime', etc.).
my $ip_option = opt('use', $h) eq 'ip' || opt('usev6', $h) eq 'ip';
my $ip = $config{$h}{'ip'};
delete($config{$h}{'ip'}) if !$ip_option;
# TODO: See above comment for $ip_option. This is the same situation, but for 'ipv4'
# and 'ipv6'.
my $vip_option = opt("usev$ipv{$h}", $h) eq "ipv$ipv{$h}";
if ($vip_option && defined(my $vip = $config{$h}{"ipv$ipv{$h}"})) {
if (!defined($ip) || $ip ne $vip) {
my $fip = defined($ip) ? "'$ip'" : '<undefined>';
debug("unable to set 'ipv$ipv{$h}' to $fip; already set to '$vip'");
}
next;
}
$config{$h}{"ipv$ipv{$h}"} = $ip;
} }
}; };
} }
@ -1502,29 +1469,39 @@ sub update_nics {
$0 = sprintf("%s - updating %s", $program, join(',', @hosts)); $0 = sprintf("%s - updating %s", $program, join(',', @hosts));
local $_l = pushlogctx($p); local $_l = pushlogctx($p);
for my $h (@hosts) { for my $h (@hosts) {
$config{$h}{'atime'} = $now; $recap{$h}{'atime'} = $now;
delete($config{$h}{$_}) for qw(status-ipv4 status-ipv6 wtime delete($recap{$h}{$_}) for qw(status-ipv4 status-ipv6 wtime
warned-min-interval warned-min-error-interval); warned-min-interval warned-min-error-interval);
# Update configuration change detection variables in `%recap` with the latest
# values in `%config`. Notes about why this is done here:
# * The vars must not be updated if the host is not being updated because change
# detection is defined relative to the previous update attempt. In particular,
# these can't be updated when the protocol's `force_update` method is called
# because that method is not always called before an update is attempted.
# * The vars must be updated after the `force_update` method would be called so
# that `force_update` can check whether any settings have changed since the
# last time an update was attempted.
# * The vars are updated before the protocol's `update` method is called so that
# `update` sees consistent values between `%recap` and `%config`. This reduces
# the impact of Hyrum's Law; if a protocol needs a variable to be updated after
# the `update` method is called then that behavior should be made explicit.
my $vars = $protocols{opt('protocol', $h)}{variables};
for my $v (qw(static wildcard mx backupmx)) {
next if !$vars->{$v} || !$vars->{$v}{recap};
if (defined(my $val = opt($v, $h))) {
$recap{$h}{$v} = $val;
} else {
# Entries in `%recap` with `undef` values are deleted to avoid needing to
# figure out how to represent `undef` in the cache file and to simplify
# testing.
delete($recap{$h}{$v});
}
}
} }
&$update(@hosts); &$update(@hosts);
for my $h (@hosts) { for my $h (@hosts) {
delete($config{$h}{$_}) for qw(wantipv4 wantipv6); delete($config{$h}{$_}) for qw(wantipv4 wantipv6);
} }
for my $h (@hosts) {
# Update `%recap` with the latest values in `%config`. This is done after the
# protocol's update method returns in case that method mutates any of the `%config`
# values or needs access to the previous iteration's `%recap` values. Entries in
# `%recap` with `undef` values are deleted to avoid needing to figure out how to
# represent `undef` in the cache file and to simplify testing. Also, entries for
# non-recap variables (which can happen after changing a host's protocol or after
# switching to a different version of ddclient) are deleted.
my $vars = $protocols{$p}{variables};
$recap{$h} = {};
for my $v (keys(%$vars)) {
next if !$vars->{$v}{recap} || !defined(opt($v, $h));
$recap{$h}{$v} = opt($v, $h);
}
}
runpostscript(join ' ', keys %ipsv4, keys %ipsv6); runpostscript(join ' ', keys %ipsv4, keys %ipsv6);
} }
} }
@ -1614,7 +1591,6 @@ sub read_recap {
%opt = (); %opt = ();
$saved_recap = _read_config(\%recap, $globals, "##\\s*$program-$version\\s*", $file); $saved_recap = _read_config(\%recap, $globals, "##\\s*$program-$version\\s*", $file);
%opt = %saved; %opt = %saved;
$recap{$_} //= {} for keys(%config);
for my $h (keys(%recap)) { for my $h (keys(%recap)) {
if (!exists($config{$h})) { if (!exists($config{$h})) {
delete($recap{$h}); delete($recap{$h});
@ -1624,26 +1600,6 @@ sub read_recap {
for my $v (keys(%{$recap{$h}})) { for my $v (keys(%{$recap{$h}})) {
delete($recap{$h}{$v}) if !$vars->{$v} || !$vars->{$v}{recap}; delete($recap{$h}{$v}) if !$vars->{$v} || !$vars->{$v}{recap};
} }
# Only status variables are copied from `%recap` into `%config` because the recap should
# not change the configuration. See the documenting comments for `%recap`.
# TODO: Hard-coding this list impairs readability and maintainability. In particular,
# different pieces of code need to know the list of status variables, and keeping them all
# in sync is error-prone (DRY). Also, different protocols might need different status
# variables.
# TODO: None of the recap variables should be copied into `%config` because `%config` is
# not the semantically appropriate place to record update status. Code that currently
# reads/sets `$config{$h}{'status-ipv4'}` (and friends) should instead access
# `$recap{$h}{'status-ipv4'}` directly.
for my $v (qw(atime mtime wtime ipv4 ipv6 status-ipv4 status-ipv6
warned-min-interval warned-min-error-interval)) {
next if !$vars->{$v} || !$vars->{$v}{recap};
if (defined($recap{$h}{$v})) {
$config{$h}{$v} = $recap{$h}{$v};
} else {
delete($config{$h}{$v});
}
}
delete($recap{$h}) if !%{$recap{$h}};
} }
} }
###################################################################### ######################################################################
@ -3519,15 +3475,14 @@ sub nic_updateable {
if (($recap{$host}{'status-ipv4'} // '') eq 'good' && if (($recap{$host}{'status-ipv4'} // '') eq 'good' &&
!interval_expired($host, 'mtime', 'min-interval')) { !interval_expired($host, 'mtime', 'min-interval')) {
warning("skipped update from $previpv4 to $ipv4 because it has been less than $prettyi{'min-interval'} since the previous update (on $prettyt{'mtime'})") warning("skipped update from $previpv4 to $ipv4 because it has been less than $prettyi{'min-interval'} since the previous update (on $prettyt{'mtime'})")
if opt('verbose') || !($config{$host}{'warned-min-interval'} // 0); if opt('verbose') || !($recap{$host}{'warned-min-interval'} // 0);
$config{$host}{'warned-min-interval'} = $now;
$recap{$host}{'warned-min-interval'} = $now; $recap{$host}{'warned-min-interval'} = $now;
} elsif (($recap{$host}{'status-ipv4'} // '') ne 'good' && } elsif (($recap{$host}{'status-ipv4'} // '') ne 'good' &&
!interval_expired($host, 'atime', 'min-error-interval')) { !interval_expired($host, 'atime', 'min-error-interval')) {
if (opt('verbose') || (!$config{$host}{'warned-min-error-interval'} && if (opt('verbose') || (!$recap{$host}{'warned-min-error-interval'} &&
($warned_ipv4{$host} // 0) < $inv_ip_warn_count)) { ($warned_ipv4{$host} // 0) < $inv_ip_warn_count)) {
warning("skipped update from $previpv4 to $ipv4 because it has been less than $prettyi{'min-error-interval'} since the previous update attempt (on $prettyt{'atime'}), which failed"); warning("skipped update from $previpv4 to $ipv4 because it has been less than $prettyi{'min-error-interval'} since the previous update attempt (on $prettyt{'atime'}), which failed");
if (!$ipv4 && !opt('verbose')) { if (!$ipv4 && !opt('verbose')) {
@ -3537,7 +3492,6 @@ sub nic_updateable {
} }
} }
$config{$host}{'warned-min-error-interval'} = $now;
$recap{$host}{'warned-min-error-interval'} = $now; $recap{$host}{'warned-min-error-interval'} = $now;
} else { } else {
@ -3548,15 +3502,14 @@ sub nic_updateable {
if (($recap{$host}{'status-ipv6'} // '') eq 'good' && if (($recap{$host}{'status-ipv6'} // '') eq 'good' &&
!interval_expired($host, 'mtime', 'min-interval')) { !interval_expired($host, 'mtime', 'min-interval')) {
warning("skipped update from $previpv6 to $ipv6 because it has been less than $prettyi{'min-interval'} since the previous update (on $prettyt{'mtime'})") warning("skipped update from $previpv6 to $ipv6 because it has been less than $prettyi{'min-interval'} since the previous update (on $prettyt{'mtime'})")
if opt('verbose') || !($config{$host}{'warned-min-interval'} // 0); if opt('verbose') || !($recap{$host}{'warned-min-interval'} // 0);
$config{$host}{'warned-min-interval'} = $now;
$recap{$host}{'warned-min-interval'} = $now; $recap{$host}{'warned-min-interval'} = $now;
} elsif (($recap{$host}{'status-ipv6'} // '') ne 'good' && } elsif (($recap{$host}{'status-ipv6'} // '') ne 'good' &&
!interval_expired($host, 'atime', 'min-error-interval')) { !interval_expired($host, 'atime', 'min-error-interval')) {
if (opt('verbose') || (!$config{$host}{'warned-min-error-interval'} && if (opt('verbose') || (!$recap{$host}{'warned-min-error-interval'} &&
($warned_ipv6{$host} // 0) < $inv_ip_warn_count)) { ($warned_ipv6{$host} // 0) < $inv_ip_warn_count)) {
warning("skipped update from $previpv6 to $ipv6 because it has been less than $prettyi{'min-error-interval'} since the previous update attempt (on $prettyt{'atime'}, which failed"); warning("skipped update from $previpv6 to $ipv6 because it has been less than $prettyi{'min-error-interval'} since the previous update attempt (on $prettyt{'atime'}, which failed");
if (!$ipv6 && !opt('verbose')) { if (!$ipv6 && !opt('verbose')) {
@ -3566,7 +3519,6 @@ sub nic_updateable {
} }
} }
$config{$host}{'warned-min-error-interval'} = $now;
$recap{$host}{'warned-min-error-interval'} = $now; $recap{$host}{'warned-min-error-interval'} = $now;
} else { } else {
@ -3712,16 +3664,16 @@ sub nic_dyndns1_update {
} }
if ($return_code ne 'NOERROR' || $error_code ne 'NOERROR' || !$title) { if ($return_code ne 'NOERROR' || $error_code ne 'NOERROR' || !$title) {
$config{$h}{'status'} = 'failed'; $recap{$h}{'status'} = 'failed';
$title = 'incomplete response from ' . opt('server', $h) unless $title; $title = 'incomplete response from ' . opt('server', $h) unless $title;
warning("SENT: %s", $url) unless opt('verbose'); warning("SENT: %s", $url) unless opt('verbose');
warning("REPLIED: %s", $reply); warning("REPLIED: %s", $reply);
failed($title); failed($title);
next; next;
} }
$config{$h}{'ip'} = $ip; $recap{$h}{'ip'} = $ip;
$config{$h}{'mtime'} = $now; $recap{$h}{'mtime'} = $now;
$config{$h}{'status'} = 'good'; $recap{$h}{'status'} = 'good';
success("$return_code: IP address set to $ip ($title)"); success("$return_code: IP address set to $ip ($title)");
} }
} }
@ -3868,8 +3820,8 @@ sub nic_dyndns2_update {
warning("$status: $errors{$status}"); warning("$status: $errors{$status}");
$status = 'good'; $status = 'good';
} }
$config{$h}{'status-ipv4'} = $status if $ipv4; $recap{$h}{'status-ipv4'} = $status if $ipv4;
$config{$h}{'status-ipv6'} = $status if $ipv6; $recap{$h}{'status-ipv6'} = $status if $ipv6;
if ($status ne 'good') { if ($status ne 'good') {
if (exists($errors{$status})) { if (exists($errors{$status})) {
failed("$status: $errors{$status}"); failed("$status: $errors{$status}");
@ -3887,9 +3839,9 @@ sub nic_dyndns2_update {
# some services do not return the IP; and (2) comparison is brittle (e.g., # some services do not return the IP; and (2) comparison is brittle (e.g.,
# 192.000.002.001 vs. 192.0.2.1) and false errors could cause high load on the service # 192.000.002.001 vs. 192.0.2.1) and false errors could cause high load on the service
# (an update attempt every min-error-interval instead of every max-interval). # (an update attempt every min-error-interval instead of every max-interval).
$config{$h}{'ipv4'} = $ipv4 if $ipv4; $recap{$h}{'ipv4'} = $ipv4 if $ipv4;
$config{$h}{'ipv6'} = $ipv6 if $ipv6; $recap{$h}{'ipv6'} = $ipv6 if $ipv6;
$config{$h}{'mtime'} = $now; $recap{$h}{'mtime'} = $now;
success("IPv4 address set to $ipv4") if $ipv4; success("IPv4 address set to $ipv4") if $ipv4;
success("IPv6 address set to $ipv6") if $ipv6; success("IPv6 address set to $ipv6") if $ipv6;
} }
@ -3970,7 +3922,7 @@ sub dnsexit2_update_host {
my $ip = delete($config{$h}{"wantipv$ipv"}) or next; my $ip = delete($config{$h}{"wantipv$ipv"}) or next;
$ips{$ipv} = $ip; $ips{$ipv} = $ip;
info("updating IPv$ipv address to $ip"); info("updating IPv$ipv address to $ip");
$config{$h}{"status-ipv$ipv"} = 'failed'; $recap{$h}{"status-ipv$ipv"} = 'failed';
push(@updates, { push(@updates, {
name => $name, name => $name,
type => ($ipv eq '6') ? 'AAAA' : 'A', type => ($ipv eq '6') ? 'AAAA' : 'A',
@ -4036,11 +3988,11 @@ sub dnsexit2_update_host {
return; return;
} }
success($message); success($message);
$config{$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)) {
$config{$h}{"ipv$ipv"} = $ip; $recap{$h}{"ipv$ipv"} = $ip;
$config{$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");
} }
} }
@ -4100,27 +4052,27 @@ sub nic_noip_update {
for my $ip (split_by_comma($returnedips)) { for my $ip (split_by_comma($returnedips)) {
next if (!$ip); next if (!$ip);
my $ipv = ($ip eq ($ipv6 // '')) ? '6' : '4'; my $ipv = ($ip eq ($ipv6 // '')) ? '6' : '4';
$config{$h}{"status-ipv$ipv"} = $status; $recap{$h}{"status-ipv$ipv"} = $status;
} }
if ($status eq 'good') { if ($status eq 'good') {
$config{$h}{'mtime'} = $now; $recap{$h}{'mtime'} = $now;
for my $ip (split_by_comma($returnedips)) { for my $ip (split_by_comma($returnedips)) {
next if (!$ip); next if (!$ip);
my $ipv = ($ip eq ($ipv6 // '')) ? '6' : '4'; my $ipv = ($ip eq ($ipv6 // '')) ? '6' : '4';
$config{$h}{"ipv$ipv"} = $ip; $recap{$h}{"ipv$ipv"} = $ip;
success("$status: IPv$ipv address set to $ip"); success("$status: IPv$ipv address set to $ip");
} }
} elsif (exists $errors{$status}) { } elsif (exists $errors{$status}) {
if ($status eq 'nochg') { if ($status eq 'nochg') {
warning("$status: $errors{$status}"); warning("$status: $errors{$status}");
$config{$h}{'mtime'} = $now; $recap{$h}{'mtime'} = $now;
for my $ip (split_by_comma($returnedips)) { for my $ip (split_by_comma($returnedips)) {
next if (!$ip); next if (!$ip);
my $ipv = ($ip eq ($ipv6 // '')) ? '6' : '4'; my $ipv = ($ip eq ($ipv6 // '')) ? '6' : '4';
$config{$h}{"ipv$ipv"} = $ip; $recap{$h}{"ipv$ipv"} = $ip;
$config{$h}{"status-ipv$ipv"} = 'good'; $recap{$h}{"status-ipv$ipv"} = 'good';
} }
} else { } else {
failed("$status: $errors{$status}"); failed("$status: $errors{$status}");
@ -4135,7 +4087,7 @@ sub nic_noip_update {
($scale, $units) = (60*60, 'hours') if $units eq 'h'; ($scale, $units) = (60*60, 'hours') if $units eq 'h';
$sec = $wait * $scale; $sec = $wait * $scale;
$config{$h}{'wtime'} = $now + $sec; $recap{$h}{'wtime'} = $now + $sec;
warning("$status: wait $wait $units before further updates"); warning("$status: wait $wait $units before further updates");
} else { } else {
@ -4236,13 +4188,13 @@ sub nic_dslreports1_update {
} }
if ($return_code !~ /NOERROR/) { if ($return_code !~ /NOERROR/) {
$config{$h}{'status'} = 'failed'; $recap{$h}{'status'} = 'failed';
failed($reply); failed($reply);
next; next;
} }
$config{$h}{'ip'} = $ip; $recap{$h}{'ip'} = $ip;
$config{$h}{'mtime'} = $now; $recap{$h}{'mtime'} = $now;
$config{$h}{'status'} = 'good'; $recap{$h}{'status'} = 'good';
success("$return_code: IP address set to $ip"); success("$return_code: IP address set to $ip");
} }
} }
@ -4291,9 +4243,9 @@ sub nic_domeneshop_update {
password => opt('password', $h), password => opt('password', $h),
); );
next if !header_ok($reply); next if !header_ok($reply);
$config{$h}{"ipv$ipv"} = $ip; $recap{$h}{"ipv$ipv"} = $ip;
$config{$h}{'mtime'} = $now; $recap{$h}{'mtime'} = $now;
$config{$h}{"status-ipv$ipv"} = 'good'; $recap{$h}{"status-ipv$ipv"} = 'good';
success("IPv$ipv address set to $ip"); success("IPv$ipv address set to $ip");
} }
} }
@ -4381,14 +4333,14 @@ sub nic_zoneedit1_update {
$status_ip = $var{'IP'} if exists $var{'IP'}; $status_ip = $var{'IP'} if exists $var{'IP'};
if ($status eq 'SUCCESS' || ($status eq 'ERROR' && $var{'CODE'} eq '707')) { if ($status eq 'SUCCESS' || ($status eq 'ERROR' && $var{'CODE'} eq '707')) {
$config{$h}{'ip'} = $status_ip; $recap{$h}{'ip'} = $status_ip;
$config{$h}{'mtime'} = $now; $recap{$h}{'mtime'} = $now;
$config{$h}{'status'} = 'good'; $recap{$h}{'status'} = 'good';
success("IP address set to $ip ($status_code: $status_text)"); success("IP address set to $ip ($status_code: $status_text)");
} else { } else {
$config{$h}{'status'} = 'failed'; $recap{$h}{'status'} = 'failed';
failed("$status_code: $status_text"); failed("$status_code: $status_text");
} }
shift @hosts; shift @hosts;
@ -4491,7 +4443,7 @@ sub nic_easydns_update {
# values are considered to be failures and will result in frequent retries (every # values are considered to be failures and will result in frequent retries (every
# min-error-interval, which defaults to 5m). # min-error-interval, which defaults to 5m).
$status = 'good' if ($status // '') =~ qr/^NOERROR|OK$/; $status = 'good' if ($status // '') =~ qr/^NOERROR|OK$/;
$config{$h}{"status-ipv$ipv"} = $status; $recap{$h}{"status-ipv$ipv"} = $status;
if ($status ne 'good') { if ($status ne 'good') {
if (exists $errors{$status}) { if (exists $errors{$status}) {
failed("$status: $errors{$status}"); failed("$status: $errors{$status}");
@ -4500,8 +4452,8 @@ sub nic_easydns_update {
} }
next; next;
} }
$config{$h}{"ipv$ipv"} = $ip; $recap{$h}{"ipv$ipv"} = $ip;
$config{$h}{'mtime'} = $now; $recap{$h}{'mtime'} = $now;
success("IPv$ipv address set to $ip"); success("IPv$ipv address set to $ip");
} }
} }
@ -4568,12 +4520,12 @@ sub nic_namecheap_update {
my @reply = split /\n/, $reply; my @reply = split /\n/, $reply;
if (grep /<ErrCount>0/i, @reply) { if (grep /<ErrCount>0/i, @reply) {
$config{$h}{'ip'} = $ip; $recap{$h}{'ip'} = $ip;
$config{$h}{'mtime'} = $now; $recap{$h}{'mtime'} = $now;
$config{$h}{'status'} = 'good'; $recap{$h}{'status'} = 'good';
success("IP address set to $ip"); success("IP address set to $ip");
} else { } else {
$config{$h}{'status'} = 'failed'; $recap{$h}{'status'} = 'failed';
failed("invalid reply: $reply"); failed("invalid reply: $reply");
} }
} }
@ -4739,7 +4691,7 @@ sub nic_nfsn_update {
if ($h eq $zone) { if ($h eq $zone) {
$name = ''; $name = '';
} elsif ($h !~ /$zone$/) { } elsif ($h !~ /$zone$/) {
$config{$h}{'status'} = 'failed'; $recap{$h}{'status'} = 'failed';
failed("$h is outside zone $zone"); failed("$h is outside zone $zone");
next; next;
} else { } else {
@ -4754,7 +4706,7 @@ sub nic_nfsn_update {
my $list_body = encode_www_form_urlencoded({name => $name, type => 'A'}); my $list_body = encode_www_form_urlencoded({name => $name, type => 'A'});
my $list_resp = nic_nfsn_make_request($h, $list_path, 'POST', $list_body); my $list_resp = nic_nfsn_make_request($h, $list_path, 'POST', $list_body);
if (!header_ok($list_resp)) { if (!header_ok($list_resp)) {
$config{$h}{'status'} = 'failed'; $recap{$h}{'status'} = 'failed';
nic_nfsn_handle_error($list_resp, $h); nic_nfsn_handle_error($list_resp, $h);
next; next;
} }
@ -4762,7 +4714,7 @@ sub nic_nfsn_update {
$list_resp =~ s/^.*?\n\n//s; # Strip header $list_resp =~ s/^.*?\n\n//s; # Strip header
my $list = eval { decode_json($list_resp) }; my $list = eval { decode_json($list_resp) };
if ($@) { if ($@) {
$config{$h}{'status'} = 'failed'; $recap{$h}{'status'} = 'failed';
failed("JSON decoding failure"); failed("JSON decoding failure");
next; next;
} }
@ -4779,7 +4731,7 @@ sub nic_nfsn_update {
my $rm_resp = nic_nfsn_make_request($h, $rm_path, my $rm_resp = nic_nfsn_make_request($h, $rm_path,
'POST', $rm_body); 'POST', $rm_body);
if (!header_ok($rm_resp)) { if (!header_ok($rm_resp)) {
$config{$h}{'status'} = 'failed'; $recap{$h}{'status'} = 'failed';
nic_nfsn_handle_error($rm_resp, $h); nic_nfsn_handle_error($rm_resp, $h);
next; next;
} }
@ -4794,12 +4746,12 @@ sub nic_nfsn_update {
my $add_resp = nic_nfsn_make_request($h, $add_path, 'POST', my $add_resp = nic_nfsn_make_request($h, $add_path, 'POST',
$add_body); $add_body);
if (header_ok($add_resp)) { if (header_ok($add_resp)) {
$config{$h}{'ip'} = $ip; $recap{$h}{'ip'} = $ip;
$config{$h}{'mtime'} = $now; $recap{$h}{'mtime'} = $now;
$config{$h}{'status'} = 'good'; $recap{$h}{'status'} = 'good';
success("IP address set to $ip"); success("IP address set to $ip");
} else { } else {
$config{$h}{'status'} = 'failed'; $recap{$h}{'status'} = 'failed';
nic_nfsn_handle_error($add_resp, $h); nic_nfsn_handle_error($add_resp, $h);
} }
} }
@ -4903,11 +4855,11 @@ sub nic_njalla_update {
} }
} }
if ($status eq 'good') { if ($status eq 'good') {
$config{$h}{'ipv4'} = $ipv4 if $ipv4; $recap{$h}{'ipv4'} = $ipv4 if $ipv4;
$config{$h}{'ipv6'} = $ipv6 if $ipv6; $recap{$h}{'ipv6'} = $ipv6 if $ipv6;
} }
$config{$h}{'status-ipv4'} = $status if $ipv4; $recap{$h}{'status-ipv4'} = $status if $ipv4;
$config{$h}{'status-ipv6'} = $status if $ipv6; $recap{$h}{'status-ipv6'} = $status if $ipv6;
} }
} }
@ -4968,12 +4920,12 @@ sub nic_sitelutions_update {
my @reply = split /\n/, $reply; my @reply = split /\n/, $reply;
if (grep /success/i, @reply) { if (grep /success/i, @reply) {
$config{$h}{'ip'} = $ip; $recap{$h}{'ip'} = $ip;
$config{$h}{'mtime'} = $now; $recap{$h}{'mtime'} = $now;
$config{$h}{'status'} = 'good'; $recap{$h}{'status'} = 'good';
success("IP address set to $ip"); success("IP address set to $ip");
} else { } else {
$config{$h}{'status'} = 'failed'; $recap{$h}{'status'} = 'failed';
warning("SENT: %s", $url) unless opt('verbose'); warning("SENT: %s", $url) unless opt('verbose');
warning("REPLIED: %s", $reply); warning("REPLIED: %s", $reply);
failed("invalid reply"); failed("invalid reply");
@ -5075,8 +5027,8 @@ sub nic_freedns_update {
my $ipv6 = delete $config{$h}{'wantipv6'}; my $ipv6 = delete $config{$h}{'wantipv6'};
if ($record_list_error ne '') { if ($record_list_error ne '') {
$config{$h}{'status-ipv4'} = 'failed' if ($ipv4); $recap{$h}{'status-ipv4'} = 'failed' if ($ipv4);
$config{$h}{'status-ipv6'} = 'failed' if ($ipv6); $recap{$h}{'status-ipv6'} = 'failed' if ($ipv6);
failed($record_list_error); failed($record_list_error);
next; next;
} }
@ -5094,12 +5046,12 @@ sub nic_freedns_update {
} }
info("setting IP address to $ip"); info("setting IP address to $ip");
$config{$h}{"status-ipv$ipv"} = 'failed'; $recap{$h}{"status-ipv$ipv"} = 'failed';
if ($ip eq $rec->[1]) { if ($ip eq $rec->[1]) {
$config{$h}{"ipv$ipv"} = $ip; $recap{$h}{"ipv$ipv"} = $ip;
$config{$h}{'mtime'} = $now; $recap{$h}{'mtime'} = $now;
$config{$h}{"status-ipv$ipv"} = 'good'; $recap{$h}{"status-ipv$ipv"} = 'good';
success("update not necessary, '$type' record already set to $ip") success("update not necessary, '$type' record already set to $ip")
if (!$daemon || opt('verbose')); if (!$daemon || opt('verbose'));
} else { } else {
@ -5113,9 +5065,9 @@ sub nic_freedns_update {
if (header_ok($reply)) { if (header_ok($reply)) {
$reply =~ s/^.*?\n\n//s; # Strip the headers. $reply =~ s/^.*?\n\n//s; # Strip the headers.
if ($reply =~ /Updated.*$h.*to.*$ip/) { if ($reply =~ /Updated.*$h.*to.*$ip/) {
$config{$h}{"ipv$ipv"} = $ip; $recap{$h}{"ipv$ipv"} = $ip;
$config{$h}{'mtime'} = $now; $recap{$h}{'mtime'} = $now;
$config{$h}{"status-ipv$ipv"} = 'good'; $recap{$h}{"status-ipv$ipv"} = 'good';
success("IPv$ipv address set to $ip"); success("IPv$ipv address set to $ip");
} else { } else {
warning("SENT: %s", $url_tmpl) unless opt('verbose'); warning("SENT: %s", $url_tmpl) unless opt('verbose');
@ -5191,8 +5143,8 @@ sub nic_1984_update {
next; next;
} }
$config{$host}{'status'} = 'good'; $recap{$host}{'status'} = 'good';
$config{$host}{'ip'} = $ip; $recap{$host}{'ip'} = $ip;
if ($response->{msg} =~ /unaltered/) { if ($response->{msg} =~ /unaltered/) {
success("skipped: IP was already set to $response->{ip}"); success("skipped: IP was already set to $response->{ip}");
} else { } else {
@ -5259,12 +5211,12 @@ sub nic_changeip_update {
my @reply = split /\n/, $reply; my @reply = split /\n/, $reply;
if (grep /success/i, @reply) { if (grep /success/i, @reply) {
$config{$h}{'ip'} = $ip; $recap{$h}{'ip'} = $ip;
$config{$h}{'mtime'} = $now; $recap{$h}{'mtime'} = $now;
$config{$h}{'status'} = 'good'; $recap{$h}{'status'} = 'good';
success("IP address set to $ip"); success("IP address set to $ip");
} else { } else {
$config{$h}{'status'} = 'failed'; $recap{$h}{'status'} = 'failed';
warning("SENT: %s", $url) unless opt('verbose'); warning("SENT: %s", $url) unless opt('verbose');
warning("REPLIED: %s", $reply); warning("REPLIED: %s", $reply);
failed("invalid reply"); failed("invalid reply");
@ -5380,9 +5332,9 @@ sub nic_godaddy_update {
failed($msg); failed($msg);
next; next;
} }
$config{$h}{"ipv$ipv"} = $ip; $recap{$h}{"ipv$ipv"} = $ip;
$config{$h}{'mtime'} = $now; $recap{$h}{'mtime'} = $now;
$config{$h}{"status-ipv$ipv"} = 'good'; $recap{$h}{"status-ipv$ipv"} = 'good';
success("updated successfully to $ip (status: $code)"); success("updated successfully to $ip (status: $code)");
} }
} }
@ -5444,7 +5396,7 @@ sub nic_henet_update {
my ($line) = split(/\n/, $body, 2); my ($line) = split(/\n/, $body, 2);
my ($status, $returnedip) = split(/ /, lc($line)); my ($status, $returnedip) = split(/ /, lc($line));
$status = 'good' if $status eq 'nochg'; $status = 'good' if $status eq 'nochg';
$config{$h}{"status-ipv$ipv"} = $status; $recap{$h}{"status-ipv$ipv"} = $status;
if ($status ne 'good') { if ($status ne 'good') {
if (exists($errors{$status})) { if (exists($errors{$status})) {
failed("$status: $errors{$status}"); failed("$status: $errors{$status}");
@ -5454,8 +5406,8 @@ sub nic_henet_update {
next; next;
} }
success("$status: IPv$ipv address set to $returnedip"); success("$status: IPv$ipv address set to $returnedip");
$config{$h}{"ipv$ipv"} = $returnedip; $recap{$h}{"ipv$ipv"} = $returnedip;
$config{$h}{'mtime'} = $now; $recap{$h}{'mtime'} = $now;
} }
} }
} }
@ -5524,8 +5476,8 @@ sub nic_mythicdyn_update {
); );
my $ok = header_ok($reply); my $ok = header_ok($reply);
if ($ok) { if ($ok) {
$config{$h}{'mtime'} = $now; $recap{$h}{'mtime'} = $now;
$config{$h}{"status-ipv$mythver"} = "good"; $recap{$h}{"status-ipv$mythver"} = "good";
success("IPv$mythver updated successfully"); success("IPv$mythver updated successfully");
} }
@ -5634,12 +5586,12 @@ EoINSTR4
my $status = pipecmd($command, $instructions); my $status = pipecmd($command, $instructions);
if ($status eq 1) { if ($status eq 1) {
for (@hosts) { for (@hosts) {
$config{$_}{'mtime'} = $now; $recap{$_}{'mtime'} = $now;
for my $ip ($ipv4, $ipv6) { for my $ip ($ipv4, $ipv6) {
next if (!$ip); next if (!$ip);
my $ipv = ($ip eq ($ipv6 // '')) ? '6' : '4'; my $ipv = ($ip eq ($ipv6 // '')) ? '6' : '4';
$config{$_}{"ipv$ipv"} = $ip; $recap{$_}{"ipv$ipv"} = $ip;
$config{$_}{"status-ipv$ipv"} = 'good'; $recap{$_}{"status-ipv$ipv"} = 'good';
} }
} }
success("IPv4 address set to $ipv4") if $ipv4; success("IPv4 address set to $ipv4") if $ipv4;
@ -5751,7 +5703,7 @@ sub nic_cloudflare_update {
my $type = ($ip eq ($ipv6 // '')) ? 'AAAA' : 'A'; my $type = ($ip eq ($ipv6 // '')) ? 'AAAA' : 'A';
info("setting IPv$ipv address to $ip"); info("setting IPv$ipv address to $ip");
$config{$domain}{"status-ipv$ipv"} = 'failed'; $recap{$domain}{"status-ipv$ipv"} = 'failed';
# Get DNS 'A' or 'AAAA' record ID # Get DNS 'A' or 'AAAA' record ID
$url = "https://" . opt('server', $domain) . "/zones/$zone_id/dns_records?"; $url = "https://" . opt('server', $domain) . "/zones/$zone_id/dns_records?";
@ -5790,9 +5742,9 @@ sub nic_cloudflare_update {
$response = eval {decode_json(${^MATCH})}; $response = eval {decode_json(${^MATCH})};
if ($response && $response->{result}) { if ($response && $response->{result}) {
success("IPv$ipv address set to $ip"); success("IPv$ipv address set to $ip");
$config{$domain}{"ipv$ipv"} = $ip; $recap{$domain}{"ipv$ipv"} = $ip;
$config{$domain}{'mtime'} = $now; $recap{$domain}{'mtime'} = $now;
$config{$domain}{"status-ipv$ipv"} = 'good'; $recap{$domain}{"status-ipv$ipv"} = 'good';
} else { } else {
failed("invalid json or result"); failed("invalid json or result");
} }
@ -5874,7 +5826,7 @@ sub nic_hetzner_update {
my $type = ($ip eq ($ipv6 // '')) ? 'AAAA' : 'A'; my $type = ($ip eq ($ipv6 // '')) ? 'AAAA' : 'A';
info("setting IPv$ipv address to $ip"); info("setting IPv$ipv address to $ip");
$config{$domain}{"status-ipv$ipv"} = 'failed'; $recap{$domain}{"status-ipv$ipv"} = 'failed';
# Get DNS 'A' or 'AAAA' record ID # Get DNS 'A' or 'AAAA' record ID
$url = "https://" . opt('server', $domain) . "/records?zone_id=$zone_id"; $url = "https://" . opt('server', $domain) . "/records?zone_id=$zone_id";
@ -5919,9 +5871,9 @@ sub nic_hetzner_update {
$response = eval {decode_json(${^MATCH})}; $response = eval {decode_json(${^MATCH})};
if ($response && $response->{record}) { if ($response && $response->{record}) {
success("IPv$ipv address set to $ip"); success("IPv$ipv address set to $ip");
$config{$domain}{"ipv$ipv"} = $ip; $recap{$domain}{"ipv$ipv"} = $ip;
$config{$domain}{'mtime'} = $now; $recap{$domain}{'mtime'} = $now;
$config{$domain}{"status-ipv$ipv"} = 'good'; $recap{$domain}{"status-ipv$ipv"} = 'good';
} else { } else {
failed("invalid json or result"); failed("invalid json or result");
} }
@ -6067,8 +6019,8 @@ sub nic_inwx_update {
$status = 'good'; $status = 'good';
} }
for my $h (@hosts) { for my $h (@hosts) {
$config{$h}{'status-ipv4'} = $status if $ipv4; $recap{$h}{'status-ipv4'} = $status if $ipv4;
$config{$h}{'status-ipv6'} = $status if $ipv6; $recap{$h}{'status-ipv6'} = $status if $ipv6;
} }
if ($status ne 'good') { if ($status ne 'good') {
if (exists($errors{$status})) { if (exists($errors{$status})) {
@ -6079,9 +6031,9 @@ sub nic_inwx_update {
next; next;
} }
for my $h (@hosts) { for my $h (@hosts) {
$config{$h}{'ipv4'} = $ipv4 if $ipv4; $recap{$h}{'ipv4'} = $ipv4 if $ipv4;
$config{$h}{'ipv6'} = $ipv6 if $ipv6; $recap{$h}{'ipv6'} = $ipv6 if $ipv6;
$config{$h}{'mtime'} = $now; $recap{$h}{'mtime'} = $now;
} }
success("IPv4 address set to $ipv4") if $ipv4; success("IPv4 address set to $ipv4") if $ipv4;
success("IPv6 address set to $ipv6") if $ipv6; success("IPv6 address set to $ipv6") if $ipv6;
@ -6181,9 +6133,9 @@ sub nic_yandex_update {
failed("%s", $response->{error}); failed("%s", $response->{error});
next; next;
} }
$config{$host}{'ip'} = $ip; $recap{$host}{'ip'} = $ip;
$config{$host}{'mtime'} = $now; $recap{$host}{'mtime'} = $now;
$config{$host}{'status'} = 'good'; $recap{$host}{'status'} = 'good';
success("updated successfully to $ip"); success("updated successfully to $ip");
} }
} }
@ -6247,11 +6199,11 @@ sub nic_duckdns_update {
next; next;
} }
for my $h (@hosts) { for my $h (@hosts) {
$config{$h}{'ipv4'} = $ipv4 if $ipv4; $recap{$h}{'ipv4'} = $ipv4 if $ipv4;
$config{$h}{'ipv6'} = $ipv6 if $ipv6; $recap{$h}{'ipv6'} = $ipv6 if $ipv6;
$config{$h}{'mtime'} = $now; $recap{$h}{'mtime'} = $now;
$config{$h}{'status-ipv4'} = 'good' if $ipv4; $recap{$h}{'status-ipv4'} = 'good' if $ipv4;
$config{$h}{'status-ipv6'} = 'good' if $ipv6; $recap{$h}{'status-ipv6'} = 'good' if $ipv6;
} }
success("IPv4 address set to $ipv4") if $ipv4; success("IPv4 address set to $ipv4") if $ipv4;
success("IPv6 address set to $ipv6") if $ipv6; success("IPv6 address set to $ipv6") if $ipv6;
@ -6302,9 +6254,9 @@ sub nic_freemyip_update {
failed("server said: $body"); failed("server said: $body");
next; next;
} }
$config{$h}{'ip'} = $ip; $recap{$h}{'ip'} = $ip;
$config{$h}{'mtime'} = $now; $recap{$h}{'mtime'} = $now;
$config{$h}{'status'} = 'good'; $recap{$h}{'status'} = 'good';
success("IP address set to $ip"); success("IP address set to $ip");
} }
} }
@ -6352,9 +6304,9 @@ sub nic_ddnsfm_update {
url => opt('server', $h) . "/update?key=" . opt('password', $h) . "&domain=$h&myip=$ip", url => opt('server', $h) . "/update?key=" . opt('password', $h) . "&domain=$h&myip=$ip",
); );
next if !header_ok($reply); next if !header_ok($reply);
$config{$h}{"ipv$ipv"} = $ip; $recap{$h}{"ipv$ipv"} = $ip;
$config{$h}{'mtime'} = $now; $recap{$h}{'mtime'} = $now;
$config{$h}{"status-ipv$ipv"} = 'good'; $recap{$h}{"status-ipv$ipv"} = 'good';
success("IPv$ipv address set to $ip"); success("IPv$ipv address set to $ip");
} }
} }
@ -6401,9 +6353,9 @@ sub nic_dondominio_update {
failed("server said: $returned"); failed("server said: $returned");
next; next;
} }
$config{$h}{'ip'} = $ip; $recap{$h}{'ip'} = $ip;
$config{$h}{'mtime'} = $now; $recap{$h}{'mtime'} = $now;
$config{$h}{'status'} = 'good'; $recap{$h}{'status'} = 'good';
success("IP address set to $ip"); success("IP address set to $ip");
} }
} }
@ -6465,9 +6417,9 @@ sub nic_dnsmadeeasy_update {
failed("server said: $err"); failed("server said: $err");
next; next;
} }
$config{$h}{'ip'} = $ip; $recap{$h}{'ip'} = $ip;
$config{$h}{'mtime'} = $now; $recap{$h}{'mtime'} = $now;
$config{$h}{'status'} = 'good'; $recap{$h}{'status'} = 'good';
success("IP address set to $ip"); success("IP address set to $ip");
} }
} }
@ -6533,16 +6485,16 @@ sub nic_ovh_update {
my @reply = split /\n/, $reply; my @reply = split /\n/, $reply;
my $returned = List::Util::first { $_ =~ /good/ || $_ =~ /nochg/ } @reply; my $returned = List::Util::first { $_ =~ /good/ || $_ =~ /nochg/ } @reply;
if ($returned) { if ($returned) {
$config{$h}{'ip'} = $ip; $recap{$h}{'ip'} = $ip;
$config{$h}{'mtime'} = $now; $recap{$h}{'mtime'} = $now;
$config{$h}{'status'} = 'good'; $recap{$h}{'status'} = 'good';
if ($returned =~ /good/) { if ($returned =~ /good/) {
success("IP address set to $ip"); success("IP address set to $ip");
} else { } else {
success("skipped: IP address was already set to $ip"); success("skipped: IP address was already set to $ip");
} }
} else { } else {
$config{$h}{'status'} = 'failed'; $recap{$h}{'status'} = 'failed';
failed("server said: $reply"); failed("server said: $reply");
} }
} }
@ -6678,7 +6630,7 @@ sub nic_porkbun_update {
warning("There are multiple applicable records. Only first record is used. Overwrite all with the same content.") warning("There are multiple applicable records. Only first record is used. Overwrite all with the same content.")
if @$records > 1; if @$records > 1;
if ($records->[0]{'content'} eq $ip) { if ($records->[0]{'content'} eq $ip) {
$config{$h}{"status-ipv$ipv"} = "good"; $recap{$h}{"status-ipv$ipv"} = "good";
success("skipped: IPv$ipv address was already set to $ip"); success("skipped: IPv$ipv address was already set to $ip");
next; next;
} }
@ -6700,7 +6652,7 @@ sub nic_porkbun_update {
}), }),
); );
next if !header_ok($reply); next if !header_ok($reply);
$config{$h}{"status-ipv$ipv"} = "good"; $recap{$h}{"status-ipv$ipv"} = "good";
success("IPv$ipv address set to $ip"); success("IPv$ipv address set to $ip");
} }
} }
@ -6755,14 +6707,14 @@ sub nic_cloudns_update {
$reply =~ s/^.*?\n\n//s; # Strip the headers. $reply =~ s/^.*?\n\n//s; # Strip the headers.
chomp($reply); chomp($reply);
if ($reply eq "The record's key is wrong!" || $reply eq "Invalid request.") { if ($reply eq "The record's key is wrong!" || $reply eq "Invalid request.") {
$config{$_}{'status'} = 'failed' for @hosts; $recap{$_}{'status'} = 'failed' for @hosts;
failed($reply); failed($reply);
next; next;
} }
# There's no documentation explaining possible return values, so we assume success. # There's no documentation explaining possible return values, so we assume success.
$config{$_}{'ip'} = $ip for @hosts; $recap{$_}{'ip'} = $ip for @hosts;
$config{$_}{'mtime'} = $now for @hosts; $recap{$_}{'mtime'} = $now for @hosts;
$config{$_}{'status'} = 'good' for @hosts; $recap{$_}{'status'} = 'good' for @hosts;
success("IP address set to $ip"); success("IP address set to $ip");
} }
} }
@ -6811,7 +6763,7 @@ sub nic_dinahosting_update {
password => opt('password', $h), password => opt('password', $h),
url => $url, url => $url,
); );
$config{$h}{'status'} = 'failed'; # assume failure until otherwise determined $recap{$h}{'status'} = 'failed'; # assume failure until otherwise determined
next if !header_ok($reply); next if !header_ok($reply);
$reply =~ s/^.*?\n\n//s; # Strip the headers. $reply =~ s/^.*?\n\n//s; # Strip the headers.
if ($reply !~ /Success/i) { if ($reply !~ /Success/i) {
@ -6822,9 +6774,9 @@ sub nic_dinahosting_update {
failed("error $code: $message"); failed("error $code: $message");
next; next;
} }
$config{$h}{'ip'} = $ip; $recap{$h}{'ip'} = $ip;
$config{$h}{'mtime'} = $now; $recap{$h}{'mtime'} = $now;
$config{$h}{'status'} = 'good'; $recap{$h}{'status'} = 'good';
success("IP address set to $ip"); success("IP address set to $ip");
} }
} }
@ -6876,20 +6828,20 @@ sub nic_directnic_update {
(my $body = $reply) =~ s/^.*?\n\n//s; (my $body = $reply) =~ s/^.*?\n\n//s;
my $response = eval {decode_json($body)}; my $response = eval {decode_json($body)};
if (ref($response) ne 'HASH') { if (ref($response) ne 'HASH') {
$config{$h}{"status-ipv$ipv"} = 'bad'; $recap{$h}{"status-ipv$ipv"} = 'bad';
failed("response is not a JSON object:\n$body"); failed("response is not a JSON object:\n$body");
next; next;
} }
if ($response->{'result'} ne 'success') { if ($response->{'result'} ne 'success') {
$config{$h}{"status-ipv$ipv"} = 'failed'; $recap{$h}{"status-ipv$ipv"} = 'failed';
failed("server said:\n$body"); failed("server said:\n$body");
next; next;
} }
$config{$h}{"ipv$ipv"} = $ip; $recap{$h}{"ipv$ipv"} = $ip;
$config{$h}{"status-ipv$ipv"} = 'good'; $recap{$h}{"status-ipv$ipv"} = 'good';
$config{$h}{'mtime'} = $now; $recap{$h}{'mtime'} = $now;
success("IPv$ipv address set to $ip"); success("IPv$ipv address set to $ip");
} }
} }
@ -6972,15 +6924,15 @@ sub nic_gandi_update {
$reply =~ s/^.*?\n\n//s; $reply =~ s/^.*?\n\n//s;
my $response = eval { decode_json($reply) }; my $response = eval { decode_json($reply) };
if (ref($response) ne 'HASH') { if (ref($response) ne 'HASH') {
$config{$h}{"status-$ipv"} = "bad"; $recap{$h}{"status-$ipv"} = "bad";
failed("response is not a JSON object: $reply"); failed("response is not a JSON object: $reply");
next; next;
} }
if ($response->{'rrset_values'}->[0] eq $ip && (!defined(opt('ttl', $h)) || if ($response->{'rrset_values'}->[0] eq $ip && (!defined(opt('ttl', $h)) ||
$response->{'rrset_ttl'} eq opt('ttl', $h))) { $response->{'rrset_ttl'} eq opt('ttl', $h))) {
$config{$h}{'ip'} = $ip; $recap{$h}{'ip'} = $ip;
$config{$h}{'mtime'} = $now; $recap{$h}{'mtime'} = $now;
$config{$h}{"status-$ipv"} = "good"; $recap{$h}{"status-$ipv"} = "good";
success("skipped: address was already set to $ip"); success("skipped: address was already set to $ip");
next; next;
} }
@ -6995,7 +6947,7 @@ sub nic_gandi_update {
}), }),
); );
if (!header_ok($reply)) { if (!header_ok($reply)) {
$config{$h}{"status-$ipv"} = "bad"; $recap{$h}{"status-$ipv"} = "bad";
$reply =~ s/^.*?\n\n//s; $reply =~ s/^.*?\n\n//s;
my $response = eval { decode_json($reply) }; my $response = eval { decode_json($reply) };
if (ref($response) eq 'HASH' && ($response->{message} // '') ne '') { if (ref($response) eq 'HASH' && ($response->{message} // '') ne '') {
@ -7005,9 +6957,9 @@ sub nic_gandi_update {
} }
next; next;
} }
$config{$h}{'ip'} = $ip; $recap{$h}{'ip'} = $ip;
$config{$h}{'mtime'} = $now; $recap{$h}{'mtime'} = $now;
$config{$h}{"status-$ipv"} = "good"; $recap{$h}{"status-$ipv"} = "good";
success("updated successfully to $ip"); success("updated successfully to $ip");
} }
} }
@ -7055,12 +7007,12 @@ sub nic_keysystems_update {
last if !header_ok($reply); last if !header_ok($reply);
if ($reply =~ /code = 200/) { if ($reply =~ /code = 200/) {
$config{$h}{'ip'} = $ip; $recap{$h}{'ip'} = $ip;
$config{$h}{'mtime'} = $now; $recap{$h}{'mtime'} = $now;
$config{$h}{'status'} = 'good'; $recap{$h}{'status'} = 'good';
success("IP address set to $ip"); success("IP address set to $ip");
} else { } else {
$config{$h}{'status'} = 'failed'; $recap{$h}{'status'} = 'failed';
failed("server said: $reply"); failed("server said: $reply");
} }
} }
@ -7109,11 +7061,11 @@ sub nic_regfishde_update {
failed("server said: $reply"); failed("server said: $reply");
next; next;
} }
$config{$h}{'ipv4'} = $ipv4 if $ipv4; $recap{$h}{'ipv4'} = $ipv4 if $ipv4;
$config{$h}{'ipv6'} = $ipv6 if $ipv6; $recap{$h}{'ipv6'} = $ipv6 if $ipv6;
$config{$h}{'status-ipv4'} = 'good' if $ipv4; $recap{$h}{'status-ipv4'} = 'good' if $ipv4;
$config{$h}{'status-ipv6'} = 'good' if $ipv6; $recap{$h}{'status-ipv6'} = 'good' if $ipv6;
$config{$h}{'mtime'} = $now; $recap{$h}{'mtime'} = $now;
success("IPv4 address set to $ipv4") if $ipv4; success("IPv4 address set to $ipv4") if $ipv4;
success("IPv6 address set to $ipv6") if $ipv6; success("IPv6 address set to $ipv6") if $ipv6;
} }
@ -7183,12 +7135,12 @@ sub nic_enom_update {
my @reply = split /\n/, $reply; my @reply = split /\n/, $reply;
if (grep /Done=true/i, @reply) { if (grep /Done=true/i, @reply) {
$config{$h}{'ip'} = $ip; $recap{$h}{'ip'} = $ip;
$config{$h}{'mtime'} = $now; $recap{$h}{'mtime'} = $now;
$config{$h}{'status'} = 'good'; $recap{$h}{'status'} = 'good';
success("IP address set to $ip"); success("IP address set to $ip");
} else { } else {
$config{$h}{'status'} = 'failed'; $recap{$h}{'status'} = 'failed';
warning("SENT: %s", $url) unless opt('verbose'); warning("SENT: %s", $url) unless opt('verbose');
warning("REPLIED: %s", $reply); warning("REPLIED: %s", $reply);
failed("invalid reply"); failed("invalid reply");
@ -7252,7 +7204,7 @@ sub nic_digitalocean_update_one {
my $list = eval { decode_json($list_resp) }; my $list = eval { decode_json($list_resp) };
if ($@) { if ($@) {
$config{$h}{"status-$ipv"} = 'failed'; $recap{$h}{"status-$ipv"} = 'failed';
failed("listing $ipv: JSON decoding failure"); failed("listing $ipv: JSON decoding failure");
return; return;
} }
@ -7261,7 +7213,7 @@ sub nic_digitalocean_update_one {
unless ((ref($elem) eq 'HASH') && unless ((ref($elem) eq 'HASH') &&
(ref ($elem = $elem->{'domain_records'}) eq 'ARRAY') && (ref ($elem = $elem->{'domain_records'}) eq 'ARRAY') &&
(@$elem == 1 && ref ($elem = $elem->[0]) eq 'HASH')) { (@$elem == 1 && ref ($elem = $elem->[0]) eq 'HASH')) {
$config{$h}{"status-$ipv"} = 'failed'; $recap{$h}{"status-$ipv"} = 'failed';
failed("listing $ipv: no record, multiple records, or malformed JSON"); failed("listing $ipv: no record, multiple records, or malformed JSON");
return; return;
} }
@ -7283,9 +7235,9 @@ sub nic_digitalocean_update_one {
return if !header_ok($update_resp); return if !header_ok($update_resp);
} }
$config{$h}{"status-$ipv"} = 'good'; $recap{$h}{"status-$ipv"} = 'good';
$config{$h}{"ip-$ipv"} = $ip; $recap{$h}{"ip-$ipv"} = $ip;
$config{$h}{"mtime"} = $now; $recap{$h}{"mtime"} = $now;
} }
sub nic_digitalocean_update { sub nic_digitalocean_update {
@ -7393,9 +7345,9 @@ sub nic_infomaniak_update {
next; next;
} }
success($msg); success($msg);
$config{$h}{"ipv$v"} = $ip; $recap{$h}{"ipv$v"} = $ip;
$config{$h}{'mtime'} = $now; $recap{$h}{'mtime'} = $now;
$config{$h}{"status-ipv$v"} = 'good'; $recap{$h}{"status-ipv$v"} = 'good';
} }
} }
} }
@ -7417,11 +7369,11 @@ sub nic_emailonly_update {
logmsg(email => 1, raw => 1, join("\n", 'Host IP addresses:', map({ logmsg(email => 1, raw => 1, join("\n", 'Host IP addresses:', map({
my $ipv4 = delete($config{$_}{'wantipv4'}); my $ipv4 = delete($config{$_}{'wantipv4'});
my $ipv6 = delete($config{$_}{'wantipv6'}); my $ipv6 = delete($config{$_}{'wantipv6'});
$config{$_}{'status-ipv4'} = 'good' if $ipv4; $recap{$_}{'status-ipv4'} = 'good' if $ipv4;
$config{$_}{'status-ipv6'} = 'good' if $ipv6; $recap{$_}{'status-ipv6'} = 'good' if $ipv6;
$config{$_}{'ipv4'} = $ipv4 if $ipv4; $recap{$_}{'ipv4'} = $ipv4 if $ipv4;
$config{$_}{'ipv6'} = $ipv6 if $ipv6; $recap{$_}{'ipv6'} = $ipv6 if $ipv6;
$config{$_}{'mtime'} = $now; $recap{$_}{'mtime'} = $now;
sprintf('%30s %s', $_, join(' ', grep(defined($_), $ipv4, $ipv6))); sprintf('%30s %s', $_, join(' ', grep(defined($_), $ipv4, $ipv6)));
} @_))); } @_)));
} }

View file

@ -52,7 +52,7 @@ my @test_cases = (
{ {
desc => 'IPv4, good', desc => 'IPv4, good',
cfg => {h1 => {urlv4 => "$hostname/dns/gateway/abc/", wantipv4 => '192.0.2.1'}}, cfg => {h1 => {urlv4 => "$hostname/dns/gateway/abc/", wantipv4 => '192.0.2.1'}},
wantstatus => { wantrecap => {
h1 => {'status-ipv4' => 'good', 'ipv4' => '192.0.2.1', 'mtime' => $ddclient::now}, h1 => {'status-ipv4' => 'good', 'ipv4' => '192.0.2.1', 'mtime' => $ddclient::now},
}, },
wantlogs => [ wantlogs => [
@ -62,7 +62,7 @@ my @test_cases = (
{ {
desc => 'IPv4, failed', desc => 'IPv4, failed',
cfg => {h1 => {urlv4 => "$hostname/dns/gateway/bad_token/", wantipv4 => '192.0.2.1'}}, cfg => {h1 => {urlv4 => "$hostname/dns/gateway/bad_token/", wantipv4 => '192.0.2.1'}},
wantstatus => { wantrecap => {
h1 => {'status-ipv4' => 'failed'}, h1 => {'status-ipv4' => 'failed'},
}, },
wantlogs => [ wantlogs => [
@ -72,7 +72,7 @@ my @test_cases = (
{ {
desc => 'IPv4, bad', desc => 'IPv4, bad',
cfg => {h1 => {urlv4 => "$hostname/bad/path/", wantipv4 => '192.0.2.1'}}, cfg => {h1 => {urlv4 => "$hostname/bad/path/", wantipv4 => '192.0.2.1'}},
wantstatus => { wantrecap => {
h1 => {'status-ipv4' => 'bad'}, h1 => {'status-ipv4' => 'bad'},
}, },
wantlogs => [ wantlogs => [
@ -82,7 +82,7 @@ my @test_cases = (
{ {
desc => 'IPv4, unexpected response', desc => 'IPv4, unexpected response',
cfg => {h1 => {urlv4 => "$hostname/unexpected/path/", wantipv4 => '192.0.2.1'}}, cfg => {h1 => {urlv4 => "$hostname/unexpected/path/", wantipv4 => '192.0.2.1'}},
wantstatus => {h1 => {}}, wantrecap => {},
wantlogs => [ wantlogs => [
{label => 'FAILED', ctx => ['h1'], msg => qr/400 Bad Request/}, {label => 'FAILED', ctx => ['h1'], msg => qr/400 Bad Request/},
], ],
@ -90,7 +90,7 @@ my @test_cases = (
{ {
desc => 'IPv4, no urlv4', desc => 'IPv4, no urlv4',
cfg => {h1 => {wantipv4 => '192.0.2.1'}}, cfg => {h1 => {wantipv4 => '192.0.2.1'}},
wantstatus => {h1 => {}}, wantrecap => {},
wantlogs => [ wantlogs => [
{label => 'FAILED', ctx => ['h1'], msg => qr/missing urlv4 option/}, {label => 'FAILED', ctx => ['h1'], msg => qr/missing urlv4 option/},
], ],
@ -98,7 +98,7 @@ my @test_cases = (
{ {
desc => 'IPv6, good', desc => 'IPv6, good',
cfg => {h1 => {urlv6 => "$hostname/dns/gateway/abc/", wantipv6 => '2001:db8::1'}}, cfg => {h1 => {urlv6 => "$hostname/dns/gateway/abc/", wantipv6 => '2001:db8::1'}},
wantstatus => { wantrecap => {
h1 => {'status-ipv6' => 'good', 'ipv6' => '2001:db8::1', 'mtime' => $ddclient::now}, h1 => {'status-ipv6' => 'good', 'ipv6' => '2001:db8::1', 'mtime' => $ddclient::now},
}, },
wantlogs => [ wantlogs => [
@ -113,7 +113,7 @@ my @test_cases = (
wantipv4 => '192.0.2.1', wantipv4 => '192.0.2.1',
wantipv6 => '2001:db8::1', wantipv6 => '2001:db8::1',
}}, }},
wantstatus => { wantrecap => {
h1 => {'status-ipv4' => 'good', 'ipv4' => '192.0.2.1', h1 => {'status-ipv4' => 'good', 'ipv4' => '192.0.2.1',
'status-ipv6' => 'good', 'ipv6' => '2001:db8::1', 'status-ipv6' => 'good', 'ipv6' => '2001:db8::1',
'mtime' => $ddclient::now}, 'mtime' => $ddclient::now},
@ -132,7 +132,7 @@ my @test_cases = (
wantipv6 => '2001:db8::1', wantipv6 => '2001:db8::1',
}}, }},
wantips => {h1 => {wantipv4 => '192.0.2.1', wantipv6 => '2001:db8::1'}}, wantips => {h1 => {wantipv4 => '192.0.2.1', wantipv6 => '2001:db8::1'}},
wantstatus => { wantrecap => {
h1 => {'status-ipv4' => 'failed', h1 => {'status-ipv4' => 'failed',
'status-ipv6' => 'good', 'ipv6' => '2001:db8::1', 'status-ipv6' => 'good', 'ipv6' => '2001:db8::1',
'mtime' => $ddclient::now}, 'mtime' => $ddclient::now},
@ -152,20 +152,14 @@ for my $tc (@test_cases) {
local $ddclient::globals{verbose} = 1; local $ddclient::globals{verbose} = 1;
my $l = Logger->new($ddclient::_l); my $l = Logger->new($ddclient::_l);
local %ddclient::config = %{$tc->{cfg}}; local %ddclient::config = %{$tc->{cfg}};
local %ddclient::recap;
{ {
local $ddclient::_l = $l; local $ddclient::_l = $l;
ddclient::nic_directnic_update(sort(keys(%{$tc->{cfg}}))); ddclient::nic_directnic_update(sort(keys(%{$tc->{cfg}})));
} }
# These are the properties in %ddclient::config to check against $tc->{wantstatus}. is_deeply(\%ddclient::recap, $tc->{wantrecap}, "$tc->{desc}: recap")
my %statuskeys = map(($_ => undef), qw(atime ip ipv4 ipv6 mtime status status-ipv4 status-ipv6 or diag(ddclient::repr(Values => [\%ddclient::recap, $tc->{wantrecap}],
wantip wantipv4 wantipv6 wtime)); Names => ['*got', '*want']));
my %gotstatus;
for my $h (keys(%ddclient::config)) {
$gotstatus{$h} = {map(($_ => $ddclient::config{$h}{$_}),
grep(exists($statuskeys{$_}), keys(%{$ddclient::config{$h}})))};
}
is_deeply(\%gotstatus, $tc->{wantstatus}, "$tc->{desc}: status")
or diag(ddclient::repr(\%ddclient::config, Names => ['*ddclient::config']));
$tc->{wantlogs} //= []; $tc->{wantlogs} //= [];
subtest("$tc->{desc}: logs" => sub { subtest("$tc->{desc}: logs" => sub {
my @got = @{$l->{logs}}; my @got = @{$l->{logs}};

View file

@ -45,7 +45,7 @@ my @test_cases = (
cfg => {h1 => {wantipv4 => '192.0.2.1'}}, cfg => {h1 => {wantipv4 => '192.0.2.1'}},
resp => ['good'], resp => ['good'],
wantquery => 'hostname=h1&myip=192.0.2.1', wantquery => 'hostname=h1&myip=192.0.2.1',
wantstatus => { wantrecap => {
h1 => {'status-ipv4' => 'good', 'ipv4' => '192.0.2.1', 'mtime' => $ddclient::now}, h1 => {'status-ipv4' => 'good', 'ipv4' => '192.0.2.1', 'mtime' => $ddclient::now},
}, },
wantlogs => [ wantlogs => [
@ -57,7 +57,7 @@ my @test_cases = (
cfg => {h1 => {wantipv4 => '192.0.2.1'}}, cfg => {h1 => {wantipv4 => '192.0.2.1'}},
resp => ['nochg'], resp => ['nochg'],
wantquery => 'hostname=h1&myip=192.0.2.1', wantquery => 'hostname=h1&myip=192.0.2.1',
wantstatus => { wantrecap => {
h1 => {'status-ipv4' => 'good', 'ipv4' => '192.0.2.1', 'mtime' => $ddclient::now}, h1 => {'status-ipv4' => 'good', 'ipv4' => '192.0.2.1', 'mtime' => $ddclient::now},
}, },
wantlogs => [ wantlogs => [
@ -70,7 +70,7 @@ my @test_cases = (
cfg => {h1 => {wantipv4 => '192.0.2.1'}}, cfg => {h1 => {wantipv4 => '192.0.2.1'}},
resp => ['nohost'], resp => ['nohost'],
wantquery => 'hostname=h1&myip=192.0.2.1', wantquery => 'hostname=h1&myip=192.0.2.1',
wantstatus => { wantrecap => {
h1 => {'status-ipv4' => 'nohost'}, h1 => {'status-ipv4' => 'nohost'},
}, },
wantlogs => [ wantlogs => [
@ -82,7 +82,7 @@ my @test_cases = (
cfg => {h1 => {wantipv4 => '192.0.2.1'}}, cfg => {h1 => {wantipv4 => '192.0.2.1'}},
resp => ['WAT'], resp => ['WAT'],
wantquery => 'hostname=h1&myip=192.0.2.1', wantquery => 'hostname=h1&myip=192.0.2.1',
wantstatus => { wantrecap => {
h1 => {'status-ipv4' => 'WAT'}, h1 => {'status-ipv4' => 'WAT'},
}, },
wantlogs => [ wantlogs => [
@ -100,7 +100,7 @@ my @test_cases = (
'good', 'good',
], ],
wantquery => 'hostname=h1,h2&myip=192.0.2.1', wantquery => 'hostname=h1,h2&myip=192.0.2.1',
wantstatus => { wantrecap => {
h1 => {'status-ipv4' => 'good', 'ipv4' => '192.0.2.1', 'mtime' => $ddclient::now}, h1 => {'status-ipv4' => 'good', 'ipv4' => '192.0.2.1', 'mtime' => $ddclient::now},
h2 => {'status-ipv4' => 'good', 'ipv4' => '192.0.2.1', 'mtime' => $ddclient::now}, h2 => {'status-ipv4' => 'good', 'ipv4' => '192.0.2.1', 'mtime' => $ddclient::now},
}, },
@ -122,7 +122,7 @@ my @test_cases = (
'dnserr', 'dnserr',
], ],
wantquery => 'hostname=h1,h2,h3&myip=192.0.2.1', wantquery => 'hostname=h1,h2,h3&myip=192.0.2.1',
wantstatus => { wantrecap => {
h1 => {'status-ipv4' => 'good', 'ipv4' => '192.0.2.1', 'mtime' => $ddclient::now}, h1 => {'status-ipv4' => 'good', 'ipv4' => '192.0.2.1', 'mtime' => $ddclient::now},
h2 => {'status-ipv4' => 'good', 'ipv4' => '192.0.2.1', 'mtime' => $ddclient::now}, h2 => {'status-ipv4' => 'good', 'ipv4' => '192.0.2.1', 'mtime' => $ddclient::now},
h3 => {'status-ipv4' => 'dnserr'}, h3 => {'status-ipv4' => 'dnserr'},
@ -139,7 +139,7 @@ my @test_cases = (
cfg => {h1 => {wantipv6 => '2001:db8::1'}}, cfg => {h1 => {wantipv6 => '2001:db8::1'}},
resp => ['good'], resp => ['good'],
wantquery => 'hostname=h1&myip=2001:db8::1', wantquery => 'hostname=h1&myip=2001:db8::1',
wantstatus => { wantrecap => {
h1 => {'status-ipv6' => 'good', 'ipv6' => '2001:db8::1', 'mtime' => $ddclient::now}, h1 => {'status-ipv6' => 'good', 'ipv6' => '2001:db8::1', 'mtime' => $ddclient::now},
}, },
wantlogs => [ wantlogs => [
@ -151,7 +151,7 @@ my @test_cases = (
cfg => {h1 => {wantipv4 => '192.0.2.1', wantipv6 => '2001:db8::1'}}, cfg => {h1 => {wantipv4 => '192.0.2.1', wantipv6 => '2001:db8::1'}},
resp => ['good'], resp => ['good'],
wantquery => 'hostname=h1&myip=192.0.2.1,2001:db8::1', wantquery => 'hostname=h1&myip=192.0.2.1,2001:db8::1',
wantstatus => { wantrecap => {
h1 => {'status-ipv4' => 'good', 'ipv4' => '192.0.2.1', h1 => {'status-ipv4' => 'good', 'ipv4' => '192.0.2.1',
'status-ipv6' => 'good', 'ipv6' => '2001:db8::1', 'status-ipv6' => 'good', 'ipv6' => '2001:db8::1',
'mtime' => $ddclient::now}, 'mtime' => $ddclient::now},
@ -173,7 +173,7 @@ my @test_cases = (
'WAT', 'WAT',
], ],
wantquery => 'hostname=h1,h2&myip=192.0.2.1', wantquery => 'hostname=h1,h2&myip=192.0.2.1',
wantstatus => { wantrecap => {
h1 => {'status-ipv4' => 'good', 'ipv4' => '192.0.2.1', 'mtime' => $ddclient::now}, h1 => {'status-ipv4' => 'good', 'ipv4' => '192.0.2.1', 'mtime' => $ddclient::now},
h2 => {'status-ipv4' => 'good', 'ipv4' => '192.0.2.1', 'mtime' => $ddclient::now}, h2 => {'status-ipv4' => 'good', 'ipv4' => '192.0.2.1', 'mtime' => $ddclient::now},
}, },
@ -191,7 +191,7 @@ my @test_cases = (
}, },
resp => ['abuse'], resp => ['abuse'],
wantquery => 'hostname=h1,h2&myip=192.0.2.1', wantquery => 'hostname=h1,h2&myip=192.0.2.1',
wantstatus => { wantrecap => {
h1 => {'status-ipv4' => 'abuse'}, h1 => {'status-ipv4' => 'abuse'},
h2 => {'status-ipv4' => 'abuse'}, h2 => {'status-ipv4' => 'abuse'},
}, },
@ -208,7 +208,7 @@ my @test_cases = (
}, },
resp => ['good'], resp => ['good'],
wantquery => 'hostname=h1,h2&myip=192.0.2.1', wantquery => 'hostname=h1,h2&myip=192.0.2.1',
wantstatus => { wantrecap => {
h1 => {'status-ipv4' => 'good', 'ipv4' => '192.0.2.1', 'mtime' => $ddclient::now}, h1 => {'status-ipv4' => 'good', 'ipv4' => '192.0.2.1', 'mtime' => $ddclient::now},
h2 => {'status-ipv4' => 'good', 'ipv4' => '192.0.2.1', 'mtime' => $ddclient::now}, h2 => {'status-ipv4' => 'good', 'ipv4' => '192.0.2.1', 'mtime' => $ddclient::now},
}, },
@ -230,7 +230,7 @@ my @test_cases = (
'nochg', 'nochg',
], ],
wantquery => 'hostname=h1,h2,h3&myip=192.0.2.1', wantquery => 'hostname=h1,h2,h3&myip=192.0.2.1',
wantstatus => { wantrecap => {
h1 => {'status-ipv4' => 'good', 'ipv4' => '192.0.2.1', 'mtime' => $ddclient::now}, h1 => {'status-ipv4' => 'good', 'ipv4' => '192.0.2.1', 'mtime' => $ddclient::now},
h2 => {'status-ipv4' => 'good', 'ipv4' => '192.0.2.1', 'mtime' => $ddclient::now}, h2 => {'status-ipv4' => 'good', 'ipv4' => '192.0.2.1', 'mtime' => $ddclient::now},
h3 => {'status-ipv4' => 'unknown'}, h3 => {'status-ipv4' => 'unknown'},
@ -252,6 +252,7 @@ for my $tc (@test_cases) {
local $ddclient::globals{verbose} = 1; local $ddclient::globals{verbose} = 1;
my $l = Logger->new($ddclient::_l); my $l = Logger->new($ddclient::_l);
local %ddclient::config; local %ddclient::config;
local %ddclient::recap;
$ddclient::config{$_} = { $ddclient::config{$_} = {
login => 'username', login => 'username',
password => 'password', password => 'password',
@ -267,18 +268,9 @@ for my $tc (@test_cases) {
local $ddclient::_l = $l; local $ddclient::_l = $l;
ddclient::nic_dyndns2_update(sort(keys(%{$tc->{cfg}}))); ddclient::nic_dyndns2_update(sort(keys(%{$tc->{cfg}})));
} }
# These are the properties in %ddclient::config to check against $tc->{wantstatus}. Keys are is_deeply(\%ddclient::recap, $tc->{wantrecap}, "$tc->{desc}: recap")
# explicitly listed here rather than read from $tc->{wantstatus} to ensure that entries that or diag(ddclient::repr(Values => [\%ddclient::recap, $tc->{wantrecap}],
# should not exist (e.g., wantipv4 and friends) are deleted (or never set). Names => ['*got', '*want']));
my %statuskeys = map(($_ => undef), qw(atime ip ipv4 ipv6 mtime status status-ipv4 status-ipv6
wantip wantipv4 wantipv6 wtime));
my %gotstatus;
for my $h (keys(%ddclient::config)) {
$gotstatus{$h} = {map(($_ => $ddclient::config{$h}{$_}),
grep(exists($statuskeys{$_}), keys(%{$ddclient::config{$h}})))};
}
is_deeply(\%gotstatus, $tc->{wantstatus}, "$tc->{desc}: status")
or diag(ddclient::repr(\%ddclient::config, Names => ['*ddclient::config']));
$tc->{wantlogs} //= []; $tc->{wantlogs} //= [];
subtest("$tc->{desc}: logs" => sub { subtest("$tc->{desc}: logs" => sub {
my @got = @{$l->{logs}}; my @got = @{$l->{logs}};

View file

@ -1,6 +1,5 @@
use Test::More; use Test::More;
use File::Temp; use File::Temp;
use Scalar::Util qw(refaddr);
SKIP: { eval { require Test::Warnings; } or skip($@, 1); } SKIP: { eval { require Test::Warnings; } or skip($@, 1); }
eval { require 'ddclient'; } or BAIL_OUT($@); eval { require 'ddclient'; } or BAIL_OUT($@);
@ -9,42 +8,26 @@ local $ddclient::globals{verbose} = 1;
local %ddclient::protocols = ( local %ddclient::protocols = (
protocol_a => { protocol_a => {
variables => { variables => {
'host' => {type => ddclient::T_STRING(), recap => 1}, host => {type => ddclient::T_STRING(), recap => 1},
'mtime' => {type => ddclient::T_NUMBER(), recap => 1}, var_a => {type => ddclient::T_BOOL(), recap => 1},
'atime' => {type => ddclient::T_NUMBER(), recap => 1},
'wtime' => {type => ddclient::T_NUMBER(), recap => 1},
'ip' => {type => ddclient::T_IP(), recap => 1},
'ipv4' => {type => ddclient::T_IPV4(), recap => 1},
'ipv6' => {type => ddclient::T_IPV6(), recap => 1},
'status' => {type => ddclient::T_ANY(), recap => 1},
'status-ipv4' => {type => ddclient::T_ANY(), recap => 1},
'status-ipv6' => {type => ddclient::T_ANY(), recap => 1},
'warned-min-error-interval' => {type => ddclient::T_ANY(), recap => 1},
'warned-min-interval' => {type => ddclient::T_ANY(), recap => 1},
'var_a' => {type => ddclient::T_BOOL(), recap => 1},
}, },
}, },
protocol_b => { protocol_b => {
variables => { variables => {
'host' => {type => ddclient::T_STRING(), recap => 1}, host => {type => ddclient::T_STRING(), recap => 1},
'mtime' => {type => ddclient::T_NUMBER()}, # Intentionally not a recap var. var_b => {type => ddclient::T_NUMBER(), recap => 1},
'var_b' => {type => ddclient::T_NUMBER(), recap => 1}, var_b_non_recap => {type => ddclient::T_ANY()},
}, },
}, },
); );
local %ddclient::variables = local %ddclient::variables =
(merged => {map({ %{$ddclient::protocols{$_}{variables}}; } sort(keys(%ddclient::protocols)))}); (merged => {map({ %{$ddclient::protocols{$_}{variables}}; } sort(keys(%ddclient::protocols)))});
# Sentinel value that means "this hash entry should be deleted."
my $DOES_NOT_EXIST = [];
my @test_cases = ( my @test_cases = (
{ {
desc => "ok value", desc => "ok value",
cachefile_lines => ["var_a=yes host_a"], cachefile_lines => ["var_a=yes host_a"],
want => {host_a => {host => 'host_a', var_a => 1}}, want => {host_a => {host => 'host_a', var_a => 1}},
# No config changes are expected because `var_a` is not a "status" recap var.
}, },
{ {
desc => "unknown host", desc => "unknown host",
@ -71,80 +54,16 @@ my @test_cases = (
host_a => {host => 'host_a', var_a => 1}, host_a => {host => 'host_a', var_a => 1},
host_b => {host => 'host_b', var_b => 123}, host_b => {host => 'host_b', var_b => 123},
}, },
# No config changes are expected because `var_a` and `var_b` are not "status" recap vars.
}, },
{ {
desc => "used to be status vars", desc => "non-recap vars are not loaded to %recap",
cachefile_lines => ["ip=192.0.2.1,status=good host_a"], cachefile_lines => ["var_b_non_recap=foo host_b"],
want => {host_a => {host => 'host_a', ip => '192.0.2.1', status => 'good'}},
# No config changes are expected because `ip` and `status` are no longer "status" recap
# vars.
},
{
desc => "status vars",
cachefile_lines => ["mtime=1234567890,atime=1234567891,wtime=1234567892,ipv4=192.0.2.1,ipv6=2001:db8::1,status-ipv4=good,status-ipv6=bad,warned-min-interval=1234567893,warned-min-error-interval=1234567894 host_a"],
want => {host_a => {
'host' => 'host_a',
'mtime' => 1234567890,
'atime' => 1234567891,
'wtime' => 1234567892,
'ipv4' => '192.0.2.1',
'ipv6' => '2001:db8::1',
'status-ipv4' => 'good',
'status-ipv6' => 'bad',
'warned-min-interval' => 1234567893,
'warned-min-error-interval' => 1234567894,
}},
want_config_changes => {host_a => {
'mtime' => 1234567890,
'atime' => 1234567891,
'wtime' => 1234567892,
'ipv4' => '192.0.2.1',
'ipv6' => '2001:db8::1',
'status-ipv4' => 'good',
'status-ipv6' => 'bad',
'warned-min-interval' => 1234567893,
'warned-min-error-interval' => 1234567894,
}},
},
{
desc => "unset status var clears config",
cachefile_lines => ["host_a"],
config => {host_a => {
'mtime' => 1234567890,
'atime' => 1234567891,
'wtime' => 1234567892,
'ipv4' => '192.0.2.1',
'ipv6' => '2001:db8::1',
'status-ipv4' => 'good',
'status-ipv6' => 'bad',
'warned-min-interval' => 1234567893,
'warned-min-error-interval' => 1234567894,
'var_a' => 1,
}},
want => {host_a => {host => 'host_a'}},
want_config_changes => {host_a => {
'mtime' => $DOES_NOT_EXIST,
'atime' => $DOES_NOT_EXIST,
'wtime' => $DOES_NOT_EXIST,
'ipv4' => $DOES_NOT_EXIST,
'ipv6' => $DOES_NOT_EXIST,
'status-ipv4' => $DOES_NOT_EXIST,
'status-ipv6' => $DOES_NOT_EXIST,
'warned-min-interval' => $DOES_NOT_EXIST,
'warned-min-error-interval' => $DOES_NOT_EXIST,
# `var_a` should remain untouched.
}},
},
{
desc => "non-recap vars are not loaded to %recap or copied to %config",
cachefile_lines => ["mtime=1234567890 host_b"],
want => {host_b => {host => 'host_b'}}, want => {host_b => {host => 'host_b'}},
}, },
{ {
desc => "non-recap vars are scrubbed from %recap", desc => "non-recap vars are scrubbed from %recap",
cachefile_lines => ["mtime=1234567890 host_b"], cachefile_lines => ["var_b_non_recap=foo host_b"],
recap => {host_b => {host => 'host_b', mtime => 1234567891}}, recap => {host_b => {host => 'host_b', var_b_non_recap => 'foo'}},
want => {host_b => {host => 'host_b'}}, want => {host_b => {host => 'host_b'}},
}, },
{ {
@ -166,8 +85,6 @@ for my $tc (@test_cases) {
host_a => {protocol => 'protocol_a'}, host_a => {protocol => 'protocol_a'},
host_b => {protocol => 'protocol_b'}, host_b => {protocol => 'protocol_b'},
); );
$tc->{config} //= {};
$want_config{$_} = {%{$want_config{$_} // {}}, %{$tc->{config}{$_}}} for keys(%{$tc->{config}});
# Deep clone %want_config so we can check for changes. # Deep clone %want_config so we can check for changes.
local %ddclient::config; local %ddclient::config;
$ddclient::config{$_} = {%{$want_config{$_}}} for keys(%want_config); $ddclient::config{$_} = {%{$want_config{$_}}} for keys(%want_config);
@ -180,21 +97,9 @@ for my $tc (@test_cases) {
or diag(ddclient::repr(Values => [\%ddclient::recap, $tc->{want}], or diag(ddclient::repr(Values => [\%ddclient::recap, $tc->{want}],
Names => ['*got', '*want'])); Names => ['*got', '*want']));
} }
TODO: { is_deeply(\%ddclient::config, \%want_config, "$tc->{desc}: %config")
local $TODO = $tc->{want_config_changes_TODO}; or diag(ddclient::repr(Values => [\%ddclient::config, \%want_config],
$tc->{want_config_changes} //= {}; Names => ['*got', '*want']));
$want_config{$_} = {%{$want_config{$_} // {}}, %{$tc->{want_config_changes}{$_}}}
for keys(%{$tc->{want_config_changes}});
for my $h (keys(%want_config)) {
for my $k (keys(%{$want_config{$h}})) {
my $a = refaddr($want_config{$h}{$k});
delete($want_config{$h}{$k}) if defined($a) && $a == refaddr($DOES_NOT_EXIST);
}
}
is_deeply(\%ddclient::config, \%want_config, "$tc->{desc}: %config")
or diag(ddclient::repr(Values => [\%ddclient::config, \%want_config],
Names => ['*got', '*want']));
}
} }
done_testing(); done_testing();

View file

@ -54,9 +54,9 @@ local %ddclient::protocols = (
local $ddclient::_l = ddclient::pushlogctx($h); local $ddclient::_l = ddclient::pushlogctx($h);
ddclient::debug('updating host'); ddclient::debug('updating host');
push(@updates, [@_]); push(@updates, [@_]);
$ddclient::config{$h}{status} = 'good'; $ddclient::recap{$h}{status} = 'good';
$ddclient::config{$h}{ip} = delete($ddclient::config{$h}{wantip}); $ddclient::recap{$h}{ip} = delete($ddclient::config{$h}{wantip});
$ddclient::config{$h}{mtime} = $ddclient::now; $ddclient::recap{$h}{mtime} = $ddclient::now;
} }
ddclient::debug('returning from update'); ddclient::debug('returning from update');
}), }),
@ -83,12 +83,6 @@ my @test_cases = (
'mtime' => $ddclient::now, 'mtime' => $ddclient::now,
'status-ipv4' => 'good', 'status-ipv4' => 'good',
}, },
want_cfg_changes => {
'atime' => $ddclient::now,
'ipv4' => '192.0.2.1',
'mtime' => $ddclient::now,
'status-ipv4' => 'good',
},
%$_, %$_,
}; };
} {cfg => {use => 'web'}}, {cfg => {usev4 => 'webv4'}}), } {cfg => {use => 'web'}}, {cfg => {usev4 => 'webv4'}}),
@ -107,12 +101,6 @@ my @test_cases = (
'mtime' => $ddclient::now, 'mtime' => $ddclient::now,
'status-ipv6' => 'good', 'status-ipv6' => 'good',
}, },
want_cfg_changes => {
'atime' => $ddclient::now,
'ipv6' => '2001:db8::1',
'mtime' => $ddclient::now,
'status-ipv6' => 'good',
},
}, },
{ {
desc => 'legacy, fresh, usev6=webv6', desc => 'legacy, fresh, usev6=webv6',
@ -128,12 +116,6 @@ my @test_cases = (
'mtime' => $ddclient::now, 'mtime' => $ddclient::now,
'status-ipv6' => 'good', 'status-ipv6' => 'good',
}, },
want_cfg_changes => {
'atime' => $ddclient::now,
'ipv6' => '2001:db8::1',
'mtime' => $ddclient::now,
'status-ipv6' => 'good',
},
}, },
{ {
desc => 'legacy, fresh, usev4=webv4 usev6=webv6', desc => 'legacy, fresh, usev4=webv4 usev6=webv6',
@ -150,12 +132,6 @@ my @test_cases = (
'mtime' => $ddclient::now, 'mtime' => $ddclient::now,
'status-ipv4' => 'good', 'status-ipv4' => 'good',
}, },
want_cfg_changes => {
'atime' => $ddclient::now,
'ipv4' => '192.0.2.1',
'mtime' => $ddclient::now,
'status-ipv4' => 'good',
},
}, },
map({ map({
my %cfg = %{delete($_->{cfg})}; my %cfg = %{delete($_->{cfg})};
@ -211,9 +187,6 @@ my @test_cases = (
want_recap_changes => { want_recap_changes => {
'warned-min-interval' => $ddclient::now, 'warned-min-interval' => $ddclient::now,
}, },
want_cfg_changes => {
'warned-min-interval' => $ddclient::now,
},
%$_, %$_,
}; };
} {cfg => {use => 'web'}}, {cfg => {usev4 => 'webv4'}}), } {cfg => {use => 'web'}}, {cfg => {usev4 => 'webv4'}}),
@ -238,11 +211,6 @@ my @test_cases = (
'ipv4' => '192.0.2.1', 'ipv4' => '192.0.2.1',
'mtime' => $ddclient::now, 'mtime' => $ddclient::now,
}, },
want_cfg_changes => {
'atime' => $ddclient::now,
'ipv4' => '192.0.2.1',
'mtime' => $ddclient::now,
},
%$_, %$_,
}; };
} {cfg => {use => 'web'}}, {cfg => {usev4 => 'webv4'}}), } {cfg => {use => 'web'}}, {cfg => {usev4 => 'webv4'}}),
@ -265,9 +233,6 @@ my @test_cases = (
want_recap_changes => { want_recap_changes => {
'warned-min-error-interval' => $ddclient::now, 'warned-min-error-interval' => $ddclient::now,
}, },
want_cfg_changes => {
'warned-min-error-interval' => $ddclient::now,
},
%$_, %$_,
}; };
} {cfg => {use => 'web'}}, {cfg => {usev4 => 'webv4'}}), } {cfg => {use => 'web'}}, {cfg => {usev4 => 'webv4'}}),
@ -293,12 +258,6 @@ my @test_cases = (
'mtime' => $ddclient::now, 'mtime' => $ddclient::now,
'status-ipv4' => 'good', 'status-ipv4' => 'good',
}, },
want_cfg_changes => {
'atime' => $ddclient::now,
'ipv4' => '192.0.2.1',
'mtime' => $ddclient::now,
'status-ipv4' => 'good',
},
%$_, %$_,
}; };
} {cfg => {use => 'web'}}, {cfg => {usev4 => 'webv4'}}), } {cfg => {use => 'web'}}, {cfg => {usev4 => 'webv4'}}),
@ -317,7 +276,6 @@ for my $tc (@test_cases) {
# $cachef is an object that stringifies to a filename. # $cachef is an object that stringifies to a filename.
local $ddclient::globals{cache} = "$cachef"; local $ddclient::globals{cache} = "$cachef";
my %cfg = ( my %cfg = (
%{$tc->{recap} // {}}, # Simulate a previous update.
web => 'v4', web => 'v4',
webv4 => 'v4', webv4 => 'v4',
webv6 => 'v6', webv6 => 'v6',