We have a NetBox instance which we use to store information about virtual machines. The Config Context tab for a virtual machine object is populated with a YAML file structured as such:
stuff:
- hostname: myserver
os: Linux
os_version: RHEL 8.1
network:
- ip_address: 192.168.2.3
network: ABC
gateway: 192.168.2.1
server_type: XYZ
other_data: Foobar
The same data is also available in JSON format.
I am doing extraction of the standard NetBox fields to a CSV file via the following Python script:
config = configparser.ConfigParser()
netbox_token = config.get('NetBox', 'token')
netbox_api_base_url = config.get('NetBox', 'api_base_url')
netbox_devices_endpoint = config.get('NetBox', 'devices_endpoint')
nb = pynetbox.api(netbox_api_base_url, token=netbox_token)
nb.http_session.verify = True
vms = nb.virtualization.virtual_machines.all()
csv_file_vms = 'netbox_vms.csv'
with open(csv_file_vms, mode='w', newline='') as csv_file:
csv_writer = csv.writer(csv_file)
csv_writer.writerow(['Name', 'Status', 'Site', 'VCPUs', 'Memory (MB)', 'Disk (GB)', 'IP Address'])
for vm in vms:
csv_writer.writerow([vm.name, vm.status, vm.site, vm.vcpus, vm.memory, vm.disk, vm.primary_ip])
How do I modify the writerow()
function to add the data stored in the Config Context, e.g. the os_version
field?
You will need to add a dedicated column in the first call to writerow
, and access the relevant VM attribute in consecutive calls inside the for
loop. As I assume your question revolves more around attribute access rather than how to write CSVs - I'll dive deeper into the former;
The Config Context can be accessed via the vm.config_context
attribute. The provided YAML example translates to a dictionary with one key stuff
, which points to a list of items. Each item in that list is a dictionary having keys such as hostname
, os_version
, and network
. The values of the first keys mentioned are strings, but the value of network
is yet another list. Following is a practical demonstration for accessing such attributes for the given example, while assuming that all list types have one (and only one) item:
>>> vms = nb.virtualization.virtual_machines.all()
>>> for vm in vms:
... print(f"{vm.name=}")
... print(f"{vm.vcpus=}")
... print(f"os_version: {vm.config_context['stuff'][0]['os_version']}")
... print(f"network/gateway: {vm.config_context['stuff'][0]['network'][0]['gateway']}")
... print(f"other_data: {vm.config_context['stuff'][0]['other_data']}")
... print("======================")
...
vm.name='VM1'
vm.vcpus=1.0
os_version: RHEL 9.2
network/gateway: 192.168.2.1
other_data: BarFoo
======================
vm.name='vm2'
vm.vcpus=2.0
os_version: RHEL 8.1
network/gateway: 192.168.2.1
other_data: Foobar
======================
>>>
A complete answer to your question would include the adaptations required for writing the CSV, so here goes:
>>> vms = nb.virtualization.virtual_machines.all()
>>> csv_file_vms = 'netbox_vms.csv'
>>> with open(csv_file_vms, mode='w', newline='') as csv_file:
... csv_writer = csv.writer(csv_file)
... csv_writer.writerow(['Name', 'Status', 'Site', 'VCPUs', 'Memory (MB)', 'Disk (GB)', 'IP Address', 'OS Version'])
... for vm in vms:
... csv_writer.writerow([vm.name, vm.status, vm.site, vm.vcpus, vm.memory, vm.disk, vm.primary_ip, vm.config_context['stuff'][0]['os_version']])
...
68
37
37
>>>
>>> with open(csv_file_vms) as input_file:
... print(input_file.read())
...
Name,Status,Site,VCPUs,Memory (MB),Disk (GB),IP Address,OS Version
VM1,Active,,1.0,1023,2049,,RHEL 9.2
vm2,Active,,2.0,1025,2051,,RHEL 8.1
>>>
If you can change the YAML structure, removing the first dash -
(from in front of the hostname
field in your example) can make attribute access simpler, and also represent the reality more accurately; consider the following YAML as a config context for a router:
stuff:
hostname: MyRouter
os: Linux
os_version: RHEL 8.1
network:
- ip_address: 192.168.1.1
name: ABC
- ip_address: 172.16.1.1
name: DEF
Handling such case in code can look like this:
>>> for vm in routers:
... print(f"{vm.name=}")
... print(f"{vm.vcpus=}")
... print(f"{vm.config_context['stuff']['os_version']=}")
... print("Routing:")
... for network in vm.config_context['stuff']['network']:
... print(f"{network['name']}: {network['ip_address']}")
...
vm.name='Router1'
vm.vcpus=None
vm.config_context['stuff']['os_version']='RHEL 8.1'
Routing:
ABC: 192.168.1.1
DEF: 172.16.1.1
>>>
You may not have to deal with multiple IP addresses in your situation, but you won't need to handle the possibility of a single VM configured (probably erroneously) with multiple OSes.
Note: This question involves interoperability between client and server functions. I've tested this on python 3.9.18, netbox 3.6.5, and pynetbox 7.2.0. Other versions may behave differently.