Repository: cloudflare/cloudflare-go
Pull request #1130 (2022-11-19 03:05:55)

Add support for workers domain API
## Description

Adds support for [Workers domain API](https://api.cloudflare.com/#worker-domain-properties). Needed for https://github.com/cloudflare/terraform-provider-cloudflare/issues/1921

## Has your change been tested?

New tests have been created and pass

## 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/1130.txt b/.changelog/1130.txt
new file mode 100644
index 000000000..229e12b78
--- /dev/null
+++ b/.changelog/1130.txt
@@ -0,0 +1,3 @@
+```release-note:enhancement
+workers_domain: add support for workers domain API
+```
\ No newline at end of file
diff --git a/workers_domain.go b/workers_domain.go
new file mode 100644
index 000000000..975786d1b
--- /dev/null
+++ b/workers_domain.go
@@ -0,0 +1,150 @@
+package cloudflare
+
+import (
+	"context"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"net/http"
+)
+
+var (
+	ErrMissingHostname    = errors.New("required hostname missing")
+	ErrMissingService     = errors.New("required service missing")
+	ErrMissingEnvironment = errors.New("required environment missing")
+)
+
+type AttachWorkersDomainParams struct {
+	ID          string `json:"id,omitempty"`
+	ZoneID      string `json:"zone_id,omitempty"`
+	ZoneName    string `json:"zone_name,omitempty"`
+	Hostname    string `json:"hostname,omitempty"`
+	Service     string `json:"service,omitempty"`
+	Environment string `json:"environment,omitempty"`
+}
+
+type WorkersDomain struct {
+	ID          string `json:"id,omitempty"`
+	ZoneID      string `json:"zone_id,omitempty"`
+	ZoneName    string `json:"zone_name,omitempty"`
+	Hostname    string `json:"hostname,omitempty"`
+	Service     string `json:"service,omitempty"`
+	Environment string `json:"environment,omitempty"`
+}
+
+type WorkersDomainResponse struct {
+	Response
+	Result WorkersDomain `json:"result"`
+}
+
+type ListWorkersDomainParams struct {
+	ZoneID      string `url:"zone_id,omitempty"`
+	ZoneName    string `url:"zone_name,omitempty"`
+	Hostname    string `url:"hostname,omitempty"`
+	Service     string `url:"service,omitempty"`
+	Environment string `url:"environment,omitempty"`
+}
+
+type WorkersDomainListResponse struct {
+	Response
+	Result []WorkersDomain `json:"result"`
+}
+
+// ListWorkersDomains lists all Worker Domains.
+//
+// API reference: https://api.cloudflare.com/#worker-domain-list-domains
+func (api *API) ListWorkersDomains(ctx context.Context, rc *ResourceContainer, params ListWorkersDomainParams) ([]WorkersDomain, error) {
+	if rc.Identifier == "" {
+		return []WorkersDomain{}, ErrMissingAccountID
+	}
+
+	uri := buildURI(fmt.Sprintf("/accounts/%s/workers/domains", rc.Identifier), params)
+	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
+	if err != nil {
+		return []WorkersDomain{}, fmt.Errorf("%s: %w", errMakeRequestError, err)
+	}
+
+	var r WorkersDomainListResponse
+	if err := json.Unmarshal(res, &r); err != nil {
+		return []WorkersDomain{}, fmt.Errorf("%s: %w", errUnmarshalError, err)
+	}
+
+	return r.Result, nil
+}
+
+// AttachWorkersDomain attaches a worker to a zone and hostname.
+//
+// API reference: https://api.cloudflare.com/#worker-domain-attach-to-domain
+func (api *API) AttachWorkersDomain(ctx context.Context, rc *ResourceContainer, domain AttachWorkersDomainParams) (WorkersDomain, error) {
+	if rc.Identifier == "" {
+		return WorkersDomain{}, ErrMissingAccountID
+	}
+
+	if domain.ZoneID == "" {
+		return WorkersDomain{}, ErrMissingZoneID
+	}
+
+	if domain.Hostname == "" {
+		return WorkersDomain{}, ErrMissingHostname
+	}
+
+	if domain.Service == "" {
+		return WorkersDomain{}, ErrMissingService
+	}
+
+	if domain.Environment == "" {
+		return WorkersDomain{}, ErrMissingEnvironment
+	}
+
+	uri := fmt.Sprintf("/accounts/%s/workers/domains", rc.Identifier)
+	res, err := api.makeRequestContext(ctx, http.MethodPut, uri, domain)
+	if err != nil {
+		return WorkersDomain{}, fmt.Errorf("%s: %w", errMakeRequestError, err)
+	}
+
+	var r WorkersDomainResponse
+	if err := json.Unmarshal(res, &r); err != nil {
+		return WorkersDomain{}, fmt.Errorf("%s: %w", errUnmarshalError, err)
+	}
+
+	return r.Result, nil
+}
+
+// GetWorkersDomain gets a single Worker Domain.
+//
+// API reference: https://api.cloudflare.com/#worker-domain-get-a-domain
+func (api *API) GetWorkersDomain(ctx context.Context, rc *ResourceContainer, domainID string) (WorkersDomain, error) {
+	if rc.Identifier == "" {
+		return WorkersDomain{}, ErrMissingAccountID
+	}
+
+	uri := fmt.Sprintf("/accounts/%s/workers/domains/%s", rc.Identifier, domainID)
+	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
+	if err != nil {
+		return WorkersDomain{}, fmt.Errorf("%s: %w", errMakeRequestError, err)
+	}
+
+	var r WorkersDomainResponse
+	if err := json.Unmarshal(res, &r); err != nil {
+		return WorkersDomain{}, fmt.Errorf("%s: %w", errUnmarshalError, err)
+	}
+
+	return r.Result, nil
+}
+
+// DetachWorkersDomain detaches a worker from a zone and hostname.
+//
+// API reference: https://api.cloudflare.com/#worker-domain-detach-from-domain
+func (api *API) DetachWorkersDomain(ctx context.Context, rc *ResourceContainer, domainID string) error {
+	if rc.Identifier == "" {
+		return ErrMissingAccountID
+	}
+
+	uri := fmt.Sprintf("/accounts/%s/workers/domains/%s", rc.Identifier, domainID)
+	_, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil)
+	if err != nil {
+		return fmt.Errorf("%s: %w", errMakeRequestError, err)
+	}
+
+	return nil
+}


Test patch:
diff --git a/workers_domain_test.go b/workers_domain_test.go
new file mode 100644
index 000000000..443929f28
--- /dev/null
+++ b/workers_domain_test.go
@@ -0,0 +1,181 @@
+package cloudflare
+
+import (
+	"context"
+	"fmt"
+	"net/http"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+const testWorkerDomainID = "dbe10b4bc17c295377eabd600e1787fd"
+
+var expectedWorkerDomain = WorkersDomain{
+	ID:          testWorkerDomainID,
+	ZoneID:      "593c9c94de529bbbfaac7c53ced0447d",
+	ZoneName:    "example.com",
+	Hostname:    "foo.example.com",
+	Service:     "foo",
+	Environment: "production",
+}
+
+func TestWorkersDomain_GetDomain(t *testing.T) {
+	setup()
+	defer teardown()
+
+	mux.HandleFunc(fmt.Sprintf("/accounts/%s/workers/domains/%s", testAccountID, testWorkerDomainID), 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": "dbe10b4bc17c295377eabd600e1787fd",
+			"zone_id": "593c9c94de529bbbfaac7c53ced0447d",
+			"zone_name": "example.com",
+			"hostname": "foo.example.com",
+			"service": "foo",
+			"environment": "production"
+		  }
+		}`)
+	})
+	_, err := client.GetWorkersDomain(context.Background(), AccountIdentifier(""), "")
+	if assert.Error(t, err) {
+		assert.Equal(t, ErrMissingAccountID, err)
+	}
+	res, err := client.GetWorkersDomain(context.Background(), AccountIdentifier(testAccountID), testWorkerDomainID)
+	if assert.NoError(t, err) {
+		assert.Equal(t, expectedWorkerDomain, res)
+	}
+}
+
+func TestWorkersDomain_ListDomains(t *testing.T) {
+	setup()
+	defer teardown()
+
+	mux.HandleFunc(fmt.Sprintf("/accounts/%s/workers/domains", testAccountID), 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": "dbe10b4bc17c295377eabd600e1787fd",
+			  "zone_id": "593c9c94de529bbbfaac7c53ced0447d",
+			  "zone_name": "example.com",
+			  "hostname": "foo.example.com",
+			  "service": "foo",
+			  "environment": "production"
+			}
+		  ]
+		}`)
+	})
+	_, err := client.ListWorkersDomains(context.Background(), AccountIdentifier(""), ListWorkersDomainParams{})
+	if assert.Error(t, err) {
+		assert.Equal(t, ErrMissingAccountID, err)
+	}
+	res, err := client.ListWorkersDomains(context.Background(), AccountIdentifier(testAccountID), ListWorkersDomainParams{})
+	if assert.NoError(t, err) {
+		assert.Equal(t, 1, len(res))
+		assert.Equal(t, expectedWorkerDomain, res[0])
+	}
+}
+
+func TestWorkersDomain_AttachDomain(t *testing.T) {
+	setup()
+	defer teardown()
+
+	mux.HandleFunc(fmt.Sprintf("/accounts/%s/workers/domains", testAccountID), func(w http.ResponseWriter, r *http.Request) {
+		assert.Equal(t, http.MethodPut, r.Method, "Expected method 'PUT', got %s", r.Method)
+		w.Header().Set("content-type", "application/json")
+		fmt.Fprintf(w, `{
+		  "success": true,
+		  "errors": [],
+		  "messages": [],
+		  "result": {
+			"id": "dbe10b4bc17c295377eabd600e1787fd",
+			"zone_id": "593c9c94de529bbbfaac7c53ced0447d",
+			"zone_name": "example.com",
+			"hostname": "foo.example.com",
+			"service": "foo",
+			"environment": "production"
+		  }
+		}`)
+	})
+	_, err := client.AttachWorkersDomain(context.Background(), AccountIdentifier(""), AttachWorkersDomainParams{})
+	if assert.Error(t, err) {
+		assert.Equal(t, ErrMissingAccountID, err)
+	}
+
+	_, err = client.AttachWorkersDomain(context.Background(), AccountIdentifier(testAccountID), AttachWorkersDomainParams{})
+	if assert.Error(t, err) {
+		assert.Equal(t, ErrMissingZoneID, err)
+	}
+
+	_, err = client.AttachWorkersDomain(context.Background(), AccountIdentifier(testAccountID), AttachWorkersDomainParams{
+		ZoneID: testZoneID,
+	})
+	if assert.Error(t, err) {
+		assert.Equal(t, ErrMissingHostname, err)
+	}
+
+	_, err = client.AttachWorkersDomain(context.Background(), AccountIdentifier(testAccountID), AttachWorkersDomainParams{
+		ZoneID:   testZoneID,
+		Hostname: "foo.example.com",
+	})
+	if assert.Error(t, err) {
+		assert.Equal(t, ErrMissingService, err)
+	}
+
+	_, err = client.AttachWorkersDomain(context.Background(), AccountIdentifier(testAccountID), AttachWorkersDomainParams{
+		ZoneID:   testZoneID,
+		Hostname: "foo.example.com",
+		Service:  "foo",
+	})
+	if assert.Error(t, err) {
+		assert.Equal(t, ErrMissingEnvironment, err)
+	}
+
+	res, err := client.AttachWorkersDomain(context.Background(), AccountIdentifier(testAccountID), AttachWorkersDomainParams{
+		ZoneID:      testZoneID,
+		Hostname:    "foo.example.com",
+		Service:     "foo",
+		Environment: "production",
+	})
+	if assert.NoError(t, err) {
+		assert.Equal(t, expectedWorkerDomain, res)
+	}
+}
+
+func TestWorkersDomain_DetachDomain(t *testing.T) {
+	setup()
+	defer teardown()
+
+	mux.HandleFunc(fmt.Sprintf("/accounts/%s/workers/domains/%s", testAccountID, testWorkerDomainID), func(w http.ResponseWriter, r *http.Request) {
+		assert.Equal(t, http.MethodDelete, r.Method, "Expected method 'DELETE', got %s", r.Method)
+		w.Header().Set("content-type", "application/json")
+		fmt.Fprintf(w, `{
+		  "success": true,
+		  "errors": [],
+		  "messages": [],
+		  "result": {
+			"id": "dbe10b4bc17c295377eabd600e1787fd",
+			"zone_id": "593c9c94de529bbbfaac7c53ced0447d",
+			"zone_name": "example.com",
+			"hostname": "foo.example.com",
+			"service": "foo",
+			"environment": "production"
+		  }
+		}`)
+	})
+	err := client.DetachWorkersDomain(context.Background(), AccountIdentifier(""), testWorkerDomainID)
+	if assert.Error(t, err) {
+		assert.Equal(t, ErrMissingAccountID, err)
+	}
+	err = client.DetachWorkersDomain(context.Background(), AccountIdentifier(testAccountID), testWorkerDomainID)
+	assert.NoError(t, err)
+}