-
Notifications
You must be signed in to change notification settings - Fork 630
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2304 from rwhelan/resource_list_item
`cloudflare_list_item` resource
- Loading branch information
Showing
14 changed files
with
666 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,4 +4,4 @@ cloudflare_list | |
|
||
```release-note:new-data-source | ||
cloudflare_lists | ||
``` | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
```release-note:new-resource | ||
cloudflare_list_item | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
--- | ||
page_title: "cloudflare_list_item Resource - Cloudflare" | ||
subcategory: "" | ||
description: |- | ||
Provides individual list items (IPs, Redirects) to be used in Edge Rules Engine | ||
across all zones within the same account. | ||
--- | ||
|
||
# cloudflare_list_item (Resource) | ||
|
||
Provides individual list items (IPs, Redirects) to be used in Edge Rules Engine | ||
across all zones within the same account. | ||
|
||
## Example Usage | ||
|
||
```terraform | ||
resource "cloudflare_list" "example_ip_list" { | ||
account_id = "f037e56e89293a057740de681ac9abbe" | ||
name = "example_list" | ||
description = "example IPs for a list" | ||
kind = "ip" | ||
} | ||
# IP List Item | ||
resource "cloudflare_list_item" "example_ip_item" { | ||
account_id = "f037e56e89293a057740de681ac9abbe" | ||
list_id = cloudflare_list.example_ip_list.id | ||
comment = "List Item Comment" | ||
ip = "192.0.2.0" | ||
} | ||
# Redirect List Item | ||
resource "cloudflare_list_item" "test_two" { | ||
account_id = "f037e56e89293a057740de681ac9abbe" | ||
list_id = cloudflare_list.example_ip_list.id | ||
redirect { | ||
source_url = "https://source.tld" | ||
target_url = "https://target.tld" | ||
status_code = 302 | ||
subpath_matching = "enabled" | ||
} | ||
} | ||
``` | ||
<!-- schema generated by tfplugindocs --> | ||
## Schema | ||
|
||
### Required | ||
|
||
- `account_id` (String) The account identifier to target for the resource. | ||
- `list_id` (String) The list identifier to target for the resource. | ||
|
||
### Optional | ||
|
||
- `comment` (String) An optional comment for the item. | ||
- `ip` (String) IP address to include in the list. Must provide only one of `ip`, `redirect`. **Modifying this attribute will force creation of a new resource.** | ||
- `redirect` (Block List, Max: 1) Redirect configuration to store in the list. Must provide only one of `ip`, `redirect`. **Modifying this attribute will force creation of a new resource.** (see [below for nested schema](#nestedblock--redirect)) | ||
|
||
### Read-Only | ||
|
||
- `id` (String) The ID of this resource. | ||
|
||
<a id="nestedblock--redirect"></a> | ||
### Nested Schema for `redirect` | ||
|
||
Required: | ||
|
||
- `source_url` (String) The source url of the redirect. | ||
- `target_url` (String) The target url of the redirect. | ||
|
||
Optional: | ||
|
||
- `include_subdomains` (String) Whether the redirect also matches subdomains of the source url. Available values: `disabled`, `enabled`. | ||
- `preserve_path_suffix` (String) Whether to preserve the path suffix when doing subpath matching. Available values: `disabled`, `enabled`. | ||
- `preserve_query_string` (String) Whether the redirect target url should keep the query string of the request's url. Available values: `disabled`, `enabled`. | ||
- `status_code` (Number) The status code to be used when redirecting a request. | ||
- `subpath_matching` (String) Whether the redirect also matches subpaths of the source url. Available values: `disabled`, `enabled`. | ||
|
||
## Import | ||
|
||
Import is supported using the following syntax: | ||
|
||
```shell | ||
$ terraform import cloudflare_list.example <account_id>/<list_id>/<item_id> | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
$ terraform import cloudflare_list.example <account_id>/<list_id>/<item_id> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
resource "cloudflare_list" "example_ip_list" { | ||
account_id = "f037e56e89293a057740de681ac9abbe" | ||
name = "example_list" | ||
description = "example IPs for a list" | ||
kind = "ip" | ||
} | ||
|
||
# IP List Item | ||
resource "cloudflare_list_item" "example_ip_item" { | ||
account_id = "f037e56e89293a057740de681ac9abbe" | ||
list_id = cloudflare_list.example_ip_list.id | ||
comment = "List Item Comment" | ||
ip = "192.0.2.0" | ||
} | ||
|
||
|
||
# Redirect List Item | ||
resource "cloudflare_list_item" "test_two" { | ||
account_id = "f037e56e89293a057740de681ac9abbe" | ||
list_id = cloudflare_list.example_ip_list.id | ||
redirect { | ||
source_url = "https://source.tld" | ||
target_url = "https://target.tld" | ||
status_code = 302 | ||
subpath_matching = "enabled" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
210 changes: 210 additions & 0 deletions
210
internal/sdkv2provider/resource_cloudflare_list_item.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,210 @@ | ||
package sdkv2provider | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"sort" | ||
"strings" | ||
|
||
"github.com/MakeNowJust/heredoc/v2" | ||
"github.com/cloudflare/cloudflare-go" | ||
"github.com/cloudflare/terraform-provider-cloudflare/internal/consts" | ||
"github.com/hashicorp/terraform-plugin-log/tflog" | ||
"github.com/hashicorp/terraform-plugin-sdk/v2/diag" | ||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" | ||
"github.com/pkg/errors" | ||
) | ||
|
||
func resourceCloudflareListItem() *schema.Resource { | ||
return &schema.Resource{ | ||
Schema: resourceCloudflareListItemSchema(), | ||
CreateContext: resourceCloudflareListItemCreate, | ||
ReadContext: resourceCloudflareListItemRead, | ||
UpdateContext: resourceCloudflareListItemUpdate, | ||
DeleteContext: resourceCloudflareListItemDelete, | ||
Importer: &schema.ResourceImporter{ | ||
StateContext: resourceCloudflareListItemImport, | ||
}, | ||
Description: heredoc.Doc(` | ||
Provides individual list items (IPs, Redirects) to be used in Edge Rules Engine | ||
across all zones within the same account. | ||
`), | ||
} | ||
} | ||
|
||
func resourceCloudflareListItemCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { | ||
client := meta.(*cloudflare.API) | ||
accountID := d.Get(consts.AccountIDSchemaKey).(string) | ||
listID := d.Get("list_id").(string) | ||
listItemType := listItemType(d) | ||
|
||
list, err := client.GetList(ctx, cloudflare.AccountIdentifier(accountID), listID) | ||
if err != nil { | ||
return diag.FromErr(fmt.Errorf("unable to find list with id %s: %w", listID, err)) | ||
} | ||
|
||
if list.Kind != listItemType { | ||
return diag.FromErr(fmt.Errorf("items of type %s can not be added to lists of type %s", listItemType, list.Kind)) | ||
} | ||
|
||
createListItemResponse, err := client.CreateListItem(ctx, cloudflare.AccountIdentifier(accountID), cloudflare.ListCreateItemParams{ | ||
ID: listID, | ||
Item: buildListItemCreateRequest(d), | ||
}) | ||
if err != nil { | ||
return diag.FromErr(fmt.Errorf("unable to create list item on list id %s: %w", listID, err)) | ||
} | ||
|
||
newestItem := mostRecentlyCreatedItem(createListItemResponse) | ||
d.SetId(newestItem.ID) | ||
|
||
return nil | ||
} | ||
|
||
func resourceCloudflareListItemImport(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { | ||
attributes := strings.SplitN(d.Id(), "/", 3) | ||
|
||
if len(attributes) != 3 { | ||
return nil, fmt.Errorf("invalid id (\"%s\") specified, should be in format \"accountID/listID/itemID\"", d.Id()) | ||
} | ||
|
||
accountID, listID, itemID := attributes[0], attributes[1], attributes[2] | ||
d.SetId(itemID) | ||
d.Set(consts.AccountIDSchemaKey, accountID) | ||
d.Set("list_id", listID) | ||
|
||
resourceCloudflareListItemRead(ctx, d, meta) | ||
|
||
return []*schema.ResourceData{d}, nil | ||
} | ||
|
||
func resourceCloudflareListItemRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { | ||
client := meta.(*cloudflare.API) | ||
accountID := d.Get(consts.AccountIDSchemaKey).(string) | ||
listID := d.Get("list_id").(string) | ||
|
||
listItem, err := client.GetListItem(ctx, cloudflare.AccountIdentifier(accountID), listID, d.Id()) | ||
if err != nil { | ||
var notFoundError *cloudflare.NotFoundError | ||
if errors.As(err, ¬FoundError) { | ||
tflog.Info(ctx, fmt.Sprintf("List item %s no longer exists", d.Id())) | ||
d.SetId("") | ||
return nil | ||
} | ||
return diag.FromErr(errors.Wrap(err, fmt.Sprintf("error reading List Item with ID %q", d.Id()))) | ||
} | ||
|
||
d.Set("comment", listItem.Comment) | ||
|
||
if listItem.IP != nil { | ||
d.Set("ip", listItem.IP) | ||
} | ||
|
||
if listItem.Redirect != nil { | ||
optBoolToString := func(b *bool) string { | ||
if b != nil { | ||
switch *b { | ||
case true: | ||
return "enabled" | ||
case false: | ||
return "disabled" | ||
} | ||
} | ||
return "" | ||
} | ||
|
||
d.Set("source_url", listItem.Redirect.SourceUrl) | ||
d.Set("include_subdomains", optBoolToString(listItem.Redirect.IncludeSubdomains)) | ||
d.Set("target_url", listItem.Redirect.TargetUrl) | ||
d.Set("status_code", listItem.Redirect.StatusCode) | ||
d.Set("preserve_query_string", optBoolToString(listItem.Redirect.PreserveQueryString)) | ||
d.Set("subpath_matching", optBoolToString(listItem.Redirect.SubpathMatching)) | ||
d.Set("preserve_path_suffix", optBoolToString(listItem.Redirect.PreservePathSuffix)) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func resourceCloudflareListItemUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { | ||
cr := resourceCloudflareListItemCreate(ctx, d, meta) | ||
if cr != nil { | ||
return cr | ||
} | ||
|
||
return resourceCloudflareListItemRead(ctx, d, meta) | ||
} | ||
|
||
func resourceCloudflareListItemDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { | ||
client := meta.(*cloudflare.API) | ||
accountID := d.Get(consts.AccountIDSchemaKey).(string) | ||
listID := d.Get("list_id").(string) | ||
|
||
_, err := client.DeleteListItems(ctx, cloudflare.AccountIdentifier(accountID), cloudflare.ListDeleteItemsParams{ | ||
ID: listID, | ||
Items: cloudflare.ListItemDeleteRequest{ | ||
Items: []cloudflare.ListItemDeleteItemRequest{{ID: d.Id()}}, | ||
}, | ||
}) | ||
if err != nil { | ||
return diag.FromErr(errors.Wrap(err, fmt.Sprintf("error removing List Item %s from list %s", listID, d.Id()))) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func listItemType(d *schema.ResourceData) string { | ||
if _, ok := d.GetOk("ip"); ok { | ||
return "ip" | ||
} | ||
|
||
return "redirect" | ||
} | ||
|
||
func buildListItemCreateRequest(d *schema.ResourceData) cloudflare.ListItemCreateRequest { | ||
itemType := listItemType(d) | ||
|
||
request := cloudflare.ListItemCreateRequest{ | ||
Comment: d.Get("comment").(string), | ||
} | ||
|
||
if itemType == "ip" { | ||
request.IP = cloudflare.StringPtr(d.Get("ip").(string)) | ||
return request | ||
} | ||
|
||
stringToOptBool := func(r map[string]interface{}, s string) *bool { | ||
switch r[s] { | ||
case "enabled": | ||
return cloudflare.BoolPtr(true) | ||
case "disabled": | ||
return cloudflare.BoolPtr(false) | ||
default: | ||
return nil | ||
} | ||
} | ||
|
||
redirect := d.Get("redirect").([]interface{})[0].(map[string]interface{}) | ||
request.Redirect = &cloudflare.Redirect{ | ||
SourceUrl: redirect["source_url"].(string), | ||
TargetUrl: redirect["target_url"].(string), | ||
} | ||
|
||
if value, ok := redirect["status_code"]; ok && value != 0 { | ||
request.Redirect.StatusCode = cloudflare.IntPtr(value.(int)) | ||
} | ||
|
||
request.Redirect.IncludeSubdomains = stringToOptBool(redirect, "include_subdomains") | ||
request.Redirect.PreserveQueryString = stringToOptBool(redirect, "preserve_query_string") | ||
request.Redirect.SubpathMatching = stringToOptBool(redirect, "subpath_matching") | ||
request.Redirect.PreservePathSuffix = stringToOptBool(redirect, "preserve_path_suffix") | ||
|
||
return request | ||
} | ||
|
||
func mostRecentlyCreatedItem(createListItemResponse []cloudflare.ListItem) cloudflare.ListItem { | ||
sort.Slice(createListItemResponse, func(i, j int) bool { | ||
return createListItemResponse[i].CreatedOn.After(*createListItemResponse[j].CreatedOn) | ||
}) | ||
|
||
return createListItemResponse[0] | ||
} |
Oops, something went wrong.