Repository: cloudflare/cloudflare-go
Pull request #3670 (2024-11-24 20:42:42)

Add DNS settings
## Description

Adds new Settings field to DNSRecord. Only has CNAME Flattening currently.
## Has your change been tested?

Add new test and it passes

## Screenshots (if appropriate):

## Types of changes

What sort of change does your code introduce/modify?

- [ ] Bug fix (non-breaking change which fixes an issue)
- [X] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)

## Checklist:

- [X] My code follows the code style of this project.
- [ ] My change requires a change to the documentation.
- [ ] I have updated the documentation accordingly.
- [X] I have added tests to cover my changes.
- [X] All new and existing tests passed.
- [X] This change is using publicly documented in [cloudflare/api-schemas](https://github.com/cloudflare/api-schemas) 
      and relies on stable APIs.



Patch:
diff --git a/.changelog/3670.txt b/.changelog/3670.txt
new file mode 100644
index 000000000..f964b5db0
--- /dev/null
+++ b/.changelog/3670.txt
@@ -0,0 +1,3 @@
+```release-note:enhancement
+dns: Add settings to DNSRecord
+```
diff --git a/dns.go b/dns.go
index 6549e60d1..4169eb6cf 100644
--- a/dns.go
+++ b/dns.go
@@ -17,22 +17,27 @@ import (
 // ErrMissingBINDContents is for when the BIND file contents is required but not set.
 var ErrMissingBINDContents = errors.New("required BIND config contents missing")
 
+type DNSRecordSettings struct {
+	FlattenCNAME *bool `json:"flatten_cname,omitempty"`
+}
+
 // DNSRecord represents a DNS record in a zone.
 type DNSRecord struct {
-	CreatedOn  time.Time   `json:"created_on,omitempty"`
-	ModifiedOn time.Time   `json:"modified_on,omitempty"`
-	Type       string      `json:"type,omitempty"`
-	Name       string      `json:"name,omitempty"`
-	Content    string      `json:"content,omitempty"`
-	Meta       interface{} `json:"meta,omitempty"`
-	Data       interface{} `json:"data,omitempty"` // data returned by: SRV, LOC
-	ID         string      `json:"id,omitempty"`
-	Priority   *uint16     `json:"priority,omitempty"`
-	TTL        int         `json:"ttl,omitempty"`
-	Proxied    *bool       `json:"proxied,omitempty"`
-	Proxiable  bool        `json:"proxiable,omitempty"`
-	Comment    string      `json:"comment,omitempty"` // the server will omit the comment field when the comment is empty
-	Tags       []string    `json:"tags,omitempty"`
+	CreatedOn  time.Time         `json:"created_on,omitempty"`
+	ModifiedOn time.Time         `json:"modified_on,omitempty"`
+	Type       string            `json:"type,omitempty"`
+	Name       string            `json:"name,omitempty"`
+	Content    string            `json:"content,omitempty"`
+	Meta       interface{}       `json:"meta,omitempty"`
+	Data       interface{}       `json:"data,omitempty"` // data returned by: SRV, LOC
+	ID         string            `json:"id,omitempty"`
+	Priority   *uint16           `json:"priority,omitempty"`
+	TTL        int               `json:"ttl,omitempty"`
+	Proxied    *bool             `json:"proxied,omitempty"`
+	Proxiable  bool              `json:"proxiable,omitempty"`
+	Comment    string            `json:"comment,omitempty"` // the server will omit the comment field when the comment is empty
+	Tags       []string          `json:"tags,omitempty"`
+	Settings   DNSRecordSettings `json:"settings,omitempty"`
 }
 
 // DNSRecordResponse represents the response from the DNS endpoint.
@@ -66,16 +71,17 @@ type ListDNSRecordsParams struct {
 }
 
 type UpdateDNSRecordParams struct {
-	Type     string      `json:"type,omitempty"`
-	Name     string      `json:"name,omitempty"`
-	Content  string      `json:"content,omitempty"`
-	Data     interface{} `json:"data,omitempty"` // data for: SRV, LOC
-	ID       string      `json:"-"`
-	Priority *uint16     `json:"priority,omitempty"`
-	TTL      int         `json:"ttl,omitempty"`
-	Proxied  *bool       `json:"proxied,omitempty"`
-	Comment  *string     `json:"comment,omitempty"` // nil will keep the current comment, while StringPtr("") will empty it
-	Tags     []string    `json:"tags"`
+	Type     string            `json:"type,omitempty"`
+	Name     string            `json:"name,omitempty"`
+	Content  string            `json:"content,omitempty"`
+	Data     interface{}       `json:"data,omitempty"` // data for: SRV, LOC
+	ID       string            `json:"-"`
+	Priority *uint16           `json:"priority,omitempty"`
+	TTL      int               `json:"ttl,omitempty"`
+	Proxied  *bool             `json:"proxied,omitempty"`
+	Comment  *string           `json:"comment,omitempty"` // nil will keep the current comment, while StringPtr("") will empty it
+	Tags     []string          `json:"tags"`
+	Settings DNSRecordSettings `json:"settings,omitempty"`
 }
 
 // DNSListResponse represents the response from the list DNS records endpoint.
@@ -175,20 +181,21 @@ type ImportDNSRecordsParams struct {
 }
 
 type CreateDNSRecordParams struct {
-	CreatedOn  time.Time   `json:"created_on,omitempty" url:"created_on,omitempty"`
-	ModifiedOn time.Time   `json:"modified_on,omitempty" url:"modified_on,omitempty"`
-	Type       string      `json:"type,omitempty" url:"type,omitempty"`
-	Name       string      `json:"name,omitempty" url:"name,omitempty"`
-	Content    string      `json:"content,omitempty" url:"content,omitempty"`
-	Meta       interface{} `json:"meta,omitempty"`
-	Data       interface{} `json:"data,omitempty"` // data returned by: SRV, LOC
-	ID         string      `json:"id,omitempty"`
-	Priority   *uint16     `json:"priority,omitempty"`
-	TTL        int         `json:"ttl,omitempty"`
-	Proxied    *bool       `json:"proxied,omitempty" url:"proxied,omitempty"`
-	Proxiable  bool        `json:"proxiable,omitempty"`
-	Comment    string      `json:"comment,omitempty" url:"comment,omitempty"` // to the server, there's no difference between "no comment" and "empty comment"
-	Tags       []string    `json:"tags,omitempty"`
+	CreatedOn  time.Time         `json:"created_on,omitempty" url:"created_on,omitempty"`
+	ModifiedOn time.Time         `json:"modified_on,omitempty" url:"modified_on,omitempty"`
+	Type       string            `json:"type,omitempty" url:"type,omitempty"`
+	Name       string            `json:"name,omitempty" url:"name,omitempty"`
+	Content    string            `json:"content,omitempty" url:"content,omitempty"`
+	Meta       interface{}       `json:"meta,omitempty"`
+	Data       interface{}       `json:"data,omitempty"` // data returned by: SRV, LOC
+	ID         string            `json:"id,omitempty"`
+	Priority   *uint16           `json:"priority,omitempty"`
+	TTL        int               `json:"ttl,omitempty"`
+	Proxied    *bool             `json:"proxied,omitempty" url:"proxied,omitempty"`
+	Proxiable  bool              `json:"proxiable,omitempty"`
+	Comment    string            `json:"comment,omitempty" url:"comment,omitempty"` // to the server, there's no difference between "no comment" and "empty comment"
+	Tags       []string          `json:"tags,omitempty"`
+	Settings   DNSRecordSettings `json:"settings,omitempty"`
 }
 
 // CreateDNSRecord creates a DNS record for the zone identifier.


Test patch:
diff --git a/dns_test.go b/dns_test.go
index 01af5c1b7..92d24fcef 100644
--- a/dns_test.go
+++ b/dns_test.go
@@ -674,3 +674,99 @@ func TestDeleteDNSRecord(t *testing.T) {
 	err = client.DeleteDNSRecord(context.Background(), ZoneIdentifier(testZoneID), dnsRecordID)
 	require.NoError(t, err)
 }
+
+func TestCreateDNSRecordSettings(t *testing.T) {
+	setup()
+	defer teardown()
+
+	priority := uint16(10)
+	proxied := false
+	FlattenInput := DNSRecord{
+		Type:     "CNAME",
+		Name:     "example.com",
+		Content:  "example1.com",
+		TTL:      120,
+		Priority: &priority,
+		Proxied:  &proxied,
+		Settings: DNSRecordSettings{
+			FlattenCNAME: BoolPtr(true),
+		},
+	}
+
+	handler := func(w http.ResponseWriter, r *http.Request) {
+		assert.Equal(t, http.MethodPost, r.Method, "Expected method 'POST', got %s", r.Method)
+
+		var v DNSRecord
+		err := json.NewDecoder(r.Body).Decode(&v)
+		require.NoError(t, err)
+		assert.Equal(t, FlattenInput, v)
+
+		w.Header().Set("content-type", "application/json")
+		fmt.Fprint(w, `{
+			"success": true,
+			"errors": [],
+			"messages": [],
+			"result": {
+				"id": "372e67954025e0ba6aaa6d586b9e0b59",
+				"type": "CNAME",
+				"name": "example.com",
+				"content": "example1.com",
+				"proxiable": true,
+				"proxied": false,
+				"ttl": 120,
+				"created_on": "2014-01-01T05:20:00Z",
+				"modified_on": "2014-01-01T05:20:00Z",
+				"data": {},
+				"meta": {
+					"auto_added": true,
+					"source": "primary"
+				},
+				"settings": {
+					"flatten_cname": true
+				}
+			}
+		}`)
+	}
+
+	mux.HandleFunc("/zones/"+testZoneID+"/dns_records", handler)
+
+	createdOn, _ := time.Parse(time.RFC3339, "2014-01-01T05:20:00Z")
+	modifiedOn, _ := time.Parse(time.RFC3339, "2014-01-01T05:20:00Z")
+	want := DNSRecord{
+		ID:         "372e67954025e0ba6aaa6d586b9e0b59",
+		Type:       "CNAME",
+		Name:       "example.com",
+		Content:    "example1.com",
+		Proxiable:  true,
+		Proxied:    FlattenInput.Proxied,
+		TTL:        FlattenInput.TTL,
+		CreatedOn:  createdOn,
+		ModifiedOn: modifiedOn,
+		Data:       map[string]interface{}{},
+		Meta: map[string]interface{}{
+			"auto_added": true,
+			"source":     "primary",
+		},
+		Settings: DNSRecordSettings{
+			FlattenCNAME: BoolPtr(true),
+		},
+	}
+
+	_, err := client.CreateDNSRecord(context.Background(), ZoneIdentifier(""), CreateDNSRecordParams{})
+	assert.ErrorIs(t, err, ErrMissingZoneID)
+
+	actual, err := client.CreateDNSRecord(context.Background(), ZoneIdentifier(testZoneID), CreateDNSRecordParams{
+		Type:     "CNAME",
+		Name:     "example.com",
+		Content:  "example1.com",
+		TTL:      120,
+		Priority: &priority,
+		Proxied:  &proxied,
+		Settings: DNSRecordSettings{
+			FlattenCNAME: BoolPtr(true),
+		},
+	})
+	require.NoError(t, err)
+
+	assert.Equal(t, want, actual)
+}