Skip to content

Commit 671bafa

Browse files
authored
Merge pull request #3 from Charlie-Root/update-from-upstream
Update from upstream
2 parents e480efa + 5feed7b commit 671bafa

38 files changed

Lines changed: 1646 additions & 1268 deletions

.github/workflows/tests.yml

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
name: Tests
2+
3+
on:
4+
pull_request:
5+
types: [opened, synchronize, reopened]
6+
jobs:
7+
tests:
8+
runs-on: ubuntu-latest
9+
strategy:
10+
fail-fast: false
11+
matrix:
12+
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
13+
steps:
14+
- name: Check out repository code
15+
uses: actions/checkout@v4
16+
- name: Set up Python ${{ matrix.python-version }}
17+
uses: actions/setup-python@v5
18+
with:
19+
python-version: ${{ matrix.python-version }}
20+
cache: 'pip'
21+
- name: Display Python version
22+
run: python -c "import sys; print(sys.version)"
23+
- name: Install build dependencies
24+
run: pip install build
25+
- name: Build the package
26+
run: python3 -m build
27+
- name: Install the built package from tar.gz
28+
run: pip install "$(echo dist/*.tar.gz)"[dev]
29+
- name: Run netbox_agent to check that the installed package contains the required modules
30+
run: netbox_agent --help
31+
- name: Run tests
32+
run: ./tests.sh
33+
#- name: Upload coverage to Codecov
34+
# if: matrix.python-version == '3.13.0'
35+
# uses: codecov/codecov-action@v3
36+
# continue-on-error: true
37+
# with:
38+
# files: ./coverage.xml
39+
# token: ${{ secrets.CODECOV_TOKEN }}
40+
# fail_ci_if_error: true
41+
ruff_linter:
42+
runs-on: ubuntu-latest
43+
steps:
44+
- name: Check out repository code
45+
uses: actions/checkout@v4
46+
- name: Install Ruff
47+
run: pip install $(grep -Po '(?<=")ruff[^"]+' pyproject.toml)
48+
- name: Ruff linter
49+
run: ruff check
50+
ruff_formatter:
51+
runs-on: ubuntu-latest
52+
steps:
53+
- name: Check out repository code
54+
uses: actions/checkout@v4
55+
- name: Install Ruff
56+
run: pip install $(grep -Po '(?<=")ruff[^"]+' pyproject.toml)
57+
- name: Ruff formatter
58+
run: ruff format --diff

README.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,15 @@ The goal is to generate an existing infrastructure on Netbox and have the abilit
1717
* Automatic cabling (server's interface to switch's interface) using lldp
1818
* Local inventory using `Inventory Item` for CPU, GPU, RAM, RAID cards, physical disks (behind raid cards)
1919
* PSUs creation and power consumption reporting (based on vendor's tools)
20+
* Associate hypervisor devices to the virtualization cluster
21+
* Associate virtual machines to the hypervisor device
2022

2123
# Requirements
2224

2325
- Netbox >= 3.7
2426
- Python >= 3.8
2527
- [pynetbox](https://github.com/digitalocean/pynetbox/)
26-
- [python3-netaddr](https://github.com/drkjam/netaddr)
28+
- [python3-netaddr](https://github.com/netaddr/netaddr)
2729
- [python3-netifaces](https://github.com/al45tair/netifaces)
2830
- [jsonargparse](https://github.com/omni-us/jsonargparse/)
2931

@@ -128,6 +130,13 @@ network:
128130
# # see https://netbox.company.com/virtualization/clusters/
129131
# cluster_name: my_vm_cluster
130132
133+
## Enable hypervisor support
134+
# virtual:
135+
# enabled: false
136+
# hypervisor: true
137+
# cluster_name: my_cluster
138+
# list_guests_cmd: command that lists VMs names
139+
131140
# Enable datacenter location feature in Netbox
132141
datacenter_location:
133142
driver: "cmd:cat /etc/qualification | tr [A-Z] [a-z]"
@@ -290,3 +299,9 @@ On a personal note, I use the docker image from [netbox-community/netbox-docker]
290299
# docker-compose pull
291300
# docker-compose up
292301
```
302+
303+
For the linter and code formatting, you need to run:
304+
```
305+
ruff check
306+
ruff format
307+
```

netbox_agent/cli.py

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import sys
12
from packaging import version
23
import netbox_agent.dmidecode as dmidecode
34
from netbox_agent.config import config
@@ -29,32 +30,43 @@ def run(config):
2930
dmi = dmidecode.parse()
3031

3132
if config.virtual.enabled or is_vm(dmi):
33+
if config.virtual.hypervisor:
34+
raise Exception("This host can't be a hypervisor because it's a VM")
3235
if not config.virtual.cluster_name:
33-
raise Exception('virtual.cluster_name parameter is mandatory because it\'s a VM')
36+
raise Exception("virtual.cluster_name parameter is mandatory because it's a VM")
3437
server = VirtualMachine(dmi=dmi)
3538
else:
36-
manufacturer = dmidecode.get_by_type(dmi, 'Chassis')[0].get('Manufacturer')
37-
logging.info("Found manufacturer: %s" % manufacturer)
39+
if config.virtual.hypervisor and not config.virtual.cluster_name:
40+
raise Exception(
41+
"virtual.cluster_name parameter is mandatory because it's a hypervisor"
42+
)
43+
manufacturer = dmidecode.get_by_type(dmi, "Chassis")[0].get("Manufacturer")
3844
try:
3945
server = MANUFACTURERS[manufacturer](dmi=dmi)
4046
except KeyError:
4147
server = GenericHost(dmi=dmi)
4248

43-
if version.parse(nb.version) < version.parse('3.7'):
44-
print('netbox-agent is not compatible with Netbox prior to version 3.7')
45-
return False
49+
if version.parse(nb.version) < version.parse("3.7"):
50+
print("netbox-agent is not compatible with Netbox prior to version 3.7")
51+
return 1
4652

47-
if config.register or config.update_all or config.update_network or \
48-
config.update_location or config.update_inventory or config.update_psu:
53+
if (
54+
config.register
55+
or config.update_all
56+
or config.update_network
57+
or config.update_location
58+
or config.update_inventory
59+
or config.update_psu
60+
):
4961
server.netbox_create_or_update(config)
5062
if config.debug:
5163
server.print_debug()
52-
return True
64+
return 0
5365

5466

5567
def main():
5668
return run(config)
5769

5870

59-
if __name__ == '__main__':
60-
main()
71+
if __name__ == "__main__":
72+
sys.exit(main())

netbox_agent/config.py

Lines changed: 132 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -10,87 +10,142 @@
1010
def get_config():
1111
p = jsonargparse.ArgumentParser(
1212
default_config_files=[
13-
'/etc/netbox_agent.yaml',
14-
'~/.config/netbox_agent.yaml',
15-
'~/.netbox_agent.yaml',
13+
"/etc/netbox_agent.yaml",
14+
"~/.config/netbox_agent.yaml",
15+
"~/.netbox_agent.yaml",
1616
],
17-
prog='netbox_agent',
17+
prog="netbox_agent",
1818
description="Netbox agent to run on your infrastructure's servers",
19-
env_prefix='NETBOX_AGENT_',
20-
default_env=True
19+
env_prefix="NETBOX_AGENT_",
20+
default_env=True,
2121
)
22-
p.add_argument('-c', '--config', action=jsonargparse.ActionConfigFile)
22+
p.add_argument("-c", "--config", action=jsonargparse.ActionConfigFile)
2323

24-
p.add_argument('-r', '--register', action='store_true', help='Register server to Netbox')
25-
p.add_argument('-u', '--update-all', action='store_true', help='Update all infos in Netbox')
26-
p.add_argument('-d', '--debug', action='store_true', help='Print debug infos')
27-
p.add_argument('--update-network', action='store_true', help='Update network')
28-
p.add_argument('--update-inventory', action='store_true', help='Update inventory')
29-
p.add_argument('--update-location', action='store_true', help='Update location')
30-
p.add_argument('--update-psu', action='store_true', help='Update PSU')
31-
p.add_argument('--update-old-devices', action='store_true',
32-
help='Update serial number of existing (old ?) devices having same name but different serial')
33-
p.add_argument('--purge-old-devices', action='store_true',
34-
help='Purge existing (old ?) devices having same name but different serial')
35-
p.add_argument('--expansion-as-device', action='store_true',
36-
help='Manage blade expansions as external devices')
24+
p.add_argument("-r", "--register", action="store_true", help="Register server to Netbox")
25+
p.add_argument("-u", "--update-all", action="store_true", help="Update all infos in Netbox")
26+
p.add_argument("-d", "--debug", action="store_true", help="Print debug infos")
27+
p.add_argument("--update-network", action="store_true", help="Update network")
28+
p.add_argument("--update-inventory", action="store_true", help="Update inventory")
29+
p.add_argument("--update-location", action="store_true", help="Update location")
30+
p.add_argument("--update-psu", action="store_true", help="Update PSU")
31+
p.add_argument(
32+
"--update-hypervisor",
33+
action="store_true",
34+
help="Update virtualization cluster and virtual machines",
35+
)
36+
p.add_argument(
37+
"--update-old-devices",
38+
action="store_true",
39+
help="Update serial number of existing (old ?) devices having same name but different serial",
40+
)
41+
p.add_argument(
42+
"--purge-old-devices",
43+
action="store_true",
44+
help="Purge existing (old ?) devices having same name but different serial",
45+
)
46+
p.add_argument(
47+
"--expansion-as-device",
48+
action="store_true",
49+
help="Manage blade expansions as external devices",
50+
)
3751

38-
p.add_argument('--log_level', default='debug')
39-
p.add_argument('--netbox.ssl_ca_certs_file', help='SSL CA certificates file')
40-
p.add_argument('--netbox.url', help='Netbox URL')
41-
p.add_argument('--netbox.token', help='Netbox API Token')
42-
p.add_argument('--netbox.ssl_verify', default=True, action='store_true',
43-
help='Disable SSL verification')
44-
p.add_argument('--virtual.enabled', action='store_true', help='Is a virtual machine or not')
45-
p.add_argument('--virtual.cluster_name', help='Cluster name of VM')
46-
p.add_argument('--hostname_cmd', default=None,
47-
help="Command to output hostname, used as Device's name in netbox")
48-
p.add_argument('--device.platform', default=None,
49-
help='Override device platform. Here we use OS distribution.')
50-
p.add_argument('--device.tags', default=r'',
51-
help='tags to use for a host')
52-
p.add_argument('--preserve-tags', action='store_true', help='Append new unique tags, preserve those already present')
53-
p.add_argument('--device.custom_fields', default=r'',
54-
help='custom_fields to use for a host, eg: field1=v1,field2=v2')
55-
p.add_argument('--device.blade_role', default=r'Blade',
56-
help='role to use for a blade server')
57-
p.add_argument('--device.chassis_role', default=r'Server Chassis',
58-
help='role to use for a chassis')
59-
p.add_argument('--device.server_role', default=r'Server',
60-
help='role to use for a server')
61-
p.add_argument('--tenant.driver',
62-
help='tenant driver, ie cmd, file')
63-
p.add_argument('--tenant.driver_file',
64-
help='tenant driver custom driver file path')
65-
p.add_argument('--tenant.regex',
66-
help='tenant regex to extract Netbox tenant slug')
67-
p.add_argument('--datacenter_location.driver',
68-
help='Datacenter location driver, ie: cmd, file')
69-
p.add_argument('--datacenter_location.driver_file',
70-
help='Datacenter location custom driver file path')
71-
p.add_argument('--datacenter_location.regex',
72-
help='Datacenter location regex to extract Netbox DC slug')
73-
p.add_argument('--rack_location.driver', help='Rack location driver, ie: cmd, file')
74-
p.add_argument('--rack_location.driver_file', help='Rack location custom driver file path')
75-
p.add_argument('--rack_location.regex', help='Rack location regex to extract Rack name')
76-
p.add_argument('--slot_location.driver', help='Slot location driver, ie: cmd, file')
77-
p.add_argument('--slot_location.driver_file', help='Slot location custom driver file path')
78-
p.add_argument('--slot_location.regex', help='Slot location regex to extract slot name')
79-
p.add_argument('--network.ignore_interfaces', default=r'(dummy.*|docker.*)',
80-
help='Regex to ignore interfaces')
81-
p.add_argument('--network.ignore_ips', default=r'^(127\.0\.0\..*|fe80.*|::1.*)',
82-
help='Regex to ignore IPs')
83-
p.add_argument('--network.ipmi', default=True, help='Enable gathering IPMI information')
84-
p.add_argument('--network.lldp', help='Enable auto-cabling feature through LLDP infos')
85-
p.add_argument('--inventory', action='store_true',
86-
help='Enable HW inventory (CPU, Memory, RAID Cards, Disks) feature')
87-
p.add_argument('--process-virtual-drives', action='store_true',
88-
help='Process virtual drives information from RAID '
89-
'controllers to fill disk custom_fields')
90-
p.add_argument('--force-disk-refresh', action='store_true',
91-
help='Forces disks detection reprocessing')
92-
p.add_argument('--dump-disks-map',
93-
help='File path to dump physical/virtual disks map')
52+
p.add_argument("--log_level", default="debug")
53+
p.add_argument("--netbox.ssl_ca_certs_file", help="SSL CA certificates file")
54+
p.add_argument("--netbox.url", help="Netbox URL")
55+
p.add_argument("--netbox.token", help="Netbox API Token")
56+
p.add_argument(
57+
"--netbox.ssl_verify", default=True, action="store_true", help="Disable SSL verification"
58+
)
59+
p.add_argument("--virtual.enabled", action="store_true", help="Is a virtual machine or not")
60+
p.add_argument("--virtual.cluster_name", help="Cluster name of VM")
61+
p.add_argument("--virtual.hypervisor", action="store_true", help="Is a hypervisor or not")
62+
p.add_argument(
63+
"--virtual.list_guests_cmd",
64+
default=None,
65+
help="Command to output the list of vrtualization guests in the hypervisor separated by whitespace",
66+
)
67+
p.add_argument(
68+
"--hostname_cmd",
69+
default=None,
70+
help="Command to output hostname, used as Device's name in netbox",
71+
)
72+
p.add_argument(
73+
"--device.platform",
74+
default=None,
75+
help="Override device platform. Here we use OS distribution.",
76+
)
77+
p.add_argument("--device.tags", default=r"", help="tags to use for a host")
78+
p.add_argument(
79+
"--preserve-tags",
80+
action="store_true",
81+
help="Append new unique tags, preserve those already present",
82+
)
83+
p.add_argument(
84+
"--device.custom_fields",
85+
default=r"",
86+
help="custom_fields to use for a host, eg: field1=v1,field2=v2",
87+
)
88+
p.add_argument("--device.blade_role", default=r"Blade", help="role to use for a blade server")
89+
p.add_argument(
90+
"--device.chassis_role", default=r"Server Chassis", help="role to use for a chassis"
91+
)
92+
p.add_argument("--device.server_role", default=r"Server", help="role to use for a server")
93+
p.add_argument("--tenant.driver", help="tenant driver, ie cmd, file")
94+
p.add_argument("--tenant.driver_file", help="tenant driver custom driver file path")
95+
p.add_argument("--tenant.regex", help="tenant regex to extract Netbox tenant slug")
96+
p.add_argument(
97+
"--datacenter_location.driver", help="Datacenter location driver, ie: cmd, file"
98+
)
99+
p.add_argument(
100+
"--datacenter_location.driver_file", help="Datacenter location custom driver file path"
101+
)
102+
p.add_argument(
103+
"--datacenter_location.regex", help="Datacenter location regex to extract Netbox DC slug"
104+
)
105+
p.add_argument("--rack_location.driver", help="Rack location driver, ie: cmd, file")
106+
p.add_argument("--rack_location.driver_file", help="Rack location custom driver file path")
107+
p.add_argument("--rack_location.regex", help="Rack location regex to extract Rack name")
108+
p.add_argument("--slot_location.driver", help="Slot location driver, ie: cmd, file")
109+
p.add_argument("--slot_location.driver_file", help="Slot location custom driver file path")
110+
p.add_argument("--slot_location.regex", help="Slot location regex to extract slot name")
111+
p.add_argument(
112+
"--network.ignore_interfaces",
113+
default=r"(dummy.*|docker.*)",
114+
help="Regex to ignore interfaces",
115+
)
116+
p.add_argument(
117+
"--network.ignore_ips",
118+
default=r"^(127\.0\.0\..*|fe80.*|::1.*)",
119+
help="Regex to ignore IPs",
120+
)
121+
p.add_argument("--network.ipmi", default=True, help="Enable gathering IPMI information")
122+
p.add_argument("--network.lldp", help="Enable auto-cabling feature through LLDP infos")
123+
p.add_argument(
124+
"--network.nic_id",
125+
choices=("name", "mac"),
126+
default="name",
127+
help="What property to use as NIC identifier",
128+
)
129+
p.add_argument(
130+
"--network.primary_mac",
131+
choices=("permanent", "temp"),
132+
default="temp",
133+
help="Which MAC address to use as primary. Permanent requires ethtool and fallbacks to temporary",
134+
)
135+
p.add_argument(
136+
"--inventory",
137+
action="store_true",
138+
help="Enable HW inventory (CPU, Memory, RAID Cards, Disks) feature",
139+
)
140+
p.add_argument(
141+
"--process-virtual-drives",
142+
action="store_true",
143+
help="Process virtual drives information from RAID controllers to fill disk custom_fields",
144+
)
145+
p.add_argument(
146+
"--force-disk-refresh", action="store_true", help="Forces disks detection reprocessing"
147+
)
148+
p.add_argument("--dump-disks-map", help="File path to dump physical/virtual disks map")
94149

95150
options = p.parse_args()
96151
return options
@@ -101,7 +156,7 @@ def get_config():
101156

102157
def get_netbox_instance():
103158
if config.netbox.url is None or config.netbox.token is None:
104-
logging.error('Netbox URL and token are mandatory')
159+
logging.error("Netbox URL and token are mandatory")
105160
sys.exit(1)
106161

107162
nb = pynetbox.api(

0 commit comments

Comments
 (0)