refactor, move cloudflare API interactions into dedicated package
This commit is contained in:
parent
00718515f8
commit
9028d9a69e
186
cf/cf.go
Normal file
186
cf/cf.go
Normal file
@ -0,0 +1,186 @@
|
||||
// package cf implements Cloudflare API client
|
||||
package cf
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
APIKey string
|
||||
APIURL string
|
||||
}
|
||||
|
||||
func NewClient(apiKey string) *Client {
|
||||
return &Client{APIKey: apiKey, APIURL: "https://api.cloudflare.com/client/v4"}
|
||||
}
|
||||
|
||||
// Record represents a single DNS record in cf
|
||||
type Record struct {
|
||||
ID string `json:"id"`
|
||||
ZoneID string `json:"zone_id"`
|
||||
ZoneName string `json:"zone_name"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Content string `json:"content"`
|
||||
Proxiable bool `json:"proxiable"`
|
||||
Proxied bool `json:"proxied"`
|
||||
TTL int `json:"ttl"`
|
||||
Comment string `json:"comment"`
|
||||
Tags []string `json:"tags"`
|
||||
CreatedOn time.Time `json:"created_on"`
|
||||
ModifiedOn time.Time `json:"modified_on"`
|
||||
NumberQueries int
|
||||
}
|
||||
|
||||
// RecordResult represents the HTTP response body of an API call to get DNS Records
|
||||
type RecordResult struct {
|
||||
Result []Record `json:"result"`
|
||||
}
|
||||
|
||||
// Zone represents a single DNS zone in cf
|
||||
type Zone struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// ZoneResult represents the HTTP response body of an API call to get DNS Zones
|
||||
type ZoneResult struct {
|
||||
Result []Zone `json:"result"`
|
||||
}
|
||||
|
||||
// Analytic represents the DNS analytic data returned from cf
|
||||
type Analytic struct {
|
||||
Result struct {
|
||||
Rows int `json:"rows"`
|
||||
Data []struct {
|
||||
Metrics []int `json:"metrics"`
|
||||
} `json:"data"`
|
||||
DataLag int `json:"data_lag"`
|
||||
Min struct {
|
||||
} `json:"min"`
|
||||
Max struct {
|
||||
} `json:"max"`
|
||||
Totals struct {
|
||||
QueryCount int `json:"queryCount"`
|
||||
} `json:"totals"`
|
||||
Query struct {
|
||||
Dimensions []any `json:"dimensions"`
|
||||
Metrics []string `json:"metrics"`
|
||||
Since time.Time `json:"since"`
|
||||
Until time.Time `json:"until"`
|
||||
Limit int `json:"limit"`
|
||||
} `json:"query"`
|
||||
} `json:"result"`
|
||||
}
|
||||
|
||||
// getZones provides a slice of all zones in the cf account
|
||||
func (c *Client) GetZones() ([]Zone, error) {
|
||||
req, err := http.NewRequest("GET", fmt.Sprintf("%s/zones", c.APIURL), nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create new http request: %v", err)
|
||||
}
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", c.APIKey))
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to execute http request: %v", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("http response has status '%d' but expected '%d'", resp.StatusCode, http.StatusOK)
|
||||
}
|
||||
|
||||
b, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read http response body: %v", err)
|
||||
}
|
||||
zoneResult := &ZoneResult{}
|
||||
|
||||
err = json.Unmarshal(b, zoneResult)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal response body to struct: %v", err)
|
||||
}
|
||||
|
||||
return zoneResult.Result, nil
|
||||
}
|
||||
|
||||
// getRecords provides a slice of all records in the cf zone provided
|
||||
func (c *Client) GetRecords(zone Zone) ([]Record, error) {
|
||||
req, err := http.NewRequest("GET", fmt.Sprintf("%s/zones/%s/dns_records", c.APIURL, zone.ID), nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create new http request: %v", err)
|
||||
}
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", c.APIKey))
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to execute http request: %v", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("http response has status '%d' but expected '%d'", resp.StatusCode, http.StatusOK)
|
||||
}
|
||||
|
||||
b, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read http response body: %v", err)
|
||||
}
|
||||
recordResult := &RecordResult{}
|
||||
|
||||
err = json.Unmarshal(b, recordResult)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal response body to struct: %v", err)
|
||||
}
|
||||
|
||||
return recordResult.Result, nil
|
||||
}
|
||||
|
||||
// getRecordAnalytic provides a pointer to an Analytic for the given
|
||||
func (c *Client) GetRecordAnalytic(record Record) (*Analytic, error) {
|
||||
req, err := http.NewRequest("GET", fmt.Sprintf("%s/zones/%s/dns_analytics/report", c.APIURL, record.ZoneID), nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create new http request: %v", err)
|
||||
}
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", c.APIKey))
|
||||
|
||||
q := req.URL.Query()
|
||||
q.Add("filters", fmt.Sprintf("queryName==%s", record.Name))
|
||||
/*
|
||||
TBD: Why does this give HTTP 400?
|
||||
q.Add("filters", fmt.Sprintf("queryName==%s,queryType==%s", record.Name, record.Type))
|
||||
*/
|
||||
req.URL.RawQuery = q.Encode()
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to execute http request: %v", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("http response has status '%d' but expected '%d'", resp.StatusCode, http.StatusOK)
|
||||
}
|
||||
|
||||
b, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read http response body: %v", err)
|
||||
}
|
||||
|
||||
analytic := &Analytic{}
|
||||
|
||||
err = json.Unmarshal(b, analytic)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal response body to struct: %v", err)
|
||||
}
|
||||
|
||||
return analytic, nil
|
||||
}
|
187
main.go
187
main.go
@ -3,95 +3,50 @@ package main
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/csv"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"code.stevenpolley.net/steven/cfcleaner/cf"
|
||||
)
|
||||
|
||||
const apiURL = "https://api.cloudflare.com/client/v4"
|
||||
func init() {
|
||||
|
||||
var apiKey string
|
||||
|
||||
type Zone struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type Record struct {
|
||||
ID string `json:"id"`
|
||||
ZoneID string `json:"zone_id"`
|
||||
ZoneName string `json:"zone_name"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Content string `json:"content"`
|
||||
Proxiable bool `json:"proxiable"`
|
||||
Proxied bool `json:"proxied"`
|
||||
TTL int `json:"ttl"`
|
||||
Comment string `json:"comment"`
|
||||
Tags []string `json:"tags"`
|
||||
CreatedOn time.Time `json:"created_on"`
|
||||
ModifiedOn time.Time `json:"modified_on"`
|
||||
NumberQueries int
|
||||
}
|
||||
|
||||
type ZoneResult struct {
|
||||
Result []Zone `json:"result"`
|
||||
}
|
||||
|
||||
type RecordResult struct {
|
||||
Result []Record `json:"result"`
|
||||
}
|
||||
|
||||
type Analytic struct {
|
||||
Result struct {
|
||||
Rows int `json:"rows"`
|
||||
Data []struct {
|
||||
Metrics []int `json:"metrics"`
|
||||
} `json:"data"`
|
||||
DataLag int `json:"data_lag"`
|
||||
Min struct {
|
||||
} `json:"min"`
|
||||
Max struct {
|
||||
} `json:"max"`
|
||||
Totals struct {
|
||||
QueryCount int `json:"queryCount"`
|
||||
} `json:"totals"`
|
||||
Query struct {
|
||||
Dimensions []any `json:"dimensions"`
|
||||
Metrics []string `json:"metrics"`
|
||||
Since time.Time `json:"since"`
|
||||
Until time.Time `json:"until"`
|
||||
Limit int `json:"limit"`
|
||||
} `json:"query"`
|
||||
} `json:"result"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
apiKey = os.Getenv("cfapikey")
|
||||
|
||||
zones, err := getZones()
|
||||
cfAPIKey := flag.String("cfapikey", "", "in cf, my profile -> API Tokens -> Create Token")
|
||||
flag.Parse()
|
||||
|
||||
if *cfAPIKey == "" {
|
||||
fmt.Println("you must specify an API key with the -cfapikey=<key> parameter")
|
||||
return
|
||||
}
|
||||
|
||||
cfClient := cf.NewClient(*cfAPIKey)
|
||||
|
||||
zones, err := cfClient.GetZones()
|
||||
if err != nil {
|
||||
log.Fatalf("failed to get zones: %v", err)
|
||||
}
|
||||
|
||||
outRows := make([]Record, 0)
|
||||
outRows := make([]cf.Record, 0)
|
||||
|
||||
for _, zone := range zones {
|
||||
log.Printf("processing zone '%s' with ID '%s'", zone.Name, zone.ID)
|
||||
|
||||
records, err := getRecords(zone)
|
||||
records, err := cfClient.GetRecords(zone)
|
||||
if err != nil {
|
||||
log.Printf("failed to get records for zone '%s' with ID '%s': %v", zone.Name, zone.ID, err)
|
||||
}
|
||||
|
||||
for _, record := range records {
|
||||
analytic, err := getRecordAnalytic(zone, record)
|
||||
analytic, err := cfClient.GetRecordAnalytic(record)
|
||||
if err != nil {
|
||||
log.Printf("failed to get record report for record '%s' in zone '%s' with ID '%s': %v", record.Name, zone.Name, zone.ID, err)
|
||||
continue
|
||||
@ -122,109 +77,3 @@ func main() {
|
||||
fmt.Println(string(b))
|
||||
|
||||
}
|
||||
|
||||
func getZones() ([]Zone, error) {
|
||||
req, err := http.NewRequest("GET", fmt.Sprintf("%s/zones", apiURL), nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create new http request: %v", err)
|
||||
}
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
//req.Header.Add("X-Auth-Email", apiKey)
|
||||
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", apiKey))
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to execute http request: %v", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("http response has status '%d' but expected '%d'", resp.StatusCode, http.StatusOK)
|
||||
}
|
||||
|
||||
b, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read http response body: %v", err)
|
||||
}
|
||||
zoneResult := &ZoneResult{}
|
||||
|
||||
err = json.Unmarshal(b, zoneResult)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal response body to struct: %v", err)
|
||||
}
|
||||
|
||||
return zoneResult.Result, nil
|
||||
}
|
||||
|
||||
func getRecords(zone Zone) ([]Record, error) {
|
||||
req, err := http.NewRequest("GET", fmt.Sprintf("%s/zones/%s/dns_records", apiURL, zone.ID), nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create new http request: %v", err)
|
||||
}
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
//req.Header.Add("X-Auth-Email", apiKey)
|
||||
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", apiKey))
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to execute http request: %v", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("http response has status '%d' but expected '%d'", resp.StatusCode, http.StatusOK)
|
||||
}
|
||||
|
||||
b, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read http response body: %v", err)
|
||||
}
|
||||
recordResult := &RecordResult{}
|
||||
|
||||
err = json.Unmarshal(b, recordResult)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal response body to struct: %v", err)
|
||||
}
|
||||
|
||||
return recordResult.Result, nil
|
||||
}
|
||||
|
||||
func getRecordAnalytic(zone Zone, record Record) (*Analytic, error) {
|
||||
req, err := http.NewRequest("GET", fmt.Sprintf("%s/zones/%s/dns_analytics/report", apiURL, zone.ID), nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create new http request: %v", err)
|
||||
}
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
//req.Header.Add("X-Auth-Email", apiKey)
|
||||
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", apiKey))
|
||||
|
||||
q := req.URL.Query()
|
||||
q.Add("filters", fmt.Sprintf("queryName==%s", record.Name))
|
||||
//q.Add("filters", fmt.Sprintf("queryName==%s,queryType==%s", record.Name, record.Type))
|
||||
req.URL.RawQuery = q.Encode()
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to execute http request: %v", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("http response has status '%d' but expected '%d'", resp.StatusCode, http.StatusOK)
|
||||
}
|
||||
|
||||
b, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read http response body: %v", err)
|
||||
}
|
||||
|
||||
analytic := &Analytic{}
|
||||
|
||||
err = json.Unmarshal(b, analytic)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal response body to struct: %v", err)
|
||||
}
|
||||
|
||||
return analytic, nil
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user