From f1a0d18ac19d0db4f7060ea5a5c662348118e399 Mon Sep 17 00:00:00 2001 From: Jeff Carr Date: Mon, 1 Jan 2024 15:31:33 -0600 Subject: [PATCH] initial commit of cloudflare api Signed-off-by: Jeff Carr --- api.go | 228 ++++++++++++++++++++++++++++++++++++++++++++++ create.go | 57 ++++++++++++ curl.sh | 23 +++++ delete.go | 64 +++++++++++++ http.go | 190 ++++++++++++++++++++++++++++++++++++++ json.go | 25 +++++ loadZoneWindow.go | 91 ++++++++++++++++++ mainWindow.go | 170 ++++++++++++++++++++++++++++++++++ rr.go | 157 +++++++++++++++++++++++++++++++ structs.go | 105 +++++++++++++++++++++ 10 files changed, 1110 insertions(+) create mode 100644 api.go create mode 100644 create.go create mode 100755 curl.sh create mode 100644 delete.go create mode 100644 http.go create mode 100644 json.go create mode 100644 loadZoneWindow.go create mode 100644 mainWindow.go create mode 100644 rr.go create mode 100644 structs.go diff --git a/api.go b/api.go new file mode 100644 index 0000000..41ddae6 --- /dev/null +++ b/api.go @@ -0,0 +1,228 @@ +// This is a simple example +package cloudflare + +import ( + "encoding/json" + "io/ioutil" + "net/http" + + "go.wit.com/log" +) + +/* + This function should run each time + the user chanegs anything in the GUi + or each time something in general changes + + It returns a RR record which then can be + turned into JSON and sent via http + to cloudflare's API +*/ +func DoChange() *RRT { + var dnsRow *RRT + dnsRow = new(RRT) + + log.Println("DoChange() START") + if (CFdialog.proxyNode.S == "On") { + dnsRow.Proxied = true + } else { + dnsRow.Proxied = false + } + dnsRow.Auth = CFdialog.apiNode.S + dnsRow.Email = CFdialog.emailNode.S + + dnsRow.Domain = CFdialog.zoneNode.S + dnsRow.ZoneID = CFdialog.zoneIdNode.S + dnsRow.ID = CFdialog.rrNode.S + + dnsRow.Content = CFdialog.ValueNode.S + dnsRow.Name = CFdialog.NameNode.S + dnsRow.Type = CFdialog.TypeNode.S + dnsRow.url = CFdialog.urlNode.S + + dnsRow.data = makeJSON(dnsRow) + // show the JSON + log.Println(dnsRow) + + if (CFdialog.curlNode != nil) { + pretty, _ := FormatJSON(dnsRow.data) + log.Println("http PUT curl =", pretty) + CFdialog.curlNode.SetText(pretty) + } + return dnsRow +} + +func SetRow(dnsRow *RRT) { + log.Println("Look for changes in row", dnsRow.ID) + if (CFdialog.proxyNode != nil) { + log.Println("Proxy", dnsRow.Proxied, "vs", CFdialog.proxyNode.S) + if (dnsRow.Proxied == true) { + CFdialog.proxyNode.SetText("On") + } else { + CFdialog.proxyNode.SetText("Off") + } + } + if (CFdialog.zoneNode != nil) { + CFdialog.zoneNode.SetText(dnsRow.Domain) + } + if (CFdialog.zoneIdNode != nil) { + CFdialog.zoneIdNode.SetText(dnsRow.ZoneID) + } + log.Println("zoneIdNode =", dnsRow.ZoneID) + if (CFdialog.rrNode != nil) { + CFdialog.rrNode.SetText(dnsRow.ID) + } + if (CFdialog.ValueNode != nil) { + log.Println("Content", dnsRow.Content, "vs", CFdialog.ValueNode.S) + CFdialog.ValueNode.SetText(dnsRow.Content) + } + if (CFdialog.NameNode != nil) { + CFdialog.NameNode.SetText(dnsRow.Name) + } + if (CFdialog.TypeNode != nil) { + CFdialog.TypeNode.SetText(dnsRow.Type) + } + + if (CFdialog.urlNode != nil) { + url := cloudflareURL + dnsRow.ZoneID + "/dns_records/" + dnsRow.ID + CFdialog.urlNode.SetText(url) + } + + // show the JSON + tmp := makeJSON(dnsRow) + log.Println(tmp) + if (CFdialog.curlNode != nil) { + pretty, _ := FormatJSON(tmp) + log.Println("http PUT curl =", pretty) + CFdialog.curlNode.SetText(pretty) + } +} + +func GetZonefile(c *ConfigT) *DNSRecords { + var url = cloudflareURL + c.ZoneID + "/dns_records/?per_page=100" + log.Println("getZonefile()", c.Domain, url) + req, err := http.NewRequest("GET", url, nil) + if err != nil { + log.Println("http.NewRequest error:", err) + return nil + } + + // Set headers + req.Header.Set("X-Auth-Key", c.Auth) + req.Header.Set("X-Auth-Email", c.Email) + + log.Println("getZonefile() auth, email", c.Auth, c.Email) + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + log.Println("http.Client error:", err) + return nil + } + defer resp.Body.Close() + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + log.Println("ioutil.ReadAll() error", err) + return nil + } + + var records DNSRecords + if err := json.Unmarshal(body, &records); err != nil { + log.Println("json.Unmarshal() error", err) + return nil + } + + log.Println("getZonefile() worked", records) + return &records +} + +/* + pass in a DNS Resource Records (the stuff in a zonefile) + + This will talk to the cloudflare API and generate a resource record in the zonefile: + + For example: + gitea.wit.com. 3600 IN CNAME git.wit.com. + go.wit.com. 3600 IN A 1.1.1.9 + test.wit.com. 3600 IN NS ns1.wit.com. +*/ +func makeJSON(dnsRow *RRT) string { + // make a json record to send on port 80 to cloudflare + var tmp string + tmp = `{"content": "` + dnsRow.Content + `", ` + tmp += `"name": "` + dnsRow.Name + `", ` + tmp += `"type": "` + dnsRow.Type + `", ` + tmp+= `"ttl": "` + "1" + `", ` + tmp += `"comment": "WIT DNS Control Panel"` + tmp += `}` + + return tmp +} + +// https://api.cloudflare.com/client/v4/zones +func GetZones(auth, email string) *DNSRecords { + var url = "https://api.cloudflare.com/client/v4/zones?per_page=100" + log.Println("getZones()", url) + req, err := http.NewRequest("GET", url, nil) + if err != nil { + log.Println("http.NewRequest error:", err) + return nil + } + + // Set headers + req.Header.Set("X-Auth-Key", auth) + req.Header.Set("X-Auth-Email", email) + + log.Println("getZones() auth, email", auth, email) + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + log.Println("getZones() http.Client error:", err) + return nil + } + defer resp.Body.Close() + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + log.Println("getZones() ioutil.ReadAll() error", err) + return nil + } + + var records DNSRecords + if err := json.Unmarshal(body, &records); err != nil { + log.Println("getZones() json.Unmarshal() error", err) + return nil + } + + /* Cloudflare API returns struct[] of: + struct { ID string "json:\"id\""; Type string "json:\"type\""; Name string "json:\"name\""; + Content string "json:\"content\""; Proxied bool "json:\"proxied\""; + Proxiable bool "json:\"proxiable\""; TTL int "json:\"ttl\"" } + */ + + // log.Println("getZones() worked", records) + // log.Println("spew dump:") + // spew.Dump(records) + for _, record := range records.Result { + log.Println("spew record:", record) + log.Println("record:", record.Name, record.ID) + + var newc *ConfigT + newc = new(ConfigT) + + newc.Domain = record.Name + newc.ZoneID = record.ID + newc.Auth = auth + newc.Email = email + + Config[record.Name] = newc + log.Println("zonedrop.AddText:", record.Name, record.ID) + } + for d, _ := range Config { + log.Println("Config entry:", d) + } + + return &records +} diff --git a/create.go b/create.go new file mode 100644 index 0000000..5d75b0c --- /dev/null +++ b/create.go @@ -0,0 +1,57 @@ +/* + This will attempt to create a RR in a DNS zone file. + + Create("wit.com", "test.wit.com", "1.1.1.1" +*/ + +package cloudflare + +import ( + "os" + + "go.wit.com/log" +) + +func Create(zone string, hostname string, value string) bool { + log.Info("cloudflare.Create() START", zone, hostname, value) + key := os.Getenv("CF_API_KEY") + email := os.Getenv("CF_API_EMAIL") + + if (key == "") { + log.Warn("cloudflare.Create() MISSING environment variable CF_API_KEY") + return false + } + if (email == "") { + log.Warn("cloudflare.Create() MISSING environment variable CF_API_EMAIL") + return false + } + + GetZones(key, email) + var z *ConfigT + for d, v := range Config { + log.Info("cloudflare.Create() zone =", d, "value =", v) + if (zone == d) { + z = Config[zone] + log.Info("cloudflare.Create() FOUND ZONE", zone, "ID =", z.ZoneID) + } + } + if (z == nil) { + log.Warn("cloudflare.Create() COULD NOT FIND ZONE", zone) + return false + } + log.Info("cloudflare.Create() FOUND ZONE", z) + + // make a json record to send on port 80 to cloudflare + var data string + data = `{"content": "` + value + `", ` + data += `"name": "` + hostname + `", ` + data += `"type": "AAAA", ` + data += `"ttl": "1", ` + data += `"comment": "WIT DNS Control Panel"` + data += `}` + + result := doCurlCreate(key, email, z.ZoneID, data) + pretty, _ := FormatJSON(result) + log.Info("cloudflare.Create() result =", pretty) + return true +} diff --git a/curl.sh b/curl.sh new file mode 100755 index 0000000..13a477d --- /dev/null +++ b/curl.sh @@ -0,0 +1,23 @@ +# +# this curl POST will create a new DNS resource record (RR) in zone the wit.com +# In this case it will map www3.wit.com to a IPv6 address +# replace the auth key (e088...) and zone ID (27b9...) with the ones from your cloudflare account +# +curl --request POST \ + --url https://api.cloudflare.com/client/v4/zones/27llxxPutYourZoneIDherexxx497f90/dns_records \ + --header 'Content-Type: application/json' \ + --header 'X-Auth-Key: e08806adxxxPutYourAPIKeyHerexxxxa7d417a7x' \ + --header 'X-Auth-Email: test@wit.com' \ + --data '{ + "name": "www3", + "type": "AAAA" + "content": "2001:4860:4860::5555", + "ttl": 3600, + "proxied": false, + "comment": "WIT DNS Control Panel", +}' + +# This will verify an API token +curl -X GET "https://api.cloudflare.com/client/v4/user/tokens/verify" \ + -H "Authorization: Bearer AAAPutYourTokenInHereSoYouCanTestItL5Cl3" \ + -H "Content-Type:application/json" diff --git a/delete.go b/delete.go new file mode 100644 index 0000000..aa59105 --- /dev/null +++ b/delete.go @@ -0,0 +1,64 @@ +/* + This will attempt to delete a RR in a DNS zone file. + + Delete("wit.com", "test.wit.com", "1.1.1.1" +*/ + +package cloudflare + +import ( + "os" + + "go.wit.com/log" +) + +func Delete(zone string, hostname string, value string) bool { + // CFdialog.emailNode.SetText(os.Getenv("CF_API_EMAIL")) + // CFdialog.apiNode.SetText(os.Getenv("CF_API_KEY")) + + log.Info("cloudflare.Delete() START", zone, hostname, value) + key := os.Getenv("CF_API_KEY") + email := os.Getenv("CF_API_EMAIL") + + if (key == "") { + log.Warn("cloudflare.Delete() MISSING environment variable CF_API_KEY") + return false + } + if (email == "") { + log.Warn("cloudflare.Delete() MISSING environment variable CF_API_EMAIL") + return false + } + + GetZones(key, email) + var z *ConfigT + for d, v := range Config { + log.Info("cloudflare.Delete() zone =", d, "value =", v) + if (zone == d) { + z = Config[zone] + log.Info("cloudflare.Delete() FOUND ZONE", zone, "ID =", z.ZoneID) + } + } + if (z == nil) { + log.Warn("cloudflare.Delete() COULD NOT FIND ZONE", zone) + return false + } + log.Info("cloudflare.Delete() FOUND ZONE", z) + + records := GetZonefile(z) + for i, record := range records.Result { + if (record.Name == hostname) { + log.Info("cloudflare.Delete() FOUND hostname:", i, record.ID, record.Type, record.Name, record.Content) + } + if (record.Content == value) { + log.Info("cloudflare.Delete() FOUND CONTENT:", i, record.ID, record.Type, record.Name, record.Content) + log.Info("cloudflare.Delete() DO THE ACTUAL cloudflare DELETE here") + result := doCurlDelete(key, email, z.ZoneID, record.ID) + pretty, _ := FormatJSON(result) + log.Info("cloudflare.Delete() result =", pretty) + return true + } + } + + log.Info("cloudflare.Delete() NEVER FOUND cloudflare value:", value) + return false +} diff --git a/http.go b/http.go new file mode 100644 index 0000000..f976b96 --- /dev/null +++ b/http.go @@ -0,0 +1,190 @@ +// This is a simple example +package cloudflare + +import ( + "io/ioutil" + "net/http" + "bytes" + + "go.wit.com/log" +) + +/* +curl --request POST \ + --url https://api.cloudflare.com/client/v4/zones/zone_identifier/dns_records \ + --header 'Content-Type: application/json' \ + --header 'X-Auth-Email: ' \ + --data '{ + "content": "198.51.100.4", + "name": "example.com", + "proxied": false, + "type": "A", + "comment": "Domain verification record", + "tags": [ + "owner:dns-team" + ], + "ttl": 3600 +}' +*/ + +func doCurlDelete(auth string, email string, zoneId string, rrId string) string { + var err error + var req *http.Request + + if zoneId == "" { + log.Warn("doCurlDelete() zoneId == nil") + return "" + } + + if rrId == "" { + log.Warn("doCurlDelete() rrId == nil") + return "" + } + + data := []byte("") + + url := "https://api.cloudflare.com/client/v4/zones/" + zoneId + "/dns_records/" + rrId + + req, err = http.NewRequest(http.MethodDelete, url, bytes.NewBuffer(data)) + + // Set headers + req.Header.Set("Content-Type", "application/json") + req.Header.Set("X-Auth-Key", auth) + req.Header.Set("X-Auth-Email", email) + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + log.Println(err) + return "" + } + defer resp.Body.Close() + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + log.Println(err) + return "" + } + + return string(body) +} + +func doCurlCreate(auth string, email string, zoneId string, data string) string { + var err error + var req *http.Request + + if zoneId == "" { + log.Warn("doCurlDelete() zoneId == nil") + return "" + } + + url := "https://api.cloudflare.com/client/v4/zones/" + zoneId + "/dns_records/" + + log.Info("doCurlCreate() POST url =", url) + log.Info("doCurlCreate() POST Auth =", auth) + log.Info("doCurlCreate() POST Email =", email) + log.Info("doCurlCreate() POST data =", data) + + req, err = http.NewRequest(http.MethodPost, url, bytes.NewBuffer( []byte(data) )) + + // Set headers + req.Header.Set("Content-Type", "application/json") + req.Header.Set("X-Auth-Key", auth) + req.Header.Set("X-Auth-Email", email) + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + log.Println(err) + return "" + } + defer resp.Body.Close() + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + log.Println(err) + return "" + } + + return string(body) +} + +func doCurl(method string, rr *RRT) string { + var err error + var req *http.Request + + data := []byte(rr.data) + + if (method == "PUT") { + req, err = http.NewRequest(http.MethodPut, rr.url, bytes.NewBuffer(data)) + } else { + req, err = http.NewRequest(http.MethodPost, rr.url, bytes.NewBuffer(data)) + } + + // Set headers + req.Header.Set("Content-Type", "application/json") + req.Header.Set("X-Auth-Key", rr.Auth) + req.Header.Set("X-Auth-Email", rr.Email) + + log.Println("http PUT url =", rr.url) + log.Println("http PUT Auth =", rr.Auth) + log.Println("http PUT Email =", rr.Email) + log.Println("http PUT data =", rr.data) + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + log.Println(err) + return "" + } + defer resp.Body.Close() + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + log.Println(err) + return "" + } + + return string(body) +} + +func curlPost(dnsRow *RRT) string { + var authKey string = dnsRow.Auth + var email string = dnsRow.Email + + url := dnsRow.url + tmp := dnsRow.data + + log.Println("curlPost() START") + log.Println("curlPost() authkey = ", authKey) + log.Println("curlPost() email = ", email) + log.Println("curlPost() url = ", url) + data := []byte(tmp) + req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(data)) + + // Set headers + req.Header.Set("Content-Type", "application/json") + req.Header.Set("X-Auth-Key", authKey) + req.Header.Set("X-Auth-Email", email) + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + log.Println(err) + return "" + } + defer resp.Body.Close() + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + log.Println(err) + return "" + } + // log.Println("http PUT body =", body) + // spew.Dump(body) + + log.Println("result =", string(body)) + log.Println("curl() END") + pretty, _ := FormatJSON(string(body)) + return pretty +} diff --git a/json.go b/json.go new file mode 100644 index 0000000..b7c71a8 --- /dev/null +++ b/json.go @@ -0,0 +1,25 @@ +// This is a simple example +package cloudflare + +import ( + "encoding/json" +) + +// formatJSON takes an unformatted JSON string and returns a formatted version. +func FormatJSON(unformattedJSON string) (string, error) { + var jsonData interface{} + + // Decode the JSON string into an interface + err := json.Unmarshal([]byte(unformattedJSON), &jsonData) + if err != nil { + return "", err + } + + // Re-encode the JSON with indentation for formatting + formattedJSON, err := json.MarshalIndent(jsonData, "", " ") + if err != nil { + return "", err + } + + return string(formattedJSON), nil +} diff --git a/loadZoneWindow.go b/loadZoneWindow.go new file mode 100644 index 0000000..56f65d8 --- /dev/null +++ b/loadZoneWindow.go @@ -0,0 +1,91 @@ +// This is a simple example +package cloudflare + +import ( + "log" + "strconv" + + "go.wit.com/gui/gui" +) + +func LoadZoneWindow(n *gui.Node, c *ConfigT) { + hostname := c.Domain + zoneID := c.ZoneID + log.Println("adding DNS record", hostname) + + newt := n.NewTab(hostname) + vb := newt.NewBox("vBox", false) + newg := vb.NewGroup("more zoneID = " + zoneID) + + // make a grid 6 things wide + grid := newg.NewGrid("gridnuts", 6, 1) + +// grid.NewButton("Type", func () { +// log.Println("sort by Type") +// }) + grid.NewLabel("RR type") + grid.NewLabel("hostname") + + grid.NewLabel("Proxy") + grid.NewLabel("TTL") + grid.NewLabel("Value") + grid.NewLabel("Save") + + records := GetZonefile(c) + for _, record := range records.Result { + var rr RRT // dns zonefile resource record + + // copy all the JSON values into the row record. + rr.ID = record.ID + rr.Type = record.Type + rr.Name = record.Name + rr.Content = record.Content + rr.Proxied = record.Proxied + rr.Proxiable = record.Proxiable + rr.ZoneID = zoneID + // rr.Ttl = record.TTL + + rr.Domain = hostname + rr.ZoneID = zoneID + rr.Auth = c.Auth + rr.Email = c.Email + + grid.NewLabel(record.Type) + grid.NewLabel(record.Name) + + proxy := grid.NewLabel("proxy") + if (record.Proxied) { + proxy.SetText("On") + } else { + proxy.SetText("Off") + } + + var ttl string + if (record.TTL == 1) { + ttl = "Auto" + } else { + ttl = strconv.Itoa(record.TTL) + } + grid.NewLabel(ttl) + + val := grid.NewLabel("Value") + val.SetText(record.Content) + + load := grid.NewButton("Load", nil) + load.Custom = func () { + name := "save stuff to cloudflare for " + rr.ID + log.Println(name) + + /* + rr.Domain = domainWidget.S + rr.ZoneID = zoneWidget.S + rr.Auth = authWidget.S + rr.Email = emailWidget.S + */ + + SetRow(&rr) + } + } + + grid.Pad() +} diff --git a/mainWindow.go b/mainWindow.go new file mode 100644 index 0000000..3655600 --- /dev/null +++ b/mainWindow.go @@ -0,0 +1,170 @@ +// This is a simple example +package cloudflare + +import ( + "os" + "log" + + "go.wit.com/gui/gui" + "go.wit.com/gui/gadgets" +) + +// This creates a window +func MakeCloudflareWindow(n *gui.Node) *gui.Node { + CFdialog.rootGui = n + var t *gui.Node + + log.Println("buttonWindow() START") + + CFdialog.mainWindow = n.NewWindow("Cloudflare Config") + + // this tab has the master cloudflare API credentials + makeConfigWindow(CFdialog.mainWindow) + + t = CFdialog.mainWindow.NewTab("Zones") + vb := t.NewBox("vBox", false) + g1 := vb.NewGroup("zones") + + // make dropdown list of zones + CFdialog.zonedrop = g1.NewDropdown("zone") + CFdialog.zonedrop.AddText("example.org") + for d, _ := range Config { + CFdialog.zonedrop.AddText(d) + } + CFdialog.zonedrop.AddText("stablesid.org") + + CFdialog.zonedrop.Custom = func () { + domain := CFdialog.zonedrop.S + log.Println("custom dropdown() zone (domain name) =", CFdialog.zonedrop.Name, domain) + if (Config[domain] == nil) { + log.Println("custom dropdown() Config[domain] = nil for domain =", domain) + CFdialog.domainWidget.SetText(domain) + CFdialog.zoneWidget.SetText("") + CFdialog.authWidget.SetText("") + CFdialog.emailWidget.SetText("") + } else { + log.Println("custom dropdown() a =", domain, Config[domain].ZoneID, Config[domain].Auth, Config[domain].Email) + CFdialog.domainWidget.SetText(Config[domain].Domain) + CFdialog.zoneWidget.SetText(Config[domain].ZoneID) + CFdialog.authWidget.SetText(Config[domain].Auth) + CFdialog.emailWidget.SetText(Config[domain].Email) + } + } + + more := g1.NewGroup("data") + showCloudflareCredentials(more) + + makeDebugWindow(CFdialog.mainWindow) + return CFdialog.mainWindow +} + +func makeConfigWindow(n *gui.Node) { + t := n.NewTab("Get Zones") + vb := t.NewBox("vBox", false) + g1 := vb.NewGroup("Cloudflare API Config") + + g1.NewLabel("If you have an API key with access to list all of /n your zone files, enter it here. \n \n Alternatively, you can set the enviroment variables: \n env $CF_API_KEY \n env $CF_API_EMAIL\n") + + // make grid to display credentials + grid := g1.NewGrid("credsGrid", 2, 4) // width = 2 + + grid.NewLabel("Auth Key") + aw := grid.NewEntryLine("CF_API_KEY") + aw.SetText(os.Getenv("CF_API_KEY")) + + grid.NewLabel("Email") + ew := grid.NewEntryLine("CF_API_EMAIL") + ew.SetText(os.Getenv("CF_API_EMAIL")) + + var url string = "https://api.cloudflare.com/client/v4/zones/" + grid.NewLabel("Cloudflare API") + grid.NewLabel(url) + + hostname := gadgets.NewBasicEntry(grid, "hostname") + zone := gadgets.NewBasicEntry(grid, "domain name") + + grid.Pad() + + vb.NewButton("Lookup Hostname", func () { + log.Println("Find all the Resource Records for hostname:", hostname.Get()) + log.Println("Find all the Resource Records for zone:", zone.Get()) + GetZones(aw.S, ew.S) + for d, v := range Config { + log.Println("Zone =", d, "v =", v) + } + }) + + vb.NewButton("getZones()", func () { + log.Println("getZones()") + GetZones(aw.S, ew.S) + for d, _ := range Config { + CFdialog.zonedrop.AddText(d) + } + }) + + vb.NewButton("cloudflare wit.com", func () { + CreateRR(CFdialog.rootGui, "wit.com", "3777302ac4a78cd7fa4f6d3f72086d06") + }) + + t.Pad() + t.Margin() + vb.Pad() + vb.Margin() + g1.Pad() + g1.Margin() +} + +func makeDebugWindow(window *gui.Node) { + t2 := window.NewTab("debug") + g := t2.NewGroup("debug") + g.NewButton("Load 'gocui'", func () { + CFdialog.rootGui.LoadToolkit("gocui") + }) + + g.NewButton("Load 'andlabs'", func () { + CFdialog.rootGui.LoadToolkit("andlabs") + }) + + g.NewButton("gui.DebugWindow()", func () { + gui.DebugWindow() + }) + + g.NewButton("List all Widgets", func () { + CFdialog.rootGui.ListChildren(true) + }) + g.NewButton("Dump all Widgets", func () { + CFdialog.rootGui.Dump() + }) +} + +func showCloudflareCredentials(box *gui.Node) { + // make grid to display credentials + grid := box.NewGrid("credsGrid", 2, 4) // width = 2 + + grid.NewLabel("Domain") + CFdialog.domainWidget = grid.NewEntryLine("CF_API_DOMAIN") + + grid.NewLabel("Zone ID") + CFdialog.zoneWidget = grid.NewEntryLine("CF_API_ZONEID") + + grid.NewLabel("Auth Key") + CFdialog.authWidget = grid.NewEntryLine("CF_API_KEY") + + grid.NewLabel("Email") + CFdialog.emailWidget = grid.NewEntryLine("CF_API_EMAIL") + + var url string = "https://api.cloudflare.com/client/v4/zones/" + grid.NewLabel("Cloudflare API") + grid.NewLabel(url) + + grid.Pad() + + CFdialog.loadButton = box.NewButton("Load Cloudflare DNS zonefile", func () { + var domain ConfigT + domain.Domain = CFdialog.domainWidget.S + domain.ZoneID = CFdialog.zoneWidget.S + domain.Auth = CFdialog.authWidget.S + domain.Email = CFdialog.emailWidget.S + LoadZoneWindow(CFdialog.mainWindow, &domain) + }) +} diff --git a/rr.go b/rr.go new file mode 100644 index 0000000..3dc9dee --- /dev/null +++ b/rr.go @@ -0,0 +1,157 @@ +/* + This will let you edit a single Resource Record within + a DNS zone file. For example: + google-dns.wit.com. 1 IN A 8.8.8.8 +*/ + +package cloudflare + +import ( + "log" + "os" + + "go.wit.com/gui/gui" +) + +func init() { + Config = make(map[string]*ConfigT) +} + +func CreateRR(myGui *gui.Node, zone string, zoneID string) { + if (CFdialog.cloudflareW != nil) { + // skip this if the window has already been created + log.Println("createRR() the cloudflare window already exists") + CFdialog.cloudflareB.Disable() + return + } + CFdialog.cloudflareW = myGui.NewWindow("cloudflare " + zone + " API") + CFdialog.cloudflareW.Custom = func () { + log.Println("createRR() don't really exit here") + CFdialog.cloudflareW = nil + CFdialog.cloudflareB.Enable() + } + + group := CFdialog.cloudflareW.NewGroup("Create a new DNS Resource Record (rr)") + + // make a grid 2 things wide + grid := group.NewGrid("gridnuts", 2, 3) + + grid.NewLabel("zone") + CFdialog.zoneNode = grid.NewLabel("zone") + CFdialog.zoneNode.SetText(zone) + + grid.NewLabel("zone ID") + CFdialog.zoneIdNode = grid.NewLabel("zoneID") + CFdialog.zoneIdNode.SetText(zoneID) + + grid.NewLabel("shell env $CF_API_EMAIL") + CFdialog.emailNode = grid.NewLabel("type") + CFdialog.emailNode.SetText(os.Getenv("CF_API_EMAIL")) + + grid.NewLabel("shell env $CF_API_KEY") + CFdialog.apiNode = grid.NewLabel("type") + CFdialog.apiNode.SetText(os.Getenv("CF_API_KEY")) + + grid.NewLabel("Resource Record ID") + CFdialog.rrNode = grid.NewLabel("type") + CFdialog.rrNode.SetText(os.Getenv("cloudflare RR id")) + + grid.NewLabel("Record Type") + CFdialog.TypeNode = grid.NewCombobox("type") + CFdialog.TypeNode.AddText("A") + CFdialog.TypeNode.AddText("AAAA") + CFdialog.TypeNode.AddText("CNAME") + CFdialog.TypeNode.AddText("TXT") + CFdialog.TypeNode.AddText("MX") + CFdialog.TypeNode.AddText("NS") + CFdialog.TypeNode.Custom = func () { + DoChange() + } + CFdialog.TypeNode.SetText("AAAA") + + grid.NewLabel("Name (usually the hostname)") + CFdialog.NameNode = grid.NewCombobox("name") + CFdialog.NameNode.AddText("www") + CFdialog.NameNode.AddText("mail") + CFdialog.NameNode.AddText("git") + CFdialog.NameNode.AddText("go") + CFdialog.NameNode.AddText("blog") + CFdialog.NameNode.AddText("ns1") + CFdialog.NameNode.Custom = func () { + DoChange() + } + CFdialog.NameNode.SetText("www") + + grid.NewLabel("Cloudflare Proxy") + CFdialog.proxyNode = grid.NewDropdown("proxy") + CFdialog.proxyNode.AddText("On") + CFdialog.proxyNode.AddText("Off") + CFdialog.proxyNode.Custom = func () { + DoChange() + } + CFdialog.proxyNode.SetText("Off") + + grid.NewLabel("Value") + CFdialog.ValueNode = grid.NewCombobox("value") + CFdialog.ValueNode.AddText("127.0.0.1") + CFdialog.ValueNode.AddText("2001:4860:4860::8888") + CFdialog.ValueNode.AddText("ipv6.wit.com") + CFdialog.ValueNode.Custom = func () { + DoChange() + } + CFdialog.ValueNode.SetText("127.0.0.1") + CFdialog.ValueNode.Expand() + + grid.NewLabel("URL") + CFdialog.urlNode = grid.NewLabel("URL") + + group.NewLabel("curl") + CFdialog.curlNode = group.NewTextbox("curl") + CFdialog.curlNode.Custom = func () { + DoChange() + } + CFdialog.curlNode.SetText("put the curl text here") + + CFdialog.resultNode = group.NewTextbox("result") + CFdialog.resultNode.SetText("API response will show here") + + CFdialog.SaveNode = group.NewButton("Save curlPost()", func () { + dnsRow := DoChange() + result := curlPost(dnsRow) + CFdialog.resultNode.SetText(result) + // CreateCurlRR() + // url, data := CreateCurlRR() + // result := curl(url, data) + // CFdialog.resultNode.SetText(result) + }) + // CFdialog.saveNode.Disable() + group.NewButton("New RR doCurl(PUT)", func () { + rr := DoChange() + + rr.url = "https://api.cloudflare.com/client/v4/zones/" + rr.ZoneID + "/dns_records" + + result := doCurl("POST", rr) + CFdialog.resultNode.SetText(result) + + pretty, _ := FormatJSON(result) + log.Println(pretty) + }) + + group.NewButton("Update RR doCurl(PUT)", func () { + rr := DoChange() + + rr.url = "https://api.cloudflare.com/client/v4/zones/" + rr.ZoneID + "/dns_records/" + rr.ID + + result := doCurl("PUT", rr) + CFdialog.resultNode.SetText(result) + + pretty, _ := FormatJSON(result) + log.Println(pretty) + }) + // CFdialog.saveNode.Disable() + + + group.Pad() + grid.Pad() + grid.Expand() +} diff --git a/structs.go b/structs.go new file mode 100644 index 0000000..675cd5e --- /dev/null +++ b/structs.go @@ -0,0 +1,105 @@ +// This is a simple example +package cloudflare + +import ( + "go.wit.com/gui/gui" +) + +var cloudflareURL string = "https://api.cloudflare.com/client/v4/zones/" + +// Define a struct to match the JSON structure of the response. +// This structure should be adjusted based on the actual format of the response. +type DNSRecords struct { + Result []struct { + ID string `json:"id"` + Type string `json:"type"` + Name string `json:"name"` + Content string `json:"content"` + Proxied bool `json:"proxied"` + Proxiable bool `json:"proxiable"` + TTL int `json:"ttl"` + } `json:"result"` +} + +// CFdialog is everything you need forcreating +// a new record: name, TTL, type (CNAME, A, etc) +var CFdialog dialogT + +type dialogT struct { + rootGui *gui.Node // the root node + mainWindow *gui.Node // the window node + zonedrop *gui.Node // the drop down menu of zones + + domainWidget *gui.Node + zoneWidget *gui.Node + authWidget *gui.Node + emailWidget *gui.Node + + loadButton *gui.Node + saveButton *gui.Node + + cloudflareW *gui.Node // the window node + cloudflareB *gui.Node // the cloudflare button + + TypeNode *gui.Node // CNAME, A, AAAA, ... + NameNode *gui.Node // www, mail, ... + ValueNode *gui.Node // 4.2.2.2, "dkim stuff", etc + + rrNode *gui.Node // cloudflare Resource Record ID + proxyNode *gui.Node // If cloudflare is a port 80 & 443 proxy + ttlNode *gui.Node // just set to 1 which means automatic to cloudflare + curlNode *gui.Node // shows you what you could run via curl + resultNode *gui.Node // what the cloudflare API returned + SaveNode *gui.Node // button to send it to cloudflare + + zoneNode *gui.Node // "wit.com" + zoneIdNode *gui.Node // cloudflare zone ID + apiNode *gui.Node // cloudflare API key (from environment var CF_API_KEY) + emailNode *gui.Node // cloudflare email (from environment var CF_API_EMAIL) + urlNode *gui.Node // the URL to POST, PUT, DELETE, etc +} + +// Resource Record (used in a DNS zonefile) +type RRT struct { + ID string + Type string + Name string + Content string + ProxyS string + Proxied bool + Proxiable bool + Ttl string + + Domain string + ZoneID string + Auth string + Email string + url string + data string +} + +/* + This is a structure of all the RR's (Resource Records) + in the DNS zonefiile for a hostname. For example: + + For the host test.wit.com: + + test.wit.com A 127.0.0.1 + test.wit.com AAAA + test.wit.com TXT email test@wit.com + test.wit.com TXT phone 212-555-1212 + test.wit.com CNAME real.wit.com +*/ +type hostT struct { + hostname string + RRs []ConfigT +} + +type ConfigT struct { + Domain string + ZoneID string + Auth string + Email string +} + +var Config map[string]*ConfigT