From 975c8772aef08768a55e3efc43a11d2455adec30 Mon Sep 17 00:00:00 2001 From: Joshua Rich Date: Tue, 28 Feb 2023 10:39:41 +1000 Subject: [PATCH] Better code re-use. Better typing. --- .goreleaser.yml | 12 +-- internal/cloudflare/cloudflare.go | 137 +++++++++++++----------------- internal/iplookup/iplookup.go | 30 +++---- 3 files changed, 77 insertions(+), 102 deletions(-) diff --git a/.goreleaser.yml b/.goreleaser.yml index 0051389..539cdc6 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -10,12 +10,12 @@ builds: - windows - darwin archives: - - replacements: - darwin: Darwin - linux: Linux - windows: Windows - 386: i386 - amd64: x86_64 + - name_template: >- + {{ .ProjectName }}_ + {{- title .Os }}_ + {{- if eq .Arch "amd64" }}x86_64 + {{- else if eq .Arch "386" }}i386 + {{- else }}{{ .Arch }}{{ end }} checksum: name_template: 'checksums.txt' snapshot: diff --git a/internal/cloudflare/cloudflare.go b/internal/cloudflare/cloudflare.go index aa638d2..3938b21 100644 --- a/internal/cloudflare/cloudflare.go +++ b/internal/cloudflare/cloudflare.go @@ -26,16 +26,17 @@ import ( ) type cloudflare struct { - api *cf.API - zoneID string - record map[string]*dnsRecord + api *cf.API + zoneID string + records map[string]*dnsRecord } type dnsRecord struct { - name, id, recordType, IpAddr string + name, iptype, ipaddr string } func NewCloudflare() *cloudflare { + log.Debug("Establishing API connection") api, err := cf.New( viper.GetString("account.apiKey"), viper.GetString("account.email")) @@ -43,119 +44,95 @@ func NewCloudflare() *cloudflare { log.Fatalf("Unable to establish Cloudflare API connection: %v", err) } + log.Debug("Retrieving Zone ID") id, err := api.ZoneIDByName(viper.GetString("account.zone")) if err != nil { - log.Fatalf("Unable to retrieve zone details from Cloudflare API: %v", err) + log.Fatalf("Unable to retrieve zone ID from Cloudflare API: %v", err) } - ctx := context.Background() - record := make(map[string]*dnsRecord) + cfDetails := &cloudflare{ + api: api, + zoneID: id, + records: make(map[string]*dnsRecord), + } + + if viper.GetStringSlice("records") == nil { + log.Fatal("No records specified in config, nothing to do") + } - for _, r := range getRecordsFromConfig() { + for _, r := range viper.GetStringSlice("records") { log.Debugf("Getting DNS record(s) for %s", r) recs, _, err := api.ListDNSRecords( - ctx, - cf.ZoneIdentifier(id), + context.Background(), + cf.ZoneIdentifier(cfDetails.zoneID), cf.ListDNSRecordsParams{Name: r}) if err != nil { log.Fatalf("Unable to retrieve record details for %s from Cloudflare API: %v", r, err) } if recs != nil { for _, r := range recs { - record[r.ID] = &dnsRecord{ - name: r.Name, - IpAddr: r.Content, - recordType: r.Type, + cfDetails.records[r.ID] = &dnsRecord{ + name: r.Name, + ipaddr: r.Content, + iptype: r.Type, } } } else { - log.Warnf("%s has no matching DNS record(s) in Cloudflare zone, creating a new one", r) + log.Warnf("%s has no matching DNS record(s) in Cloudflare zone, creating them", r) addr := iplookup.LookupExternalIP() - if addr.Ipv4 != "" { - res, err := api.CreateDNSRecord( - ctx, - cf.ZoneIdentifier(id), - cf.CreateDNSRecordParams{ - Name: r, - Content: addr.Ipv4, - Type: "A", - }) - if err != nil { - log.Warnf("Unable to create new IPv4 record for %s: %v", r, err) - } else { - log.Infof("Created new IPv4 record for %s", r) - } - record[res.Result.ID] = &dnsRecord{ - name: res.Result.Name, - IpAddr: res.Result.Content, - recordType: res.Result.Type, + if addr.IPv4 != nil { + record := &dnsRecord{ + name: r, + iptype: "A", + ipaddr: addr.IPv4.String(), } + cfDetails.setDNSRecord(record) } - if addr.Ipv6 != "" { - res, err := api.CreateDNSRecord( - ctx, - cf.ZoneIdentifier(id), - cf.CreateDNSRecordParams{ - Name: r, - Content: addr.Ipv6, - Type: "AAAA", - }) - if err != nil { - log.Warnf("Unable to create new IPv4 record for %s: %v", r, err) - } else { - log.Infof("Created new IPv4 record for %s", r) - } - record[res.Result.ID] = &dnsRecord{ - name: res.Result.Name, - IpAddr: res.Result.Content, - recordType: res.Result.Type, + if addr.IPv6 != nil { + record := &dnsRecord{ + name: r, + iptype: "AAAA", + ipaddr: addr.IPv6.String(), } + cfDetails.setDNSRecord(record) } } } - return &cloudflare{ - api: api, - zoneID: id, - record: record, - } + return cfDetails } func (c *cloudflare) CheckAndUpdate() { addr := iplookup.LookupExternalIP() - for id, details := range c.record { - switch details.recordType { - case "A": - if details.IpAddr != addr.Ipv4 { - log.Debugf("Record %s (type %s) needs updating. Previously %s, now %s", details.name, details.recordType, details.IpAddr, addr.Ipv4) - c.setDNSRecord(id, details.recordType, addr.Ipv4) - } - case "AAAA": - if details.IpAddr != addr.Ipv6 { - log.Debugf("Record %s (type %s) needs updating. Previously %s, now %s", details.name, details.recordType, details.IpAddr, addr.Ipv6) - c.setDNSRecord(id, details.recordType, addr.Ipv6) + if addr != nil { + for recordID, details := range c.records { + switch details.iptype { + case "A": + if details.ipaddr != addr.IPv4.String() { + log.Debugf("Record %s (type %s) needs updating. Previously %s, now %s", details.name, details.iptype, details.ipaddr, addr.IPv4.String()) + c.setDNSRecord(c.records[recordID]) + } + case "AAAA": + if details.ipaddr != addr.IPv6.String() { + log.Debugf("Record %s (type %s) needs updating. Previously %s, now %s", details.name, details.iptype, details.ipaddr, addr.IPv6.String()) + c.setDNSRecord(c.records[recordID]) + } } } } } -func (c *cloudflare) setDNSRecord(id string, recordType string, addr string) { +func (c *cloudflare) setDNSRecord(record *dnsRecord) { _, err := c.api.CreateDNSRecord( context.Background(), - cf.ResourceIdentifier(id), + cf.ResourceIdentifier(c.zoneID), cf.CreateDNSRecordParams{ - Content: addr, - Type: recordType, + Name: record.name, + Content: record.ipaddr, + Type: record.iptype, + TTL: 1, }) if err != nil { - log.Errorf("Unable to update record %s: %v", id, err) - } -} - -func getRecordsFromConfig() []string { - records := viper.GetStringSlice("records") - if records == nil { - log.Fatal("No records to check found in config? Nothing to do.") + log.Errorf("Unable to update record %s (type %s, content %s): %v", record.name, record.iptype, record.ipaddr, err) } - return records } diff --git a/internal/iplookup/iplookup.go b/internal/iplookup/iplookup.go index ef2f0ad..c23c899 100644 --- a/internal/iplookup/iplookup.go +++ b/internal/iplookup/iplookup.go @@ -5,7 +5,7 @@ Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 + http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, @@ -24,12 +24,12 @@ import ( ) type address struct { - Ipv4, Ipv6 string + IPv4, IPv6 net.IP } -var ipLookupHosts = map[string]address{ - "icanhazip": {Ipv4: "https://4.icanhazip.com", Ipv6: "https://6.icanhazip.com"}, - "ipify": {Ipv4: "https://api.ipify.org", Ipv6: "https://api6.ipify.org"}, +var ipLookupHosts = map[string]map[string]string{ + "icanhazip": {"IPv4": "https://4.icanhazip.com", "IPv6": "https://6.icanhazip.com"}, + "ipify": {"IPv4": "https://api.ipify.org", "IPv6": "https://api6.ipify.org"}, } func LookupExternalIP() *address { @@ -44,34 +44,32 @@ func LookupExternalIP() *address { defer wg.Done() client := resty.New().SetLogger(&log.Logger{}) client.SetRetryCount(3) - resp, err := client.R().Get(addr.Ipv4) + log.Debugf("Fetching IPv4 address from %s", addr["IPv4"]) + resp, err := client.R().Get(addr["IPv4"]) if err != nil { log.Warnf("Unable to retrieve external IPv4 address: %v", err) } if resp.StatusCode() == 200 && resp.Body() != nil { - externalAddr.Ipv4 = resp.String() - log.Debugf("Found external IPv4 address %s using %s", externalAddr.Ipv4, host) + log.Debugf("Found external IPv4 address %s", resp.String()) + externalAddr.IPv4 = net.ParseIP(resp.String()) } }() go func() { defer wg.Done() client := resty.New().SetLogger(&log.Logger{}) client.SetRetryCount(3) - resp, err := client.R().Get(addr.Ipv6) + log.Debugf("Fetching IPv6 address from %s", addr["IPv6"]) + resp, err := client.R().Get(addr["IPv6"]) if err != nil { log.Warnf("Unable to retrieve external IPv6 address: %v", err) } if resp.StatusCode() == 200 && resp.Body() != nil { - log.Debugf("Found external IPv6 address %s using %s", externalAddr.Ipv6, host) - externalAddr.Ipv6 = resp.String() + log.Debugf("Found external IPv6 address %s", resp.String(), host) + externalAddr.IPv6 = net.ParseIP(resp.String()) } }() wg.Wait() - // If we have at least, either a valid v4 or v6 external address, - // return those - if net.ParseIP(externalAddr.Ipv4) != nil || net.ParseIP(externalAddr.Ipv6) != nil { - return &externalAddr - } + return &externalAddr } // At this point, we've gone through all IP checkers and not found an // external address