* [pve-devel] [PATCH pve-network v6 1/2] ipam: add Nautobot plugin
[not found] <20250922091942.4715-1-lou.lecrivain@orange.fr>
@ 2025-09-22 9:19 ` Lou Lecrivain via pve-devel
2025-09-22 9:19 ` [pve-devel] [PATCH pve-network v6 2/2] ipam: add test cases for nautobot plugin Lou Lecrivain via pve-devel
` (2 subsequent siblings)
3 siblings, 0 replies; 4+ messages in thread
From: Lou Lecrivain via pve-devel @ 2025-09-22 9:19 UTC (permalink / raw)
To: pve-devel; +Cc: lou.lecrivain
[-- Attachment #1: Type: message/rfc822, Size: 19243 bytes --]
From: lou.lecrivain@orange.fr
To: pve-devel@lists.proxmox.com
Subject: [PATCH pve-network v6 1/2] ipam: add Nautobot plugin
Date: Mon, 22 Sep 2025 11:19:39 +0200
Message-ID: <20250922091942.4715-2-lou.lecrivain@orange.fr>
From: Lou Lecrivain <lou.lecrivain@wdz.de>
Nautobot is a network source of truth [0] and as such this plugin offers
the possibility to automatically enter IP addresses and subnets
(prefixes in Nautobot jargon) into Nautobot as soon as they are created
in Proxmox VE.
Limitations:
* Only Nautobot versions from v2.4.14 and onwards are supported,
previous versions didn't had support for IP range [1].
* The IPAM plugin currently does not recognize whether VMs/CTs are
active/online and initially sets the status to Active but when the
VMs/CTs are switched off the status is not deactivated.
* Nautobot has the possibility to map DNS names to IP addresses.
However, since we have no standardized way to set the DNS names in
Proxmox VE (for containers the container name in Proxmox VE is set as
the host name in the container, but this is not possible for VMs) we
refrain from setting a DNS name at the moment.
* Nautobot does not have a field in IP address objects for a MAC address
or to mark them as gateway, so we write this in the comment.
[0] https://networktocode.com/nautobot/
[1] https://github.com/nautobot/nautobot/releases/tag/v2.4.14
Co-authored-by: lou lecrivain <lou.lecrivain@wdz.de>
Signed-off-by: Hannes Duerr <h.duerr@proxmox.com>
Signed-off-by: Lou Lecrivain <lou.lecrivain@wdz.de>
---
src/PVE/API2/Network/SDN/Ipams.pm | 1 +
src/PVE/Network/SDN/Ipams.pm | 3 +
src/PVE/Network/SDN/Ipams/Makefile | 2 +-
src/PVE/Network/SDN/Ipams/NautobotPlugin.pm | 460 ++++++++++++++++++++
4 files changed, 465 insertions(+), 1 deletion(-)
create mode 100644 src/PVE/Network/SDN/Ipams/NautobotPlugin.pm
diff --git a/src/PVE/API2/Network/SDN/Ipams.pm b/src/PVE/API2/Network/SDN/Ipams.pm
index 9955817..4e76bce 100644
--- a/src/PVE/API2/Network/SDN/Ipams.pm
+++ b/src/PVE/API2/Network/SDN/Ipams.pm
@@ -12,6 +12,7 @@ use PVE::Network::SDN::Ipams::Plugin;
use PVE::Network::SDN::Ipams::PVEPlugin;
use PVE::Network::SDN::Ipams::PhpIpamPlugin;
use PVE::Network::SDN::Ipams::NetboxPlugin;
+use PVE::Network::SDN::Ipams::NautobotPlugin;
use PVE::Network::SDN::Dhcp;
use PVE::Network::SDN::Vnets;
use PVE::Network::SDN::Zones;
diff --git a/src/PVE/Network/SDN/Ipams.pm b/src/PVE/Network/SDN/Ipams.pm
index 00aa20c..ee36ab5 100644
--- a/src/PVE/Network/SDN/Ipams.pm
+++ b/src/PVE/Network/SDN/Ipams.pm
@@ -12,11 +12,14 @@ use PVE::Network;
use PVE::Network::SDN::Ipams::PVEPlugin;
use PVE::Network::SDN::Ipams::NetboxPlugin;
+use PVE::Network::SDN::Ipams::NautobotPlugin;
use PVE::Network::SDN::Ipams::PhpIpamPlugin;
use PVE::Network::SDN::Ipams::Plugin;
+
PVE::Network::SDN::Ipams::PVEPlugin->register();
PVE::Network::SDN::Ipams::NetboxPlugin->register();
+PVE::Network::SDN::Ipams::NautobotPlugin->register();
PVE::Network::SDN::Ipams::PhpIpamPlugin->register();
PVE::Network::SDN::Ipams::Plugin->init();
diff --git a/src/PVE/Network/SDN/Ipams/Makefile b/src/PVE/Network/SDN/Ipams/Makefile
index 4e7d65f..75e5b9a 100644
--- a/src/PVE/Network/SDN/Ipams/Makefile
+++ b/src/PVE/Network/SDN/Ipams/Makefile
@@ -1,4 +1,4 @@
-SOURCES=Plugin.pm PhpIpamPlugin.pm NetboxPlugin.pm PVEPlugin.pm
+SOURCES=Plugin.pm PhpIpamPlugin.pm NetboxPlugin.pm PVEPlugin.pm NautobotPlugin.pm
PERL5DIR=${DESTDIR}/usr/share/perl5
diff --git a/src/PVE/Network/SDN/Ipams/NautobotPlugin.pm b/src/PVE/Network/SDN/Ipams/NautobotPlugin.pm
new file mode 100644
index 0000000..447bfaa
--- /dev/null
+++ b/src/PVE/Network/SDN/Ipams/NautobotPlugin.pm
@@ -0,0 +1,460 @@
+package PVE::Network::SDN::Ipams::NautobotPlugin;
+
+use strict;
+use warnings;
+use PVE::INotify;
+use PVE::Cluster;
+use PVE::Tools;
+use NetAddr::IP;
+use Net::Subnet qw(subnet_matcher);
+use version 0.77;
+
+use base('PVE::Network::SDN::Ipams::Plugin');
+
+our $MINIMAL_NAUTOBOT_VERSION = version->declare('v2.4.14');
+
+sub type {
+ return 'nautobot';
+}
+
+sub properties {
+ return {
+ namespace => {
+ type => 'string',
+ },
+ };
+}
+
+sub options {
+ return {
+ url => { optional => 0 },
+ token => { optional => 0 },
+ namespace => { optional => 0 },
+ fingerprint => { optional => 1 },
+ };
+}
+
+sub default_ip_status {
+ return 'Active';
+}
+
+sub nautobot_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}",
+ 'Accept' => "application/json",
+ ],
+ $params,
+ $config->{fingerprint},
+ );
+}
+
+sub add_subnet {
+ my ($class, $config, undef, $subnet, $noerr) = @_;
+
+ my $cidr = $subnet->{cidr};
+ my $namespace = $config->{namespace};
+
+ my $internalid = get_prefix_id($config, $cidr, $noerr);
+ if ($internalid) {
+ return if $noerr;
+ die "could not add the subnet $subnet because it already exists in nautobot\n";
+ }
+
+ my $params = {
+ prefix => $cidr,
+ namespace => $namespace,
+ status => default_ip_status(),
+ };
+
+ eval { nautobot_api_request($config, "POST", "/ipam/prefixes/", $params); };
+ if ($@) {
+ return if $noerr;
+ die "error adding the subnet $subnet to nautobot $@\n";
+ }
+}
+
+sub update_subnet {
+ my ($class, $plugin_config, $subnetid, $subnet, $old_subnet, $noerr) = @_;
+ # dhcp ranges are not supported in nautobot so we don't have to update them
+}
+
+sub del_subnet {
+ my ($class, $config, $subnetid, $subnet, $noerr) = @_;
+
+ my $cidr = $subnet->{cidr};
+
+ my $internalid = get_prefix_id($config, $cidr, $noerr);
+ if (!$internalid) {
+ warn("could not find delete the subnet $cidr because it does not exist in nautobot\n");
+ return;
+ }
+
+ if (!subnet_is_deletable($config, $subnetid, $subnet, $internalid, $noerr)) {
+ return if $noerr;
+ die "could not delete the subnet $cidr, it still contains ip addresses!\n";
+ }
+
+ # delete associated gateway IP addresses
+ $class->empty_subnet($config, $subnetid, $subnet, $internalid, $noerr);
+
+ eval { nautobot_api_request($config, "DELETE", "/ipam/prefixes/$internalid/"); };
+ if ($@) {
+ return if $noerr;
+ die "error deleting subnet from nautobot: $@\n";
+ }
+ return 1;
+}
+
+sub add_ip {
+ my ($class, $config, undef, $subnet, $ip, $hostname, $mac, undef, $is_gateway, $noerr) = @_;
+
+ my $mask = $subnet->{mask};
+ my $namespace = $config->{namespace};
+
+ my $description = undef;
+ if ($is_gateway) {
+ $description = 'gateway';
+ } elsif ($mac) {
+ $description = "mac:$mac";
+ }
+
+ my $params = {
+ address => "$ip/$mask",
+ type => "dhcp",
+ description => $description,
+ namespace => $namespace,
+ status => default_ip_status(),
+ };
+
+ eval { nautobot_api_request($config, "POST", "/ipam/ip-addresses/", $params); };
+
+ if ($@) {
+ if ($is_gateway) {
+ die "error add subnet ip to ipam: ip $ip already exist: $@"
+ if !is_ip_gateway($config, $ip, $noerr);
+ } elsif (!$noerr) {
+ die "error add subnet ip to ipam: ip already exist: $@";
+ }
+ }
+}
+
+sub add_next_freeip {
+ my ($class, $config, undef, $subnet, $hostname, $mac, undef, $noerr) = @_;
+
+ my $cidr = $subnet->{cidr};
+ my $namespace = $config->{namespace};
+
+ my $internalid = get_prefix_id($config, $cidr, $noerr);
+ if (!defined($internalid)) {
+ return if $noerr;
+ die "could not find prefix $cidr in nautobot\n";
+ }
+
+ my $description = undef;
+ $description = "mac:$mac" if $mac;
+
+ my $params = {
+ type => "dhcp",
+ description => $description,
+ namespace => $namespace,
+ status => default_ip_status(),
+ };
+
+ my $response = eval {
+ return nautobot_api_request(
+ $config, "POST", "/ipam/prefixes/$internalid/available-ips/", $params,
+ );
+ };
+ if ($@ || !$response) {
+ return if $noerr;
+ die "could not allocate ip in subnet $cidr: $@\n";
+ }
+
+ my $ip = NetAddr::IP->new($response->{address});
+
+ return $ip->addr;
+}
+
+sub add_range_next_freeip {
+ my ($class, $config, $subnet, $range, $data, $noerr) = @_;
+
+ my $cidr = $subnet->{cidr};
+ my $namespace = $config->{namespace};
+
+ my $internalid = get_prefix_id($config, $cidr, $noerr);
+ if (!defined($internalid)) {
+ return if $noerr;
+ die "could not find prefix $cidr in nautobot\n";
+ }
+
+ my $description = undef;
+ $description = "mac:$data->{mac}" if $data->{mac};
+
+ my $params = {
+ type => "dhcp",
+ description => $description,
+ namespace => $namespace,
+ status => default_ip_status(),
+ };
+
+ my $range_start = $range->{'start-address'};
+ my $range_end = $range->{'end-address'};
+
+ my $response = eval {
+ return nautobot_api_request(
+ $config,
+ "POST",
+ "/ipam/prefixes/$internalid/available-ips/"
+ . "?range_start=$range_start&range_end=$range_end",
+ $params,
+ );
+ };
+ if ($@ || !$response) {
+ return if $noerr;
+ die "could not allocate ip in subnet $cidr: $@\n";
+ }
+
+ my $ip = NetAddr::IP->new($response->{address});
+
+ return $ip->addr;
+}
+
+sub update_ip {
+ my ($class, $config, $subnetid, $subnet, $ip, $hostname, $mac, undef, $is_gateway, $noerr) = @_;
+
+ my $mask = $subnet->{mask};
+ my $namespace = $config->{namespace};
+
+ my $description = undef;
+ if ($is_gateway) {
+ $description = 'gateway';
+ } elsif ($mac) {
+ $description = "mac:$mac";
+ }
+
+ my $params = {
+ address => "$ip/$mask",
+ type => "dhcp",
+ description => $description,
+ namespace => $namespace,
+ status => default_ip_status(),
+ };
+
+ my $ip_id = get_ip_id($config, $ip, $noerr);
+ if (!defined($ip_id)) {
+ return if $noerr;
+ die "could not find the ip $ip in nautobot\n";
+ }
+
+ eval { nautobot_api_request($config, "PATCH", "/ipam/ip-addresses/$ip_id/", $params); };
+ if ($@) {
+ return if $noerr;
+ die "error updating ip $ip: $@";
+ }
+}
+
+sub del_ip {
+ my ($class, $config, undef, undef, $ip, $noerr) = @_;
+
+ return if !$ip;
+
+ my $ip_id = get_ip_id($config, $ip, $noerr);
+ if (!defined($ip_id)) {
+ warn("could not find the ip $ip in nautobot\n");
+ return;
+ }
+
+ eval { nautobot_api_request($config, "DELETE", "/ipam/ip-addresses/$ip_id/"); };
+ if ($@) {
+ return if $noerr;
+ die "error deleting ip $ip : $@\n";
+ }
+
+ return 1;
+}
+
+sub empty_subnet {
+ my ($class, $config, $subnetid, $subnet, $subnetuuid, $noerr) = @_;
+
+ my $namespace = $config->{namespace};
+
+ my $response = eval {
+ return nautobot_api_request(
+ $config,
+ "GET",
+ "/ipam/ip-addresses/?namespace=$namespace&parent=$subnetuuid",
+ );
+ };
+ if ($@) {
+ return if $noerr;
+ die "could not find the subnet $subnet in nautobot: $@\n";
+ }
+
+ for my $ip (@{ $response->{results} }) {
+ del_ip($class, $config, undef, undef, $ip->{host}, $noerr);
+ }
+
+ return 1;
+}
+
+sub subnet_is_deletable {
+ my ($config, $subnetid, $subnet, $subnetuuid, $noerr) = @_;
+
+ my $namespace = $config->{namespace};
+
+ my $response = eval {
+ return nautobot_api_request(
+ $config,
+ "GET",
+ "/ipam/ip-addresses/?namespace=$namespace&parent=$subnetuuid",
+ );
+ };
+ if ($@) {
+ return if $noerr;
+ die "error querying prefix $subnet: $@\n";
+ }
+ my $n_ips = scalar $response->{results}->@*;
+
+ # least costly check operation 1st
+ return 1 if ($n_ips == 0);
+
+ for my $ip (values $response->{results}->@*) {
+ if (!is_ip_gateway($config, $ip->{host}, $noerr)) {
+ # some remaining IP is not a gateway so we can't delete the subnet
+ return 0;
+ }
+ }
+ #all remaining IPs are gateways
+ return 1;
+}
+
+sub verify_api {
+ my ($class, $config) = @_;
+
+ my $namespace = $config->{namespace};
+
+ # check if the namespace and the status "Active" exist
+ eval {
+ my $status = nautobot_api_request($config, "GET", "/status/");
+ my $current_version = version->parse($status->{'nautobot-version'});
+ $current_version >= $MINIMAL_NAUTOBOT_VERSION
+ || die "version $current_version is below minimal required Nautobot version "
+ . "$MINIMAL_NAUTOBOT_VERSION";
+
+ get_namespace_id($config, $namespace) // die "namespace $namespace does not exist";
+ get_status_id($config, default_ip_status())
+ // die "the status " . default_ip_status() . " does not exist";
+
+ };
+ if ($@) {
+ die "could not use nautobot api: $@\n";
+ }
+}
+
+sub get_ips_from_mac {
+ my ($class, $config, $mac, $zone) = @_;
+
+ my $ip4 = undef;
+ my $ip6 = undef;
+
+ my $data = eval { nautobot_api_request($config, "GET", "/ipam/ip-addresses/?q=$mac"); };
+ if ($@) {
+ die "could not query ip address entry for mac $mac: $@";
+ }
+
+ for my $ip (@{ $data->{results} }) {
+ if ($ip->{ip_version} == 4 && !$ip4) {
+ ($ip4, undef) = split(/\//, $ip->{address});
+ }
+
+ if ($ip->{ip_version} == 6 && !$ip6) {
+ ($ip6, undef) = split(/\//, $ip->{address});
+ }
+ }
+
+ return ($ip4, $ip6);
+}
+
+sub on_update_hook {
+ my ($class, $config) = @_;
+
+ PVE::Network::SDN::Ipams::NautobotPlugin::verify_api($class, $config);
+}
+
+sub get_ip_id {
+ my ($config, $ip, $noerr) = @_;
+
+ my $result =
+ eval { return nautobot_api_request($config, "GET", "/ipam/ip-addresses/?address=$ip"); };
+ if ($@) {
+ return if $noerr;
+ die "error while querying for ip $ip id: $@\n";
+ }
+
+ my $data = @{ $result->{results} }[0];
+ return $data->{id};
+}
+
+sub get_prefix_id {
+ my ($config, $cidr, $noerr) = @_;
+
+ my $result =
+ eval { return nautobot_api_request($config, "GET", "/ipam/prefixes/?prefix=$cidr"); };
+ if ($@) {
+ return if $noerr;
+ die "error while querying for cidr $cidr prefix id: $@\n";
+ }
+
+ my $data = @{ $result->{results} }[0];
+ return $data->{id};
+}
+
+sub get_namespace_id {
+ my ($config, $namespace, $noerr) = @_;
+
+ my $result =
+ eval { return nautobot_api_request($config, "GET", "/ipam/namespaces/?name=$namespace"); };
+ if ($@) {
+ return if $noerr;
+ die "error while querying for namespace $namespace id: $@\n";
+ }
+
+ my $data = @{ $result->{results} }[0];
+ return $data->{id};
+}
+
+sub get_status_id {
+ my ($config, $status, $noerr) = @_;
+
+ my $result =
+ eval { return nautobot_api_request($config, "GET", "/extras/statuses/?name=$status"); };
+ if ($@) {
+ return if $noerr;
+ die "error while querying for status $status id: $@\n";
+ }
+
+ my $data = @{ $result->{results} }[0];
+ return $data->{id};
+}
+
+sub is_ip_gateway {
+ my ($config, $ip, $noerr) = @_;
+
+ my $result =
+ eval { return nautobot_api_request($config, "GET", "/ipam/ip-addresses/?address=$ip"); };
+ if ($@) {
+ return if $noerr;
+ die "error while checking if $ip is a gateway: $@\n";
+ }
+
+ my $data = @{ $result->{results} }[0];
+ return $data->{description} eq 'gateway';
+}
+
+1;
--
2.47.3
[-- Attachment #2: Type: text/plain, Size: 160 bytes --]
_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
^ permalink raw reply [flat|nested] 4+ messages in thread
* [pve-devel] [PATCH pve-network v6 2/2] ipam: add test cases for nautobot plugin
[not found] <20250922091942.4715-1-lou.lecrivain@orange.fr>
2025-09-22 9:19 ` [pve-devel] [PATCH pve-network v6 1/2] ipam: add Nautobot plugin Lou Lecrivain via pve-devel
@ 2025-09-22 9:19 ` Lou Lecrivain via pve-devel
2025-09-22 9:19 ` [pve-devel] [PATCH pve-docs v6] add documentation for nautobot ipam plugin Lou Lecrivain via pve-devel
2025-09-22 9:19 ` [pve-devel] [PATCH pve-manager v6] ipam: add UI dialog " Lou Lecrivain via pve-devel
3 siblings, 0 replies; 4+ messages in thread
From: Lou Lecrivain via pve-devel @ 2025-09-22 9:19 UTC (permalink / raw)
To: pve-devel; +Cc: lou.lecrivain
[-- Attachment #1: Type: message/rfc822, Size: 13727 bytes --]
From: lou.lecrivain@orange.fr
To: pve-devel@lists.proxmox.com
Subject: [PATCH pve-network v6 2/2] ipam: add test cases for nautobot plugin
Date: Mon, 22 Sep 2025 11:19:40 +0200
Message-ID: <20250922091942.4715-3-lou.lecrivain@orange.fr>
From: Lou Lecrivain <lou.lecrivain@wdz.de>
Co-authored-by: Lou Lecrivain <lou.lecrivain@wdz.de>
Signed-off-by: Hannes Duerr <h.duerr@proxmox.com>
Signed-off-by: Lou Lecrivain <lou.lecrivain@wdz.de>
---
src/test/ipams/nautobot/expected.add_ip | 11 +++++++++
.../ipams/nautobot/expected.add_ip_notgateway | 11 +++++++++
.../ipams/nautobot/expected.add_next_freeip | 11 +++++++++
src/test/ipams/nautobot/expected.add_subnet | 11 +++++++++
src/test/ipams/nautobot/expected.del_ip | 11 +++++++++
src/test/ipams/nautobot/expected.update_ip | 11 +++++++++
src/test/ipams/nautobot/ipam_config | 24 +++++++++++++++++++
src/test/ipams/nautobot/sdn_config | 20 ++++++++++++++++
src/test/ipams/netbox/ipam_config | 8 ++++++-
src/test/ipams/phpipam/ipam_config | 8 ++++++-
10 files changed, 124 insertions(+), 2 deletions(-)
create mode 100644 src/test/ipams/nautobot/expected.add_ip
create mode 100644 src/test/ipams/nautobot/expected.add_ip_notgateway
create mode 100644 src/test/ipams/nautobot/expected.add_next_freeip
create mode 100644 src/test/ipams/nautobot/expected.add_subnet
create mode 100644 src/test/ipams/nautobot/expected.del_ip
create mode 100644 src/test/ipams/nautobot/expected.update_ip
create mode 100644 src/test/ipams/nautobot/ipam_config
create mode 100644 src/test/ipams/nautobot/sdn_config
diff --git a/src/test/ipams/nautobot/expected.add_ip b/src/test/ipams/nautobot/expected.add_ip
new file mode 100644
index 0000000..bec1f3a
--- /dev/null
+++ b/src/test/ipams/nautobot/expected.add_ip
@@ -0,0 +1,11 @@
+bless( {
+ '_content' => '{"address":"10.0.0.1/24","description":"gateway","namespace":"TestNamespace","status":"Active","type":"dhcp"}',
+ '_headers' => bless( {
+ 'accept' => 'application/json',
+ 'authorization' => 'token FAKETESTTOKEN',
+ 'content-type' => 'application/json; charset=UTF-8',
+ }, 'HTTP::Headers' ),
+ '_max_body_size' => undef,
+ '_method' => 'POST',
+ '_uri' => bless( do{\(my $o = 'http://localhost:8080/api/ipam/ip-addresses/')}, 'URI::http' )
+ }, 'HTTP::Request' );
diff --git a/src/test/ipams/nautobot/expected.add_ip_notgateway b/src/test/ipams/nautobot/expected.add_ip_notgateway
new file mode 100644
index 0000000..bae92e6
--- /dev/null
+++ b/src/test/ipams/nautobot/expected.add_ip_notgateway
@@ -0,0 +1,11 @@
+bless( {
+ '_content' => '{"address":"10.0.0.1/24","description":"mac:da:65:8f:18:9b:6f","namespace":"TestNamespace","status":"Active","type":"dhcp"}',
+ '_headers' => bless( {
+ 'accept' => 'application/json',
+ 'authorization' => 'token FAKETESTTOKEN',
+ 'content-type' => 'application/json; charset=UTF-8',
+ }, 'HTTP::Headers' ),
+ '_max_body_size' => undef,
+ '_method' => 'POST',
+ '_uri' => bless( do{\(my $o = 'http://localhost:8080/api/ipam/ip-addresses/')}, 'URI::http' )
+ }, 'HTTP::Request' );
diff --git a/src/test/ipams/nautobot/expected.add_next_freeip b/src/test/ipams/nautobot/expected.add_next_freeip
new file mode 100644
index 0000000..0181719
--- /dev/null
+++ b/src/test/ipams/nautobot/expected.add_next_freeip
@@ -0,0 +1,11 @@
+bless( {
+ '_content' => '{"description":"mac:da:65:8f:18:9b:6f","namespace":"TestNamespace","status":"Active","type":"dhcp"}',
+ '_headers' => bless( {
+ 'accept' => 'application/json',
+ 'authorization' => 'token FAKETESTTOKEN',
+ 'content-type' => 'application/json; charset=UTF-8',
+ }, 'HTTP::Headers' ),
+ '_max_body_size' => undef,
+ '_method' => 'POST',
+ '_uri' => bless( do{\(my $o = 'http://localhost:8080/api/ipam/prefixes/1/available-ips/')}, 'URI::http' )
+ }, 'HTTP::Request' );
diff --git a/src/test/ipams/nautobot/expected.add_subnet b/src/test/ipams/nautobot/expected.add_subnet
new file mode 100644
index 0000000..78f911d
--- /dev/null
+++ b/src/test/ipams/nautobot/expected.add_subnet
@@ -0,0 +1,11 @@
+bless({
+ '_content' => '{"namespace":"TestNamespace","prefix":"10.0.0.0/24","status":"Active"}',
+ '_headers' => bless({
+ 'accept' => 'application/json',
+ 'authorization' => 'token FAKETESTTOKEN',
+ 'content-type' => 'application/json; charset=UTF-8',
+ }, 'HTTP::Headers'),
+ '_max_body_size' => undef,
+ '_method' => 'POST',
+ '_uri' => bless(do{\(my $o = 'http://localhost:8080/api/ipam/prefixes/')}, 'URI::http'),
+}, 'HTTP::Request');
diff --git a/src/test/ipams/nautobot/expected.del_ip b/src/test/ipams/nautobot/expected.del_ip
new file mode 100644
index 0000000..eaa133e
--- /dev/null
+++ b/src/test/ipams/nautobot/expected.del_ip
@@ -0,0 +1,11 @@
+bless( {
+ '_content' => '',
+ '_headers' => bless( {
+ 'accept' => 'application/json',
+ 'authorization' => 'token FAKETESTTOKEN',
+ 'content-type' => 'application/json; charset=UTF-8',
+ }, 'HTTP::Headers' ),
+ '_max_body_size' => undef,
+ '_method' => 'DELETE',
+ '_uri' => bless( do{\(my $o = 'http://localhost:8080/api/ipam/ip-addresses/1/')}, 'URI::http' )
+ }, 'HTTP::Request' );
diff --git a/src/test/ipams/nautobot/expected.update_ip b/src/test/ipams/nautobot/expected.update_ip
new file mode 100644
index 0000000..be6df47
--- /dev/null
+++ b/src/test/ipams/nautobot/expected.update_ip
@@ -0,0 +1,11 @@
+bless( {
+ '_content' => '{"address":"10.0.0.1/24","description":"gateway","namespace":"TestNamespace","status":"Active","type":"dhcp"}',
+ '_headers' => bless( {
+ 'accept' => 'application/json',
+ 'authorization' => 'token FAKETESTTOKEN',
+ 'content-type' => 'application/json; charset=UTF-8',
+ }, 'HTTP::Headers' ),
+ '_max_body_size' => undef,
+ '_method' => 'PATCH',
+ '_uri' => bless( do{\(my $o = 'http://localhost:8080/api/ipam/ip-addresses/1/')}, 'URI::http' )
+ }, 'HTTP::Request' );
diff --git a/src/test/ipams/nautobot/ipam_config b/src/test/ipams/nautobot/ipam_config
new file mode 100644
index 0000000..014d6b1
--- /dev/null
+++ b/src/test/ipams/nautobot/ipam_config
@@ -0,0 +1,24 @@
+{
+ 'ids' => {
+ 'phpipam' => {
+ 'url' => 'https://localhost/api/apiadmin',
+ 'type' => 'phpipam',
+ 'section' => 1,
+ 'token' => 'JPHkPSLB4O_XL-GQz4qtEFmNpx-99Htw'
+ },
+ 'pve' => {
+ 'type' => 'pve'
+ },
+ 'netbox' => {
+ 'token' => '0123456789abcdef0123456789abcdef01234567',
+ 'type' => 'netbox',
+ 'url' => 'http://localhost:8000/api'
+ },
+ 'nautobot' => {
+ 'url' => 'http://localhost:8080/api',
+ 'type' => 'nautobot',
+ 'token' => 'FAKETESTTOKEN',
+ 'namespace' => 'TestNamespace'
+ }
+ },
+}
diff --git a/src/test/ipams/nautobot/sdn_config b/src/test/ipams/nautobot/sdn_config
new file mode 100644
index 0000000..784cd95
--- /dev/null
+++ b/src/test/ipams/nautobot/sdn_config
@@ -0,0 +1,20 @@
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { type => "vnet", zone => "myzone" },
+ },
+ },
+
+ zones => {
+ ids => { myzone => { ipam => "nautobot" } },
+ },
+
+ subnets => {
+ ids => { 'myzone-10.0.0.0-24' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ }
+ }
+ }
+}
diff --git a/src/test/ipams/netbox/ipam_config b/src/test/ipams/netbox/ipam_config
index a33be30..6a6fe44 100644
--- a/src/test/ipams/netbox/ipam_config
+++ b/src/test/ipams/netbox/ipam_config
@@ -13,6 +13,12 @@
'token' => '0123456789abcdef0123456789abcdef01234567',
'type' => 'netbox',
'url' => 'http://localhost:8000/api'
- }
+ },
+ 'nautobot' => {
+ 'url' => 'http://localhost:8080/api',
+ 'type' => 'nautobot',
+ 'token' => 'FAKETESTTOKEN',
+ 'namespace' => 'TestNamespace'
+ }
},
}
diff --git a/src/test/ipams/phpipam/ipam_config b/src/test/ipams/phpipam/ipam_config
index a33be30..014d6b1 100644
--- a/src/test/ipams/phpipam/ipam_config
+++ b/src/test/ipams/phpipam/ipam_config
@@ -13,6 +13,12 @@
'token' => '0123456789abcdef0123456789abcdef01234567',
'type' => 'netbox',
'url' => 'http://localhost:8000/api'
- }
+ },
+ 'nautobot' => {
+ 'url' => 'http://localhost:8080/api',
+ 'type' => 'nautobot',
+ 'token' => 'FAKETESTTOKEN',
+ 'namespace' => 'TestNamespace'
+ }
},
}
--
2.47.3
[-- Attachment #2: Type: text/plain, Size: 160 bytes --]
_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
^ permalink raw reply [flat|nested] 4+ messages in thread
* [pve-devel] [PATCH pve-docs v6] add documentation for nautobot ipam plugin
[not found] <20250922091942.4715-1-lou.lecrivain@orange.fr>
2025-09-22 9:19 ` [pve-devel] [PATCH pve-network v6 1/2] ipam: add Nautobot plugin Lou Lecrivain via pve-devel
2025-09-22 9:19 ` [pve-devel] [PATCH pve-network v6 2/2] ipam: add test cases for nautobot plugin Lou Lecrivain via pve-devel
@ 2025-09-22 9:19 ` Lou Lecrivain via pve-devel
2025-09-22 9:19 ` [pve-devel] [PATCH pve-manager v6] ipam: add UI dialog " Lou Lecrivain via pve-devel
3 siblings, 0 replies; 4+ messages in thread
From: Lou Lecrivain via pve-devel @ 2025-09-22 9:19 UTC (permalink / raw)
To: pve-devel; +Cc: lou.lecrivain
[-- Attachment #1: Type: message/rfc822, Size: 4614 bytes --]
From: lou.lecrivain@orange.fr
To: pve-devel@lists.proxmox.com
Subject: [PATCH pve-docs v6] add documentation for nautobot ipam plugin
Date: Mon, 22 Sep 2025 11:19:41 +0200
Message-ID: <20250922091942.4715-4-lou.lecrivain@orange.fr>
From: lou lecrivain <lou.lecrivain@wdz.de>
Co-authored-by: Jonatan Crystall <jonatan.crystall@gwdg.de>
Co-authored-by: Lou Lecrivain <lou.lecrivain@wdz.de>
Signed-off-by: Hannes Duerr <h.duerr@proxmox.com>
Signed-off-by: Lou Lecrivain <lou.lecrivain@wdz.de>
---
pvesdn.adoc | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/pvesdn.adoc b/pvesdn.adoc
index 8759691..1ee6896 100644
--- a/pvesdn.adoc
+++ b/pvesdn.adoc
@@ -817,6 +817,26 @@ Fingerprint:: The SHA-256 fingerprint of the NetBox API. Can be retrieved with
using the default certificate.
+[[pvesdn_ipam_plugin_nautobot]]
+Nautobot IPAM Plugin
+~~~~~~~~~~~~~~~~~~
+
+link:https://github.com/nautobot/nautobot[Nautobot] is an open-source IP
+Address Management (IPAM) and datacenter infrastructure management (DCIM) tool.
+
+To integrate Nautobot with {pve} SDN, create an API token in Nautobot as described
+here:
+https://docs.nautobot.com/projects/core/en/stable/user-guide/platform-functionality/users/token/
+
+The Nautobot configuration properties are:
+
+URL:: The Nautobot REST API endpoint: `http://yournautobot.domain.com/api`
+
+Token:: An API access token
+
+Namespace:: Nautobot IP Namespace
+
+
[[pvesdn_ipam_plugin_phpipam]]
phpIPAM Plugin
~~~~~~~~~~~~~~
--
2.47.3
[-- Attachment #2: Type: text/plain, Size: 160 bytes --]
_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
^ permalink raw reply [flat|nested] 4+ messages in thread
* [pve-devel] [PATCH pve-manager v6] ipam: add UI dialog for nautobot ipam plugin
[not found] <20250922091942.4715-1-lou.lecrivain@orange.fr>
` (2 preceding siblings ...)
2025-09-22 9:19 ` [pve-devel] [PATCH pve-docs v6] add documentation for nautobot ipam plugin Lou Lecrivain via pve-devel
@ 2025-09-22 9:19 ` Lou Lecrivain via pve-devel
3 siblings, 0 replies; 4+ messages in thread
From: Lou Lecrivain via pve-devel @ 2025-09-22 9:19 UTC (permalink / raw)
To: pve-devel; +Cc: lou.lecrivain
[-- Attachment #1: Type: message/rfc822, Size: 6154 bytes --]
From: lou.lecrivain@orange.fr
To: pve-devel@lists.proxmox.com
Subject: [PATCH pve-manager v6] ipam: add UI dialog for nautobot ipam plugin
Date: Mon, 22 Sep 2025 11:19:42 +0200
Message-ID: <20250922091942.4715-5-lou.lecrivain@orange.fr>
From: Lou Lecrivain <lou.lecrivain@wdz.de>
Co-authored-by: Jonatan Crystall <jonatan.crystall@gwdg.de>
Co-authored-by: Lou Lecrivain <lou.lecrivain@wdz.de>
Signed-off-by: Hannes Duerr <h.duerr@proxmox.com>
Signed-off-by: Lou Lecrivain <lou.lecrivain@wdz.de>
---
www/manager6/Makefile | 1 +
www/manager6/Utils.js | 5 +++
www/manager6/sdn/ipams/NautobotEdit.js | 62 ++++++++++++++++++++++++++
3 files changed, 68 insertions(+)
create mode 100644 www/manager6/sdn/ipams/NautobotEdit.js
diff --git a/www/manager6/Makefile b/www/manager6/Makefile
index 85f9268d..43ecbdba 100644
--- a/www/manager6/Makefile
+++ b/www/manager6/Makefile
@@ -301,6 +301,7 @@ JSSRC= \
sdn/IpamView.js \
sdn/ipams/Base.js \
sdn/ipams/NetboxEdit.js \
+ sdn/ipams/NautobotEdit.js \
sdn/ipams/PVEIpamEdit.js \
sdn/ipams/PhpIpamEdit.js \
sdn/DnsView.js \
diff --git a/www/manager6/Utils.js b/www/manager6/Utils.js
index c48ee0b2..851a5267 100644
--- a/www/manager6/Utils.js
+++ b/www/manager6/Utils.js
@@ -939,6 +939,11 @@ Ext.define('PVE.Utils', {
ipanel: 'NetboxInputPanel',
faIcon: 'th',
},
+ nautobot: {
+ name: 'Nautobot',
+ ipanel: 'NautobotInputPanel',
+ faIcon: 'th',
+ },
phpipam: {
name: 'PhpIpam',
ipanel: 'PhpIpamInputPanel',
diff --git a/www/manager6/sdn/ipams/NautobotEdit.js b/www/manager6/sdn/ipams/NautobotEdit.js
new file mode 100644
index 00000000..46162d98
--- /dev/null
+++ b/www/manager6/sdn/ipams/NautobotEdit.js
@@ -0,0 +1,62 @@
+Ext.define('PVE.sdn.ipams.NautobotInputPanel', {
+ extend: 'PVE.panel.SDNIpamBase',
+
+ onlineHelp: 'pvesdn_ipam_plugin_nautobot',
+
+ onGetValues: function(values) {
+ var me = this;
+
+ if (me.isCreate) {
+ values.type = me.type;
+ } else {
+ delete values.ipam;
+ }
+
+ return values;
+ },
+
+ initComponent: function() {
+ var me = this;
+
+ me.column1 = [
+ {
+ xtype: me.isCreate ? 'textfield' : 'displayfield',
+ name: 'ipam',
+ maxLength: 10,
+ value: me.zone || '',
+ fieldLabel: 'ID',
+ allowBlank: false,
+ },
+ {
+ xtype: 'textfield',
+ name: 'token',
+ fieldLabel: gettext('Token'),
+ allowBlank: false,
+ },
+ ];
+ me.column2 = [
+ {
+ xtype: 'textfield',
+ name: 'url',
+ fieldLabel: gettext('URL'),
+ allowBlank: false,
+ },
+ {
+ xtype: 'textfield',
+ name: 'namespace',
+ fieldLabel: gettext('Namespace'),
+ allowBlank: false,
+ },
+ ];
+ me.columnB = [
+ {
+ xtype: 'pmxFingerprintField',
+ name: 'fingerprint',
+ value: me.isCreate ? null : undefined,
+ deleteEmpty: !me.isCreate,
+ },
+ ];
+
+ me.callParent();
+ },
+});
--
2.47.3
[-- Attachment #2: Type: text/plain, Size: 160 bytes --]
_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2025-09-22 9:20 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
[not found] <20250922091942.4715-1-lou.lecrivain@orange.fr>
2025-09-22 9:19 ` [pve-devel] [PATCH pve-network v6 1/2] ipam: add Nautobot plugin Lou Lecrivain via pve-devel
2025-09-22 9:19 ` [pve-devel] [PATCH pve-network v6 2/2] ipam: add test cases for nautobot plugin Lou Lecrivain via pve-devel
2025-09-22 9:19 ` [pve-devel] [PATCH pve-docs v6] add documentation for nautobot ipam plugin Lou Lecrivain via pve-devel
2025-09-22 9:19 ` [pve-devel] [PATCH pve-manager v6] ipam: add UI dialog " Lou Lecrivain via pve-devel
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.