Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[gateway] support descriptions in gateway list items #3488

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/3488.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
resource/cloudflare_teams_list: add support for descriptions on list items
```
52 changes: 42 additions & 10 deletions internal/sdkv2provider/resource_cloudflare_teams_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,21 +35,28 @@ func resourceCloudflareTeamsList() *schema.Resource {
func resourceCloudflareTeamsListCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*cloudflare.API)

accountID := d.Get(consts.AccountIDSchemaKey).(string)

newTeamsList := cloudflare.CreateTeamsListParams{
Name: d.Get("name").(string),
Type: d.Get("type").(string),
Description: d.Get("description").(string),
}

itemValues := d.Get("items").(*schema.Set).List()
for _, v := range itemValues {
newTeamsList.Items = append(newTeamsList.Items, cloudflare.TeamsListItem{Value: v.(string)})
itemsWithoutDescription := d.Get("items").(*schema.Set).List()
itemsWithDescriptionValues := d.Get("items_with_description").(*schema.Set).List()
allItems := append([]interface{}{}, itemsWithoutDescription...)
allItems = append(allItems, itemsWithDescriptionValues...)
for _, v := range allItems {
item, err := convertItemCFTeamsListItems(v)
if err != nil {
return diag.FromErr(fmt.Errorf("error creating Teams List for account %q: %w", accountID, err))
}
newTeamsList.Items = append(newTeamsList.Items, *item)
}

tflog.Debug(ctx, fmt.Sprintf("Creating Cloudflare Teams List from struct: %+v", newTeamsList))

accountID := d.Get(consts.AccountIDSchemaKey).(string)

identifier := cloudflare.AccountIdentifier(accountID)
list, err := client.CreateTeamsList(ctx, identifier, newTeamsList)
if err != nil {
Expand Down Expand Up @@ -89,7 +96,13 @@ func resourceCloudflareTeamsListRead(ctx context.Context, d *schema.ResourceData
return diag.FromErr(fmt.Errorf("error finding Teams List %q: %w", d.Id(), err))
}

d.Set("items", convertListItemsToSchema(listItems))
itemsWithoutDescription, itemsWithDescription := convertListItemsToSchema(listItems)
// items with description and without description are processed in separate attributes,
// so customers may mix and match these two formats instead of forcing them to adopt one style
// The provider will stitch these fields together before processing
// this was done to avoid having to specify all items in object format(which is clunky), since terraform can not implement mixed types atm
d.Set("items", itemsWithoutDescription)
d.Set("items_with_description", itemsWithDescription)

return nil
}
Expand Down Expand Up @@ -192,13 +205,32 @@ func setListItemDiff(patchList *cloudflare.PatchTeamsListParams, oldItems, newIt
}
}

func convertListItemsToSchema(listItems []cloudflare.TeamsListItem) []string {
itemValues := []string{}
func convertItemCFTeamsListItems(item any) (*cloudflare.TeamsListItem, error) {
switch item.(type) {
case string:
return &cloudflare.TeamsListItem{Description: "", Value: item.(string)}, nil
case map[string]interface{}:
return &cloudflare.TeamsListItem{Description: item.(map[string]interface{})["description"].(string), Value: item.(map[string]interface{})["value"].(string)}, nil
}

return nil, fmt.Errorf("invalid list item `%v`. Should be string OR {\"description\": .., \"value\": ..} object", item)
}

// this method returns array of list items without any description and map of items with description and value separate
func convertListItemsToSchema(listItems []cloudflare.TeamsListItem) ([]string, []map[string]string) {
itemValuesWithDescription := []map[string]string{}
itemValuesWithoutDescription := []string{}
// The API returns items in reverse order so we iterate backwards for correct ordering.
for i := len(listItems) - 1; i >= 0; i-- {
item := listItems[i]
itemValues = append(itemValues, item.Value)
if item.Description != "" {
itemValuesWithDescription = append(itemValuesWithDescription,
map[string]string{"value": item.Value, "description": item.Description},
)
} else {
itemValuesWithoutDescription = append(itemValuesWithoutDescription, item.Value)
}
}

return itemValues
return itemValuesWithoutDescription, itemValuesWithDescription
}
50 changes: 50 additions & 0 deletions internal/sdkv2provider/resource_cloudflare_teams_list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,43 @@ func TestAccCloudflareTeamsList_Basic(t *testing.T) {
})
}

func TestAccCloudflareTeamsList_BasicWithDescription(t *testing.T) {
// Temporarily unset CLOUDFLARE_API_TOKEN if it is set as the Access
// service does not yet support the API tokens and it results in
// misleading state error messages.
if os.Getenv("CLOUDFLARE_API_TOKEN") != "" {
t.Setenv("CLOUDFLARE_API_TOKEN", "")
}

rnd := generateRandomResourceName()
name := fmt.Sprintf("cloudflare_teams_list.%s", rnd)

resource.Test(t, resource.TestCase{
PreCheck: func() {
testAccPreCheck(t)
},
ProviderFactories: providerFactories,
CheckDestroy: testAccCheckCloudflareTeamsListDestroy,
Steps: []resource.TestStep{
{
Config: testAccCloudflareTeamsListConfigBasicWithDescription(rnd, accountID),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(name, consts.AccountIDSchemaKey, accountID),
resource.TestCheckResourceAttr(name, "name", rnd),
resource.TestCheckResourceAttr(name, "type", "DOMAIN"),
resource.TestCheckResourceAttr(name, "description", "My description"),
resource.TestCheckResourceAttr(name, "items.#", "1"),
resource.TestCheckResourceAttr(name, "items_with_description.#", "2"),
resource.TestCheckResourceAttr(name, "items.0", "abcdef.com"),
resource.TestCheckResourceAttr(name, "items_with_description.0.value", "abcd.com"),
resource.TestCheckResourceAttr(name, "items_with_description.0.description", "test"),
resource.TestCheckResourceAttr(name, "items_with_description.1.value", "abcdefghijk.com"),
resource.TestCheckResourceAttr(name, "items_with_description.1.description", "test-2"),
),
},
},
})
}
func TestAccCloudflareTeamsList_LottaListItems(t *testing.T) {
// Temporarily unset CLOUDFLARE_API_TOKEN if it is set as the Access
// service does not yet support the API tokens and it results in
Expand Down Expand Up @@ -118,6 +155,19 @@ resource "cloudflare_teams_list" "%[1]s" {
`, rnd, accountID)
}

func testAccCloudflareTeamsListConfigBasicWithDescription(rnd, accountID string) string {
return fmt.Sprintf(`
resource "cloudflare_teams_list" "%[1]s" {
account_id = "%[2]s"
name = "%[1]s"
description = "My description"
type = "DOMAIN"
items = [ "abcdef.com"]
items_with_description = [{"value" : "abcd.com", "description": "test"}, {"value" : "abcdefghijk.com", "description": "test-2"}]
}
`, rnd, accountID)
}

func testAccCloudflareTeamsListConfigBigItemCount(rnd, accountID string) string {
items := []string{}
for i := 0; i < 1000; i++ {
Expand Down
22 changes: 22 additions & 0 deletions internal/sdkv2provider/schema_cloudflare_teams_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,32 @@ func resourceCloudflareTeamsListSchema() map[string]*schema.Schema {
"items": {
Type: schema.TypeSet,
Optional: true,
MaxItems: 1,
Description: "The items of the teams list.",
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
// Adding items with description as optional separate field, so they do not drown in between 1000s of string values at items attribute.
// Use this field only if you have descriptions. The provider joins items without description and this field together before processing
"items_with_description": {
Type: schema.TypeSet,
Optional: true,
MaxItems: 30000,
Description: "The items of the teams list that has explicit description.",
ConfigMode: schema.SchemaConfigModeAttr,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"value": {
Type: schema.TypeString,
Required: true,
},
"description": {
Type: schema.TypeString,
Required: true,
},
},
},
},
}
}
Loading