1package cloudflare 2 3import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "net/http" 8 "time" 9 10 "github.com/pkg/errors" 11) 12 13const ( 14 // IPListTypeIP specifies a list containing IP addresses 15 IPListTypeIP = "ip" 16) 17 18// IPListBulkOperation contains information about a Bulk Operation 19type IPListBulkOperation struct { 20 ID string `json:"id"` 21 Status string `json:"status"` 22 Error string `json:"error"` 23 Completed *time.Time `json:"completed"` 24} 25 26// IPList contains information about an IP List 27type IPList struct { 28 ID string `json:"id"` 29 Name string `json:"name"` 30 Description string `json:"description"` 31 Kind string `json:"kind"` 32 NumItems int `json:"num_items"` 33 NumReferencingFilters int `json:"num_referencing_filters"` 34 CreatedOn *time.Time `json:"created_on"` 35 ModifiedOn *time.Time `json:"modified_on"` 36} 37 38// IPListItem contains information about a single IP List Item 39type IPListItem struct { 40 ID string `json:"id"` 41 IP string `json:"ip"` 42 Comment string `json:"comment"` 43 CreatedOn *time.Time `json:"created_on"` 44 ModifiedOn *time.Time `json:"modified_on"` 45} 46 47// IPListCreateRequest contains data for a new IP List 48type IPListCreateRequest struct { 49 Name string `json:"name"` 50 Description string `json:"description"` 51 Kind string `json:"kind"` 52} 53 54// IPListItemCreateRequest contains data for a new IP List Item 55type IPListItemCreateRequest struct { 56 IP string `json:"ip"` 57 Comment string `json:"comment"` 58} 59 60// IPListItemDeleteRequest wraps IP List Items that shall be deleted 61type IPListItemDeleteRequest struct { 62 Items []IPListItemDeleteItemRequest `json:"items"` 63} 64 65// IPListItemDeleteItemRequest contains single IP List Items that shall be deleted 66type IPListItemDeleteItemRequest struct { 67 ID string `json:"id"` 68} 69 70// IPListUpdateRequest contains data for an IP List update 71type IPListUpdateRequest struct { 72 Description string `json:"description"` 73} 74 75// IPListResponse contains a single IP List 76type IPListResponse struct { 77 Response 78 Result IPList `json:"result"` 79} 80 81// IPListItemCreateResponse contains information about the creation of an IP List Item 82type IPListItemCreateResponse struct { 83 Response 84 Result struct { 85 OperationID string `json:"operation_id"` 86 } `json:"result"` 87} 88 89// IPListListResponse contains a slice of IP Lists 90type IPListListResponse struct { 91 Response 92 Result []IPList `json:"result"` 93} 94 95// IPListBulkOperationResponse contains information about a Bulk Operation 96type IPListBulkOperationResponse struct { 97 Response 98 Result IPListBulkOperation `json:"result"` 99} 100 101// IPListDeleteResponse contains information about the deletion of an IP List 102type IPListDeleteResponse struct { 103 Response 104 Result struct { 105 ID string `json:"id"` 106 } `json:"result"` 107} 108 109// IPListItemsListResponse contains information about IP List Items 110type IPListItemsListResponse struct { 111 Response 112 ResultInfo `json:"result_info"` 113 Result []IPListItem `json:"result"` 114} 115 116// IPListItemDeleteResponse contains information about the deletion of an IP List Item 117type IPListItemDeleteResponse struct { 118 Response 119 Result struct { 120 OperationID string `json:"operation_id"` 121 } `json:"result"` 122} 123 124// IPListItemsGetResponse contains information about a single IP List Item 125type IPListItemsGetResponse struct { 126 Response 127 Result IPListItem `json:"result"` 128} 129 130// ListIPLists lists all IP Lists 131// 132// API reference: https://api.cloudflare.com/#rules-lists-list-lists 133func (api *API) ListIPLists(ctx context.Context) ([]IPList, error) { 134 uri := fmt.Sprintf("/accounts/%s/rules/lists", api.AccountID) 135 res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) 136 if err != nil { 137 return []IPList{}, err 138 } 139 140 result := IPListListResponse{} 141 if err := json.Unmarshal(res, &result); err != nil { 142 return []IPList{}, errors.Wrap(err, errUnmarshalError) 143 } 144 145 return result.Result, nil 146} 147 148// CreateIPList creates a new IP List 149// 150// API reference: https://api.cloudflare.com/#rules-lists-create-list 151func (api *API) CreateIPList(ctx context.Context, name string, description string, kind string) (IPList, 152 error) { 153 uri := fmt.Sprintf("/accounts/%s/rules/lists", api.AccountID) 154 res, err := api.makeRequestContext(ctx, http.MethodPost, uri, 155 IPListCreateRequest{Name: name, Description: description, Kind: kind}) 156 if err != nil { 157 return IPList{}, err 158 } 159 160 result := IPListResponse{} 161 if err := json.Unmarshal(res, &result); err != nil { 162 return IPList{}, errors.Wrap(err, errUnmarshalError) 163 } 164 165 return result.Result, nil 166} 167 168// GetIPList returns a single IP List 169// 170// API reference: https://api.cloudflare.com/#rules-lists-get-list 171func (api *API) GetIPList(ctx context.Context, id string) (IPList, error) { 172 uri := fmt.Sprintf("/accounts/%s/rules/lists/%s", api.AccountID, id) 173 res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) 174 if err != nil { 175 return IPList{}, err 176 } 177 178 result := IPListResponse{} 179 if err := json.Unmarshal(res, &result); err != nil { 180 return IPList{}, errors.Wrap(err, errUnmarshalError) 181 } 182 183 return result.Result, nil 184} 185 186// UpdateIPList updates the description of an existing IP List 187// 188// API reference: https://api.cloudflare.com/#rules-lists-update-list 189func (api *API) UpdateIPList(ctx context.Context, id string, description string) (IPList, error) { 190 uri := fmt.Sprintf("/accounts/%s/rules/lists/%s", api.AccountID, id) 191 res, err := api.makeRequestContext(ctx, http.MethodPut, uri, IPListUpdateRequest{Description: description}) 192 if err != nil { 193 return IPList{}, err 194 } 195 196 result := IPListResponse{} 197 if err := json.Unmarshal(res, &result); err != nil { 198 return IPList{}, errors.Wrap(err, errUnmarshalError) 199 } 200 201 return result.Result, nil 202} 203 204// DeleteIPList deletes an IP List 205// 206// API reference: https://api.cloudflare.com/#rules-lists-delete-list 207func (api *API) DeleteIPList(ctx context.Context, id string) (IPListDeleteResponse, error) { 208 uri := fmt.Sprintf("/accounts/%s/rules/lists/%s", api.AccountID, id) 209 res, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil) 210 if err != nil { 211 return IPListDeleteResponse{}, err 212 } 213 214 result := IPListDeleteResponse{} 215 if err := json.Unmarshal(res, &result); err != nil { 216 return IPListDeleteResponse{}, errors.Wrap(err, errUnmarshalError) 217 } 218 219 return result, nil 220} 221 222// ListIPListItems returns a list with all items in an IP List 223// 224// API reference: https://api.cloudflare.com/#rules-lists-list-list-items 225func (api *API) ListIPListItems(ctx context.Context, id string) ([]IPListItem, error) { 226 var list []IPListItem 227 var cursor string 228 var cursorQuery string 229 230 for { 231 if len(cursor) > 0 { 232 cursorQuery = fmt.Sprintf("?cursor=%s", cursor) 233 } 234 uri := fmt.Sprintf("/accounts/%s/rules/lists/%s/items%s", api.AccountID, id, cursorQuery) 235 res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) 236 if err != nil { 237 return []IPListItem{}, err 238 } 239 240 result := IPListItemsListResponse{} 241 if err := json.Unmarshal(res, &result); err != nil { 242 return []IPListItem{}, errors.Wrap(err, errUnmarshalError) 243 } 244 245 list = append(list, result.Result...) 246 if cursor = result.ResultInfo.Cursors.After; cursor == "" { 247 break 248 } 249 } 250 251 return list, nil 252} 253 254// CreateIPListItemAsync creates a new IP List Item asynchronously. Users have to poll the operation status by 255// using the operation_id returned by this function. 256// 257// API reference: https://api.cloudflare.com/#rules-lists-create-list-items 258func (api *API) CreateIPListItemAsync(ctx context.Context, id, ip, comment string) (IPListItemCreateResponse, error) { 259 uri := fmt.Sprintf("/accounts/%s/rules/lists/%s/items", api.AccountID, id) 260 res, err := api.makeRequestContext(ctx, http.MethodPost, uri, []IPListItemCreateRequest{{IP: ip, Comment: comment}}) 261 if err != nil { 262 return IPListItemCreateResponse{}, err 263 } 264 265 result := IPListItemCreateResponse{} 266 if err := json.Unmarshal(res, &result); err != nil { 267 return IPListItemCreateResponse{}, errors.Wrap(err, errUnmarshalError) 268 } 269 270 return result, nil 271} 272 273// CreateIPListItem creates a new IP List Item synchronously and returns the current set of IP List Items 274func (api *API) CreateIPListItem(ctx context.Context, id, ip, comment string) ([]IPListItem, error) { 275 result, err := api.CreateIPListItemAsync(ctx, id, ip, comment) 276 277 if err != nil { 278 return []IPListItem{}, err 279 } 280 281 err = api.pollIPListBulkOperation(ctx, result.Result.OperationID) 282 if err != nil { 283 return []IPListItem{}, err 284 } 285 286 return api.ListIPListItems(ctx, id) 287} 288 289// CreateIPListItemsAsync bulk creates many IP List Items asynchronously. Users have to poll the operation status by 290// using the operation_id returned by this function. 291// 292// API reference: https://api.cloudflare.com/#rules-lists-create-list-items 293func (api *API) CreateIPListItemsAsync(ctx context.Context, id string, items []IPListItemCreateRequest) ( 294 IPListItemCreateResponse, error) { 295 uri := fmt.Sprintf("/accounts/%s/rules/lists/%s/items", api.AccountID, id) 296 res, err := api.makeRequestContext(ctx, http.MethodPost, uri, items) 297 if err != nil { 298 return IPListItemCreateResponse{}, err 299 } 300 301 result := IPListItemCreateResponse{} 302 if err := json.Unmarshal(res, &result); err != nil { 303 return IPListItemCreateResponse{}, errors.Wrap(err, errUnmarshalError) 304 } 305 306 return result, nil 307} 308 309// CreateIPListItems bulk creates many IP List Items synchronously and returns the current set of IP List Items 310func (api *API) CreateIPListItems(ctx context.Context, id string, items []IPListItemCreateRequest) ( 311 []IPListItem, error) { 312 result, err := api.CreateIPListItemsAsync(ctx, id, items) 313 if err != nil { 314 return []IPListItem{}, err 315 } 316 317 err = api.pollIPListBulkOperation(ctx, result.Result.OperationID) 318 if err != nil { 319 return []IPListItem{}, err 320 } 321 322 return api.ListIPListItems(ctx, id) 323} 324 325// ReplaceIPListItemsAsync replaces all IP List Items asynchronously. Users have to poll the operation status by 326// using the operation_id returned by this function. 327// 328// API reference: https://api.cloudflare.com/#rules-lists-replace-list-items 329func (api *API) ReplaceIPListItemsAsync(ctx context.Context, id string, items []IPListItemCreateRequest) ( 330 IPListItemCreateResponse, error) { 331 uri := fmt.Sprintf("/accounts/%s/rules/lists/%s/items", api.AccountID, id) 332 res, err := api.makeRequestContext(ctx, http.MethodPut, uri, items) 333 if err != nil { 334 return IPListItemCreateResponse{}, err 335 } 336 337 result := IPListItemCreateResponse{} 338 if err := json.Unmarshal(res, &result); err != nil { 339 return IPListItemCreateResponse{}, errors.Wrap(err, errUnmarshalError) 340 } 341 342 return result, nil 343} 344 345// ReplaceIPListItems replaces all IP List Items synchronously and returns the current set of IP List Items 346func (api *API) ReplaceIPListItems(ctx context.Context, id string, items []IPListItemCreateRequest) ( 347 []IPListItem, error) { 348 result, err := api.ReplaceIPListItemsAsync(ctx, id, items) 349 if err != nil { 350 return []IPListItem{}, err 351 } 352 353 err = api.pollIPListBulkOperation(ctx, result.Result.OperationID) 354 if err != nil { 355 return []IPListItem{}, err 356 } 357 358 return api.ListIPListItems(ctx, id) 359} 360 361// DeleteIPListItemsAsync removes specific Items of an IP List by their ID asynchronously. Users have to poll the 362// operation status by using the operation_id returned by this function. 363// 364// API reference: https://api.cloudflare.com/#rules-lists-delete-list-items 365func (api *API) DeleteIPListItemsAsync(ctx context.Context, id string, items IPListItemDeleteRequest) ( 366 IPListItemDeleteResponse, error) { 367 uri := fmt.Sprintf("/accounts/%s/rules/lists/%s/items", api.AccountID, id) 368 res, err := api.makeRequestContext(ctx, http.MethodDelete, uri, items) 369 if err != nil { 370 return IPListItemDeleteResponse{}, err 371 } 372 373 result := IPListItemDeleteResponse{} 374 if err := json.Unmarshal(res, &result); err != nil { 375 return IPListItemDeleteResponse{}, errors.Wrap(err, errUnmarshalError) 376 } 377 378 return result, nil 379} 380 381// DeleteIPListItems removes specific Items of an IP List by their ID synchronously and returns the current set 382// of IP List Items 383func (api *API) DeleteIPListItems(ctx context.Context, id string, items IPListItemDeleteRequest) ( 384 []IPListItem, error) { 385 result, err := api.DeleteIPListItemsAsync(ctx, id, items) 386 if err != nil { 387 return []IPListItem{}, err 388 } 389 390 err = api.pollIPListBulkOperation(ctx, result.Result.OperationID) 391 if err != nil { 392 return []IPListItem{}, err 393 } 394 395 return api.ListIPListItems(ctx, id) 396} 397 398// GetIPListItem returns a single IP List Item 399// 400// API reference: https://api.cloudflare.com/#rules-lists-get-list-item 401func (api *API) GetIPListItem(ctx context.Context, listID, id string) (IPListItem, error) { 402 uri := fmt.Sprintf("/accounts/%s/rules/lists/%s/items/%s", api.AccountID, listID, id) 403 res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) 404 if err != nil { 405 return IPListItem{}, err 406 } 407 408 result := IPListItemsGetResponse{} 409 if err := json.Unmarshal(res, &result); err != nil { 410 return IPListItem{}, errors.Wrap(err, errUnmarshalError) 411 } 412 413 return result.Result, nil 414} 415 416// GetIPListBulkOperation returns the status of a bulk operation 417// 418// API reference: https://api.cloudflare.com/#rules-lists-get-bulk-operation 419func (api *API) GetIPListBulkOperation(ctx context.Context, id string) (IPListBulkOperation, error) { 420 uri := fmt.Sprintf("/accounts/%s/rules/lists/bulk_operations/%s", api.AccountID, id) 421 res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) 422 if err != nil { 423 return IPListBulkOperation{}, err 424 } 425 426 result := IPListBulkOperationResponse{} 427 if err := json.Unmarshal(res, &result); err != nil { 428 return IPListBulkOperation{}, errors.Wrap(err, errUnmarshalError) 429 } 430 431 return result.Result, nil 432} 433 434// pollIPListBulkOperation implements synchronous behaviour for some asynchronous endpoints. 435// bulk-operation status can be either pending, running, failed or completed 436func (api *API) pollIPListBulkOperation(ctx context.Context, id string) error { 437 for i := uint8(0); i < 16; i++ { 438 sleepDuration := 1 << (i / 2) * time.Second 439 select { 440 case <-time.After(sleepDuration): 441 case <-ctx.Done(): 442 return errors.Wrap(ctx.Err(), "operation aborted during backoff") 443 } 444 445 bulkResult, err := api.GetIPListBulkOperation(ctx, id) 446 if err != nil { 447 return err 448 } 449 450 switch bulkResult.Status { 451 case "failed": 452 return errors.New(bulkResult.Error) 453 case "pending", "running": 454 continue 455 case "completed": 456 return nil 457 default: 458 return errors.New(fmt.Sprintf("%s: %s", errOperationUnexpectedStatus, bulkResult.Status)) 459 } 460 } 461 462 return errors.New(errOperationStillRunning) 463} 464