commit f1a0d18ac19d0db4f7060ea5a5c662348118e399 Author: Jeff Carr Date: Mon Jan 1 15:31:33 2024 -0600 initial commit of cloudflare api Signed-off-by: Jeff Carr 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