Non-Official GO CloudFlare DNS API client for go. It was created because the official API is not stable and breaks its consumers multiple times a year. Some of the breaks are immediately apparent because the compiler itself can find the problem, sometimes the expectation can't be detected automatically, while when the returned error is changed, leading to unexpected behavior in code that might be mission-critical.
This library was designed to support only the DNS service.
This project is in pre-release stage and backwards compatibility is not guaranteed.
go get github.com/simplesurance/cfdns@latest
Listing records uses the Iterator pattern to completely abstract the complexity of pagination, while keeping constant memory usage, even when the resulting list is arbitrarily large.
ctx := context.Background()
apitoken := os.Getenv("CFTOKEN")
creds, err := cfdns.APIToken(apitoken)
if err != nil {
panic(err)
}
client := cfdns.NewClient(creds)
iter := client.ListZones(&cfdns.ListZonesRequest{})
for {
zone, err := iter.Next(ctx)
if err != nil {
if errors.Is(err, io.EOF) {
break
}
panic(err)
}
fmt.Printf("Found zone %s\n", zone.Name)
}
All methods that do not return a list receive a context and a request struct and return a struct and an error.
ctx := context.Background()
apitoken := os.Getenv("TEST_CF_APITOKEN")
testZoneID := os.Getenv("TEST_CF_ZONE_ID")
creds, err := cfdns.APIToken(apitoken)
if err != nil {
panic(err)
}
client := cfdns.NewClient(creds)
resp, err := client.CreateRecord(ctx, &cfdns.CreateRecordRequest{
ZoneID: testZoneID,
Name: "example-record",
Type: "CNAME",
Content: "github.com",
Proxied: false,
Comment: "Created by cfdns example",
TTL: time.Duration(30 * time.Minute),
})
if err != nil {
panic(err)
}
fmt.Printf("Created DNS record %s", resp.Name)
// cleanup
_, _ = client.DeleteRecord(ctx, &cfdns.DeleteRecordRequest{
ZoneID: testZoneID,
RecordID: resp.ID,
})
// Output: Created DNS record example-record.simplesurance.top
Rules for errors returned are as follows:
- If the error response came from sending the HTTP response to the CloudFlare, even if it is an invalid response, the error is HTTPError;
- If the response is a valid CloudFlare API response, then the error is also a CloudFlareError (so it is both HTTPError and CloudFlareError);
- If not valid HTTP response could be obtained from the server, then some other error is returned.
In all cases, the caller MUST use errors.As()
to get either the
HTTPError
or CloudFlareError
object.
All errors that result from calling the CloudFlare REST API allow reading the HTTP response that caused it.
ctx := context.Background()
apitoken := os.Getenv("TEST_CF_APITOKEN")
testZoneID := os.Getenv("TEST_CF_ZONE_ID")
creds, err := cfdns.APIToken(apitoken)
if err != nil {
panic(err)
}
client := cfdns.NewClient(creds)
_, err = client.CreateRecord(ctx, &cfdns.CreateRecordRequest{
ZoneID: testZoneID,
Name: "invalid name",
Type: "A",
Content: "github.com",
Comment: "Created by cfdns example",
TTL: 30 * time.Minute,
})
httpErr := cfdns.HTTPError{}
if !errors.As(err, &httpErr) {
panic("not an HTTP error")
}
fmt.Printf("Got HTTP error %v", httpErr.Code) // can also access response headers and raw response body
// Output: Got HTTP error 400
ctx := context.Background()
apitoken := os.Getenv("TEST_CF_APITOKEN")
testZoneID := os.Getenv("TEST_CF_ZONE_ID")
creds, err := cfdns.APIToken(apitoken)
if err != nil {
panic(err)
}
client := cfdns.NewClient(creds)
_, err = client.CreateRecord(ctx, &cfdns.CreateRecordRequest{
ZoneID: testZoneID,
Name: "invalid name",
Type: "A",
Content: "github.com",
Comment: "Created by cfdns example",
TTL: 30 * time.Minute,
})
cfErr := cfdns.CloudFlareError{}
if !errors.As(err, &cfErr) {
panic("not a CloudFlareError")
}
fmt.Printf("Got HTTP error %v\n", cfErr.HTTPError.Code) // can also access response headers and raw response body
for _, cfe := range cfErr.Errors {
fmt.Printf("- CF error %d: %s\n", cfe.Code, cfe.Message) // can also access response headers and raw response body
}
// Output:
// Got HTTP error 400
// - CF error 1004: DNS Validation Error