From a8b5d0985f0c5ffa747d54033e10d6e90c01b790 Mon Sep 17 00:00:00 2001 From: Jacob Fowler Date: Wed, 14 Dec 2022 14:22:54 -0500 Subject: [PATCH] Revert "Merge pull request #1137 from jacobbednarz/modernise-workers" This reverts commit 3839b5e11e156d0f09fd8e51e541d321660023d7, reversing changes made to d7efda7b30549c48e7e69cf9b1b76c518138dfdc. --- .changelog/1137.txt | 39 -- errors.go | 5 +- workers.go | 791 ++++++++++++++++++++++++++++++---- workers_account_settings.go | 8 - workers_bindings.go | 466 -------------------- workers_bindings_test.go | 97 ----- workers_cron_triggers.go | 35 +- workers_cron_triggers_test.go | 8 +- workers_example_test.go | 206 +++++++++ workers_kv.go | 68 --- workers_kv_test.go | 26 +- workers_routes.go | 161 ------- workers_routes_test.go | 121 ------ workers_secrets.go | 58 +-- workers_secrets_test.go | 30 +- workers_tail.go | 7 +- workers_test.go | 728 ++++++++++++++++++++++++++----- 17 files changed, 1591 insertions(+), 1263 deletions(-) delete mode 100644 .changelog/1137.txt delete mode 100644 workers_bindings.go delete mode 100644 workers_bindings_test.go create mode 100644 workers_example_test.go delete mode 100644 workers_routes.go delete mode 100644 workers_routes_test.go diff --git a/.changelog/1137.txt b/.changelog/1137.txt deleted file mode 100644 index b479d14d44..0000000000 --- a/.changelog/1137.txt +++ /dev/null @@ -1,39 +0,0 @@ -```release-note:note -workers: all worker methods have been split into product ownership(-ish) files -``` - -```release-note:note -workers: all worker methods now require an explicit `ResourceContainer` for endpoints instead of relying on the globally defined `api.AccountID` -``` - -```release-note:breaking-change -workers: method signatures have been updated to align with the upcoming client conventions -``` - -```release-note:breaking-change -workers: API operations now target account level resources instead of older zone level resources (these are a 1:1 now) -``` - -```release-note:breaking-change -workers_bindings: method signatures have been updated to align with the upcoming client conventions -``` - -```release-note:breaking-change -workers_kv: method signatures have been updated to align with the upcoming client conventions -``` - -```release-note:breaking-change -workers_tails: method signatures have been updated to align with the upcoming client conventions -``` - -```release-note:breaking-change -workers_secrets: method signatures have been updated to align with the upcoming client conventions -``` - -```release-note:breaking-change -workers_routes: method signatures have been updated to align with the upcoming client conventions -``` - -```release-note:breaking-change -workers_cron_triggers: method signatures have been updated to align with the upcoming client conventions -``` diff --git a/errors.go b/errors.go index 723c18bae3..994707978e 100644 --- a/errors.go +++ b/errors.go @@ -29,8 +29,7 @@ const ( errAPIKeysAndTokensAreMutuallyExclusive = "API keys and tokens are mutually exclusive" //nolint:gosec errMissingCredentials = "no credentials provided" - errInvalidResourceContainerAccess = "requested resource container (%q) is not supported for this endpoint" - errRequiredAccountLevelResourceContainer = "this endpoint requires using an account level resource container and identifiers" + errInvalidResourceContainerAccess = "requested resource container (%q) is not supported for this endpoint" ) var ( @@ -41,8 +40,6 @@ var ( ErrAccountIDOrZoneIDAreRequired = errors.New(errMissingAccountOrZoneID) ErrAccountIDAndZoneIDAreMutuallyExclusive = errors.New(errAccountIDAndZoneIDAreMutuallyExclusive) ErrMissingResourceIdentifier = errors.New(errMissingResourceIdentifier) - - ErrRequiredAccountLevelResourceContainer = errors.New(errRequiredAccountLevelResourceContainer) ) type ErrorType string diff --git a/workers.go b/workers.go index d5474bc034..7bc6e9a42d 100644 --- a/workers.go +++ b/workers.go @@ -3,7 +3,10 @@ package cloudflare import ( "bytes" "context" + rand "crypto/rand" + "encoding/hex" "encoding/json" + "errors" "fmt" "io" "mime" @@ -20,22 +23,9 @@ type WorkerRequestParams struct { ScriptName string } -type CreateWorkerParams struct { - ScriptName string - Script string - - // Module changes the Content-Type header to specify the script is an - // ES Module syntax script. - Module bool - - // Bindings should be a map where the keys are the binding name, and the - // values are the binding content - Bindings map[string]WorkerBinding -} - // WorkerScriptParams provides a worker script and the associated bindings. type WorkerScriptParams struct { - ScriptName string + Script string // Module changes the Content-Type header to specify the script is an // ES Module syntax script. @@ -50,9 +40,10 @@ type WorkerScriptParams struct { // // API reference: https://api.cloudflare.com/#worker-routes-properties type WorkerRoute struct { - ID string `json:"id,omitempty"` - Pattern string `json:"pattern"` - ScriptName string `json:"script,omitempty"` + ID string `json:"id,omitempty"` + Pattern string `json:"pattern"` + Enabled bool `json:"enabled"` // this is deprecated: https://api.cloudflare.com/#worker-filters-deprecated--properties + Script string `json:"script,omitempty"` } // WorkerRoutesResponse embeds Response struct and slice of WorkerRoutes. @@ -86,7 +77,6 @@ type WorkerMetaData struct { // WorkerListResponse wrapper struct for API response to worker script list API call. type WorkerListResponse struct { Response - ResultInfo WorkerList []WorkerMetaData `json:"result"` } @@ -97,54 +87,375 @@ type WorkerScriptResponse struct { WorkerScript `json:"result"` } -type ListWorkersParams struct{} +// WorkerBindingType represents a particular type of binding. +type WorkerBindingType string + +func (b WorkerBindingType) String() string { + return string(b) +} + +const ( + // WorkerDurableObjectBindingType is the type for Durable Object bindings. + WorkerDurableObjectBindingType WorkerBindingType = "durable_object_namespace" + // WorkerInheritBindingType is the type for inherited bindings. + WorkerInheritBindingType WorkerBindingType = "inherit" + // WorkerKvNamespaceBindingType is the type for KV Namespace bindings. + WorkerKvNamespaceBindingType WorkerBindingType = "kv_namespace" + // WorkerWebAssemblyBindingType is the type for Web Assembly module bindings. + WorkerWebAssemblyBindingType WorkerBindingType = "wasm_module" + // WorkerSecretTextBindingType is the type for secret text bindings. + WorkerSecretTextBindingType WorkerBindingType = "secret_text" + // WorkerPlainTextBindingType is the type for plain text bindings. + WorkerPlainTextBindingType WorkerBindingType = "plain_text" + // WorkerServiceBindingType is the type for service bindings. + WorkerServiceBindingType WorkerBindingType = "service" + // WorkerR2BucketBindingType is the type for R2 bucket bindings. + WorkerR2BucketBindingType WorkerBindingType = "r2_bucket" + // WorkerAnalyticsEngineBindingType is the type for Analytics Engine dataset bindings. + WorkerAnalyticsEngineBindingType WorkerBindingType = "analytics_engine" +) + +// WorkerBindingListItem a struct representing an individual binding in a list of bindings. +type WorkerBindingListItem struct { + Name string `json:"name"` + Binding WorkerBinding +} + +// WorkerBindingListResponse wrapper struct for API response to worker binding list API call. +type WorkerBindingListResponse struct { + Response + BindingList []WorkerBindingListItem +} + +// Workers supports multiple types of bindings, e.g. KV namespaces or WebAssembly modules, and each type +// of binding will be represented differently in the upload request body. At a high-level, every binding +// will specify metadata, which is a JSON object with the properties "name" and "type". Some types of bindings +// will also have additional metadata properties. For example, KV bindings also specify the KV namespace. +// In addition to the metadata, some binding types may need to include additional data as part of the +// multipart form. For example, WebAssembly bindings will include the contents of the WebAssembly module. + +// WorkerBinding is the generic interface implemented by all of +// the various binding types. +type WorkerBinding interface { + Type() WorkerBindingType + + // serialize is responsible for returning the binding metadata as well as an optionally + // returning a function that can modify the multipart form body. For example, this is used + // by WebAssembly bindings to add a new part containing the WebAssembly module contents. + serialize(bindingName string) (workerBindingMeta, workerBindingBodyWriter, error) +} + +// workerBindingMeta is the metadata portion of the binding. +type workerBindingMeta = map[string]interface{} + +// workerBindingBodyWriter allows for a binding to add additional parts to the multipart body. +type workerBindingBodyWriter func(*multipart.Writer) error + +// WorkerInheritBinding will just persist whatever binding content was previously uploaded. +type WorkerInheritBinding struct { + // Optional parameter that allows for renaming a binding without changing + // its contents. If `OldName` is empty, the binding name will not be changed. + OldName string +} + +// Type returns the type of the binding. +func (b WorkerInheritBinding) Type() WorkerBindingType { + return WorkerInheritBindingType +} + +func (b WorkerInheritBinding) serialize(bindingName string) (workerBindingMeta, workerBindingBodyWriter, error) { + meta := workerBindingMeta{ + "name": bindingName, + "type": b.Type(), + } + + if b.OldName != "" { + meta["old_name"] = b.OldName + } + + return meta, nil, nil +} + +// WorkerKvNamespaceBinding is a binding to a Workers KV Namespace +// +// https://developers.cloudflare.com/workers/archive/api/resource-bindings/kv-namespaces/ +type WorkerKvNamespaceBinding struct { + NamespaceID string +} -type DeleteWorkerParams struct { +// Type returns the type of the binding. +func (b WorkerKvNamespaceBinding) Type() WorkerBindingType { + return WorkerKvNamespaceBindingType +} + +func (b WorkerKvNamespaceBinding) serialize(bindingName string) (workerBindingMeta, workerBindingBodyWriter, error) { + if b.NamespaceID == "" { + return nil, nil, fmt.Errorf(`NamespaceID for binding "%s" cannot be empty`, bindingName) + } + + return workerBindingMeta{ + "name": bindingName, + "type": b.Type(), + "namespace_id": b.NamespaceID, + }, nil, nil +} + +// WorkerDurableObjectBinding is a binding to a Workers Durable Object +// +// https://api.cloudflare.com/#durable-objects-namespace-properties +type WorkerDurableObjectBinding struct { + ClassName string ScriptName string } -// DeleteWorker deletes a single Worker. +// Type returns the type of the binding. +func (b WorkerDurableObjectBinding) Type() WorkerBindingType { + return WorkerDurableObjectBindingType +} + +func (b WorkerDurableObjectBinding) serialize(bindingName string) (workerBindingMeta, workerBindingBodyWriter, error) { + if b.ClassName == "" { + return nil, nil, fmt.Errorf(`ClassName for binding "%s" cannot be empty`, bindingName) + } + + return workerBindingMeta{ + "name": bindingName, + "type": b.Type(), + "class_name": b.ClassName, + "script_name": b.ScriptName, + }, nil, nil +} + +// WorkerWebAssemblyBinding is a binding to a WebAssembly module // -// API reference: https://api.cloudflare.com/#worker-script-delete-worker -func (api *API) DeleteWorker(ctx context.Context, rc *ResourceContainer, params DeleteWorkerParams) error { - if rc.Level != AccountRouteLevel { - return ErrRequiredAccountLevelResourceContainer +// https://developers.cloudflare.com/workers/archive/api/resource-bindings/webassembly-modules/ +type WorkerWebAssemblyBinding struct { + Module io.Reader +} + +// Type returns the type of the binding. +func (b WorkerWebAssemblyBinding) Type() WorkerBindingType { + return WorkerWebAssemblyBindingType +} + +func (b WorkerWebAssemblyBinding) serialize(bindingName string) (workerBindingMeta, workerBindingBodyWriter, error) { + partName := getRandomPartName() + + bodyWriter := func(mpw *multipart.Writer) error { + var hdr = textproto.MIMEHeader{} + hdr.Set("content-disposition", fmt.Sprintf(`form-data; name="%s"`, partName)) + hdr.Set("content-type", "application/wasm") + pw, err := mpw.CreatePart(hdr) + if err != nil { + return err + } + _, err = io.Copy(pw, b.Module) + return err } - if rc.Identifier == "" { - return ErrMissingAccountID + return workerBindingMeta{ + "name": bindingName, + "type": b.Type(), + "part": partName, + }, bodyWriter, nil +} + +// WorkerPlainTextBinding is a binding to plain text +// +// https://developers.cloudflare.com/workers/tooling/api/scripts/#add-a-plain-text-binding +type WorkerPlainTextBinding struct { + Text string +} + +// Type returns the type of the binding. +func (b WorkerPlainTextBinding) Type() WorkerBindingType { + return WorkerPlainTextBindingType +} + +func (b WorkerPlainTextBinding) serialize(bindingName string) (workerBindingMeta, workerBindingBodyWriter, error) { + if b.Text == "" { + return nil, nil, fmt.Errorf(`Text for binding "%s" cannot be empty`, bindingName) } - uri := fmt.Sprintf("/accounts/%s/workers/scripts/%s", rc.Identifier, params.ScriptName) - res, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil) + return workerBindingMeta{ + "name": bindingName, + "type": b.Type(), + "text": b.Text, + }, nil, nil +} +// WorkerSecretTextBinding is a binding to secret text +// +// https://developers.cloudflare.com/workers/tooling/api/scripts/#add-a-secret-text-binding +type WorkerSecretTextBinding struct { + Text string +} + +// Type returns the type of the binding. +func (b WorkerSecretTextBinding) Type() WorkerBindingType { + return WorkerSecretTextBindingType +} + +func (b WorkerSecretTextBinding) serialize(bindingName string) (workerBindingMeta, workerBindingBodyWriter, error) { + if b.Text == "" { + return nil, nil, fmt.Errorf(`Text for binding "%s" cannot be empty`, bindingName) + } + + return workerBindingMeta{ + "name": bindingName, + "type": b.Type(), + "text": b.Text, + }, nil, nil +} + +type WorkerServiceBinding struct { + Service string + Environment *string +} + +func (b WorkerServiceBinding) Type() WorkerBindingType { + return WorkerServiceBindingType +} + +func (b WorkerServiceBinding) serialize(bindingName string) (workerBindingMeta, workerBindingBodyWriter, error) { + if b.Service == "" { + return nil, nil, fmt.Errorf(`Service for binding "%s" cannot be empty`, bindingName) + } + + meta := workerBindingMeta{ + "name": bindingName, + "type": b.Type(), + "service": b.Service, + } + + if b.Environment != nil { + meta["environment"] = *b.Environment + } + + return meta, nil, nil +} + +// WorkerR2BucketBinding is a binding to an R2 bucket. +type WorkerR2BucketBinding struct { + BucketName string +} + +// Type returns the type of the binding. +func (b WorkerR2BucketBinding) Type() WorkerBindingType { + return WorkerR2BucketBindingType +} + +func (b WorkerR2BucketBinding) serialize(bindingName string) (workerBindingMeta, workerBindingBodyWriter, error) { + if b.BucketName == "" { + return nil, nil, fmt.Errorf(`BucketName for binding "%s" cannot be empty`, bindingName) + } + + return workerBindingMeta{ + "name": bindingName, + "type": b.Type(), + "bucket_name": b.BucketName, + }, nil, nil +} + +// WorkerAnalyticsEngineBinding is a binding to an Analytics Engine dataset. +type WorkerAnalyticsEngineBinding struct { + Dataset string +} + +// Type returns the type of the binding. +func (b WorkerAnalyticsEngineBinding) Type() WorkerBindingType { + return WorkerAnalyticsEngineBindingType +} + +func (b WorkerAnalyticsEngineBinding) serialize(bindingName string) (workerBindingMeta, workerBindingBodyWriter, error) { + if b.Dataset == "" { + return nil, nil, fmt.Errorf(`Dataset for binding "%s" cannot be empty`, bindingName) + } + + return workerBindingMeta{ + "name": bindingName, + "type": b.Type(), + "dataset": b.Dataset, + }, nil, nil +} + +// Each binding that adds a part to the multipart form body will need +// a unique part name so we just generate a random 128bit hex string. +func getRandomPartName() string { + randBytes := make([]byte, 16) + rand.Read(randBytes) //nolint:errcheck + return hex.EncodeToString(randBytes) +} + +// DeleteWorker deletes worker for a zone. +// +// API reference: https://api.cloudflare.com/#worker-script-delete-worker +func (api *API) DeleteWorker(ctx context.Context, requestParams *WorkerRequestParams) (WorkerScriptResponse, error) { + // if ScriptName is provided we will treat as org request + if requestParams.ScriptName != "" { + return api.deleteWorkerWithName(ctx, requestParams.ScriptName) + } + uri := fmt.Sprintf("/zones/%s/workers/script", requestParams.ZoneID) + res, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil) var r WorkerScriptResponse if err != nil { - return err + return r, err } - err = json.Unmarshal(res, &r) if err != nil { - return fmt.Errorf("%s: %w", errUnmarshalError, err) + return r, fmt.Errorf("%s: %w", errUnmarshalError, err) } - - return nil + return r, nil } -// GetWorker fetch raw script content for your worker returns string containing -// worker code js. +// DeleteWorkerWithName deletes worker for a zone. +// Sccount must be specified as api option https://godoc.org/github.com/cloudflare/cloudflare-go#UsingAccount // // API reference: https://developers.cloudflare.com/workers/tooling/api/scripts/ -func (api *API) GetWorker(ctx context.Context, rc *ResourceContainer, scriptName string) (WorkerScriptResponse, error) { - if rc.Level != AccountRouteLevel { - return WorkerScriptResponse{}, ErrRequiredAccountLevelResourceContainer +func (api *API) deleteWorkerWithName(ctx context.Context, scriptName string) (WorkerScriptResponse, error) { + if api.AccountID == "" { + return WorkerScriptResponse{}, errors.New("account ID required") } + uri := fmt.Sprintf("/accounts/%s/workers/scripts/%s", api.AccountID, scriptName) + res, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil) + var r WorkerScriptResponse + if err != nil { + return r, err + } + err = json.Unmarshal(res, &r) + if err != nil { + return r, fmt.Errorf("%s: %w", errUnmarshalError, err) + } + return r, nil +} - if rc.Identifier == "" { - return WorkerScriptResponse{}, ErrMissingAccountID +// DownloadWorker fetch raw script content for your worker returns []byte containing worker code js +// +// API reference: https://api.cloudflare.com/#worker-script-download-worker +func (api *API) DownloadWorker(ctx context.Context, requestParams *WorkerRequestParams) (WorkerScriptResponse, error) { + if requestParams.ScriptName != "" { + return api.downloadWorkerWithName(ctx, requestParams.ScriptName) } + uri := fmt.Sprintf("/zones/%s/workers/script", requestParams.ZoneID) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + var r WorkerScriptResponse + if err != nil { + return r, err + } + r.Script = string(res) + r.Module = false + r.Success = true + return r, nil +} - uri := fmt.Sprintf("/accounts/%s/workers/scripts/%s", rc.Identifier, scriptName) +// DownloadWorkerWithName fetch raw script content for your worker returns string containing worker code js +// +// API reference: https://developers.cloudflare.com/workers/tooling/api/scripts/ +func (api *API) downloadWorkerWithName(ctx context.Context, scriptName string) (WorkerScriptResponse, error) { + if api.AccountID == "" { + return WorkerScriptResponse{}, errors.New("account ID required") + } + uri := fmt.Sprintf("/accounts/%s/workers/scripts/%s", api.AccountID, scriptName) res, err := api.makeRequestContextWithHeadersComplete(ctx, http.MethodGet, uri, nil, nil) var r WorkerScriptResponse if err != nil { @@ -175,78 +486,233 @@ func (api *API) GetWorker(ctx context.Context, rc *ResourceContainer, scriptName return r, nil } -// ListWorkers returns list of Workers for given account. -// -// API reference: https://developers.cloudflare.com/workers/tooling/api/scripts/ -func (api *API) ListWorkers(ctx context.Context, rc *ResourceContainer, params ListWorkersParams) (WorkerListResponse, *ResultInfo, error) { - if rc.Level != AccountRouteLevel { - return WorkerListResponse{}, &ResultInfo{}, ErrRequiredAccountLevelResourceContainer +// ListWorkerBindings returns all the bindings for a particular worker. +func (api *API) ListWorkerBindings(ctx context.Context, requestParams *WorkerRequestParams) (WorkerBindingListResponse, error) { + if requestParams.ScriptName == "" { + return WorkerBindingListResponse{}, errors.New("ScriptName is required") } - - if rc.Identifier == "" { - return WorkerListResponse{}, &ResultInfo{}, ErrMissingAccountID + if api.AccountID == "" { + return WorkerBindingListResponse{}, errors.New("account ID required") } - uri := fmt.Sprintf("/accounts/%s/workers/scripts", rc.Identifier) + uri := fmt.Sprintf("/accounts/%s/workers/scripts/%s/bindings", api.AccountID, requestParams.ScriptName) + + var jsonRes struct { + Response + Bindings []workerBindingMeta `json:"result"` + } + var r WorkerBindingListResponse res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) if err != nil { - return WorkerListResponse{}, &ResultInfo{}, err + return r, err + } + err = json.Unmarshal(res, &jsonRes) + if err != nil { + return r, fmt.Errorf("%s: %w", errUnmarshalError, err) + } + + r = WorkerBindingListResponse{ + Response: jsonRes.Response, + BindingList: make([]WorkerBindingListItem, 0, len(jsonRes.Bindings)), + } + for _, jsonBinding := range jsonRes.Bindings { + name, ok := jsonBinding["name"].(string) + if !ok { + return r, fmt.Errorf("Binding missing name %v", jsonBinding) + } + bType, ok := jsonBinding["type"].(string) + if !ok { + return r, fmt.Errorf("Binding missing type %v", jsonBinding) + } + bindingListItem := WorkerBindingListItem{ + Name: name, + } + + switch WorkerBindingType(bType) { + case WorkerDurableObjectBindingType: + class_name := jsonBinding["class_name"].(string) + script_name := jsonBinding["script_name"].(string) + bindingListItem.Binding = WorkerDurableObjectBinding{ + ClassName: class_name, + ScriptName: script_name, + } + case WorkerKvNamespaceBindingType: + namespaceID := jsonBinding["namespace_id"].(string) + bindingListItem.Binding = WorkerKvNamespaceBinding{ + NamespaceID: namespaceID, + } + case WorkerWebAssemblyBindingType: + bindingListItem.Binding = WorkerWebAssemblyBinding{ + Module: &bindingContentReader{ + ctx: ctx, + api: api, + requestParams: requestParams, + bindingName: name, + }, + } + case WorkerPlainTextBindingType: + text := jsonBinding["text"].(string) + bindingListItem.Binding = WorkerPlainTextBinding{ + Text: text, + } + case WorkerServiceBindingType: + service := jsonBinding["service"].(string) + environment := jsonBinding["environment"].(string) + bindingListItem.Binding = WorkerServiceBinding{ + Service: service, + Environment: &environment, + } + case WorkerSecretTextBindingType: + bindingListItem.Binding = WorkerSecretTextBinding{} + case WorkerR2BucketBindingType: + bucketName := jsonBinding["bucket_name"].(string) + bindingListItem.Binding = WorkerR2BucketBinding{ + BucketName: bucketName, + } + case WorkerAnalyticsEngineBindingType: + dataset := jsonBinding["dataset"].(string) + bindingListItem.Binding = WorkerAnalyticsEngineBinding{ + Dataset: dataset, + } + default: + bindingListItem.Binding = WorkerInheritBinding{} + } + r.BindingList = append(r.BindingList, bindingListItem) + } + + return r, nil +} + +// bindingContentReader is an io.Reader that will lazily load the +// raw bytes for a binding from the API when the Read() method +// is first called. This is only useful for binding types +// that store raw bytes, like WebAssembly modules. +type bindingContentReader struct { + api *API + requestParams *WorkerRequestParams + ctx context.Context + bindingName string + content []byte + position int +} + +func (b *bindingContentReader) Read(p []byte) (n int, err error) { + // Lazily load the content when Read() is first called + if b.content == nil { + uri := fmt.Sprintf("/accounts/%s/workers/scripts/%s/bindings/%s/content", b.api.AccountID, b.requestParams.ScriptName, b.bindingName) + res, err := b.api.makeRequestContext(b.ctx, http.MethodGet, uri, nil) + if err != nil { + return 0, err + } + b.content = res + } + + if b.position >= len(b.content) { + return 0, io.EOF } + bytesRemaining := len(b.content) - b.position + bytesToProcess := 0 + if len(p) < bytesRemaining { + bytesToProcess = len(p) + } else { + bytesToProcess = bytesRemaining + } + + for i := 0; i < bytesToProcess; i++ { + p[i] = b.content[b.position] + b.position = b.position + 1 + } + + return bytesToProcess, nil +} + +// ListWorkerScripts returns list of worker scripts for given account. +// +// API reference: https://developers.cloudflare.com/workers/tooling/api/scripts/ +func (api *API) ListWorkerScripts(ctx context.Context) (WorkerListResponse, error) { + if api.AccountID == "" { + return WorkerListResponse{}, errors.New("account ID required") + } + uri := fmt.Sprintf("/accounts/%s/workers/scripts", api.AccountID) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return WorkerListResponse{}, err + } var r WorkerListResponse err = json.Unmarshal(res, &r) if err != nil { - return WorkerListResponse{}, &ResultInfo{}, fmt.Errorf("%s: %w", errUnmarshalError, err) + return WorkerListResponse{}, fmt.Errorf("%s: %w", errUnmarshalError, err) } - - return r, &r.ResultInfo, nil + return r, nil } -// UploadWorker pushes raw script content for your Worker. +// UploadWorker push raw script content for your worker. // // API reference: https://api.cloudflare.com/#worker-script-upload-worker -func (api *API) UploadWorker(ctx context.Context, rc *ResourceContainer, params CreateWorkerParams) (WorkerScriptResponse, error) { - if rc.Level != AccountRouteLevel { - return WorkerScriptResponse{}, ErrRequiredAccountLevelResourceContainer +func (api *API) UploadWorker(ctx context.Context, requestParams *WorkerRequestParams, params *WorkerScriptParams) (WorkerScriptResponse, error) { + if params.Module { + return api.UploadWorkerWithBindings(ctx, requestParams, params) } - if rc.Identifier == "" { - return WorkerScriptResponse{}, ErrMissingAccountID + contentType := "application/javascript" + if requestParams.ScriptName != "" { + return api.uploadWorkerWithName(ctx, requestParams.ScriptName, contentType, []byte(params.Script)) } + return api.uploadWorkerForZone(ctx, requestParams.ZoneID, contentType, []byte(params.Script)) +} - var ( - contentType = "application/javascript" - err error - body []byte - ) - - if params.Module || len(params.Bindings) > 0 { - contentType, body, err = formatMultipartBody(params) - if err != nil { - return WorkerScriptResponse{}, err - } +// UploadWorkerWithBindings push raw script content and bindings for your worker +// +// API reference: https://api.cloudflare.com/#worker-script-upload-worker +func (api *API) UploadWorkerWithBindings(ctx context.Context, requestParams *WorkerRequestParams, data *WorkerScriptParams) (WorkerScriptResponse, error) { + contentType, body, err := formatMultipartBody(data) + if err != nil { + return WorkerScriptResponse{}, err } + if requestParams.ScriptName != "" { + return api.uploadWorkerWithName(ctx, requestParams.ScriptName, contentType, body) + } + return api.uploadWorkerForZone(ctx, requestParams.ZoneID, contentType, body) +} - uri := fmt.Sprintf("/accounts/%s/workers/scripts/%s", rc.Identifier, params.ScriptName) +func (api *API) uploadWorkerForZone(ctx context.Context, zoneID, contentType string, body []byte) (WorkerScriptResponse, error) { + uri := fmt.Sprintf("/zones/%s/workers/script", zoneID) headers := make(http.Header) headers.Set("Content-Type", contentType) res, err := api.makeRequestContextWithHeaders(ctx, http.MethodPut, uri, body, headers) - var r WorkerScriptResponse if err != nil { return r, err } - err = json.Unmarshal(res, &r) if err != nil { return r, fmt.Errorf("%s: %w", errUnmarshalError, err) } + return r, nil +} +func (api *API) uploadWorkerWithName(ctx context.Context, scriptName, contentType string, body []byte) (WorkerScriptResponse, error) { + if api.AccountID == "" { + return WorkerScriptResponse{}, errors.New("account ID required") + } + uri := fmt.Sprintf("/accounts/%s/workers/scripts/%s", api.AccountID, scriptName) + headers := make(http.Header) + headers.Set("Content-Type", contentType) + res, err := api.makeRequestContextWithHeaders(ctx, http.MethodPut, uri, body, headers) + var r WorkerScriptResponse + if err != nil { + return r, err + } + err = json.Unmarshal(res, &r) + if err != nil { + return r, fmt.Errorf("%s: %w", errUnmarshalError, err) + } return r, nil } // Returns content-type, body, error. -func formatMultipartBody(params CreateWorkerParams) (string, []byte, error) { +func formatMultipartBody(params *WorkerScriptParams) (string, []byte, error) { var buf = &bytes.Buffer{} var mpw = multipart.NewWriter(buf) defer mpw.Close() @@ -331,3 +797,174 @@ func formatMultipartBody(params CreateWorkerParams) (string, []byte, error) { return mpw.FormDataContentType(), buf.Bytes(), nil } + +// CreateWorkerRoute creates worker route for a zone +// +// API reference: https://api.cloudflare.com/#worker-filters-create-filter, https://api.cloudflare.com/#worker-routes-create-route +func (api *API) CreateWorkerRoute(ctx context.Context, zoneID string, route WorkerRoute) (WorkerRouteResponse, error) { + pathComponent, err := getRouteEndpoint(route) + if err != nil { + return WorkerRouteResponse{}, err + } + + uri := fmt.Sprintf("/zones/%s/workers/%s", zoneID, pathComponent) + res, err := api.makeRequestContext(ctx, http.MethodPost, uri, route) + if err != nil { + return WorkerRouteResponse{}, err + } + var r WorkerRouteResponse + err = json.Unmarshal(res, &r) + if err != nil { + return WorkerRouteResponse{}, fmt.Errorf("%s: %w", errUnmarshalError, err) + } + return r, nil +} + +// DeleteWorkerRoute deletes worker route for a zone +// +// API reference: https://api.cloudflare.com/#worker-routes-delete-route +func (api *API) DeleteWorkerRoute(ctx context.Context, zoneID string, routeID string) (WorkerRouteResponse, error) { + uri := fmt.Sprintf("/zones/%s/workers/routes/%s", zoneID, routeID) + res, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil) + if err != nil { + return WorkerRouteResponse{}, err + } + var r WorkerRouteResponse + err = json.Unmarshal(res, &r) + if err != nil { + return WorkerRouteResponse{}, fmt.Errorf("%s: %w", errUnmarshalError, err) + } + return r, nil +} + +// ListWorkerRoutes returns list of worker routes +// +// API reference: https://api.cloudflare.com/#worker-filters-list-filters, https://api.cloudflare.com/#worker-routes-list-routes +func (api *API) ListWorkerRoutes(ctx context.Context, zoneID string) (WorkerRoutesResponse, error) { + pathComponent := "filters" + // Unfortunately we don't have a good signal of whether the user is wanting + // to use the deprecated filters endpoint (https://api.cloudflare.com/#worker-filters-list-filters) + // or the multi-script routes endpoint (https://api.cloudflare.com/#worker-script-list-workers) + // + // The filters endpoint does not support API tokens, so if an API token is specified we need to use + // the routes endpoint. Otherwise, since the multi-script API endpoints that operate on a script + // require an AccountID, we assume that anyone specifying an AccountID is using the routes endpoint. + // This is likely too presumptuous. In the next major version, we should just remove the deprecated + // filter endpoints entirely to avoid this ambiguity. + if api.AccountID != "" || api.APIToken != "" { + pathComponent = "routes" + } + uri := fmt.Sprintf("/zones/%s/workers/%s", zoneID, pathComponent) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return WorkerRoutesResponse{}, err + } + var r WorkerRoutesResponse + err = json.Unmarshal(res, &r) + if err != nil { + return WorkerRoutesResponse{}, fmt.Errorf("%s: %w", errUnmarshalError, err) + } + for i := range r.Routes { + route := &r.Routes[i] + // The Enabled flag will not be set in the multi-script API response + // so we manually set it to true if the script name is not empty + // in case any multi-script customers rely on the Enabled field + if route.Script != "" { + route.Enabled = true + } + } + return r, nil +} + +// GetWorkerRoute returns a worker route. +// +// API reference: https://api.cloudflare.com/#worker-routes-get-route +func (api *API) GetWorkerRoute(ctx context.Context, zoneID string, routeID string) (WorkerRouteResponse, error) { + uri := fmt.Sprintf("/zones/%s/workers/routes/%s", zoneID, routeID) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return WorkerRouteResponse{}, err + } + var r WorkerRouteResponse + err = json.Unmarshal(res, &r) + if err != nil { + return WorkerRouteResponse{}, fmt.Errorf("%s: %w", errUnmarshalError, err) + } + return r, nil +} + +// UpdateWorkerRoute updates worker route for a zone. +// +// API reference: https://api.cloudflare.com/#worker-filters-update-filter, https://api.cloudflare.com/#worker-routes-update-route +func (api *API) UpdateWorkerRoute(ctx context.Context, zoneID string, routeID string, route WorkerRoute) (WorkerRouteResponse, error) { + pathComponent, err := getRouteEndpoint(route) + if err != nil { + return WorkerRouteResponse{}, err + } + uri := fmt.Sprintf("/zones/%s/workers/%s/%s", zoneID, pathComponent, routeID) + res, err := api.makeRequestContext(ctx, http.MethodPut, uri, route) + if err != nil { + return WorkerRouteResponse{}, err + } + var r WorkerRouteResponse + err = json.Unmarshal(res, &r) + if err != nil { + return WorkerRouteResponse{}, fmt.Errorf("%s: %w", errUnmarshalError, err) + } + return r, nil +} + +func getRouteEndpoint(route WorkerRoute) (string, error) { + if route.Script != "" && route.Enabled { + return "", errors.New("Only `Script` or `Enabled` may be specified for a WorkerRoute, not both") + } + + // For backwards-compatibility, fallback to the deprecated filter + // endpoint if Enabled == true + // https://api.cloudflare.com/#worker-filters-deprecated--properties + if route.Enabled { + return "filters", nil + } + + return "routes", nil +} + +type WorkerDomainParams struct { + ZoneID string `json:"zone_id"` + Hostname string `json:"hostname"` + Service string `json:"service"` + Environment string `json:"environment,omitempty"` +} + +type WorkerDomainResult struct { + ID string `json:"id"` + ZoneID string `json:"zone_id"` + ZoneName string `json:"zone_name"` + Hostname string `json:"hostname"` + Service string `json:"service"` + Environment string `json:"environment"` +} + +type WorkerDomainResponse struct { + Response + WorkerDomainResult `json:"result"` +} + +// AttachWorkerToDomain attaches a worker to a zone and hostname +// +// API reference: https://api.cloudflare.com/#worker-domain-attach-to-domain +func (api *API) AttachWorkerToDomain(ctx context.Context, rc *ResourceContainer, params *WorkerDomainParams) (WorkerDomainResponse, error) { + uri := fmt.Sprintf("/accounts/%s/workers/domains", rc.Identifier) + res, err := api.makeRequestContext(ctx, http.MethodPut, uri, params) + if err != nil { + return WorkerDomainResponse{}, err + } + + var r WorkerDomainResponse + err = json.Unmarshal(res, &r) + if err != nil { + return WorkerDomainResponse{}, fmt.Errorf("%s: %w", errUnmarshalError, err) + } + + return r, nil +} diff --git a/workers_account_settings.go b/workers_account_settings.go index 6b3b65d6df..d8e58516a0 100644 --- a/workers_account_settings.go +++ b/workers_account_settings.go @@ -37,10 +37,6 @@ func (api *API) CreateWorkersAccountSettings(ctx context.Context, rc *ResourceCo return WorkersAccountSettings{}, ErrMissingAccountID } - if rc.Level != AccountRouteLevel { - return WorkersAccountSettings{}, ErrRequiredAccountLevelResourceContainer - } - uri := fmt.Sprintf("/accounts/%s/workers/account-settings", rc.Identifier) res, err := api.makeRequestContext(ctx, http.MethodPut, uri, params) if err != nil { @@ -63,10 +59,6 @@ func (api *API) WorkersAccountSettings(ctx context.Context, rc *ResourceContaine return WorkersAccountSettings{}, ErrMissingAccountID } - if rc.Level != AccountRouteLevel { - return WorkersAccountSettings{}, ErrRequiredAccountLevelResourceContainer - } - uri := fmt.Sprintf("/accounts/%s/workers/account-settings", rc.Identifier) res, err := api.makeRequestContext(ctx, http.MethodGet, uri, params) if err != nil { diff --git a/workers_bindings.go b/workers_bindings.go deleted file mode 100644 index 9631065892..0000000000 --- a/workers_bindings.go +++ /dev/null @@ -1,466 +0,0 @@ -package cloudflare - -import ( - "context" - rand "crypto/rand" - "encoding/hex" - "encoding/json" - "errors" - "fmt" - "io" - "mime/multipart" - "net/http" - "net/textproto" -) - -// WorkerBindingType represents a particular type of binding. -type WorkerBindingType string - -func (b WorkerBindingType) String() string { - return string(b) -} - -const ( - // WorkerDurableObjectBindingType is the type for Durable Object bindings. - WorkerDurableObjectBindingType WorkerBindingType = "durable_object_namespace" - // WorkerInheritBindingType is the type for inherited bindings. - WorkerInheritBindingType WorkerBindingType = "inherit" - // WorkerKvNamespaceBindingType is the type for KV Namespace bindings. - WorkerKvNamespaceBindingType WorkerBindingType = "kv_namespace" - // WorkerWebAssemblyBindingType is the type for Web Assembly module bindings. - WorkerWebAssemblyBindingType WorkerBindingType = "wasm_module" - // WorkerSecretTextBindingType is the type for secret text bindings. - WorkerSecretTextBindingType WorkerBindingType = "secret_text" - // WorkerPlainTextBindingType is the type for plain text bindings. - WorkerPlainTextBindingType WorkerBindingType = "plain_text" - // WorkerServiceBindingType is the type for service bindings. - WorkerServiceBindingType WorkerBindingType = "service" - // WorkerR2BucketBindingType is the type for R2 bucket bindings. - WorkerR2BucketBindingType WorkerBindingType = "r2_bucket" - // WorkerAnalyticsEngineBindingType is the type for Analytics Engine dataset bindings. - WorkerAnalyticsEngineBindingType WorkerBindingType = "analytics_engine" -) - -type ListWorkerBindingsParams struct { - ScriptName string -} - -// WorkerBindingListItem a struct representing an individual binding in a list of bindings. -type WorkerBindingListItem struct { - Name string `json:"name"` - Binding WorkerBinding -} - -// WorkerBindingListResponse wrapper struct for API response to worker binding list API call. -type WorkerBindingListResponse struct { - Response - BindingList []WorkerBindingListItem -} - -// Workers supports multiple types of bindings, e.g. KV namespaces or WebAssembly modules, and each type -// of binding will be represented differently in the upload request body. At a high-level, every binding -// will specify metadata, which is a JSON object with the properties "name" and "type". Some types of bindings -// will also have additional metadata properties. For example, KV bindings also specify the KV namespace. -// In addition to the metadata, some binding types may need to include additional data as part of the -// multipart form. For example, WebAssembly bindings will include the contents of the WebAssembly module. - -// WorkerBinding is the generic interface implemented by all of -// the various binding types. -type WorkerBinding interface { - Type() WorkerBindingType - - // serialize is responsible for returning the binding metadata as well as an optionally - // returning a function that can modify the multipart form body. For example, this is used - // by WebAssembly bindings to add a new part containing the WebAssembly module contents. - serialize(bindingName string) (workerBindingMeta, workerBindingBodyWriter, error) -} - -// workerBindingMeta is the metadata portion of the binding. -type workerBindingMeta = map[string]interface{} - -// workerBindingBodyWriter allows for a binding to add additional parts to the multipart body. -type workerBindingBodyWriter func(*multipart.Writer) error - -// WorkerInheritBinding will just persist whatever binding content was previously uploaded. -type WorkerInheritBinding struct { - // Optional parameter that allows for renaming a binding without changing - // its contents. If `OldName` is empty, the binding name will not be changed. - OldName string -} - -// Type returns the type of the binding. -func (b WorkerInheritBinding) Type() WorkerBindingType { - return WorkerInheritBindingType -} - -func (b WorkerInheritBinding) serialize(bindingName string) (workerBindingMeta, workerBindingBodyWriter, error) { - meta := workerBindingMeta{ - "name": bindingName, - "type": b.Type(), - } - - if b.OldName != "" { - meta["old_name"] = b.OldName - } - - return meta, nil, nil -} - -// WorkerKvNamespaceBinding is a binding to a Workers KV Namespace. -// -// https://developers.cloudflare.com/workers/archive/api/resource-bindings/kv-namespaces/ -type WorkerKvNamespaceBinding struct { - NamespaceID string -} - -// Type returns the type of the binding. -func (b WorkerKvNamespaceBinding) Type() WorkerBindingType { - return WorkerKvNamespaceBindingType -} - -func (b WorkerKvNamespaceBinding) serialize(bindingName string) (workerBindingMeta, workerBindingBodyWriter, error) { - if b.NamespaceID == "" { - return nil, nil, fmt.Errorf(`NamespaceID for binding "%s" cannot be empty`, bindingName) - } - - return workerBindingMeta{ - "name": bindingName, - "type": b.Type(), - "namespace_id": b.NamespaceID, - }, nil, nil -} - -// WorkerDurableObjectBinding is a binding to a Workers Durable Object. -// -// https://api.cloudflare.com/#durable-objects-namespace-properties -type WorkerDurableObjectBinding struct { - ClassName string - ScriptName string -} - -// Type returns the type of the binding. -func (b WorkerDurableObjectBinding) Type() WorkerBindingType { - return WorkerDurableObjectBindingType -} - -func (b WorkerDurableObjectBinding) serialize(bindingName string) (workerBindingMeta, workerBindingBodyWriter, error) { - if b.ClassName == "" { - return nil, nil, fmt.Errorf(`ClassName for binding "%s" cannot be empty`, bindingName) - } - - return workerBindingMeta{ - "name": bindingName, - "type": b.Type(), - "class_name": b.ClassName, - "script_name": b.ScriptName, - }, nil, nil -} - -// WorkerWebAssemblyBinding is a binding to a WebAssembly module. -// -// https://developers.cloudflare.com/workers/archive/api/resource-bindings/webassembly-modules/ -type WorkerWebAssemblyBinding struct { - Module io.Reader -} - -// Type returns the type of the binding. -func (b WorkerWebAssemblyBinding) Type() WorkerBindingType { - return WorkerWebAssemblyBindingType -} - -func (b WorkerWebAssemblyBinding) serialize(bindingName string) (workerBindingMeta, workerBindingBodyWriter, error) { - partName := getRandomPartName() - - bodyWriter := func(mpw *multipart.Writer) error { - var hdr = textproto.MIMEHeader{} - hdr.Set("content-disposition", fmt.Sprintf(`form-data; name="%s"`, partName)) - hdr.Set("content-type", "application/wasm") - pw, err := mpw.CreatePart(hdr) - if err != nil { - return err - } - _, err = io.Copy(pw, b.Module) - return err - } - - return workerBindingMeta{ - "name": bindingName, - "type": b.Type(), - "part": partName, - }, bodyWriter, nil -} - -// WorkerPlainTextBinding is a binding to plain text. -// -// https://developers.cloudflare.com/workers/tooling/api/scripts/#add-a-plain-text-binding -type WorkerPlainTextBinding struct { - Text string -} - -// Type returns the type of the binding. -func (b WorkerPlainTextBinding) Type() WorkerBindingType { - return WorkerPlainTextBindingType -} - -func (b WorkerPlainTextBinding) serialize(bindingName string) (workerBindingMeta, workerBindingBodyWriter, error) { - if b.Text == "" { - return nil, nil, fmt.Errorf(`Text for binding "%s" cannot be empty`, bindingName) - } - - return workerBindingMeta{ - "name": bindingName, - "type": b.Type(), - "text": b.Text, - }, nil, nil -} - -// WorkerSecretTextBinding is a binding to secret text. -// -// https://developers.cloudflare.com/workers/tooling/api/scripts/#add-a-secret-text-binding -type WorkerSecretTextBinding struct { - Text string -} - -// Type returns the type of the binding. -func (b WorkerSecretTextBinding) Type() WorkerBindingType { - return WorkerSecretTextBindingType -} - -func (b WorkerSecretTextBinding) serialize(bindingName string) (workerBindingMeta, workerBindingBodyWriter, error) { - if b.Text == "" { - return nil, nil, fmt.Errorf(`Text for binding "%s" cannot be empty`, bindingName) - } - - return workerBindingMeta{ - "name": bindingName, - "type": b.Type(), - "text": b.Text, - }, nil, nil -} - -type WorkerServiceBinding struct { - Service string - Environment *string -} - -func (b WorkerServiceBinding) Type() WorkerBindingType { - return WorkerServiceBindingType -} - -func (b WorkerServiceBinding) serialize(bindingName string) (workerBindingMeta, workerBindingBodyWriter, error) { - if b.Service == "" { - return nil, nil, fmt.Errorf(`Service for binding "%s" cannot be empty`, bindingName) - } - - meta := workerBindingMeta{ - "name": bindingName, - "type": b.Type(), - "service": b.Service, - } - - if b.Environment != nil { - meta["environment"] = *b.Environment - } - - return meta, nil, nil -} - -// WorkerR2BucketBinding is a binding to an R2 bucket. -type WorkerR2BucketBinding struct { - BucketName string -} - -// Type returns the type of the binding. -func (b WorkerR2BucketBinding) Type() WorkerBindingType { - return WorkerR2BucketBindingType -} - -func (b WorkerR2BucketBinding) serialize(bindingName string) (workerBindingMeta, workerBindingBodyWriter, error) { - if b.BucketName == "" { - return nil, nil, fmt.Errorf(`BucketName for binding "%s" cannot be empty`, bindingName) - } - - return workerBindingMeta{ - "name": bindingName, - "type": b.Type(), - "bucket_name": b.BucketName, - }, nil, nil -} - -// WorkerAnalyticsEngineBinding is a binding to an Analytics Engine dataset. -type WorkerAnalyticsEngineBinding struct { - Dataset string -} - -// Type returns the type of the binding. -func (b WorkerAnalyticsEngineBinding) Type() WorkerBindingType { - return WorkerAnalyticsEngineBindingType -} - -func (b WorkerAnalyticsEngineBinding) serialize(bindingName string) (workerBindingMeta, workerBindingBodyWriter, error) { - if b.Dataset == "" { - return nil, nil, fmt.Errorf(`Dataset for binding "%s" cannot be empty`, bindingName) - } - - return workerBindingMeta{ - "name": bindingName, - "type": b.Type(), - "dataset": b.Dataset, - }, nil, nil -} - -// Each binding that adds a part to the multipart form body will need -// a unique part name so we just generate a random 128bit hex string. -func getRandomPartName() string { - randBytes := make([]byte, 16) - rand.Read(randBytes) //nolint:errcheck - return hex.EncodeToString(randBytes) -} - -// ListWorkerBindings returns all the bindings for a particular worker. -func (api *API) ListWorkerBindings(ctx context.Context, rc *ResourceContainer, params ListWorkerBindingsParams) (WorkerBindingListResponse, error) { - if params.ScriptName == "" { - return WorkerBindingListResponse{}, errors.New("ScriptName is required") - } - - if rc.Level != AccountRouteLevel { - return WorkerBindingListResponse{}, ErrRequiredAccountLevelResourceContainer - } - - if rc.Identifier == "" { - return WorkerBindingListResponse{}, ErrMissingAccountID - } - - uri := fmt.Sprintf("/accounts/%s/workers/scripts/%s/bindings", rc.Identifier, params.ScriptName) - - var jsonRes struct { - Response - Bindings []workerBindingMeta `json:"result"` - } - var r WorkerBindingListResponse - res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) - if err != nil { - return r, err - } - err = json.Unmarshal(res, &jsonRes) - if err != nil { - return r, fmt.Errorf("%s: %w", errUnmarshalError, err) - } - - r = WorkerBindingListResponse{ - Response: jsonRes.Response, - BindingList: make([]WorkerBindingListItem, 0, len(jsonRes.Bindings)), - } - for _, jsonBinding := range jsonRes.Bindings { - name, ok := jsonBinding["name"].(string) - if !ok { - return r, fmt.Errorf("Binding missing name %v", jsonBinding) - } - bType, ok := jsonBinding["type"].(string) - if !ok { - return r, fmt.Errorf("Binding missing type %v", jsonBinding) - } - bindingListItem := WorkerBindingListItem{ - Name: name, - } - - switch WorkerBindingType(bType) { - case WorkerDurableObjectBindingType: - class_name := jsonBinding["class_name"].(string) - script_name := jsonBinding["script_name"].(string) - bindingListItem.Binding = WorkerDurableObjectBinding{ - ClassName: class_name, - ScriptName: script_name, - } - case WorkerKvNamespaceBindingType: - namespaceID := jsonBinding["namespace_id"].(string) - bindingListItem.Binding = WorkerKvNamespaceBinding{ - NamespaceID: namespaceID, - } - case WorkerWebAssemblyBindingType: - bindingListItem.Binding = WorkerWebAssemblyBinding{ - Module: &bindingContentReader{ - api: api, - ctx: ctx, - accountID: rc.Identifier, - params: ¶ms, - bindingName: name, - }, - } - case WorkerPlainTextBindingType: - text := jsonBinding["text"].(string) - bindingListItem.Binding = WorkerPlainTextBinding{ - Text: text, - } - case WorkerServiceBindingType: - service := jsonBinding["service"].(string) - environment := jsonBinding["environment"].(string) - bindingListItem.Binding = WorkerServiceBinding{ - Service: service, - Environment: &environment, - } - case WorkerSecretTextBindingType: - bindingListItem.Binding = WorkerSecretTextBinding{} - case WorkerR2BucketBindingType: - bucketName := jsonBinding["bucket_name"].(string) - bindingListItem.Binding = WorkerR2BucketBinding{ - BucketName: bucketName, - } - case WorkerAnalyticsEngineBindingType: - dataset := jsonBinding["dataset"].(string) - bindingListItem.Binding = WorkerAnalyticsEngineBinding{ - Dataset: dataset, - } - default: - bindingListItem.Binding = WorkerInheritBinding{} - } - r.BindingList = append(r.BindingList, bindingListItem) - } - - return r, nil -} - -// bindingContentReader is an io.Reader that will lazily load the -// raw bytes for a binding from the API when the Read() method -// is first called. This is only useful for binding types -// that store raw bytes, like WebAssembly modules. -type bindingContentReader struct { - api *API - accountID string - params *ListWorkerBindingsParams - ctx context.Context - bindingName string - content []byte - position int -} - -func (b *bindingContentReader) Read(p []byte) (n int, err error) { - // Lazily load the content when Read() is first called - if b.content == nil { - uri := fmt.Sprintf("/accounts/%s/workers/scripts/%s/bindings/%s/content", b.accountID, b.params.ScriptName, b.bindingName) - res, err := b.api.makeRequestContext(b.ctx, http.MethodGet, uri, nil) - if err != nil { - return 0, err - } - b.content = res - } - - if b.position >= len(b.content) { - return 0, io.EOF - } - - bytesRemaining := len(b.content) - b.position - bytesToProcess := 0 - if len(p) < bytesRemaining { - bytesToProcess = len(p) - } else { - bytesToProcess = bytesRemaining - } - - for i := 0; i < bytesToProcess; i++ { - p[i] = b.content[b.position] - b.position = b.position + 1 - } - - return bytesToProcess, nil -} diff --git a/workers_bindings_test.go b/workers_bindings_test.go deleted file mode 100644 index f8dea25ad3..0000000000 --- a/workers_bindings_test.go +++ /dev/null @@ -1,97 +0,0 @@ -package cloudflare - -import ( - "context" - "fmt" - "io" - "net/http" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestListWorkerBindings(t *testing.T) { - setup() - defer teardown() - - mux.HandleFunc("/accounts/"+testAccountID+"/workers/scripts/my-script/bindings", 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.Fprint(w, listBindingsResponseData) - }) - - mux.HandleFunc("/accounts/"+testAccountID+"/workers/scripts/my-script/bindings/MY_WASM/content", 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/wasm") - _, _ = w.Write([]byte("mock multi-script wasm")) - }) - - res, err := client.ListWorkerBindings(context.Background(), AccountIdentifier(testAccountID), ListWorkerBindingsParams{ - ScriptName: "my-script", - }) - assert.NoError(t, err) - - assert.Equal(t, successResponse, res.Response) - assert.Equal(t, 8, len(res.BindingList)) - - assert.Equal(t, res.BindingList[0], WorkerBindingListItem{ - Name: "MY_KV", - Binding: WorkerKvNamespaceBinding{ - NamespaceID: "89f5f8fd93f94cb98473f6f421aa3b65", - }, - }) - assert.Equal(t, WorkerKvNamespaceBindingType, res.BindingList[0].Binding.Type()) - - assert.Equal(t, "MY_WASM", res.BindingList[1].Name) - wasmBinding := res.BindingList[1].Binding.(WorkerWebAssemblyBinding) - wasmModuleContent, err := io.ReadAll(wasmBinding.Module) - assert.NoError(t, err) - assert.Equal(t, []byte("mock multi-script wasm"), wasmModuleContent) - assert.Equal(t, WorkerWebAssemblyBindingType, res.BindingList[1].Binding.Type()) - - assert.Equal(t, res.BindingList[2], WorkerBindingListItem{ - Name: "MY_PLAIN_TEXT", - Binding: WorkerPlainTextBinding{ - Text: "text", - }, - }) - assert.Equal(t, WorkerPlainTextBindingType, res.BindingList[2].Binding.Type()) - - assert.Equal(t, res.BindingList[3], WorkerBindingListItem{ - Name: "MY_SECRET_TEXT", - Binding: WorkerSecretTextBinding{}, - }) - assert.Equal(t, WorkerSecretTextBindingType, res.BindingList[3].Binding.Type()) - - environment := "MY_ENVIRONMENT" - assert.Equal(t, res.BindingList[4], WorkerBindingListItem{ - Name: "MY_SERVICE_BINDING", - Binding: WorkerServiceBinding{ - Service: "MY_SERVICE", - Environment: &environment, - }, - }) - assert.Equal(t, WorkerServiceBindingType, res.BindingList[4].Binding.Type()) - - assert.Equal(t, res.BindingList[5], WorkerBindingListItem{ - Name: "MY_NEW_BINDING", - Binding: WorkerInheritBinding{}, - }) - assert.Equal(t, WorkerInheritBindingType, res.BindingList[5].Binding.Type()) - - assert.Equal(t, res.BindingList[6], WorkerBindingListItem{ - Name: "MY_BUCKET", - Binding: WorkerR2BucketBinding{ - BucketName: "bucket", - }, - }) - assert.Equal(t, WorkerR2BucketBindingType, res.BindingList[6].Binding.Type()) - - assert.Equal(t, res.BindingList[7], WorkerBindingListItem{ - Name: "MY_DATASET", - Binding: WorkerAnalyticsEngineBinding{ - Dataset: "my_dataset", - }, - }) - assert.Equal(t, WorkerAnalyticsEngineBindingType, res.BindingList[7].Binding.Type()) -} diff --git a/workers_cron_triggers.go b/workers_cron_triggers.go index 2322a5d682..8938862aef 100644 --- a/workers_cron_triggers.go +++ b/workers_cron_triggers.go @@ -27,29 +27,12 @@ type WorkerCronTrigger struct { ModifiedOn *time.Time `json:"modified_on,omitempty"` } -type ListWorkerCronTriggersParams struct { - ScriptName string -} - -type UpdateWorkerCronTriggersParams struct { - ScriptName string - Crons []WorkerCronTrigger -} - // ListWorkerCronTriggers fetches all available cron triggers for a single Worker // script. // // API reference: https://api.cloudflare.com/#worker-cron-trigger-get-cron-triggers -func (api *API) ListWorkerCronTriggers(ctx context.Context, rc *ResourceContainer, params ListWorkerCronTriggersParams) ([]WorkerCronTrigger, error) { - if rc.Level != AccountRouteLevel { - return []WorkerCronTrigger{}, ErrRequiredAccountLevelResourceContainer - } - - if rc.Identifier == "" { - return []WorkerCronTrigger{}, ErrMissingIdentifier - } - - uri := fmt.Sprintf("/accounts/%s/workers/scripts/%s/schedules", rc.Identifier, params.ScriptName) +func (api *API) ListWorkerCronTriggers(ctx context.Context, accountID, scriptName string) ([]WorkerCronTrigger, error) { + uri := fmt.Sprintf("/accounts/%s/workers/scripts/%s/schedules", accountID, scriptName) res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) if err != nil { return []WorkerCronTrigger{}, err @@ -66,17 +49,9 @@ func (api *API) ListWorkerCronTriggers(ctx context.Context, rc *ResourceContaine // UpdateWorkerCronTriggers updates a single schedule for a Worker cron trigger. // // API reference: https://api.cloudflare.com/#worker-cron-trigger-update-cron-triggers -func (api *API) UpdateWorkerCronTriggers(ctx context.Context, rc *ResourceContainer, params UpdateWorkerCronTriggersParams) ([]WorkerCronTrigger, error) { - if rc.Level != AccountRouteLevel { - return []WorkerCronTrigger{}, ErrRequiredAccountLevelResourceContainer - } - - if rc.Identifier == "" { - return []WorkerCronTrigger{}, ErrMissingIdentifier - } - - uri := fmt.Sprintf("/accounts/%s/workers/scripts/%s/schedules", rc.Identifier, params.ScriptName) - res, err := api.makeRequestContext(ctx, http.MethodPut, uri, params.Crons) +func (api *API) UpdateWorkerCronTriggers(ctx context.Context, accountID, scriptName string, crons []WorkerCronTrigger) ([]WorkerCronTrigger, error) { + uri := fmt.Sprintf("/accounts/%s/workers/scripts/%s/schedules", accountID, scriptName) + res, err := api.makeRequestContext(ctx, http.MethodPut, uri, crons) if err != nil { return []WorkerCronTrigger{}, err } diff --git a/workers_cron_triggers_test.go b/workers_cron_triggers_test.go index 0194d75ef3..d2acba1e25 100644 --- a/workers_cron_triggers_test.go +++ b/workers_cron_triggers_test.go @@ -11,7 +11,7 @@ import ( ) func TestListWorkerCronTriggers(t *testing.T) { - setup() + setup(UsingAccount("9a7806061c88ada191ed06f989cc3dac")) defer teardown() handler := func(w http.ResponseWriter, r *http.Request) { @@ -42,14 +42,14 @@ func TestListWorkerCronTriggers(t *testing.T) { CreatedOn: &createdOn, }} - actual, err := client.ListWorkerCronTriggers(context.Background(), AccountIdentifier(testAccountID), ListWorkerCronTriggersParams{ScriptName: "example-script"}) + actual, err := client.ListWorkerCronTriggers(context.Background(), testAccountID, "example-script") if assert.NoError(t, err) { assert.Equal(t, want, actual) } } func TestUpdateWorkerCronTriggers(t *testing.T) { - setup() + setup(UsingAccount("9a7806061c88ada191ed06f989cc3dac")) defer teardown() handler := func(w http.ResponseWriter, r *http.Request) { @@ -80,7 +80,7 @@ func TestUpdateWorkerCronTriggers(t *testing.T) { CreatedOn: &createdOn, }} - actual, err := client.UpdateWorkerCronTriggers(context.Background(), AccountIdentifier(testAccountID), UpdateWorkerCronTriggersParams{ScriptName: "example-script", Crons: want}) + actual, err := client.UpdateWorkerCronTriggers(context.Background(), testAccountID, "example-script", want) if assert.NoError(t, err) { assert.Equal(t, want, actual) } diff --git a/workers_example_test.go b/workers_example_test.go new file mode 100644 index 0000000000..2206a35045 --- /dev/null +++ b/workers_example_test.go @@ -0,0 +1,206 @@ +package cloudflare_test + +import ( + "context" + "fmt" + "log" + + cloudflare "github.com/cloudflare/cloudflare-go" +) + +var ( + workerScript = "addEventListener('fetch', event => {\n event.passThroughOnException()\nevent.respondWith(handleRequest(event.request))\n})\n\nasync function handleRequest(request) {\n return fetch(request)\n}" +) + +func ExampleAPI_UploadWorker() { + api, err := cloudflare.New(apiKey, user) + if err != nil { + log.Fatal(err) + } + + zoneID, err := api.ZoneIDByName(domain) + if err != nil { + log.Fatal(err) + } + + res, err := api.UploadWorker(context.Background(), &cloudflare.WorkerRequestParams{ZoneID: zoneID}, &cloudflare.WorkerScriptParams{Script: workerScript}) + if err != nil { + log.Fatal(err) + } + fmt.Printf("%+v", res) + + UploadWorkerWithName() +} + +func UploadWorkerWithName() { + api, err := cloudflare.New(apiKey, user, cloudflare.UsingAccount("foo")) + if err != nil { + log.Fatal(err) + } + + res, err := api.UploadWorker(context.Background(), &cloudflare.WorkerRequestParams{ScriptName: "baz"}, &cloudflare.WorkerScriptParams{Script: workerScript}) + if err != nil { + log.Fatal(err) + } + fmt.Printf("%+v", res) +} + +func ExampleAPI_DownloadWorker() { + api, err := cloudflare.New(apiKey, user) + if err != nil { + log.Fatal(err) + } + + zoneID, err := api.ZoneIDByName(domain) + if err != nil { + log.Fatal(err) + } + + res, err := api.DownloadWorker(context.Background(), &cloudflare.WorkerRequestParams{ZoneID: zoneID}) + if err != nil { + log.Fatal(err) + } + fmt.Printf("%+v", res) + + DownloadWorkerWithName() +} + +func DownloadWorkerWithName() { + api, err := cloudflare.New(apiKey, user, cloudflare.UsingAccount("foo")) + if err != nil { + log.Fatal(err) + } + + res, err := api.DownloadWorker(context.Background(), &cloudflare.WorkerRequestParams{ScriptName: "baz"}) + if err != nil { + log.Fatal(err) + } + fmt.Printf("%+v", res) +} + +func ExampleAPI_DeleteWorker() { + api, err := cloudflare.New(apiKey, user) + if err != nil { + log.Fatal(err) + } + + zoneID, err := api.ZoneIDByName(domain) + if err != nil { + log.Fatal(err) + } + res, err := api.DeleteWorker(context.Background(), &cloudflare.WorkerRequestParams{ZoneID: zoneID}) + if err != nil { + log.Fatal(err) + } + fmt.Printf("%+v", res) + + DeleteWorkerWithName() +} + +func DeleteWorkerWithName() { + api, err := cloudflare.New(apiKey, user, cloudflare.UsingAccount("foo")) + if err != nil { + log.Fatal(err) + } + + res, err := api.DeleteWorker(context.Background(), &cloudflare.WorkerRequestParams{ScriptName: "baz"}) + if err != nil { + log.Fatal(err) + } + fmt.Printf("%+v", res) +} + +func ExampleAPI_ListWorkerScripts() { + api, err := cloudflare.New(apiKey, user, cloudflare.UsingAccount("foo")) + if err != nil { + log.Fatal(err) + } + + res, err := api.ListWorkerScripts(context.Background()) + if err != nil { + log.Fatal(err) + } + fmt.Printf("%+v", res.WorkerList) +} + +func ExampleAPI_CreateWorkerRoute() { + api, err := cloudflare.New(apiKey, user) + if err != nil { + log.Fatal(err) + } + + zoneID, err := api.ZoneIDByName(domain) + if err != nil { + log.Fatal(err) + } + route := cloudflare.WorkerRoute{Pattern: "app1.example.com/*", Enabled: true} + res, err := api.CreateWorkerRoute(context.Background(), zoneID, route) + if err != nil { + log.Fatal(err) + } + fmt.Printf("%+v", res) +} + +func ExampleAPI_UpdateWorkerRoute() { + api, err := cloudflare.New(apiKey, user) + if err != nil { + log.Fatal(err) + } + + zoneID, err := api.ZoneIDByName(domain) + if err != nil { + log.Fatal(err) + } + // pull from existing list of routes to perform update on + routesResponse, err := api.ListWorkerRoutes(context.Background(), zoneID) + if err != nil { + log.Fatal(err) + } + route := cloudflare.WorkerRoute{Pattern: "app2.example.com/*", Enabled: true} + // update first route retrieved from the listWorkerRoutes call with details above + res, err := api.UpdateWorkerRoute(context.Background(), zoneID, routesResponse.Routes[0].ID, route) + if err != nil { + log.Fatal(err) + } + fmt.Printf("%+v", res) +} + +func ExampleAPI_ListWorkerRoutes() { + api, err := cloudflare.New(apiKey, user) + if err != nil { + log.Fatal(err) + } + + zoneID, err := api.ZoneIDByName(domain) + if err != nil { + log.Fatal(err) + } + res, err := api.ListWorkerRoutes(context.Background(), zoneID) + if err != nil { + log.Fatal(err) + } + fmt.Printf("%+v", res) +} + +func ExampleAPI_DeleteWorkerRoute() { + api, err := cloudflare.New(apiKey, user) + if err != nil { + log.Fatal(err) + } + + zoneID, err := api.ZoneIDByName(domain) + if err != nil { + log.Fatal(err) + } + // pull from existing list of routes to perform delete on + routesResponse, err := api.ListWorkerRoutes(context.Background(), zoneID) + if err != nil { + log.Fatal(err) + } + // delete first route retrieved from the listWorkerRoutes call + res, err := api.DeleteWorkerRoute(context.Background(), zoneID, routesResponse.Routes[0].ID) + if err != nil { + log.Fatal(err) + } + fmt.Printf("%+v", res) +} diff --git a/workers_kv.go b/workers_kv.go index 7cdf30e3c6..f7a01bc062 100644 --- a/workers_kv.go +++ b/workers_kv.go @@ -106,13 +106,6 @@ type ListWorkersKVsParams struct { // // API reference: https://api.cloudflare.com/#workers-kv-namespace-create-a-namespace func (api *API) CreateWorkersKVNamespace(ctx context.Context, rc *ResourceContainer, params CreateWorkersKVNamespaceParams) (WorkersKVNamespaceResponse, error) { - if rc.Level != AccountRouteLevel { - return WorkersKVNamespaceResponse{}, ErrRequiredAccountLevelResourceContainer - } - - if rc.Identifier == "" { - return WorkersKVNamespaceResponse{}, ErrMissingIdentifier - } uri := fmt.Sprintf("/accounts/%s/storage/kv/namespaces", rc.Identifier) res, err := api.makeRequestContext(ctx, http.MethodPost, uri, params) if err != nil { @@ -131,14 +124,6 @@ func (api *API) CreateWorkersKVNamespace(ctx context.Context, rc *ResourceContai // // API reference: https://api.cloudflare.com/#workers-kv-namespace-list-namespaces func (api *API) ListWorkersKVNamespaces(ctx context.Context, rc *ResourceContainer, params ListWorkersKVNamespacesParams) ([]WorkersKVNamespace, *ResultInfo, error) { - if rc.Level != AccountRouteLevel { - return []WorkersKVNamespace{}, &ResultInfo{}, ErrRequiredAccountLevelResourceContainer - } - - if rc.Identifier == "" { - return []WorkersKVNamespace{}, &ResultInfo{}, ErrMissingIdentifier - } - autoPaginate := true if params.PerPage >= 1 || params.Page >= 1 { autoPaginate = false @@ -198,14 +183,6 @@ func (api *API) DeleteWorkersKVNamespace(ctx context.Context, rc *ResourceContai // // API reference: https://api.cloudflare.com/#workers-kv-namespace-rename-a-namespace func (api *API) UpdateWorkersKVNamespace(ctx context.Context, rc *ResourceContainer, params UpdateWorkersKVNamespaceParams) (Response, error) { - if rc.Level != AccountRouteLevel { - return Response{}, ErrRequiredAccountLevelResourceContainer - } - - if rc.Identifier == "" { - return Response{}, ErrMissingIdentifier - } - uri := fmt.Sprintf("/accounts/%s/storage/kv/namespaces/%s", rc.Identifier, params.NamespaceID) res, err := api.makeRequestContext(ctx, http.MethodPut, uri, params) if err != nil { @@ -224,14 +201,6 @@ func (api *API) UpdateWorkersKVNamespace(ctx context.Context, rc *ResourceContai // // API reference: https://api.cloudflare.com/#workers-kv-namespace-write-key-value-pair func (api *API) WriteWorkersKVEntry(ctx context.Context, rc *ResourceContainer, params WriteWorkersKVEntryParams) (Response, error) { - if rc.Level != AccountRouteLevel { - return Response{}, ErrRequiredAccountLevelResourceContainer - } - - if rc.Identifier == "" { - return Response{}, ErrMissingIdentifier - } - uri := fmt.Sprintf("/accounts/%s/storage/kv/namespaces/%s/values/%s", rc.Identifier, params.NamespaceID, url.PathEscape(params.Key)) res, err := api.makeRequestContextWithHeaders( ctx, http.MethodPut, uri, params.Value, http.Header{"Content-Type": []string{"application/octet-stream"}}, @@ -252,14 +221,6 @@ func (api *API) WriteWorkersKVEntry(ctx context.Context, rc *ResourceContainer, // // API reference: https://api.cloudflare.com/#workers-kv-namespace-write-multiple-key-value-pairs func (api *API) WriteWorkersKVEntries(ctx context.Context, rc *ResourceContainer, params WriteWorkersKVEntriesParams) (Response, error) { - if rc.Level != AccountRouteLevel { - return Response{}, ErrRequiredAccountLevelResourceContainer - } - - if rc.Identifier == "" { - return Response{}, ErrMissingIdentifier - } - uri := fmt.Sprintf("/accounts/%s/storage/kv/namespaces/%s/bulk", rc.Identifier, params.NamespaceID) res, err := api.makeRequestContextWithHeaders( ctx, http.MethodPut, uri, params.KVs, http.Header{"Content-Type": []string{"application/json"}}, @@ -281,13 +242,6 @@ func (api *API) WriteWorkersKVEntries(ctx context.Context, rc *ResourceContainer // // API reference: https://api.cloudflare.com/#workers-kv-namespace-read-key-value-pair func (api API) GetWorkersKV(ctx context.Context, rc *ResourceContainer, params GetWorkersKVParams) ([]byte, error) { - if rc.Level != AccountRouteLevel { - return []byte(``), ErrRequiredAccountLevelResourceContainer - } - - if rc.Identifier == "" { - return []byte(``), ErrMissingIdentifier - } uri := fmt.Sprintf("/accounts/%s/storage/kv/namespaces/%s/values/%s", rc.Identifier, params.NamespaceID, url.PathEscape(params.Key)) res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) if err != nil { @@ -300,13 +254,6 @@ func (api API) GetWorkersKV(ctx context.Context, rc *ResourceContainer, params G // // API reference: https://api.cloudflare.com/#workers-kv-namespace-delete-key-value-pair func (api API) DeleteWorkersKVEntry(ctx context.Context, rc *ResourceContainer, params DeleteWorkersKVEntryParams) (Response, error) { - if rc.Level != AccountRouteLevel { - return Response{}, ErrRequiredAccountLevelResourceContainer - } - - if rc.Identifier == "" { - return Response{}, ErrMissingIdentifier - } uri := fmt.Sprintf("/accounts/%s/storage/kv/namespaces/%s/values/%s", rc.Identifier, params.NamespaceID, url.PathEscape(params.Key)) res, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil) if err != nil { @@ -324,13 +271,6 @@ func (api API) DeleteWorkersKVEntry(ctx context.Context, rc *ResourceContainer, // // API reference: https://api.cloudflare.com/#workers-kv-namespace-delete-multiple-key-value-pairs func (api *API) DeleteWorkersKVEntries(ctx context.Context, rc *ResourceContainer, params DeleteWorkersKVEntriesParams) (Response, error) { - if rc.Level != AccountRouteLevel { - return Response{}, ErrRequiredAccountLevelResourceContainer - } - - if rc.Identifier == "" { - return Response{}, ErrMissingIdentifier - } uri := fmt.Sprintf("/accounts/%s/storage/kv/namespaces/%s/bulk", rc.Identifier, params.NamespaceID) res, err := api.makeRequestContextWithHeaders( ctx, http.MethodDelete, uri, params.Keys, http.Header{"Content-Type": []string{"application/json"}}, @@ -351,14 +291,6 @@ func (api *API) DeleteWorkersKVEntries(ctx context.Context, rc *ResourceContaine // // API Reference: https://api.cloudflare.com/#workers-kv-namespace-list-a-namespace-s-keys func (api API) ListWorkersKVKeys(ctx context.Context, rc *ResourceContainer, params ListWorkersKVsParams) (ListStorageKeysResponse, error) { - if rc.Level != AccountRouteLevel { - return ListStorageKeysResponse{}, ErrRequiredAccountLevelResourceContainer - } - - if rc.Identifier == "" { - return ListStorageKeysResponse{}, ErrMissingIdentifier - } - uri := buildURI( fmt.Sprintf("/accounts/%s/storage/kv/namespaces/%s/keys", rc.Identifier, params.NamespaceID), params, diff --git a/workers_kv_test.go b/workers_kv_test.go index 80d7b140b9..1ad7ae091d 100644 --- a/workers_kv_test.go +++ b/workers_kv_test.go @@ -29,7 +29,7 @@ func TestWorkersKV_CreateWorkersKVNamespace(t *testing.T) { mux.HandleFunc("/accounts/"+testAccountID+"/storage/kv/namespaces", func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, http.MethodPost, r.Method, "Expected method 'POST', got %s", r.Method) w.Header().Set("content-type", "application/javascript") - fmt.Fprint(w, response) + fmt.Fprintf(w, response) //nolint }) res, err := client.CreateWorkersKVNamespace(context.Background(), AccountIdentifier(testAccountID), CreateWorkersKVNamespaceParams{Title: "Namespace"}) @@ -60,7 +60,7 @@ func TestWorkersKV_DeleteWorkersKVNamespace(t *testing.T) { mux.HandleFunc(fmt.Sprintf("/accounts/"+testAccountID+"/storage/kv/namespaces/%s", namespace), 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/javascript") - fmt.Fprint(w, response) + fmt.Fprintf(w, response) //nolint }) res, err := client.DeleteWorkersKVNamespace(context.Background(), AccountIdentifier(testAccountID), namespace) @@ -99,7 +99,7 @@ func TestWorkersKV_ListWorkersKVNamespaces(t *testing.T) { mux.HandleFunc("/accounts/"+testAccountID+"/storage/kv/namespaces", 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/javascript") - fmt.Fprint(w, response) + fmt.Fprintf(w, response) //nolint }) res, _, err := client.ListWorkersKVNamespaces(context.Background(), AccountIdentifier(testAccountID), ListWorkersKVNamespacesParams{}) @@ -170,10 +170,10 @@ func TestWorkersKV_ListWorkersKVNamespaceMultiplePages(t *testing.T) { w.Header().Set("content-type", "application/javascript") if r.URL.Query().Get("page") == "1" { - fmt.Fprint(w, response1) + fmt.Fprintf(w, response1) //nolint return } else if r.URL.Query().Get("page") == "2" { - fmt.Fprint(w, response2) + fmt.Fprintf(w, response2) //nolint return } else { panic(errors.New("Got a request for an unexpected page")) @@ -218,7 +218,7 @@ func TestWorkersKV_UpdateWorkersKVNamespace(t *testing.T) { mux.HandleFunc(fmt.Sprintf("/accounts/"+testAccountID+"/storage/kv/namespaces/%s", namespace), 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/javascript") - fmt.Fprint(w, response) + fmt.Fprintf(w, response) //nolint }) res, err := client.UpdateWorkersKVNamespace(context.Background(), AccountIdentifier(testAccountID), UpdateWorkersKVNamespaceParams{Title: "Namespace", NamespaceID: namespace}) @@ -246,7 +246,7 @@ func TestWorkersKV_WriteWorkersKVEntry(t *testing.T) { mux.HandleFunc(fmt.Sprintf("/accounts/"+testAccountID+"/storage/kv/namespaces/%s/values/%s", namespace, key), 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/octet-stream") - fmt.Fprint(w, response) + fmt.Fprintf(w, response) //nolint }) want := successResponse @@ -278,7 +278,7 @@ func TestWorkersKV_WriteWorkersKVEntries(t *testing.T) { mux.HandleFunc(fmt.Sprintf("/accounts/"+testAccountID+"/storage/kv/namespaces/%s/bulk", namespace), 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.Fprint(w, response) + fmt.Fprintf(w, response) //nolint }) want := successResponse @@ -297,7 +297,7 @@ func TestWorkersKV_ReadWorkersKV(t *testing.T) { mux.HandleFunc(fmt.Sprintf("/accounts/"+testAccountID+"/storage/kv/namespaces/%s/values/%s", namespace, key), 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", "text/plain") - fmt.Fprint(w, "test_value") + fmt.Fprintf(w, "test_value") }) res, err := client.GetWorkersKV(context.Background(), AccountIdentifier(testAccountID), GetWorkersKVParams{NamespaceID: namespace, Key: key}) @@ -324,7 +324,7 @@ func TestWorkersKV_DeleteWorkersKVEntry(t *testing.T) { mux.HandleFunc(fmt.Sprintf("/accounts/"+testAccountID+"/storage/kv/namespaces/%s/values/%s", namespace, key), 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/javascript") - fmt.Fprint(w, response) + fmt.Fprintf(w, response) //nolint }) res, err := client.DeleteWorkersKVEntry(context.Background(), AccountIdentifier(testAccountID), DeleteWorkersKVEntryParams{NamespaceID: namespace, Key: key}) @@ -352,7 +352,7 @@ func TestWorkersKV_DeleteWorkersKVBulk(t *testing.T) { mux.HandleFunc(fmt.Sprintf("/accounts/"+testAccountID+"/storage/kv/namespaces/%s/bulk", namespace), 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.Fprint(w, response) + fmt.Fprintf(w, response) //nolint }) want := successResponse @@ -386,7 +386,7 @@ func TestWorkersKV_ListKeys(t *testing.T) { mux.HandleFunc(fmt.Sprintf("/accounts/"+testAccountID+"/storage/kv/namespaces/%s/keys", namespace), 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/javascript") - fmt.Fprint(w, response) + fmt.Fprintf(w, response) //nolint }) res, err := client.ListWorkersKVKeys(context.Background(), AccountIdentifier(testAccountID), ListWorkersKVsParams{NamespaceID: namespace}) @@ -456,7 +456,7 @@ func TestWorkersKV_ListKeysWithParameters(t *testing.T) { mux.HandleFunc(fmt.Sprintf("/accounts/"+testAccountID+"/storage/kv/namespaces/%s/keys", namespace), 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/javascript") - fmt.Fprint(w, response) + fmt.Fprintf(w, response) //nolint }) limit, prefix := 25, "test-prefix" diff --git a/workers_routes.go b/workers_routes.go deleted file mode 100644 index d6dc1c2a71..0000000000 --- a/workers_routes.go +++ /dev/null @@ -1,161 +0,0 @@ -package cloudflare - -import ( - "context" - "encoding/json" - "errors" - "fmt" - "net/http" -) - -var ErrMissingWorkerRouteID = errors.New("missing required route ID") - -type ListWorkerRoutes struct{} - -type CreateWorkerRouteParams struct { - Pattern string `json:"pattern"` - Script string `json:"script,omitempty"` -} - -type ListWorkerRoutesParams struct{} - -type UpdateWorkerRouteParams struct { - ID string `json:"id,omitempty"` - Pattern string `json:"pattern"` - Script string `json:"script,omitempty"` -} - -// CreateWorkerRoute creates worker route for a script. -// -// API reference: https://api.cloudflare.com/#worker-routes-create-route -func (api *API) CreateWorkerRoute(ctx context.Context, rc *ResourceContainer, params CreateWorkerRouteParams) (WorkerRouteResponse, error) { - if rc.Level != ZoneRouteLevel { - return WorkerRouteResponse{}, fmt.Errorf(errInvalidResourceContainerAccess, ZoneRouteLevel) - } - - if rc.Identifier == "" { - return WorkerRouteResponse{}, ErrMissingIdentifier - } - - uri := fmt.Sprintf("/zones/%s/workers/routes", rc.Identifier) - res, err := api.makeRequestContext(ctx, http.MethodPost, uri, params) - if err != nil { - return WorkerRouteResponse{}, err - } - - var r WorkerRouteResponse - err = json.Unmarshal(res, &r) - if err != nil { - return WorkerRouteResponse{}, fmt.Errorf("%s: %w", errUnmarshalError, err) - } - return r, nil -} - -// DeleteWorkerRoute deletes worker route for a script. -// -// API reference: https://api.cloudflare.com/#worker-routes-delete-route -func (api *API) DeleteWorkerRoute(ctx context.Context, rc *ResourceContainer, routeID string) (WorkerRouteResponse, error) { - if rc.Level != ZoneRouteLevel { - return WorkerRouteResponse{}, fmt.Errorf(errInvalidResourceContainerAccess, ZoneRouteLevel) - } - - if rc.Identifier == "" { - return WorkerRouteResponse{}, ErrMissingIdentifier - } - - if routeID == "" { - return WorkerRouteResponse{}, errors.New("missing required route ID") - } - - uri := fmt.Sprintf("/zones/%s/workers/routes/%s", rc.Identifier, routeID) - res, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil) - if err != nil { - return WorkerRouteResponse{}, err - } - var r WorkerRouteResponse - err = json.Unmarshal(res, &r) - if err != nil { - return WorkerRouteResponse{}, fmt.Errorf("%s: %w", errUnmarshalError, err) - } - return r, nil -} - -// ListWorkerRoutes returns list of Worker routes. -// -// API reference: https://api.cloudflare.com/#worker-routes-list-routes -func (api *API) ListWorkerRoutes(ctx context.Context, rc *ResourceContainer, params ListWorkerRoutesParams) (WorkerRoutesResponse, error) { - if rc.Level != ZoneRouteLevel { - return WorkerRoutesResponse{}, fmt.Errorf(errInvalidResourceContainerAccess, ZoneRouteLevel) - } - - if rc.Identifier == "" { - return WorkerRoutesResponse{}, ErrMissingIdentifier - } - - uri := fmt.Sprintf("/zones/%s/workers/routes", rc.Identifier) - res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) - if err != nil { - return WorkerRoutesResponse{}, err - } - var r WorkerRoutesResponse - err = json.Unmarshal(res, &r) - if err != nil { - return WorkerRoutesResponse{}, fmt.Errorf("%s: %w", errUnmarshalError, err) - } - - return r, nil -} - -// GetWorkerRoute returns a Workers route. -// -// API reference: https://api.cloudflare.com/#worker-routes-get-route -func (api *API) GetWorkerRoute(ctx context.Context, rc *ResourceContainer, routeID string) (WorkerRouteResponse, error) { - if rc.Level != ZoneRouteLevel { - return WorkerRouteResponse{}, fmt.Errorf(errInvalidResourceContainerAccess, ZoneRouteLevel) - } - - if rc.Identifier == "" { - return WorkerRouteResponse{}, ErrMissingIdentifier - } - - uri := fmt.Sprintf("/zones/%s/workers/routes/%s", rc.Identifier, routeID) - res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) - if err != nil { - return WorkerRouteResponse{}, err - } - var r WorkerRouteResponse - err = json.Unmarshal(res, &r) - if err != nil { - return WorkerRouteResponse{}, fmt.Errorf("%s: %w", errUnmarshalError, err) - } - return r, nil -} - -// UpdateWorkerRoute updates worker route for a script. -// -// API reference: https://api.cloudflare.com/#worker-routes-update-route -func (api *API) UpdateWorkerRoute(ctx context.Context, rc *ResourceContainer, params UpdateWorkerRouteParams) (WorkerRouteResponse, error) { - if rc.Level != ZoneRouteLevel { - return WorkerRouteResponse{}, fmt.Errorf(errInvalidResourceContainerAccess, ZoneRouteLevel) - } - - if rc.Identifier == "" { - return WorkerRouteResponse{}, ErrMissingIdentifier - } - - if params.ID == "" { - return WorkerRouteResponse{}, ErrMissingWorkerRouteID - } - - uri := fmt.Sprintf("/zones/%s/workers/routes/%s", rc.Identifier, params.ID) - res, err := api.makeRequestContext(ctx, http.MethodPut, uri, params) - if err != nil { - return WorkerRouteResponse{}, err - } - var r WorkerRouteResponse - err = json.Unmarshal(res, &r) - if err != nil { - return WorkerRouteResponse{}, fmt.Errorf("%s: %w", errUnmarshalError, err) - } - return r, nil -} diff --git a/workers_routes_test.go b/workers_routes_test.go deleted file mode 100644 index 8d36fdc9d4..0000000000 --- a/workers_routes_test.go +++ /dev/null @@ -1,121 +0,0 @@ -package cloudflare - -import ( - "context" - "fmt" - "net/http" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestCreateWorkersRoute(t *testing.T) { - setup() - defer teardown() - - mux.HandleFunc("/zones/"+testZoneID+"/workers/routes", func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, http.MethodPost, r.Method, "Expected method 'POST', got %s", r.Method) - w.Header().Set("content-type", "application/json") - fmt.Fprint(w, createWorkerRouteResponse) - }) - - res, err := client.CreateWorkerRoute(context.Background(), ZoneIdentifier(testZoneID), CreateWorkerRouteParams{ - Pattern: "app1.example.com/*", - Script: "example", - }) - - want := WorkerRouteResponse{successResponse, WorkerRoute{ID: "e7a57d8746e74ae49c25994dadb421b1"}} - if assert.NoError(t, err) { - assert.Equal(t, want, res) - } -} - -func TestDeleteWorkersRoute(t *testing.T) { - setup() - defer teardown() - - mux.HandleFunc("/zones/"+testZoneID+"/workers/routes/e7a57d8746e74ae49c25994dadb421b1", 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.Fprint(w, deleteWorkerRouteResponseData) - }) - res, err := client.DeleteWorkerRoute(context.Background(), ZoneIdentifier(testZoneID), "e7a57d8746e74ae49c25994dadb421b1") - want := WorkerRouteResponse{successResponse, - WorkerRoute{ - ID: "e7a57d8746e74ae49c25994dadb421b1", - }} - if assert.NoError(t, err) { - assert.Equal(t, want, res) - } -} - -func TestListWorkersRoute(t *testing.T) { - setup() - defer teardown() - - mux.HandleFunc("/zones/"+testZoneID+"/workers/routes", 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.Fprint(w, listWorkerRouteResponse) - }) - - res, err := client.ListWorkerRoutes(context.Background(), ZoneIdentifier(testZoneID), ListWorkerRoutesParams{}) - want := WorkerRoutesResponse{successResponse, - []WorkerRoute{ - {ID: "e7a57d8746e74ae49c25994dadb421b1", Pattern: "app1.example.com/*", ScriptName: "test_script_1"}, - {ID: "f8b68e9857f85bf59c25994dadb421b1", Pattern: "app2.example.com/*", ScriptName: "test_script_2"}, - {ID: "2b5bf4240cd34c77852fac70b1bf745a", Pattern: "app3.example.com/*", ScriptName: "test_script_3"}, - }, - } - if assert.NoError(t, err) { - assert.Equal(t, want, res) - } -} - -func TestGetWorkersRoute(t *testing.T) { - setup() - defer teardown() - - mux.HandleFunc("/zones/"+testZoneID+"/workers/routes/1234", 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.Fprint(w, getRouteResponseData) - }) - - res, err := client.GetWorkerRoute(context.Background(), ZoneIdentifier(testZoneID), "1234") - want := WorkerRouteResponse{successResponse, - WorkerRoute{ - ID: "e7a57d8746e74ae49c25994dadb421b1", - Pattern: "app1.example.com/*", - ScriptName: "script-name"}, - } - if assert.NoError(t, err) { - assert.Equal(t, want, res) - } -} - -func TestUpdateWorkersRoute(t *testing.T) { - setup() - defer teardown() - - mux.HandleFunc("/zones/"+testZoneID+"/workers/routes/e7a57d8746e74ae49c25994dadb421b1", 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.Fprint(w, updateWorkerRouteEntResponse) - }) - - res, err := client.UpdateWorkerRoute(context.Background(), ZoneIdentifier(testZoneID), UpdateWorkerRouteParams{ - ID: "e7a57d8746e74ae49c25994dadb421b1", - Pattern: "app3.example.com/*", - Script: "test_script_1", - }) - want := WorkerRouteResponse{successResponse, - WorkerRoute{ - ID: "e7a57d8746e74ae49c25994dadb421b1", - Pattern: "app3.example.com/*", - ScriptName: "test_script_1", - }} - if assert.NoError(t, err) { - assert.Equal(t, want, res) - } -} diff --git a/workers_secrets.go b/workers_secrets.go index 458cd1a7c4..09d8ba337e 100644 --- a/workers_secrets.go +++ b/workers_secrets.go @@ -32,34 +32,11 @@ type WorkersListSecretsResponse struct { Result []WorkersSecret `json:"result"` } -type SetWorkersSecretParams struct { - ScriptName string - Secret *WorkersPutSecretRequest -} - -type DeleteWorkersSecretParams struct { - ScriptName string - SecretName string -} - -type ListWorkersSecretsParams struct { - ScriptName string -} - -// SetWorkersSecret creates or updates a secret. -// +// SetWorkersSecret creates or updates a secret // API reference: https://api.cloudflare.com/ -func (api *API) SetWorkersSecret(ctx context.Context, rc *ResourceContainer, params SetWorkersSecretParams) (WorkersPutSecretResponse, error) { - if rc.Level != AccountRouteLevel { - return WorkersPutSecretResponse{}, ErrRequiredAccountLevelResourceContainer - } - - if rc.Identifier == "" { - return WorkersPutSecretResponse{}, ErrMissingAccountID - } - - uri := fmt.Sprintf("/accounts/%s/workers/scripts/%s/secrets", rc.Identifier, params.ScriptName) - res, err := api.makeRequestContext(ctx, http.MethodPut, uri, params.Secret) +func (api *API) SetWorkersSecret(ctx context.Context, script string, req *WorkersPutSecretRequest) (WorkersPutSecretResponse, error) { + uri := fmt.Sprintf("/accounts/%s/workers/scripts/%s/secrets", api.AccountID, script) + res, err := api.makeRequestContext(ctx, http.MethodPut, uri, req) if err != nil { return WorkersPutSecretResponse{}, err } @@ -72,19 +49,10 @@ func (api *API) SetWorkersSecret(ctx context.Context, rc *ResourceContainer, par return result, err } -// DeleteWorkersSecret deletes a secret. -// +// DeleteWorkersSecret deletes a secret // API reference: https://api.cloudflare.com/ -func (api *API) DeleteWorkersSecret(ctx context.Context, rc *ResourceContainer, params DeleteWorkersSecretParams) (Response, error) { - if rc.Level != AccountRouteLevel { - return Response{}, ErrRequiredAccountLevelResourceContainer - } - - if rc.Identifier == "" { - return Response{}, ErrMissingAccountID - } - - uri := fmt.Sprintf("/accounts/%s/workers/scripts/%s/secrets/%s", rc.Identifier, params.ScriptName, params.SecretName) +func (api *API) DeleteWorkersSecret(ctx context.Context, script, secretName string) (Response, error) { + uri := fmt.Sprintf("/accounts/%s/workers/scripts/%s/secrets/%s", api.AccountID, script, secretName) res, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil) if err != nil { return Response{}, err @@ -100,16 +68,8 @@ func (api *API) DeleteWorkersSecret(ctx context.Context, rc *ResourceContainer, // ListWorkersSecrets lists secrets for a given worker // API reference: https://api.cloudflare.com/ -func (api *API) ListWorkersSecrets(ctx context.Context, rc *ResourceContainer, params ListWorkersSecretsParams) (WorkersListSecretsResponse, error) { - if rc.Level != AccountRouteLevel { - return WorkersListSecretsResponse{}, ErrRequiredAccountLevelResourceContainer - } - - if rc.Identifier == "" { - return WorkersListSecretsResponse{}, ErrMissingAccountID - } - - uri := fmt.Sprintf("/accounts/%s/workers/scripts/%s/secrets", rc.Identifier, params.ScriptName) +func (api *API) ListWorkersSecrets(ctx context.Context, script string) (WorkersListSecretsResponse, error) { + uri := fmt.Sprintf("/accounts/%s/workers/scripts/%s/secrets", api.AccountID, script) res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) if err != nil { return WorkersListSecretsResponse{}, err diff --git a/workers_secrets_test.go b/workers_secrets_test.go index dc3ac99faa..c08ef71dd2 100644 --- a/workers_secrets_test.go +++ b/workers_secrets_test.go @@ -9,8 +9,8 @@ import ( "github.com/stretchr/testify/assert" ) -func TestSetWorkersSecret(t *testing.T) { - setup() +func TestWorkers_SetWorkersSecret(t *testing.T) { + setup(UsingAccount("foo")) defer teardown() response := `{ @@ -23,16 +23,16 @@ func TestSetWorkersSecret(t *testing.T) { "messages": [] }` - mux.HandleFunc("/accounts/"+testAccountID+"/workers/scripts/test-script/secrets", func(w http.ResponseWriter, r *http.Request) { + mux.HandleFunc("/accounts/foo/workers/scripts/test-script/secrets", 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/javascript") - fmt.Fprint(w, response) + fmt.Fprintf(w, response) //nolint }) req := &WorkersPutSecretRequest{ Name: "my-secret", Text: "super-secret", } - res, err := client.SetWorkersSecret(context.Background(), AccountIdentifier(testAccountID), SetWorkersSecretParams{ScriptName: "test-script", Secret: req}) + res, err := client.SetWorkersSecret(context.Background(), "test-script", req) want := WorkersPutSecretResponse{ successResponse, WorkersSecret{ @@ -46,8 +46,8 @@ func TestSetWorkersSecret(t *testing.T) { } } -func TestDeleteWorkersSecret(t *testing.T) { - setup() +func TestWorkers_DeleteWorkersSecret(t *testing.T) { + setup(UsingAccount("foo")) defer teardown() response := `{ @@ -60,13 +60,13 @@ func TestDeleteWorkersSecret(t *testing.T) { "messages": [] }` - mux.HandleFunc("/accounts/"+testAccountID+"/workers/scripts/test-script/secrets/my-secret", func(w http.ResponseWriter, r *http.Request) { + mux.HandleFunc("/accounts/foo/workers/scripts/test-script/secrets/my-secret", 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/javascript") - fmt.Fprint(w, response) + fmt.Fprintf(w, response) //nolint }) - res, err := client.DeleteWorkersSecret(context.Background(), AccountIdentifier(testAccountID), DeleteWorkersSecretParams{ScriptName: "test-script", SecretName: "my-secret"}) + res, err := client.DeleteWorkersSecret(context.Background(), "test-script", "my-secret") want := successResponse if assert.NoError(t, err) { @@ -74,8 +74,8 @@ func TestDeleteWorkersSecret(t *testing.T) { } } -func TestListWorkersSecret(t *testing.T) { - setup() +func TestWorkers_ListWorkersSecret(t *testing.T) { + setup(UsingAccount("foo")) defer teardown() response := `{ @@ -88,13 +88,13 @@ func TestListWorkersSecret(t *testing.T) { "messages": [] }` - mux.HandleFunc("/accounts/"+testAccountID+"/workers/scripts/test-script/secrets", func(w http.ResponseWriter, r *http.Request) { + mux.HandleFunc("/accounts/foo/workers/scripts/test-script/secrets", 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/javascript") - fmt.Fprint(w, response) + fmt.Fprintf(w, response) //nolint }) - res, err := client.ListWorkersSecrets(context.Background(), AccountIdentifier(testAccountID), ListWorkersSecretsParams{ScriptName: "test-script"}) + res, err := client.ListWorkersSecrets(context.Background(), "test-script") want := WorkersListSecretsResponse{ successResponse, []WorkersSecret{ diff --git a/workers_tail.go b/workers_tail.go index 0f074b0b66..478112b4a0 100644 --- a/workers_tail.go +++ b/workers_tail.go @@ -35,14 +35,13 @@ type ListWorkersTailResponse struct { Result WorkersTail } -// StartWorkersTail Starts a tail that receives logs and exception from a Worker. +// StartWorkersTail Starts a tail that receives logs and exception from a Worker // // API reference: https://api.cloudflare.com/#worker-tail-logs-start-tail func (api *API) StartWorkersTail(ctx context.Context, rc *ResourceContainer, scriptName string) (WorkersTail, error) { if rc.Identifier == "" { return WorkersTail{}, ErrMissingAccountID } - if scriptName == "" { return WorkersTail{}, ErrMissingScriptName } @@ -61,7 +60,7 @@ func (api *API) StartWorkersTail(ctx context.Context, rc *ResourceContainer, scr return workerstailResponse.Result, nil } -// ListWorkersTail Get list of tails currently deployed on a Worker. +// ListWorkersTail Get list of tails currently deployed on a worker // // API reference: https://api.cloudflare.com/#worker-tail-logs-list-tails func (api *API) ListWorkersTail(ctx context.Context, rc *ResourceContainer, params ListWorkersTailParameters) (WorkersTail, error) { @@ -87,7 +86,7 @@ func (api *API) ListWorkersTail(ctx context.Context, rc *ResourceContainer, para return workerstailResponse.Result, nil } -// DeleteWorkersTail Deletes a tail from a Worker. +// DeleteWorkersTail Deletes a tail from a Worker // // API reference: https://api.cloudflare.com/#worker-tail-logs-delete-tail func (api *API) DeleteWorkersTail(ctx context.Context, rc *ResourceContainer, scriptName, tailID string) error { diff --git a/workers_test.go b/workers_test.go index 647f1af327..1f5dbfbc08 100644 --- a/workers_test.go +++ b/workers_test.go @@ -89,7 +89,7 @@ const ( "errors": [], "messages": [] }` - listWorkerRouteResponse = `{ + listRouteEntResponseData = `{ "result": [ { "id": "e7a57d8746e74ae49c25994dadb421b1", @@ -103,8 +103,7 @@ const ( }, { "id": "2b5bf4240cd34c77852fac70b1bf745a", - "pattern": "app3.example.com/*", - "script": "test_script_3" + "pattern": "app3.example.com/*" } ], "success": true, @@ -321,30 +320,61 @@ func parseMultipartUpload(r *http.Request) (multipartUpload, error) { }, nil } -func TestDeleteWorker(t *testing.T) { +func TestWorkers_DeleteWorker(t *testing.T) { setup() defer teardown() - mux.HandleFunc("/accounts/"+testAccountID+"/workers/scripts/bar", func(w http.ResponseWriter, r *http.Request) { + mux.HandleFunc("/zones/foo/workers/script", 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/javascript") - fmt.Fprint(w, deleteWorkerResponseData) + fmt.Fprintf(w, deleteWorkerResponseData) //nolint }) + res, err := client.DeleteWorker(context.Background(), &WorkerRequestParams{ZoneID: "foo"}) + want := WorkerScriptResponse{ + Response: successResponse, + } - err := client.DeleteWorker(context.Background(), AccountIdentifier(testAccountID), DeleteWorkerParams{ScriptName: "bar"}) - assert.NoError(t, err) + if assert.NoError(t, err) { + assert.Equal(t, want.Response, res.Response) + } +} + +func TestWorkers_DeleteWorkerWithName(t *testing.T) { + setup(UsingAccount("foo")) + defer teardown() + + mux.HandleFunc("/accounts/foo/workers/scripts/bar", 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/javascript") + fmt.Fprintf(w, deleteWorkerResponseData) //nolint + }) + res, err := client.DeleteWorker(context.Background(), &WorkerRequestParams{ScriptName: "bar"}) + want := WorkerScriptResponse{ + Response: successResponse, + } + if assert.NoError(t, err) { + assert.Equal(t, want.Response, res.Response) + } +} + +func TestWorkers_DeleteWorkerWithNameErrorsWithoutAccountId(t *testing.T) { + setup() + defer teardown() + + _, err := client.DeleteWorker(context.Background(), &WorkerRequestParams{ScriptName: "bar"}) + assert.Error(t, err) } -func TestGetWorker(t *testing.T) { +func TestWorkers_DownloadWorker(t *testing.T) { setup() defer teardown() - mux.HandleFunc("/accounts/"+testAccountID+"/workers/scripts/foo", func(w http.ResponseWriter, r *http.Request) { + mux.HandleFunc("/zones/foo/workers/script", 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/javascript") - fmt.Fprint(w, workerScript) + fmt.Fprintf(w, workerScript) //nolint }) - res, err := client.GetWorker(context.Background(), AccountIdentifier(testAccountID), "foo") + res, err := client.DownloadWorker(context.Background(), &WorkerRequestParams{ZoneID: "foo"}) want := WorkerScriptResponse{ successResponse, false, @@ -356,41 +386,67 @@ func TestGetWorker(t *testing.T) { } } -func TestGetWorker_Module(t *testing.T) { +func TestWorkers_DownloadWorkerWithName(t *testing.T) { + setup(UsingAccount("foo")) + defer teardown() + + mux.HandleFunc("/accounts/foo/workers/scripts/bar", 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/javascript") + fmt.Fprintf(w, workerScript) //nolint + }) + res, err := client.DownloadWorker(context.Background(), &WorkerRequestParams{ScriptName: "bar"}) + want := WorkerScriptResponse{ + successResponse, + false, + WorkerScript{ + Script: workerScript, + }} + if assert.NoError(t, err) { + assert.Equal(t, want.Script, res.Script) + } +} + +func TestWorkers_DownloadWorkerWithNameErrorsWithoutAccountId(t *testing.T) { setup() defer teardown() - mux.HandleFunc("/accounts/"+testAccountID+"/workers/scripts/foo", func(w http.ResponseWriter, r *http.Request) { + _, err := client.DownloadWorker(context.Background(), &WorkerRequestParams{ScriptName: "bar"}) + assert.Error(t, err) +} + +func TestWorkers_DownloadWorkerModule(t *testing.T) { + setup(UsingAccount("foo")) + defer teardown() + + mux.HandleFunc("/accounts/foo/workers/scripts/bar", 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", "multipart/form-data; boundary=workermodulescriptdownload") - fmt.Fprint(w, workerModuleScriptDownloadResponse) + fmt.Fprintf(w, workerModuleScriptDownloadResponse) //nolint }) - - res, err := client.GetWorker(context.Background(), AccountIdentifier(testAccountID), "foo") + res, err := client.DownloadWorker(context.Background(), &WorkerRequestParams{ScriptName: "bar"}) want := WorkerScriptResponse{ successResponse, true, WorkerScript{ Script: workerModuleScript, - }, - } - + }} if assert.NoError(t, err) { assert.Equal(t, want.Script, res.Script) } } -func TestListWorkers(t *testing.T) { - setup() +func TestWorkers_ListWorkerScripts(t *testing.T) { + setup(UsingAccount("foo")) defer teardown() - mux.HandleFunc("/accounts/"+testAccountID+"/workers/scripts", func(w http.ResponseWriter, r *http.Request) { + mux.HandleFunc("/accounts/foo/workers/scripts", 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.Fprint(w, listWorkersResponseData) + fmt.Fprintf(w, listWorkersResponseData) //nolint }) - res, _, err := client.ListWorkers(context.Background(), AccountIdentifier(testAccountID), ListWorkersParams{}) + res, err := client.ListWorkerScripts(context.Background()) sampleDate, _ := time.Parse(time.RFC3339Nano, "2018-04-22T17:10:48.938097Z") want := []WorkerMetaData{ { @@ -411,18 +467,18 @@ func TestListWorkers(t *testing.T) { } } -func TestUploadWorker_Basic(t *testing.T) { +func TestWorkers_UploadWorker(t *testing.T) { setup() defer teardown() - mux.HandleFunc("/accounts/"+testAccountID+"/workers/scripts/foo", func(w http.ResponseWriter, r *http.Request) { + mux.HandleFunc("/zones/foo/workers/script", func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, http.MethodPut, r.Method, "Expected method 'PUT', got %s", r.Method) contentTypeHeader := r.Header.Get("content-type") assert.Equal(t, "application/javascript", contentTypeHeader, "Expected content-type request header to be 'application/javascript', got %s", contentTypeHeader) w.Header().Set("content-type", "application/json") - fmt.Fprint(w, uploadWorkerResponseData) + fmt.Fprintf(w, uploadWorkerResponseData) //nolint }) - res, err := client.UploadWorker(context.Background(), AccountIdentifier(testAccountID), CreateWorkerParams{ScriptName: "foo", Script: workerScript}) + res, err := client.UploadWorker(context.Background(), &WorkerRequestParams{ZoneID: "foo"}, &WorkerScriptParams{Script: workerScript}) formattedTime, _ := time.Parse(time.RFC3339Nano, "2018-06-09T15:17:01.989141Z") want := WorkerScriptResponse{ successResponse, @@ -440,11 +496,11 @@ func TestUploadWorker_Basic(t *testing.T) { } } -func TestUploadWorker_Module(t *testing.T) { +func TestWorkers_UploadWorkerAsModule(t *testing.T) { setup() defer teardown() - mux.HandleFunc("/accounts/"+testAccountID+"/workers/scripts/foo", func(w http.ResponseWriter, r *http.Request) { + mux.HandleFunc("/zones/foo/workers/script", func(w http.ResponseWriter, r *http.Request) { mpUpload, err := parseMultipartUpload(r) assert.NoError(t, err) @@ -459,9 +515,9 @@ func TestUploadWorker_Module(t *testing.T) { assert.Equal(t, expectedContentType, contentTypeHeader, "Expected content-type request header to be %s, got %s", expectedContentType, contentTypeHeader) w.Header().Set("content-type", "application/json") - fmt.Fprint(w, uploadWorkerModuleResponseData) + fmt.Fprintf(w, uploadWorkerModuleResponseData) //nolint }) - res, err := client.UploadWorker(context.Background(), AccountIdentifier(testAccountID), CreateWorkerParams{ScriptName: "foo", Script: workerModuleScript, Module: true}) + res, err := client.UploadWorker(context.Background(), &WorkerRequestParams{ZoneID: "foo"}, &WorkerScriptParams{Script: workerModuleScript, Module: true}) formattedTime, _ := time.Parse(time.RFC3339Nano, "2018-06-09T15:17:01.989141Z") want := WorkerScriptResponse{ successResponse, @@ -479,10 +535,76 @@ func TestUploadWorker_Module(t *testing.T) { } } -func TestUploadWorker_WithDurableObjectBinding(t *testing.T) { +func TestWorkers_UploadWorkerWithName(t *testing.T) { + setup(UsingAccount("foo")) + defer teardown() + + mux.HandleFunc("/accounts/foo/workers/scripts/bar", func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodPut, r.Method, "Expected method 'PUT', got %s", r.Method) + contentTypeHeader := r.Header.Get("content-type") + assert.Equal(t, "application/javascript", contentTypeHeader, "Expected content-type request header to be 'application/javascript', got %s", contentTypeHeader) + w.Header().Set("content-type", "application/json") + fmt.Fprintf(w, uploadWorkerResponseData) //nolint + }) + res, err := client.UploadWorker(context.Background(), &WorkerRequestParams{ScriptName: "bar"}, &WorkerScriptParams{Script: workerScript}) + formattedTime, _ := time.Parse(time.RFC3339Nano, "2018-06-09T15:17:01.989141Z") + want := WorkerScriptResponse{ + successResponse, + false, + WorkerScript{ + Script: workerScript, + WorkerMetaData: WorkerMetaData{ + ETAG: "279cf40d86d70b82f6cd3ba90a646b3ad995912da446836d7371c21c6a43977a", + Size: 191, + ModifiedOn: formattedTime, + }, + }} + if assert.NoError(t, err) { + assert.Equal(t, want, res) + } +} + +func TestWorkers_UploadWorkerSingleScriptWithAccount(t *testing.T) { + setup(UsingAccount("foo")) + defer teardown() + + mux.HandleFunc("/zones/foo/workers/script", func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodPut, r.Method, "Expected method 'PUT', got %s", r.Method) + contentTypeHeader := r.Header.Get("content-type") + assert.Equal(t, "application/javascript", contentTypeHeader, "Expected content-type request header to be 'application/javascript', got %s", contentTypeHeader) + w.Header().Set("content-type", "application/json") + fmt.Fprintf(w, uploadWorkerResponseData) //nolint + }) + res, err := client.UploadWorker(context.Background(), &WorkerRequestParams{ZoneID: "foo"}, &WorkerScriptParams{Script: workerScript}) + formattedTime, _ := time.Parse(time.RFC3339Nano, "2018-06-09T15:17:01.989141Z") + want := WorkerScriptResponse{ + successResponse, + false, + WorkerScript{ + Script: workerScript, + WorkerMetaData: WorkerMetaData{ + ETAG: "279cf40d86d70b82f6cd3ba90a646b3ad995912da446836d7371c21c6a43977a", + Size: 191, + ModifiedOn: formattedTime, + }, + }} + if assert.NoError(t, err) { + assert.Equal(t, want, res) + } +} + +func TestWorkers_UploadWorkerWithNameErrorsWithoutAccountId(t *testing.T) { setup() defer teardown() + _, err := client.UploadWorker(context.Background(), &WorkerRequestParams{ScriptName: "bar"}, &WorkerScriptParams{Script: workerScript}) + assert.Error(t, err) +} + +func TestWorkers_UploadWorkerWithDurableObjectBinding(t *testing.T) { + setup(UsingAccount("foo")) + defer teardown() + handler := func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, http.MethodPut, r.Method, "Expected method 'PUT', got %s", r.Method) @@ -501,27 +623,25 @@ func TestUploadWorker_WithDurableObjectBinding(t *testing.T) { assert.Equal(t, expectedBindings, mpUpload.BindingMeta) w.Header().Set("content-type", "application/json") - fmt.Fprint(w, uploadWorkerResponseData) + fmt.Fprintf(w, uploadWorkerResponseData) //nolint } + mux.HandleFunc("/accounts/foo/workers/scripts/bar", handler) - mux.HandleFunc("/accounts/"+testAccountID+"/workers/scripts/bar", handler) - - _, err := client.UploadWorker(context.Background(), AccountIdentifier(testAccountID), CreateWorkerParams{ - ScriptName: "bar", - Script: workerScript, + scriptParams := WorkerScriptParams{ + Script: workerScript, Bindings: map[string]WorkerBinding{ "b1": WorkerDurableObjectBinding{ ClassName: "TheClass", ScriptName: "the_script", }, }, - }) - + } + _, err := client.UploadWorkerWithBindings(context.Background(), &WorkerRequestParams{ScriptName: "bar"}, &scriptParams) assert.NoError(t, err) } -func TestUploadWorker_WithInheritBinding(t *testing.T) { - setup() +func TestWorkers_UploadWorkerWithInheritBinding(t *testing.T) { + setup(UsingAccount("foo")) defer teardown() // Setup route handler for both single-script and multi-script @@ -546,10 +666,22 @@ func TestUploadWorker_WithInheritBinding(t *testing.T) { assert.Equal(t, expectedBindings, mpUpload.BindingMeta) w.Header().Set("content-type", "application/json") - fmt.Fprint(w, uploadWorkerResponseData) + fmt.Fprintf(w, uploadWorkerResponseData) //nolint + } + mux.HandleFunc("/zones/foo/workers/script", handler) + mux.HandleFunc("/accounts/foo/workers/scripts/bar", handler) + + scriptParams := WorkerScriptParams{ + Script: workerScript, + Bindings: map[string]WorkerBinding{ + "b1": WorkerInheritBinding{}, + "b2": WorkerInheritBinding{ + OldName: "old_binding_name", + }, + }, } - mux.HandleFunc("/accounts/"+testAccountID+"/workers/scripts/bar", handler) + // Expected response formattedTime, _ := time.Parse(time.RFC3339Nano, "2018-06-09T15:17:01.989141Z") want := WorkerScriptResponse{ successResponse, @@ -563,22 +695,21 @@ func TestUploadWorker_WithInheritBinding(t *testing.T) { }, }} - res, err := client.UploadWorker(context.Background(), AccountIdentifier(testAccountID), CreateWorkerParams{ - ScriptName: "bar", - Script: workerScript, - Bindings: map[string]WorkerBinding{ - "b1": WorkerInheritBinding{}, - "b2": WorkerInheritBinding{ - OldName: "old_binding_name", - }, - }}) + // Test single-script + res, err := client.UploadWorkerWithBindings(context.Background(), &WorkerRequestParams{ZoneID: "foo"}, &scriptParams) + if assert.NoError(t, err) { + assert.Equal(t, want, res) + } + + // Test multi-script + res, err = client.UploadWorkerWithBindings(context.Background(), &WorkerRequestParams{ScriptName: "bar"}, &scriptParams) if assert.NoError(t, err) { assert.Equal(t, want, res) } } -func TestUploadWorker_WithKVBinding(t *testing.T) { - setup() +func TestWorkers_UploadWorkerWithKVBinding(t *testing.T) { + setup(UsingAccount("foo")) defer teardown() handler := func(w http.ResponseWriter, r *http.Request) { @@ -598,23 +729,24 @@ func TestUploadWorker_WithKVBinding(t *testing.T) { assert.Equal(t, expectedBindings, mpUpload.BindingMeta) w.Header().Set("content-type", "application/json") - fmt.Fprint(w, uploadWorkerResponseData) + fmt.Fprintf(w, uploadWorkerResponseData) //nolint } - mux.HandleFunc("/accounts/"+testAccountID+"/workers/scripts/bar", handler) + mux.HandleFunc("/accounts/foo/workers/scripts/bar", handler) - _, err := client.UploadWorker(context.Background(), AccountIdentifier(testAccountID), CreateWorkerParams{ - ScriptName: "bar", - Script: workerScript, + scriptParams := WorkerScriptParams{ + Script: workerScript, Bindings: map[string]WorkerBinding{ "b1": WorkerKvNamespaceBinding{ NamespaceID: "test-namespace", }, - }}) + }, + } + _, err := client.UploadWorkerWithBindings(context.Background(), &WorkerRequestParams{ScriptName: "bar"}, &scriptParams) assert.NoError(t, err) } -func TestUploadWorker_WithWasmBinding(t *testing.T) { - setup() +func TestWorkers_UploadWorkerWithWasmBinding(t *testing.T) { + setup(UsingAccount("foo")) defer teardown() handler := func(w http.ResponseWriter, r *http.Request) { @@ -639,25 +771,24 @@ func TestUploadWorker_WithWasmBinding(t *testing.T) { assert.Equal(t, []byte("fake-wasm"), wasmContent) w.Header().Set("content-type", "application/json") - fmt.Fprint(w, uploadWorkerResponseData) + fmt.Fprintf(w, uploadWorkerResponseData) //nolint } - mux.HandleFunc("/accounts/"+testAccountID+"/workers/scripts/bar", handler) + mux.HandleFunc("/accounts/foo/workers/scripts/bar", handler) - _, err := client.UploadWorker(context.Background(), AccountIdentifier(testAccountID), CreateWorkerParams{ - ScriptName: "bar", - Script: workerScript, + scriptParams := WorkerScriptParams{ + Script: workerScript, Bindings: map[string]WorkerBinding{ "b1": WorkerWebAssemblyBinding{ Module: strings.NewReader("fake-wasm"), }, }, - }) - + } + _, err := client.UploadWorkerWithBindings(context.Background(), &WorkerRequestParams{ScriptName: "bar"}, &scriptParams) assert.NoError(t, err) } -func TestUploadWorker_WithPlainTextBinding(t *testing.T) { - setup() +func TestWorkers_UploadWorkerWithPlainTextBinding(t *testing.T) { + setup(UsingAccount("foo")) defer teardown() handler := func(w http.ResponseWriter, r *http.Request) { @@ -677,28 +808,27 @@ func TestUploadWorker_WithPlainTextBinding(t *testing.T) { assert.Equal(t, expectedBindings, mpUpload.BindingMeta) w.Header().Set("content-type", "application/json") - fmt.Fprint(w, uploadWorkerResponseData) + fmt.Fprintf(w, uploadWorkerResponseData) //nolint } - mux.HandleFunc("/accounts/"+testAccountID+"/workers/scripts/bar", handler) + mux.HandleFunc("/accounts/foo/workers/scripts/bar", handler) - _, err := client.UploadWorker(context.Background(), AccountIdentifier(testAccountID), CreateWorkerParams{ - ScriptName: "bar", - Script: workerScript, + scriptParams := WorkerScriptParams{ + Script: workerScript, Bindings: map[string]WorkerBinding{ "b1": WorkerPlainTextBinding{ Text: "plain text value", }, }, - }) - + } + _, err := client.UploadWorkerWithBindings(context.Background(), &WorkerRequestParams{ScriptName: "bar"}, &scriptParams) assert.NoError(t, err) } -func TestUploadWorker_ModuleWithPlainTextBinding(t *testing.T) { - setup() +func TestWorkers_UploadWorkerAsModuleWithPlainTextBinding(t *testing.T) { + setup(UsingAccount("foo")) defer teardown() - mux.HandleFunc("/accounts/"+testAccountID+"/workers/scripts/bar", func(w http.ResponseWriter, r *http.Request) { + mux.HandleFunc("/accounts/foo/workers/scripts/bar", func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, http.MethodPut, r.Method, "Expected method 'PUT', got %s", r.Method) mpUpload, err := parseMultipartUpload(r) @@ -723,25 +853,24 @@ func TestUploadWorker_ModuleWithPlainTextBinding(t *testing.T) { assert.Equal(t, expectedContentDisposition, contentDispositonHeader, "Expected content-disposition request header to be %s, got %s", expectedContentDisposition, contentDispositonHeader) w.Header().Set("content-type", "application/json") - fmt.Fprint(w, uploadWorkerModuleResponseData) + fmt.Fprintf(w, uploadWorkerModuleResponseData) //nolint }) - _, err := client.UploadWorker(context.Background(), AccountIdentifier(testAccountID), CreateWorkerParams{ - ScriptName: "bar", - Script: workerModuleScript, - Module: true, + scriptParams := WorkerScriptParams{ + Script: workerModuleScript, + Module: true, Bindings: map[string]WorkerBinding{ "b1": WorkerPlainTextBinding{ Text: "plain text value", }, }, - }) - + } + _, err := client.UploadWorkerWithBindings(context.Background(), &WorkerRequestParams{ScriptName: "bar"}, &scriptParams) assert.NoError(t, err) } -func TestUploadWorker_WithSecretTextBinding(t *testing.T) { - setup() +func TestWorkers_UploadWorkerWithSecretTextBinding(t *testing.T) { + setup(UsingAccount("foo")) defer teardown() handler := func(w http.ResponseWriter, r *http.Request) { @@ -761,24 +890,24 @@ func TestUploadWorker_WithSecretTextBinding(t *testing.T) { assert.Equal(t, expectedBindings, mpUpload.BindingMeta) w.Header().Set("content-type", "application/json") - fmt.Fprint(w, uploadWorkerResponseData) + fmt.Fprintf(w, uploadWorkerResponseData) //nolint } - mux.HandleFunc("/accounts/"+testAccountID+"/workers/scripts/bar", handler) + mux.HandleFunc("/accounts/foo/workers/scripts/bar", handler) - _, err := client.UploadWorker(context.Background(), AccountIdentifier(testAccountID), CreateWorkerParams{ - ScriptName: "bar", - Script: workerScript, + scriptParams := WorkerScriptParams{ + Script: workerScript, Bindings: map[string]WorkerBinding{ "b1": WorkerSecretTextBinding{ Text: "secret text value", }, }, - }) + } + _, err := client.UploadWorkerWithBindings(context.Background(), &WorkerRequestParams{ScriptName: "bar"}, &scriptParams) assert.NoError(t, err) } -func TestUploadWorker_WithServiceBinding(t *testing.T) { - setup() +func TestWorkers_UploadWorkerWithServiceBinding(t *testing.T) { + setup(UsingAccount("foo")) defer teardown() handler := func(w http.ResponseWriter, r *http.Request) { @@ -804,22 +933,407 @@ func TestUploadWorker_WithServiceBinding(t *testing.T) { assert.Equal(t, expectedBindings, mpUpload.BindingMeta) w.Header().Set("content-type", "application/json") - fmt.Fprint(w, uploadWorkerResponseData) + fmt.Fprintf(w, uploadWorkerResponseData) //nolint } - mux.HandleFunc("/accounts/"+testAccountID+"/workers/scripts/bar", handler) + mux.HandleFunc("/accounts/foo/workers/scripts/bar", handler) - _, err := client.UploadWorker(context.Background(), AccountIdentifier(testAccountID), CreateWorkerParams{ - ScriptName: "bar", - Script: workerScript, + environment := "the_environment" + scriptParams := WorkerScriptParams{ + Script: workerScript, Bindings: map[string]WorkerBinding{ "b1": WorkerServiceBinding{ Service: "the_service", }, "b2": WorkerServiceBinding{ Service: "the_service", - Environment: StringPtr("the_environment"), + Environment: &environment, }, }, + } + _, err := client.UploadWorkerWithBindings(context.Background(), &WorkerRequestParams{ScriptName: "bar"}, &scriptParams) + assert.NoError(t, err) +} + +func TestWorkers_CreateWorkerRoute(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/zones/foo/workers/filters", func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodPost, r.Method, "Expected method 'POST', got %s", r.Method) + w.Header().Set("content-type", "application/json") + fmt.Fprintf(w, createWorkerRouteResponse) //nolint }) + route := WorkerRoute{Pattern: "app1.example.com/*", Enabled: true} + res, err := client.CreateWorkerRoute(context.Background(), "foo", route) + want := WorkerRouteResponse{successResponse, WorkerRoute{ID: "e7a57d8746e74ae49c25994dadb421b1"}} + if assert.NoError(t, err) { + assert.Equal(t, want, res) + } +} + +func TestWorkers_CreateWorkerRouteEnt(t *testing.T) { + setup(UsingAccount("foo")) + defer teardown() + + mux.HandleFunc("/zones/foo/workers/routes", func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodPost, r.Method, "Expected method 'POST', got %s", r.Method) + w.Header().Set("content-type", "application/json") + fmt.Fprintf(w, createWorkerRouteResponse) //nolint + }) + route := WorkerRoute{Pattern: "app1.example.com/*", Script: "test_script"} + res, err := client.CreateWorkerRoute(context.Background(), "foo", route) + want := WorkerRouteResponse{successResponse, WorkerRoute{ID: "e7a57d8746e74ae49c25994dadb421b1"}} + if assert.NoError(t, err) { + assert.Equal(t, want, res) + } +} + +func TestWorkers_CreateWorkerRouteSingleScriptWithAccount(t *testing.T) { + setup(UsingAccount("foo")) + defer teardown() + + mux.HandleFunc("/zones/foo/workers/filters", func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodPost, r.Method, "Expected method 'POST', got %s", r.Method) + w.Header().Set("content-type", "application/json") + fmt.Fprintf(w, createWorkerRouteResponse) //nolint + }) + route := WorkerRoute{Pattern: "app1.example.com/*", Enabled: true} + res, err := client.CreateWorkerRoute(context.Background(), "foo", route) + want := WorkerRouteResponse{successResponse, WorkerRoute{ID: "e7a57d8746e74ae49c25994dadb421b1"}} + if assert.NoError(t, err) { + assert.Equal(t, want, res) + } +} + +func TestWorkers_CreateWorkerRouteErrorsWhenMixingSingleAndMultiScriptProperties(t *testing.T) { + setup(UsingAccount("foo")) + defer teardown() + + route := WorkerRoute{Pattern: "app1.example.com/*", Script: "test_script", Enabled: true} + _, err := client.CreateWorkerRoute(context.Background(), "foo", route) + assert.EqualError(t, err, "Only `Script` or `Enabled` may be specified for a WorkerRoute, not both") +} + +func TestWorkers_CreateWorkerRouteWithNoScript(t *testing.T) { + setup(UsingAccount("foo")) + + mux.HandleFunc("/zones/foo/workers/routes", func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodPost, r.Method, "Expected method 'POST', got %s", r.Method) + w.Header().Set("content-type", "application/json") + fmt.Fprintf(w, createWorkerRouteResponse) //nolint + }) + + route := WorkerRoute{Pattern: "app1.example.com/*"} + _, err := client.CreateWorkerRoute(context.Background(), "foo", route) assert.NoError(t, err) } + +func TestWorkers_DeleteWorkerRoute(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/zones/foo/workers/routes/e7a57d8746e74ae49c25994dadb421b1", 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, deleteWorkerRouteResponseData) //nolint + }) + res, err := client.DeleteWorkerRoute(context.Background(), "foo", "e7a57d8746e74ae49c25994dadb421b1") + want := WorkerRouteResponse{successResponse, + WorkerRoute{ + ID: "e7a57d8746e74ae49c25994dadb421b1", + }} + if assert.NoError(t, err) { + assert.Equal(t, want, res) + } +} + +func TestWorkers_DeleteWorkerRouteEnt(t *testing.T) { + setup(UsingAccount("foo")) + defer teardown() + + mux.HandleFunc("/zones/foo/workers/routes/e7a57d8746e74ae49c25994dadb421b1", 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, deleteWorkerRouteResponseData) //nolint + }) + res, err := client.DeleteWorkerRoute(context.Background(), "foo", "e7a57d8746e74ae49c25994dadb421b1") + want := WorkerRouteResponse{successResponse, + WorkerRoute{ + ID: "e7a57d8746e74ae49c25994dadb421b1", + }} + if assert.NoError(t, err) { + assert.Equal(t, want, res) + } +} + +func TestWorkers_ListWorkerRoutes(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/zones/foo/workers/filters", 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, listRouteResponseData) //nolint + }) + + res, err := client.ListWorkerRoutes(context.Background(), "foo") + want := WorkerRoutesResponse{successResponse, + []WorkerRoute{ + {ID: "e7a57d8746e74ae49c25994dadb421b1", Pattern: "app1.example.com/*", Enabled: true}, + {ID: "f8b68e9857f85bf59c25994dadb421b1", Pattern: "app2.example.com/*", Enabled: false}, + }, + } + if assert.NoError(t, err) { + assert.Equal(t, want, res) + } +} + +func TestWorkers_ListWorkerRoutesEnt(t *testing.T) { + setup(UsingAccount("foo")) + defer teardown() + + mux.HandleFunc("/zones/foo/workers/routes", 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, listRouteEntResponseData) //nolint + }) + + res, err := client.ListWorkerRoutes(context.Background(), "foo") + want := WorkerRoutesResponse{successResponse, + []WorkerRoute{ + {ID: "e7a57d8746e74ae49c25994dadb421b1", Pattern: "app1.example.com/*", Script: "test_script_1", Enabled: true}, + {ID: "f8b68e9857f85bf59c25994dadb421b1", Pattern: "app2.example.com/*", Script: "test_script_2", Enabled: true}, + {ID: "2b5bf4240cd34c77852fac70b1bf745a", Pattern: "app3.example.com/*", Script: "", Enabled: false}, + }, + } + if assert.NoError(t, err) { + assert.Equal(t, want, res) + } +} + +func TestWorkers_GetWorkerRoute(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/zones/foo/workers/routes/1234", 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, getRouteResponseData) //nolint + }) + + res, err := client.GetWorkerRoute(context.Background(), "foo", "1234") + want := WorkerRouteResponse{successResponse, + WorkerRoute{ + ID: "e7a57d8746e74ae49c25994dadb421b1", + Pattern: "app1.example.com/*", + Script: "script-name"}, + } + if assert.NoError(t, err) { + assert.Equal(t, want, res) + } +} + +func TestWorkers_UpdateWorkerRoute(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/zones/foo/workers/filters/e7a57d8746e74ae49c25994dadb421b1", 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, updateWorkerRouteResponse) //nolint + }) + route := WorkerRoute{Pattern: "app3.example.com/*", Enabled: true} + res, err := client.UpdateWorkerRoute(context.Background(), "foo", "e7a57d8746e74ae49c25994dadb421b1", route) + want := WorkerRouteResponse{successResponse, + WorkerRoute{ + ID: "e7a57d8746e74ae49c25994dadb421b1", + Pattern: "app3.example.com/*", + Enabled: true, + }} + if assert.NoError(t, err) { + assert.Equal(t, want, res) + } +} + +func TestWorkers_UpdateWorkerRouteEnt(t *testing.T) { + setup(UsingAccount("foo")) + defer teardown() + + mux.HandleFunc("/zones/foo/workers/routes/e7a57d8746e74ae49c25994dadb421b1", 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, updateWorkerRouteEntResponse) //nolint + }) + route := WorkerRoute{Pattern: "app3.example.com/*", Script: "test_script_1"} + res, err := client.UpdateWorkerRoute(context.Background(), "foo", "e7a57d8746e74ae49c25994dadb421b1", route) + want := WorkerRouteResponse{successResponse, + WorkerRoute{ + ID: "e7a57d8746e74ae49c25994dadb421b1", + Pattern: "app3.example.com/*", + Script: "test_script_1", + }} + if assert.NoError(t, err) { + assert.Equal(t, want, res) + } +} + +func TestWorkers_UpdateWorkerRouteSingleScriptWithAccount(t *testing.T) { + setup(UsingAccount("foo")) + defer teardown() + + mux.HandleFunc("/zones/foo/workers/filters/e7a57d8746e74ae49c25994dadb421b1", 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, updateWorkerRouteEntResponse) //nolint + }) + route := WorkerRoute{Pattern: "app3.example.com/*", Enabled: true} + res, err := client.UpdateWorkerRoute(context.Background(), "foo", "e7a57d8746e74ae49c25994dadb421b1", route) + want := WorkerRouteResponse{successResponse, + WorkerRoute{ + ID: "e7a57d8746e74ae49c25994dadb421b1", + Pattern: "app3.example.com/*", + Script: "test_script_1", + }} + if assert.NoError(t, err) { + assert.Equal(t, want, res) + } +} + +func TestWorkers_ListWorkerBindingsMultiScript(t *testing.T) { + setup(UsingAccount("foo")) + defer teardown() + + mux.HandleFunc("/accounts/foo/workers/scripts/my-script/bindings", 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, listBindingsResponseData) //nolint + }) + + mux.HandleFunc("/accounts/foo/workers/scripts/my-script/bindings/MY_WASM/content", 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/wasm") + _, _ = w.Write([]byte("mock multi-script wasm")) + }) + + res, err := client.ListWorkerBindings(context.Background(), &WorkerRequestParams{ + ScriptName: "my-script", + }) + assert.NoError(t, err) + + assert.Equal(t, successResponse, res.Response) + assert.Equal(t, 8, len(res.BindingList)) + + assert.Equal(t, res.BindingList[0], WorkerBindingListItem{ + Name: "MY_KV", + Binding: WorkerKvNamespaceBinding{ + NamespaceID: "89f5f8fd93f94cb98473f6f421aa3b65", + }, + }) + assert.Equal(t, WorkerKvNamespaceBindingType, res.BindingList[0].Binding.Type()) + + assert.Equal(t, "MY_WASM", res.BindingList[1].Name) + wasmBinding := res.BindingList[1].Binding.(WorkerWebAssemblyBinding) + wasmModuleContent, err := io.ReadAll(wasmBinding.Module) + assert.NoError(t, err) + assert.Equal(t, []byte("mock multi-script wasm"), wasmModuleContent) + assert.Equal(t, WorkerWebAssemblyBindingType, res.BindingList[1].Binding.Type()) + + assert.Equal(t, res.BindingList[2], WorkerBindingListItem{ + Name: "MY_PLAIN_TEXT", + Binding: WorkerPlainTextBinding{ + Text: "text", + }, + }) + assert.Equal(t, WorkerPlainTextBindingType, res.BindingList[2].Binding.Type()) + + assert.Equal(t, res.BindingList[3], WorkerBindingListItem{ + Name: "MY_SECRET_TEXT", + Binding: WorkerSecretTextBinding{}, + }) + assert.Equal(t, WorkerSecretTextBindingType, res.BindingList[3].Binding.Type()) + + environment := "MY_ENVIRONMENT" + assert.Equal(t, res.BindingList[4], WorkerBindingListItem{ + Name: "MY_SERVICE_BINDING", + Binding: WorkerServiceBinding{ + Service: "MY_SERVICE", + Environment: &environment, + }, + }) + assert.Equal(t, WorkerServiceBindingType, res.BindingList[4].Binding.Type()) + + assert.Equal(t, res.BindingList[5], WorkerBindingListItem{ + Name: "MY_NEW_BINDING", + Binding: WorkerInheritBinding{}, + }) + assert.Equal(t, WorkerInheritBindingType, res.BindingList[5].Binding.Type()) + + assert.Equal(t, res.BindingList[6], WorkerBindingListItem{ + Name: "MY_BUCKET", + Binding: WorkerR2BucketBinding{ + BucketName: "bucket", + }, + }) + assert.Equal(t, WorkerR2BucketBindingType, res.BindingList[6].Binding.Type()) + + assert.Equal(t, res.BindingList[7], WorkerBindingListItem{ + Name: "MY_DATASET", + Binding: WorkerAnalyticsEngineBinding{ + Dataset: "my_dataset", + }, + }) + assert.Equal(t, WorkerAnalyticsEngineBindingType, res.BindingList[7].Binding.Type()) +} + +func TestWorkers_UpdateWorkerRouteErrorsWhenMixingSingleAndMultiScriptProperties(t *testing.T) { + setup(UsingAccount("foo")) + defer teardown() + + route := WorkerRoute{Pattern: "app1.example.com/*", Script: "test_script", Enabled: true} + _, err := client.UpdateWorkerRoute(context.Background(), "foo", "e7a57d8746e74ae49c25994dadb421b1", route) + assert.EqualError(t, err, "Only `Script` or `Enabled` may be specified for a WorkerRoute, not both") +} + +func TestWorkers_UpdateWorkerRouteWithNoScript(t *testing.T) { + setup(UsingAccount("foo")) + defer teardown() + + mux.HandleFunc("/zones/foo/workers/routes/e7a57d8746e74ae49c25994dadb421b1", 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, updateWorkerRouteEntResponse) //nolint + }) + + route := WorkerRoute{Pattern: "app1.example.com/*"} + _, err := client.UpdateWorkerRoute(context.Background(), "foo", "e7a57d8746e74ae49c25994dadb421b1", route) + assert.NoError(t, err) +} + +func TestWorkers_AttachWorkerToDomain(t *testing.T) { + setup(UsingAccount(testAccountID)) + defer teardown() + + mux.HandleFunc("/accounts/"+testAccountID+"/workers/domains", 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, attachWorkerToDomainResponse) //nolint + }) + res, err := client.AttachWorkerToDomain(context.Background(), AccountIdentifier(testAccountID), &WorkerDomainParams{ + ZoneID: testZoneID, + Hostname: "app4.example.com", + Service: "test_script_1", + Environment: "production", + }) + want := WorkerDomainResponse{ + successResponse, + WorkerDomainResult{ + ID: "e7a57d8746e74ae49c25994dadb421b1", + ZoneID: testZoneID, + Service: "test_script_1", + Hostname: "api4.example.com", + Environment: "production", + }} + if assert.NoError(t, err) { + assert.Equal(t, want.Response, res.Response) + } +}