Skip to content

Commit

Permalink
Add quick clean filter (#7)
Browse files Browse the repository at this point in the history
* Add quick cleaning function

* chore: Change to date

* docs: Change formatting

* docs: Update README
  • Loading branch information
Cyb3r-Jak3 authored Jun 11, 2022
1 parent 6df622f commit fc61f02
Show file tree
Hide file tree
Showing 10 changed files with 218 additions and 53 deletions.
1 change: 0 additions & 1 deletion .github/workflows/golang.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ jobs:
- name: Checkout code
uses: actions/checkout@v3


- name: Run GoReleaser
uses: goreleaser/[email protected]
with:
Expand Down
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,6 @@
dns-records.yml
*.env

**/dist/*
**/dist/*

documentation/site/
11 changes: 9 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
GIT_VERSION ?= $(shell git describe --tags --always --dirty="-dev")
DATE ?= $(shell date -u '+%Y-%m-%d %H:%M UTC')
VERSION_FLAGS := -X "main.version=$(GIT_VERSION)" -X "main.BuildTime=$(DATE)"
VERSION_FLAGS := -X "main.version=$(GIT_VERSION)" -X "main.date=$(DATE)"

build:
go build -ldflags='$(VERSION_FLAGS)' ./...

lint:
go vet ./...
golanglint-ci run -E revive,gofmt ./...
golangci-lint run -E revive,gofmt ./...

test:
go test -race -v -coverprofile="c.out" ./...
go tool cover -func="c.out"

docs:
cd documentation && mkdocs build
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Cloudflare Utilities

[![Go Checks](https://github.com/Cyb3r-Jak3/cloudflare-utils/actions/workflows/golang.yml/badge.svg)](https://github.com/Cyb3r-Jak3/cloudflare-utils/actions/workflows/golang.yml) [![Golanglint CI](https://github.com/Cyb3r-Jak3/cloudflare-utils/actions/workflows/golangci-lint.yml/badge.svg)](https://github.com/Cyb3r-Jak3/cloudflare-utils/actions/workflows/golangci-lint.yml)

[![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/Cyb3r-Jak3/cloudflare-utils)](https://github.com/Cyb3r-Jak3/cloudflare-utils/releases/latest)

## Tools

### DNS Cleaner
Expand All @@ -12,3 +16,5 @@ Basic Steps:
2. There will be a file called `dns-records.yml`
3. For any record you do not want to keep change `keep: true` to false
4. Rerun `./cloudflare-utils dns-cleaner` and all records not marked to keep will be removed. **This tool does not recreate records if they are missing**

[Read More](https://cloudflare-utils.cyberjake.xyz/dns-cleaner/)
153 changes: 134 additions & 19 deletions cmd/cloudflare-utils/dns-cleaner.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,63 +2,103 @@ package main

import (
"errors"
"fmt"
"github.com/cloudflare/cloudflare-go"
"github.com/urfave/cli/v2"
"gopkg.in/yaml.v3"
"os"
"strconv"
"strings"
)

const (
dnsFileFlag = "dns-file"
noKeepFlag = "no-keep"
downloadSubCommand = "download"
uploadSubCommand = "upload"
dnsFileFlag = "dns-file"
noKeepFlag = "no-keep"
quickCleanFlag = "quick-clean"
noOverwriteFlag = "no-overwrite"
removeDNSFileFlag = "remove-file"
)

type DNSRecord struct {
ID string `yaml:"id"`
Keep bool `yaml:"keep"`
Name string `yaml:"name"`
Type string `yaml:"type"`
ID string `yaml:"id"`
Keep bool `yaml:"keep"`
Name string `yaml:"name"`
Type string `yaml:"type"`
Content string `yaml:"content"`
}

// RecordFile is the struct of the YAML DNS file
type RecordFile struct {
ZoneName string `yaml:"zone_name"`
ZoneID string `yaml:"zone_id"`
Records []DNSRecord `yaml:"records"`
}

// BuildDNSCleanerCommand builds the `dns-cleaner` command for the application
func BuildDNSCleanerCommand() *cli.Command {
return &cli.Command{
Name: "dns-cleaner",
Usage: "Clean DNS records. API Token Requirements: DNS:Edit",
Action: DNSCleaner,
Subcommands: []*cli.Command{
{
Name: downloadSubCommand,
Action: DownloadDNS,
Usage: "Download DNS records",
},
{
Name: uploadSubCommand,
Action: UploadDNS,
Usage: "Upload DNS records",
},
},
Flags: []cli.Flag{
&cli.StringFlag{
Name: dnsFileFlag,
Usage: "path to the dns record file",
Usage: "Path to the dns record file",
Aliases: []string{"f"},
EnvVars: []string{"DNS_RECORD_FILE"},
Value: "./dns-records.yml",
},
&cli.BoolFlag{
Name: noKeepFlag,
Usage: "mark records for removal by default",
Usage: "Mark records for removal by default",
Aliases: []string{"k"},
EnvVars: []string{"NO_KEEP"},
Value: false,
},
&cli.BoolFlag{
Name: quickCleanFlag,
Usage: "Auto marks DNS records that are numeric to be removed",
Aliases: []string{"q"},
EnvVars: []string{"QUICK_CLEAN"},
},
&cli.BoolFlag{
Name: noOverwriteFlag,
Usage: "Do not replace existing DNS file",
Aliases: []string{"n"},
Value: false,
},
&cli.BoolFlag{
Name: removeDNSFileFlag,
Usage: "Remove the DNS file once the upload completes",
Value: false,
},
},
}
}

// DNSCleaner is the main action function for the dns-cleaner command.
// It checks if a DNS file exists. If there isn't a DNS file then it downloads records, if there is a file there then it uploads records
func DNSCleaner(c *cli.Context) error {
if err := setup(c); err != nil {
return err
}
log.Info("Running DNS Cleaner")

fileExists := true
_, err := os.Stat(c.String(dnsFileFlag))
if err != nil {
fileExists = os.IsExist(err)
}
fileExists := FileExists(c.String(dnsFileFlag))
log.Debugf("Existing DNS file: %t", fileExists)
if !fileExists {
log.Info("Downloading DNS Records")
Expand All @@ -74,17 +114,45 @@ func DNSCleaner(c *cli.Context) error {
return nil
}

// quickClean checks to see if a DNS record is numeric
func quickClean(zoneName, record string) bool {
r := strings.Split(record, fmt.Sprintf(".%s", zoneName))[0]
log.Debugf("Stripped record: %s", r)
_, err := strconv.Atoi(r)
log.Debugf("Error converting: %t", err != nil)
// if err != nil there is no number, and we should keep
return err != nil
}

// DownloadDNS downloads current DNS records from Cloudflare
func DownloadDNS(c *cli.Context) error {
// Always need to set up
if APIClient == nil {
if err := setup(c); err != nil {
return err
}
}

// Make sure that we don't overwrite if told not to
if FileExists(c.String(dnsFileFlag)) && c.Bool(noOverwriteFlag) {
return errors.New("existing DNS file found and no overwrite flag is set")
}

zoneName := c.String(zoneNameFlag)
log.Infof("Zone name: %s. DNS file: %s", zoneName, c.String(dnsFileFlag))
if zoneName == "" {
return errors.New("need zone-name set when downloading DNS")
}

// Get the Zone ID which is what the API calls are made with
// Easier to ask for this than having users get the ID
// ToDo: Allow for Zone ID input
id, err := APIClient.ZoneIDByName(zoneName)
if err != nil {
log.WithError(err).Debug("Error getting zone id from name")
return err
}
// Get all DNS records
records, err := APIClient.DNSRecords(ctx, id, cloudflare.DNSRecord{})
if err != nil {
log.WithError(err).Error("Error getting zone info with ID")
Expand All @@ -94,37 +162,77 @@ func DownloadDNS(c *cli.Context) error {
ZoneID: id,
ZoneName: zoneName,
}
// Default keep action
toKeep := !c.Bool(noKeepFlag)
log.Debugf("ToKeep setting: %t", toKeep)

// If using quick clean to filter out numeric records
useQuickClean := c.Bool(quickCleanFlag)
log.Debugf("Quick Clean setting: %t", useQuickClean)

if useQuickClean && !toKeep {
return errors.New("using `--quick-clean` is not supported with `--no-keep`")
}

// Create Records for the RecordFile
for _, record := range records {
var keepValue bool
if useQuickClean {
keepValue = quickClean(zoneName, record.Name)
} else {
keepValue = toKeep
}
recordFile.Records = append(recordFile.Records, DNSRecord{
Name: record.Name,
ID: record.ID,
Type: record.Type,
Keep: c.Bool(noKeepFlag),
Name: record.Name,
ID: record.ID,
Type: record.Type,
Keep: keepValue,
Content: record.Content,
})
}
data, err := yaml.Marshal(&recordFile)
if err != nil {
log.WithError(err).Error("Error marshing yaml data")
log.WithError(err).Error("Error marshalling yaml data")
return err
}
// Write out the RecordFile Data
if err := os.WriteFile(c.String(dnsFileFlag), data, 0755); err != nil {
log.WithError(err).Error("Error writing DNS file")
return err
}
return nil
}

// UploadDNS makes the changes to DNS records based on the DNS file
func UploadDNS(c *cli.Context) error {
file, err := os.ReadFile(c.String(dnsFileFlag))

// Always need to set up
if APIClient == nil {
if err := setup(c); err != nil {
return err
}
}

// Make sure the DNS File exists
dnsFilePath := c.String(dnsFileFlag)
if !FileExists(c.String(dnsFilePath)) {
return fmt.Errorf("no DNS file found at '%s'", dnsFilePath)
}

// Read the DNS file and parse the data
file, err := os.ReadFile(dnsFilePath)
if err != nil {
log.WithError(err).Error("Error reading DNS file")
return err
}

recordFile := &RecordFile{}
if err := yaml.Unmarshal(file, recordFile); err != nil {
log.WithError(err).Error("Error unmarshalling yaml")
return err
}

// Remove the records
zoneID := recordFile.ZoneID
recordCount, errorCount, toRemove := len(recordFile.Records), 0, 0
for _, record := range recordFile.Records {
Expand All @@ -137,5 +245,12 @@ func UploadDNS(c *cli.Context) error {
}
}
log.Infof("%d total records. %d to removed. %d errors removing records", recordCount, errorCount, toRemove)

// Remove DNS record file
if c.Bool(removeDNSFileFlag) {
if err := os.Remove(dnsFilePath); err != nil {
log.WithError(err).Warn("Error deleting old DNS file")
}
}
return nil
}
30 changes: 8 additions & 22 deletions cmd/cloudflare-utils/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,13 @@ import (
"github.com/urfave/cli/v2"
"os"
"sort"
"strings"
)

var (
log = logrus.New()
ctx = context.Background()
Version = "DEV"
BuildTime = "unknown"
version = "DEV"
date = "unknown"
APIClient *cloudflare.API
)

Expand All @@ -31,7 +30,7 @@ func main() {
app := &cli.App{
Name: "cloudflare-utils",
Usage: "Program for quick cloudflare utils",
Version: fmt.Sprintf("%s (built %s)", Version, BuildTime),
Version: fmt.Sprintf("%s (built %s)", version, date),
Suggest: true,
Authors: []*cli.Author{
{
Expand Down Expand Up @@ -82,14 +81,19 @@ func main() {
}

func setup(c *cli.Context) (err error) {
// Set up log level
setLogLevel(c)

// Create Cloudflare API Client
if c.String(apiTokenFlag) != "" {
// Create new API Client using an API Token
APIClient, err = cloudflare.NewWithAPIToken(c.String(apiTokenFlag), cloudflare.UserAgent("cloudflare-utils"))
if err != nil {
log.WithError(err).Error("Error creating new API instance with token")
return err
}
} else if c.String(apiKeyFlag) != "" || c.String(apiEmailFlag) != "" {
// Create new API Client using legacy API Key and API Email
if c.String(apiKeyFlag) == "" || c.String(apiEmailFlag) == "" {
log.Error("Need to have both API Key and Email set for legacy method")
}
Expand All @@ -103,21 +107,3 @@ func setup(c *cli.Context) (err error) {
}
return err
}

func setLogLevel(c *cli.Context) {
if c.Bool("debug") {
log.SetLevel(logrus.DebugLevel)
} else if c.Bool("verbose") {
log.SetLevel(logrus.WarnLevel)
} else {
switch strings.ToLower(os.Getenv("LOG_LEVEL")) {
case "trace":
log.SetLevel(logrus.TraceLevel)
case "debug":
log.SetLevel(logrus.DebugLevel)
default:
log.SetLevel(logrus.InfoLevel)
}
}
log.Debugf("Log Level set to %v", log.Level)
}
Loading

0 comments on commit fc61f02

Please sign in to comment.