Compare commits
87 commits
Author | SHA1 | Date | |
---|---|---|---|
4242832396 | |||
13b84b4da1 | |||
837860e31a | |||
c1a7f661a3 | |||
|
7d268ea0e8 | ||
|
9d496c6854 | ||
|
6ef23eae4d | ||
12ceea413c | |||
7a968deee9 | |||
d55cbd62fc | |||
59ce76fc29 | |||
|
c3d3e6857a | ||
|
56627c1aa9 | ||
|
8dde35dd31 | ||
|
de88ca85b9 | ||
|
514627aa72 | ||
|
4b54a0a3db | ||
|
e44fd2fe78 | ||
|
1429fedb9d | ||
|
818c835711 | ||
|
40af19e801 | ||
|
1d69f4e2f0 | ||
|
a7104b6b94 | ||
|
ee41fb4fc2 | ||
|
e04d0c6d59 | ||
|
c9a57de843 | ||
|
f512e7a0a9 | ||
|
116334be2f | ||
|
ba4cdb217b | ||
|
7ab7bbb9e1 | ||
|
b6a3acd6b4 | ||
|
bb05e12f6e | ||
|
fa51ca31ca | ||
|
0fdb56e01d | ||
|
19a158ec82 | ||
|
08360bafbb | ||
|
e0d734d0ca | ||
|
dc224e209b | ||
|
7259c7602d | ||
|
8cba98ec43 | ||
|
ab96965767 | ||
|
221ac16e87 | ||
|
9204ae2187 | ||
|
528ecc09b0 | ||
|
117b39350e | ||
|
4cf054278f | ||
|
b82dc80fe3 | ||
|
49b269efa6 | ||
|
cedb6818a3 | ||
|
a9af96bba2 | ||
|
282f914665 | ||
|
ca2a69b66f | ||
|
778814b0a1 | ||
|
e0685e7167 | ||
|
ba0be73d3c | ||
|
bf65da0c58 | ||
|
5b0df6ca05 | ||
|
2d8ec831e6 | ||
|
fde6211f5b | ||
|
e96a50379b | ||
|
633b6d3851 | ||
|
04c1f83e74 | ||
|
9b06584fed | ||
|
f7cdd92fa3 | ||
|
53769db3e4 | ||
|
bce02a5e7c | ||
|
368d793704 | ||
|
776f951e3b | ||
|
84f1f00ffe | ||
|
8c12fa8e86 | ||
|
5d0f0bf2fa | ||
|
4bd4b6bb94 | ||
|
d286fde999 | ||
|
dfe937d54e | ||
|
042a6fcf35 | ||
|
f4d7796094 | ||
|
0b9087fa41 | ||
|
79a08359ae | ||
|
29d2f23986 | ||
|
838ffd8e41 | ||
|
ea66becd3d | ||
|
0cff7d3477 | ||
|
be770a947a | ||
|
6181800cb3 | ||
|
2f23844dfd | ||
|
5f0aae6c01 | ||
|
6717b43cc9 |
40 changed files with 1786 additions and 1293 deletions
1
.envrc
Normal file
1
.envrc
Normal file
|
@ -0,0 +1 @@
|
|||
use nix
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -181,3 +181,6 @@ dmypy.json
|
|||
# End of https://www.gitignore.io/api/emacs,python
|
||||
|
||||
netbox-docker
|
||||
/.vscode
|
||||
.direnv
|
||||
.pre-commit-config.yaml
|
||||
|
|
|
@ -20,8 +20,8 @@ The goal is to generate an existing infrastructure on Netbox and have the abilit
|
|||
|
||||
# Requirements
|
||||
|
||||
- Netbox >= 2.6
|
||||
- Python >= 3.4
|
||||
- Netbox >= 3.7
|
||||
- Python >= 3.7
|
||||
- [pynetbox](https://github.com/digitalocean/pynetbox/)
|
||||
- [python3-netaddr](https://github.com/drkjam/netaddr)
|
||||
- [python3-netifaces](https://github.com/al45tair/netifaces)
|
||||
|
|
55
default.nix
Normal file
55
default.nix
Normal file
|
@ -0,0 +1,55 @@
|
|||
{
|
||||
sources ? import ./npins,
|
||||
pkgs ? import sources.nixpkgs { },
|
||||
}:
|
||||
|
||||
let
|
||||
checks = (import sources.git-hooks).run {
|
||||
src = ./.;
|
||||
|
||||
hooks =
|
||||
{
|
||||
commitizen.enable = true;
|
||||
}
|
||||
// (pkgs.lib.genAttrs
|
||||
[
|
||||
"black"
|
||||
"isort"
|
||||
"ruff"
|
||||
]
|
||||
(hook: {
|
||||
enable = true;
|
||||
stages = [ "pre-push" ];
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
python3 = pkgs.python3.override {
|
||||
packageOverrides = self: _: {
|
||||
netifaces2 = self.callPackage ./nix/netifaces2.nix { };
|
||||
};
|
||||
};
|
||||
in
|
||||
|
||||
{
|
||||
devShell = pkgs.mkShell {
|
||||
name = "netbox-agent.dev";
|
||||
|
||||
packages = [
|
||||
(python3.withPackages (ps: [
|
||||
ps.pynetbox
|
||||
ps.netaddr
|
||||
ps.netifaces2
|
||||
ps.pyyaml
|
||||
ps.jsonargparse
|
||||
ps.python-slugify
|
||||
ps.packaging
|
||||
ps.distro
|
||||
]))
|
||||
] ++ checks.enabledPackages;
|
||||
|
||||
shellHook = ''
|
||||
${checks.shellHook}
|
||||
'';
|
||||
};
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
from pkg_resources import DistributionNotFound, get_distribution
|
||||
from importlib.metadata import PackageNotFoundError
|
||||
from importlib.metadata import version as _get_version
|
||||
|
||||
try:
|
||||
__version__ = get_distribution(__name__).version
|
||||
except DistributionNotFound:
|
||||
__version__ = _get_version(__name__)
|
||||
except PackageNotFoundError:
|
||||
pass
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from packaging import version
|
||||
|
||||
import netbox_agent.dmidecode as dmidecode
|
||||
from netbox_agent.config import config
|
||||
from netbox_agent.config import netbox_instance as nb
|
||||
|
@ -11,12 +12,12 @@ from netbox_agent.vendors.supermicro import SupermicroHost
|
|||
from netbox_agent.virtualmachine import VirtualMachine, is_vm
|
||||
|
||||
MANUFACTURERS = {
|
||||
'Dell Inc.': DellHost,
|
||||
'HP': HPHost,
|
||||
'HPE': HPHost,
|
||||
'Supermicro': SupermicroHost,
|
||||
'Quanta Cloud Technology Inc.': QCTHost,
|
||||
'Generic': GenericHost,
|
||||
"Dell Inc.": DellHost,
|
||||
"HP": HPHost,
|
||||
"HPE": HPHost,
|
||||
"Supermicro": SupermicroHost,
|
||||
"Quanta Cloud Technology Inc.": QCTHost,
|
||||
"Generic": GenericHost,
|
||||
}
|
||||
|
||||
|
||||
|
@ -25,21 +26,29 @@ def run(config):
|
|||
|
||||
if config.virtual.enabled or is_vm(dmi):
|
||||
if not config.virtual.cluster_name:
|
||||
raise Exception('virtual.cluster_name parameter is mandatory because it\'s a VM')
|
||||
raise Exception(
|
||||
"virtual.cluster_name parameter is mandatory because it's a VM"
|
||||
)
|
||||
server = VirtualMachine(dmi=dmi)
|
||||
else:
|
||||
manufacturer = dmidecode.get_by_type(dmi, 'Chassis')[0].get('Manufacturer')
|
||||
manufacturer = dmidecode.get_by_type(dmi, "Chassis")[0].get("Manufacturer")
|
||||
try:
|
||||
server = MANUFACTURERS[manufacturer](dmi=dmi)
|
||||
except KeyError:
|
||||
server = GenericHost(dmi=dmi)
|
||||
|
||||
if version.parse(nb.version) < version.parse('2.9'):
|
||||
print('netbox-agent is not compatible with Netbox prior to verison 2.9')
|
||||
if version.parse(nb.version) < version.parse("3.7"):
|
||||
print("netbox-agent is not compatible with Netbox prior to version 3.7")
|
||||
return False
|
||||
|
||||
if config.register or config.update_all or config.update_network or \
|
||||
config.update_location or config.update_inventory or config.update_psu:
|
||||
if (
|
||||
config.register
|
||||
or config.update_all
|
||||
or config.update_network
|
||||
or config.update_location
|
||||
or config.update_inventory
|
||||
or config.update_psu
|
||||
):
|
||||
server.netbox_create_or_update(config)
|
||||
if config.debug:
|
||||
server.print_debug()
|
||||
|
@ -47,8 +56,8 @@ def run(config):
|
|||
|
||||
|
||||
def main():
|
||||
return run(config)
|
||||
return 0 if run(config) else 1
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
|
@ -10,84 +10,148 @@ import urllib3
|
|||
def get_config():
|
||||
p = jsonargparse.ArgumentParser(
|
||||
default_config_files=[
|
||||
'/etc/netbox_agent.yaml',
|
||||
'~/.config/netbox_agent.yaml',
|
||||
'~/.netbox_agent.yaml',
|
||||
"/etc/netbox_agent.yaml",
|
||||
"~/.config/netbox_agent.yaml",
|
||||
"~/.netbox_agent.yaml",
|
||||
],
|
||||
prog='netbox_agent',
|
||||
prog="netbox_agent",
|
||||
description="Netbox agent to run on your infrastructure's servers",
|
||||
env_prefix='NETBOX_AGENT_',
|
||||
default_env=True
|
||||
env_prefix="NETBOX_AGENT_",
|
||||
default_env=True,
|
||||
)
|
||||
p.add_argument('-c', '--config', action=jsonargparse.ActionConfigFile)
|
||||
p.add_argument("-c", "--config", action=jsonargparse.ActionConfigFile)
|
||||
|
||||
p.add_argument('-r', '--register', action='store_true', help='Register server to Netbox')
|
||||
p.add_argument('-u', '--update-all', action='store_true', help='Update all infos in Netbox')
|
||||
p.add_argument('-d', '--debug', action='store_true', help='Print debug infos')
|
||||
p.add_argument('--update-network', action='store_true', help='Update network')
|
||||
p.add_argument('--update-inventory', action='store_true', help='Update inventory')
|
||||
p.add_argument('--update-location', action='store_true', help='Update location')
|
||||
p.add_argument('--update-psu', action='store_true', help='Update PSU')
|
||||
p.add_argument('--purge-old-devices', action='store_true',
|
||||
help='Purge existing (old ?) devices having same name but different serial')
|
||||
p.add_argument('--expansion-as-device', action='store_true',
|
||||
help='Manage blade expansions as external devices')
|
||||
p.add_argument(
|
||||
"-r", "--register", action="store_true", help="Register server to Netbox"
|
||||
)
|
||||
p.add_argument(
|
||||
"-u", "--update-all", action="store_true", help="Update all infos in Netbox"
|
||||
)
|
||||
p.add_argument("-d", "--debug", action="store_true", help="Print debug infos")
|
||||
p.add_argument("--update-network", action="store_true", help="Update network")
|
||||
p.add_argument("--update-inventory", action="store_true", help="Update inventory")
|
||||
p.add_argument("--update-location", action="store_true", help="Update location")
|
||||
p.add_argument("--update-psu", action="store_true", help="Update PSU")
|
||||
p.add_argument(
|
||||
"--purge-old-devices",
|
||||
action="store_true",
|
||||
help="Purge existing (old ?) devices having same name but different serial",
|
||||
)
|
||||
p.add_argument(
|
||||
"--expansion-as-device",
|
||||
action="store_true",
|
||||
help="Manage blade expansions as external devices",
|
||||
)
|
||||
|
||||
p.add_argument('--log_level', default='debug')
|
||||
p.add_argument('--netbox.ssl_ca_certs_file', help='SSL CA certificates file')
|
||||
p.add_argument('--netbox.url', help='Netbox URL')
|
||||
p.add_argument('--netbox.token', help='Netbox API Token')
|
||||
p.add_argument('--netbox.ssl_verify', default=True, action='store_true',
|
||||
help='Disable SSL verification')
|
||||
p.add_argument('--virtual.enabled', action='store_true', help='Is a virtual machine or not')
|
||||
p.add_argument('--virtual.cluster_name', help='Cluster name of VM')
|
||||
p.add_argument('--hostname_cmd', default=None,
|
||||
help="Command to output hostname, used as Device's name in netbox")
|
||||
p.add_argument('--device.platform', default=None,
|
||||
help='Override device platform. Here we use OS distribution.')
|
||||
p.add_argument('--device.tags', default=r'',
|
||||
help='tags to use for a host')
|
||||
p.add_argument('--preserve-tags', action='store_true', help='Append new unique tags, preserve those already present')
|
||||
p.add_argument('--device.custom_fields', default=r'',
|
||||
help='custom_fields to use for a host, eg: field1=v1,field2=v2')
|
||||
p.add_argument('--device.blade_role', default=r'Blade',
|
||||
help='role to use for a blade server')
|
||||
p.add_argument('--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('--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')
|
||||
p.add_argument('--datacenter_location.driver',
|
||||
help='Datacenter location driver, ie: cmd, file')
|
||||
p.add_argument('--datacenter_location.driver_file',
|
||||
help='Datacenter location custom driver file path')
|
||||
p.add_argument('--datacenter_location.regex',
|
||||
help='Datacenter location regex to extract Netbox DC slug')
|
||||
p.add_argument('--rack_location.driver', help='Rack location driver, ie: cmd, file')
|
||||
p.add_argument('--rack_location.driver_file', help='Rack location custom driver file path')
|
||||
p.add_argument('--rack_location.regex', help='Rack location regex to extract Rack name')
|
||||
p.add_argument('--slot_location.driver', help='Slot location driver, ie: cmd, file')
|
||||
p.add_argument('--slot_location.driver_file', help='Slot location custom driver file path')
|
||||
p.add_argument('--slot_location.regex', help='Slot location regex to extract slot name')
|
||||
p.add_argument('--network.ignore_interfaces', default=r'(dummy.*|docker.*)',
|
||||
help='Regex to ignore interfaces')
|
||||
p.add_argument('--network.ignore_ips', default=r'^(127\.0\.0\..*|fe80.*|::1.*)',
|
||||
help='Regex to ignore IPs')
|
||||
p.add_argument('--network.lldp', help='Enable auto-cabling feature through LLDP infos')
|
||||
p.add_argument('--inventory', action='store_true',
|
||||
help='Enable HW inventory (CPU, Memory, RAID Cards, Disks) feature')
|
||||
p.add_argument('--process-virtual-drives', action='store_true',
|
||||
help='Process virtual drives information from RAID '
|
||||
'controllers to fill disk custom_fields')
|
||||
p.add_argument('--force-disk-refresh', action='store_true',
|
||||
help='Forces disks detection reprocessing')
|
||||
p.add_argument('--dump-disks-map',
|
||||
help='File path to dump physical/virtual disks map')
|
||||
p.add_argument("--log_level", default="debug")
|
||||
p.add_argument("--netbox.ssl_ca_certs_file", help="SSL CA certificates file")
|
||||
p.add_argument("--netbox.url", help="Netbox URL")
|
||||
p.add_argument("--netbox.token", help="Netbox API Token")
|
||||
p.add_argument(
|
||||
"--netbox.ssl_verify",
|
||||
default=True,
|
||||
action="store_true",
|
||||
help="Disable SSL verification",
|
||||
)
|
||||
p.add_argument(
|
||||
"--virtual.enabled", action="store_true", help="Is a virtual machine or not"
|
||||
)
|
||||
p.add_argument("--virtual.cluster_name", help="Cluster name of VM")
|
||||
p.add_argument(
|
||||
"--hostname_cmd",
|
||||
default=None,
|
||||
help="Command to output hostname, used as Device's name in netbox",
|
||||
)
|
||||
p.add_argument(
|
||||
"--device.platform",
|
||||
default=None,
|
||||
help="Override device platform. Here we use OS distribution.",
|
||||
)
|
||||
p.add_argument("--device.tags", default=r"", help="tags to use for a host")
|
||||
p.add_argument(
|
||||
"--preserve-tags",
|
||||
action="store_true",
|
||||
help="Append new unique tags, preserve those already present",
|
||||
)
|
||||
p.add_argument(
|
||||
"--device.custom_fields",
|
||||
default=r"",
|
||||
help="custom_fields to use for a host, eg: field1=v1,field2=v2",
|
||||
)
|
||||
p.add_argument(
|
||||
"--device.blade_role", default=r"Blade", help="role to use for a blade server"
|
||||
)
|
||||
p.add_argument(
|
||||
"--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("--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")
|
||||
p.add_argument(
|
||||
"--datacenter_location.driver", help="Datacenter location driver, ie: cmd, file"
|
||||
)
|
||||
p.add_argument(
|
||||
"--datacenter_location.driver_file",
|
||||
help="Datacenter location custom driver file path",
|
||||
)
|
||||
p.add_argument(
|
||||
"--datacenter_location.regex",
|
||||
help="Datacenter location regex to extract Netbox DC slug",
|
||||
)
|
||||
p.add_argument("--rack_location.driver", help="Rack location driver, ie: cmd, file")
|
||||
p.add_argument(
|
||||
"--rack_location.driver_file", help="Rack location custom driver file path"
|
||||
)
|
||||
p.add_argument(
|
||||
"--rack_location.regex", help="Rack location regex to extract Rack name"
|
||||
)
|
||||
p.add_argument("--slot_location.driver", help="Slot location driver, ie: cmd, file")
|
||||
p.add_argument(
|
||||
"--slot_location.driver_file", help="Slot location custom driver file path"
|
||||
)
|
||||
p.add_argument(
|
||||
"--slot_location.regex", help="Slot location regex to extract slot name"
|
||||
)
|
||||
p.add_argument(
|
||||
"--network.ignore_interfaces",
|
||||
default=r"(dummy.*|docker.*)",
|
||||
help="Regex to ignore interfaces",
|
||||
)
|
||||
p.add_argument(
|
||||
"--network.ignore_ips",
|
||||
default=r"^(127\.0\.0\..*|fe80.*|::1.*)",
|
||||
help="Regex to ignore IPs",
|
||||
)
|
||||
p.add_argument(
|
||||
"--network.ipmi", default=True, help="Enable gathering IPMI information"
|
||||
)
|
||||
p.add_argument(
|
||||
"--network.lldp", help="Enable auto-cabling feature through LLDP infos"
|
||||
)
|
||||
p.add_argument(
|
||||
"--inventory",
|
||||
action="store_true",
|
||||
help="Enable HW inventory (CPU, Memory, RAID Cards, Disks) feature",
|
||||
)
|
||||
p.add_argument(
|
||||
"--process-virtual-drives",
|
||||
action="store_true",
|
||||
help="Process virtual drives information from RAID "
|
||||
"controllers to fill disk custom_fields",
|
||||
)
|
||||
p.add_argument(
|
||||
"--force-disk-refresh",
|
||||
action="store_true",
|
||||
help="Forces disks detection reprocessing",
|
||||
)
|
||||
p.add_argument(
|
||||
"--dump-disks-map", help="File path to dump physical/virtual disks map"
|
||||
)
|
||||
|
||||
options = p.parse_args()
|
||||
return options
|
||||
|
@ -98,7 +162,7 @@ config = get_config()
|
|||
|
||||
def get_netbox_instance():
|
||||
if config.netbox.url is None or config.netbox.token is None:
|
||||
logging.error('Netbox URL and token are mandatory')
|
||||
logging.error("Netbox URL and token are mandatory")
|
||||
sys.exit(1)
|
||||
|
||||
nb = pynetbox.api(
|
||||
|
|
|
@ -5,55 +5,57 @@ import sys
|
|||
|
||||
from netbox_agent.misc import is_tool
|
||||
|
||||
_handle_re = _re.compile('^Handle\\s+(.+),\\s+DMI\\s+type\\s+(\\d+),\\s+(\\d+)\\s+bytes$')
|
||||
_in_block_re = _re.compile('^\\t\\t(.+)$')
|
||||
_record_re = _re.compile('\\t(.+):\\s+(.+)$')
|
||||
_record2_re = _re.compile('\\t(.+):$')
|
||||
_handle_re = _re.compile(
|
||||
"^Handle\\s+(.+),\\s+DMI\\s+type\\s+(\\d+),\\s+(\\d+)\\s+bytes$"
|
||||
)
|
||||
_in_block_re = _re.compile("^\\t\\t(.+)$")
|
||||
_record_re = _re.compile("\\t(.+):\\s+(.+)$")
|
||||
_record2_re = _re.compile("\\t(.+):$")
|
||||
|
||||
_type2str = {
|
||||
0: 'BIOS',
|
||||
1: 'System',
|
||||
2: 'Baseboard',
|
||||
3: 'Chassis',
|
||||
4: 'Processor',
|
||||
5: 'Memory Controller',
|
||||
6: 'Memory Module',
|
||||
7: 'Cache',
|
||||
8: 'Port Connector',
|
||||
9: 'System Slots',
|
||||
10: ' On Board Devices',
|
||||
11: ' OEM Strings',
|
||||
12: ' System Configuration Options',
|
||||
13: ' BIOS Language',
|
||||
14: ' Group Associations',
|
||||
15: ' System Event Log',
|
||||
16: ' Physical Memory Array',
|
||||
17: ' Memory Device',
|
||||
18: ' 32-bit Memory Error',
|
||||
19: ' Memory Array Mapped Address',
|
||||
20: ' Memory Device Mapped Address',
|
||||
21: ' Built-in Pointing Device',
|
||||
22: ' Portable Battery',
|
||||
23: ' System Reset',
|
||||
24: ' Hardware Security',
|
||||
25: ' System Power Controls',
|
||||
26: ' Voltage Probe',
|
||||
27: ' Cooling Device',
|
||||
28: ' Temperature Probe',
|
||||
29: ' Electrical Current Probe',
|
||||
30: ' Out-of-band Remote Access',
|
||||
31: ' Boot Integrity Services',
|
||||
32: ' System Boot',
|
||||
33: ' 64-bit Memory Error',
|
||||
34: ' Management Device',
|
||||
35: ' Management Device Component',
|
||||
36: ' Management Device Threshold Data',
|
||||
37: ' Memory Channel',
|
||||
38: ' IPMI Device',
|
||||
39: ' Power Supply',
|
||||
40: ' Additional Information',
|
||||
41: ' Onboard Devices Extended Information',
|
||||
42: ' Management Controller Host Interface'
|
||||
0: "BIOS",
|
||||
1: "System",
|
||||
2: "Baseboard",
|
||||
3: "Chassis",
|
||||
4: "Processor",
|
||||
5: "Memory Controller",
|
||||
6: "Memory Module",
|
||||
7: "Cache",
|
||||
8: "Port Connector",
|
||||
9: "System Slots",
|
||||
10: " On Board Devices",
|
||||
11: " OEM Strings",
|
||||
12: " System Configuration Options",
|
||||
13: " BIOS Language",
|
||||
14: " Group Associations",
|
||||
15: " System Event Log",
|
||||
16: " Physical Memory Array",
|
||||
17: " Memory Device",
|
||||
18: " 32-bit Memory Error",
|
||||
19: " Memory Array Mapped Address",
|
||||
20: " Memory Device Mapped Address",
|
||||
21: " Built-in Pointing Device",
|
||||
22: " Portable Battery",
|
||||
23: " System Reset",
|
||||
24: " Hardware Security",
|
||||
25: " System Power Controls",
|
||||
26: " Voltage Probe",
|
||||
27: " Cooling Device",
|
||||
28: " Temperature Probe",
|
||||
29: " Electrical Current Probe",
|
||||
30: " Out-of-band Remote Access",
|
||||
31: " Boot Integrity Services",
|
||||
32: " System Boot",
|
||||
33: " 64-bit Memory Error",
|
||||
34: " Management Device",
|
||||
35: " Management Device Component",
|
||||
36: " Management Device Threshold Data",
|
||||
37: " Memory Channel",
|
||||
38: " IPMI Device",
|
||||
39: " Power Supply",
|
||||
40: " Additional Information",
|
||||
41: " Onboard Devices Extended Information",
|
||||
42: " Management Controller Host Interface",
|
||||
}
|
||||
_str2type = {}
|
||||
for type_id, type_str in _type2str.items():
|
||||
|
@ -70,7 +72,7 @@ def parse(output=None):
|
|||
else:
|
||||
buffer = _execute_cmd()
|
||||
if isinstance(buffer, bytes):
|
||||
buffer = buffer.decode('utf-8')
|
||||
buffer = buffer.decode("utf-8")
|
||||
_data = _parse(buffer)
|
||||
return _data
|
||||
|
||||
|
@ -129,24 +131,31 @@ def get_by_type(data, type_id):
|
|||
|
||||
result = []
|
||||
for entry in data.values():
|
||||
if entry['DMIType'] == type_id:
|
||||
if entry["DMIType"] == type_id:
|
||||
result.append(entry)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def _execute_cmd():
|
||||
if not is_tool('dmidecode'):
|
||||
logging.error('Dmidecode does not seem to be present on your system. Add it your path or '
|
||||
'check the compatibility of this project with your distro.')
|
||||
if not is_tool("dmidecode"):
|
||||
logging.error(
|
||||
"Dmidecode does not seem to be present on your system. Add it your path or "
|
||||
"check the compatibility of this project with your distro."
|
||||
)
|
||||
sys.exit(1)
|
||||
return _subprocess.check_output(['dmidecode', ], stderr=_subprocess.PIPE)
|
||||
return _subprocess.check_output(
|
||||
[
|
||||
"dmidecode",
|
||||
],
|
||||
stderr=_subprocess.PIPE,
|
||||
)
|
||||
|
||||
|
||||
def _parse(buffer):
|
||||
output_data = {}
|
||||
# Each record is separated by double newlines
|
||||
split_output = buffer.split('\n\n')
|
||||
split_output = buffer.split("\n\n")
|
||||
|
||||
for record in split_output:
|
||||
record_element = record.splitlines()
|
||||
|
@ -164,21 +173,21 @@ def _parse(buffer):
|
|||
dmi_handle = handle_data[0]
|
||||
|
||||
output_data[dmi_handle] = {}
|
||||
output_data[dmi_handle]['DMIType'] = int(handle_data[1])
|
||||
output_data[dmi_handle]['DMISize'] = int(handle_data[2])
|
||||
output_data[dmi_handle]["DMIType"] = int(handle_data[1])
|
||||
output_data[dmi_handle]["DMISize"] = int(handle_data[2])
|
||||
|
||||
# Okay, we know 2nd line == name
|
||||
output_data[dmi_handle]['DMIName'] = record_element[1]
|
||||
output_data[dmi_handle]["DMIName"] = record_element[1]
|
||||
|
||||
in_block_elemet = ''
|
||||
in_block_list = ''
|
||||
in_block_elemet = ""
|
||||
in_block_list = ""
|
||||
|
||||
# Loop over the rest of the record, gathering values
|
||||
for i in range(2, len(record_element), 1):
|
||||
if i >= len(record_element):
|
||||
break
|
||||
# Check whether we are inside a \t\t block
|
||||
if in_block_elemet != '':
|
||||
if in_block_elemet != "":
|
||||
in_block_data = _in_block_re.findall(record_element[i])
|
||||
|
||||
if in_block_data:
|
||||
|
@ -192,7 +201,7 @@ def _parse(buffer):
|
|||
else:
|
||||
# We are out of the \t\t block; reset it again, and let
|
||||
# the parsing continue
|
||||
in_block_elemet = ''
|
||||
in_block_elemet = ""
|
||||
|
||||
record_data = _record_re.findall(record_element[i])
|
||||
|
||||
|
@ -208,7 +217,7 @@ def _parse(buffer):
|
|||
# This is an array of data - let the loop know we are inside
|
||||
# an array block
|
||||
in_block_elemet = record_data2[0]
|
||||
in_block_list = ''
|
||||
in_block_list = ""
|
||||
|
||||
continue
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import re
|
|||
|
||||
|
||||
def get(value, regex):
|
||||
for line in open(value, 'r'):
|
||||
for line in open(value, "r"):
|
||||
r = re.search(regex, line)
|
||||
if r and len(r.groups()) > 0:
|
||||
return r.groups()[0]
|
||||
|
|
|
@ -6,16 +6,16 @@ from shutil import which
|
|||
|
||||
# mapping fields from ethtool output to simple names
|
||||
field_map = {
|
||||
'Supported ports': 'ports',
|
||||
'Supported link modes': 'sup_link_modes',
|
||||
'Supports auto-negotiation': 'sup_autoneg',
|
||||
'Advertised link modes': 'adv_link_modes',
|
||||
'Advertised auto-negotiation': 'adv_autoneg',
|
||||
'Speed': 'speed',
|
||||
'Duplex': 'duplex',
|
||||
'Port': 'port',
|
||||
'Auto-negotiation': 'autoneg',
|
||||
'Link detected': 'link',
|
||||
"Supported ports": "ports",
|
||||
"Supported link modes": "sup_link_modes",
|
||||
"Supports auto-negotiation": "sup_autoneg",
|
||||
"Advertised link modes": "adv_link_modes",
|
||||
"Advertised auto-negotiation": "adv_autoneg",
|
||||
"Speed": "speed",
|
||||
"Duplex": "duplex",
|
||||
"Port": "port",
|
||||
"Auto-negotiation": "autoneg",
|
||||
"Link detected": "link",
|
||||
}
|
||||
|
||||
|
||||
|
@ -25,7 +25,7 @@ def merge_two_dicts(x, y):
|
|||
return z
|
||||
|
||||
|
||||
class Ethtool():
|
||||
class Ethtool:
|
||||
"""
|
||||
This class aims to parse ethtool output
|
||||
There is several bindings to have something proper, but it requires
|
||||
|
@ -40,39 +40,40 @@ class Ethtool():
|
|||
parse ethtool output
|
||||
"""
|
||||
|
||||
output = subprocess.getoutput('ethtool {}'.format(self.interface))
|
||||
output = subprocess.getoutput("ethtool {}".format(self.interface))
|
||||
|
||||
fields = {}
|
||||
field = ''
|
||||
fields['speed'] = '-'
|
||||
fields['link'] = '-'
|
||||
fields['duplex'] = '-'
|
||||
for line in output.split('\n')[1:]:
|
||||
field = ""
|
||||
fields["speed"] = "-"
|
||||
fields["link"] = "-"
|
||||
fields["duplex"] = "-"
|
||||
for line in output.split("\n")[1:]:
|
||||
line = line.rstrip()
|
||||
r = line.find(':')
|
||||
r = line.find(":")
|
||||
if r > 0:
|
||||
field = line[:r].strip()
|
||||
if field not in field_map:
|
||||
continue
|
||||
field = field_map[field]
|
||||
output = line[r + 1:].strip()
|
||||
output = line[r + 1 :].strip()
|
||||
fields[field] = output
|
||||
else:
|
||||
if len(field) > 0 and \
|
||||
field in field_map:
|
||||
fields[field] += ' ' + line.strip()
|
||||
if len(field) > 0 and field in field_map:
|
||||
fields[field] += " " + line.strip()
|
||||
return fields
|
||||
|
||||
def _parse_ethtool_module_output(self):
|
||||
status, output = subprocess.getstatusoutput('ethtool -m {}'.format(self.interface))
|
||||
status, output = subprocess.getstatusoutput(
|
||||
"ethtool -m {}".format(self.interface)
|
||||
)
|
||||
if status == 0:
|
||||
r = re.search(r'Identifier.*\((\w+)\)', output)
|
||||
r = re.search(r"Identifier.*\((\w+)\)", output)
|
||||
if r and len(r.groups()) > 0:
|
||||
return {'form_factor': r.groups()[0]}
|
||||
return {"form_factor": r.groups()[0]}
|
||||
return {}
|
||||
|
||||
def parse(self):
|
||||
if which('ethtool') is None:
|
||||
if which("ethtool") is None:
|
||||
return None
|
||||
output = self._parse_ethtool_output()
|
||||
output.update(self._parse_ethtool_module_output())
|
||||
|
|
|
@ -1,3 +1,11 @@
|
|||
import json
|
||||
import logging
|
||||
import re
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
import pynetbox
|
||||
|
||||
from netbox_agent.config import config
|
||||
from netbox_agent.config import netbox_instance as nb
|
||||
from netbox_agent.lshw import LSHW
|
||||
|
@ -5,25 +13,19 @@ from netbox_agent.misc import get_vendor, is_tool
|
|||
from netbox_agent.raid.hp import HPRaid
|
||||
from netbox_agent.raid.omreport import OmreportRaid
|
||||
from netbox_agent.raid.storcli import StorcliRaid
|
||||
import traceback
|
||||
import pynetbox
|
||||
import logging
|
||||
import json
|
||||
import re
|
||||
|
||||
|
||||
INVENTORY_TAG = {
|
||||
'cpu': {'name': 'hw:cpu', 'slug': 'hw-cpu'},
|
||||
'gpu': {'name': 'hw:gpu', 'slug': 'hw-gpu'},
|
||||
'disk': {'name': 'hw:disk', 'slug': 'hw-disk'},
|
||||
'interface': {'name': 'hw:interface', 'slug': 'hw-interface'},
|
||||
'memory': {'name': 'hw:memory', 'slug': 'hw-memory'},
|
||||
'motherboard': {'name': 'hw:motherboard', 'slug': 'hw-motherboard'},
|
||||
'raid_card': {'name': 'hw:raid_card', 'slug': 'hw-raid-card'},
|
||||
"cpu": {"name": "hw:cpu", "slug": "hw-cpu"},
|
||||
"gpu": {"name": "hw:gpu", "slug": "hw-gpu"},
|
||||
"disk": {"name": "hw:disk", "slug": "hw-disk"},
|
||||
"interface": {"name": "hw:interface", "slug": "hw-interface"},
|
||||
"memory": {"name": "hw:memory", "slug": "hw-memory"},
|
||||
"motherboard": {"name": "hw:motherboard", "slug": "hw-motherboard"},
|
||||
"raid_card": {"name": "hw:raid_card", "slug": "hw-raid-card"},
|
||||
}
|
||||
|
||||
|
||||
class Inventory():
|
||||
class Inventory:
|
||||
"""
|
||||
Better Inventory items coming, see:
|
||||
- https://github.com/netbox-community/netbox/issues/3087
|
||||
|
@ -62,14 +64,12 @@ class Inventory():
|
|||
def create_netbox_tags(self):
|
||||
ret = []
|
||||
for key, tag in INVENTORY_TAG.items():
|
||||
nb_tag = nb.extras.tags.get(
|
||||
name=tag['name']
|
||||
)
|
||||
nb_tag = nb.extras.tags.get(name=tag["name"])
|
||||
if not nb_tag:
|
||||
nb_tag = nb.extras.tags.create(
|
||||
name=tag['name'],
|
||||
slug=tag['slug'],
|
||||
comments=tag['name'],
|
||||
name=tag["name"],
|
||||
slug=tag["slug"],
|
||||
comments=tag["name"],
|
||||
)
|
||||
ret.append(nb_tag)
|
||||
return ret
|
||||
|
@ -82,29 +82,28 @@ class Inventory():
|
|||
name=name,
|
||||
)
|
||||
if not manufacturer:
|
||||
logging.info('Creating missing manufacturer {name}'.format(name=name))
|
||||
logging.info("Creating missing manufacturer {name}".format(name=name))
|
||||
manufacturer = nb.dcim.manufacturers.create(
|
||||
name=name,
|
||||
slug=re.sub('[^A-Za-z0-9]+', '-', name).lower(),
|
||||
slug=re.sub("[^A-Za-z0-9]+", "-", name).lower(),
|
||||
)
|
||||
|
||||
logging.info('Creating missing manufacturer {name}'.format(name=name))
|
||||
logging.info("Creating missing manufacturer {name}".format(name=name))
|
||||
|
||||
return manufacturer
|
||||
|
||||
def get_netbox_inventory(self, device_id, tag):
|
||||
try:
|
||||
items = nb.dcim.inventory_items.filter(
|
||||
device_id=device_id,
|
||||
tag=tag
|
||||
)
|
||||
items = nb.dcim.inventory_items.filter(device_id=device_id, tag=tag)
|
||||
except pynetbox.core.query.RequestError:
|
||||
logging.info('Tag {tag} is missing, returning empty array.'.format(tag=tag))
|
||||
logging.info("Tag {tag} is missing, returning empty array.".format(tag=tag))
|
||||
items = []
|
||||
|
||||
return list(items)
|
||||
|
||||
def create_netbox_inventory_item(self, device_id, tags, vendor, name, serial, description):
|
||||
def create_netbox_inventory_item(
|
||||
self, device_id, tags, vendor, name, serial, description
|
||||
):
|
||||
manufacturer = self.find_or_create_manufacturer(vendor)
|
||||
|
||||
_ = nb.dcim.inventory_items.create(
|
||||
|
@ -112,26 +111,25 @@ class Inventory():
|
|||
manufacturer=manufacturer.id,
|
||||
discovered=True,
|
||||
tags=tags,
|
||||
name='{}'.format(name),
|
||||
serial='{}'.format(serial),
|
||||
description=description
|
||||
name="{}".format(name),
|
||||
serial="{}".format(serial),
|
||||
description=description,
|
||||
)
|
||||
|
||||
logging.info('Creating inventory item {} {}/{} {} '.format(
|
||||
vendor,
|
||||
name,
|
||||
serial,
|
||||
description)
|
||||
logging.info(
|
||||
"Creating inventory item {} {}/{} {} ".format(
|
||||
vendor, name, serial, description
|
||||
)
|
||||
)
|
||||
|
||||
def get_hw_motherboards(self):
|
||||
motherboards = []
|
||||
|
||||
m = {}
|
||||
m['serial'] = self.lshw.motherboard_serial
|
||||
m['vendor'] = self.lshw.vendor
|
||||
m['name'] = '{} {}'.format(self.lshw.vendor, self.lshw.motherboard)
|
||||
m['description'] = '{} Motherboard'.format(self.lshw.motherboard)
|
||||
m["serial"] = self.lshw.motherboard_serial
|
||||
m["vendor"] = self.lshw.vendor
|
||||
m["name"] = "{} {}".format(self.lshw.vendor, self.lshw.motherboard)
|
||||
m["description"] = "{} Motherboard".format(self.lshw.motherboard)
|
||||
|
||||
motherboards.append(m)
|
||||
|
||||
|
@ -141,27 +139,29 @@ class Inventory():
|
|||
|
||||
motherboards = self. |