From 7534108b7940b460a6af812479aab819cc20d540 Mon Sep 17 00:00:00 2001 From: Balakrishnan Balasubramanian Date: Thu, 23 Mar 2023 18:57:30 -0400 Subject: [PATCH] Support deleting records --- main.go | 194 ++++++++++++++++++++++++++++++++++++++++----------- main_test.go | 15 ++++ 2 files changed, 167 insertions(+), 42 deletions(-) create mode 100644 main_test.go diff --git a/main.go b/main.go index 79d4c57..9142089 100644 --- a/main.go +++ b/main.go @@ -8,6 +8,8 @@ import ( "log" "net" "os" + "sort" + "strings" "github.com/libdns/cloudflare" "github.com/libdns/libdns" @@ -16,76 +18,184 @@ import ( var ( domain = os.Getenv("DOMAIN") token = os.Getenv("CF_TOKEN") + ip = net.IPv4(127, 0, 0, 1) + cname string + sub = "" + path string + del = false ) +func genRecord() (libdns.Record, error) { + switch { + case cname != "": + return libdns.Record{ + Type: "CNAME", + Name: sub, + Value: cname, + }, nil + case len(ip) == net.IPv4len: + return libdns.Record{ + Type: "A", + Name: sub, + Value: ip.To4().String(), + }, nil + case len(ip) == net.IPv6len: + return libdns.Record{ + Type: "AAAA", + Name: sub, + Value: ip.To16().String(), + }, nil + } + return libdns.Record{}, fmt.Errorf("neither cname nor valid ip is set") +} + +func selectRecordsToDelete(recs []libdns.Record) []libdns.Record { + name := func() string { + if sub == "@" { + return domain + } + return fmt.Sprintf("%s.%s", sub, domain) + }() + recs = func() (result []libdns.Record) { + for _, r := range recs { + if r.Name == name { + result = append(result, r) + } + } + return + }() + fmt.Printf("Records matching %s\n", name) + for i, r := range recs { + fmt.Printf("%5d %6s %s\n", i, r.Type, r.Value) + } + var delRange string + fmt.Print("Enter record indexes seperated by comma(,) to delete. Use hyphen(-) for a closed range. Also can be all or none\nIndexes: ") + if _, err := fmt.Scanln(&delRange); err != nil { + log.Panicln(err) + } + switch { + case delRange == "all": + return recs + case delRange == "none": + return nil + default: + indexGen := func() func() int { + indexes := parseRange(delRange, len(recs)-1) + indexIndex := 0 + return func() int { + if indexIndex == len(indexes) { + return -1 + } + val := indexes[indexIndex] + indexIndex++ + return val + } + }() + return func() (result []libdns.Record) { + nextIndex := indexGen() + for i, r := range recs { + if i == nextIndex { + result = append(result, r) + nextIndex = indexGen() + } + } + return + }() + } + +} + func main() { ctx := context.TODO() - ip := net.IPv4(127, 0, 0, 1) - var cname string - sub := "" - var path string flag.StringVar(&domain, "d", domain, "Domain name, e.g. example.com") - flag.StringVar(&sub, "s", sub, "Subdomain to add dns entry for, e.g. blog") + flag.StringVar(&sub, "s", sub, "Subdomain, e.g. blog. Use @ for root") flag.StringVar(&token, "a", token, "Cloudflare API Token") flag.TextVar(&ip, "i", ip, "IP address") flag.StringVar(&cname, "c", cname, "CNAME target") - flag.StringVar(&path, "o", path, "path to save all records as json, e.g. ./records.json") + flag.StringVar(&path, "o", path, "Path to save all records as json, e.g. ./records.json") + flag.BoolVar(&del, "x", del, "Delete records of subdomain") flag.Parse() provider := cloudflare.Provider{APIToken: token} zone := domain + "." - fmt.Println(zone, sub, cname) - makeRecord := func() (libdns.Record, error) { - switch { - case cname != "": - return libdns.Record{ - Type: "CNAME", - Name: sub, - Value: cname, - }, nil - case len(ip) == net.IPv4len: - return libdns.Record{ - Type: "A", - Name: sub, - Value: ip.To4().String(), - }, nil - case len(ip) == net.IPv6len: - return libdns.Record{ - Type: "AAAA", - Name: sub, - Value: ip.To16().String(), - }, nil - } - return libdns.Record{}, fmt.Errorf("neither cname nor valid ip is set") - } if sub != "" { - record, err := makeRecord() - if err != nil { - panic(err) + if del { + recs, err := provider.GetRecords(ctx, zone) + if err != nil { + log.Panicln(err) + } + recs = selectRecordsToDelete(recs) + recs, err = provider.DeleteRecords(ctx, zone, recs) + if err != nil { + log.Panicln(err) + } + if len(recs) == 0 { + fmt.Println("Nothing was deleted") + } else { + fmt.Println("Records deleted:") + for i, r := range recs { + fmt.Printf("%5d %6s %s\n", i, r.Type, r.Value) + } + } + } else { + + record, err := genRecord() + if err != nil { + log.Panicln(err) + } + log.Printf("setting record, %+v", record) + newRecs, err := provider.SetRecords(ctx, zone, []libdns.Record{record}) + if err != nil { + log.Panicln(err) + } + fmt.Println(newRecs) } - log.Printf("setting record, %+v", record) - newRecs, err := provider.SetRecords(ctx, zone, []libdns.Record{record}) - if err != nil { - panic(err) - } - fmt.Println(newRecs) } if path != "" { recs, err := provider.GetRecords(ctx, zone) if err != nil { - panic(err) + log.Panicln(err) } data, err := json.Marshal(recs) if err != nil { - panic(err) + log.Panicln(err) } err = os.WriteFile(path, data, 0644) if err != nil { - panic(err) + log.Panicln(err) } } } + +func parseRange(rs string, max int) []int { + var indexes []int + m := uint(max) + for _, s := range strings.Split(rs, ",") { + s = strings.TrimSpace(s) + var i, a, b uint + if _, err := fmt.Sscanf(s, "%v-%v", &a, &b); err == nil { + for s := a; s <= b && s <= m; { + indexes = append(indexes, int(s)) + s++ + } + } else { + if _, err := fmt.Sscanf(s, "%v", &i); err == nil && i <= m { + indexes = append(indexes, int(i)) + } + } + } + sort.Ints(indexes) + var uniq []int + last := -1 + for _, i := range indexes { + if i != last { + uniq = append(uniq, i) + last = i + } + } + return uniq +} diff --git a/main_test.go b/main_test.go new file mode 100644 index 0000000..cc8f23b --- /dev/null +++ b/main_test.go @@ -0,0 +1,15 @@ +package main + +import ( + "fmt" + "testing" +) + +func TestParseRange(t *testing.T) { + expected := []int{0, 1, 2, 6} + actual := parseRange("0-2,-5,6,-7-7,1, 11", 10) + if fmt.Sprint(expected) != fmt.Sprint(actual) { + fmt.Printf("unexpected %#v\n", actual) + t.Fail() + } +}