1package cloudflare 2 3import ( 4 "encoding/json" 5 "net/http" 6 "time" 7 8 "github.com/pkg/errors" 9) 10 11// WorkerRequestParams provides parameters for worker requests for both enterprise and standard requests 12type WorkerRequestParams struct { 13 ZoneID string 14 ScriptName string 15} 16 17// WorkerRoute aka filters are patterns used to enable or disable workers that match requests. 18// 19// API reference: https://api.cloudflare.com/#worker-filters-properties 20type WorkerRoute struct { 21 ID string `json:"id,omitempty"` 22 Pattern string `json:"pattern"` 23 Enabled bool `json:"enabled"` 24 Script string `json:"script,omitempty"` 25} 26 27// WorkerRoutesResponse embeds Response struct and slice of WorkerRoutes 28type WorkerRoutesResponse struct { 29 Response 30 Routes []WorkerRoute `json:"result"` 31} 32 33// WorkerRouteResponse embeds Response struct and a single WorkerRoute 34type WorkerRouteResponse struct { 35 Response 36 WorkerRoute `json:"result"` 37} 38 39// WorkerScript Cloudflare Worker struct with metadata 40type WorkerScript struct { 41 WorkerMetaData 42 Script string `json:"script"` 43} 44 45// WorkerMetaData contains worker script information such as size, creation & modification dates 46type WorkerMetaData struct { 47 ID string `json:"id,omitempty"` 48 ETAG string `json:"etag,omitempty"` 49 Size int `json:"size,omitempty"` 50 CreatedOn time.Time `json:"created_on,omitempty"` 51 ModifiedOn time.Time `json:"modified_on,omitempty"` 52} 53 54// WorkerListResponse wrapper struct for API response to worker script list API call 55type WorkerListResponse struct { 56 Response 57 WorkerList []WorkerMetaData `json:"result"` 58} 59 60// WorkerScriptResponse wrapper struct for API response to worker script calls 61type WorkerScriptResponse struct { 62 Response 63 WorkerScript `json:"result"` 64} 65 66// DeleteWorker deletes worker for a zone. 67// 68// API reference: https://api.cloudflare.com/#worker-script-delete-worker 69func (api *API) DeleteWorker(requestParams *WorkerRequestParams) (WorkerScriptResponse, error) { 70 // if ScriptName is provided we will treat as org request 71 if requestParams.ScriptName != "" { 72 return api.deleteWorkerWithName(requestParams.ScriptName) 73 } 74 uri := "/zones/" + requestParams.ZoneID + "/workers/script" 75 res, err := api.makeRequest("DELETE", uri, nil) 76 var r WorkerScriptResponse 77 if err != nil { 78 return r, errors.Wrap(err, errMakeRequestError) 79 } 80 err = json.Unmarshal(res, &r) 81 if err != nil { 82 return r, errors.Wrap(err, errUnmarshalError) 83 } 84 return r, nil 85} 86 87// DeleteWorkerWithName deletes worker for a zone. 88// This is an enterprise only feature https://developers.cloudflare.com/workers/api/config-api-for-enterprise 89// account must be specified as api option https://godoc.org/github.com/cloudflare/cloudflare-go#UsingAccount 90// 91// API reference: https://api.cloudflare.com/#worker-script-delete-worker 92func (api *API) deleteWorkerWithName(scriptName string) (WorkerScriptResponse, error) { 93 if api.AccountID == "" { 94 return WorkerScriptResponse{}, errors.New("account ID required for enterprise only request") 95 } 96 uri := "/accounts/" + api.AccountID + "/workers/scripts/" + scriptName 97 res, err := api.makeRequest("DELETE", uri, nil) 98 var r WorkerScriptResponse 99 if err != nil { 100 return r, errors.Wrap(err, errMakeRequestError) 101 } 102 err = json.Unmarshal(res, &r) 103 if err != nil { 104 return r, errors.Wrap(err, errUnmarshalError) 105 } 106 return r, nil 107} 108 109// DownloadWorker fetch raw script content for your worker returns []byte containing worker code js 110// 111// API reference: https://api.cloudflare.com/#worker-script-download-worker 112func (api *API) DownloadWorker(requestParams *WorkerRequestParams) (WorkerScriptResponse, error) { 113 if requestParams.ScriptName != "" { 114 return api.downloadWorkerWithName(requestParams.ScriptName) 115 } 116 uri := "/zones/" + requestParams.ZoneID + "/workers/script" 117 res, err := api.makeRequest("GET", uri, nil) 118 var r WorkerScriptResponse 119 if err != nil { 120 return r, errors.Wrap(err, errMakeRequestError) 121 } 122 r.Script = string(res) 123 r.Success = true 124 return r, nil 125} 126 127// DownloadWorkerWithName fetch raw script content for your worker returns string containing worker code js 128// This is an enterprise only feature https://developers.cloudflare.com/workers/api/config-api-for-enterprise/ 129// 130// API reference: https://api.cloudflare.com/#worker-script-download-worker 131func (api *API) downloadWorkerWithName(scriptName string) (WorkerScriptResponse, error) { 132 if api.AccountID == "" { 133 return WorkerScriptResponse{}, errors.New("account ID required for enterprise only request") 134 } 135 uri := "/accounts/" + api.AccountID + "/workers/scripts/" + scriptName 136 res, err := api.makeRequest("GET", uri, nil) 137 var r WorkerScriptResponse 138 if err != nil { 139 return r, errors.Wrap(err, errMakeRequestError) 140 } 141 r.Script = string(res) 142 r.Success = true 143 return r, nil 144} 145 146// ListWorkerScripts returns list of worker scripts for given account. 147// 148// This is an enterprise only feature https://developers.cloudflare.com/workers/api/config-api-for-enterprise 149// 150// API reference: https://developers.cloudflare.com/workers/api/config-api-for-enterprise/ 151func (api *API) ListWorkerScripts() (WorkerListResponse, error) { 152 if api.AccountID == "" { 153 return WorkerListResponse{}, errors.New("account ID required for enterprise only request") 154 } 155 uri := "/accounts/" + api.AccountID + "/workers/scripts" 156 res, err := api.makeRequest("GET", uri, nil) 157 if err != nil { 158 return WorkerListResponse{}, errors.Wrap(err, errMakeRequestError) 159 } 160 var r WorkerListResponse 161 err = json.Unmarshal(res, &r) 162 if err != nil { 163 return WorkerListResponse{}, errors.Wrap(err, errUnmarshalError) 164 } 165 return r, nil 166} 167 168// UploadWorker push raw script content for your worker. 169// 170// API reference: https://api.cloudflare.com/#worker-script-upload-worker 171func (api *API) UploadWorker(requestParams *WorkerRequestParams, data string) (WorkerScriptResponse, error) { 172 if requestParams.ScriptName != "" { 173 return api.uploadWorkerWithName(requestParams.ScriptName, data) 174 } 175 uri := "/zones/" + requestParams.ZoneID + "/workers/script" 176 headers := make(http.Header) 177 headers.Set("Content-Type", "application/javascript") 178 res, err := api.makeRequestWithHeaders("PUT", uri, []byte(data), headers) 179 var r WorkerScriptResponse 180 if err != nil { 181 return r, errors.Wrap(err, errMakeRequestError) 182 } 183 err = json.Unmarshal(res, &r) 184 if err != nil { 185 return r, errors.Wrap(err, errUnmarshalError) 186 } 187 return r, nil 188} 189 190// UploadWorkerWithName push raw script content for your worker. 191// 192// This is an enterprise only feature https://developers.cloudflare.com/workers/api/config-api-for-enterprise/ 193// 194// API reference: https://api.cloudflare.com/#worker-script-upload-worker 195func (api *API) uploadWorkerWithName(scriptName string, data string) (WorkerScriptResponse, error) { 196 if api.AccountID == "" { 197 return WorkerScriptResponse{}, errors.New("account ID required for enterprise only request") 198 } 199 uri := "/accounts/" + api.AccountID + "/workers/scripts/" + scriptName 200 headers := make(http.Header) 201 headers.Set("Content-Type", "application/javascript") 202 res, err := api.makeRequestWithHeaders("PUT", uri, []byte(data), headers) 203 var r WorkerScriptResponse 204 if err != nil { 205 return r, errors.Wrap(err, errMakeRequestError) 206 } 207 err = json.Unmarshal(res, &r) 208 if err != nil { 209 return r, errors.Wrap(err, errUnmarshalError) 210 } 211 return r, nil 212} 213 214// CreateWorkerRoute creates worker route for a zone 215// 216// API reference: https://api.cloudflare.com/#worker-filters-create-filter 217func (api *API) CreateWorkerRoute(zoneID string, route WorkerRoute) (WorkerRouteResponse, error) { 218 // Check whether a script name is defined in order to determine whether 219 // to use the single-script or multi-script endpoint. 220 pathComponent := "filters" 221 if route.Script != "" { 222 if api.AccountID == "" { 223 return WorkerRouteResponse{}, errors.New("account ID required for enterprise only request") 224 } 225 pathComponent = "routes" 226 } 227 228 uri := "/zones/" + zoneID + "/workers/" + pathComponent 229 res, err := api.makeRequest("POST", uri, route) 230 if err != nil { 231 return WorkerRouteResponse{}, errors.Wrap(err, errMakeRequestError) 232 } 233 var r WorkerRouteResponse 234 err = json.Unmarshal(res, &r) 235 if err != nil { 236 return WorkerRouteResponse{}, errors.Wrap(err, errUnmarshalError) 237 } 238 return r, nil 239} 240 241// DeleteWorkerRoute deletes worker route for a zone 242// 243// API reference: https://api.cloudflare.com/#worker-filters-delete-filter 244func (api *API) DeleteWorkerRoute(zoneID string, routeID string) (WorkerRouteResponse, error) { 245 // For deleting a route, it doesn't matter whether we use the 246 // single-script or multi-script endpoint 247 uri := "/zones/" + zoneID + "/workers/filters/" + routeID 248 res, err := api.makeRequest("DELETE", uri, nil) 249 if err != nil { 250 return WorkerRouteResponse{}, errors.Wrap(err, errMakeRequestError) 251 } 252 var r WorkerRouteResponse 253 err = json.Unmarshal(res, &r) 254 if err != nil { 255 return WorkerRouteResponse{}, errors.Wrap(err, errUnmarshalError) 256 } 257 return r, nil 258} 259 260// ListWorkerRoutes returns list of worker routes 261// 262// API reference: https://api.cloudflare.com/#worker-filters-list-filters 263func (api *API) ListWorkerRoutes(zoneID string) (WorkerRoutesResponse, error) { 264 pathComponent := "filters" 265 if api.AccountID != "" { 266 pathComponent = "routes" 267 } 268 uri := "/zones/" + zoneID + "/workers/" + pathComponent 269 res, err := api.makeRequest("GET", uri, nil) 270 if err != nil { 271 return WorkerRoutesResponse{}, errors.Wrap(err, errMakeRequestError) 272 } 273 var r WorkerRoutesResponse 274 err = json.Unmarshal(res, &r) 275 if err != nil { 276 return WorkerRoutesResponse{}, errors.Wrap(err, errUnmarshalError) 277 } 278 for i := range r.Routes { 279 route := &r.Routes[i] 280 // The Enabled flag will not be set in the multi-script API response 281 // so we manually set it to true if the script name is not empty 282 // in case any multi-script customers rely on the Enabled field 283 if route.Script != "" { 284 route.Enabled = true 285 } 286 } 287 return r, nil 288} 289 290// UpdateWorkerRoute updates worker route for a zone. 291// 292// API reference: https://api.cloudflare.com/#worker-filters-update-filter 293func (api *API) UpdateWorkerRoute(zoneID string, routeID string, route WorkerRoute) (WorkerRouteResponse, error) { 294 // Check whether a script name is defined in order to determine whether 295 // to use the single-script or multi-script endpoint. 296 pathComponent := "filters" 297 if route.Script != "" { 298 if api.AccountID == "" { 299 return WorkerRouteResponse{}, errors.New("account ID required for enterprise only request") 300 } 301 pathComponent = "routes" 302 } 303 uri := "/zones/" + zoneID + "/workers/" + pathComponent + "/" + routeID 304 res, err := api.makeRequest("PUT", uri, route) 305 if err != nil { 306 return WorkerRouteResponse{}, errors.Wrap(err, errMakeRequestError) 307 } 308 var r WorkerRouteResponse 309 err = json.Unmarshal(res, &r) 310 if err != nil { 311 return WorkerRouteResponse{}, errors.Wrap(err, errUnmarshalError) 312 } 313 return r, nil 314} 315