Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
test:
strategy:
matrix:
go: ["1.22", "stable"]
go: ["1.23", "stable"]
name: test
runs-on: ubuntu-latest
steps:
Expand All @@ -31,8 +31,8 @@ jobs:
golangci:
strategy:
matrix:
go: ["1.22", "stable"]
lint: ["v2.1.6"]
go: ["1.23", "stable"]
lint: ["v2.5.0"]
name: lint
runs-on: ubuntu-latest
steps:
Expand Down
11 changes: 11 additions & 0 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ var errResponseTooLarge = retry.PermanentError{

type Client struct {
*settings

creds Credentials
}

Expand Down Expand Up @@ -66,10 +67,13 @@ func sendRequestRetry[TRESP commonResponseSetter](
error,
) {
var resp *response[TRESP]

reterr := retry.ExpBackoff(ctx, logger, retryFirstDelay, retryMaxDelay,
retryFactor, retryMaxAttempts, func() error {
var err error

resp, err = sendRequest[TRESP](ctx, client, logger, req)

return err
})

Expand Down Expand Up @@ -105,8 +109,10 @@ func sendRequest[TRESP commonResponseSetter](

// request timeout
reqCtx := ctx

if client.requestTimeout > 0 {
var reqCtxCancel func()

reqCtx, reqCtxCancel = context.WithTimeout(reqCtx, client.requestTimeout)
defer reqCtxCancel()
}
Expand Down Expand Up @@ -145,6 +151,7 @@ func sendRequest[TRESP commonResponseSetter](
if resp.StatusCode >= 400 {
err = handleErrorResponse(resp, logger)
logFullRequestResponse(logger, reqNoAuth, reqBody, resp, rawResponseFromErr(err))

return nil, err
}

Expand All @@ -171,6 +178,7 @@ func handleSuccessResponse[TRESP commonResponseSetter](httpResp *http.Response,
ret.headers = httpResp.Header

var err error

ret.rawBody, err = readResponseBody(httpResp.Body)
if err != nil {
// error response already specifies is can retry or not
Expand All @@ -183,6 +191,7 @@ func handleSuccessResponse[TRESP commonResponseSetter](httpResp *http.Response,

if len(ret.rawBody) == maxResponseLength {
logger.W(fmt.Sprintf("Response from CloudFlare rejected because is bigger than %d", maxResponseLength))

return nil, retry.PermanentError{
Cause: errors.Join(err, HTTPError{
Code: httpResp.StatusCode,
Expand Down Expand Up @@ -238,6 +247,7 @@ func handleErrorResponse(resp *http.Response, _ *log.Logger) error {
}

var cfcommon cfResponseCommon

err = json.Unmarshal(respBody, &cfcommon)
if err != nil {
return retry.PermanentError{Cause: fmt.Errorf("CloudFlare returned an error, unmarshaling the error body as json failed: %w; %w", err, httpErr)}
Expand Down Expand Up @@ -283,6 +293,7 @@ func logFullRequestResponse(

func requestURL(treq *request) string {
urlstring := baseURL + "/" + treq.path

theurl, err := url.Parse(urlstring)
if err != nil {
// this only happens in case of coding error on cfapi
Expand Down
19 changes: 16 additions & 3 deletions client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,15 @@ const (

func TestCreateCNAME(t *testing.T) {
t.Parallel()

ctx := context.Background()
client, testZoneID := getClient(ctx, t)
cname := "CNAME"
comment := "integration test"

// create a DNS record
recName := testRecordName(t)

resp, err := client.CreateRecord(ctx, &cfdns.CreateRecordRequest{
ZoneID: testZoneID,
Name: recName,
Expand All @@ -52,6 +54,7 @@ func TestCreateCNAME(t *testing.T) {

// assert that it is present
var recs []*cfdns.ListRecordsResponseItem

recs, err = cfdns.ReadAll(ctx, client.ListRecords(&cfdns.ListRecordsRequest{
ZoneID: testZoneID,
Name: resp.Name,
Expand All @@ -75,15 +78,17 @@ func TestCreateCNAME(t *testing.T) {

func TestUpdate(t *testing.T) {
t.Parallel()

originalComment := "integration test"
changedComment := "integration test"
changedComment := "integration test - changed"
cname := "cname"

ctx := context.Background()
client, testZoneID := getClient(ctx, t)

// create a DNS record
recName := testRecordName(t)

resp, err := client.CreateRecord(ctx, &cfdns.CreateRecordRequest{
ZoneID: testZoneID,
Name: recName,
Expand Down Expand Up @@ -139,6 +144,7 @@ func TestUpdate(t *testing.T) {
// Test a few cases of error to make sure error handling works.
func TestConflict(t *testing.T) {
t.Parallel()

ctx := context.Background()
client, testZoneID := getClient(ctx, t)

Expand All @@ -160,13 +166,14 @@ func TestConflict(t *testing.T) {
}

for _, tc := range cases {
tc := tc //nolint:copyloopvar
t.Run(tc.typ, func(t *testing.T) {
t.Parallel()

comment := "integration test"

// create a DNS record
recName := testRecordName(t)

resp, err := client.CreateRecord(ctx, &cfdns.CreateRecordRequest{
ZoneID: testZoneID,
Name: recName,
Expand Down Expand Up @@ -238,6 +245,7 @@ func getClient(ctx context.Context, t *testing.T) (_ *cfdns.Client, testZoneID s
}

t.Fatalf("Zone %s not found on CloudFlare", testzone)

return nil, ""
}

Expand All @@ -250,7 +258,9 @@ func testRecordName(t *testing.T) string {
testzone := os.Getenv(envTestZone)

rnd := make([]byte, 4)
if _, err := rand.Read(rnd); err != nil {

_, err := rand.Read(rnd)
if err != nil {
t.Fatalf("Error reading random number: %v", err)
}

Expand Down Expand Up @@ -292,6 +302,7 @@ func cleanup(
}

t.Logf("Error listing records when looking for old test data: %v", err)

return
}

Expand All @@ -304,6 +315,7 @@ func cleanup(
if err != nil {
t.Errorf("Record %s (%s %s %s) has a time part %q that is invalid",
record.ID, record.Name, record.Type, record.Content, matches[1])

continue
}

Expand All @@ -326,6 +338,7 @@ func cleanup(

func requireNotNil(t *testing.T, v any) {
t.Helper()

if v == nil {
t.Fatalf("Unexpected nil value")
}
Expand Down
3 changes: 2 additions & 1 deletion createrecord.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ func (c *Client) CreateRecord(
TTL: ttl,
},
})

if err != nil {
return nil, err
}
Expand All @@ -50,6 +49,7 @@ func (c *Client) CreateRecord(
log(fmt.Sprintf("Record %s %s %s created with ID=%s",
req.Name, req.Type, req.Content, resp.body.Result.ID))
})

return &CreateRecordResponse{
ID: resp.body.Result.ID,
Name: resp.body.Result.Name,
Expand Down Expand Up @@ -84,6 +84,7 @@ type createRecordAPIRequest struct {

type createRecordAPIResponse struct {
cfResponseCommon

Result struct {
ID string `json:"id"`
Name string `json:"name"`
Expand Down
2 changes: 1 addition & 1 deletion deleterecord.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,14 @@ func (c *Client) DeleteRecord(
queryParams: url.Values{},
body: nil,
})

if err != nil {
return nil, err
}

c.logger.D(func(log log.DebugFn) {
log(fmt.Sprintf("Record %s deleted", req.RecordID))
})

return &DeleteRecordResponse{}, err
}

Expand Down
2 changes: 0 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ module github.com/simplesurance/cfdns

go 1.23.0

toolchain go1.24.3

require (
github.com/fatih/color v1.18.0
golang.org/x/time v0.12.0
Expand Down
3 changes: 3 additions & 0 deletions iterator.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type fetchFn[T any] func(ctx context.Context) (batch []*T, last bool, _ error)
func (it *Iterator[T]) Next(ctx context.Context) (retElm *T, err error) {
if len(it.elements) == 0 && !it.isLast {
var elements []*T

elements, it.isLast, err = it.fetchNext(ctx)
if err != nil {
return nil, err
Expand All @@ -37,13 +38,15 @@ func (it *Iterator[T]) Next(ctx context.Context) (retElm *T, err error) {

retElm = it.elements[0]
it.elements = it.elements[1:]

return retElm, nil
}

// ReadAll is an utility function that reads all elements from an iterator
// and return them as an array.
func ReadAll[T any](ctx context.Context, it *Iterator[T]) ([]*T, error) {
ret := []*T{}

for {
item, err := it.Next(ctx)
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion listrecords.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ func (c *Client) ListRecords(
queryParams: queryParams,
body: nil,
})

if err != nil {
return nil, false, err
}
Expand Down Expand Up @@ -100,6 +99,7 @@ type ListRecordsResponseItem struct {

type listRecordsAPIResponse struct {
cfResponseCommon

Result []listRecordsAPIResponseItem `json:"result"`
}

Expand Down
4 changes: 2 additions & 2 deletions listzones.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
"github.com/simplesurance/cfdns/log"
)

// Listzones lists zones on CloudFlare.
// ListZones lists zones on CloudFlare.
//
// API Reference: https://developers.cloudflare.com/api/operations/zones-get
func (c *Client) ListZones(
Expand Down Expand Up @@ -44,7 +44,6 @@ func (c *Client) ListZones(
queryParams: queryParams,
body: nil,
})

if err != nil {
return nil, false, err
}
Expand Down Expand Up @@ -75,6 +74,7 @@ type ListZonesResponseItem struct {

type listZoneAPIResponse struct {
cfResponseCommon

Result []listZoneAPIResponseItem `json:"result"`
}

Expand Down
4 changes: 4 additions & 0 deletions log/logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,27 +66,31 @@ func (l *Logger) I(msg string, opt ...Option) {
if helper := l.driver.PreLog(); helper != nil {
helper()
}

l.log(msg, Info, opt...)
}

func (l *Logger) W(msg string, opt ...Option) {
if helper := l.driver.PreLog(); helper != nil {
helper()
}

l.log(msg, Warn, opt...)
}

func (l *Logger) E(msg string, opt ...Option) {
if helper := l.driver.PreLog(); helper != nil {
helper()
}

l.log(msg, Error, opt...)
}

func (l *Logger) d(msg string, opt ...Option) {
if helper := l.driver.PreLog(); helper != nil {
helper()
}

l.log(msg, Debug, opt...)
}

Expand Down
1 change: 1 addition & 0 deletions log/testtarget/testtarget.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ func (t testDriver) Send(l *log.Entry) {

keys := slices.Collect(maps.Keys(l.Tags))
slices.Sort(keys)

for _, key := range keys {
fmt.Fprintf(msg, "\n- %s: %v", key, format(l.Tags[key]))
}
Expand Down
2 changes: 2 additions & 0 deletions log/texttarget/textlogger.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ type logger struct {

func (l *logger) Send(entry *log.Entry) {
w := l.outw

mux := l.outMux
if entry.Severity == log.Error {
w = l.errw
Expand Down Expand Up @@ -77,6 +78,7 @@ func (l *logger) Send(entry *log.Entry) {

mux.Lock()
defer mux.Unlock()

fmt.Fprintln(w)
}

Expand Down
3 changes: 3 additions & 0 deletions request.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,11 @@ func (e HTTPError) Error() string {
fmt.Fprintf(msg, "HTTP %d\n", e.Code)
headers := slices.Collect(maps.Keys(e.Headers))
slices.Sort(headers)

for _, k := range headers {
fmt.Fprintf(msg, "%s: %s\n", k, e.Headers.Get(k))
}

fmt.Fprintln(msg)
fmt.Fprintf(msg, "%s", e.RawBody)

Expand All @@ -57,6 +59,7 @@ var _ error = HTTPError{}

type CloudFlareError struct {
cfResponseCommon

HTTPError HTTPError
}

Expand Down
Loading