Repository: cloudflare/cloudflare-go (go)

Issue:
Add support for Cache Reserve
### Current cloudflare-go version

v0.66.0

### Description

Cache Reserve appear to have come out of beta recently (see https://github.com/cloudflare/cloudflare-docs/pull/8561)

We use terraform and the cloudflare-terraform provider to manage all/most of our Cloudflare services and settings. Cache Reserve is currently not available in either. When we asked our Cloudflare reps we were told it would eventually become available once Cache Reserve is out of beta

Handy links:
* https://www.cloudflare.com/en-ca/products/cache-reserve/
* https://developers.cloudflare.com/cache/about/cache-reserve/
* https://developers.cloudflare.com/api/operations/zone-cache-settings-get-cache-reserve-setting

### Use cases

The primary goal for us is to be able to get the ability to configure Cache Reserve via terraform. For that to be possible, cloudflare-go needs to support this capability so that it can be added to https://github.com/cloudflare/terraform-provider-cloudflare (will create a feature request issue there as well)

At present, we manage everything through Terraform, except for Cache Reserve which we enable manually in the dashboard. This is less than ideal for companies relying on IaC and gitops processes.

### Potential cloudflare-go usage

```go
Implementation would probably be fairly standard as it is a simple on/off value
```


### References

_No response_

Pull request description:
add support for cache reserve
## Description

Add support for retrieving and updating the `Cache Reserve` setting on a zone

This is a rather naive implementation which follows the same pattern as the argo.go methods

Closes #1277 

## Has your change been tested?

Unit tests included

I also tested locally on a real account and I was able to retrieve and set the cache_reserve setting

## 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 (api.cloudflare.com or developers.cloudflare.com) and stable APIs.


Patch:
diff --git a/.changelog/1278.txt b/.changelog/1278.txt
new file mode 100644
index 000000000..64e605cf1
--- /dev/null
+++ b/.changelog/1278.txt
@@ -0,0 +1,3 @@
+```release-note:enhancement
+zone: Added `GetCacheReserve` and `UpdateacheReserve` to allow setting Cache Reserve for a zone.
+```
diff --git a/.changelog/1280.txt b/.changelog/1280.txt
deleted file mode 100644
index 7f690ce83..000000000
--- a/.changelog/1280.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-```release-note:dependency
-deps: bumps golang.org/x/net from 0.9.0 to 0.10.0
-```
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1a334a79c..2077a5c03 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,4 @@
-## 0.68.0 (Unreleased)
-
-## 0.67.0 (May 10th, 2023)
+## 0.67.0 (Unreleased)
 
 NOTES:
 
@@ -18,7 +16,6 @@ BUG FIXES:
 DEPENDENCIES:
 
 * deps: bumps github.com/urfave/cli/v2 from 2.25.1 to 2.25.3 ([#1274](https://github.com/cloudflare/cloudflare-go/issues/1274))
-* deps: bumps golang.org/x/net from 0.9.0 to 0.10.0 ([#1280](https://github.com/cloudflare/cloudflare-go/issues/1280))
 
 ## 0.66.0 (26th April, 2023)
 
diff --git a/cache_reserve.go b/cache_reserve.go
new file mode 100644
index 000000000..a62a6144a
--- /dev/null
+++ b/cache_reserve.go
@@ -0,0 +1,82 @@
+package cloudflare
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"time"
+)
+
+// CacheReserve is the structure of the API object for the cache reserve
+// setting.
+type CacheReserve struct {
+	ID         string    `json:"id,omitempty"`
+	ModifiedOn time.Time `json:"modified_on,omitempty"`
+	Value      string    `json:"value"`
+}
+
+// CacheReserveDetailsResponse is the API response for the cache reserve
+// setting.
+type CacheReserveDetailsResponse struct {
+	Result CacheReserve `json:"result"`
+	Response
+}
+
+type zoneCacheReserveSingleResponse struct {
+	Response
+	Result CacheReserve `json:"result"`
+}
+
+type GetCacheReserveParams struct{}
+
+type UpdateCacheReserveParams struct {
+	Value string `json:"value"`
+}
+
+// GetCacheReserve returns information about the current cache reserve settings.
+//
+// API reference: https://developers.cloudflare.com/api/operations/zone-cache-settings-get-cache-reserve-setting
+func (api *API) GetCacheReserve(ctx context.Context, rc *ResourceContainer, params GetCacheReserveParams) (CacheReserve, error) {
+	if rc.Level != ZoneRouteLevel {
+		return CacheReserve{}, ErrRequiredZoneLevelResourceContainer
+	}
+
+	uri := fmt.Sprintf("/%s/%s/cache/cache_reserve", rc.Level, rc.Identifier)
+
+	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
+	if err != nil {
+		return CacheReserve{}, err
+	}
+
+	var cacheReserveDetailsResponse CacheReserveDetailsResponse
+	err = json.Unmarshal(res, &cacheReserveDetailsResponse)
+	if err != nil {
+		return CacheReserve{}, fmt.Errorf("%s: %w", errUnmarshalError, err)
+	}
+	return cacheReserveDetailsResponse.Result, nil
+}
+
+// UpdateCacheReserve updates the cache reserve setting for a zone
+//
+// API reference: https://developers.cloudflare.com/api/operations/zone-cache-settings-change-cache-reserve-setting
+func (api *API) UpdateCacheReserve(ctx context.Context, rc *ResourceContainer, params UpdateCacheReserveParams) (CacheReserve, error) {
+	if rc.Level != ZoneRouteLevel {
+		return CacheReserve{}, ErrRequiredZoneLevelResourceContainer
+	}
+
+	uri := fmt.Sprintf("/%s/%s/cache/cache_reserve", rc.Level, rc.Identifier)
+
+	res, err := api.makeRequestContext(ctx, http.MethodPatch, uri, params)
+	if err != nil {
+		return CacheReserve{}, err
+	}
+
+	response := &zoneCacheReserveSingleResponse{}
+	err = json.Unmarshal(res, &response)
+	if err != nil {
+		return CacheReserve{}, fmt.Errorf("%s: %w", errUnmarshalError, err)
+	}
+
+	return response.Result, nil
+}
diff --git a/go.mod b/go.mod
index 7ace5d409..6526788fb 100644
--- a/go.mod
+++ b/go.mod
@@ -8,7 +8,7 @@ require (
 	github.com/olekukonko/tablewriter v0.0.5
 	github.com/stretchr/testify v1.8.2
 	github.com/urfave/cli/v2 v2.25.3
-	golang.org/x/net v0.10.0
+	golang.org/x/net v0.9.0
 	golang.org/x/time v0.3.0
 )
 
diff --git a/go.sum b/go.sum
index b49c19aff..ab53d2ccc 100644
--- a/go.sum
+++ b/go.sum
@@ -63,15 +63,15 @@ github.com/urfave/cli/v2 v2.25.3 h1:VJkt6wvEBOoSjPFQvOkv6iWIrsJyCrKGtCtxXWwmGeY=
 github.com/urfave/cli/v2 v2.25.3/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc=
 github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
 github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
-golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
-golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
+golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
+golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
 golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
+golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
 golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
 golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
 golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=


Test patch:
diff --git a/cache_reserve_test.go b/cache_reserve_test.go
new file mode 100644
index 000000000..4434c9cf2
--- /dev/null
+++ b/cache_reserve_test.go
@@ -0,0 +1,82 @@
+package cloudflare
+
+import (
+	"context"
+	"fmt"
+	"net/http"
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/assert"
+)
+
+var cacheReserveTimestampString = "2019-02-20T22:37:07.107449Z"
+var cacheReserveTimestamp, _ = time.Parse(time.RFC3339Nano, cacheReserveTimestampString)
+
+func TestCacheReserve(t *testing.T) {
+	setup()
+	defer teardown()
+
+	handler := func(w http.ResponseWriter, r *http.Request) {
+		assert.Equal(t, http.MethodGet, r.Method, "Expected method 'GET', got %s", r.Method)
+		w.Header().Set("content-type", "application/json")
+		fmt.Fprintf(w, `{
+			"success": true,
+			"errors": [],
+			"messages": [],
+			"result": {
+				"id": "cache_reserve",
+				"value": "on",
+				"modified_on": "%s"
+			}
+		}
+		`, cacheReserveTimestampString)
+	}
+
+	mux.HandleFunc("/zones/01a7362d577a6c3019a474fd6f485823/cache/cache_reserve", handler)
+	want := CacheReserve{
+		ID:         "cache_reserve",
+		Value:      "on",
+		ModifiedOn: cacheReserveTimestamp,
+	}
+
+	actual, err := client.GetCacheReserve(context.Background(), ZoneIdentifier("01a7362d577a6c3019a474fd6f485823"), GetCacheReserveParams{})
+
+	if assert.NoError(t, err) {
+		assert.Equal(t, want, actual)
+	}
+}
+
+func TestUpdateCacheReserve(t *testing.T) {
+	setup()
+	defer teardown()
+
+	handler := func(w http.ResponseWriter, r *http.Request) {
+		assert.Equal(t, http.MethodPatch, r.Method, "Expected method 'PATCH', got %s", r.Method)
+		w.Header().Set("content-type", "application/json")
+		fmt.Fprintf(w, `{
+			"success": true,
+			"errors": [],
+			"messages": [],
+			"result": {
+				"id": "cache_reserve",
+				"value": "off",
+				"modified_on": "%s"
+			}
+		}
+		`, cacheReserveTimestampString)
+	}
+
+	mux.HandleFunc("/zones/01a7362d577a6c3019a474fd6f485823/cache/cache_reserve", handler)
+	want := CacheReserve{
+		ID:         "cache_reserve",
+		Value:      "off",
+		ModifiedOn: cacheReserveTimestamp,
+	}
+
+	actual, err := client.UpdateCacheReserve(context.Background(), ZoneIdentifier("01a7362d577a6c3019a474fd6f485823"), UpdateCacheReserveParams{Value: "off"})
+
+	if assert.NoError(t, err) {
+		assert.Equal(t, want, actual)
+	}
+}