Device sync: v6 primary IP, DMI placeholders, CLI report
CI / agent (push) Successful in 3s
CI / site (push) Successful in 3s
CI / go (push) Successful in 41s

The primary IP of each family is the management address, else the first
address on the management interface (the v6 address next to a v4
management address). DMI placeholders ("Not Specified", "To Be Filled
By O.E.M.") are not written as serials. rollcall ipam sync prints the
device report.

Refs #10

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Ilker ManapandClaude Opus 5.5 committed 2026-10-30 22:07:22 +01:00
1 parent 133b5f38d9
commit c3032683ab
2 files changed
+41 -6

No files matched your search

+27 -5
View File
@@ -232,7 +232,7 @@ func (n *netBox) syncDevices(ctx context.Context, cfg DeviceSyncConfig, machines
site = id
}
body := obj{"name": name, "device_type": types[tk], "role": roleID, "site": site, "status": nbStatus(m.State),
"serial": trunc(deref(m.Serial), 50), "tags": tags, "custom_fields": obj{n.field: m.ID},
"serial": trunc(dmiValue(deref(m.Serial)), 50), "tags": tags, "custom_fields": obj{n.field: m.ID},
"location": nil, "rack": nil, "position": nil, "face": nil}
if id, ok := locBy[siteName{site, strings.ToLower(m.Location.Room)}]; ok && m.Location.Room != "" {
body["location"] = id
@@ -376,14 +376,21 @@ func (n *netBox) syncInterfaces(ctx context.Context, dev obj, m *Machine, ips []
}
}
}
// addresses on their interfaces; the management address (else the first
// one on the boot NIC) is the primary IP
// addresses on their interfaces; the primary IP of each family is the
// management address, else the first address on the management (or
// boot) interface
var boot string
if b := bootNIC(nics, m.MACs); b != nil {
boot = b.Name
}
mgmt := deref(m.MgmtIP)
type cand struct {
id, iface int64
fam int
}
var cands []cand
primary := map[int]int64{}
mgmtIface := ifaceID[boot]
for _, x := range ips {
a, ok := nbIPs[x.IP]
if !ok || n.mine(a) != m.ID {
@@ -405,8 +412,14 @@ func (n *netBox) syncInterfaces(ctx context.Context, dev obj, m *Machine, ips []
if strings.Contains(x.IP, ":") {
fam = 6
}
if _, set := primary[fam]; x.IP == mgmt || (!set && iid == ifaceID[boot] && mgmt == "") {
primary[fam] = nbID(a)
if x.IP == mgmt {
primary[fam], mgmtIface = nbID(a), iid
}
cands = append(cands, cand{nbID(a), iid, fam})
}
for _, c := range cands {
if _, set := primary[c.fam]; !set && c.iface == mgmtIface {
primary[c.fam] = c.id
}
}
body := obj{}
@@ -441,3 +454,12 @@ func nbIPFromObj(a obj) nbIP {
}
return x
}
// dmiValue drops the placeholders firmware leaves in DMI fields.
func dmiValue(s string) string {
switch strings.ToLower(strings.TrimSpace(s)) {
case "", "not specified", "to be filled by o.e.m.", "default string", "system serial number", "0123456789", "none", "n/a":
return ""
}
return strings.TrimSpace(s)
}
+14 -1
View File
@@ -231,6 +231,8 @@ func TestNetBoxDeviceSync(t *testing.T) {
tagged := []any{map[string]any{"name": "rollcall"}}
ipID := f.put("ipam/ip-addresses/", map[string]any{"address": "10.213.77.50/24", "tags": tagged,
"custom_fields": map[string]any{"rollcall_machine": "m-a"}})
ip6ID := f.put("ipam/ip-addresses/", map[string]any{"address": "fd00:213:77::50/64", "tags": tagged,
"custom_fields": map[string]any{"rollcall_machine": "m-a"}})
foreignIP := f.put("ipam/ip-addresses/", map[string]any{"address": "10.213.77.51/24", "tags": []any{},
"custom_fields": map[string]any{}})
// a device someone else made with a machine's name
@@ -241,7 +243,12 @@ func TestNetBoxDeviceSync(t *testing.T) {
b := dcimMachine("m-b", "", "ready", Location{Building: "nowhere", Rack: "R12"}, [2]string{"eth0", "aa:bb:cc:00:00:03"})
c := dcimMachine("m-c", "taken", "commission-failed", Location{})
n := dcimMachine("m-n", "", "new", Location{})
ips := []IPAddr{{IP: "10.213.77.50", MachineID: "m-a", Iface: "eno1"}, {IP: "10.213.77.51", MachineID: "m-b"}}
ips := []IPAddr{{IP: "10.213.77.50", MachineID: "m-a", Iface: "eno1"}, {IP: "fd00:213:77::50", MachineID: "m-a", Iface: "eno1"},
{IP: "10.213.77.51", MachineID: "m-b"}}
mgmt := "10.213.77.50"
a.MgmtIP = &mgmt
ns := "Not Specified"
b.Serial = &ns
rep := nb.syncDevices(ctx, cfg, []*Machine{a, b, c, n}, ips)
if rep.Error != "" {
@@ -283,6 +290,12 @@ func TestNetBoxDeviceSync(t *testing.T) {
if ip["assigned_object_type"] != "dcim.interface" || ip["assigned_object_id"] != eno1["id"] || d["primary_ip4"] != float64(ipID) {
t.Fatalf("ip %v primary %v", ip, d["primary_ip4"])
}
if d["primary_ip6"] != float64(ip6ID) {
t.Fatalf("primary ip6 %v (the v6 address on the management interface)", d["primary_ip6"])
}
if db["serial"] != "" {
t.Fatalf("DMI placeholder as serial: %v", db["serial"])
}
if f.objs["ipam/ip-addresses/"][foreignIP]["assigned_object_id"] != nil {
t.Fatal("assigned an address that is not Rollcall's")
}