package main import ( "slices" "strings" "git.readonly.ch/bouzoure/gestion-dns/dnsconfig" "git.readonly.ch/bouzoure/gestion-dns/helpers" "git.readonly.ch/bouzoure/gestion-dns/hetzner" ) func main() { log := helpers.GetLogger() config, err := helpers.GetConfig() if err != nil { log.Fatal(err) } if config.DryRun { log.Warn("Dry run is enabled, changes will not be applied") } else { log.Warn("Dry run is disabled, changes will be applied") } // Fetch zones from Hetzner API log.Info("Fetching existing zones from Hetzner API") hZonesTmp, err := hetzner.GetZones() if err != nil { log.Fatal(err) } // Fetch records from Hetzner API log.Info("Fetching existing records from Hetzner API") var hZones []hetzner.Zone for _, zone := range hZonesTmp { err = hetzner.GetRecords(&zone) if err != nil { log.Error(err) continue } hZones = append(hZones, zone) } // Fetch zones from config files log.Info("Fetching zones from DNS config files") zones, err := dnsconfig.GetZones() if err != nil { log.Fatal(err) } // Create missing config files from existing zones log.Info("Checking if we need to create DNS config files from existing zones") for _, hZone := range hZones { found := false for _, zone := range zones { if strings.EqualFold(hZone.Name, zone.Domain) { found = true break } } if found { continue } log.Info("Creating DNS config file for existing zone", "zone", hZone.Name) zone := dnsconfig.Zone{ Domain: strings.ToLower(hZone.Name), DefaultTTL: 3600, } for _, record := range hZone.Records { zone.Records = append(zone.Records, dnsconfig.Record{ Name: record.Name, Type: record.Type, Value: record.Value, TTL: record.TTL, Flat: false, }) } err = dnsconfig.CreateZone(zone) if err != nil { log.Fatal(err) } } // Fetch zones from config files again // This is because the previous step might have created new config files log.Info("Fetching zones from DNS config files") zones, err = dnsconfig.GetZones() if err != nil { log.Fatal(err) } log.Info("Starting sync (step 1: create/update)", "dry_run", config.DryRun) var keepTheseIds []string for _, zone := range zones { if zone.DefaultTTL <= 0 { zone.DefaultTTL = 3600 } for _, hZone := range hZones { if strings.EqualFold(zone.Domain, hZone.Name) { log.Info("Syncing zone", "step", 1, "name", zone.Domain) var alreadyFoundIds []string for _, record := range zone.Records { if record.Flat { record.Value = helpers.ResolveRecord( record.Value, record.Type, ) if len(record.Value) == 0 { log.Error("Could not flatten record, skipping", "record", record) continue } } if record.TTL <= 0 { record.TTL = zone.DefaultTTL } var id string for _, hRecord := range hZone.Records { if slices.Contains(alreadyFoundIds, hRecord.ID) { continue } if !strings.EqualFold(record.Name, hRecord.Name) { continue } if !strings.EqualFold(record.Type, hRecord.Type) { continue } id = hRecord.ID alreadyFoundIds = append(alreadyFoundIds, id) break } if len(id) > 0 { keepTheseIds = append(keepTheseIds, id) updateNeeded := false for _, hRecord := range hZone.Records { if hRecord.ID == id { if record.TTL != hRecord.TTL { updateNeeded = true } if record.Value != hRecord.Value { updateNeeded = true } break } } if updateNeeded { log.Info( "Updating record", "name", record.Name, "type", record.Type, "id", id, ) if !config.DryRun { newRecord, err := hetzner.UpdateRecord(&hetzner.Record{ ID: id, Type: record.Type, Name: record.Name, Value: record.Value, TTL: record.TTL, ZoneID: hZone.ID, }) if err != nil { log.Error(err) } log.Info( "Record updated", "ID", newRecord.ID, ) } } } else { log.Info( "Creating record", "name", record.Name, "type", record.Type, "value", record.Value, "ttl", record.TTL, ) if !config.DryRun { newRecord, err := hetzner.CreateRecord(&hetzner.Record{ Type: record.Type, Name: record.Name, Value: record.Value, TTL: record.TTL, ZoneID: hZone.ID, }) if err != nil { log.Error(err) } log.Info( "Record created", "ID", newRecord.ID, ) } } } break } } } log.Info("Starting sync (step 2: delete)", "dry_run", config.DryRun) for _, zone := range zones { for _, hZone := range hZones { if strings.EqualFold(zone.Domain, hZone.Name) { log.Info("Syncing zone", "setp", 2, "name", zone.Domain) for _, hRecord := range hZone.Records { if !slices.Contains(keepTheseIds, hRecord.ID) { log.Info( "Deleting record", "name", hRecord.Name, "type", hRecord.Type, "id", hRecord.ID, ) if !config.DryRun { hetzner.DeleteRecord(&hetzner.Record{ ID: hRecord.ID, }) if err != nil { log.Error(err) } log.Info( "Record deleted", "ID", hRecord.ID, ) } } } break } } } log.Info("Sync is finished, all done!") }