devicesync

package
v1.0.9 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 8, 2026 License: AGPL-3.0 Imports: 17 Imported by: 0

Documentation

Overview

syncs interfaces, addresses, VRF-aware address dedup, LLDP-discovered cable connections, and on-device services (ELINE/ELAN as NetBox L2VPNs, L3VPN as VRFs — kinds come from cfgmgmt service types) from live network devices into NetBox. The device/interface/address snapshot read for comparison comes from factum's own DB (already synced from Netbox by internal/netbox, see FactumAPI) rather than Netbox directly - only the actual writes go straight to Netbox (see NetboxAPI).

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func FetchRemoteConfig

func FetchRemoteConfig(factumConfig *util.ConfigFactum) (*util.ConfigDeviceSync, error)

FetchRemoteConfig pulls internal/device-sync's config from the primary, authenticated with factumConfig.Token.

func Sync

func Sync(api NetboxAPI, factumAPI FactumAPI, cfg *util.ConfigDeviceSync, reporter jobevent.Reporter, opts SyncOptions) error

Sync fetches every eligible device from factum (already synced from Netbox), connects to each over its driver, and syncs interfaces/addresses/connections/prefixes back to Netbox.

Types

type DeviceSync

type DeviceSync struct {
	// contains filtered or unexported fields
}

type FactumAPI

type FactumAPI interface {
	// GetDevices returns every device, without interfaces/addresses
	// populated - used only for the device-selection filter pass.
	GetDevices() ([]*models.Device, error)
	// GetDeviceByName returns one device with interfaces/addresses
	// populated.
	GetDeviceByName(name string) (*models.Device, error)
	// ApplyOpticalInventory persists a driver's optical dump on the device.
	ApplyOpticalInventory(deviceID uint, inv optical.Inventory) (*optical.ApplyResult, error)
}

FactumAPI narrows *factum.FactumClient to what DeviceSync needs, so tests can substitute a fake - same shape as NetboxAPI.

type NetboxAPI

type NetboxAPI interface {
	GetDevices() ([]*netboxtool.NBDevice, error)
	GetDevice(name string, id int) (*netboxtool.NBDevice, error)
	GetDeviceType(manufacturer, model string) (*netboxtool.NetboxDeviceTypeDetail, error)

	CreateInterfaceWithOptions(deviceID uint, name string, extra map[string]any) (*netboxtool.NetboxInterfaceREST, error)
	InterfaceUpdate(interfaceID int, changes map[string]any) error
	InterfaceDelete(interfaceID int) error

	CreateInterfaceAddress(interfaceID uint, address string, extra map[string]any) (*netboxtool.NBAddress, error)
	AddressUpdate(addressID int, changes map[string]any) error
	AddressDelete(addressID int) error

	GetCable(cableID uint) (*netboxtool.NBCable, error)
	CreateCableWithOptions(aInterfaceID, bInterfaceID uint, extra map[string]any) (*netboxtool.NBCable, error)
	DeleteCable(cableID uint) error
	UpdateCableTermination(cableID uint, side int, interfaceID uint) error

	GetPrefix(prefix string) (*netboxtool.NBPrefix, error)
	CreatePrefix(prefix string) (*netboxtool.NBPrefix, error)

	GetVlanGroup(name string) (*netboxtool.NBVlanGroup, error)
	CreateVlanGroup(name, slug string) (*netboxtool.NBVlanGroup, error)
	GetVlan(vid int, groupID uint) (*netboxtool.NBVlan, error)
	CreateVlan(vid int, name string, groupID uint) (*netboxtool.NBVlan, error)
	UpdateVlan(id uint, changes map[string]any) error

	GetL2VPNByName(name string) (*netboxtool.NBL2VPN, error)
	GetL2VPNByIdentifier(identifier int) (*netboxtool.NBL2VPN, error)
	CreateL2VPN(name, slug, l2vpnType string, identifier int) (*netboxtool.NBL2VPN, error)
	UpdateL2VPN(l2vpnID uint, changes map[string]any) error
	GetL2VPNTerminations(l2vpnID uint) ([]*netboxtool.NBL2VPNTermination, error)
	CreateL2VPNTermination(l2vpnID, interfaceID uint) (*netboxtool.NBL2VPNTermination, error)

	GetVRFByName(name string) (*netboxtool.NBVRF, error)
	CreateVRF(name, rd, description string) (*netboxtool.NBVRF, error)

	UpdateDevice(deviceID uint, changes map[string]any) error
}

NetboxAPI narrows *netboxtool.NetboxClient to what NetboxMgr needs, so tests can substitute a fake - same shape as drivers.DriverClient.

type NetboxMgr

type NetboxMgr struct {
	// contains filtered or unexported fields
}

NetboxMgr wraps NetboxAPI with netboxtool's optional Cache (so repeated device-type lookups within one sync run don't re-fetch) and emits a jobevent.Reporter line for every change made. Device/interface/address reads no longer go through NetboxMgr at all - see DeviceSync.factum - this is now purely the Netbox write path plus device-type template lookups.

sharedMu guards every access to cache and the EnsurePrefix check-then-create below: netboxtool.Cache is documented as not safe for concurrent use, and EnsurePrefix's exists-check + create is its own tiny race if two devices' pairs are synced concurrently (device-sync.go's forEachPairParallel) and share a prefix. The rest of NetboxMgr's methods are plain one-shot API calls with no shared mutable state, so they're left unlocked - serializing them too would defeat the point of syncing pairs in parallel.

func NewNetboxMgr

func NewNetboxMgr(api NetboxAPI, reporter jobevent.Reporter) *NetboxMgr

func (*NetboxMgr) BuildInterfaceVlanChanges

func (m *NetboxMgr) BuildInterfaceVlanChanges(mode string, untaggedVID int, taggedVIDs []int) (map[string]any, error)

BuildInterfaceVlanChanges builds a Netbox interface PATCH payload (untagged_vlan/qinq_svlan, tagged_vlans, mode) for an explicit "set this interface's switchport state" edit - the web-triggered counterpart of DeviceSync.syncInterfaceVlans's diff-against-last-sync PATCH building (device-sync.go), which this deliberately doesn't share: that path is a best-effort background reconciliation that skips whatever VID didn't resolve and sends only the fields that changed, while this one is a single explicit user edit that should fail loudly - and atomically, all fields together - if any referenced VID wasn't ensured first via EnsureVlan. Every non-zero VID in untaggedVID/taggedVIDs must already be known to VlanNetboxID.

func (*NetboxMgr) CreateAddress

func (m *NetboxMgr) CreateAddress(device *models.Device, iface *models.Interface, address string, extra map[string]any) (*netboxtool.NBAddress, error)

CreateAddress creates a Netbox address on iface. extra carries whatever REST fields the caller has already decided on (role/vrf) - see device-sync.go's addressesCreate, the Go equivalent of main.py's address_create.

func (*NetboxMgr) CreateCable

func (m *NetboxMgr) CreateCable(device, remoteDevice *models.Device, localIface, remoteIface *models.Interface) error

func (*NetboxMgr) CreateInterface

func (m *NetboxMgr) CreateInterface(device *models.Device, iface *drivers.Interface) (*netboxtool.NetboxInterfaceREST, error)

CreateInterface creates a Netbox interface from a parsed device interface. Every driver's parser always sets a concrete Type (physical interfaces get "other", not "virtual" - see e.g. eosInterfaceType - Netbox rejects terminating a cable on a "virtual" interface), but Type is only sent here when non-empty as a defensive fallback rather than sending Netbox an empty value for that required field. iface.Parent, if set (see e.g. srosAddSapInterface), is resolved by name against device.Interfaces - which by this point already includes any earlier-in-this-phase creation (interfacesCreate appends in-memory, see its own comment) - so a parent created earlier in the same run is found without a re-fetch; a Parent that doesn't resolve is dropped with a warning rather than sent as a missing/zero Netbox ID.

func (*NetboxMgr) DeleteAddress

func (m *NetboxMgr) DeleteAddress(device *models.Device, iface *models.Interface, addr *models.Address) error

func (*NetboxMgr) DeleteCable

func (m *NetboxMgr) DeleteCable(cable *netboxtool.NBCable) error

func (*NetboxMgr) DeleteInterface

func (m *NetboxMgr) DeleteInterface(device *models.Device, iface *models.Interface) error

func (*NetboxMgr) EnsureL2VPN added in v1.0.3

func (m *NetboxMgr) EnsureL2VPN(deviceName, name, l2vpnType string, identifier int) (*netboxtool.NBL2VPN, error)

EnsureL2VPN finds or creates a Netbox L2VPN of l2vpnType (evpl, vpls, …) for an on-device service. Lookup order: exact name, then (when identifier > 0) identifier - so a service-provisioned L2VPN named after the ServiceID is reused when the device still carries that same identifier under a matching or near-matching name. When found by identifier alone the existing name is left alone (device-sync does not rename service-owned L2VPNs). When creating, identifier is only sent if > 0 (same-device patches often have no PWID). Locked because the name/identifier check-then-create races when both ends of a cross-device service are synced concurrently in the same run.

func (*NetboxMgr) EnsureL2VPNTermination

func (m *NetboxMgr) EnsureL2VPNTermination(deviceName, l2vpnName string, l2vpnID, interfaceID uint, ifaceName string, terms *[]*netboxtool.NBL2VPNTermination) error

EnsureL2VPNTermination creates a termination of l2vpn on interfaceID unless one already exists. terms is the current termination list for that L2VPN (caller-fetched); on success the new termination is appended in-memory so a second call in the same phase sees it. Locked with sharedMu so two concurrent ends of the same L2VPN don't race to create the same termination.

func (*NetboxMgr) EnsurePrefix

func (m *NetboxMgr) EnsurePrefix(prefix string) error

EnsurePrefix creates prefix (a CIDR network literal) if it doesn't already exist. Locked because it's a check-then-create: two devices whose addresses fall in the same prefix, synced concurrently, would otherwise both see it missing and race to create it.

func (*NetboxMgr) EnsureVRF added in v1.0.3

func (m *NetboxMgr) EnsureVRF(deviceName, name, rd, description string) (*netboxtool.NBVRF, error)

EnsureVRF finds or creates a Netbox VRF by name. RD is set on create only; an existing VRF is left alone (device-sync does not rename or rewrite operator-owned VRFs). Locked because two devices in the same run can both see a new L3VPN and race to create it.

func (*NetboxMgr) EnsureVlan

func (m *NetboxMgr) EnsureVlan(vid int, name string) (*netboxtool.NBVlan, error)

EnsureVlan creates vid in the resolved VLAN group if it doesn't already exist there, or updates its name if it does but the name differs. Records the VLAN's Netbox ID in m.vlanIDsByVID either way, for DeviceSync.syncInterfaceVlans to resolve later. Locked because it's a check-then-create/update, same race as EnsurePrefix: two devices sharing a VID, synced concurrently, would otherwise both see it missing and race to create it.

func (*NetboxMgr) EnsureVlanGroup

func (m *NetboxMgr) EnsureVlanGroup(name string) (uint, error)

EnsureVlanGroup resolves name to a Netbox VLAN group ID, creating a new global (unscoped) group if none exists, and memoizes the result on m.vlanGroupID - safe to call more than once per run, but DeviceSync.run only ever does so once, before any per-device phase.

func (*NetboxMgr) GetL2VPNTerminations

func (m *NetboxMgr) GetL2VPNTerminations(l2vpnID uint) ([]*netboxtool.NBL2VPNTermination, error)

GetL2VPNTerminations returns every termination currently on l2vpnID.

func (*NetboxMgr) SetConnectionTermination

func (m *NetboxMgr) SetConnectionTermination(device *models.Device, cable *netboxtool.NBCable, side int, iface *models.Interface) error

SetConnectionTermination replaces one side (1=A, 2=B) of an existing cable with iface.

func (*NetboxMgr) TemplateInterfaceTypes

func (m *NetboxMgr) TemplateInterfaceTypes(manufacturer, model string) (map[string]string, error)

TemplateInterfaceTypes returns the Netbox interface type (e.g. "1000base-t") of every interface defined by a device type's template (manufacturer+model), keyed by interface name - the template's own type is admin/vendor-curated per-port data, more accurate than anything a parsed running-config can tell us, so device-sync.go prefers it over its own guessed type (see interfacesCreate/syncInterfaceValues) wherever an interface is template-defined. Presence in the map (regardless of value) is also what interfacesDelete uses to avoid deleting a Netbox interface that only exists because of the template (Netbox, or an admin re-applying the template, would just recreate it) - port of the `nb_ifname in nb_device._device_type.interfaces_name` guard in netbox_mgr.py's interfaces_delete. Results are cached per manufacturer/model for the life of the NetboxMgr - see netboxtool.Cache.

func (*NetboxMgr) UpdateAddress

func (m *NetboxMgr) UpdateAddress(device *models.Device, iface *models.Interface, addr *models.Address, changes map[string]any) error

func (*NetboxMgr) UpdateDevice added in v1.0.4

func (m *NetboxMgr) UpdateDevice(device *models.Device, changes map[string]any) error

func (*NetboxMgr) UpdateInterface

func (m *NetboxMgr) UpdateInterface(device *models.Device, iface *models.Interface, changes map[string]any) error

UpdateInterface applies changes (field -> new value) to an existing interface.

func (*NetboxMgr) VlanNetboxID

func (m *NetboxMgr) VlanNetboxID(vid int) (uint, bool)

VlanNetboxID returns the Netbox ID of the VLAN with this VID in the resolved group (populated by EnsureVlan), and whether it's known.

type SyncOptions

type SyncOptions struct {
	Name       string // sync only the device with this name, "" for all
	Platform   string // sync only devices with this Netbox platform, "" for all
	Unattended bool   // don't prompt before deleting an interface/address
}

SyncOptions controls which devices are synced and how destructive changes are confirmed

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL