package main import ( "bytes" "encoding/csv" "encoding/json" "fmt" "io" "log" "net/http" "os" "sort" "strconv" "time" ) const apiURL = "https://api.cloudflare.com/client/v4" 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() if err != nil { log.Fatalf("failed to get zones: %v", err) } outRows := make([]Record, 0) for _, zone := range zones { log.Printf("processing zone '%s' with ID '%s'", zone.Name, zone.ID) records, err := 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) 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 } record.NumberQueries = analytic.Result.Totals.QueryCount outRows = append(outRows, record) } } sort.Slice(outRows, func(i, j int) bool { return outRows[i].NumberQueries < outRows[j].NumberQueries }) buf := bytes.NewBuffer(nil) writer := csv.NewWriter(buf) writer.Write([]string{"Name", "Type", "NumberQueries", "CreatedOn", "ModifiedOn", "Comment", "Content"}) for _, row := range outRows { writer.Write([]string{row.Name, row.Type, strconv.Itoa(row.NumberQueries), row.CreatedOn.Format(time.RFC3339), row.ModifiedOn.Format(time.RFC3339), row.Comment, row.Content}) } writer.Flush() b, err := io.ReadAll(buf) if err != nil { log.Fatalf("failed to read bytes from output buffer: %v", err) } 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 }