Merge pull request #520 from tchebb/add-digitalocean

This commit is contained in:
Sandro 2023-03-20 01:35:30 +01:00 committed by GitHub
commit 419716fd22
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 137 additions and 1 deletions

View file

@ -347,3 +347,11 @@ ssl=yes # use ssl-support. Works with
# login=domain.name, # login=domain.name,
# password=domain-password # password=domain-password
# my-domain.com # my-domain.com
##
## DigitalOcean (www.digitalocean.com)
##
#protocol=digitalocean, \
#zone=example.com, \
#password=api-token \
#example.com,sub.example.com

View file

@ -605,6 +605,17 @@ my %services = (
'password' => setv(T_STRING, 0, 0, 'unused', undef), 'password' => setv(T_STRING, 0, 0, 'unused', undef),
}, },
}, },
'digitalocean' => {
'updateable' => undef,
'update' => \&nic_digitalocean_update,
'examples' => \&nic_digitalocean_examples,
'variables' => {
%{$variables{'service-common-defaults'}},
'server' => setv(T_FQDNP, 1, 0, 'api.digitalocean.com', undef),
'zone' => setv(T_FQDN, 1, 0, '', undef),
'login' => setv(T_LOGIN, 0, 0, 'unused', undef),
},
},
'dinahosting' => { 'dinahosting' => {
'updateable' => undef, 'updateable' => undef,
'update' => \&nic_dinahosting_update, 'update' => \&nic_dinahosting_update,
@ -1795,7 +1806,7 @@ sub init_config {
$proto = opt('protocol') if !defined($proto); $proto = opt('protocol') if !defined($proto);
load_sha1_support($proto) if (grep (/^$proto$/, ("freedns", "nfsn"))); load_sha1_support($proto) if (grep (/^$proto$/, ("freedns", "nfsn")));
load_json_support($proto) if (grep (/^$proto$/, ("1984", "cloudflare", "gandi", "godaddy", "hetzner", "yandex", "nfsn", "njalla", "porkbun"))); load_json_support($proto) if (grep (/^$proto$/, ("1984", "cloudflare", "digitalocean", "gandi", "godaddy", "hetzner", "yandex", "nfsn", "njalla", "porkbun")));
if (!exists($services{$proto})) { if (!exists($services{$proto})) {
warning("skipping host: %s: unrecognized protocol '%s'", $h, $proto); warning("skipping host: %s: unrecognized protocol '%s'", $h, $proto);
@ -7839,6 +7850,123 @@ sub nic_enom_update {
} }
} }
sub nic_digitalocean_examples {
return <<"EoEXAMPLE";
o 'digitalocean'
The 'digitalocean' protocol updates domains hosted by Digital Ocean (https://www.digitalocean.com/).
This protocol supports both IPv4 and IPv6. It will only update an existing record; it will not
create a new one. So, before using it, make sure there's already one (and at most one) of each
record type (A and/or AAAA) you plan to update present in your Digital Ocean zone.
This protocol implements the API documented here:
https://docs.digitalocean.com/reference/api/api-reference/.
You can get your API token by following these instructions:
https://docs.digitalocean.com/reference/api/create-personal-access-token/
Available configuration variables:
* server (optional): API server. Defaults to 'api.digitalocean.com'.
* zone (required): DNS zone under which the hostname falls.
* password (required): API token from DigitalOcean Control Panel. See instructions linked above.
Example ${program}.conf file entries:
protocol=digitalocean, \\
zone=example.com, \\
password=api-token \\
example.com,sub.example.com
EoEXAMPLE
}
sub nic_digitalocean_update_one {
my ($h, $ip, $ipv) = @_;
info("setting %s address to %s for %s", $ipv, $ip, $h);
my $server = $config{$h}{'server'};
my $type = $ipv eq 'ipv6' ? 'AAAA' : 'A';
my $headers;
$headers = "Content-Type: application/json\n";
$headers .= "Authorization: Bearer $config{$h}{'password'}\n";
my $list_url;
$list_url = "https://$server/v2/domains/$config{$h}{'zone'}/records";
$list_url .= "?name=$h";
$list_url .= "&type=$type";
my $list_resp = geturl(
proxy => opt('proxy'),
url => $list_url,
headers => $headers,
);
unless ($list_resp && header_ok($h, $list_resp)) {
$config{$h}{"status-$ipv"} = 'failed';
failed("listing %s %s: Failed connection or bad response from %s.", $h, $ipv, $server);
return;
}
$list_resp =~ s/^.*?\n\n//s; # Strip header
my $list = eval { decode_json($list_resp) };
if ($@) {
$config{$h}{"status-$ipv"} = 'failed';
failed("listing %s %s: JSON decoding failure", $h, $ipv);
return;
}
my $elem = $list;
unless ((ref($elem) eq 'HASH') &&
(ref ($elem = $elem->{'domain_records'}) eq 'ARRAY') &&
(@$elem == 1 && ref ($elem = $elem->[0]) eq 'HASH')) {
$config{$h}{"status-$ipv"} = 'failed';
failed("listing %s %s: no record, multiple records, or malformed JSON", $h, $ipv);
return;
}
my $current_ip = $elem->{'data'};
my $record_id = $elem->{'id'};
if ($current_ip eq $ip) {
info("updating %s %s: IP is already %s, no update needed.", $h, $ipv, $ip);
} else {
my $update_data = encode_json({'type' => $type, 'data' => $ip});
my $update_resp = geturl(
proxy => opt('proxy'),
url => "https://$server/v2/domains/$config{$h}{'zone'}/records/$record_id",
method => 'PATCH',
headers => $headers,
data => $update_data,
);
unless ($update_resp && header_ok($h, $update_resp)) {
$config{$h}{"status-$ipv"} = 'failed';
failed("updating %s %s: Failed connection or bad response from %s.", $h, $ipv, $server);
return;
}
}
$config{$h}{"status-$ipv"} = 'good';
$config{$h}{"ip-$ipv"} = $ip;
$config{$h}{"mtime"} = $now;
}
sub nic_digitalocean_update {
debug("\nnic_digitalocean_update -------------------");
foreach my $h (@_) {
my $ipv4 = delete $config{$h}{'wantipv4'};
my $ipv6 = delete $config{$h}{'wantipv6'};
if ($ipv4) {
nic_digitalocean_update_one($h, $ipv4, 'ipv4');
}
if ($ipv6) {
nic_digitalocean_update_one($h, $ipv6, 'ipv6');
}
}
}
# Execute main() if this file is run as a script or run via PAR (https://metacpan.org/pod/PAR), # Execute main() if this file is run as a script or run via PAR (https://metacpan.org/pod/PAR),
# otherwise do nothing. This "modulino" pattern makes it possible to import this file as a module # otherwise do nothing. This "modulino" pattern makes it possible to import this file as a module
# and test its functions directly; there's no need for test-only command-line arguments or stdout # and test its functions directly; there's no need for test-only command-line arguments or stdout