package main import ( "context" "fmt" "os" "strings" "git.faercol.me/faercol/dnsmasq-netbox-connector/internal/config" "git.faercol.me/faercol/dnsmasq-netbox-connector/internal/dnsmasq" "git.faercol.me/faercol/dnsmasq-netbox-connector/internal/healthchecks" "git.faercol.me/faercol/dnsmasq-netbox-connector/internal/netbox" gohealthchecks "git.faercol.me/faercol/go-healthchecks" ) func main() { configPath := "/etc/dnmasq-netbox/config.json" if len(os.Args) > 1 { configPath = os.Args[1] } conf, err := config.New(configPath) if err != nil { fmt.Fprintf(os.Stderr, "Failed to read configuration: %s\n", err) os.Exit(1) } healtchecksClt := gohealthchecks.NewPingClient(conf.HostPingAPI) check, err := healthchecks.Start(context.Background(), conf.HealthchecksConfig, healtchecksClt) if err != nil { fmt.Fprintf(os.Stderr, "Failed to notify process start: %s\n", err) } leases, err := dnsmasq.GetDynamicLeases(conf.LeasesPath, conf.DHCPRangeStart, conf.DHCPRangeEnd) if err != nil { fmt.Fprintf(os.Stderr, "Failed to parse leases: %s\n", err) if err := healthchecks.Failure(context.Background(), conf.HealthchecksConfig, healtchecksClt, check, "failed to parse lease file"); err != nil { fmt.Fprintf(os.Stderr, "Failed to notify failure: %s\n", err) } os.Exit(1) } for _, l := range leases { fmt.Printf("Got lease %s\n", l) } netboxClt := netbox.New(*conf) assignedIP, freeIP, err := netboxClt.GetUsedDHCPAddresses(context.Background()) if err != nil { fmt.Fprintf(os.Stderr, "Failed to get list of assigned IPs: %s\n", err) if err := healthchecks.Failure(context.Background(), conf.HealthchecksConfig, healtchecksClt, check, "failed to get assigned IPs"); err != nil { fmt.Fprintf(os.Stderr, "Failed to notify failure: %s\n", err) } os.Exit(1) } allInterfaces, err := netboxClt.GetAllInterfaces(context.Background()) if err != nil { fmt.Fprintf(os.Stderr, "Failed to get list of interfaces: %s\n", err) if err := healthchecks.Failure(context.Background(), conf.HealthchecksConfig, healtchecksClt, check, "failed to get interfaces"); err != nil { fmt.Fprintf(os.Stderr, "Failed to notify failure: %s\n", err) } os.Exit(1) } toDelete, toAdd := reconcile(leases, assignedIP, freeIP, allInterfaces) for _, a := range toDelete { fmt.Printf("Following IP should be unassigned: %s\n", a.String()) } for _, a := range toAdd { fmt.Printf("Following IP should be assigned: %s\n", a.String()) } if err := netboxClt.UpdateIPs(context.Background(), toDelete, toAdd); err != nil { fmt.Fprintf(os.Stderr, "Failed to update IP assignment in netbox: %s\n", err) if err := healthchecks.Failure(context.Background(), conf.HealthchecksConfig, healtchecksClt, check, "failed to update assignment"); err != nil { fmt.Fprintf(os.Stderr, "Failed to notify failure: %s\n", err) } os.Exit(1) } if err := healthchecks.Success(context.Background(), conf.HealthchecksConfig, healtchecksClt, check); err != nil { fmt.Fprintf(os.Stderr, "Failed to notify process success: %s\n", err) } } // this method would probably require a bit of refacto, it's quite ugly func reconcile(leases []*dnsmasq.Lease, assigned []*netbox.IP, free []*netbox.IP, interfaces []*netbox.Interface) (toDelete []*netbox.IP, toAdd []*netbox.IP) { dhcpMapping := map[string]string{} for _, l := range leases { dhcpMapping[l.IP.String()] = l.Mac } assignedMapping := map[string]string{} freeMapping := map[string]*netbox.IP{} interfaceMapping := map[string]*netbox.Interface{} for _, i := range interfaces { if i.MacAddress == "" { continue } interfaceMapping[strings.ToLower(i.MacAddress)] = i } for _, a := range assigned { assignedMapping[a.Address.Addr().String()] = "" _, ok := dhcpMapping[a.Address.Addr().String()] if !ok { toDelete = append(toDelete, a) } } for _, a := range free { freeMapping[a.Address.Addr().String()] = a } for _, l := range leases { _, ok := assignedMapping[l.IP.String()] if !ok { iface, ok := interfaceMapping[strings.ToLower(l.Mac)] if !ok { continue } ip, ok := freeMapping[l.IP.String()] if !ok { continue } ip.Interface = iface toAdd = append(toAdd, ip) } } return }