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 +} 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) +}