package main import ( "fmt" "os" "strings" "sync" "git.xbudex.com/buddy/update-dns/dns" "git.xbudex.com/buddy/update-dns/ip" "github.com/pkg/errors" "github.com/urfave/cli" ) var validTypes = []string{"A", "AAAA", "TXT"} type typeFlag struct { value string } func (f *typeFlag) Set(v string) error { value := strings.ToUpper(v) for _, val := range validTypes { if val == value { f.value = val return nil } } return errors.Errorf("invalid type %s, expected %v", v, validTypes) } func (f *typeFlag) String() string { return f.value } func main() { app := cli.NewApp() app.Name = "update-dns" app.Usage = "update dns using dnsimple with public ip address" app.UsageText = "update-dns [options] host" app.Version = "0.3.0-dev" app.Action = action app.HideHelp = true app.Authors = []cli.Author{ cli.Author{ Name: "Buddy Sandidge", Email: "buddy@sandidge.lol", }, } app.Flags = []cli.Flag{ cli.GenericFlag{ Name: "type", Value: &typeFlag{value: "AAAA"}, Usage: "type of dns record to create", }, cli.IntFlag{ Name: "ttl", Value: 600, Usage: "time to live in seconds for dns record", }, cli.StringFlag{ Name: "content", Value: "", Usage: "content to set manually, defaults to fetching ip address if not set", }, cli.StringFlag{ Name: "get-ipv4-url", Value: "https://whatismyipv4.buddy.wtf/json", Usage: "url to use to get public IPv4 address", }, cli.StringFlag{ Name: "get-ipv6-url", Value: "https://whatismyipv6.buddy.wtf/json", Usage: "url to use to get public IPv6 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 { var wg sync.WaitGroup if context.NArg() < 1 { return runtimeError("requires host argument") } host := context.Args().Get(0) kind := context.Generic("type").(*typeFlag).value ttl := context.Int("ttl") wg.Add(1) ipClient := &ip.Client{ URLv4: context.String("get-ipv4-url"), URLv6: context.String("get-ipv6-url"), } var getIPError error var content string go func() { defer wg.Done() var err error result := context.String("content") switch kind { case "A": if result == "" { result, err = ipClient.GetIPv4() } case "AAAA": if result == "" { result, err = ipClient.GetIPv6() } default: if result == "" { err = errors.Errorf("could not get content for type %s", kind) return } } if err != nil { getIPError = errors.Wrap(err, "could not get IP address") } else { content = result } }() client := dns.New(context.String("dnsimple-token")) records := []dns.Record{} var getRecordsError error wg.Add(1) go func() { defer wg.Done() if results, err := client.GetRecords(host, kind); err != nil { getRecordsError = errors.Wrap(err, "could not get records for host") } else { records = results } }() wg.Wait() if getIPError != nil { return errors.Wrap(getIPError, "could no get ip address") } if getRecordsError != nil { return errors.Wrap(getRecordsError, "could no get record") } if len(records) == 1 && records[0].Content == content { fmt.Printf("%s record for %s already set as %s\n", kind, host, content) return nil } newRecord := dns.Record{Type: kind, Content: content, TTL: ttl} if err := client.CreateRecord(host, newRecord); err != nil { return errors.Wrap(err, "could not create record") } fmt.Printf("%s record created for %s as %s\n", kind, host, content) var deleteWG sync.WaitGroup for _, record := range records { deleteWG.Add(1) go func(r dns.Record) { defer deleteWG.Done() fmt.Printf("%s record for %s being deleting %s\n", r.Type, host, r.Content) client.DeleteRecord(r) }(record) } deleteWG.Wait() return nil }