diff options
Diffstat (limited to '')
| -rw-r--r-- | main.go | 179 |
1 files changed, 179 insertions, 0 deletions
@@ -0,0 +1,179 @@ +package main + +import ( + "context" + "flag" + "fmt" + "log" + "os" + "strings" + + "google.golang.org/api/dns/v1" +) + +const ( + GCP_PROJECT_NAME = "fcuny-homelab" + GCP_MANAGED_ZONE = "fcuny-xyz" + TS_DEVICE_NAME = "tahoe" + TTL = 300 +) + +var desiredRecords = []string{ + "bt", + "dash", + "music", + "unifi", +} + +const usage = `Usage: + dns-updater [OPTIONS] <COMMAND> + +Options: + -ts-api-key API key for tailscale + +Examples: + $ dns-updater -ts-api-key $(passage credentials/tailscale) + $ dns-updater -ts-api-key $(passage credentials/tailscale) update +` + +var l = log.New(os.Stderr, "", 0) + +func main() { + var ( + tsAPIKeyFlag string + command string + ) + + flag.Usage = func() { fmt.Fprintf(os.Stderr, "%s\n", usage) } + flag.StringVar(&tsAPIKeyFlag, "ts-api-key", "", "the API key for tailscale") + flag.Parse() + + if flag.NArg() > 1 { + flag.Usage() + os.Exit(1) + } else { + command = flag.Arg(0) + if command != "" && command != "update" { + flag.Usage() + os.Exit(1) + } + } + + ctx := context.Background() + + // we only care about IPv4 for now + tsIpV4Addresses, _, err := getTsIpsDevice(ctx, tsAPIKeyFlag, TS_DEVICE_NAME) + if err != nil { + l.Fatalf("failed to get the IP addresses for %s: %v", TS_DEVICE_NAME, err) + } + + svc, err := dns.NewService(ctx) + if err != nil { + l.Fatalf("failed to create the client for Google Cloud DNS: %v", err) + } + + zone, err := svc.ManagedZones.Get(GCP_PROJECT_NAME, GCP_MANAGED_ZONE).Context(ctx).Do() + if err != nil { + l.Fatalf("failed to get information about the managed zone %s: %+v", GCP_MANAGED_ZONE, err) + } + + recordSets, err := svc.ResourceRecordSets.List(GCP_PROJECT_NAME, GCP_MANAGED_ZONE).Context(ctx).Do() + if err != nil { + l.Fatalf("failed to get the list of records: %+v", err) + } + + switch command { + case "update": + updateRecords(ctx, svc, recordSets, zone, tsIpV4Addresses) + default: + listRecords(recordSets) + } +} + +func listRecords(recordSets *dns.ResourceRecordSetsListResponse) { + for _, record := range recordSets.Rrsets { + switch record.Type { + case "A": + ips := strings.Join(record.Rrdatas[:], ",") + fmt.Printf("%-25s %4d %s\n", record.Name, record.Ttl, ips) + } + } +} + +func updateRecords(ctx context.Context, svc *dns.Service, recordSets *dns.ResourceRecordSetsListResponse, zone *dns.ManagedZone, tsIpV4Addresses []string) { + var ( + existingRecordSets = []*dns.ResourceRecordSet{} + recordSetsToAdd = []*dns.ResourceRecordSet{} + recordSetsToDelete = []*dns.ResourceRecordSet{} + ) + + for _, record := range recordSets.Rrsets { + if record.Type == "A" { + existingRecordSets = append(existingRecordSets, record) + } + } + + // first pass: create what's missing + for _, subdomain := range desiredRecords { + found := false + subdomain = fmt.Sprintf("%s.%s", subdomain, zone.DnsName) + for _, r := range existingRecordSets { + if subdomain == r.Name && r.Type == "A" { + // check that the IP addresses are correct + ipsFound := 0 + for _, rr := range r.Rrdatas { + for _, ip := range tsIpV4Addresses { + if rr == ip { + ipsFound += 1 + continue + } + } + } + // while we found the subdomain with the correct type, + // we also need to make sure the list of IPs is + // correct. If they are not, we delete the record and + // add it again with the correct values. + if ipsFound == len(tsIpV4Addresses) { + found = true + continue + } else { + l.Printf("will delete %s (incorrect IPv4 addresses)\n", subdomain) + recordSetsToDelete = append(recordSetsToDelete, r) + } + } + } + if !found { + l.Printf("will add %s\n", subdomain) + r := &dns.ResourceRecordSet{ + Name: subdomain, + Type: "A", + Ttl: TTL, + Rrdatas: tsIpV4Addresses, + } + recordSetsToAdd = append(recordSetsToAdd, r) + } + } + + // second pass: delete what's not needed + for _, r := range existingRecordSets { + found := false + for _, subdomain := range desiredRecords { + subdomain = fmt.Sprintf("%s.%s", subdomain, zone.DnsName) + if subdomain == r.Name && r.Type == "A" { + found = true + continue + } + } + if !found { + l.Printf("will delete %s\n", r.Name) + recordSetsToDelete = append(recordSetsToDelete, r) + } + } + + if len(recordSetsToAdd) > 0 || len(recordSetsToDelete) > 0 { + change := &dns.Change{Additions: recordSetsToAdd, Deletions: recordSetsToDelete} + if _, err := svc.Changes.Create(GCP_PROJECT_NAME, GCP_MANAGED_ZONE, change).Context(ctx).Do(); err != nil { + l.Fatalf("failed to apply the change: %+v", err) + } + } +} |
