diff --git a/workers.go b/workers.go index dd4df618ce..b762acad8f 100644 --- a/workers.go +++ b/workers.go @@ -3,10 +3,7 @@ package cloudflare import ( "bytes" "context" - rand "crypto/rand" - "encoding/hex" "encoding/json" - "errors" "fmt" "io" "mime" @@ -23,6 +20,19 @@ type WorkerRequestParams struct { ScriptName string } +type CreateWorkerParams struct { + Name 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 { Script string @@ -40,9 +50,7 @@ type WorkerScriptParams struct { // // API reference: https://api.cloudflare.com/#worker-routes-properties type WorkerRoute struct { - 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"` } @@ -87,351 +95,46 @@ type WorkerScriptResponse struct { WorkerScript `json:"result"` } -// 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" -) - -// 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 ListWorkersParams 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 +type DeleteWorkerParams struct { 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 -} - -// 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) +func (api *API) DeleteWorker(ctx context.Context, rc *ResourceContainer, params DeleteWorkerParams) error { + uri := fmt.Sprintf("/accounts/%s/workers/scripts/%s", rc.Identifier, params.ScriptName) res, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil) + var r WorkerScriptResponse if err != nil { - return r, err + return err } + err = json.Unmarshal(res, &r) if err != nil { - return r, fmt.Errorf("%s: %w", errUnmarshalError, err) + return fmt.Errorf("%s: %w", errUnmarshalError, err) } - return r, nil + + return nil } -// DeleteWorkerWithName deletes worker for a zone. -// Sccount must be specified as api option https://godoc.org/github.com/cloudflare/cloudflare-go#UsingAccount +// GetWorker 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) 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 +func (api *API) GetWorker(ctx context.Context, rc *ResourceContainer, scriptName string) (WorkerScriptResponse, error) { + if rc.Level != AccountRouteLevel { + return WorkerScriptResponse{}, fmt.Errorf(errInvalidResourceContainerAccess, ZoneRouteLevel) } - err = json.Unmarshal(res, &r) - if err != nil { - return r, fmt.Errorf("%s: %w", errUnmarshalError, err) - } - return r, nil -} -// 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) + if rc.Identifier == "" { + return WorkerScriptResponse{}, ErrMissingIdentifier } - 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 -} -// 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) + uri := fmt.Sprintf("/accounts/%s/workers/scripts/%s", rc.Identifier, scriptName) res, err := api.makeRequestContextWithHeadersComplete(ctx, http.MethodGet, uri, nil, nil) var r WorkerScriptResponse if err != nil { @@ -462,228 +165,96 @@ func (api *API) downloadWorkerWithName(ctx context.Context, scriptName string) ( return r, nil } -// 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 api.AccountID == "" { - return WorkerBindingListResponse{}, errors.New("account ID required") - } - - 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 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, - } - 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. +// ListWorkers returns list of Workers 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") +func (api *API) ListWorkers(ctx context.Context, rc *ResourceContainer, params ListWorkersParams) (WorkerListResponse, error) { + if rc.Identifier == "" { + return WorkerListResponse{}, ErrMissingAccountID } - uri := fmt.Sprintf("/accounts/%s/workers/scripts", api.AccountID) + + uri := fmt.Sprintf("/accounts/%s/workers/scripts", rc.Identifier) 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{}, fmt.Errorf("%s: %w", errUnmarshalError, err) } + return r, nil } -// UploadWorker push raw script content for your worker. +// UploadWorker pushes raw script content for your Worker. // // API reference: https://api.cloudflare.com/#worker-script-upload-worker -func (api *API) UploadWorker(ctx context.Context, requestParams *WorkerRequestParams, params *WorkerScriptParams) (WorkerScriptResponse, error) { - if params.Module { - return api.UploadWorkerWithBindings(ctx, requestParams, params) - } - - contentType := "application/javascript" - if requestParams.ScriptName != "" { - return api.uploadWorkerWithName(ctx, requestParams.ScriptName, contentType, []byte(params.Script)) +func (api *API) UploadWorker(ctx context.Context, rc *ResourceContainer, params CreateWorkerParams) (WorkerScriptResponse, error) { + 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 + } } - return api.uploadWorkerForZone(ctx, requestParams.ZoneID, contentType, []byte(params.Script)) -} -// 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) + if rc.Level == AccountRouteLevel { + return api.uploadWorkerWithName(ctx, rc, params.Name, contentType, []byte(body)) + } else { + return api.uploadWorkerForZone(ctx, rc, contentType, []byte(body)) } - return api.uploadWorkerForZone(ctx, requestParams.ZoneID, contentType, body) } -func (api *API) uploadWorkerForZone(ctx context.Context, zoneID, contentType string, body []byte) (WorkerScriptResponse, error) { - uri := fmt.Sprintf("/zones/%s/workers/script", zoneID) +func (api *API) uploadWorkerForZone(ctx context.Context, rc *ResourceContainer, contentType string, body []byte) (WorkerScriptResponse, error) { + uri := fmt.Sprintf("/zones/%s/workers/script", rc.Identifier) 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") +func (api *API) uploadWorkerWithName(ctx context.Context, rc *ResourceContainer, scriptName, contentType string, body []byte) (WorkerScriptResponse, error) { + if rc.Identifier == "" { + return WorkerScriptResponse{}, ErrMissingAccountID } - uri := fmt.Sprintf("/accounts/%s/workers/scripts/%s", api.AccountID, scriptName) + uri := fmt.Sprintf("/accounts/%s/workers/scripts/%s", rc.Identifier, 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 *WorkerScriptParams) (string, []byte, error) { +func formatMultipartBody(params CreateWorkerParams) (string, []byte, error) { var buf = &bytes.Buffer{} var mpw = multipart.NewWriter(buf) defer mpw.Close() @@ -768,174 +339,3 @@ func formatMultipartBody(params *WorkerScriptParams) (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_bindings.go b/workers_bindings.go new file mode 100644 index 0000000000..d64717c4d9 --- /dev/null +++ b/workers_bindings.go @@ -0,0 +1,426 @@ +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" +) + +// 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 +} + +// 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, requestParams *WorkerRequestParams) (WorkerBindingListResponse, error) { + if requestParams.ScriptName == "" { + return WorkerBindingListResponse{}, errors.New("ScriptName is required") + } + if api.AccountID == "" { + return WorkerBindingListResponse{}, errors.New("account ID required") + } + + 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 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, + } + 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 +} diff --git a/workers_domains.go b/workers_domains.go new file mode 100644 index 0000000000..e8caea9793 --- /dev/null +++ b/workers_domains.go @@ -0,0 +1,48 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "fmt" + "net/http" +) + +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_example_test.go b/workers_example_test.go index 2206a35045..f70c7b93a7 100644 --- a/workers_example_test.go +++ b/workers_example_test.go @@ -1,206 +1,198 @@ 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) -} +// 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_routes.go b/workers_routes.go new file mode 100644 index 0000000000..31ec7937bf --- /dev/null +++ b/workers_routes.go @@ -0,0 +1,154 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "net/http" +) + +type ListWorkerRoutes struct{} + +type CreateWorkerRouteParams struct { + Pattern string `json:"pattern"` + Script string `json:"script,omitempty"` +} + +type ListWorkerRoutesParams struct{} + +type UpdateWorkerRouteParams struct { + Pattern string `json:"pattern"` + Script string `json:"script,omitempty"` +} + +// CreateWorkerRoute creates worker route for a zone +// +// 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 zone +// +// 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 zone. +// +// 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 + } + + uri := fmt.Sprintf("/zones/%s/workers/routes/%s", rc.Identifier, params.Script) + 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 new file mode 100644 index 0000000000..7182c1571a --- /dev/null +++ b/workers_routes_test.go @@ -0,0 +1,348 @@ +package cloudflare + +// func TestWorkers_CreateWorkerRoute(t *testing.T) { +// setup() +// defer teardown() + +// mux.HandleFunc("/zones"+testAccountID+"/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() +// defer teardown() + +// mux.HandleFunc("/zones"+testAccountID+"/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() +// defer teardown() + +// mux.HandleFunc("/zones"+testAccountID+"/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() +// 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() + +// mux.HandleFunc("/zones"+testAccountID+"/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"+testAccountID+"/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() +// defer teardown() + +// mux.HandleFunc("/zones"+testAccountID+"/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"+testAccountID+"/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() +// defer teardown() + +// mux.HandleFunc("/zones"+testAccountID+"/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"+testAccountID+"/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"+testAccountID+"/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() +// defer teardown() + +// mux.HandleFunc("/zones"+testAccountID+"/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() +// defer teardown() + +// mux.HandleFunc("/zones"+testAccountID+"/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() +// 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.Fprintf(w, listBindingsResponseData) //nolint +// }) + +// 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(), &WorkerRequestParams{ +// ScriptName: "my-script", +// }) +// assert.NoError(t, err) + +// assert.Equal(t, successResponse, res.Response) +// assert.Equal(t, 7, 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()) +// } + +// func TestWorkers_UpdateWorkerRouteErrorsWhenMixingSingleAndMultiScriptProperties(t *testing.T) { +// setup() +// 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() +// defer teardown() + +// mux.HandleFunc("/zones"+testAccountID+"/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) +// } diff --git a/workers_test.go b/workers_test.go index 434bb065d1..9890b35dba 100644 --- a/workers_test.go +++ b/workers_test.go @@ -315,61 +315,30 @@ func parseMultipartUpload(r *http.Request) (multipartUpload, error) { }, nil } -func TestWorkers_DeleteWorker(t *testing.T) { +func TestDeleteWorker(t *testing.T) { setup() defer teardown() - mux.HandleFunc("/zones/foo/workers/script", func(w http.ResponseWriter, r *http.Request) { + mux.HandleFunc("/accounts/"+testAccountID+"/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 + fmt.Fprintf(w, deleteWorkerResponseData) }) - res, err := client.DeleteWorker(context.Background(), &WorkerRequestParams{ZoneID: "foo"}) - want := WorkerScriptResponse{ - Response: successResponse, - } - - 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) + err := client.DeleteWorker(context.Background(), AccountIdentifier(testAccountID), DeleteWorkerParams{ScriptName: "bar"}) + assert.NoError(t, err) } -func TestWorkers_DownloadWorker(t *testing.T) { +func TestGetWorker(t *testing.T) { setup() defer teardown() - mux.HandleFunc("/zones/foo/workers/script", func(w http.ResponseWriter, r *http.Request) { + mux.HandleFunc("/accounts/"+testAccountID+"/workers/scripts/foo", 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 + fmt.Fprintf(w, workerScript) }) - res, err := client.DownloadWorker(context.Background(), &WorkerRequestParams{ZoneID: "foo"}) + res, err := client.GetWorker(context.Background(), AccountIdentifier(testAccountID), "foo") want := WorkerScriptResponse{ successResponse, false, @@ -381,67 +350,41 @@ func TestWorkers_DownloadWorker(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) { +func TestGetWorker_Module(t *testing.T) { setup() defer teardown() - _, 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) { + mux.HandleFunc("/accounts/"+testAccountID+"/workers/scripts/foo", 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.Fprintf(w, workerModuleScriptDownloadResponse) //nolint + fmt.Fprintf(w, workerModuleScriptDownloadResponse) }) - res, err := client.DownloadWorker(context.Background(), &WorkerRequestParams{ScriptName: "bar"}) + + res, err := client.GetWorker(context.Background(), AccountIdentifier(testAccountID), "foo") want := WorkerScriptResponse{ successResponse, true, WorkerScript{ Script: workerModuleScript, - }} + }, + } + if assert.NoError(t, err) { assert.Equal(t, want.Script, res.Script) } } -func TestWorkers_ListWorkerScripts(t *testing.T) { - setup(UsingAccount("foo")) +func TestListWorkers(t *testing.T) { + setup() defer teardown() - mux.HandleFunc("/accounts/foo/workers/scripts", func(w http.ResponseWriter, r *http.Request) { + mux.HandleFunc("/accounts/"+testAccountID+"/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.Fprintf(w, listWorkersResponseData) //nolint + fmt.Fprintf(w, listWorkersResponseData) }) - res, err := client.ListWorkerScripts(context.Background()) + res, err := client.ListWorkers(context.Background(), AccountIdentifier(testAccountID), ListWorkersParams{}) sampleDate, _ := time.Parse(time.RFC3339Nano, "2018-04-22T17:10:48.938097Z") want := []WorkerMetaData{ { @@ -462,18 +405,18 @@ func TestWorkers_ListWorkerScripts(t *testing.T) { } } -func TestWorkers_UploadWorker(t *testing.T) { +func TestUploadWorker_Basic(t *testing.T) { setup() defer teardown() - mux.HandleFunc("/zones/foo/workers/script", func(w http.ResponseWriter, r *http.Request) { + mux.HandleFunc("/accounts/"+testAccountID+"/workers/scripts/foo", 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}) + res, err := client.UploadWorker(context.Background(), AccountIdentifier(testAccountID), CreateWorkerParams{Name: "foo", Script: workerScript}) formattedTime, _ := time.Parse(time.RFC3339Nano, "2018-06-09T15:17:01.989141Z") want := WorkerScriptResponse{ successResponse, @@ -491,11 +434,11 @@ func TestWorkers_UploadWorker(t *testing.T) { } } -func TestWorkers_UploadWorkerAsModule(t *testing.T) { +func TestUploadWorker_Module(t *testing.T) { setup() defer teardown() - mux.HandleFunc("/zones/foo/workers/script", func(w http.ResponseWriter, r *http.Request) { + mux.HandleFunc("/accounts/"+testAccountID+"/workers/scripts/foo", func(w http.ResponseWriter, r *http.Request) { mpUpload, err := parseMultipartUpload(r) assert.NoError(t, err) @@ -510,9 +453,9 @@ func TestWorkers_UploadWorkerAsModule(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.Fprintf(w, uploadWorkerModuleResponseData) //nolint + fmt.Fprintf(w, uploadWorkerModuleResponseData) }) - res, err := client.UploadWorker(context.Background(), &WorkerRequestParams{ZoneID: "foo"}, &WorkerScriptParams{Script: workerModuleScript, Module: true}) + res, err := client.UploadWorker(context.Background(), AccountIdentifier(testAccountID), CreateWorkerParams{Name: "foo", Script: workerModuleScript, Module: true}) formattedTime, _ := time.Parse(time.RFC3339Nano, "2018-06-09T15:17:01.989141Z") want := WorkerScriptResponse{ successResponse, @@ -530,76 +473,10 @@ func TestWorkers_UploadWorkerAsModule(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) { +func TestUploadWorker_WithDurableObjectBinding(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) @@ -618,11 +495,13 @@ func TestWorkers_UploadWorkerWithDurableObjectBinding(t *testing.T) { assert.Equal(t, expectedBindings, mpUpload.BindingMeta) w.Header().Set("content-type", "application/json") - fmt.Fprintf(w, uploadWorkerResponseData) //nolint + fmt.Fprintf(w, uploadWorkerResponseData) } - mux.HandleFunc("/accounts/foo/workers/scripts/bar", handler) - scriptParams := WorkerScriptParams{ + mux.HandleFunc("/accounts/"+testAccountID+"/workers/scripts/bar", handler) + + _, err := client.UploadWorker(context.Background(), AccountIdentifier(testAccountID), CreateWorkerParams{ + Name: "bar", Script: workerScript, Bindings: map[string]WorkerBinding{ "b1": WorkerDurableObjectBinding{ @@ -630,13 +509,13 @@ func TestWorkers_UploadWorkerWithDurableObjectBinding(t *testing.T) { ScriptName: "the_script", }, }, - } - _, err := client.UploadWorkerWithBindings(context.Background(), &WorkerRequestParams{ScriptName: "bar"}, &scriptParams) + }) + assert.NoError(t, err) } -func TestWorkers_UploadWorkerWithInheritBinding(t *testing.T) { - setup(UsingAccount("foo")) +func TestUploadWorker_WithInheritBinding(t *testing.T) { + setup() defer teardown() // Setup route handler for both single-script and multi-script @@ -661,22 +540,10 @@ func TestWorkers_UploadWorkerWithInheritBinding(t *testing.T) { assert.Equal(t, expectedBindings, mpUpload.BindingMeta) w.Header().Set("content-type", "application/json") - fmt.Fprintf(w, uploadWorkerResponseData) //nolint + fmt.Fprintf(w, uploadWorkerResponseData) } - mux.HandleFunc("/zones/foo/workers/script", handler) - mux.HandleFunc("/accounts/foo/workers/scripts/bar", handler) + mux.HandleFunc("/accounts/"+testAccountID+"/workers/scripts/bar", handler) - scriptParams := WorkerScriptParams{ - Script: workerScript, - Bindings: map[string]WorkerBinding{ - "b1": WorkerInheritBinding{}, - "b2": WorkerInheritBinding{ - OldName: "old_binding_name", - }, - }, - } - - // Expected response formattedTime, _ := time.Parse(time.RFC3339Nano, "2018-06-09T15:17:01.989141Z") want := WorkerScriptResponse{ successResponse, @@ -690,21 +557,22 @@ func TestWorkers_UploadWorkerWithInheritBinding(t *testing.T) { }, }} - // 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) + res, err := client.UploadWorker(context.Background(), AccountIdentifier(testAccountID), CreateWorkerParams{ + Name: "bar", + Script: workerScript, + Bindings: map[string]WorkerBinding{ + "b1": WorkerInheritBinding{}, + "b2": WorkerInheritBinding{ + OldName: "old_binding_name", + }, + }}) if assert.NoError(t, err) { assert.Equal(t, want, res) } } -func TestWorkers_UploadWorkerWithKVBinding(t *testing.T) { - setup(UsingAccount("foo")) +func TestUploadWorker_WithKVBinding(t *testing.T) { + setup() defer teardown() handler := func(w http.ResponseWriter, r *http.Request) { @@ -724,24 +592,23 @@ func TestWorkers_UploadWorkerWithKVBinding(t *testing.T) { assert.Equal(t, expectedBindings, mpUpload.BindingMeta) w.Header().Set("content-type", "application/json") - fmt.Fprintf(w, uploadWorkerResponseData) //nolint + fmt.Fprintf(w, uploadWorkerResponseData) } - mux.HandleFunc("/accounts/foo/workers/scripts/bar", handler) + mux.HandleFunc("/accounts/"+testAccountID+"/workers/scripts/bar", handler) - scriptParams := WorkerScriptParams{ + _, err := client.UploadWorker(context.Background(), AccountIdentifier(testAccountID), CreateWorkerParams{ + Name: "bar", 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 TestWorkers_UploadWorkerWithWasmBinding(t *testing.T) { - setup(UsingAccount("foo")) +func TestUploadWorker_WithWasmBinding(t *testing.T) { + setup() defer teardown() handler := func(w http.ResponseWriter, r *http.Request) { @@ -766,24 +633,25 @@ func TestWorkers_UploadWorkerWithWasmBinding(t *testing.T) { assert.Equal(t, []byte("fake-wasm"), wasmContent) w.Header().Set("content-type", "application/json") - fmt.Fprintf(w, uploadWorkerResponseData) //nolint + fmt.Fprintf(w, uploadWorkerResponseData) } - mux.HandleFunc("/accounts/foo/workers/scripts/bar", handler) + mux.HandleFunc("/accounts/"+testAccountID+"/workers/scripts/bar", handler) - scriptParams := WorkerScriptParams{ + _, err := client.UploadWorker(context.Background(), AccountIdentifier(testAccountID), CreateWorkerParams{ + Name: "bar", 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 TestWorkers_UploadWorkerWithPlainTextBinding(t *testing.T) { - setup(UsingAccount("foo")) +func TestUploadWorker_WithPlainTextBinding(t *testing.T) { + setup() defer teardown() handler := func(w http.ResponseWriter, r *http.Request) { @@ -805,25 +673,26 @@ func TestWorkers_UploadWorkerWithPlainTextBinding(t *testing.T) { w.Header().Set("content-type", "application/json") fmt.Fprintf(w, uploadWorkerResponseData) //nolint } - mux.HandleFunc("/accounts/foo/workers/scripts/bar", handler) + mux.HandleFunc("/accounts/"+testAccountID+"/workers/scripts/bar", handler) - scriptParams := WorkerScriptParams{ + _, err := client.UploadWorker(context.Background(), AccountIdentifier(testAccountID), CreateWorkerParams{ + Name: "bar", 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 TestWorkers_UploadWorkerAsModuleWithPlainTextBinding(t *testing.T) { - setup(UsingAccount("foo")) +func TestUploadWorker_ModuleWithPlainTextBinding(t *testing.T) { + setup() defer teardown() - mux.HandleFunc("/accounts/foo/workers/scripts/bar", func(w http.ResponseWriter, r *http.Request) { + mux.HandleFunc("/accounts/"+testAccountID+"/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) @@ -851,7 +720,8 @@ func TestWorkers_UploadWorkerAsModuleWithPlainTextBinding(t *testing.T) { fmt.Fprintf(w, uploadWorkerModuleResponseData) //nolint }) - scriptParams := WorkerScriptParams{ + _, err := client.UploadWorker(context.Background(), AccountIdentifier(testAccountID), CreateWorkerParams{ + Name: "bar", Script: workerModuleScript, Module: true, Bindings: map[string]WorkerBinding{ @@ -859,13 +729,13 @@ func TestWorkers_UploadWorkerAsModuleWithPlainTextBinding(t *testing.T) { Text: "plain text value", }, }, - } - _, err := client.UploadWorkerWithBindings(context.Background(), &WorkerRequestParams{ScriptName: "bar"}, &scriptParams) + }) + assert.NoError(t, err) } -func TestWorkers_UploadWorkerWithSecretTextBinding(t *testing.T) { - setup(UsingAccount("foo")) +func TestUploadWorker_WithSecretTextBinding(t *testing.T) { + setup() defer teardown() handler := func(w http.ResponseWriter, r *http.Request) { @@ -887,22 +757,22 @@ func TestWorkers_UploadWorkerWithSecretTextBinding(t *testing.T) { w.Header().Set("content-type", "application/json") fmt.Fprintf(w, uploadWorkerResponseData) //nolint } - mux.HandleFunc("/accounts/foo/workers/scripts/bar", handler) + mux.HandleFunc("/accounts/"+testAccountID+"/workers/scripts/bar", handler) - scriptParams := WorkerScriptParams{ + _, err := client.UploadWorker(context.Background(), AccountIdentifier(testAccountID), CreateWorkerParams{ + Name: "bar", 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 TestWorkers_UploadWorkerWithServiceBinding(t *testing.T) { - setup(UsingAccount("foo")) +func TestUploadWorker_WithServiceBinding(t *testing.T) { + setup() defer teardown() handler := func(w http.ResponseWriter, r *http.Request) { @@ -930,10 +800,10 @@ func TestWorkers_UploadWorkerWithServiceBinding(t *testing.T) { w.Header().Set("content-type", "application/json") fmt.Fprintf(w, uploadWorkerResponseData) //nolint } - mux.HandleFunc("/accounts/foo/workers/scripts/bar", handler) + mux.HandleFunc("/accounts/"+testAccountID+"/workers/scripts/bar", handler) - environment := "the_environment" - scriptParams := WorkerScriptParams{ + _, err := client.UploadWorker(context.Background(), AccountIdentifier(testAccountID), CreateWorkerParams{ + Name: "bar", Script: workerScript, Bindings: map[string]WorkerBinding{ "b1": WorkerServiceBinding{ @@ -941,386 +811,9 @@ func TestWorkers_UploadWorkerWithServiceBinding(t *testing.T) { }, "b2": WorkerServiceBinding{ Service: "the_service", - Environment: &environment, + Environment: StringPtr("the_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, 7, 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()) -} - -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) - } }