mirror of
git://git.proxmox.com/git/pve-network.git
synced 2025-04-30 11:27:11 +02:00
ipam: netbox: factor out common api methods and unify error handling
Create a helper method that abstracts the common code used in making netbox requests. Move all api_request incovations over to using the helper method. This saves us from writing lots of repeated code. This also updates the helpers and introduces error checking there. Helpers didn't catch any errors and the invoking methods didn't as well. This meant that functions with $noerr set to 1 would still error out. We now pass $noerr to the helper functions and they behave the same as the parent methods. This requires some additional checks in the call sites of the helpers. Also canonicalize all URLs, since Netbox does that and it saves us a redirect. Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com> Tested-by: Hannes Duerr <h.duerr@proxmox.com> Link: https://lore.proxmox.com/20250310085103.30549-1-s.hanreich@proxmox.com Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
This commit is contained in:
parent
f79f21a1bc
commit
f5a439d194
|
@ -25,6 +25,21 @@ sub options {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub netbox_api_request {
|
||||||
|
my ($config, $method, $path, $params) = @_;
|
||||||
|
|
||||||
|
return PVE::Network::SDN::api_request(
|
||||||
|
$method,
|
||||||
|
"$config->{url}${path}",
|
||||||
|
[
|
||||||
|
'Content-Type' => 'application/json; charset=UTF-8',
|
||||||
|
'Authorization' => "token $config->{token}"
|
||||||
|
],
|
||||||
|
$params,
|
||||||
|
$config->{fingerprint},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
# Plugin implementation
|
# Plugin implementation
|
||||||
|
|
||||||
sub add_subnet {
|
sub add_subnet {
|
||||||
|
@ -32,62 +47,42 @@ sub add_subnet {
|
||||||
|
|
||||||
my $cidr = $subnet->{cidr};
|
my $cidr = $subnet->{cidr};
|
||||||
my $gateway = $subnet->{gateway};
|
my $gateway = $subnet->{gateway};
|
||||||
my $url = $plugin_config->{url};
|
|
||||||
my $token = $plugin_config->{token};
|
|
||||||
my $fingerprint = $plugin_config->{fingerprint};
|
|
||||||
my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Authorization' => "token $token"];
|
|
||||||
|
|
||||||
my $internalid = get_prefix_id($url, $cidr, $headers, $fingerprint);
|
if (get_prefix_id($plugin_config, $cidr, $noerr)) {
|
||||||
|
return if $noerr;
|
||||||
#create subnet
|
die "prefix $cidr already exists in netbox";
|
||||||
if (!$internalid) {
|
}
|
||||||
|
|
||||||
my $params = { prefix => $cidr };
|
eval {
|
||||||
|
netbox_api_request($plugin_config, "POST", "/ipam/prefixes/", {
|
||||||
eval {
|
prefix => $cidr
|
||||||
my $result = PVE::Network::SDN::api_request(
|
});
|
||||||
"POST", "$url/ipam/prefixes/", $headers, $params, $fingerprint );
|
};
|
||||||
};
|
if ($@) {
|
||||||
if ($@) {
|
return if $noerr;
|
||||||
die "error add subnet to ipam: $@" if !$noerr;
|
die "error adding subnet to ipam: $@";
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sub del_subnet {
|
sub del_subnet {
|
||||||
my ($class, $plugin_config, $subnetid, $subnet, $noerr) = @_;
|
my ($class, $plugin_config, $subnetid, $subnet, $noerr) = @_;
|
||||||
|
|
||||||
my $cidr = $subnet->{cidr};
|
my $cidr = $subnet->{cidr};
|
||||||
my $url = $plugin_config->{url};
|
|
||||||
my $token = $plugin_config->{token};
|
|
||||||
my $gateway = $subnet->{gateway};
|
|
||||||
my $fingerprint = $plugin_config->{fingerprint};
|
|
||||||
my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Authorization' => "token $token"];
|
|
||||||
|
|
||||||
my $internalid = get_prefix_id($url, $cidr, $headers, $fingerprint);
|
my $internalid = get_prefix_id($plugin_config, $cidr, $noerr);
|
||||||
return if !$internalid;
|
|
||||||
|
|
||||||
return; #fixme: check that prefix is empty exluding gateway, before delete
|
return; #fixme: check that prefix is empty exluding gateway, before delete
|
||||||
|
|
||||||
eval {
|
eval {
|
||||||
PVE::Network::SDN::api_request(
|
netbox_api_request($plugin_config, "DELETE", "/ipam/prefixes/$internalid/");
|
||||||
"DELETE", "$url/ipam/prefixes/$internalid/", $headers, undef, $fingerprint);
|
|
||||||
};
|
};
|
||||||
if ($@) {
|
die "error deleting subnet from ipam: $@" if $@ && !$noerr;
|
||||||
die "error deleting subnet from ipam: $@" if !$noerr;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sub add_ip {
|
sub add_ip {
|
||||||
my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $vmid, $is_gateway, $noerr) = @_;
|
my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $vmid, $is_gateway, $noerr) = @_;
|
||||||
|
|
||||||
my $mask = $subnet->{mask};
|
my $mask = $subnet->{mask};
|
||||||
my $url = $plugin_config->{url};
|
|
||||||
my $token = $plugin_config->{token};
|
|
||||||
my $fingerprint = $plugin_config->{fingerprint};
|
|
||||||
my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Authorization' => "token $token"];
|
|
||||||
|
|
||||||
my $description = undef;
|
my $description = undef;
|
||||||
if ($is_gateway) {
|
if ($is_gateway) {
|
||||||
|
@ -96,18 +91,18 @@ sub add_ip {
|
||||||
$description = "mac:$mac";
|
$description = "mac:$mac";
|
||||||
}
|
}
|
||||||
|
|
||||||
my $params = { address => "$ip/$mask", dns_name => $hostname, description => $description };
|
|
||||||
|
|
||||||
eval {
|
eval {
|
||||||
PVE::Network::SDN::api_request(
|
netbox_api_request($plugin_config, "POST", "/ipam/ip-addresses/", {
|
||||||
"POST", "$url/ipam/ip-addresses/", $headers, $params, $fingerprint);
|
address => "$ip/$mask",
|
||||||
|
dns_name => $hostname,
|
||||||
|
description => $description,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
if ($@) {
|
if ($@) {
|
||||||
if ($is_gateway) {
|
if ($is_gateway) {
|
||||||
if (!is_ip_gateway($url, $ip, $headers, $fingerprint) && !$noerr) {
|
die "error add subnet ip to ipam: ip $ip already exist: $@"
|
||||||
die "error add subnet ip to ipam: ip $ip already exist: $@";
|
if !is_ip_gateway($plugin_config, $ip, $noerr);
|
||||||
}
|
|
||||||
} elsif (!$noerr) {
|
} elsif (!$noerr) {
|
||||||
die "error add subnet ip to ipam: ip already exist: $@";
|
die "error add subnet ip to ipam: ip already exist: $@";
|
||||||
}
|
}
|
||||||
|
@ -118,11 +113,6 @@ sub update_ip {
|
||||||
my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $vmid, $is_gateway, $noerr) = @_;
|
my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $vmid, $is_gateway, $noerr) = @_;
|
||||||
|
|
||||||
my $mask = $subnet->{mask};
|
my $mask = $subnet->{mask};
|
||||||
my $url = $plugin_config->{url};
|
|
||||||
my $token = $plugin_config->{token};
|
|
||||||
my $section = $plugin_config->{section};
|
|
||||||
my $fingerprint = $plugin_config->{fingerprint};
|
|
||||||
my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Authorization' => "token $token"];
|
|
||||||
|
|
||||||
my $description = undef;
|
my $description = undef;
|
||||||
if ($is_gateway) {
|
if ($is_gateway) {
|
||||||
|
@ -131,14 +121,20 @@ sub update_ip {
|
||||||
$description = "mac:$mac";
|
$description = "mac:$mac";
|
||||||
}
|
}
|
||||||
|
|
||||||
my $params = { address => "$ip/$mask", dns_name => $hostname, description => $description };
|
my $ip_id = get_ip_id($plugin_config, $ip, $noerr);
|
||||||
|
|
||||||
my $ip_id = get_ip_id($url, $ip, $headers, $fingerprint);
|
# definedness check, because ID could be 0
|
||||||
die "can't find ip $ip in ipam" if !$ip_id;
|
if (!defined($ip_id)) {
|
||||||
|
return if $noerr;
|
||||||
|
die "could not find id for ip address $ip";
|
||||||
|
}
|
||||||
|
|
||||||
eval {
|
eval {
|
||||||
PVE::Network::SDN::api_request(
|
netbox_api_request($plugin_config, "PATCH", "/ipam/ip-addresses/$ip_id/", {
|
||||||
"PATCH", "$url/ipam/ip-addresses/$ip_id/", $headers, $params, $fingerprint);
|
address => "$ip/$mask",
|
||||||
|
dns_name => $hostname,
|
||||||
|
description => $description,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
if ($@) {
|
if ($@) {
|
||||||
die "error update ip $ip : $@" if !$noerr;
|
die "error update ip $ip : $@" if !$noerr;
|
||||||
|
@ -150,20 +146,22 @@ sub add_next_freeip {
|
||||||
|
|
||||||
my $cidr = $subnet->{cidr};
|
my $cidr = $subnet->{cidr};
|
||||||
|
|
||||||
my $url = $plugin_config->{url};
|
my $internalid = get_prefix_id($plugin_config, $cidr, $noerr);
|
||||||
my $token = $plugin_config->{token};
|
|
||||||
my $fingerprint = $plugin_config->{fingerprint};
|
|
||||||
my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Authorization' => "token $token"];
|
|
||||||
|
|
||||||
my $internalid = get_prefix_id($url, $cidr, $headers, $fingerprint);
|
# definedness check, because ID could be 0
|
||||||
|
if (!defined($internalid)) {
|
||||||
|
return if $noerr;
|
||||||
|
die "could not find id for prefix $cidr";
|
||||||
|
}
|
||||||
|
|
||||||
my $description = "mac:$mac" if $mac;
|
my $description = "mac:$mac" if $mac;
|
||||||
|
|
||||||
my $params = { dns_name => $hostname, description => $description };
|
|
||||||
|
|
||||||
eval {
|
eval {
|
||||||
my $result = PVE::Network::SDN::api_request(
|
my $result = netbox_api_request($plugin_config, "POST", "/ipam/prefixes/$internalid/available-ips/", {
|
||||||
"POST", "$url/ipam/prefixes/$internalid/available-ips/", $headers, $params, $fingerprint);
|
dns_name => $hostname,
|
||||||
|
description => $description,
|
||||||
|
});
|
||||||
|
|
||||||
my ($ip, undef) = split(/\//, $result->{address});
|
my ($ip, undef) = split(/\//, $result->{address});
|
||||||
return $ip;
|
return $ip;
|
||||||
};
|
};
|
||||||
|
@ -176,19 +174,22 @@ sub add_next_freeip {
|
||||||
sub add_range_next_freeip {
|
sub add_range_next_freeip {
|
||||||
my ($class, $plugin_config, $subnet, $range, $data, $noerr) = @_;
|
my ($class, $plugin_config, $subnet, $range, $data, $noerr) = @_;
|
||||||
|
|
||||||
my $url = $plugin_config->{url};
|
my $internalid = get_iprange_id($plugin_config, $range, $noerr);
|
||||||
my $token = $plugin_config->{token};
|
|
||||||
my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Authorization' => "token $token"];
|
# definedness check, because ID could be 0
|
||||||
my $fingerprint = $plugin_config->{fingerprint};
|
if (!defined($internalid)) {
|
||||||
|
return if $noerr;
|
||||||
|
die "could not find id for ip range $range->{'start-address'}:$range->{'end-address'}";
|
||||||
|
}
|
||||||
|
|
||||||
my $internalid = get_iprange_id($url, $range, $headers, $fingerprint);
|
|
||||||
my $description = "mac:$data->{mac}" if $data->{mac};
|
my $description = "mac:$data->{mac}" if $data->{mac};
|
||||||
|
|
||||||
my $params = { dns_name => $data->{hostname}, description => $description };
|
|
||||||
|
|
||||||
eval {
|
eval {
|
||||||
my $result = PVE::Network::SDN::api_request(
|
my $result = netbox_api_request($plugin_config, "POST", "/ipam/ip-ranges/$internalid/available-ips/", {
|
||||||
"POST", "$url/ipam/ip-ranges/$internalid/available-ips/", $headers, $params, $fingerprint);
|
dns_name => $data->{hostname},
|
||||||
|
description => $description,
|
||||||
|
});
|
||||||
|
|
||||||
my ($ip, undef) = split(/\//, $result->{address});
|
my ($ip, undef) = split(/\//, $result->{address});
|
||||||
print "found ip free $ip in range $range->{'start-address'}-$range->{'end-address'}\n" if $ip;
|
print "found ip free $ip in range $range->{'start-address'}-$range->{'end-address'}\n" if $ip;
|
||||||
return $ip;
|
return $ip;
|
||||||
|
@ -204,16 +205,14 @@ sub del_ip {
|
||||||
|
|
||||||
return if !$ip;
|
return if !$ip;
|
||||||
|
|
||||||
my $url = $plugin_config->{url};
|
my $ip_id = get_ip_id($plugin_config, $ip, $noerr);
|
||||||
my $token = $plugin_config->{token};
|
if (!defined($ip_id)) {
|
||||||
my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Authorization' => "token $token"];
|
warn "could not find id for ip $ip";
|
||||||
my $fingerprint = $plugin_config->{fingerprint};
|
return;
|
||||||
|
}
|
||||||
my $ip_id = get_ip_id($url, $ip, $headers, $fingerprint);
|
|
||||||
die "can't find ip $ip in ipam" if !$ip_id;
|
|
||||||
|
|
||||||
eval {
|
eval {
|
||||||
PVE::Network::SDN::api_request("DELETE", "$url/ipam/ip-addresses/$ip_id/", $headers, undef, $fingerprint);
|
netbox_api_request($plugin_config, "DELETE", "/ipam/ip-addresses/$ip_id/");
|
||||||
};
|
};
|
||||||
if ($@) {
|
if ($@) {
|
||||||
die "error delete ip $ip : $@" if !$noerr;
|
die "error delete ip $ip : $@" if !$noerr;
|
||||||
|
@ -221,18 +220,14 @@ sub del_ip {
|
||||||
}
|
}
|
||||||
|
|
||||||
sub get_ips_from_mac {
|
sub get_ips_from_mac {
|
||||||
my ($class, $plugin_config, $mac, $zoneid) = @_;
|
my ($class, $plugin_config, $mac, $zoneid, $noerr) = @_;
|
||||||
|
|
||||||
my $url = $plugin_config->{url};
|
|
||||||
my $token = $plugin_config->{token};
|
|
||||||
my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Authorization' => "token $token"];
|
|
||||||
my $fingerprint = $plugin_config->{fingerprint};
|
|
||||||
|
|
||||||
my $ip4 = undef;
|
my $ip4 = undef;
|
||||||
my $ip6 = undef;
|
my $ip6 = undef;
|
||||||
|
|
||||||
my $data = PVE::Network::SDN::api_request(
|
my $data = eval {
|
||||||
"GET", "$url/ipam/ip-addresses/?description__ic=$mac", $headers, undef, $fingerprint);
|
netbox_api_request($plugin_config, "GET", "/ipam/ip-addresses/?description__ic=$mac");
|
||||||
|
};
|
||||||
|
|
||||||
for my $ip (@{$data->{results}}) {
|
for my $ip (@{$data->{results}}) {
|
||||||
if ($ip->{family}->{value} == 4 && !$ip4) {
|
if ($ip->{family}->{value} == 4 && !$ip4) {
|
||||||
|
@ -247,18 +242,10 @@ sub get_ips_from_mac {
|
||||||
return ($ip4, $ip6);
|
return ($ip4, $ip6);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
sub verify_api {
|
sub verify_api {
|
||||||
my ($class, $plugin_config) = @_;
|
my ($class, $plugin_config) = @_;
|
||||||
|
|
||||||
my $url = $plugin_config->{url};
|
eval { netbox_api_request($plugin_config, "GET", "/ipam/aggregates/"); };
|
||||||
my $token = $plugin_config->{token};
|
|
||||||
my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Authorization' => "token $token"];
|
|
||||||
my $fingerprint = $plugin_config->{fingerprint};
|
|
||||||
|
|
||||||
eval {
|
|
||||||
PVE::Network::SDN::api_request("GET", "$url/ipam/aggregates/", $headers, undef, $fingerprint);
|
|
||||||
};
|
|
||||||
if ($@) {
|
if ($@) {
|
||||||
die "Can't connect to netbox api: $@";
|
die "Can't connect to netbox api: $@";
|
||||||
}
|
}
|
||||||
|
@ -266,47 +253,71 @@ sub verify_api {
|
||||||
|
|
||||||
sub on_update_hook {
|
sub on_update_hook {
|
||||||
my ($class, $plugin_config) = @_;
|
my ($class, $plugin_config) = @_;
|
||||||
|
verify_api($class, $plugin_config);
|
||||||
PVE::Network::SDN::Ipams::NetboxPlugin::verify_api($class, $plugin_config);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#helpers
|
# helpers
|
||||||
|
|
||||||
sub get_prefix_id {
|
sub get_prefix_id {
|
||||||
my ($url, $cidr, $headers, $fingerprint) = @_;
|
my ($config, $cidr, $noerr) = @_;
|
||||||
my $result = PVE::Network::SDN::api_request(
|
|
||||||
"GET", "$url/ipam/prefixes/?q=$cidr", $headers, undef, $fingerprint);
|
# we need to supply any IP inside the prefix, without supplying the mask, so
|
||||||
|
# just take the one from the cidr
|
||||||
|
my ($ip, undef) = split(/\//, $cidr);
|
||||||
|
|
||||||
|
my $result = eval { netbox_api_request($config, "GET", "/ipam/prefixes/?q=$ip") };
|
||||||
|
if ($@) {
|
||||||
|
return if $noerr;
|
||||||
|
die "could not obtain ID for prefix $cidr: $@";
|
||||||
|
}
|
||||||
|
|
||||||
my $data = @{$result->{results}}[0];
|
my $data = @{$result->{results}}[0];
|
||||||
my $internalid = $data->{id};
|
my $internalid = $data->{id};
|
||||||
return $internalid;
|
return $internalid;
|
||||||
}
|
}
|
||||||
|
|
||||||
sub get_iprange_id {
|
sub get_iprange_id {
|
||||||
my ($url, $range, $headers, $fingerprint) = @_;
|
my ($config, $range, $noerr) = @_;
|
||||||
my $result = PVE::Network::SDN::api_request(
|
|
||||||
"GET",
|
my $result = eval {
|
||||||
"$url/ipam/ip-ranges/?start_address=$range->{'start-address'}&end_address=$range->{'end-address'}",
|
netbox_api_request(
|
||||||
$headers,
|
$config,
|
||||||
undef,
|
"GET",
|
||||||
$fingerprint
|
"/ipam/ip-ranges/?start_address=$range->{'start-address'}&end_address=$range->{'end-address'}",
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
if ($@) {
|
||||||
|
return if $noerr;
|
||||||
|
die "could not obtain ID for IP range $range->{'start-address'}:$range->{'end-address'}: $@";
|
||||||
|
}
|
||||||
|
|
||||||
my $data = @{$result->{results}}[0];
|
my $data = @{$result->{results}}[0];
|
||||||
my $internalid = $data->{id};
|
my $internalid = $data->{id};
|
||||||
return $internalid;
|
return $internalid;
|
||||||
}
|
}
|
||||||
|
|
||||||
sub get_ip_id {
|
sub get_ip_id {
|
||||||
my ($url, $ip, $headers, $fingerprint) = @_;
|
my ($config, $ip, $noerr) = @_;
|
||||||
my $result = PVE::Network::SDN::api_request(
|
|
||||||
"GET", "$url/ipam/ip-addresses/?q=$ip", $headers, undef, $fingerprint);
|
my $result = eval { netbox_api_request($config, "GET", "/ipam/ip-addresses/?q=$ip") };
|
||||||
|
if ($@) {
|
||||||
|
return if $noerr;
|
||||||
|
die "could not obtain ID for IP $ip: $@";
|
||||||
|
}
|
||||||
|
|
||||||
my $data = @{$result->{results}}[0];
|
my $data = @{$result->{results}}[0];
|
||||||
my $ip_id = $data->{id};
|
my $ip_id = $data->{id};
|
||||||
return $ip_id;
|
return $ip_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
sub is_ip_gateway {
|
sub is_ip_gateway {
|
||||||
my ($url, $ip, $headers, $fingerprint) = @_;
|
my ($config, $ip, $noerr) = @_;
|
||||||
my $result = PVE::Network::SDN::api_request("GET", "$url/ipam/ip-addresses/?q=$ip", $headers, undef, $fingerprint);
|
|
||||||
|
my $result = eval { netbox_api_request($config, "GET", "/ipam/ip-addresses/?q=$ip") };
|
||||||
|
if ($@) {
|
||||||
|
return if $noerr;
|
||||||
|
die "could not obtain ipam entry for address $ip: $@";
|
||||||
|
}
|
||||||
|
|
||||||
my $data = @{$result->{data}}[0];
|
my $data = @{$result->{data}}[0];
|
||||||
my $description = $data->{description};
|
my $description = $data->{description};
|
||||||
my $is_gateway = 1 if $description eq 'gateway';
|
my $is_gateway = 1 if $description eq 'gateway';
|
||||||
|
|
Loading…
Reference in a new issue