From 59b259e55f8ee6a6f48973f45f29c4c980215d85 Mon Sep 17 00:00:00 2001 From: Buddy Sandidge Date: Fri, 18 Nov 2016 14:53:24 -0800 Subject: [PATCH] Updating dnsimple with ip from internet service MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit • Get dnsimple oauth token from flag or environment variable • Check for ip address from whatismyipv6.buddy.wtf • Check what the dns record for the current host is set to • After getting record and ip address, either create, updated, or do nothing for the given host TODO: clean up code --- main.go | 143 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 139 insertions(+), 4 deletions(-) diff --git a/main.go b/main.go index 0d3ab15..740c87c 100644 --- a/main.go +++ b/main.go @@ -5,7 +5,11 @@ import ( "fmt" "net/http" "os" + "strconv" + "strings" + "sync" + "github.com/dnsimple/dnsimple-go/dnsimple" "github.com/pkg/errors" "github.com/urfave/cli" ) @@ -22,17 +26,97 @@ func main() { Value: "https://whatismyipv6.buddy.wtf/json", Usage: "url to use to get public ip address", }, + + cli.StringFlag{ + EnvVar: "DNSIMPLE_TOKEN", + Name: "dnsimple-token", + Usage: "dnsimple token", + }, } app.Run(os.Args) } +type runtimeError string + +func (e runtimeError) Error() string { + return string(e) +} + func action(context *cli.Context) error { - result, err := getIP(context.String("url")) - if err != nil { - return errors.Wrap(err, "could not get IP address") + var wg sync.WaitGroup + if context.NArg() < 1 { + return runtimeError("requires host argument") + } + + host := context.Args().Get(0) + + wg.Add(1) + var getIPError error + var ip string + go func() { + defer wg.Done() + result, err := getIP(context.String("url")) + if err != nil { + getIPError = errors.Wrap(err, "could not get IP address") + return + } + ip = result.IP + }() + + var dnSimpleAccountID string + var getRecordError error + var record *dnsimple.ZoneRecord + credentials := dnsimple.NewOauthTokenCredentials(context.String("dnsimple-token")) + dnSimpleClient := dnsimple.NewClient(credentials) + wg.Add(1) + go func() { + defer wg.Done() + + accountID, result, err := getRecord(dnSimpleClient, host, "AAAA") + dnSimpleAccountID = accountID + if err != nil { + getRecordError = errors.Wrap(err, "could not get ip for host") + return + } + record = result + }() + + wg.Wait() + + if getIPError != nil { + return errors.Wrap(getIPError, "could no get ip address") + } + if getRecordError != nil { + switch errors.Cause(getRecordError).(type) { + case notFoundError: + break + default: + return errors.Wrap(getRecordError, "could no get record") + } + } + + subdomain, domain, _ := splitHost(host) + if record == nil { + _, err := dnSimpleClient.Zones.CreateRecord(dnSimpleAccountID, domain, dnsimple.ZoneRecord{ + Name: subdomain, + Type: "AAAA", + Content: ip, + }) + if err != nil { + return errors.Wrap(err, "could not create record") + } + fmt.Printf("%s record created for %s as %s\n", "AAAA", host, ip) + } else if ip != record.Content { + record.Content = ip + _, err := dnSimpleClient.Zones.UpdateRecord(dnSimpleAccountID, domain, record.ID, *record) + if err != nil { + return errors.Wrap(err, "could not update record") + } + fmt.Printf("%s record for %s updated to %s\n", "AAAA", host, ip) + } else { + fmt.Printf("%s record for %s already set as %s\n", "AAAA", host, ip) } - fmt.Println(result.IP) return nil } @@ -41,6 +125,57 @@ type whatIsMyIPResult struct { Version string `json:version` } +type notFoundError error + +func getRecord(client *dnsimple.Client, host string, kind string) (string, *dnsimple.ZoneRecord, error) { + subdomain, domain, err := splitHost(host) + if err != nil { + return "", nil, errors.Wrap(err, "unable to parse host") + } + accountID, err := getAccountID(client) + if err != nil { + return "", nil, errors.Wrap(err, "could not get account id") + } + + records, err := client.Zones.ListRecords(accountID, domain, &dnsimple.ZoneRecordListOptions{}) + if err != nil { + return "", nil, errors.Wrap(err, "could not get list of records") + } + + for _, record := range records.Data { + if record.ZoneID == domain && record.Name == subdomain && record.Type == kind { + return accountID, &record, nil + } + } + + return accountID, nil, notFoundError(errors.New("could not find record")) +} + +func getAccountID(client *dnsimple.Client) (string, error) { + whoamiResponse, err := client.Identity.Whoami() + if err != nil { + return "", errors.Wrap(err, "could not get account information from dnsimple") + } + if whoamiResponse.Data.Account == nil { + return "", errors.New("could not get account information") + } + return strconv.Itoa(whoamiResponse.Data.Account.ID), nil +} + +func splitHost(host string) (subdomain string, domain string, err error) { + subdomains := strings.Split(host, ".") + + if len(subdomains) >= 3 { + domain = strings.Join(subdomains[len(subdomains)-2:], ".") + subdomain = strings.Join(subdomains[:len(subdomains)-2], ".") + } else if len(subdomains) == 2 { + domain = strings.Join(subdomains, ".") + } else { + err = errors.New("invalid domain") + } + return subdomain, domain, err +} + func getIP(url string) (*whatIsMyIPResult, error) { result, err := http.Get(url) if err != nil {