Skip to content

Commit

Permalink
zone: make settings more flexible
Browse files Browse the repository at this point in the history
For the life of this library, the settings have always been under the
`/zones/:zone_id/settings` route which was an aggregate of all the
settings. This was designed as a way to aggregate all of the settings in
one place and had its place however, now we are breaking up the zone
settings to their true paths and need to make the `Get` and `Update`
zone settings compatible with this approach.

This will enable teams to use their true paths instead of the prefixed
or aggregate paths with no friction from the library.
  • Loading branch information
jacobbednarz committed Mar 28, 2023
1 parent dbc7a5f commit c300573
Show file tree
Hide file tree
Showing 4 changed files with 166 additions and 10 deletions.
11 changes: 11 additions & 0 deletions .changelog/1251.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
```release-note:breaking-change
zone: `ZoneSingleSetting` has been renamed to `GetZoneSetting` and updated method signature inline with our expected conventions
```

```release-note:breaking-change
zone: `UpdateZoneSingleSetting` has been renamed to `UpdateZoneSetting` and updated method signature inline with our expected conventions
```

```release-note:enhancement
zone: `GetZoneSetting` and `UpdateZoneSetting` now allow configuring the path for where a setting resides instead of assuming `settings`
```
2 changes: 2 additions & 0 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const (

errInvalidResourceContainerAccess = "requested resource container (%q) is not supported for this endpoint"
errRequiredAccountLevelResourceContainer = "this endpoint requires using an account level resource container and identifiers"
errRequiredZoneLevelResourceContainer = "this endpoint requires using a zone level resource container and identifiers"
)

var (
Expand All @@ -43,6 +44,7 @@ var (
ErrMissingResourceIdentifier = errors.New(errMissingResourceIdentifier)

ErrRequiredAccountLevelResourceContainer = errors.New(errRequiredAccountLevelResourceContainer)
ErrRequiredZoneLevelResourceContainer = errors.New(errRequiredZoneLevelResourceContainer)
)

type ErrorType string
Expand Down
63 changes: 53 additions & 10 deletions zone.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ import (
"golang.org/x/net/idna"
)

var (
// ErrMissingSettingName is for when setting name is required but missing.
ErrMissingSettingName = errors.New("zone setting name required but missing")
)

// Owner describes the resource owner.
type Owner struct {
ID string `json:"id"`
Expand Down Expand Up @@ -300,6 +305,17 @@ type zoneSubscriptionRatePlanPayload struct {
} `json:"rate_plan"`
}

type GetZoneSettingParams struct {
Name string `json:"-"`
PathPrefix string `json:"-"`
}

type UpdateZoneSettingParams struct {
Name string `json:"-"`
PathPrefix string `json:"-"`
Value interface{} `json:"value"`
}

// CreateZone creates a zone on an account.
//
// Setting jumpstart to true will attempt to automatically scan for existing
Expand Down Expand Up @@ -897,11 +913,25 @@ func normalizeZoneName(name string) string {
return name
}

// ZoneSingleSetting returns information about specified setting to the specified zone.
// GetZoneSetting returns information about specified setting to the specified
// zone.
//
// API reference: https://api.cloudflare.com/#zone-settings-get-all-zone-settings
func (api *API) ZoneSingleSetting(ctx context.Context, zoneID, settingName string) (ZoneSetting, error) {
uri := fmt.Sprintf("/zones/%s/settings/%s", zoneID, settingName)
func (api *API) GetZoneSetting(ctx context.Context, rc *ResourceContainer, params GetZoneSettingParams) (ZoneSetting, error) {
if rc.Level != ZoneRouteLevel {
return ZoneSetting{}, ErrRequiredZoneLevelResourceContainer
}

if rc.Identifier == "" {
return ZoneSetting{}, ErrMissingName
}

pathPrefix := "settings"
if params.PathPrefix != "" {
pathPrefix = params.PathPrefix
}

uri := fmt.Sprintf("/zones/%s/%s/%s", rc.Identifier, pathPrefix, params.Name)
res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
if err != nil {
return ZoneSetting{}, err
Expand All @@ -914,23 +944,36 @@ func (api *API) ZoneSingleSetting(ctx context.Context, zoneID, settingName strin
return r.Result, nil
}

// UpdateZoneSingleSetting updates the specified setting for a given zone.
// UpdateZoneSetting updates the specified setting for a given zone.
//
// API reference: https://api.cloudflare.com/#zone-settings-edit-zone-settings-info
func (api *API) UpdateZoneSingleSetting(ctx context.Context, zoneID, settingName string, setting ZoneSetting) (*ZoneSettingSingleResponse, error) {
uri := fmt.Sprintf("/zones/%s/settings/%s", zoneID, settingName)
res, err := api.makeRequestContext(ctx, http.MethodPatch, uri, setting)
func (api *API) UpdateZoneSetting(ctx context.Context, rc *ResourceContainer, params UpdateZoneSettingParams) (ZoneSetting, error) {
if rc.Level != ZoneRouteLevel {
return ZoneSetting{}, ErrRequiredZoneLevelResourceContainer
}

if rc.Identifier == "" {
return ZoneSetting{}, ErrMissingName
}

pathPrefix := "settings"
if params.PathPrefix != "" {
pathPrefix = params.PathPrefix
}

uri := fmt.Sprintf("/zones/%s/%s/%s", rc.Identifier, pathPrefix, params.Name)
res, err := api.makeRequestContext(ctx, http.MethodPatch, uri, params)
if err != nil {
return nil, err
return ZoneSetting{}, err
}

response := &ZoneSettingSingleResponse{}
err = json.Unmarshal(res, &response)
if err != nil {
return nil, fmt.Errorf("%s: %w", errUnmarshalError, err)
return ZoneSetting{}, fmt.Errorf("%s: %w", errUnmarshalError, err)
}

return response, nil
return response.Result, nil
}

// ZoneExport returns the text BIND config for the given zone
Expand Down
100 changes: 100 additions & 0 deletions zone_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1510,3 +1510,103 @@ func TestUpdateZoneSSLSettings(t *testing.T) {
assert.Equal(t, s.ModifiedOn, "2014-01-01T05:20:00.12345Z")
}
}

func TestGetZoneSetting(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, `{
"result": {
"id": "ssl",
"value": "off",
"editable": true,
"modified_on": "2014-01-01T05:20:00.12345Z"
}
}`)
}
mux.HandleFunc("/zones/foo/settings/ssl", handler)
s, err := client.GetZoneSetting(context.Background(), ZoneIdentifier("foo"), GetZoneSettingParams{Name: "ssl"})
if assert.NoError(t, err) {
assert.Equal(t, s.ID, "ssl")
assert.Equal(t, s.Value, "off")
assert.Equal(t, s.Editable, true)
assert.Equal(t, s.ModifiedOn, "2014-01-01T05:20:00.12345Z")
}
}

func TestGetZoneSettingWithCustomPathPrefix(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, `{
"result": {
"id": "ssl",
"value": "off",
"editable": true,
"modified_on": "2014-01-01T05:20:00.12345Z"
}
}`)
}
mux.HandleFunc("/zones/foo/my_custom_path/ssl", handler)
s, err := client.GetZoneSetting(context.Background(), ZoneIdentifier("foo"), GetZoneSettingParams{Name: "ssl", PathPrefix: "my_custom_path"})
if assert.NoError(t, err) {
assert.Equal(t, s.ID, "ssl")
assert.Equal(t, s.Value, "off")
assert.Equal(t, s.Editable, true)
assert.Equal(t, s.ModifiedOn, "2014-01-01T05:20:00.12345Z")
}
}

func TestUpdateZoneSetting(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, `{
"result": {
"id": "ssl",
"value": "off",
"editable": true,
"modified_on": "2014-01-01T05:20:00.12345Z"
}
}`)
}
mux.HandleFunc("/zones/foo/settings/ssl", handler)
s, err := client.UpdateZoneSetting(context.Background(), ZoneIdentifier("foo"), UpdateZoneSettingParams{Name: "ssl", Value: "off"})
if assert.NoError(t, err) {
assert.Equal(t, s.ID, "ssl")
assert.Equal(t, s.Value, "off")
assert.Equal(t, s.Editable, true)
assert.Equal(t, s.ModifiedOn, "2014-01-01T05:20:00.12345Z")
}
}

func TestUpdateZoneSettingWithCustomPathPrefix(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, `{
"result": {
"id": "ssl",
"value": "off",
"editable": true,
"modified_on": "2014-01-01T05:20:00.12345Z"
}
}`)
}
mux.HandleFunc("/zones/foo/my_custom_path/ssl", handler)
s, err := client.UpdateZoneSetting(context.Background(), ZoneIdentifier("foo"), UpdateZoneSettingParams{Name: "ssl", PathPrefix: "my_custom_path", Value: "off"})
if assert.NoError(t, err) {
assert.Equal(t, s.ID, "ssl")
assert.Equal(t, s.Value, "off")
assert.Equal(t, s.Editable, true)
assert.Equal(t, s.ModifiedOn, "2014-01-01T05:20:00.12345Z")
}
}

0 comments on commit c300573

Please sign in to comment.