Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
14 changes: 13 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,27 @@ FROM python:3.14-slim AS builder
COPY requirements.txt .

ARG VENV=/opt/netbox-sync/venv
ARG INSTALL_OVIRT=false
COPY requirements-ovirt.txt .

# Install dependencies
RUN python3 -m venv $VENV && \
RUN if [ "$INSTALL_OVIRT" = "true" ]; then \
apt-get update && \
apt-get install -y --no-install-recommends gcc libc6-dev libxml2-dev libcurl4-openssl-dev && \
rm -rf /var/lib/apt/lists/*; \
fi && \
python3 -m venv $VENV && \
$VENV/bin/python3 -m pip install --upgrade pip && \
$VENV/bin/pip install -r requirements.txt && \
if [ "$INSTALL_OVIRT" = "true" ]; then $VENV/bin/pip install -r requirements-ovirt.txt; fi && \
$VENV/bin/pip install vmware-vcenter==9.1.1.0 && \
$VENV/bin/python3 -m pip uninstall -y pip && \
find $VENV -type d -name "__pycache__" -print0 | xargs -0 -n1 rm -rf

FROM python:3.14-slim AS netbox-sync

ARG VENV=/opt/netbox-sync/venv
ARG INSTALL_OVIRT=false

# Copy installed packages
COPY --from=builder $VENV $VENV
Expand All @@ -30,6 +39,9 @@ COPY . .
# can use it)
RUN apt-get update && \
apt-get dist-upgrade -y && \
if [ "$INSTALL_OVIRT" = "true" ]; then \
apt-get install -y --no-install-recommends libxml2 libcurl4t64; \
fi && \
rm -rf /var/lib/apt/lists/* && \
python3 -m pip uninstall -y --root-user-action=ignore pip && \
groupadd --gid 1000 netbox-sync && \
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ This is a tool to sync data from different sources to a NetBox instance.

Available source types:
* VMware vCenter Server
* oVirt / Red Hat Virtualization (see [oVirt source](docs/source_ovirt.md))
* [bb-ricardo/check_redfish](https://github.com/bb-Ricardo/check_redfish) inventory files

**IMPORTANT: READ INSTRUCTIONS CAREFULLY BEFORE RUNNING THIS PROGRAM**
Expand Down Expand Up @@ -38,6 +39,9 @@ This ensures stale objects are removed from NetBox keeping an accurate current s
* VMWare vCenter >= 6.0
#### Source: check_redfish (if used)
* check_redfish >= 1.2.0
#### Source: oVirt (if used)
* oVirt / Red Hat Virtualization Engine v4 API
* Optional SDK and native libraries: see [installation and configuration](docs/source_ovirt.md)

# Installing
* here we assume we install in ```/opt```
Expand Down
62 changes: 62 additions & 0 deletions docs/source_ovirt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Source: Ovirt

## Installation

The SDK is optional: installations using other sources do not need it. Selecting
an oVirt source without the SDK logs an error and skips that source.

On Debian/Ubuntu, install the SDK's build dependencies before installing Python
packages:

```sh
sudo apt-get install gcc python3-dev libxml2-dev libcurl4-openssl-dev
pip install -r requirements-ovirt.txt
```

With uv, use `uv sync --extra ovirt` after installing the native libraries.
The SDK contains a native extension; a compiler and Python development headers
matching your Python version are needed when no wheel is available. The runtime
needs libxml2 and libcurl.

For Docker, enable the optional source in both build stages:

```sh
docker build --build-arg INSTALL_OVIRT=true -t netbox-sync:ovirt .
```

## Configuration

```ini
[source/production-ovirt]
type = ovirt
url = https://engine.example.com/ovirt-engine/api
username = inventory@internal
password = replace-me
validate_tls_certs = True
ca_file = /etc/pki/ovirt-engine/ca.pem
permitted_subnets = 10.0.0.0/8, fd00::/8
cluster_site_relation = DC/Production = Main Site
```

Use an account with read access to datacenters, clusters, hosts, VMs, disks,
interfaces, and reported devices. The existing source options remain supported,
including name filters, site/tenant/role/tag relations, domain stripping,
`set_vm_name_to_uuid`, `skip_vm_comments`, `skip_vm_platform`, and
`host_custom_object_attributes` / `vm_custom_object_attributes` (dot-separated
SDK attribute paths). Generate a full example with `python netbox-sync.py -g`.

Guest IP discovery requires the guest agent to report network devices. VMs
without agent data still receive their configured interfaces and disks. As with
other sources, bare guest IPs need a matching prefix in NetBox to determine their
prefix length. Only addresses allowed by `permitted_subnets` are imported.
`ip_tenant_inheritance_order`, `preserve_primary_ips`, `skip_fhrp_group_ips`, and
interface exclusion filters use the current shared network-sync behavior.

NetBox 4.2+ cluster sites use scoped relations and interfaces use MAC address
objects. NetBox 3.7+ disks are separate virtual disk objects, with the size units
appropriate to the NetBox version. Older versions retain aggregate VM disk size.
Missing CPU or disk size data is not replaced with fabricated values.

Begin with a dry run (`python netbox-sync.py -n`) and inspect the log before
allowing writes. Unit tests use a fake SDK and the real in-memory NetBox
inventory; they do not replace validation against your live oVirt Engine.
4 changes: 3 additions & 1 deletion module/config/file_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from module.sources.vmware.config import VMWareConfig
from module.sources.check_redfish.config import CheckRedfishConfig
from module.sources.hetzner.config import HetznerConfig
from module.sources.ovirt.config import OVirtConfig
from module.common.logging import get_logger
from module.config import default_config_file_path, source_config_section_name
from module.config.files import ConfigFile, ConfigFileINI, ConfigFileYAML
Expand All @@ -35,7 +36,8 @@ class ConfigFileOutput(DescriptionFormatterMixin):
source_config_list = [
VMWareConfig,
CheckRedfishConfig,
HetznerConfig
HetznerConfig,
OVirtConfig
]

header = f"Welcome to the {__description__} configuration file."
Expand Down
3 changes: 2 additions & 1 deletion module/sources/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
# define all available sources here
from module.sources.vmware.connection import VMWareHandler
from module.sources.hetzner.connection import HetznerHandler
from module.sources.ovirt.connection import OVirtHandler
from module.sources.check_redfish.import_inventory import CheckRedfish

from module.common.logging import get_logger
Expand All @@ -19,7 +20,7 @@
from module.config import source_config_section_name

# list of valid sources
valid_sources = [VMWareHandler, CheckRedfish, HetznerHandler]
valid_sources = [VMWareHandler, CheckRedfish, HetznerHandler, OVirtHandler]


def validate_source(source_class_object=None, state="pre"):
Expand Down
11 changes: 10 additions & 1 deletion module/sources/common/source_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,14 @@ def implements(cls, source_type):
def finish(self):
pass

def object_synced_by_other_source(self, nb_object):
"""Check the current source or stored tags for ownership by another configured source."""
if nb_object.source is not None:
return nb_object.source is not self

other_source_tags = [x.source_tag for x in self.inventory.source_list if x is not self]
return len(set(nb_object.get_tags()).intersection(other_source_tags)) > 0

def ip_is_primary_ip_of_object(self, ip_object, device_vm_object) -> bool:
"""
Check if a NBIPAddress object is currently set as primary IPv4 or IPv6
Expand Down Expand Up @@ -435,7 +443,8 @@ def add_update_interface(self, interface_object, device_object, interface_data,

# skip handling of IPs for VMs with not installed/running guest tools
skip_ip_handling = False
if type(device_object) == NBVM and grab(vmware_object,'guest.toolsRunningStatus') != "guestToolsRunning":
if type(device_object) == NBVM and vmware_object is not None and \
grab(vmware_object, 'guest.toolsRunningStatus') != "guestToolsRunning":
log.debug(f"VM '{device_object.name}' guest tool status is 'NotRunning', skipping IP handling")
skip_ip_handling = True

Expand Down
Loading
Loading