Commit 54fd7a1

bryfry <bryon@fryer.io>
2024-09-18 12:58:11
init
cmd/root.go
@@ -0,0 +1,43 @@
+package cmd
+
+import (
+	"os"
+
+	"github.com/bryfry/cfdns/internal/cf"
+	"github.com/spf13/cobra"
+)
+
+var rootCmd = &cobra.Command{
+	Use:   "cfdns",
+	Short: "update cloudflare dns",
+	RunE: func(cmd *cobra.Command, args []string) error {
+
+		zoneName := "trustme.click"
+		subdomainName := "minecraft.trustme.click"
+		apiToken := ""
+
+		cfdns, err := cf.New(
+			zoneName,
+			subdomainName,
+			apiToken)
+		if err != nil {
+			return err
+		}
+		err = cfdns.SetIP("127.0.0.1")
+		if err != nil {
+			return err
+		}
+		return nil
+	},
+}
+
+func Execute() {
+	err := rootCmd.Execute()
+	if err != nil {
+		os.Exit(1)
+	}
+}
+
+func init() {
+	rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
+}
internal/cf/main.go
@@ -0,0 +1,118 @@
+package cf
+
+import (
+	"context"
+	"fmt"
+
+	"github.com/cloudflare/cloudflare-go"
+)
+
+// CF handles interactions with the Cloudflare API.
+type CF struct {
+	api          *cloudflare.API
+	zone         *cloudflare.ResourceContainer // ZoneID Container
+	record       *cloudflare.DNSRecord
+	recordExists bool
+}
+
+// New creates a new instance of CF with the provided API token.
+func New(
+	zoneName string,
+	subdomainName string,
+	apiToken string,
+) (*CF, error) {
+
+	api, err := cloudflare.NewWithAPIToken(apiToken)
+	if err != nil {
+		return nil, err
+	}
+
+	cf := &CF{
+		api: api,
+	}
+
+	id, err := cf.api.ZoneIDByName(zoneName)
+	if err != nil {
+		return nil, fmt.Errorf(
+			"failed to lookup zone id name=%s: %w",
+			zoneName,
+			err)
+	}
+	cf.zone = cloudflare.ZoneIdentifier(id)
+
+	records, resultInfo, err := cf.api.ListDNSRecords(
+		context.Background(),
+		cf.zone,
+		cloudflare.ListDNSRecordsParams{
+			Type: "A",
+			Name: subdomainName,
+		})
+	if err != nil {
+		err = fmt.Errorf(
+			"failed to lookup dns record name=%s: %w",
+			subdomainName,
+			err)
+		return nil, err
+	}
+
+	if len(records) == 0 || resultInfo.Count == 0 {
+		cf.record = &cloudflare.DNSRecord{
+			Type: "A",
+			Name: subdomainName,
+			TTL:  60,
+		}
+		cf.recordExists = false
+		return cf, nil
+	}
+
+	cf.record = &records[0]
+	cf.recordExists = true
+	return cf, nil
+}
+
+func (cf *CF) SetIP(ip string) error {
+
+	if cf.zone == nil {
+		return fmt.Errorf("zone must be set")
+	}
+	if cf.record == nil || cf.record.Name == "" {
+		return fmt.Errorf("dns record must be set")
+	}
+
+	if !cf.recordExists {
+		record, err := cf.api.CreateDNSRecord(
+			context.Background(),
+			cf.zone,
+			cloudflare.CreateDNSRecordParams{
+				Type:    cf.record.Type,
+				Name:    cf.record.Name,
+				Content: ip,
+				TTL:     cf.record.TTL,
+			})
+		if err != nil {
+			return fmt.Errorf("failed to create dns record name=%s: %w",
+				cf.record.Name,
+				err)
+		}
+		cf.record = &record
+		return nil
+	}
+
+	record, err := cf.api.UpdateDNSRecord(
+		context.Background(),
+		cf.zone,
+		cloudflare.UpdateDNSRecordParams{
+			Type:    cf.record.Type,
+			Name:    cf.record.Name,
+			Content: ip,
+			ID:      cf.record.ID,
+			TTL:     cf.record.TTL,
+		})
+	if err != nil {
+		return fmt.Errorf("failed to update dns record name=%s: %w",
+			cf.record.Name,
+			err)
+	}
+	cf.record = &record
+	return err
+}
internal/cf/prompt-fo.txt
@@ -0,0 +1,26 @@
+Update the functional options to not use with closures (the Uber Style Guide says so).
+
+Here is an example function:
+
+```go
+// Option interface enables functional options on CF
+type Option interface {
+	apply(*CF) error
+}
+
+func ZoneName(name string) Option {
+	return zoneNameOption(name)
+}
+
+type zoneNameOption string
+
+func (zn zoneNameOption) apply(cf *CF) error {
+	name := string(zn)
+	id, err := cf.api.ZoneIDByName(name)
+	if err != nil {
+		return fmt.Errorf("failed to lookup zone id name=%s: %w", name, err)
+	}
+	cf.zoneID = id
+	return nil
+}
+```
internal/cf/prompt.txt
@@ -0,0 +1,8 @@
+Write an internal golang package that 
+
+ - Follows the Uber Golang Style Guide (https://github.com/uber-go/guide/blob/master/style.md)
+ - Uses the `cloudflare-go` library (https://pkg.go.dev/github.com/cloudflare/cloudflare-go) 
+ - Uses a New constructor with api token as the only required parameter
+ - Sets via lookup by Name the ZoneID via a functional option and/or a method
+ - Sets the DNS Record ID by name for a subdomain via a functional option and/or a method
+ - Sets the DNS Record (upsert) to an A record with a provided IP address
go.mod
@@ -0,0 +1,18 @@
+module github.com/bryfry/cfdns
+
+go 1.22.0
+
+require (
+	github.com/cloudflare/cloudflare-go v0.104.0
+	github.com/spf13/cobra v1.8.1
+)
+
+require (
+	github.com/goccy/go-json v0.10.3 // indirect
+	github.com/google/go-querystring v1.1.0 // indirect
+	github.com/inconshreveable/mousetrap v1.1.0 // indirect
+	github.com/spf13/pflag v1.0.5 // indirect
+	golang.org/x/net v0.29.0 // indirect
+	golang.org/x/text v0.18.0 // indirect
+	golang.org/x/time v0.6.0 // indirect
+)
go.sum
@@ -0,0 +1,33 @@
+github.com/cloudflare/cloudflare-go v0.104.0 h1:R/lB0dZupaZbOgibAH/BRrkFbZ6Acn/WsKg2iX2xXuY=
+github.com/cloudflare/cloudflare-go v0.104.0/go.mod h1:pfUQ4PIG4ISI0/Mmc21Bp86UnFU0ktmPf3iTgbSL+cM=
+github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
+github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
+github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
+github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
+github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
+github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
+github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
+github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
+github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
+github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
+github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
+golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
+golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
+golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
+golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
+golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
main.go
@@ -0,0 +1,10 @@
+/*
+Copyright © 2024 NAME HERE <EMAIL ADDRESS>
+*/
+package main
+
+import "github.com/bryfry/cfdns/cmd"
+
+func main() {
+	cmd.Execute()
+}