mirror of
https://github.com/StackExchange/dnscontrol.git
synced 2025-10-03 18:35:32 +08:00
631 lines
19 KiB
Go
631 lines
19 KiB
Go
package cloudflare
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"mime"
|
|
"mime/multipart"
|
|
"net/http"
|
|
"net/textproto"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/goccy/go-json"
|
|
)
|
|
|
|
// WorkerRequestParams provides parameters for worker requests for both enterprise and standard requests.
|
|
type WorkerRequestParams struct {
|
|
ZoneID string
|
|
ScriptName string
|
|
}
|
|
|
|
type CreateWorkerParams struct {
|
|
ScriptName string
|
|
Script string
|
|
|
|
// DispatchNamespaceName uploads the worker to a WFP dispatch namespace if provided
|
|
DispatchNamespaceName *string
|
|
|
|
// Module changes the Content-Type header to specify the script is an
|
|
// ES Module syntax script.
|
|
Module bool
|
|
|
|
// Logpush opts the worker into Workers Logpush logging. A nil value leaves
|
|
// the current setting unchanged.
|
|
//
|
|
// Documentation: https://developers.cloudflare.com/workers/platform/logpush/
|
|
Logpush *bool
|
|
|
|
// TailConsumers specifies a list of Workers that will consume the logs of
|
|
// the attached Worker.
|
|
// Documentation: https://developers.cloudflare.com/workers/platform/tail-workers/
|
|
TailConsumers *[]WorkersTailConsumer
|
|
|
|
// Bindings should be a map where the keys are the binding name, and the
|
|
// values are the binding content
|
|
Bindings map[string]WorkerBinding
|
|
|
|
// CompatibilityDate is a date in the form yyyy-mm-dd,
|
|
// which will be used to determine which version of the Workers runtime is used.
|
|
// https://developers.cloudflare.com/workers/platform/compatibility-dates/
|
|
CompatibilityDate string
|
|
|
|
// CompatibilityFlags are the names of features of the Workers runtime to be enabled or disabled,
|
|
// usually used together with CompatibilityDate.
|
|
// https://developers.cloudflare.com/workers/platform/compatibility-dates/#compatibility-flags
|
|
CompatibilityFlags []string
|
|
|
|
Placement *Placement
|
|
|
|
// Tags are used to better manage CRUD operations at scale.
|
|
// https://developers.cloudflare.com/cloudflare-for-platforms/workers-for-platforms/platform/tags/
|
|
Tags []string
|
|
}
|
|
|
|
func (p CreateWorkerParams) RequiresMultipart() bool {
|
|
switch {
|
|
case p.Module:
|
|
return true
|
|
case p.Logpush != nil:
|
|
return true
|
|
case p.Placement != nil:
|
|
return true
|
|
case len(p.Bindings) > 0:
|
|
return true
|
|
case p.CompatibilityDate != "":
|
|
return true
|
|
case len(p.CompatibilityFlags) > 0:
|
|
return true
|
|
case p.TailConsumers != nil:
|
|
return true
|
|
case len(p.Tags) > 0:
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
type UpdateWorkersScriptContentParams struct {
|
|
ScriptName string
|
|
Script string
|
|
|
|
// DispatchNamespaceName uploads the worker to a WFP dispatch namespace if provided
|
|
DispatchNamespaceName *string
|
|
|
|
// Module changes the Content-Type header to specify the script is an
|
|
// ES Module syntax script.
|
|
Module bool
|
|
}
|
|
|
|
type UpdateWorkersScriptSettingsParams struct {
|
|
ScriptName string
|
|
|
|
// Logpush opts the worker into Workers Logpush logging. A nil value leaves
|
|
// the current setting unchanged.
|
|
//
|
|
// Documentation: https://developers.cloudflare.com/workers/platform/logpush/
|
|
Logpush *bool
|
|
|
|
// TailConsumers specifies a list of Workers that will consume the logs of
|
|
// the attached Worker.
|
|
// Documentation: https://developers.cloudflare.com/workers/platform/tail-workers/
|
|
TailConsumers *[]WorkersTailConsumer
|
|
|
|
// Bindings should be a map where the keys are the binding name, and the
|
|
// values are the binding content
|
|
Bindings map[string]WorkerBinding
|
|
|
|
// CompatibilityDate is a date in the form yyyy-mm-dd,
|
|
// which will be used to determine which version of the Workers runtime is used.
|
|
// https://developers.cloudflare.com/workers/platform/compatibility-dates/
|
|
CompatibilityDate string
|
|
|
|
// CompatibilityFlags are the names of features of the Workers runtime to be enabled or disabled,
|
|
// usually used together with CompatibilityDate.
|
|
// https://developers.cloudflare.com/workers/platform/compatibility-dates/#compatibility-flags
|
|
CompatibilityFlags []string
|
|
|
|
Placement *Placement
|
|
}
|
|
|
|
// WorkerScriptParams provides a worker script and the associated bindings.
|
|
type WorkerScriptParams struct {
|
|
ScriptName 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
|
|
}
|
|
|
|
// WorkerRoute is used to map traffic matching a URL pattern to a workers
|
|
//
|
|
// API reference: https://api.cloudflare.com/#worker-routes-properties
|
|
type WorkerRoute struct {
|
|
ID string `json:"id,omitempty"`
|
|
Pattern string `json:"pattern"`
|
|
ScriptName string `json:"script,omitempty"`
|
|
}
|
|
|
|
// WorkerRoutesResponse embeds Response struct and slice of WorkerRoutes.
|
|
type WorkerRoutesResponse struct {
|
|
Response
|
|
Routes []WorkerRoute `json:"result"`
|
|
}
|
|
|
|
// WorkerRouteResponse embeds Response struct and a single WorkerRoute.
|
|
type WorkerRouteResponse struct {
|
|
Response
|
|
WorkerRoute `json:"result"`
|
|
}
|
|
|
|
// WorkerScript Cloudflare Worker struct with metadata.
|
|
type WorkerScript struct {
|
|
WorkerMetaData
|
|
Script string `json:"script"`
|
|
UsageModel string `json:"usage_model,omitempty"`
|
|
}
|
|
|
|
type WorkersTailConsumer struct {
|
|
Service string `json:"service"`
|
|
Environment *string `json:"environment,omitempty"`
|
|
Namespace *string `json:"namespace,omitempty"`
|
|
}
|
|
|
|
// WorkerMetaData contains worker script information such as size, creation & modification dates.
|
|
type WorkerMetaData struct {
|
|
ID string `json:"id,omitempty"`
|
|
ETAG string `json:"etag,omitempty"`
|
|
Size int `json:"size,omitempty"`
|
|
CreatedOn time.Time `json:"created_on,omitempty"`
|
|
ModifiedOn time.Time `json:"modified_on,omitempty"`
|
|
Logpush *bool `json:"logpush,omitempty"`
|
|
TailConsumers *[]WorkersTailConsumer `json:"tail_consumers,omitempty"`
|
|
LastDeployedFrom *string `json:"last_deployed_from,omitempty"`
|
|
DeploymentId *string `json:"deployment_id,omitempty"`
|
|
PlacementMode *PlacementMode `json:"placement_mode,omitempty"`
|
|
PipelineHash *string `json:"pipeline_hash,omitempty"`
|
|
}
|
|
|
|
// WorkerListResponse wrapper struct for API response to worker script list API call.
|
|
type WorkerListResponse struct {
|
|
Response
|
|
ResultInfo
|
|
WorkerList []WorkerMetaData `json:"result"`
|
|
}
|
|
|
|
// WorkerScriptResponse wrapper struct for API response to worker script calls.
|
|
type WorkerScriptResponse struct {
|
|
Response
|
|
Module bool
|
|
WorkerScript `json:"result"`
|
|
}
|
|
|
|
// WorkerScriptSettingsResponse wrapper struct for API response to worker script settings calls.
|
|
type WorkerScriptSettingsResponse struct {
|
|
Response
|
|
WorkerMetaData
|
|
}
|
|
|
|
type ListWorkersParams struct{}
|
|
|
|
type DeleteWorkerParams struct {
|
|
ScriptName string
|
|
|
|
// DispatchNamespaceName is the dispatch namespace the Worker is uploaded to.
|
|
DispatchNamespace *string
|
|
}
|
|
|
|
type PlacementMode string
|
|
|
|
const (
|
|
PlacementModeOff PlacementMode = ""
|
|
PlacementModeSmart PlacementMode = "smart"
|
|
)
|
|
|
|
type Placement struct {
|
|
Mode PlacementMode `json:"mode"`
|
|
}
|
|
|
|
// DeleteWorker deletes a single Worker.
|
|
//
|
|
// API reference: https://developers.cloudflare.com/api/operations/worker-script-delete-worker
|
|
func (api *API) DeleteWorker(ctx context.Context, rc *ResourceContainer, params DeleteWorkerParams) error {
|
|
if rc.Level != AccountRouteLevel {
|
|
return ErrRequiredAccountLevelResourceContainer
|
|
}
|
|
|
|
if rc.Identifier == "" {
|
|
return ErrMissingAccountID
|
|
}
|
|
|
|
uri := fmt.Sprintf("/accounts/%s/workers/scripts/%s", rc.Identifier, params.ScriptName)
|
|
if params.DispatchNamespace != nil && *params.DispatchNamespace != "" {
|
|
uri = fmt.Sprintf("/accounts/%s/workers/dispatch/namespaces/%s/scripts/%s", rc.Identifier, *params.DispatchNamespace, params.ScriptName)
|
|
}
|
|
|
|
res, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil)
|
|
|
|
var r WorkerScriptResponse
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = json.Unmarshal(res, &r)
|
|
if err != nil {
|
|
return fmt.Errorf("%s: %w", errUnmarshalError, err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetWorker fetch raw script content for your worker returns string containing
|
|
// worker code js.
|
|
//
|
|
// API reference: https://developers.cloudflare.com/api/operations/worker-script-download-worker
|
|
func (api *API) GetWorker(ctx context.Context, rc *ResourceContainer, scriptName string) (WorkerScriptResponse, error) {
|
|
return api.GetWorkerWithDispatchNamespace(ctx, rc, scriptName, "")
|
|
}
|
|
|
|
// GetWorker fetch raw script content for your worker returns string containing
|
|
// worker code js.
|
|
//
|
|
// API reference: https://developers.cloudflare.com/api/operations/worker-script-download-worker
|
|
func (api *API) GetWorkerWithDispatchNamespace(ctx context.Context, rc *ResourceContainer, scriptName string, dispatchNamespace string) (WorkerScriptResponse, error) {
|
|
if rc.Level != AccountRouteLevel {
|
|
return WorkerScriptResponse{}, ErrRequiredAccountLevelResourceContainer
|
|
}
|
|
|
|
if rc.Identifier == "" {
|
|
return WorkerScriptResponse{}, ErrMissingAccountID
|
|
}
|
|
|
|
uri := fmt.Sprintf("/accounts/%s/workers/scripts/%s", rc.Identifier, scriptName)
|
|
if dispatchNamespace != "" {
|
|
uri = fmt.Sprintf("/accounts/%s/workers/dispatch/namespaces/%s/scripts/%s/content", rc.Identifier, dispatchNamespace, scriptName)
|
|
}
|
|
res, err := api.makeRequestContextWithHeadersComplete(ctx, http.MethodGet, uri, nil, nil)
|
|
var r WorkerScriptResponse
|
|
if err != nil {
|
|
return r, err
|
|
}
|
|
|
|
// Check if the response type is multipart, in which case this was a module worker
|
|
mediaType, mediaParams, _ := mime.ParseMediaType(res.Headers.Get("content-type"))
|
|
if strings.HasPrefix(mediaType, "multipart/") {
|
|
bytesReader := bytes.NewReader(res.Body)
|
|
mimeReader := multipart.NewReader(bytesReader, mediaParams["boundary"])
|
|
mimePart, err := mimeReader.NextPart()
|
|
if err != nil {
|
|
return r, fmt.Errorf("could not get multipart response body: %w", err)
|
|
}
|
|
mimePartBody, err := io.ReadAll(mimePart)
|
|
if err != nil {
|
|
return r, fmt.Errorf("could not read multipart response body: %w", err)
|
|
}
|
|
r.Script = string(mimePartBody)
|
|
r.Module = true
|
|
} else {
|
|
r.Script = string(res.Body)
|
|
r.Module = false
|
|
}
|
|
|
|
r.Success = true
|
|
return r, nil
|
|
}
|
|
|
|
// ListWorkers returns list of Workers for given account.
|
|
//
|
|
// API reference: https://developers.cloudflare.com/api/operations/worker-script-list-workers
|
|
func (api *API) ListWorkers(ctx context.Context, rc *ResourceContainer, params ListWorkersParams) (WorkerListResponse, *ResultInfo, error) {
|
|
if rc.Level != AccountRouteLevel {
|
|
return WorkerListResponse{}, &ResultInfo{}, ErrRequiredAccountLevelResourceContainer
|
|
}
|
|
|
|
if rc.Identifier == "" {
|
|
return WorkerListResponse{}, &ResultInfo{}, ErrMissingAccountID
|
|
}
|
|
|
|
uri := fmt.Sprintf("/accounts/%s/workers/scripts", rc.Identifier)
|
|
res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
|
|
if err != nil {
|
|
return WorkerListResponse{}, &ResultInfo{}, err
|
|
}
|
|
|
|
var r WorkerListResponse
|
|
err = json.Unmarshal(res, &r)
|
|
if err != nil {
|
|
return WorkerListResponse{}, &ResultInfo{}, fmt.Errorf("%s: %w", errUnmarshalError, err)
|
|
}
|
|
|
|
return r, &r.ResultInfo, nil
|
|
}
|
|
|
|
// UploadWorker pushes raw script content for your Worker.
|
|
//
|
|
// API reference: https://developers.cloudflare.com/api/operations/worker-script-upload-worker-module
|
|
func (api *API) UploadWorker(ctx context.Context, rc *ResourceContainer, params CreateWorkerParams) (WorkerScriptResponse, error) {
|
|
if rc.Level != AccountRouteLevel {
|
|
return WorkerScriptResponse{}, ErrRequiredAccountLevelResourceContainer
|
|
}
|
|
|
|
if rc.Identifier == "" {
|
|
return WorkerScriptResponse{}, ErrMissingAccountID
|
|
}
|
|
|
|
body := []byte(params.Script)
|
|
var (
|
|
contentType = "application/javascript"
|
|
err error
|
|
)
|
|
|
|
if params.RequiresMultipart() {
|
|
contentType, body, err = formatMultipartBody(params)
|
|
if err != nil {
|
|
return WorkerScriptResponse{}, err
|
|
}
|
|
}
|
|
|
|
uri := fmt.Sprintf("/accounts/%s/workers/scripts/%s", rc.Identifier, params.ScriptName)
|
|
if params.DispatchNamespaceName != nil && *params.DispatchNamespaceName != "" {
|
|
uri = fmt.Sprintf("/accounts/%s/workers/dispatch/namespaces/%s/scripts/%s", rc.Identifier, *params.DispatchNamespaceName, params.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
|
|
}
|
|
|
|
// GetWorkersScriptContent returns the pure script content of a worker.
|
|
//
|
|
// API reference: https://developers.cloudflare.com/api/operations/worker-script-get-content
|
|
func (api *API) GetWorkersScriptContent(ctx context.Context, rc *ResourceContainer, scriptName string) (string, error) {
|
|
if rc.Level != AccountRouteLevel {
|
|
return "", ErrRequiredAccountLevelResourceContainer
|
|
}
|
|
|
|
if rc.Identifier == "" {
|
|
return "", ErrMissingAccountID
|
|
}
|
|
|
|
uri := fmt.Sprintf("/accounts/%s/workers/scripts/%s/content/v2", rc.Identifier, scriptName)
|
|
res, err := api.makeRequestContextWithHeadersComplete(ctx, http.MethodGet, uri, nil, nil)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return string(res.Body), nil
|
|
}
|
|
|
|
// UpdateWorkersScriptContent pushes only script content, no metadata.
|
|
//
|
|
// API reference: https://developers.cloudflare.com/api/operations/worker-script-put-content
|
|
func (api *API) UpdateWorkersScriptContent(ctx context.Context, rc *ResourceContainer, params UpdateWorkersScriptContentParams) (WorkerScriptResponse, error) {
|
|
if rc.Level != AccountRouteLevel {
|
|
return WorkerScriptResponse{}, ErrRequiredAccountLevelResourceContainer
|
|
}
|
|
|
|
if rc.Identifier == "" {
|
|
return WorkerScriptResponse{}, ErrMissingAccountID
|
|
}
|
|
|
|
body := []byte(params.Script)
|
|
var (
|
|
contentType = "application/javascript"
|
|
err error
|
|
)
|
|
|
|
if params.Module {
|
|
var formattedParams CreateWorkerParams
|
|
formattedParams.Script = params.Script
|
|
formattedParams.ScriptName = params.ScriptName
|
|
formattedParams.Module = params.Module
|
|
formattedParams.DispatchNamespaceName = params.DispatchNamespaceName
|
|
contentType, body, err = formatMultipartBody(formattedParams)
|
|
if err != nil {
|
|
return WorkerScriptResponse{}, err
|
|
}
|
|
}
|
|
|
|
uri := fmt.Sprintf("/accounts/%s/workers/scripts/%s/content", rc.Identifier, params.ScriptName)
|
|
if params.DispatchNamespaceName != nil {
|
|
uri = fmt.Sprintf("/accounts/%s/workers/dispatch_namespaces/%s/scripts/%s/content", rc.Identifier, *params.DispatchNamespaceName, params.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
|
|
}
|
|
|
|
// GetWorkersScriptSettings returns the metadata of a worker.
|
|
//
|
|
// API reference: https://developers.cloudflare.com/api/operations/worker-script-get-settings
|
|
func (api *API) GetWorkersScriptSettings(ctx context.Context, rc *ResourceContainer, scriptName string) (WorkerScriptSettingsResponse, error) {
|
|
if rc.Level != AccountRouteLevel {
|
|
return WorkerScriptSettingsResponse{}, ErrRequiredAccountLevelResourceContainer
|
|
}
|
|
|
|
if rc.Identifier == "" {
|
|
return WorkerScriptSettingsResponse{}, ErrMissingAccountID
|
|
}
|
|
|
|
uri := fmt.Sprintf("/accounts/%s/workers/scripts/%s/settings", rc.Identifier, scriptName)
|
|
res, err := api.makeRequestContextWithHeaders(ctx, http.MethodGet, uri, nil, nil)
|
|
var r WorkerScriptSettingsResponse
|
|
if err != nil {
|
|
return r, err
|
|
}
|
|
|
|
err = json.Unmarshal(res, &r)
|
|
if err != nil {
|
|
return r, fmt.Errorf("%s: %w", errUnmarshalError, err)
|
|
}
|
|
|
|
r.Success = true
|
|
|
|
return r, nil
|
|
}
|
|
|
|
// UpdateWorkersScriptSettings pushes only script metadata.
|
|
//
|
|
// API reference: https://developers.cloudflare.com/api/operations/worker-script-patch-settings
|
|
func (api *API) UpdateWorkersScriptSettings(ctx context.Context, rc *ResourceContainer, params UpdateWorkersScriptSettingsParams) (WorkerScriptSettingsResponse, error) {
|
|
if rc.Level != AccountRouteLevel {
|
|
return WorkerScriptSettingsResponse{}, ErrRequiredAccountLevelResourceContainer
|
|
}
|
|
|
|
if rc.Identifier == "" {
|
|
return WorkerScriptSettingsResponse{}, ErrMissingAccountID
|
|
}
|
|
|
|
body, err := json.Marshal(params)
|
|
if err != nil {
|
|
return WorkerScriptSettingsResponse{}, err
|
|
}
|
|
headers := make(http.Header)
|
|
headers.Set("Content-Type", "application/json")
|
|
|
|
uri := fmt.Sprintf("/accounts/%s/workers/scripts/%s/settings", rc.Identifier, params.ScriptName)
|
|
res, err := api.makeRequestContextWithHeaders(ctx, http.MethodPatch, uri, body, headers)
|
|
var r WorkerScriptSettingsResponse
|
|
if err != nil {
|
|
return r, err
|
|
}
|
|
|
|
err = json.Unmarshal(res, &r)
|
|
if err != nil {
|
|
return r, fmt.Errorf("%s: %w", errUnmarshalError, err)
|
|
}
|
|
|
|
r.Success = true
|
|
|
|
return r, nil
|
|
}
|
|
|
|
// Returns content-type, body, error.
|
|
func formatMultipartBody(params CreateWorkerParams) (string, []byte, error) {
|
|
var buf = &bytes.Buffer{}
|
|
var mpw = multipart.NewWriter(buf)
|
|
defer mpw.Close()
|
|
|
|
// Write metadata part
|
|
var scriptPartName string
|
|
meta := struct {
|
|
BodyPart string `json:"body_part,omitempty"`
|
|
MainModule string `json:"main_module,omitempty"`
|
|
Bindings []workerBindingMeta `json:"bindings"`
|
|
Logpush *bool `json:"logpush,omitempty"`
|
|
TailConsumers *[]WorkersTailConsumer `json:"tail_consumers,omitempty"`
|
|
CompatibilityDate string `json:"compatibility_date,omitempty"`
|
|
CompatibilityFlags []string `json:"compatibility_flags,omitempty"`
|
|
Placement *Placement `json:"placement,omitempty"`
|
|
Tags []string `json:"tags"`
|
|
}{
|
|
Bindings: make([]workerBindingMeta, 0, len(params.Bindings)),
|
|
Logpush: params.Logpush,
|
|
TailConsumers: params.TailConsumers,
|
|
CompatibilityDate: params.CompatibilityDate,
|
|
CompatibilityFlags: params.CompatibilityFlags,
|
|
Placement: params.Placement,
|
|
Tags: params.Tags,
|
|
}
|
|
|
|
if params.Module {
|
|
scriptPartName = "worker.mjs"
|
|
meta.MainModule = scriptPartName
|
|
} else {
|
|
scriptPartName = "script"
|
|
meta.BodyPart = scriptPartName
|
|
}
|
|
|
|
bodyWriters := make([]workerBindingBodyWriter, 0, len(params.Bindings))
|
|
for name, b := range params.Bindings {
|
|
bindingMeta, bodyWriter, err := b.serialize(name)
|
|
if err != nil {
|
|
return "", nil, err
|
|
}
|
|
|
|
meta.Bindings = append(meta.Bindings, bindingMeta)
|
|
bodyWriters = append(bodyWriters, bodyWriter)
|
|
}
|
|
|
|
var hdr = textproto.MIMEHeader{}
|
|
hdr.Set("content-disposition", fmt.Sprintf(`form-data; name="%s"`, "metadata"))
|
|
hdr.Set("content-type", "application/json")
|
|
pw, err := mpw.CreatePart(hdr)
|
|
if err != nil {
|
|
return "", nil, err
|
|
}
|
|
metaJSON, err := json.Marshal(meta)
|
|
if err != nil {
|
|
return "", nil, err
|
|
}
|
|
_, err = pw.Write(metaJSON)
|
|
if err != nil {
|
|
return "", nil, err
|
|
}
|
|
|
|
// Write script part
|
|
hdr = textproto.MIMEHeader{}
|
|
|
|
contentType := "application/javascript"
|
|
if params.Module {
|
|
contentType = "application/javascript+module"
|
|
hdr.Set("content-disposition", fmt.Sprintf(`form-data; name="%s"; filename="%[1]s"`, scriptPartName))
|
|
} else {
|
|
hdr.Set("content-disposition", fmt.Sprintf(`form-data; name="%s"`, scriptPartName))
|
|
}
|
|
hdr.Set("content-type", contentType)
|
|
|
|
pw, err = mpw.CreatePart(hdr)
|
|
if err != nil {
|
|
return "", nil, err
|
|
}
|
|
_, err = pw.Write([]byte(params.Script))
|
|
if err != nil {
|
|
return "", nil, err
|
|
}
|
|
|
|
// Write other bindings with parts
|
|
for _, w := range bodyWriters {
|
|
if w != nil {
|
|
err = w(mpw)
|
|
if err != nil {
|
|
return "", nil, err
|
|
}
|
|
}
|
|
}
|
|
|
|
mpw.Close()
|
|
|
|
return mpw.FormDataContentType(), buf.Bytes(), nil
|
|
}
|