Skip to content

Commit

Permalink
Better code re-use.
Browse files Browse the repository at this point in the history
Better typing.
  • Loading branch information
joshuar committed Feb 28, 2023
1 parent ad28465 commit 975c877
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 102 deletions.
12 changes: 6 additions & 6 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
137 changes: 57 additions & 80 deletions internal/cloudflare/cloudflare.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,136 +26,113 @@ 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"))
if err != nil {
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
}
30 changes: 14 additions & 16 deletions internal/iplookup/iplookup.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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 {
Expand All @@ -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
Expand Down

0 comments on commit 975c877

Please sign in to comment.