Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
229d6f0
Added a character limit to the name of inventory items
Chluz Jun 15, 2025
0c14ece
Certain Netgear switches have the interface name set as local, and de…
Chluz Jun 15, 2025
a730d9e
Add the ability to auto create manufacturer
Chluz Jun 15, 2025
044ba80
Fixed missing code
Chluz Jun 15, 2025
b24f83b
Added logging and fixed order of functions
Chluz Jun 15, 2025
be418c6
Missing Parenthesis
Chluz Jun 15, 2025
b5f0af2
Missing Self
Chluz Jun 15, 2025
348f231
Print debug
Chluz Jun 15, 2025
78e25b0
Fixed manufacturer creation
Chluz Jun 15, 2025
c10504f
fixed error
Chluz Jun 15, 2025
939bf09
Added slug
Chluz Jun 15, 2025
bba0740
Added attribute error for when assigned_object is None
Chluz Jun 15, 2025
a407634
Added attribute error for when assigned_object is None
Chluz Jun 15, 2025
c6c4cbd
Need to specify the attached interface id to be able to get the corre…
Chluz Jun 16, 2025
6bab34c
Added set option for VRF
Chluz Jun 16, 2025
6cf8555
Fixed error
Chluz Jun 16, 2025
c3dee8b
Fixed typo
Chluz Jun 16, 2025
dc21a4e
Fix typo
Chluz Jun 16, 2025
878df4a
Fixed typoi
Chluz Jun 16, 2025
adae0ad
Fixed final bug
Chluz Jun 16, 2025
4d42b4d
Added brctl command
Chluz Jun 17, 2025
b9e9391
Added import
Chluz Jun 17, 2025
fba05b0
Added subprocess
Chluz Jun 17, 2025
4e4a600
Bridging automation
Chluz Jun 17, 2025
f27c2c0
Fixed a few issues
Chluz Jun 17, 2025
4ce4b2e
Added parent interface settings for virtual int
Chluz Jun 17, 2025
b058c8f
Fixed indents
Chluz Jun 17, 2025
51771de
Fixed indetns2
Chluz Jun 17, 2025
ecc67fb
Fixed indent3
Chluz Jun 17, 2025
b6ec1e6
Added default parent value
Chluz Jun 17, 2025
4c63728
Fixed parent settings
Chluz Jun 17, 2025
d9d52e8
Fixes for symmetric bridge
Chluz Jun 17, 2025
ffe44b3
Fix parent detection
Chluz Jun 17, 2025
5b19065
Fixed paretn nix def
Chluz Jun 17, 2025
be43afe
Fix again
Chluz Jun 17, 2025
1ae6466
Parents can only be set for virtaul interfaces
Chluz Jun 17, 2025
baeda4e
Add the VRF to IP addresses
Chluz Jun 18, 2025
0092e03
Added IP status
Chluz Jun 18, 2025
e2de80a
Added required sys import
Chluz Jun 18, 2025
a8f9173
Changes to parent managemnt
Chluz Jun 18, 2025
8268a4e
Changes to the loop onm interfaces
Chluz Jun 18, 2025
ec29aa0
Corrected loop
Chluz Jun 18, 2025
eed894a
added test for virtual field
Chluz Jun 18, 2025
513c35e
Added DNS name from fqdn
Chluz Jun 18, 2025
3462d61
Fix for bridged indicator in IIPMI interface
Chluz Jun 18, 2025
b4d5f16
Missed the IP dns assignement for update
Chluz Jun 18, 2025
9798c62
updated dns
Chluz Jun 18, 2025
cbf63c7
fix socket
Chluz Jun 18, 2025
4bad973
fix fqdn
Chluz Jun 18, 2025
017b184
Updated the prmary mac ssignmenet
Chluz Jun 21, 2025
c84fab5
Changed ID
Chluz Jun 21, 2025
e3dd8e5
Strange error with hasattr virtual. Must be a reserved keyword
Chluz Jun 21, 2025
a992404
Corrected the print
Chluz Jun 21, 2025
781b361
Fixed primary mac assignment
Chluz Jun 21, 2025
1cc0d1d
Typo
Chluz Jun 21, 2025
8f50f2f
Set primary ipv4 and ipv6 with first available ip
Chluz Jun 21, 2025
13f1282
fixed ip assignement
Chluz Jun 21, 2025
4474668
Fixed primary ipv4 and ipv6 assignmeent
Chluz Jun 21, 2025
5b744b4
Fix possible lack of mgmt interface on switch
Chluz Jun 21, 2025
85b0730
Some PSU names come back as all, so removing this case
Chluz Jun 21, 2025
e4c3464
added the cat proc option
Chluz Jun 23, 2025
0f3cde6
Added support for RPI
Chluz Jun 28, 2025
9fa3226
Remove uneeded printing
Chluz Jun 28, 2025
53794dd
Merge pull request #1 from Solvik/master
Chluz Jul 3, 2025
07e60f0
Add the ability to auto create manufacturer
Chluz Jun 15, 2025
0099a69
Merge branch 'switch_bugfix'
Chluz Jul 5, 2025
f26692d
Need to specify the attached interface id to be able to get the corre…
Chluz Jun 16, 2025
3d6c1fa
Added set option for VRF
Chluz Jun 16, 2025
8d15a6f
Merge branch 'create_VRF'
Chluz Jul 5, 2025
ea22e34
Properly manage bridge virtual interfaces
Chluz Jun 17, 2025
abd0c44
Merge branch 'manage_bridge_int'
Chluz Jul 5, 2025
d1dce0d
Added DNS name from fqdn
Chluz Jun 18, 2025
5a3482d
Merge branch 'fqdn_for_dns'
Chluz Jul 5, 2025
8e09b13
Bugfixes on : Primary mac adress assignment, primary IPv4 and IPv6 as…
Chluz Jun 21, 2025
5fe7a9f
Merge branch 'bugfixes'
Chluz Jul 5, 2025
7c01010
Merge branch 'master'
Chluz Jul 5, 2025
80e3cae
support rPi and fix power equipment duplicate issue
Chluz Jul 5, 2025
0da9141
rpi capabilities
Chluz Jul 5, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions netbox_agent/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@ def get_config():
default=None,
help="Command to output hostname, used as Device's name in netbox",
)
p.add_argument(
"--fqdn_cmd",
default=None,
help="Command to output fully qualified domain name, used as Device's DNS name in netbox",
)
p.add_argument(
"--device.platform",
default=None,
Expand All @@ -90,6 +95,7 @@ def get_config():
"--device.chassis_role", default=r"Server Chassis", help="role to use for a chassis"
)
p.add_argument("--device.server_role", default=r"Server", help="role to use for a server")
p.add_argument("--device.autocreate_device_type", default=True, help="Define whether a device type should be create when it doesnt exist.")
p.add_argument("--tenant.driver", help="tenant driver, ie cmd, file")
p.add_argument("--tenant.driver_file", help="tenant driver custom driver file path")
p.add_argument("--tenant.regex", help="tenant regex to extract Netbox tenant slug")
Expand Down Expand Up @@ -126,6 +132,8 @@ def get_config():
default="name",
help="What property to use as NIC identifier",
)
p.add_argument("--network.vrf", default=None, help="Set VRF for network interfaces")
p.add_argument("--network.status", default="Active", help="Set the status for IP Addresses")
p.add_argument(
"--network.primary_mac",
choices=("permanent", "temp"),
Expand Down
108 changes: 106 additions & 2 deletions netbox_agent/dmidecode.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@

def parse(output=None):
"""
parse the full output of the dmidecode
parse the full output of the dmidecode on normal systems,
or raspinfo for raspberry pi
command and return a dic containing the parsed information
"""
if output:
Expand All @@ -71,7 +72,14 @@ def parse(output=None):
buffer = _execute_cmd()
if isinstance(buffer, bytes):
buffer = buffer.decode("utf-8")
_data = _parse(buffer)
if buffer.splitlines()[1] == "# No SMBIOS nor DMI entry point found, sorry.":
logging.info(
"Dmidecode does not seem to be finding information on your system. "
"Using raspinfo instead."
)
_data = _rpi_parse(buffer)
else:
_data = _parse(buffer)
return _data


Expand Down Expand Up @@ -149,6 +157,51 @@ def _execute_cmd():
stderr=_subprocess.PIPE,
)

def _execute_cmd_pi():
return _subprocess.check_output([
"raspinfo",
],
stderr=_subprocess.PIPE,
)

def _execute_cmd_os_pi():
return _subprocess.check_output([
"cat",
"/etc/os-release",
"|",
"head",
"-4",
],
stderr=_subprocess.PIPE, shell=True
)

def _execute_cmd_serial_pi():
output_var = str( _subprocess.check_output([
"cat",
"/proc/cpuinfo",
],
stderr=_subprocess.PIPE
), 'utf-8' )
return output_var.splitlines()[-3:]

def _execute_cmd_ram_pi():
return _subprocess.check_output([
"cat",
"/proc/meminfo",
"|",
"head",
"-1"
],
stderr=_subprocess.PIPE, shell=True
)

def _execute_cmd_bios_pi():
return _subprocess.check_output([
"vcgencmd",
"version",
],
stderr=_subprocess.PIPE,
)

def _parse(buffer):
output_data = {}
Expand Down Expand Up @@ -224,6 +277,57 @@ def _parse(buffer):

return output_data

def _rpi_parse(buffer):
# 0 BIOS
# 1 System
# 2 Baseboard
# 3 Chassis

handles = [
"0x001A",
"0x0001",
"0x0000",
"0x001D",
]

names = [
"BIOS",
"System",
"Baseboard",
"Chassis",
]

output_data = {}

for index,cur_name in enumerate(names):
output_data[handles[index]]= {}
output_data[handles[index]]["DMIType"] = int(index)
output_data[handles[index]]["DMISize"] = int(28)
output_data[handles[index]]["DMIName"] = names[index]

# Define BIOS info
output_data[handles[0]][names[0]] = _execute_cmd_bios_pi()
output_data[handles[0]]["Version"] = str(_execute_cmd_bios_pi(),"utf-8").splitlines()[-1].split(" ")[1].strip()

# Define System info
serial_data = _execute_cmd_serial_pi()
revision_number = serial_data[0].split(":")[1].strip()
serial_number = serial_data[1].split(":")[1].strip()
model = serial_data[2].split(":")[1].strip()
output_data[handles[1]]["Product Name"] = model
output_data[handles[1]]["Serial Number"] = serial_number
output_data[handles[1]]["Manufacturer"] = "Raspberry Pi Foundation"


# Define Chassis INfo
output_data[handles[3]]["Manufacturer"] = "Raspberry Pi Foundation"


if not output_data:
raise ParseError("Unable to parse 'dmidecode' output")

return output_data


class ParseError(Exception):
pass
4 changes: 3 additions & 1 deletion netbox_agent/inventory.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,12 +159,14 @@ def do_netbox_motherboard(self):

def create_netbox_interface(self, iface):
manufacturer = self.find_or_create_manufacturer(iface["vendor"])
# Netbox name character limit is 64 characters
char_limit = 64
_ = nb.dcim.inventory_items.create(
device=self.device_id,
manufacturer=manufacturer.id,
discovered=True,
tags=[{"name": INVENTORY_TAG["interface"]["name"]}],
name="{}".format(iface["product"]),
name="{}".format((iface["product"][:char_limit-3] + '..') if (len(iface["product"]) > char_limit) else iface["product"]),
serial="{}".format(iface["serial"]),
description="{} {}".format(iface["description"], iface["name"]),
)
Expand Down
1 change: 1 addition & 0 deletions netbox_agent/ipmi.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ def parse(self):
ret["name"] = "IPMI"
ret["mtu"] = 1500
ret["bonding"] = False
ret["bridged"] = False
try:
ret["mac"] = _ipmi["MAC Address"]
if ret["mac"]:
Expand Down
7 changes: 6 additions & 1 deletion netbox_agent/lldp.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,12 @@ def get_switch_port(self, interface):
return None
if self.data["lldp"][interface]["port"].get("ifname"):
return self.data["lldp"][interface]["port"]["ifname"]
return self.data["lldp"][interface]["port"]["descr"]
if self.data["lldp"][interface]["port"].get("local"):
return self.data["lldp"][interface]["port"]["local"]
if self.data["lldp"][interface]["port"].get("descr"):
return self.data["lldp"][interface]["port"]["descr"]
return None


def get_switch_vlan(self, interface):
# lldp.eth0.vlan.vlan-id=296
Expand Down
108 changes: 83 additions & 25 deletions netbox_agent/lshw.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import logging
import json
import sys
import re


class LSHW:
Expand All @@ -26,7 +27,11 @@ def __init__(self):
self.power = []
self.disks = []
self.gpus = []
self.vendor = self.hw_info["vendor"]
if hasattr(self.hw_info, "vendor"):
vendor = self.hw_info["vendor"]
else:
vendor = "Not Specified"
self.vendor = vendor
self.product = self.hw_info["product"]
self.chassis_serial = self.hw_info["serial"]
self.motherboard_serial = self.hw_info["children"][0].get("serial", "No S/N")
Expand Down Expand Up @@ -94,20 +99,7 @@ def find_network(self, obj):
)

def find_storage(self, obj):
if "children" in obj:
for device in obj["children"]:
self.disks.append(
{
"logicalname": device.get("logicalname"),
"product": device.get("product"),
"serial": device.get("serial"),
"version": device.get("version"),
"size": device.get("size"),
"description": device.get("description"),
"type": device.get("description"),
}
)
elif "driver" in obj["configuration"] and "nvme" in obj["configuration"]["driver"]:
if "driver" in obj["configuration"] and "nvme" in obj["configuration"]["driver"]:
if not is_tool("nvme"):
logging.error("nvme-cli >= 1.0 does not seem to be installed")
return
Expand All @@ -131,22 +123,88 @@ def find_storage(self, obj):
self.disks.append(d)
except Exception:
pass
elif "children" in obj:
for device in obj["children"]:
self.disks.append(
{
"logicalname": device.get("logicalname"),
"product": device.get("product"),
"serial": device.get("serial"),
"version": device.get("version"),
"size": device.get("size"),
"description": device.get("description"),
"type": device.get("description"),
}
)

def find_cpus(self, obj):
if "product" in obj:
self.cpus.append(
{
"product": obj.get("product", "Unknown CPU"),
"vendor": obj.get("vendor", "Unknown vendor"),
"description": obj.get("description", ""),
"location": obj.get("slot", ""),
}
)
if (bool(re.search(r'cpu\:\d', obj.get("id"))) and obj.get("product") == "cpu"):
# First trey to get more information with lscpu
vendor_name = "Unknown vendor"
cpu_name = "Unknown CPU"
description_detail = ""
if not is_tool("lscpu"):
logging.error("lscpu does not seem to be installed")
try:
lscpu = json.loads(
subprocess.check_output(["lscpu", "-J"], encoding="utf8")
)
for device in lscpu["lscpu"]:
if device["field"] == "Vendor ID:":
vendor_name = device["data"]
if device["field"] == "Model name:":
cpu_name = device["data"]
if device["field"] == "Architecture:":
description_detail = description_detail + "Architecture: " + device["data"] + " "
if device["field"] == "Flags:":
description_detail = description_detail + "Flags: " + device["data"]
except Exception:
pass

# In this case each CPU core is counted as a separate entity; overwrite cputoappend entity
temp_cpu_name = obj.get("product", cpu_name)
temp_description = obj.get("description", description_detail),
if temp_cpu_name == "cpu" and cpu_name != "Unknown CPU":
# cpu this is the default name, dont use it if better data is available
temp_cpu_name = cpu_name
if temp_description[0] == "CPU" and description_detail != "":
# CPU this is the default description, dont use it if better data is available
temp_description = description_detail
self.cpus = [ {
"product": temp_cpu_name + " (" + str(int(obj.get("id").split(":")[1])+1) + " Core, " + str(obj.get("size")/1000000) + " MHz)",
"vendor": obj.get("vendor", vendor_name),
"description": temp_description,
"location": obj.get("slot", ""),
}]
else:
self.cpus.append(
{
"product": obj.get("product", "Unknown CPU"),
"vendor": obj.get("vendor", "Unknown vendor"),
"description": obj.get("description", ""),
"location": obj.get("slot", ""),
}
)

def find_memories(self, obj):
if "children" not in obj:
# print("not a DIMM memory.")
return
if obj.get("description") == "System memory":
self.memories.append(
{
# This is probably embedded memory as for a Raspberry pi
"slot": obj.get("slot", "Integrated Memory"),
"description": obj.get("description"),
"id": obj.get("id"),
"serial": obj.get("serial", "N/A"),
"vendor": obj.get("vendor", "N/A"),
"product": obj.get("product", "N/A"),
"size": obj.get("size", 0) / 2**20 / 1024,
})
return
else:
# print("not a DIMM memory or integrated memory.")
return

for dimm in obj["children"]:
if "empty" in dimm["description"]:
Expand Down
13 changes: 12 additions & 1 deletion netbox_agent/misc.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
from contextlib import suppress
from netbox_agent.config import config
from netbox_agent.config import netbox_instance as nb
from slugify import slugify
from shutil import which
import distro
import subprocess
import logging
import socket
import re

Expand All @@ -23,7 +25,12 @@ def get_device_role(role):
def get_device_type(type):
device_type = nb.dcim.device_types.get(model=type)
if device_type is None:
raise Exception('DeviceType "{}" does not exist, please create it'.format(type))
if config.device.autocreate_device_type:
logging.info(
'DeviceType "{}" does not yet exist, it will be created'.format(type)
)
else:
raise Exception('DeviceType "{}" does not exist, please create it, or set device.autocreate_device_type to true'.format(type))
return device_type


Expand Down Expand Up @@ -81,6 +88,10 @@ def get_hostname(config):
return "{}".format(socket.gethostname())
return subprocess.getoutput(config.hostname_cmd)

def get_fqdn(config):
if config.fqdn_cmd is None:
return "{}".format(socket.getfqdn())
return subprocess.getoutput(config.fqdn_cmd)

def create_netbox_tags(tags):
ret = []
Expand Down
Loading
Loading