From 4e2cc28c0048ae9873ae5484d8a1c02b172bc22c Mon Sep 17 00:00:00 2001 From: Inanna Malick Date: Mon, 15 Jul 2024 10:53:40 -0700 Subject: [PATCH 01/27] Fix partial risk behavior configuration bug Previously, if a user's account supported risk behaviors A, B and C but only behaviors A and B were configured, a 'provider produced inconsistient results after apply' error occured. This is because the cloudflare API call for user risk behaviors returns the status of all behaviors. This PR changes the user risk behavior terraform provider to - on read, create and update calls - prune the API call results to only include the behaviors the terraform config includes A test confirming that the fix works has been included --- .changelog/3463.txt | 3 ++ .../service/risk_behavior/resource.go | 36 +++++++++++++++++-- .../service/risk_behavior/resource_test.go | 32 +++++++++++++++++ 3 files changed, 68 insertions(+), 3 deletions(-) create mode 100644 .changelog/3463.txt diff --git a/.changelog/3463.txt b/.changelog/3463.txt new file mode 100644 index 0000000000..bf0d6ea3b2 --- /dev/null +++ b/.changelog/3463.txt @@ -0,0 +1,3 @@ +```release-note:bug +fix resource/cloudflare_risk_behavior bug where partial definition of risk behaviors resulted in a provider error +``` diff --git a/internal/framework/service/risk_behavior/resource.go b/internal/framework/service/risk_behavior/resource.go index 837332b916..cd99580f46 100644 --- a/internal/framework/service/risk_behavior/resource.go +++ b/internal/framework/service/risk_behavior/resource.go @@ -70,7 +70,14 @@ func (r *RiskBehaviorResource) Create(ctx context.Context, req resource.CreateRe return } - behaviorsSet := ConvertBehaviorsCtoT(behaviors.Behaviors) + retained := map[string]cloudflare.Behavior{} + for k, b := range behaviors.Behaviors { + _, ok := behaviorsMap[k] + if ok { + retained[k] = b + } + } + behaviorsSet := ConvertBehaviorsCtoT(retained) data.AccountID = types.StringValue(accountId) data.Behaviors = behaviorsSet @@ -94,13 +101,28 @@ func (r *RiskBehaviorResource) Read(ctx context.Context, req resource.ReadReques return } - behaviorsSet := ConvertBehaviorsCtoT(behaviors.Behaviors) + retained := map[string]cloudflare.Behavior{} + for k, b := range behaviors.Behaviors { + if containsBehavior(data.Behaviors, k) { + retained[k] = b + } + } + behaviorsSet := ConvertBehaviorsCtoT(retained) data.AccountID = types.StringValue(accountId) data.Behaviors = behaviorsSet resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } +func containsBehavior(s []RiskBehaviorBehaviorModel, n string) bool { + for _, a := range s { + if a.Name.ValueString() == n { + return true + } + } + return false +} + func ConvertBehaviorsTtoC(b []RiskBehaviorBehaviorModel) (map[string]cloudflare.Behavior, error) { behaviorsMap := map[string]cloudflare.Behavior{} for _, b := range b { @@ -163,7 +185,15 @@ func (r *RiskBehaviorResource) Update(ctx context.Context, req resource.UpdateRe return } - behaviorsSet := ConvertBehaviorsCtoT(behaviors.Behaviors) + retained := map[string]cloudflare.Behavior{} + for k, b := range behaviors.Behaviors { + _, ok := behaviorsMap[k] + if ok { + retained[k] = b + } + } + + behaviorsSet := ConvertBehaviorsCtoT(retained) data.AccountID = types.StringValue(accountId) data.Behaviors = behaviorsSet diff --git a/internal/framework/service/risk_behavior/resource_test.go b/internal/framework/service/risk_behavior/resource_test.go index da05af51b3..8352b7472d 100644 --- a/internal/framework/service/risk_behavior/resource_test.go +++ b/internal/framework/service/risk_behavior/resource_test.go @@ -51,6 +51,38 @@ func init() { }) } +func TestAccCloudflareRiskBehavior_Partial(t *testing.T) { + rnd := utils.GenerateRandomResourceName() + name := "cloudflare_risk_behavior." + rnd + accountID := os.Getenv("CLOUDFLARE_ACCOUNT_ID") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccCloudflareRiskBehaviorsPartial(rnd, accountID), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(name, consts.AccountIDSchemaKey, accountID), + resource.TestCheckResourceAttr(name, "behavior.#", "1"), + ), + }, + }, + }) +} + +func testAccCloudflareRiskBehaviorsPartial(name, accountId string) string { + return fmt.Sprintf(` + resource cloudflare_risk_behavior %s { + account_id = "%s" + behavior { + name = "imp_travel" + enabled = true + risk_level = "high" + } + }`, name, accountId) +} + func TestAccCloudflareRiskBehavior_Basic(t *testing.T) { rnd := utils.GenerateRandomResourceName() name := "cloudflare_risk_behavior." + rnd From 46079538fd395ce489e5bc02ce997c06d90845ed Mon Sep 17 00:00:00 2001 From: Rex Scaria Date: Mon, 22 Jul 2024 10:20:42 -0400 Subject: [PATCH 02/27] [gateway] support descriptions in gateway list items --- .changelog/3488.txt | 3 ++ .../resource_cloudflare_teams_list.go | 52 +++++++++++++++---- .../resource_cloudflare_teams_list_test.go | 50 ++++++++++++++++++ .../schema_cloudflare_teams_list.go | 22 ++++++++ 4 files changed, 117 insertions(+), 10 deletions(-) create mode 100644 .changelog/3488.txt diff --git a/.changelog/3488.txt b/.changelog/3488.txt new file mode 100644 index 0000000000..efa7e5b3f7 --- /dev/null +++ b/.changelog/3488.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +resource/cloudflare_teams_list: add support for descriptions on list items +``` diff --git a/internal/sdkv2provider/resource_cloudflare_teams_list.go b/internal/sdkv2provider/resource_cloudflare_teams_list.go index b383dc3fba..7feda1c2ca 100644 --- a/internal/sdkv2provider/resource_cloudflare_teams_list.go +++ b/internal/sdkv2provider/resource_cloudflare_teams_list.go @@ -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 { @@ -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 } @@ -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 } diff --git a/internal/sdkv2provider/resource_cloudflare_teams_list_test.go b/internal/sdkv2provider/resource_cloudflare_teams_list_test.go index 5dedbac3c0..e5eeaf5610 100644 --- a/internal/sdkv2provider/resource_cloudflare_teams_list_test.go +++ b/internal/sdkv2provider/resource_cloudflare_teams_list_test.go @@ -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 @@ -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++ { diff --git a/internal/sdkv2provider/schema_cloudflare_teams_list.go b/internal/sdkv2provider/schema_cloudflare_teams_list.go index 5f9ec24296..f7512bcded 100644 --- a/internal/sdkv2provider/schema_cloudflare_teams_list.go +++ b/internal/sdkv2provider/schema_cloudflare_teams_list.go @@ -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, + }, + }, + }, + }, } } From add52b1c2acea94cc593958c0f63422a4e646963 Mon Sep 17 00:00:00 2001 From: Jacob Bednarz Date: Wed, 24 Jul 2024 10:59:27 +1000 Subject: [PATCH 03/27] Revert "build(deps): bump github.com/cloudflare/cloudflare-go from 0.99.0 to 0.100.0" --- .changelog/3484.txt | 3 --- go.mod | 2 +- go.sum | 4 ++-- 3 files changed, 3 insertions(+), 6 deletions(-) delete mode 100644 .changelog/3484.txt diff --git a/.changelog/3484.txt b/.changelog/3484.txt deleted file mode 100644 index 2eb3fbfebc..0000000000 --- a/.changelog/3484.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:dependency -provider: bump github.com/cloudflare/cloudflare-go from 0.99.0 to 0.100.0 -``` diff --git a/go.mod b/go.mod index cc6879c77c..69f40af498 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.21 require ( github.com/agext/levenshtein v1.2.3 // indirect - github.com/cloudflare/cloudflare-go v0.100.0 + github.com/cloudflare/cloudflare-go v0.99.0 github.com/fatih/color v1.16.0 // indirect github.com/google/uuid v1.6.0 github.com/hashicorp/errwrap v1.1.0 // indirect diff --git a/go.sum b/go.sum index c9543d9c34..ff199f94f8 100644 --- a/go.sum +++ b/go.sum @@ -51,8 +51,8 @@ github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZ github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8= github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= -github.com/cloudflare/cloudflare-go v0.100.0 h1:4iCUI2ZoIhRMyd7Z1TDsHhH1OhkgHC83eYbPlSgTRjo= -github.com/cloudflare/cloudflare-go v0.100.0/go.mod h1:VQ1t9Mvgdu4VFLx6uwQgFC10XxcCRIUuvkYGc9daMRU= +github.com/cloudflare/cloudflare-go v0.99.0 h1:WIvF+1g6BKMVk98d2NLuVoKYrf6QAw74+rV3tpnne5M= +github.com/cloudflare/cloudflare-go v0.99.0/go.mod h1:sQzaVM6DlkWe1yqQXaql+CRt4rA8efMfpoPjNuUE1KI= github.com/cloudflare/cloudflare-go/v2 v2.4.0 h1:gys/26GoVDklgfq8NYV39WgvOEwzK/XAqYObmnI6iFg= github.com/cloudflare/cloudflare-go/v2 v2.4.0/go.mod h1:AoIzb05z/rvdJLztPct4tSa+3IqXJJ6c+pbUFMOlTr8= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= From e741b55a6b690cf8e228fbfa323da43b66518ffc Mon Sep 17 00:00:00 2001 From: Rex Scaria Date: Tue, 23 Jul 2024 21:35:15 -0400 Subject: [PATCH 04/27] [gateway] support descriptions in gateway list items(fix tests) --- internal/sdkv2provider/resource_cloudflare_teams_list.go | 6 ++++-- internal/sdkv2provider/schema_cloudflare_teams_list.go | 8 +++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/internal/sdkv2provider/resource_cloudflare_teams_list.go b/internal/sdkv2provider/resource_cloudflare_teams_list.go index 7feda1c2ca..fa6a033651 100644 --- a/internal/sdkv2provider/resource_cloudflare_teams_list.go +++ b/internal/sdkv2provider/resource_cloudflare_teams_list.go @@ -100,7 +100,8 @@ func resourceCloudflareTeamsListRead(ctx context.Context, d *schema.ResourceData // 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 + // 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) @@ -216,7 +217,8 @@ func convertItemCFTeamsListItems(item any) (*cloudflare.TeamsListItem, error) { 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 +// 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{} diff --git a/internal/sdkv2provider/schema_cloudflare_teams_list.go b/internal/sdkv2provider/schema_cloudflare_teams_list.go index f7512bcded..fbdbc64fc3 100644 --- a/internal/sdkv2provider/schema_cloudflare_teams_list.go +++ b/internal/sdkv2provider/schema_cloudflare_teams_list.go @@ -34,14 +34,16 @@ func resourceCloudflareTeamsListSchema() map[string]*schema.Schema { "items": { Type: schema.TypeSet, Optional: true, - MaxItems: 1, + MaxItems: 30000, 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 + // 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, From fe6a8314185a5d693a62d5afe5449dcb18a3475a Mon Sep 17 00:00:00 2001 From: Jacob Bednarz Date: Wed, 24 Jul 2024 11:41:25 +1000 Subject: [PATCH 05/27] gofmt --- internal/sdkv2provider/resource_cloudflare_teams_list.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/sdkv2provider/resource_cloudflare_teams_list.go b/internal/sdkv2provider/resource_cloudflare_teams_list.go index fa6a033651..f560939dfc 100644 --- a/internal/sdkv2provider/resource_cloudflare_teams_list.go +++ b/internal/sdkv2provider/resource_cloudflare_teams_list.go @@ -100,7 +100,7 @@ func resourceCloudflareTeamsListRead(ctx context.Context, d *schema.ResourceData // 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), + // 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) @@ -217,7 +217,7 @@ func convertItemCFTeamsListItems(item any) (*cloudflare.TeamsListItem, error) { 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 +// 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{} From 8ec521d2fcc1e21de49a84aece5828665e914f8b Mon Sep 17 00:00:00 2001 From: Jacob Bednarz Date: Wed, 24 Jul 2024 11:46:49 +1000 Subject: [PATCH 06/27] update CHANGELOG --- CHANGELOG.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7654f90e11..f8088b2871 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,31 @@ ## 4.38.0 (Unreleased) +FEATURES: + +* **New Data Source:** `cloudflare_gateway_categories` ([#3443](https://github.com/cloudflare/terraform-provider-cloudflare/issues/3443)) + +ENHANCEMENTS: + +* resource/cloudflare_teams_list: add support for descriptions on list items ([#3488](https://github.com/cloudflare/terraform-provider-cloudflare/issues/3488)) +* resource/cloudflare_teams_rules: add support for `ignore_cname_category_matches` ([#3473](https://github.com/cloudflare/terraform-provider-cloudflare/issues/3473)) + +BUG FIXES: + +* resource/cloudflare-access-application: fixes bug when updating self_hosted_domains ([#3468](https://github.com/cloudflare/terraform-provider-cloudflare/issues/3468)) +* resource/cloudflare_access_application: Fix bug that was not cleaning the API when removing all ids from the 'policies' list ([#3469](https://github.com/cloudflare/terraform-provider-cloudflare/issues/3469)) + +DEPENDENCIES: + +* provider: bump `github.com/aws/aws-sdk-go-v2/config` from 1.27.24 to 1.27.25 ([#3449](https://github.com/cloudflare/terraform-provider-cloudflare/issues/3449)) +* provider: bump `github.com/aws/aws-sdk-go-v2/config` from 1.27.25 to 1.27.27 ([#3483](https://github.com/cloudflare/terraform-provider-cloudflare/issues/3483)) +* provider: bump `github.com/aws/aws-sdk-go-v2/credentials` from 1.17.24 to 1.17.25 ([#3449](https://github.com/cloudflare/terraform-provider-cloudflare/issues/3449)) +* provider: bump `github.com/aws/aws-sdk-go-v2/credentials` from 1.17.25 to 1.17.27 ([#3483](https://github.com/cloudflare/terraform-provider-cloudflare/issues/3483)) +* provider: bump `github.com/aws/aws-sdk-go-v2/service/s3` from 1.58.0 to 1.58.1 ([#3449](https://github.com/cloudflare/terraform-provider-cloudflare/issues/3449)) +* provider: bump `github.com/aws/aws-sdk-go-v2/service/s3` from 1.58.1 to 1.58.2 ([#3483](https://github.com/cloudflare/terraform-provider-cloudflare/issues/3483)) +* provider: bump `github.com/aws/aws-sdk-go-v2` from 1.30.1 to 1.30.2 ([#3449](https://github.com/cloudflare/terraform-provider-cloudflare/issues/3449)) +* provider: bump `github.com/aws/aws-sdk-go-v2` from 1.30.2 to 1.30.3 ([#3483](https://github.com/cloudflare/terraform-provider-cloudflare/issues/3483)) +* provider: bump github.com/cloudflare/cloudflare-go/v2 from 2.3.0 to 2.4.0 ([#3480](https://github.com/cloudflare/terraform-provider-cloudflare/issues/3480)) + ## 4.37.0 (July 11th, 2024) ENHANCEMENTS: From b826c00d323a7dfbdcfd9092654d1567c905ea4a Mon Sep 17 00:00:00 2001 From: Jacob Bednarz Date: Wed, 24 Jul 2024 11:47:30 +1000 Subject: [PATCH 07/27] Update CHANGELOG.md --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f8088b2871..c39f7e84d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,6 @@ -## 4.38.0 (Unreleased) +## 4.39.0 (Unreleased) + +## 4.38.0 (July 24th, 2024) FEATURES: From b2ecf86c35c4df021cc9f9d15d0ccecb2278667b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 24 Jul 2024 23:02:56 +0000 Subject: [PATCH 08/27] build(deps): bump github.com/cloudflare/cloudflare-go Bumps [github.com/cloudflare/cloudflare-go](https://github.com/cloudflare/cloudflare-go) from 0.99.0 to 0.100.0. - [Release notes](https://github.com/cloudflare/cloudflare-go/releases) - [Changelog](https://github.com/cloudflare/cloudflare-go/blob/master/CHANGELOG.md) - [Commits](https://github.com/cloudflare/cloudflare-go/compare/v0.99.0...v0.100.0) --- updated-dependencies: - dependency-name: github.com/cloudflare/cloudflare-go dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 69f40af498..cc6879c77c 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.21 require ( github.com/agext/levenshtein v1.2.3 // indirect - github.com/cloudflare/cloudflare-go v0.99.0 + github.com/cloudflare/cloudflare-go v0.100.0 github.com/fatih/color v1.16.0 // indirect github.com/google/uuid v1.6.0 github.com/hashicorp/errwrap v1.1.0 // indirect diff --git a/go.sum b/go.sum index ff199f94f8..c9543d9c34 100644 --- a/go.sum +++ b/go.sum @@ -51,8 +51,8 @@ github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZ github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8= github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= -github.com/cloudflare/cloudflare-go v0.99.0 h1:WIvF+1g6BKMVk98d2NLuVoKYrf6QAw74+rV3tpnne5M= -github.com/cloudflare/cloudflare-go v0.99.0/go.mod h1:sQzaVM6DlkWe1yqQXaql+CRt4rA8efMfpoPjNuUE1KI= +github.com/cloudflare/cloudflare-go v0.100.0 h1:4iCUI2ZoIhRMyd7Z1TDsHhH1OhkgHC83eYbPlSgTRjo= +github.com/cloudflare/cloudflare-go v0.100.0/go.mod h1:VQ1t9Mvgdu4VFLx6uwQgFC10XxcCRIUuvkYGc9daMRU= github.com/cloudflare/cloudflare-go/v2 v2.4.0 h1:gys/26GoVDklgfq8NYV39WgvOEwzK/XAqYObmnI6iFg= github.com/cloudflare/cloudflare-go/v2 v2.4.0/go.mod h1:AoIzb05z/rvdJLztPct4tSa+3IqXJJ6c+pbUFMOlTr8= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= From 01223ad7580da9c59cef4b60bd17f4d402d15a2e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 24 Jul 2024 23:03:10 +0000 Subject: [PATCH 09/27] add CHANGELOG for #3499 --- .changelog/3499.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/3499.txt diff --git a/.changelog/3499.txt b/.changelog/3499.txt new file mode 100644 index 0000000000..2eb3fbfebc --- /dev/null +++ b/.changelog/3499.txt @@ -0,0 +1,3 @@ +```release-note:dependency +provider: bump github.com/cloudflare/cloudflare-go from 0.99.0 to 0.100.0 +``` From 96d6cbf6ddb631095789c22afd49f88833957eeb Mon Sep 17 00:00:00 2001 From: Jacob Bednarz Date: Thu, 25 Jul 2024 11:09:34 +1000 Subject: [PATCH 10/27] feat(workers): deprecate singular resources, use pluralised product name Updates the resource names to go from `worker_` to `workers_` where it wasn't previously correct. --- .changelog/3500.txt | 48 +++++ internal/framework/provider/provider.go | 6 +- .../model.go | 4 +- .../resource.go | 36 ++-- .../resource_test.go | 173 ++++++++++++++++++ .../schema.go | 6 +- .../model.go | 9 + .../resource.go | 134 ++++++++++++++ .../resource_test.go | 2 +- .../schema.go | 43 +++++ internal/sdkv2provider/provider.go | 5 + ...esource_cloudflare_workers_cron_trigger.go | 20 ++ ...ce_cloudflare_workers_cron_trigger_test.go | 8 +- .../resource_cloudflare_workers_domain.go | 17 ++ ...resource_cloudflare_workers_domain_test.go | 10 +- .../resource_cloudflare_workers_route.go | 15 ++ .../resource_cloudflare_workers_route_test.go | 18 +- .../resource_cloudflare_workers_script.go | 17 ++ ...resource_cloudflare_workers_script_test.go | 20 +- .../resource_cloudflare_workers_secret.go | 15 ++ ...resource_cloudflare_workers_secret_test.go | 10 +- 21 files changed, 557 insertions(+), 59 deletions(-) create mode 100644 .changelog/3500.txt rename internal/framework/service/{workers_for_platforms => workers_for_platforms_dispatch_namespace}/model.go (64%) rename internal/framework/service/{workers_for_platforms => workers_for_platforms_dispatch_namespace}/resource.go (64%) create mode 100644 internal/framework/service/workers_for_platforms_dispatch_namespace/resource_test.go rename internal/framework/service/{workers_for_platforms => workers_for_platforms_dispatch_namespace}/schema.go (83%) create mode 100644 internal/framework/service/workers_for_platforms_dispatch_namespace_deprecated/model.go create mode 100644 internal/framework/service/workers_for_platforms_dispatch_namespace_deprecated/resource.go rename internal/framework/service/{workers_for_platforms => workers_for_platforms_dispatch_namespace_deprecated}/resource_test.go (98%) create mode 100644 internal/framework/service/workers_for_platforms_dispatch_namespace_deprecated/schema.go diff --git a/.changelog/3500.txt b/.changelog/3500.txt new file mode 100644 index 0000000000..0aa80444fd --- /dev/null +++ b/.changelog/3500.txt @@ -0,0 +1,48 @@ +```release-note:internal +resource/cloudflare_worker_cron_trigger: deprecated in favour of `cloudflare_workers_cron_trigger` and will be removed in the next major version. +``` + +```release-note:internal +resource/cloudflare_worker_domain: deprecated in favour of `cloudflare_workers_domain` and will be removed in the next major version. +``` + +```release-note:internal +resource/cloudflare_worker_route: deprecated in favour of `cloudflare_workers_route` and will be removed in the next major version. +``` + +```release-note:internal +resource/cloudflare_worker_script: deprecated in favour of `cloudflare_workers_script` and will be removed in the next major version. +``` + +```release-note:internal +resource/cloudflare_worker_secret: deprecated in favour of `cloudflare_workers_secret` and will be removed in the next major version. +``` + + +```release-note:internal +resource/cloudflare_workers_for_platforms_namespace: deprecated in favour of `cloudflare_workers_for_platforms_dispatch_namespace` and will be removed in the next major version. +``` + +```release-note:new-resource +cloudflare_workers_secret +``` + +```release-note:new-resource +cloudflare_workers_script +``` + +```release-note:new-resource +cloudflare_workers_route +``` + +```release-note:new-resource +cloudflare_workers_domain +``` + +```release-note:new-resource +cloudflare_workers_cron_trigger +``` + +```release-note:new-resource +cloudflare_workers_for_platforms_dispatch_namespace +``` diff --git a/internal/framework/provider/provider.go b/internal/framework/provider/provider.go index d98086eae6..d955020798 100644 --- a/internal/framework/provider/provider.go +++ b/internal/framework/provider/provider.go @@ -30,7 +30,8 @@ import ( "github.com/cloudflare/terraform-provider-cloudflare/internal/framework/service/rulesets" "github.com/cloudflare/terraform-provider-cloudflare/internal/framework/service/turnstile" "github.com/cloudflare/terraform-provider-cloudflare/internal/framework/service/user" - "github.com/cloudflare/terraform-provider-cloudflare/internal/framework/service/workers_for_platforms" + "github.com/cloudflare/terraform-provider-cloudflare/internal/framework/service/workers_for_platforms_dispatch_namespace" + "github.com/cloudflare/terraform-provider-cloudflare/internal/framework/service/workers_for_platforms_dispatch_namespace_deprecated" "github.com/cloudflare/terraform-provider-cloudflare/internal/sdkv2provider" "github.com/cloudflare/terraform-provider-cloudflare/internal/utils" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" @@ -368,7 +369,8 @@ func (p *CloudflareProvider) Resources(ctx context.Context) []func() resource.Re rulesets.NewResource, turnstile.NewResource, access_mutual_tls_hostname_settings.NewResource, - workers_for_platforms.NewResource, + workers_for_platforms_dispatch_namespace_deprecated.NewResource, + workers_for_platforms_dispatch_namespace.NewResource, } } diff --git a/internal/framework/service/workers_for_platforms/model.go b/internal/framework/service/workers_for_platforms_dispatch_namespace/model.go similarity index 64% rename from internal/framework/service/workers_for_platforms/model.go rename to internal/framework/service/workers_for_platforms_dispatch_namespace/model.go index 27175eac24..f73cac9006 100644 --- a/internal/framework/service/workers_for_platforms/model.go +++ b/internal/framework/service/workers_for_platforms_dispatch_namespace/model.go @@ -1,8 +1,8 @@ -package workers_for_platforms +package workers_for_platforms_dispatch_namespace import "github.com/hashicorp/terraform-plugin-framework/types" -type WorkersForPlatformsNamespaceModel struct { +type WorkersForPlatformsDispatchNamespaceModel struct { AccountID types.String `tfsdk:"account_id"` Name types.String `tfsdk:"name"` ID types.String `tfsdk:"id"` diff --git a/internal/framework/service/workers_for_platforms/resource.go b/internal/framework/service/workers_for_platforms_dispatch_namespace/resource.go similarity index 64% rename from internal/framework/service/workers_for_platforms/resource.go rename to internal/framework/service/workers_for_platforms_dispatch_namespace/resource.go index 9ce04a3773..17ed4e2572 100644 --- a/internal/framework/service/workers_for_platforms/resource.go +++ b/internal/framework/service/workers_for_platforms_dispatch_namespace/resource.go @@ -1,4 +1,4 @@ -package workers_for_platforms +package workers_for_platforms_dispatch_namespace import ( "context" @@ -13,23 +13,23 @@ import ( ) // Ensure provider defined types fully satisfy framework interfaces. -var _ resource.Resource = &WorkersForPlatformsResource{} -var _ resource.ResourceWithImportState = &WorkersForPlatformsResource{} +var _ resource.Resource = &WorkersForPlatformsDispatchNamespaceResource{} +var _ resource.ResourceWithImportState = &WorkersForPlatformsDispatchNamespaceResource{} func NewResource() resource.Resource { - return &WorkersForPlatformsResource{} + return &WorkersForPlatformsDispatchNamespaceResource{} } -// WorkersForPlatformsResource defines the resource implementation. -type WorkersForPlatformsResource struct { +// WorkersForPlatformsDispatchNamespaceResource defines the resource implementation. +type WorkersForPlatformsDispatchNamespaceResource struct { client *muxclient.Client } -func (r *WorkersForPlatformsResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = req.ProviderTypeName + "_workers_for_platforms_namespace" +func (r *WorkersForPlatformsDispatchNamespaceResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_workers_for_platforms_dispatch_namespace" } -func (r *WorkersForPlatformsResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { +func (r *WorkersForPlatformsDispatchNamespaceResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { if req.ProviderData == nil { return } @@ -48,8 +48,8 @@ func (r *WorkersForPlatformsResource) Configure(ctx context.Context, req resourc r.client = client } -func (r *WorkersForPlatformsResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { - var data *WorkersForPlatformsNamespaceModel +func (r *WorkersForPlatformsDispatchNamespaceResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data *WorkersForPlatformsDispatchNamespaceModel resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) @@ -71,8 +71,8 @@ func (r *WorkersForPlatformsResource) Create(ctx context.Context, req resource.C resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } -func (r *WorkersForPlatformsResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { - var data *WorkersForPlatformsNamespaceModel +func (r *WorkersForPlatformsDispatchNamespaceResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data *WorkersForPlatformsDispatchNamespaceModel resp.Diagnostics.Append(req.State.Get(ctx, &data)...) @@ -90,8 +90,8 @@ func (r *WorkersForPlatformsResource) Read(ctx context.Context, req resource.Rea resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } -func (r *WorkersForPlatformsResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { - var data *WorkersForPlatformsNamespaceModel +func (r *WorkersForPlatformsDispatchNamespaceResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data *WorkersForPlatformsDispatchNamespaceModel resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) @@ -102,8 +102,8 @@ func (r *WorkersForPlatformsResource) Update(ctx context.Context, req resource.U resp.Diagnostics.AddError("failed to update Workers for Platforms namespace", "Not implemented") } -func (r *WorkersForPlatformsResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { - var data *WorkersForPlatformsNamespaceModel +func (r *WorkersForPlatformsDispatchNamespaceResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var data *WorkersForPlatformsDispatchNamespaceModel resp.Diagnostics.Append(req.State.Get(ctx, &data)...) @@ -119,7 +119,7 @@ func (r *WorkersForPlatformsResource) Delete(ctx context.Context, req resource.D } } -func (r *WorkersForPlatformsResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { +func (r *WorkersForPlatformsDispatchNamespaceResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { idparts := strings.Split(req.ID, "/") if len(idparts) != 2 { resp.Diagnostics.AddError("error importing Workers for Platforms namespace", "invalid ID specified. Please specify the ID as \"account_id/name\"") diff --git a/internal/framework/service/workers_for_platforms_dispatch_namespace/resource_test.go b/internal/framework/service/workers_for_platforms_dispatch_namespace/resource_test.go new file mode 100644 index 0000000000..e1be033e2b --- /dev/null +++ b/internal/framework/service/workers_for_platforms_dispatch_namespace/resource_test.go @@ -0,0 +1,173 @@ +package workers_for_platforms_dispatch_namespace_test + +import ( + "context" + "fmt" + "os" + "testing" + + cfv1 "github.com/cloudflare/cloudflare-go" + "github.com/cloudflare/terraform-provider-cloudflare/internal/acctest" + "github.com/cloudflare/terraform-provider-cloudflare/internal/utils" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" +) + +func TestMain(m *testing.M) { + resource.TestMain(m) +} + +func init() { + resource.AddTestSweepers("cloudflare_workers_for_platforms_dispatch_namespace", &resource.Sweeper{ + Name: "cloudflare_workers_for_platforms_dispatch_namespace", + F: func(region string) error { + client, err := acctest.SharedV1Client() + accountID := os.Getenv("CLOUDFLARE_ACCOUNT_ID") + + if err != nil { + return fmt.Errorf("error establishing client: %w", err) + } + + ctx := context.Background() + resp, err := client.ListWorkersForPlatformsDispatchNamespaces(ctx, cfv1.AccountIdentifier(accountID)) + if err != nil { + return err + } + + for _, namespace := range resp.Result { + err := client.DeleteWorkersForPlatformsDispatchNamespace(ctx, cfv1.AccountIdentifier(accountID), namespace.NamespaceName) + if err != nil { + return err + } + } + + return nil + }, + }) +} + +func TestAccCloudflareWorkersForPlatforms_NamespaceManagement(t *testing.T) { + rnd := utils.GenerateRandomResourceName() + accountID := os.Getenv("CLOUDFLARE_ACCOUNT_ID") + resourceName := "cloudflare_workers_for_platforms_dispatch_namespace." + rnd + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccCheckCloudflareWorkersForPlatformsNamespaceManagement(rnd, accountID), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "name", rnd), + resource.TestCheckResourceAttrSet(resourceName, "id"), + ), + }, + { + ResourceName: resourceName, + ImportStateIdPrefix: fmt.Sprintf("%s/", accountID), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckCloudflareWorkersForPlatformsNamespaceManagement(rnd, accountID string) string { + return fmt.Sprintf(` + resource "cloudflare_workers_for_platforms_dispatch_namespace" "%[1]s" { + account_id = "%[2]s" + name = "%[1]s" + }`, rnd, accountID) +} + +func TestAccCloudflareWorkersForPlatforms_UploadUserWorker(t *testing.T) { + rnd := utils.GenerateRandomResourceName() + accountID := os.Getenv("CLOUDFLARE_ACCOUNT_ID") + resourceName := "cloudflare_workers_for_platforms_dispatch_namespace." + rnd + workerResource := "cloudflare_worker_script.script_" + rnd + + scriptContent := `< Date: Fri, 26 Jul 2024 12:35:26 +1000 Subject: [PATCH 11/27] bump allowed resources for transition --- internal/sdkv2provider/provider.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/sdkv2provider/provider.go b/internal/sdkv2provider/provider.go index aadf50f2f4..305e9e0c9a 100644 --- a/internal/sdkv2provider/provider.go +++ b/internal/sdkv2provider/provider.go @@ -21,7 +21,7 @@ import ( const ( MAXIMUM_NUMBER_OF_ENTITIES_REACHED_SUMMARY = "You've attempted to add a new %[1]s to the `terraform-plugin-sdkv2` which is no longer considered suitable for use." MAXIMUM_NUMBER_OF_ENTITIES_REACHED_DETAIL = "Due the number of known internal issues with `terraform-plugin-sdkv2` (most notably handling of zero values), we are no longer recommending using it and instead, advise using `terraform-plugin-framework` exclusively. If you must use terraform-plugin-sdkv2 for this new %[1]s you should first discuss it with a maintainer to fully understand the impact and potential ramifications. Only then should you bump %[2]s to include your %[1]s." - MAXIMUM_ALLOWED_SDKV2_RESOURCES = 108 + MAXIMUM_ALLOWED_SDKV2_RESOURCES = 113 MAXIMUM_ALLOWED_SDKV2_DATASOURCES = 19 ) From 5bcc3e32f9ae672690d13b7f9815c1499c8f3522 Mon Sep 17 00:00:00 2001 From: Jacob Bednarz Date: Fri, 26 Jul 2024 12:36:05 +1000 Subject: [PATCH 12/27] test(record): add failing tests Signed-off-by: Jacob Bednarz --- .../resource_cloudflare_record_test.go | 44 ++++++++----------- 1 file changed, 19 insertions(+), 25 deletions(-) diff --git a/internal/sdkv2provider/resource_cloudflare_record_test.go b/internal/sdkv2provider/resource_cloudflare_record_test.go index 44c5829a98..d977be4069 100644 --- a/internal/sdkv2provider/resource_cloudflare_record_test.go +++ b/internal/sdkv2provider/resource_cloudflare_record_test.go @@ -79,7 +79,7 @@ func TestAccCloudflareRecord_Basic(t *testing.T) { testAccCheckCloudflareRecordDates(resourceName, &record, testStartTime), resource.TestCheckResourceAttr(resourceName, "name", "tf-acctest-basic"), resource.TestCheckResourceAttr(resourceName, consts.ZoneIDSchemaKey, zoneID), - resource.TestCheckResourceAttr(resourceName, "value", "192.168.0.10"), + resource.TestCheckResourceAttr(resourceName, "content", "192.168.0.10"), resource.TestCheckResourceAttr(resourceName, "hostname", fmt.Sprintf("tf-acctest-basic.%s", zoneName)), resource.TestMatchResourceAttr(resourceName, consts.ZoneIDSchemaKey, regexp.MustCompile("^[a-z0-9]{32}$")), resource.TestCheckResourceAttr(resourceName, "ttl", "3600"), @@ -147,7 +147,7 @@ func TestAccCloudflareRecord_Apex(t *testing.T) { testAccCheckCloudflareRecordAttributes(&record), resource.TestCheckResourceAttr(resourceName, "name", "@"), resource.TestCheckResourceAttr(resourceName, consts.ZoneIDSchemaKey, zoneID), - resource.TestCheckResourceAttr(resourceName, "value", "192.168.0.10"), + resource.TestCheckResourceAttr(resourceName, "content", "192.168.0.10"), ), }, }, @@ -170,7 +170,7 @@ func TestAccCloudflareRecord_LOC(t *testing.T) { Config: testAccCheckCloudflareRecordConfigLOC(zoneID, "tf-acctest-loc", rnd), Check: resource.ComposeTestCheckFunc( testAccCheckCloudflareRecordExists(resourceName, &record), - resource.TestCheckResourceAttr(resourceName, "value", "37 46 46.000 N 122 23 35.000 W 0.00 100.00 0.00 0.00"), + resource.TestCheckResourceAttr(resourceName, "content", "37 46 46.000 N 122 23 35.000 W 0.00 100.00 0.00 0.00"), resource.TestCheckResourceAttr(resourceName, "proxiable", "false"), resource.TestCheckResourceAttr(resourceName, "data.0.lat_degrees", "37"), resource.TestCheckResourceAttr(resourceName, "data.0.lat_degrees", "37"), @@ -210,15 +210,12 @@ func TestAccCloudflareRecord_SRV(t *testing.T) { testAccCheckCloudflareRecordExists(resourceName, &record), resource.TestCheckResourceAttr(resourceName, "name", fmt.Sprintf("_xmpp-client._tcp.%s", rnd)), resource.TestCheckResourceAttr(resourceName, "hostname", fmt.Sprintf("_xmpp-client._tcp.%s.%s", rnd, domain)), - resource.TestCheckResourceAttr(resourceName, "value", "0 5222 talk.l.google.com"), + resource.TestCheckResourceAttr(resourceName, "content", "0 5222 talk.l.google.com"), resource.TestCheckResourceAttr(resourceName, "proxiable", "false"), resource.TestCheckResourceAttr(resourceName, "data.0.priority", "5"), resource.TestCheckResourceAttr(resourceName, "data.0.weight", "0"), resource.TestCheckResourceAttr(resourceName, "data.0.port", "5222"), resource.TestCheckResourceAttr(resourceName, "data.0.target", "talk.l.google.com"), - resource.TestCheckResourceAttr(resourceName, "data.0.service", "_xmpp-client"), - resource.TestCheckResourceAttr(resourceName, "data.0.proto", "_tcp"), - resource.TestCheckResourceAttr(resourceName, "data.0.name", rnd+"."+domain), ), }, }, @@ -280,7 +277,7 @@ func TestAccCloudflareRecord_Proxied(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "proxiable", "true"), resource.TestCheckResourceAttr(resourceName, "proxied", "true"), resource.TestCheckResourceAttr(resourceName, "type", "CNAME"), - resource.TestCheckResourceAttr(resourceName, "value", domain), + resource.TestCheckResourceAttr(resourceName, "content", domain), ), }, }, @@ -483,7 +480,7 @@ func TestAccCloudflareRecord_MXWithPriorityZero(t *testing.T) { Config: testAccCheckCloudflareRecordConfigMXWithPriorityZero(zoneID, rnd, zoneName), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr(resourceName, "priority", "0"), - resource.TestCheckResourceAttr(resourceName, "value", "mail.terraform.cfapi.net"), + resource.TestCheckResourceAttr(resourceName, "content", "mail.terraform.cfapi.net"), ), }, }, @@ -578,7 +575,7 @@ func TestAccCloudflareRecord_MXNull(t *testing.T) { Config: testAccCheckCloudflareRecordNullMX(zoneID, rnd), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr(name, "name", rnd), - resource.TestCheckResourceAttr(name, "value", "."), + resource.TestCheckResourceAttr(name, "content", "."), resource.TestCheckResourceAttr(name, "priority", "0"), ), }, @@ -787,7 +784,7 @@ func testAccCheckCloudflareRecordConfigBasic(zoneID, name, rnd string) string { resource "cloudflare_record" "%[3]s" { zone_id = "%[1]s" name = "%[2]s" - value = "192.168.0.10" + content = "192.168.0.10" type = "A" ttl = 3600 tags = ["tag1", "tag2"] @@ -800,7 +797,7 @@ func testAccCheckCloudflareRecordConfigApex(zoneID, rnd string) string { resource "cloudflare_record" "%[2]s" { zone_id = "%[1]s" name = "@" - value = "192.168.0.10" + content = "192.168.0.10" type = "A" ttl = 3600 }`, zoneID, rnd) @@ -840,9 +837,6 @@ resource "cloudflare_record" "%[2]s" { weight = 0 port = 5222 target = "talk.l.google.com" - service = "_xmpp-client" - proto = "_tcp" - name = "%[2]s.%[3]s" } type = "SRV" ttl = 3600 @@ -869,7 +863,7 @@ func testAccCheckCloudflareRecordConfigProxied(zoneID, domain, name, rnd string) resource "cloudflare_record" "%[4]s" { zone_id = "%[1]s" name = "%[3]s" - value = "%[2]s" + content = "%[2]s" type = "CNAME" proxied = true }`, zoneID, domain, name, rnd) @@ -880,7 +874,7 @@ func testAccCheckCloudflareRecordConfigNewValue(zoneID, name, rnd string) string resource "cloudflare_record" "%[3]s" { zone_id = "%[1]s" name = "%[2]s" - value = "192.168.0.11" + content = "192.168.0.11" type = "A" ttl = 3600 tags = ["updated_tag1", "updated_tag2"] @@ -893,7 +887,7 @@ func testAccCheckCloudflareRecordConfigChangeType(zoneID, name, zoneName, rnd st resource "cloudflare_record" "%[4]s" { zone_id = "%[1]s" name = "%[2]s" - value = "%[3]s" + content = "%[3]s" type = "CNAME" ttl = 3600 }`, zoneID, name, zoneName, rnd) @@ -904,7 +898,7 @@ func testAccCheckCloudflareRecordConfigChangeHostname(zoneID, name, rnd string) resource "cloudflare_record" "%[3]s" { zone_id = "%[1]s" name = "%[2]s-changed" - value = "192.168.0.10" + content = "192.168.0.10" type = "A" ttl = 3600 }`, zoneID, name, rnd) @@ -915,7 +909,7 @@ func testAccCheckCloudflareRecordConfigTtlValidation(zoneID, name, zoneName, rnd resource "cloudflare_record" "%[4]s" { zone_id = "%[1]s" name = "%[2]s" - value = "%[3]s" + content = "%[3]s" type = "CNAME" proxied = true ttl = 3600 @@ -927,7 +921,7 @@ func testAccCheckCloudflareRecordConfigExplicitProxied(zoneID, name, zoneName, p resource "cloudflare_record" "%[2]s" { zone_id = "%[1]s" name = "%[2]s" - value = "%[3]s" + content = "%[3]s" type = "CNAME" proxied = %[4]s ttl = %[5]s @@ -939,7 +933,7 @@ func testAccCheckCloudflareRecordConfigMXWithPriorityZero(zoneID, name, zoneName resource "cloudflare_record" "%[2]s" { zone_id = "%[1]s" name = "%[2]s" - value = "mail.terraform.cfapi.net" + content = "mail.terraform.cfapi.net" type = "MX" priority = 0 proxied = false @@ -983,7 +977,7 @@ func testAccCheckCloudflareRecordNullMX(zoneID, rnd string) string { zone_id = "%[2]s" type = "MX" name = "%[1]s" - value = "." + content = "." priority = 0 } `, rnd, zoneID) @@ -994,7 +988,7 @@ func testAccCheckCloudflareRecordConfigMultipleTags(zoneID, name, rnd string) st resource "cloudflare_record" "%[3]s" { zone_id = "%[1]s" name = "%[2]s" - value = "192.168.0.10" + content = "192.168.0.10" type = "A" ttl = 3600 tags = ["tag1", "tag2"] @@ -1007,7 +1001,7 @@ func testAccCheckCloudflareRecordConfigNoTags(zoneID, name, rnd string) string { resource "cloudflare_record" "%[3]s" { zone_id = "%[1]s" name = "%[2]s" - value = "192.168.0.10" + content = "192.168.0.10" type = "A" ttl = 3600 }`, zoneID, name, rnd) From f8294f437696b35cab6224aef42872ba6043f68f Mon Sep 17 00:00:00 2001 From: Jacob Bednarz Date: Fri, 26 Jul 2024 13:16:53 +1000 Subject: [PATCH 13/27] resource/cloudflare_record: deprecate `value` for `content` Under the covers, we've always used `content`. To allow a smoother transition to v5 where the attributes are based on the schemas, we're deprecating `value` now in order to give folks plenty of migration time. Signed-off-by: Jacob Bednarz --- docs/data-sources/zone.md | 2 +- docs/resources/record.md | 5 ++-- docs/resources/regional_hostname.md | 2 +- .../cloudflare_zone/data-source.tf | 2 +- .../resources/cloudflare_record/resource.tf | 2 +- .../cloudflare_regional_hostname/resource.tf | 2 +- .../resource_cloudflare_record.go | 23 ++++++++++++++++--- .../sdkv2provider/schema_cloudflare_record.go | 10 ++++++++ 8 files changed, 38 insertions(+), 10 deletions(-) diff --git a/docs/data-sources/zone.md b/docs/data-sources/zone.md index 8f914aa3f8..65d7e04e0e 100644 --- a/docs/data-sources/zone.md +++ b/docs/data-sources/zone.md @@ -26,7 +26,7 @@ data "cloudflare_zone" "example" { resource "cloudflare_record" "example" { zone_id = data.cloudflare_zone.example.id name = "www" - value = "203.0.113.1" + content = "203.0.113.1" type = "A" proxied = true } diff --git a/docs/resources/record.md b/docs/resources/record.md index 5880f2b1ad..efd665078a 100644 --- a/docs/resources/record.md +++ b/docs/resources/record.md @@ -16,7 +16,7 @@ Provides a Cloudflare record resource. resource "cloudflare_record" "example" { zone_id = var.cloudflare_zone_id name = "terraform" - value = "192.0.2.1" + content = "192.0.2.1" type = "A" ttl = 3600 } @@ -51,13 +51,14 @@ resource "cloudflare_record" "_sip_tls" { - `allow_overwrite` (Boolean) Allow creation of this record in Terraform to overwrite an existing record, if any. This does not affect the ability to update the record in Terraform and does not prevent other resources within Terraform or manual changes outside Terraform from overwriting this record. **This configuration is not recommended for most environments**. Defaults to `false`. - `comment` (String) Comments or notes about the DNS record. This field has no effect on DNS responses. +- `content` (String) The content of the record. Conflicts with `data`. - `data` (Block List, Max: 1) Map of attributes that constitute the record value. Conflicts with `value`. (see [below for nested schema](#nestedblock--data)) - `priority` (Number) The priority of the record. - `proxied` (Boolean) Whether the record gets Cloudflare's origin protection. - `tags` (Set of String) Custom tags for the DNS record. - `timeouts` (Block, Optional) (see [below for nested schema](#nestedblock--timeouts)) - `ttl` (Number) The TTL of the record. -- `value` (String) The value of the record. Conflicts with `data`. +- `value` (String, Deprecated) The value of the record. Conflicts with `data`. ### Read-Only diff --git a/docs/resources/regional_hostname.md b/docs/resources/regional_hostname.md index 96fcd9d8d4..9ccf0a03d9 100644 --- a/docs/resources/regional_hostname.md +++ b/docs/resources/regional_hostname.md @@ -17,7 +17,7 @@ Provides a Data Localization Suite Regional Hostname. resource "cloudflare_record" "example" { zone_id = "0da42c8d2132a9ddaf714f9e7c920711" name = "example.com" - value = "192.0.2.1" + content = "192.0.2.1" type = "A" ttl = 3600 } diff --git a/examples/data-sources/cloudflare_zone/data-source.tf b/examples/data-sources/cloudflare_zone/data-source.tf index 531263d33f..cf0b08c748 100644 --- a/examples/data-sources/cloudflare_zone/data-source.tf +++ b/examples/data-sources/cloudflare_zone/data-source.tf @@ -5,7 +5,7 @@ data "cloudflare_zone" "example" { resource "cloudflare_record" "example" { zone_id = data.cloudflare_zone.example.id name = "www" - value = "203.0.113.1" + content = "203.0.113.1" type = "A" proxied = true } diff --git a/examples/resources/cloudflare_record/resource.tf b/examples/resources/cloudflare_record/resource.tf index 9b2d26f0b4..4fc2df6736 100644 --- a/examples/resources/cloudflare_record/resource.tf +++ b/examples/resources/cloudflare_record/resource.tf @@ -2,7 +2,7 @@ resource "cloudflare_record" "example" { zone_id = var.cloudflare_zone_id name = "terraform" - value = "192.0.2.1" + content = "192.0.2.1" type = "A" ttl = 3600 } diff --git a/examples/resources/cloudflare_regional_hostname/resource.tf b/examples/resources/cloudflare_regional_hostname/resource.tf index ed537f7071..a9db3855b8 100644 --- a/examples/resources/cloudflare_regional_hostname/resource.tf +++ b/examples/resources/cloudflare_regional_hostname/resource.tf @@ -3,7 +3,7 @@ resource "cloudflare_record" "example" { zone_id = "0da42c8d2132a9ddaf714f9e7c920711" name = "example.com" - value = "192.0.2.1" + content = "192.0.2.1" type = "A" ttl = 3600 } diff --git a/internal/sdkv2provider/resource_cloudflare_record.go b/internal/sdkv2provider/resource_cloudflare_record.go index 56ffb63829..7a96b25cf8 100644 --- a/internal/sdkv2provider/resource_cloudflare_record.go +++ b/internal/sdkv2provider/resource_cloudflare_record.go @@ -61,6 +61,11 @@ func resourceCloudflareRecordCreate(ctx context.Context, d *schema.ResourceData, newRecord.Content = value.(string) } + content, contentOk := d.GetOk("content") + if contentOk { + newRecord.Content = content.(string) + } + data, dataOk := d.GetOk("data") tflog.Debug(ctx, fmt.Sprintf("Data found in config: %#v", data)) @@ -81,9 +86,9 @@ func resourceCloudflareRecordCreate(ctx context.Context, d *schema.ResourceData, newRecord.Data = newDataMap } - if valueOk == dataOk { + if contentOk == dataOk { return diag.FromErr(fmt.Errorf( - "either 'value' (present: %t) or 'data' (present: %t) must be provided", + "either 'content' (present: %t) or 'data' (present: %t) must be provided", valueOk, dataOk)) } @@ -235,6 +240,7 @@ func resourceCloudflareRecordRead(ctx context.Context, d *schema.ResourceData, m d.Set("hostname", record.Name) d.Set("type", record.Type) d.Set("value", record.Content) + d.Set("content", record.Content) d.Set("ttl", record.TTL) d.Set("proxied", record.Proxied) d.Set("created_on", record.CreatedOn.Format(time.RFC3339Nano)) @@ -259,12 +265,23 @@ func resourceCloudflareRecordRead(ctx context.Context, d *schema.ResourceData, m func resourceCloudflareRecordUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { client := meta.(*cloudflare.API) zoneID := d.Get(consts.ZoneIDSchemaKey).(string) + var contentValue string + + value, valueOk := d.GetOk("value") + if valueOk { + contentValue = value.(string) + } + + content, contentOk := d.GetOk("content") + if contentOk { + contentValue = content.(string) + } updateRecord := cloudflare.UpdateDNSRecordParams{ ID: d.Id(), Type: d.Get("type").(string), Name: d.Get("name").(string), - Content: d.Get("value").(string), + Content: contentValue, } data, dataOk := d.GetOk("data") diff --git a/internal/sdkv2provider/schema_cloudflare_record.go b/internal/sdkv2provider/schema_cloudflare_record.go index 90dd07259f..0ab4480b74 100644 --- a/internal/sdkv2provider/schema_cloudflare_record.go +++ b/internal/sdkv2provider/schema_cloudflare_record.go @@ -50,6 +50,16 @@ func resourceCloudflareRecordSchema() map[string]*schema.Schema { ConflictsWith: []string{"data"}, DiffSuppressFunc: suppressTrailingDots, Description: "The value of the record.", + Deprecated: "`value` is deprecated in favour of `content` and will be removed in the next major release.", + }, + + "content": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ConflictsWith: []string{"data"}, + DiffSuppressFunc: suppressTrailingDots, + Description: "The content of the record.", }, "data": { From 1b47f923bfa8fe3b78137ddb734d6b8945ed9688 Mon Sep 17 00:00:00 2001 From: Jacob Bednarz Date: Fri, 26 Jul 2024 13:17:12 +1000 Subject: [PATCH 14/27] resource/cloudflare_record: add state migrator Signed-off-by: Jacob Bednarz --- .changelog/3509.txt | 3 + .../resource_cloudflare_record.go | 7 +- .../resource_cloudflare_record_migrate.go | 316 ++++++++++++++++++ 3 files changed, 325 insertions(+), 1 deletion(-) create mode 100644 .changelog/3509.txt diff --git a/.changelog/3509.txt b/.changelog/3509.txt new file mode 100644 index 0000000000..51bbe437b9 --- /dev/null +++ b/.changelog/3509.txt @@ -0,0 +1,3 @@ +```release-note:note +resource/cloudflare_record: `value` is now deprecated in favour of `content` +``` diff --git a/internal/sdkv2provider/resource_cloudflare_record.go b/internal/sdkv2provider/resource_cloudflare_record.go index 7a96b25cf8..5e886c8eb8 100644 --- a/internal/sdkv2provider/resource_cloudflare_record.go +++ b/internal/sdkv2provider/resource_cloudflare_record.go @@ -26,7 +26,7 @@ func resourceCloudflareRecord() *schema.Resource { StateContext: resourceCloudflareRecordImport, }, Description: heredoc.Doc(`Provides a Cloudflare record resource.`), - SchemaVersion: 2, + SchemaVersion: 3, Schema: resourceCloudflareRecordSchema(), Timeouts: &schema.ResourceTimeout{ Create: schema.DefaultTimeout(30 * time.Second), @@ -38,6 +38,11 @@ func resourceCloudflareRecord() *schema.Resource { Upgrade: resourceCloudflareRecordStateUpgradeV2, Version: 1, }, + { + Type: resourceCloudflareRecordV2().CoreConfigSchema().ImpliedType(), + Upgrade: resourceCloudflareRecordStateUpgradeV3, + Version: 2, + }, }, } } diff --git a/internal/sdkv2provider/resource_cloudflare_record_migrate.go b/internal/sdkv2provider/resource_cloudflare_record_migrate.go index 3be4ed03f5..7e7a27cdad 100644 --- a/internal/sdkv2provider/resource_cloudflare_record_migrate.go +++ b/internal/sdkv2provider/resource_cloudflare_record_migrate.go @@ -2,8 +2,12 @@ package sdkv2provider import ( "context" + "fmt" + "strings" + "github.com/cloudflare/terraform-provider-cloudflare/internal/consts" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) func resourceCloudflareRecordV1() *schema.Resource { @@ -182,3 +186,315 @@ func resourceCloudflareRecordStateUpgradeV2(_ context.Context, rawState map[stri rawState["data"] = []interface{}{rawState["data"]} return rawState, nil } + +func resourceCloudflareRecordV2() *schema.Resource { + return &schema.Resource{ + Schema: map[string]*schema.Schema{ + consts.ZoneIDSchemaKey: { + Description: consts.ZoneIDSchemaDescription, + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + StateFunc: func(i interface{}) string { + return strings.ToLower(i.(string)) + }, + + Description: "The name of the record.", + }, + + "hostname": { + Type: schema.TypeString, + Computed: true, + Description: "The FQDN of the record.", + }, + + "type": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{"A", "AAAA", "CAA", "CNAME", "TXT", "SRV", "LOC", "MX", "NS", "SPF", "CERT", "DNSKEY", "DS", "NAPTR", "SMIMEA", "SSHFP", "TLSA", "URI", "PTR", "HTTPS", "SVCB"}, false), + Description: fmt.Sprintf("The type of the record. %s", renderAvailableDocumentationValuesStringSlice([]string{"A", "AAAA", "CAA", "CNAME", "TXT", "SRV", "LOC", "MX", "NS", "SPF", "CERT", "DNSKEY", "DS", "NAPTR", "SMIMEA", "SSHFP", "TLSA", "URI", "PTR", "HTTPS", "SVCB"})), + }, + + "value": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ConflictsWith: []string{"data"}, + DiffSuppressFunc: suppressTrailingDots, + Description: "The value of the record.", + Deprecated: "`value` is deprecated in favour of `content` and will be removed in the next major release.", + }, + + "content": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ConflictsWith: []string{"data"}, + DiffSuppressFunc: suppressTrailingDots, + Description: "The content of the record.", + }, + + "data": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + ConflictsWith: []string{"value"}, + Description: "Map of attributes that constitute the record value.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + // Properties present in several record types + "algorithm": { + Type: schema.TypeInt, + Optional: true, + }, + "key_tag": { + Type: schema.TypeInt, + Optional: true, + }, + "flags": { + Type: schema.TypeString, + Optional: true, + }, + "service": { + Type: schema.TypeString, + Optional: true, + }, + "certificate": { + Type: schema.TypeString, + Optional: true, + }, + "type": { + Type: schema.TypeInt, + Optional: true, + }, + "usage": { + Type: schema.TypeInt, + Optional: true, + }, + "selector": { + Type: schema.TypeInt, + Optional: true, + }, + "matching_type": { + Type: schema.TypeInt, + Optional: true, + }, + "weight": { + Type: schema.TypeInt, + Optional: true, + }, + + // SRV record properties + "proto": { + Type: schema.TypeString, + Optional: true, + }, + "name": { + Type: schema.TypeString, + Optional: true, + }, + "priority": { + Type: schema.TypeInt, + Optional: true, + }, + "port": { + Type: schema.TypeInt, + Optional: true, + }, + "target": { + Type: schema.TypeString, + Optional: true, + }, + + // LOC record properties + "size": { + Type: schema.TypeFloat, + Optional: true, + }, + "altitude": { + Type: schema.TypeFloat, + Optional: true, + }, + "long_degrees": { + Type: schema.TypeInt, + Optional: true, + }, + "lat_degrees": { + Type: schema.TypeInt, + Optional: true, + }, + "precision_horz": { + Type: schema.TypeFloat, + Optional: true, + }, + "precision_vert": { + Type: schema.TypeFloat, + Optional: true, + }, + "long_direction": { + Type: schema.TypeString, + Optional: true, + }, + "long_minutes": { + Type: schema.TypeInt, + Optional: true, + }, + "long_seconds": { + Type: schema.TypeFloat, + Optional: true, + }, + "lat_direction": { + Type: schema.TypeString, + Optional: true, + }, + "lat_minutes": { + Type: schema.TypeInt, + Optional: true, + }, + "lat_seconds": { + Type: schema.TypeFloat, + Optional: true, + }, + + // DNSKEY record properties + "protocol": { + Type: schema.TypeInt, + Optional: true, + }, + "public_key": { + Type: schema.TypeString, + Optional: true, + }, + + // DS record properties + "digest_type": { + Type: schema.TypeInt, + Optional: true, + }, + "digest": { + Type: schema.TypeString, + Optional: true, + }, + + // NAPTR record properties + "order": { + Type: schema.TypeInt, + Optional: true, + }, + "preference": { + Type: schema.TypeInt, + Optional: true, + }, + "regex": { + Type: schema.TypeString, + Optional: true, + }, + "replacement": { + Type: schema.TypeString, + Optional: true, + }, + + // SSHFP record properties + "fingerprint": { + Type: schema.TypeString, + Optional: true, + }, + + // URI record properties + "content": { + Type: schema.TypeString, + Optional: true, + }, + + // CAA record properties + "tag": { + Type: schema.TypeString, + Optional: true, + }, + + "value": { + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + + "ttl": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + Description: "The TTL of the record.", + }, + + "priority": { + Type: schema.TypeInt, + Optional: true, + DiffSuppressFunc: suppressPriority, + Description: "The priority of the record.", + }, + + "proxied": { + Optional: true, + Type: schema.TypeBool, + Description: "Whether the record gets Cloudflare's origin protection.", + }, + + "created_on": { + Type: schema.TypeString, + Computed: true, + Description: "The RFC3339 timestamp of when the record was created.", + }, + + "metadata": { + Type: schema.TypeMap, + Computed: true, + Description: "A key-value map of string metadata Cloudflare associates with the record.", + }, + + "modified_on": { + Type: schema.TypeString, + Computed: true, + Description: "The RFC3339 timestamp of when the record was last modified.", + }, + + "proxiable": { + Type: schema.TypeBool, + Computed: true, + Description: "Shows whether this record can be proxied.", + }, + + "allow_overwrite": { + Type: schema.TypeBool, + Optional: true, + Default: false, + Description: "Allow creation of this record in Terraform to overwrite an existing record, if any. This does not affect the ability to update the record in Terraform and does not prevent other resources within Terraform or manual changes outside Terraform from overwriting this record. **This configuration is not recommended for most environments**", + }, + + "comment": { + Type: schema.TypeString, + Optional: true, + Description: "Comments or notes about the DNS record. This field has no effect on DNS responses.", + }, + + "tags": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Description: "Custom tags for the DNS record.", + }, + }, + } +} + +func resourceCloudflareRecordStateUpgradeV3(_ context.Context, rawState map[string]interface{}, meta interface{}) (map[string]interface{}, error) { + rawState["value"] = rawState["content"] + return rawState, nil +} From 2f97a53cc6ab8ca6eeb54e5df792de6c78f7e6a7 Mon Sep 17 00:00:00 2001 From: Jacob Bednarz Date: Fri, 26 Jul 2024 13:26:06 +1000 Subject: [PATCH 15/27] `make docs` for updated workers resources Signed-off-by: Jacob Bednarz --- .changelog/3500.txt | 12 +- docs/resources/workers_cron_trigger.md | 55 +++++ docs/resources/workers_domain.md | 46 +++++ ...orkers_for_platforms_dispatch_namespace.md | 48 +++++ .../workers_for_platforms_namespace.md | 2 +- docs/resources/workers_route.md | 48 +++++ docs/resources/workers_script.md | 193 ++++++++++++++++++ docs/resources/workers_secret.md | 42 ++++ .../cloudflare_workers_cron_trigger/import.sh | 1 + .../resource.tf | 14 ++ .../cloudflare_workers_domain/import.sh | 1 + .../cloudflare_workers_domain/resource.tf | 6 + .../import.sh | 1 + .../resource.tf | 12 ++ .../resource.tf | 2 +- .../cloudflare_workers_route/import.sh | 1 + .../cloudflare_workers_route/resource.tf | 10 + .../cloudflare_workers_script/import.sh | 1 + .../cloudflare_workers_script/resource.tf | 47 +++++ .../cloudflare_workers_secret/import.sh | 1 + .../cloudflare_workers_secret/resource.tf | 6 + 21 files changed, 541 insertions(+), 8 deletions(-) create mode 100644 docs/resources/workers_cron_trigger.md create mode 100644 docs/resources/workers_domain.md create mode 100644 docs/resources/workers_for_platforms_dispatch_namespace.md create mode 100644 docs/resources/workers_route.md create mode 100644 docs/resources/workers_script.md create mode 100644 docs/resources/workers_secret.md create mode 100644 examples/resources/cloudflare_workers_cron_trigger/import.sh create mode 100644 examples/resources/cloudflare_workers_cron_trigger/resource.tf create mode 100644 examples/resources/cloudflare_workers_domain/import.sh create mode 100644 examples/resources/cloudflare_workers_domain/resource.tf create mode 100644 examples/resources/cloudflare_workers_for_platforms_dispatch_namespace/import.sh create mode 100644 examples/resources/cloudflare_workers_for_platforms_dispatch_namespace/resource.tf create mode 100644 examples/resources/cloudflare_workers_route/import.sh create mode 100644 examples/resources/cloudflare_workers_route/resource.tf create mode 100644 examples/resources/cloudflare_workers_script/import.sh create mode 100644 examples/resources/cloudflare_workers_script/resource.tf create mode 100644 examples/resources/cloudflare_workers_secret/import.sh create mode 100644 examples/resources/cloudflare_workers_secret/resource.tf diff --git a/.changelog/3500.txt b/.changelog/3500.txt index 0aa80444fd..c988267909 100644 --- a/.changelog/3500.txt +++ b/.changelog/3500.txt @@ -1,25 +1,25 @@ -```release-note:internal +```release-note:note resource/cloudflare_worker_cron_trigger: deprecated in favour of `cloudflare_workers_cron_trigger` and will be removed in the next major version. ``` -```release-note:internal +```release-note:note resource/cloudflare_worker_domain: deprecated in favour of `cloudflare_workers_domain` and will be removed in the next major version. ``` -```release-note:internal +```release-note:note resource/cloudflare_worker_route: deprecated in favour of `cloudflare_workers_route` and will be removed in the next major version. ``` -```release-note:internal +```release-note:note resource/cloudflare_worker_script: deprecated in favour of `cloudflare_workers_script` and will be removed in the next major version. ``` -```release-note:internal +```release-note:note resource/cloudflare_worker_secret: deprecated in favour of `cloudflare_workers_secret` and will be removed in the next major version. ``` -```release-note:internal +```release-note:note resource/cloudflare_workers_for_platforms_namespace: deprecated in favour of `cloudflare_workers_for_platforms_dispatch_namespace` and will be removed in the next major version. ``` diff --git a/docs/resources/workers_cron_trigger.md b/docs/resources/workers_cron_trigger.md new file mode 100644 index 0000000000..433dda0ad2 --- /dev/null +++ b/docs/resources/workers_cron_trigger.md @@ -0,0 +1,55 @@ +--- +page_title: "cloudflare_workers_cron_trigger Resource - Cloudflare" +subcategory: "" +description: |- + Worker Cron Triggers allow users to map a cron expression to a Worker script + using a ScheduledEvent listener that enables Workers to be executed on a + schedule. Worker Cron Triggers are ideal for running periodic jobs for + maintenance or calling third-party APIs to collect up-to-date data. +--- + +# cloudflare_workers_cron_trigger (Resource) + +Worker Cron Triggers allow users to map a cron expression to a Worker script +using a `ScheduledEvent` listener that enables Workers to be executed on a +schedule. Worker Cron Triggers are ideal for running periodic jobs for +maintenance or calling third-party APIs to collect up-to-date data. + +## Example Usage + +```terraform +resource "cloudflare_workers_script" "example_script" { + account_id = "f037e56e89293a057740de681ac9abbe" + name = "example-script" + content = file("path/to/my.js") +} + +resource "cloudflare_workers_cron_trigger" "example_trigger" { + account_id = "f037e56e89293a057740de681ac9abbe" + script_name = cloudflare_worker_script.example_script.name + schedules = [ + "*/5 * * * *", # every 5 minutes + "10 7 * * mon-fri", # 7:10am every weekday + ] +} +``` + +## Schema + +### Required + +- `account_id` (String) The account identifier to target for the resource. +- `schedules` (Set of String) Cron expressions to execute the Worker script. +- `script_name` (String) Worker script to target for the schedules. + +### Read-Only + +- `id` (String) The ID of this resource. + +## Import + +Import is supported using the following syntax: + +```shell +$ terraform import cloudflare_workers_cron_trigger.example / +``` diff --git a/docs/resources/workers_domain.md b/docs/resources/workers_domain.md new file mode 100644 index 0000000000..e6794540c2 --- /dev/null +++ b/docs/resources/workers_domain.md @@ -0,0 +1,46 @@ +--- +page_title: "cloudflare_workers_domain Resource - Cloudflare" +subcategory: "" +description: |- + Creates a Worker Custom Domain. +--- + +# cloudflare_workers_domain (Resource) + +Creates a Worker Custom Domain. + +## Example Usage + +```terraform +resource "cloudflare_workers_domain" "example" { + account_id = "f037e56e89293a057740de681ac9abbe" + hostname = "subdomain.example.com" + service = "my-service" + zone_id = "0da42c8d2132a9ddaf714f9e7c920711" +} +``` + +## Schema + +### Required + +- `account_id` (String) The account identifier to target for the resource. **Modifying this attribute will force creation of a new resource.** +- `hostname` (String) Hostname of the Worker Domain. +- `service` (String) Name of worker script to attach the domain to. +- `zone_id` (String) The zone identifier to target for the resource. **Modifying this attribute will force creation of a new resource.** + +### Optional + +- `environment` (String) The name of the Worker environment. Defaults to `production`. + +### Read-Only + +- `id` (String) The ID of this resource. + +## Import + +Import is supported using the following syntax: + +```shell +$ terraform import cloudflare_workers_domain.example / +``` diff --git a/docs/resources/workers_for_platforms_dispatch_namespace.md b/docs/resources/workers_for_platforms_dispatch_namespace.md new file mode 100644 index 0000000000..1a854fbf1b --- /dev/null +++ b/docs/resources/workers_for_platforms_dispatch_namespace.md @@ -0,0 +1,48 @@ +--- +page_title: "cloudflare_workers_for_platforms_dispatch_namespace Resource - Cloudflare" +subcategory: "" +description: |- + The Workers for Platforms https://developers.cloudflare.com/cloudflare-for-platforms/workers-for-platforms/ resource allows you + to manage Cloudflare Workers for Platforms dispatch namespaces. +--- + +# cloudflare_workers_for_platforms_dispatch_namespace (Resource) + +The [Workers for Platforms](https://developers.cloudflare.com/cloudflare-for-platforms/workers-for-platforms/) resource allows you +to manage Cloudflare Workers for Platforms dispatch namespaces. + +## Example Usage + +```terraform +resource "cloudflare_workers_for_platforms_dispatch_namespace" "example" { + account_id = "f037e56e89293a057740de681ac9abbe" + name = "example-namespace" +} + +resource "cloudflare_workers_script" "customer_worker_1" { + account_id = "f037e56e89293a057740de681ac9abbe" + name = "customer-worker-1" + content = file("script.js") + dispatch_namespace = cloudflare_workers_for_platforms_namespace.example.name + tags = ["free"] +} +``` + +## Schema + +### Required + +- `account_id` (String) The account identifier to target for the resource. +- `name` (String) The name of the Workers for Platforms namespace. + +### Read-Only + +- `id` (String) The identifier of this resource. + +## Import + +Import is supported using the following syntax: + +```shell +$ terraform import cloudflare_workers_for_platforms_dispatch_namespace.example / +``` diff --git a/docs/resources/workers_for_platforms_namespace.md b/docs/resources/workers_for_platforms_namespace.md index f94e61ad59..61d76a5788 100644 --- a/docs/resources/workers_for_platforms_namespace.md +++ b/docs/resources/workers_for_platforms_namespace.md @@ -19,7 +19,7 @@ resource "cloudflare_workers_for_platforms_namespace" "example" { name = "example-namespace" } -resource "cloudflare_worker_script" "customer_worker_1" { +resource "cloudflare_workers_script" "customer_worker_1" { account_id = "f037e56e89293a057740de681ac9abbe" name = "customer-worker-1" content = file("script.js") diff --git a/docs/resources/workers_route.md b/docs/resources/workers_route.md new file mode 100644 index 0000000000..dc307c2528 --- /dev/null +++ b/docs/resources/workers_route.md @@ -0,0 +1,48 @@ +--- +page_title: "cloudflare_workers_route Resource - Cloudflare" +subcategory: "" +description: |- + Provides a Cloudflare worker route resource. A route will also require a cloudflare_worker_script. +--- + +# cloudflare_workers_route (Resource) + +Provides a Cloudflare worker route resource. A route will also require a `cloudflare_worker_script`. + +## Example Usage + +```terraform +# Runs the specified worker script for all URLs that match `example.com/*` +resource "cloudflare_workers_route" "my_route" { + zone_id = "0da42c8d2132a9ddaf714f9e7c920711" + pattern = "example.com/*" + script_name = cloudflare_worker_script.my_script.name +} + +resource "cloudflare_workers_script" "my_script" { + # see "cloudflare_workers_script" documentation ... +} +``` + +## Schema + +### Required + +- `pattern` (String) The [route pattern](https://developers.cloudflare.com/workers/about/routes/) to associate the Worker with. +- `zone_id` (String) The zone identifier to target for the resource. **Modifying this attribute will force creation of a new resource.** + +### Optional + +- `script_name` (String) Worker script name to invoke for requests that match the route pattern. + +### Read-Only + +- `id` (String) The ID of this resource. + +## Import + +Import is supported using the following syntax: + +```shell +$ terraform import cloudflare_workers_route.example / +``` diff --git a/docs/resources/workers_script.md b/docs/resources/workers_script.md new file mode 100644 index 0000000000..236c7887f4 --- /dev/null +++ b/docs/resources/workers_script.md @@ -0,0 +1,193 @@ +--- +page_title: "cloudflare_workers_script Resource - Cloudflare" +subcategory: "" +description: |- + Provides a Cloudflare worker script resource. In order for a script to be active, you'll also need to setup a cloudflare_worker_route. +--- + +# cloudflare_workers_script (Resource) + +Provides a Cloudflare worker script resource. In order for a script to be active, you'll also need to setup a `cloudflare_worker_route`. + +## Example Usage + +```terraform +resource "cloudflare_workers_kv_namespace" "my_namespace" { + account_id = "f037e56e89293a057740de681ac9abbe" + title = "example" +} + +# Sets the script with the name "script_1" +resource "cloudflare_workers_script" "my_script" { + account_id = "f037e56e89293a057740de681ac9abbe" + name = "script_1" + content = file("script.js") + + kv_namespace_binding { + name = "MY_EXAMPLE_KV_NAMESPACE" + namespace_id = cloudflare_workers_kv_namespace.my_namespace.id + } + + plain_text_binding { + name = "MY_EXAMPLE_PLAIN_TEXT" + text = "foobar" + } + + secret_text_binding { + name = "MY_EXAMPLE_SECRET_TEXT" + text = var.secret_foo_value + } + + webassembly_binding { + name = "MY_EXAMPLE_WASM" + module = filebase64("example.wasm") + } + + service_binding { + name = "MY_SERVICE_BINDING" + service = "MY_SERVICE" + environment = "production" + } + + r2_bucket_binding { + name = "MY_BUCKET" + bucket_name = "MY_BUCKET_NAME" + } + + analytics_engine_binding { + name = "MY_DATASET" + dataset = "dataset1" + } +} +``` + +## Schema + +### Required + +- `account_id` (String) The account identifier to target for the resource. +- `content` (String) The script content. +- `name` (String) The name for the script. **Modifying this attribute will force creation of a new resource.** + +### Optional + +- `analytics_engine_binding` (Block Set) (see [below for nested schema](#nestedblock--analytics_engine_binding)) +- `compatibility_date` (String) The date to use for the compatibility flag. +- `compatibility_flags` (Set of String) Compatibility flags used for Worker Scripts. +- `d1_database_binding` (Block Set) (see [below for nested schema](#nestedblock--d1_database_binding)) +- `dispatch_namespace` (String) Name of the Workers for Platforms dispatch namespace. +- `kv_namespace_binding` (Block Set) (see [below for nested schema](#nestedblock--kv_namespace_binding)) +- `logpush` (Boolean) Enabling allows Worker events to be sent to a defined Logpush destination. +- `module` (Boolean) Whether to upload Worker as a module. +- `placement` (Block Set) (see [below for nested schema](#nestedblock--placement)) +- `plain_text_binding` (Block Set) (see [below for nested schema](#nestedblock--plain_text_binding)) +- `queue_binding` (Block Set) (see [below for nested schema](#nestedblock--queue_binding)) +- `r2_bucket_binding` (Block Set) (see [below for nested schema](#nestedblock--r2_bucket_binding)) +- `secret_text_binding` (Block Set) (see [below for nested schema](#nestedblock--secret_text_binding)) +- `service_binding` (Block Set) (see [below for nested schema](#nestedblock--service_binding)) +- `tags` (Set of String) +- `webassembly_binding` (Block Set) (see [below for nested schema](#nestedblock--webassembly_binding)) + +### Read-Only + +- `id` (String) The ID of this resource. + + +### Nested Schema for `analytics_engine_binding` + +Required: + +- `dataset` (String) The name of the Analytics Engine dataset to write to. +- `name` (String) The global variable for the binding in your Worker code. + + + +### Nested Schema for `d1_database_binding` + +Required: + +- `database_id` (String) Database ID of D1 database to use. +- `name` (String) The global variable for the binding in your Worker code. + + + +### Nested Schema for `kv_namespace_binding` + +Required: + +- `name` (String) The global variable for the binding in your Worker code. +- `namespace_id` (String) ID of the KV namespace you want to use. + + + +### Nested Schema for `placement` + +Required: + +- `mode` (String) The placement mode for the Worker. Available values: `smart`. + + + +### Nested Schema for `plain_text_binding` + +Required: + +- `name` (String) The global variable for the binding in your Worker code. +- `text` (String) The plain text you want to store. + + + +### Nested Schema for `queue_binding` + +Required: + +- `binding` (String) The name of the global variable for the binding in your Worker code. +- `queue` (String) Name of the queue you want to use. + + + +### Nested Schema for `r2_bucket_binding` + +Required: + +- `bucket_name` (String) The name of the Bucket to bind to. +- `name` (String) The global variable for the binding in your Worker code. + + + +### Nested Schema for `secret_text_binding` + +Required: + +- `name` (String) The global variable for the binding in your Worker code. +- `text` (String, Sensitive) The secret text you want to store. + + + +### Nested Schema for `service_binding` + +Required: + +- `name` (String) The global variable for the binding in your Worker code. +- `service` (String) The name of the Worker to bind to. + +Optional: + +- `environment` (String) The name of the Worker environment to bind to. + + + +### Nested Schema for `webassembly_binding` + +Required: + +- `module` (String) The base64 encoded wasm module you want to store. +- `name` (String) The global variable for the binding in your Worker code. + +## Import + +Import is supported using the following syntax: + +```shell +$ terraform import cloudflare_workers_script.example / +``` diff --git a/docs/resources/workers_secret.md b/docs/resources/workers_secret.md new file mode 100644 index 0000000000..a03fca73df --- /dev/null +++ b/docs/resources/workers_secret.md @@ -0,0 +1,42 @@ +--- +page_title: "cloudflare_workers_secret Resource - Cloudflare" +subcategory: "" +description: |- + Provides a Cloudflare Worker secret resource. +--- + +# cloudflare_workers_secret (Resource) + +Provides a Cloudflare Worker secret resource. + +## Example Usage + +```terraform +resource "cloudflare_workers_secret" "my_secret" { + account_id = "f037e56e89293a057740de681ac9abbe" + name = "MY_EXAMPLE_SECRET_TEXT" + script_name = "script_1" + secret_text = "my_secret_value" +} +``` + +## Schema + +### Required + +- `account_id` (String) The account identifier to target for the resource. +- `name` (String) The name of the Worker secret. **Modifying this attribute will force creation of a new resource.** +- `script_name` (String) The name of the Worker script to associate the secret with. **Modifying this attribute will force creation of a new resource.** +- `secret_text` (String, Sensitive) The text of the Worker secret. **Modifying this attribute will force creation of a new resource.** + +### Read-Only + +- `id` (String) The ID of this resource. + +## Import + +Import is supported using the following syntax: + +```shell +$ terraform import cloudflare_workers_secret.example // +``` diff --git a/examples/resources/cloudflare_workers_cron_trigger/import.sh b/examples/resources/cloudflare_workers_cron_trigger/import.sh new file mode 100644 index 0000000000..013aa239a8 --- /dev/null +++ b/examples/resources/cloudflare_workers_cron_trigger/import.sh @@ -0,0 +1 @@ +$ terraform import cloudflare_workers_cron_trigger.example / diff --git a/examples/resources/cloudflare_workers_cron_trigger/resource.tf b/examples/resources/cloudflare_workers_cron_trigger/resource.tf new file mode 100644 index 0000000000..1bc261f650 --- /dev/null +++ b/examples/resources/cloudflare_workers_cron_trigger/resource.tf @@ -0,0 +1,14 @@ +resource "cloudflare_workers_script" "example_script" { + account_id = "f037e56e89293a057740de681ac9abbe" + name = "example-script" + content = file("path/to/my.js") +} + +resource "cloudflare_workers_cron_trigger" "example_trigger" { + account_id = "f037e56e89293a057740de681ac9abbe" + script_name = cloudflare_worker_script.example_script.name + schedules = [ + "*/5 * * * *", # every 5 minutes + "10 7 * * mon-fri", # 7:10am every weekday + ] +} diff --git a/examples/resources/cloudflare_workers_domain/import.sh b/examples/resources/cloudflare_workers_domain/import.sh new file mode 100644 index 0000000000..504526590b --- /dev/null +++ b/examples/resources/cloudflare_workers_domain/import.sh @@ -0,0 +1 @@ +$ terraform import cloudflare_workers_domain.example / diff --git a/examples/resources/cloudflare_workers_domain/resource.tf b/examples/resources/cloudflare_workers_domain/resource.tf new file mode 100644 index 0000000000..ff1a939684 --- /dev/null +++ b/examples/resources/cloudflare_workers_domain/resource.tf @@ -0,0 +1,6 @@ +resource "cloudflare_workers_domain" "example" { + account_id = "f037e56e89293a057740de681ac9abbe" + hostname = "subdomain.example.com" + service = "my-service" + zone_id = "0da42c8d2132a9ddaf714f9e7c920711" +} diff --git a/examples/resources/cloudflare_workers_for_platforms_dispatch_namespace/import.sh b/examples/resources/cloudflare_workers_for_platforms_dispatch_namespace/import.sh new file mode 100644 index 0000000000..5d87208a3f --- /dev/null +++ b/examples/resources/cloudflare_workers_for_platforms_dispatch_namespace/import.sh @@ -0,0 +1 @@ +$ terraform import cloudflare_workers_for_platforms_dispatch_namespace.example / diff --git a/examples/resources/cloudflare_workers_for_platforms_dispatch_namespace/resource.tf b/examples/resources/cloudflare_workers_for_platforms_dispatch_namespace/resource.tf new file mode 100644 index 0000000000..1fd5e920f0 --- /dev/null +++ b/examples/resources/cloudflare_workers_for_platforms_dispatch_namespace/resource.tf @@ -0,0 +1,12 @@ +resource "cloudflare_workers_for_platforms_dispatch_namespace" "example" { + account_id = "f037e56e89293a057740de681ac9abbe" + name = "example-namespace" +} + +resource "cloudflare_workers_script" "customer_worker_1" { + account_id = "f037e56e89293a057740de681ac9abbe" + name = "customer-worker-1" + content = file("script.js") + dispatch_namespace = cloudflare_workers_for_platforms_namespace.example.name + tags = ["free"] +} diff --git a/examples/resources/cloudflare_workers_for_platforms_namespace/resource.tf b/examples/resources/cloudflare_workers_for_platforms_namespace/resource.tf index 8317c22eac..5ff905d565 100644 --- a/examples/resources/cloudflare_workers_for_platforms_namespace/resource.tf +++ b/examples/resources/cloudflare_workers_for_platforms_namespace/resource.tf @@ -3,7 +3,7 @@ resource "cloudflare_workers_for_platforms_namespace" "example" { name = "example-namespace" } -resource "cloudflare_worker_script" "customer_worker_1" { +resource "cloudflare_workers_script" "customer_worker_1" { account_id = "f037e56e89293a057740de681ac9abbe" name = "customer-worker-1" content = file("script.js") diff --git a/examples/resources/cloudflare_workers_route/import.sh b/examples/resources/cloudflare_workers_route/import.sh new file mode 100644 index 0000000000..16c5d20e23 --- /dev/null +++ b/examples/resources/cloudflare_workers_route/import.sh @@ -0,0 +1 @@ +$ terraform import cloudflare_workers_route.example / diff --git a/examples/resources/cloudflare_workers_route/resource.tf b/examples/resources/cloudflare_workers_route/resource.tf new file mode 100644 index 0000000000..78539a68e3 --- /dev/null +++ b/examples/resources/cloudflare_workers_route/resource.tf @@ -0,0 +1,10 @@ +# Runs the specified worker script for all URLs that match `example.com/*` +resource "cloudflare_workers_route" "my_route" { + zone_id = "0da42c8d2132a9ddaf714f9e7c920711" + pattern = "example.com/*" + script_name = cloudflare_worker_script.my_script.name +} + +resource "cloudflare_workers_script" "my_script" { + # see "cloudflare_workers_script" documentation ... +} diff --git a/examples/resources/cloudflare_workers_script/import.sh b/examples/resources/cloudflare_workers_script/import.sh new file mode 100644 index 0000000000..3789cf2d8b --- /dev/null +++ b/examples/resources/cloudflare_workers_script/import.sh @@ -0,0 +1 @@ +$ terraform import cloudflare_workers_script.example / diff --git a/examples/resources/cloudflare_workers_script/resource.tf b/examples/resources/cloudflare_workers_script/resource.tf new file mode 100644 index 0000000000..b15ae74589 --- /dev/null +++ b/examples/resources/cloudflare_workers_script/resource.tf @@ -0,0 +1,47 @@ +resource "cloudflare_workers_kv_namespace" "my_namespace" { + account_id = "f037e56e89293a057740de681ac9abbe" + title = "example" +} + +# Sets the script with the name "script_1" +resource "cloudflare_workers_script" "my_script" { + account_id = "f037e56e89293a057740de681ac9abbe" + name = "script_1" + content = file("script.js") + + kv_namespace_binding { + name = "MY_EXAMPLE_KV_NAMESPACE" + namespace_id = cloudflare_workers_kv_namespace.my_namespace.id + } + + plain_text_binding { + name = "MY_EXAMPLE_PLAIN_TEXT" + text = "foobar" + } + + secret_text_binding { + name = "MY_EXAMPLE_SECRET_TEXT" + text = var.secret_foo_value + } + + webassembly_binding { + name = "MY_EXAMPLE_WASM" + module = filebase64("example.wasm") + } + + service_binding { + name = "MY_SERVICE_BINDING" + service = "MY_SERVICE" + environment = "production" + } + + r2_bucket_binding { + name = "MY_BUCKET" + bucket_name = "MY_BUCKET_NAME" + } + + analytics_engine_binding { + name = "MY_DATASET" + dataset = "dataset1" + } +} diff --git a/examples/resources/cloudflare_workers_secret/import.sh b/examples/resources/cloudflare_workers_secret/import.sh new file mode 100644 index 0000000000..75fbf888ce --- /dev/null +++ b/examples/resources/cloudflare_workers_secret/import.sh @@ -0,0 +1 @@ +$ terraform import cloudflare_workers_secret.example // diff --git a/examples/resources/cloudflare_workers_secret/resource.tf b/examples/resources/cloudflare_workers_secret/resource.tf new file mode 100644 index 0000000000..60171b7640 --- /dev/null +++ b/examples/resources/cloudflare_workers_secret/resource.tf @@ -0,0 +1,6 @@ +resource "cloudflare_workers_secret" "my_secret" { + account_id = "f037e56e89293a057740de681ac9abbe" + name = "MY_EXAMPLE_SECRET_TEXT" + script_name = "script_1" + secret_text = "my_secret_value" +} From 388c71d8ac7f94eb859b577e2462a02bd7076033 Mon Sep 17 00:00:00 2001 From: Jacob Bednarz Date: Fri, 26 Jul 2024 14:48:12 +1000 Subject: [PATCH 16/27] grit: add pattern for value deprecation --- .grit/.gitignore | 2 ++ .../cloudflare_record_deprecate_value_for_content.grit | 9 +++++++++ 2 files changed, 11 insertions(+) create mode 100644 .grit/.gitignore create mode 100644 .grit/patterns/cloudflare_record_deprecate_value_for_content.grit diff --git a/.grit/.gitignore b/.grit/.gitignore new file mode 100644 index 0000000000..e4fdfb17c1 --- /dev/null +++ b/.grit/.gitignore @@ -0,0 +1,2 @@ +.gritmodules* +*.log diff --git a/.grit/patterns/cloudflare_record_deprecate_value_for_content.grit b/.grit/patterns/cloudflare_record_deprecate_value_for_content.grit new file mode 100644 index 0000000000..0c68b45942 --- /dev/null +++ b/.grit/patterns/cloudflare_record_deprecate_value_for_content.grit @@ -0,0 +1,9 @@ +engine marzano(0.1) +language hcl + +pattern cloudflare_record_deprecate_value_for_content() { + `value = $v` as $record => `content = $v` where $record <: and { + within `resource "cloudflare_record" $_ { $_ }`, + not within `data { $_ }`, + } +} From eb77d18955b5c0f4e6ea55a7fa7611abb4bc06a0 Mon Sep 17 00:00:00 2001 From: Brenno Oliveira Date: Fri, 26 Jul 2024 16:29:59 +0200 Subject: [PATCH 17/27] resource/cloudflare_teams_rule: add disable_clipboard_redirection Signed-off-by: Brenno Oliveira --- docs/resources/teams_rule.md | 1 + .../resource_cloudflare_teams_rules.go | 23 +++--- .../resource_cloudflare_teams_rules_test.go | 80 +++++++++++++++++++ .../schema_cloudflare_teams_rules.go | 5 ++ 4 files changed, 99 insertions(+), 10 deletions(-) diff --git a/docs/resources/teams_rule.md b/docs/resources/teams_rule.md index 843a9e54d7..b2fc381208 100644 --- a/docs/resources/teams_rule.md +++ b/docs/resources/teams_rule.md @@ -90,6 +90,7 @@ Required: Optional: +- `disable_clipboard_redirection` (Boolean) Disable clipboard redirection. - `disable_copy_paste` (Boolean) Disable copy-paste. - `disable_download` (Boolean) Disable download. - `disable_keyboard` (Boolean) Disable keyboard usage. diff --git a/internal/sdkv2provider/resource_cloudflare_teams_rules.go b/internal/sdkv2provider/resource_cloudflare_teams_rules.go index c35f790710..096e899af0 100644 --- a/internal/sdkv2provider/resource_cloudflare_teams_rules.go +++ b/internal/sdkv2provider/resource_cloudflare_teams_rules.go @@ -340,11 +340,12 @@ func flattenTeamsRuleBisoAdminControls(settings *cloudflare.TeamsBISOAdminContro return nil } return []interface{}{map[string]interface{}{ - "disable_printing": settings.DisablePrinting, - "disable_copy_paste": settings.DisableCopyPaste, - "disable_download": settings.DisableDownload, - "disable_upload": settings.DisableUpload, - "disable_keyboard": settings.DisableKeyboard, + "disable_printing": settings.DisablePrinting, + "disable_copy_paste": settings.DisableCopyPaste, + "disable_download": settings.DisableDownload, + "disable_upload": settings.DisableUpload, + "disable_keyboard": settings.DisableKeyboard, + "disable_clipboard_redirection": settings.DisableClipboardRedirection, }} } @@ -370,12 +371,14 @@ func inflateTeamsRuleBisoAdminControls(settings interface{}) *cloudflare.TeamsBI disableDownload := settingsMap["disable_download"].(bool) disableUpload := settingsMap["disable_upload"].(bool) disableKeyboard := settingsMap["disable_keyboard"].(bool) + disableClipboardRedirection := settingsMap["disable_clipboard_redirection"].(bool) return &cloudflare.TeamsBISOAdminControlSettings{ - DisablePrinting: disablePrinting, - DisableCopyPaste: disableCopyPaste, - DisableDownload: disableDownload, - DisableUpload: disableUpload, - DisableKeyboard: disableKeyboard, + DisablePrinting: disablePrinting, + DisableCopyPaste: disableCopyPaste, + DisableDownload: disableDownload, + DisableUpload: disableUpload, + DisableKeyboard: disableKeyboard, + DisableClipboardRedirection: disableClipboardRedirection, } } diff --git a/internal/sdkv2provider/resource_cloudflare_teams_rules_test.go b/internal/sdkv2provider/resource_cloudflare_teams_rules_test.go index ed7d983f6e..632ffd62ee 100644 --- a/internal/sdkv2provider/resource_cloudflare_teams_rules_test.go +++ b/internal/sdkv2provider/resource_cloudflare_teams_rules_test.go @@ -266,3 +266,83 @@ resource "cloudflare_teams_rule" "%[1]s" { } `, rnd, accountID) } + +func TestAccCloudflareTeamsRule_WithClipboardRedirection(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_rule.%s", rnd) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + }, + ProviderFactories: providerFactories, + CheckDestroy: testAccCheckCloudflareTeamsRuleDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCloudflareTeamsRuleConfigWithClipboardRedirection(rnd, accountID), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(name, consts.AccountIDSchemaKey, accountID), + resource.TestCheckResourceAttr(name, "name", rnd), + resource.TestCheckResourceAttr(name, "description", "desc"), + resource.TestCheckResourceAttr(name, "precedence", "12302"), + resource.TestCheckResourceAttr(name, "action", "block"), + resource.TestCheckResourceAttr(name, "filters.0", "dns"), + resource.TestCheckResourceAttr(name, "traffic", "any(dns.domains[*] == \"example.com\")"), + resource.TestCheckResourceAttr(name, "rule_settings.#", "1"), + resource.TestCheckResourceAttr(name, "rule_settings.0.block_page_enabled", "true"), + resource.TestCheckResourceAttr(name, "rule_settings.0.block_page_reason", "cuz"), + resource.TestCheckResourceAttr(name, "rule_settings.0.insecure_disable_dnssec_validation", "false"), + resource.TestCheckResourceAttr(name, "rule_settings.0.egress.0.ipv4", "203.0.113.1"), + resource.TestCheckResourceAttr(name, "rule_settings.0.egress.0.ipv6", "2001:db8::/32"), + resource.TestCheckResourceAttr(name, "rule_settings.0.untrusted_cert.0.action", "error"), + resource.TestCheckResourceAttr(name, "rule_settings.0.payload_log.0.enabled", "true"), + resource.TestCheckResourceAttr(name, "rule_settings.0.biso_admin_controls.0.disable_clipboard_redirection", "true"), // Check new parameter + ), + }, + }, + }) +} + +func testAccCloudflareTeamsRuleConfigWithClipboardRedirection(rnd, accountID string) string { + return fmt.Sprintf(` +resource "cloudflare_teams_rule" "%[1]s" { + name = "%[1]s" + account_id = "%[2]s" + description = "desc" + precedence = 12302 + action = "block" + filters = ["dns"] + traffic = "any(dns.domains[*] == \"example.com\")" + rule_settings { + block_page_enabled = true + block_page_reason = "cuz" + insecure_disable_dnssec_validation = false + egress { + ipv4 = "203.0.113.1" + ipv6 = "2001:db8::/32" + } + untrusted_cert { + action = "error" + } + payload_log { + enabled = true + } + biso_admin_controls { + disable_printing = false + disable_copy_paste = false + disable_download = false + disable_upload = false + disable_keyboard = false + disable_clipboard_redirection = true // Set new parameter + } + } +} +`, rnd, accountID) +} diff --git a/internal/sdkv2provider/schema_cloudflare_teams_rules.go b/internal/sdkv2provider/schema_cloudflare_teams_rules.go index 83d5382043..343d1c108b 100644 --- a/internal/sdkv2provider/schema_cloudflare_teams_rules.go +++ b/internal/sdkv2provider/schema_cloudflare_teams_rules.go @@ -322,6 +322,11 @@ var teamsBisoAdminControls = map[string]*schema.Schema{ Optional: true, Description: "Disable upload.", }, + "disable_clipboard_redirection": { + Type: schema.TypeBool, + Optional: true, + Description: "Disable clipboard redirection.", + }, } var teamsCheckSessionSettings = map[string]*schema.Schema{ From 621bedce3b420538622d13720977946e25ec3899 Mon Sep 17 00:00:00 2001 From: Brenno Oliveira Date: Fri, 26 Jul 2024 16:36:00 +0200 Subject: [PATCH 18/27] add changelog entry Signed-off-by: Brenno Oliveira --- .changelog/3511.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/3511.txt diff --git a/.changelog/3511.txt b/.changelog/3511.txt new file mode 100644 index 0000000000..7d4dac1b79 --- /dev/null +++ b/.changelog/3511.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +resource/cloudflare_teams_rule: Add `disable_clipboard_redirection` attribute to `BISOAdminControls` +``` From 4dc5d4e457015c0e07956c346b01f2822b426476 Mon Sep 17 00:00:00 2001 From: Patrick Vares Date: Fri, 26 Jul 2024 14:43:24 -0400 Subject: [PATCH 19/27] bug(3506): Fix list_tiem hostname overlap --- .../framework/service/list_item/resource.go | 5 ++ .../service/list_item/resource_test.go | 55 +++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/internal/framework/service/list_item/resource.go b/internal/framework/service/list_item/resource.go index e07e8cdd1a..4195794331 100644 --- a/internal/framework/service/list_item/resource.go +++ b/internal/framework/service/list_item/resource.go @@ -308,6 +308,11 @@ func createListItem(ctx context.Context, client *muxclient.Client, data *ListIte } for _, item := range items { + if item.Hostname != nil && item.Hostname.UrlHostname == searchTerm { + items = []cfv1.ListItem{item} + break + } + if item.Redirect != nil && item.Redirect.SourceUrl == searchTerm { items = []cfv1.ListItem{item} break diff --git a/internal/framework/service/list_item/resource_test.go b/internal/framework/service/list_item/resource_test.go index bd0ccb223e..e1422560ab 100644 --- a/internal/framework/service/list_item/resource_test.go +++ b/internal/framework/service/list_item/resource_test.go @@ -434,6 +434,61 @@ func testAccCheckCloudflareHostnameRedirectWithOverlappingSourceURL(ID, name, co `, ID, name, comment, accountID) } +func TestAccCloudflareListItem_WithOverlappingHostname(t *testing.T) { + rnd := utils.GenerateRandomResourceName() + firstResource := fmt.Sprintf("cloudflare_list_item.%s_1", rnd) + secondResource := fmt.Sprintf("cloudflare_list_item.%s_2", rnd) + accountID := os.Getenv("CLOUDFLARE_ACCOUNT_ID") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.TestAccPreCheck_Account(t) + }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccCheckCloudflareHostnameWithOverlappingHostname(rnd, rnd, rnd, accountID), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(firstResource, "hostname.#", "1"), + resource.TestCheckResourceAttr(firstResource, "hostname.0.url_hostname", "site1.com"), + + resource.TestCheckResourceAttr(secondResource, "hostname.#", "1"), + resource.TestCheckResourceAttr(secondResource, "hostname.0.url_hostname", "test.site1.com"), + ), + }, + }, + }) +} + +func testAccCheckCloudflareHostnameWithOverlappingHostname(ID, name, comment, accountID string) string { + return fmt.Sprintf(` + resource "cloudflare_list" "%[2]s" { + account_id = "%[4]s" + name = "%[2]s" + description = "list named %[2]s" + kind = "hostname" + } + + resource "cloudflare_list_item" "%[1]s_1" { + account_id = "%[4]s" + list_id = cloudflare_list.%[2]s.id + comment = "%[3]s" + hostname { + url_hostname = "site1.com" + } + } + + resource "cloudflare_list_item" "%[1]s_2" { + account_id = "%[4]s" + list_id = cloudflare_list.%[2]s.id + comment = "%[3]s" + hostname { + url_hostname = "test.site1.com" + } + } + `, ID, name, comment, accountID) +} + func TestAccCloudflareListItem_IPWithOverlappingPrefix(t *testing.T) { rnd := utils.GenerateRandomResourceName() firstResource := fmt.Sprintf("cloudflare_list_item.%s_1", rnd) From f859b89a51aed22e12e2b4f75cfde205a8e6b5ef Mon Sep 17 00:00:00 2001 From: Patrick Vares Date: Fri, 26 Jul 2024 14:46:17 -0400 Subject: [PATCH 20/27] Added changelog --- .changelog/3515.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/3515.txt diff --git a/.changelog/3515.txt b/.changelog/3515.txt new file mode 100644 index 0000000000..5da02af7e7 --- /dev/null +++ b/.changelog/3515.txt @@ -0,0 +1,3 @@ +```release-note:bug +resource/cloudflare_list_item: handle overlapping hostname `url_hostname` +``` \ No newline at end of file From 51dfb47ef35c53d3dd8b5b37f3d0aad23e4da259 Mon Sep 17 00:00:00 2001 From: Emilio Assuncao Date: Fri, 28 Jun 2024 17:12:06 -0500 Subject: [PATCH 21/27] Implement Support for Hyperdrive over Access Also improved the provider to support the remaining two caching settings: max_age and stale_while_revalidate. --- .../service/hyperdrive_config/model.go | 22 +- .../service/hyperdrive_config/resource.go | 109 ++++----- .../hyperdrive_config/resource_test.go | 220 +++++++++++++++++- .../service/hyperdrive_config/schema.go | 43 +++- 4 files changed, 327 insertions(+), 67 deletions(-) diff --git a/internal/framework/service/hyperdrive_config/model.go b/internal/framework/service/hyperdrive_config/model.go index 16977b0b7d..c96e9703f0 100644 --- a/internal/framework/service/hyperdrive_config/model.go +++ b/internal/framework/service/hyperdrive_config/model.go @@ -14,20 +14,26 @@ type HyperdriveConfigModel struct { } type HyperdriveConfigOriginModel struct { - Database types.String `tfsdk:"database"` - Password types.String `tfsdk:"password"` - Host types.String `tfsdk:"host"` - Port types.Int64 `tfsdk:"port"` - Scheme types.String `tfsdk:"scheme"` - User types.String `tfsdk:"user"` + Database types.String `tfsdk:"database"` + Password types.String `tfsdk:"password"` + Host types.String `tfsdk:"host"` + Port types.Int64 `tfsdk:"port"` + Scheme types.String `tfsdk:"scheme"` + User types.String `tfsdk:"user"` + AccessClientID types.String `tfsdk:"access_client_id"` + AccessClientSecret types.String `tfsdk:"access_client_secret"` } type HyperdriveConfigCachingModel struct { - Disabled types.Bool `tfsdk:"disabled"` + Disabled types.Bool `tfsdk:"disabled"` + MaxAge types.Int64 `tfsdk:"max_age"` + StaleWhileRevalidate types.Int64 `tfsdk:"stale_while_revalidate"` } func (m HyperdriveConfigCachingModel) AttributeTypes() map[string]attr.Type { return map[string]attr.Type{ - "disabled": types.BoolType, + "disabled": types.BoolType, + "max_age": types.Int64Type, + "stale_while_revalidate": types.Int64Type, } } diff --git a/internal/framework/service/hyperdrive_config/resource.go b/internal/framework/service/hyperdrive_config/resource.go index 65d964b719..2ba1b69a7b 100644 --- a/internal/framework/service/hyperdrive_config/resource.go +++ b/internal/framework/service/hyperdrive_config/resource.go @@ -62,30 +62,16 @@ func (r *HyperdriveConfigResource) Create(ctx context.Context, req resource.Crea return } - config := buildHyperdriveConfigFromModel(data, caching) - - createHyperdriveConfig, err := r.client.V1.CreateHyperdriveConfig(ctx, cfv1.AccountIdentifier(data.AccountID.ValueString()), - cfv1.CreateHyperdriveConfigParams{ - Name: config.Name, - Origin: cfv1.HyperdriveConfigOrigin{ - Database: config.Origin.Database, - Password: config.Origin.Password, - Host: config.Origin.Host, - Port: config.Origin.Port, - Scheme: config.Origin.Scheme, - User: config.Origin.User, - }, - Caching: cfv1.HyperdriveConfigCaching{ - Disabled: config.Caching.Disabled, - }, - }) + config := buildHyperdriveCreateUpdateConfigsFromModel(data, caching) + + createHyperdriveConfig, err := r.client.V1.CreateHyperdriveConfig(ctx, cfv1.AccountIdentifier(data.AccountID.ValueString()), config) if err != nil { resp.Diagnostics.AddError("Error creating hyperdrive config", err.Error()) return } var diags diag.Diagnostics - data, diags = buildHyperdriveConfigModelFromHyperdriveConfig(ctx, data, createHyperdriveConfig) + data, diags = buildHyperdriveConfigModelFromAPIResponse(ctx, data, createHyperdriveConfig) resp.Diagnostics.Append(diags...) resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) @@ -108,7 +94,7 @@ func (r *HyperdriveConfigResource) Read(ctx context.Context, req resource.ReadRe } var diags diag.Diagnostics - data, diags = buildHyperdriveConfigModelFromHyperdriveConfig(ctx, data, config) + data, diags = buildHyperdriveConfigModelFromAPIResponse(ctx, data, config) resp.Diagnostics.Append(diags...) resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) @@ -125,22 +111,12 @@ func (r *HyperdriveConfigResource) Update(ctx context.Context, req resource.Upda return } - config := buildHyperdriveConfigFromModel(data, caching) + config := buildHyperdriveCreateUpdateConfigsFromModel(data, caching) updatedConfig, err := r.client.V1.UpdateHyperdriveConfig(ctx, cfv1.AccountIdentifier(data.AccountID.ValueString()), cfv1.UpdateHyperdriveConfigParams{ - Name: config.Name, - HyperdriveID: config.ID, - Origin: cfv1.HyperdriveConfigOrigin{ - Database: config.Origin.Database, - Password: config.Origin.Password, - Host: config.Origin.Host, - Port: config.Origin.Port, - Scheme: config.Origin.Scheme, - User: config.Origin.User, - }, - Caching: cfv1.HyperdriveConfigCaching{ - Disabled: config.Caching.Disabled, - }, + Name: config.Name, + Origin: config.Origin, + Caching: config.Caching, }) if err != nil { @@ -149,7 +125,7 @@ func (r *HyperdriveConfigResource) Update(ctx context.Context, req resource.Upda } var diags diag.Diagnostics - data, diags = buildHyperdriveConfigModelFromHyperdriveConfig(ctx, data, updatedConfig) + data, diags = buildHyperdriveConfigModelFromAPIResponse(ctx, data, updatedConfig) resp.Diagnostics.Append(diags...) resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) @@ -179,18 +155,27 @@ func (r *HyperdriveConfigResource) ImportState(ctx context.Context, req resource resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), idParts[1])...) } -func buildHyperdriveConfigFromModel(config *HyperdriveConfigModel, caching *HyperdriveConfigCachingModel) cfv1.HyperdriveConfig { - built := cfv1.HyperdriveConfig{ - ID: config.ID.ValueString(), +func buildHyperdriveCreateUpdateConfigsFromModel(config *HyperdriveConfigModel, caching *HyperdriveConfigCachingModel) cfv1.CreateHyperdriveConfigParams { + built := cfv1.CreateHyperdriveConfigParams{ Name: config.Name.ValueString(), - Origin: cfv1.HyperdriveConfigOrigin{ - Database: config.Origin.Database.ValueString(), + Origin: cfv1.HyperdriveConfigOriginWithSecrets{ + HyperdriveConfigOrigin: cfv1.HyperdriveConfigOrigin{ + Database: config.Origin.Database.ValueString(), + Host: config.Origin.Host.ValueString(), + }, Password: config.Origin.Password.ValueString(), - Host: config.Origin.Host.ValueString(), - Port: int(config.Origin.Port.ValueInt64()), }, } + if !config.Origin.Port.IsNull() { + built.Origin.Port = int(config.Origin.Port.ValueInt64()) + } + + if !config.Origin.AccessClientID.IsNull() && !config.Origin.AccessClientSecret.IsNull() { + built.Origin.AccessClientID = config.Origin.AccessClientID.ValueString() + built.Origin.AccessClientSecret = config.Origin.AccessClientSecret.ValueString() + } + if !config.Origin.Scheme.IsNull() { built.Origin.Scheme = config.Origin.Scheme.ValueString() } @@ -201,45 +186,61 @@ func buildHyperdriveConfigFromModel(config *HyperdriveConfigModel, caching *Hype built.Caching = cfv1.HyperdriveConfigCaching{} - if caching != nil && !caching.Disabled.IsNull() { - built.Caching.Disabled = cfv1.BoolPtr(caching.Disabled.ValueBool()) + if caching != nil { + if !caching.Disabled.IsNull() { + built.Caching.Disabled = cfv1.BoolPtr(caching.Disabled.ValueBool()) + } + if !caching.MaxAge.IsNull() { + built.Caching.MaxAge = int(caching.MaxAge.ValueInt64()) + } + if !caching.StaleWhileRevalidate.IsNull() { + built.Caching.StaleWhileRevalidate = int(caching.StaleWhileRevalidate.ValueInt64()) + } } return built } -func buildHyperdriveConfigModelFromHyperdriveConfig(ctx context.Context, data *HyperdriveConfigModel, config cfv1.HyperdriveConfig) (*HyperdriveConfigModel, diag.Diagnostics) { +func buildHyperdriveConfigModelFromAPIResponse(ctx context.Context, data *HyperdriveConfigModel, config cfv1.HyperdriveConfig) (*HyperdriveConfigModel, diag.Diagnostics) { var scheme = flatteners.String("postgres") if data.Origin != nil { scheme = data.Origin.Scheme } var password = flatteners.String("") + var accessClientSecret = flatteners.String("") if data.Origin != nil { password = data.Origin.Password + accessClientSecret = data.Origin.AccessClientSecret } var caching, diags = types.ObjectValueFrom( ctx, HyperdriveConfigCachingModel{}.AttributeTypes(), HyperdriveConfigCachingModel{ - Disabled: types.BoolValue(*config.Caching.Disabled), + Disabled: types.BoolValue(*config.Caching.Disabled), + MaxAge: flatteners.Int64(int64(config.Caching.MaxAge)), + StaleWhileRevalidate: flatteners.Int64(int64(config.Caching.StaleWhileRevalidate)), }, ) + origin := HyperdriveConfigOriginModel{ + Database: flatteners.String(config.Origin.Database), + Host: flatteners.String(config.Origin.Host), + Port: flatteners.Int64(int64(config.Origin.Port)), + User: flatteners.String(config.Origin.User), + Scheme: scheme, + Password: password, + AccessClientID: flatteners.String(config.Origin.AccessClientID), + AccessClientSecret: accessClientSecret, + } + built := HyperdriveConfigModel{ AccountID: data.AccountID, ID: flatteners.String(config.ID), Name: flatteners.String(config.Name), - Origin: &HyperdriveConfigOriginModel{ - Database: flatteners.String(config.Origin.Database), - Host: flatteners.String(config.Origin.Host), - Port: flatteners.Int64(int64(config.Origin.Port)), - User: flatteners.String(config.Origin.User), - Scheme: scheme, - Password: password, - }, - Caching: caching, + Origin: &origin, + Caching: caching, } return &built, diags diff --git a/internal/framework/service/hyperdrive_config/resource_test.go b/internal/framework/service/hyperdrive_config/resource_test.go index 2fc81dbbcf..3d1806c538 100644 --- a/internal/framework/service/hyperdrive_config/resource_test.go +++ b/internal/framework/service/hyperdrive_config/resource_test.go @@ -73,7 +73,7 @@ func TestAccCloudflareHyperdriveConfig_Basic(t *testing.T) { ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, Steps: []resource.TestStep{ { - Config: testHyperdriveConfigConfig( + Config: testHyperdriveConfig( rnd, accountID, rnd, @@ -90,7 +90,11 @@ func TestAccCloudflareHyperdriveConfig_Basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "origin.scheme", "postgres"), resource.TestCheckResourceAttr(resourceName, "origin.user", "user"), resource.TestCheckResourceAttr(resourceName, "origin.password", "password"), + resource.TestCheckNoResourceAttr(resourceName, "origin.access_client_id"), + resource.TestCheckNoResourceAttr(resourceName, "origin.access_client_secret"), resource.TestCheckResourceAttr(resourceName, "caching.disabled", "true"), + resource.TestCheckNoResourceAttr(resourceName, "caching.max_age"), + resource.TestCheckNoResourceAttr(resourceName, "caching.stale_while_revalidate"), ), }, { @@ -104,6 +108,127 @@ func TestAccCloudflareHyperdriveConfig_Basic(t *testing.T) { }) } +func TestAccCloudflareHyperdriveConfig_CachingSettings(t *testing.T) { + acctest.TestAccSkipForDefaultAccount(t, "Requires real Postgres instance to be available.") + + rnd := utils.GenerateRandomResourceName() + accountID := os.Getenv("CLOUDFLARE_ACCOUNT_ID") + resourceName := "cloudflare_hyperdrive_config." + rnd + + var origin = cfv1.HyperdriveConfigOrigin{ + Database: "database", + Host: "host.example.com", + Port: 5432, + Scheme: "postgres", + User: "user", + } + + var disabled = false + + var caching = cfv1.HyperdriveConfigCaching{ + Disabled: &disabled, + MaxAge: 60, + StaleWhileRevalidate: 30, + } + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testHyperdriveConfigFullCachingSettings( + rnd, + accountID, + rnd, + "password", + origin, + caching, + ), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "name", rnd), + resource.TestCheckResourceAttr(resourceName, "account_id", accountID), + resource.TestCheckResourceAttr(resourceName, "origin.database", "database"), + resource.TestCheckResourceAttr(resourceName, "origin.host", "host.example.com"), + resource.TestCheckResourceAttr(resourceName, "origin.port", "5432"), + resource.TestCheckResourceAttr(resourceName, "origin.scheme", "postgres"), + resource.TestCheckResourceAttr(resourceName, "origin.user", "user"), + resource.TestCheckResourceAttr(resourceName, "origin.password", "password"), + resource.TestCheckNoResourceAttr(resourceName, "origin.access_client_id"), + resource.TestCheckNoResourceAttr(resourceName, "origin.access_client_secret"), + resource.TestCheckResourceAttr(resourceName, "caching.disabled", "false"), + resource.TestCheckResourceAttr(resourceName, "caching.max_age", "60"), + resource.TestCheckResourceAttr(resourceName, "caching.stale_while_revalidate", "30"), + ), + }, + { + ResourceName: resourceName, + ImportStateIdPrefix: fmt.Sprintf("%s/", accountID), + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"origin.password"}, + }, + }, + }) +} + +func TestAccCloudflareHyperdriveConfig_HyperdriveOverAccess(t *testing.T) { + rnd := utils.GenerateRandomResourceName() + accountID := os.Getenv("CLOUDFLARE_ACCOUNT_ID") + resourceName := "cloudflare_hyperdrive_config." + rnd + + var origin = cfv1.HyperdriveConfigOriginWithSecrets{ + HyperdriveConfigOrigin: cfv1.HyperdriveConfigOrigin{Database: "database", + Host: "host.example.com", + Scheme: "postgres", + User: "user", + AccessClientID: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.access"}, + AccessClientSecret: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + } + + var disabled = true + + var caching = cfv1.HyperdriveConfigCaching{ + Disabled: &disabled, + } + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testHyperdriveOverAccessConfig( + rnd, + accountID, + rnd, + "password", + origin, + caching, + ), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "name", rnd), + resource.TestCheckResourceAttr(resourceName, "account_id", accountID), + resource.TestCheckResourceAttr(resourceName, "origin.database", "database"), + resource.TestCheckResourceAttr(resourceName, "origin.host", "host.example.com"), + resource.TestCheckNoResourceAttr(resourceName, "origin.port"), + resource.TestCheckResourceAttr(resourceName, "origin.scheme", "postgres"), + resource.TestCheckResourceAttr(resourceName, "origin.user", "user"), + resource.TestCheckResourceAttr(resourceName, "origin.password", "password"), + resource.TestCheckResourceAttr(resourceName, "origin.access_client_id", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.access"), + resource.TestCheckResourceAttr(resourceName, "origin.access_client_secret", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"), + resource.TestCheckResourceAttr(resourceName, "caching.disabled", "true"), + ), + }, + { + ResourceName: resourceName, + ImportStateIdPrefix: fmt.Sprintf("%s/", accountID), + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"origin.password", "origin.access_client_secret"}, + }, + }, + }) +} + func TestAccCloudflareHyperdriveConfig_Minimum(t *testing.T) { acctest.TestAccSkipForDefaultAccount(t, "Requires real Postgres instance to be available.") @@ -154,7 +279,39 @@ func TestAccCloudflareHyperdriveConfig_Minimum(t *testing.T) { }) } -func testHyperdriveConfigConfig( +func testHyperdriveConfig( + rnd, accountId, name string, password string, origin cfv1.HyperdriveConfigOrigin, caching cfv1.HyperdriveConfigCaching, +) string { + return fmt.Sprintf(` + resource "cloudflare_hyperdrive_config" "%[1]s" { + account_id = "%[2]s" + name = "%[3]s" + origin = { + password = "%[4]s" + database = "%[5]s" + host = "%[6]s" + port = "%[7]s" + scheme = "%[8]s" + user = "%[9]s" + } + caching = { + disabled = %[10]s + } + }`, + rnd, + accountId, + name, + password, + origin.Database, + origin.Host, + fmt.Sprintf("%d", origin.Port), + origin.Scheme, + origin.User, + fmt.Sprintf("%t", *caching.Disabled), + ) +} + +func testHyperdriveConfigFullCachingSettings( rnd, accountId, name string, password string, origin cfv1.HyperdriveConfigOrigin, caching cfv1.HyperdriveConfigCaching, ) string { return fmt.Sprintf(` @@ -171,9 +328,56 @@ func testHyperdriveConfigConfig( } caching = { disabled = %[10]s + max_age = %[11]s + stale_while_revalidate = %[12]s + } + }`, + rnd, + accountId, + name, + password, + origin.Database, + origin.Host, + fmt.Sprintf("%d", origin.Port), + origin.Scheme, + origin.User, + fmt.Sprintf("%t", *caching.Disabled), + fmt.Sprintf("%d", caching.MaxAge), + fmt.Sprintf("%d", caching.StaleWhileRevalidate), + ) +} + +func testHyperdriveOverAccessConfig( + rnd, accountId, name string, password string, origin cfv1.HyperdriveConfigOriginWithSecrets, caching cfv1.HyperdriveConfigCaching, +) string { + return fmt.Sprintf(` + resource "cloudflare_hyperdrive_config" "%[1]s" { + account_id = "%[2]s" + name = "%[3]s" + origin = { + password = "%[4]s" + database = "%[5]s" + host = "%[6]s" + scheme = "%[7]s" + user = "%[8]s" + access_client_id = "%[9]s" + access_client_secret = "%[10]s" + } + caching = { + disabled = %[11]s } }`, - rnd, accountId, name, password, origin.Database, origin.Host, fmt.Sprintf("%d", origin.Port), origin.Scheme, origin.User, fmt.Sprintf("%t", *caching.Disabled), + rnd, + accountId, + name, + password, + origin.Database, + origin.Host, + origin.Scheme, + origin.User, + origin.AccessClientID, + origin.AccessClientSecret, + fmt.Sprintf("%t", *caching.Disabled), ) } @@ -193,6 +397,14 @@ func testHyperdriveConfigConfigMinimum( user = "%[9]s" } }`, - rnd, accountId, name, password, origin.Database, origin.Host, fmt.Sprintf("%d", origin.Port), origin.Scheme, origin.User, + rnd, + accountId, + name, + password, + origin.Database, + origin.Host, + fmt.Sprintf("%d", origin.Port), + origin.Scheme, + origin.User, ) } diff --git a/internal/framework/service/hyperdrive_config/schema.go b/internal/framework/service/hyperdrive_config/schema.go index ab10bcb88e..d718f16f39 100644 --- a/internal/framework/service/hyperdrive_config/schema.go +++ b/internal/framework/service/hyperdrive_config/schema.go @@ -5,10 +5,14 @@ import ( "github.com/MakeNowJust/heredoc/v2" "github.com/cloudflare/terraform-provider-cloudflare/internal/consts" + "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" ) func (r *HyperdriveConfigResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { @@ -53,7 +57,11 @@ func (r *HyperdriveConfigResource) Schema(ctx context.Context, req resource.Sche }, "port": schema.Int64Attribute{ MarkdownDescription: "The port (default: 5432 for Postgres) of your origin database.", - Required: true, + Optional: true, + Validators: []validator.Int64{ + int64validator.AtLeast(0), + int64validator.AtMost(65535), + }, }, "scheme": schema.StringAttribute{ MarkdownDescription: "Specifies the URL scheme used to connect to your origin database.", @@ -63,6 +71,22 @@ func (r *HyperdriveConfigResource) Schema(ctx context.Context, req resource.Sche MarkdownDescription: "The user of your origin database.", Required: true, }, + "access_client_id": schema.StringAttribute{ + MarkdownDescription: "Client ID associated with the Cloudflare Access Service Token used to connect via Access.", + Optional: true, + Validators: []validator.String{ + stringvalidator.AlsoRequires(path.MatchRelative().AtParent().AtName("access_client_secret")), + stringvalidator.ConflictsWith(path.MatchRelative().AtParent().AtName("port")), + }, + }, + "access_client_secret": schema.StringAttribute{ + MarkdownDescription: "Client Secret associated with the Cloudflare Access Service Token used to connect via Access.", + Optional: true, + Validators: []validator.String{ + stringvalidator.AlsoRequires(path.MatchRelative().AtParent().AtName("access_client_id")), + stringvalidator.ConflictsWith(path.MatchRelative().AtParent().AtName("port")), + }, + }, }, }, "caching": schema.SingleNestedAttribute{ @@ -75,6 +99,23 @@ func (r *HyperdriveConfigResource) Schema(ctx context.Context, req resource.Sche Optional: true, Computed: true, }, + "max_age": schema.Int64Attribute{ + MarkdownDescription: "Disable caching for this Hyperdrive configuration.", + Optional: true, + Validators: []validator.Int64{ + int64validator.AtLeast(0), + int64validator.AtMost(3600), + }, + }, + "stale_while_revalidate": schema.Int64Attribute{ + MarkdownDescription: "Disable caching for this Hyperdrive configuration.", + Optional: true, + Validators: []validator.Int64{ + int64validator.AlsoRequires(path.MatchRelative().AtParent().AtName("max_age")), + int64validator.AtLeast(0), + int64validator.AtMost(3600), // Max value is the value of max_age, API will reject if not + }, + }, }, }, }, From 77ab9dc0bf05b10252e86bb213691e3a3c5f1899 Mon Sep 17 00:00:00 2001 From: Emilio Assuncao Date: Fri, 26 Jul 2024 16:45:58 -0500 Subject: [PATCH 22/27] Add changelog --- .changelog/3501.txt | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changelog/3501.txt diff --git a/.changelog/3501.txt b/.changelog/3501.txt new file mode 100644 index 0000000000..00434afadb --- /dev/null +++ b/.changelog/3501.txt @@ -0,0 +1,7 @@ +```release-note:enhancement +resource/hyperdrive_config: Add support for creating Hyperdrive over Access configs +``` + +```release-note:enhancement +resource/hyperdrive_config: Add support for max_age and stale_while_revalidate in Hyperdrive Config caching settings +``` \ No newline at end of file From 7a16cbbf9e8f4624fe0f14155e1419794207e00f Mon Sep 17 00:00:00 2001 From: Jacob Bednarz Date: Mon, 29 Jul 2024 12:28:18 +1000 Subject: [PATCH 23/27] Rename 3501.txt to 3516.txt --- .changelog/{3501.txt => 3516.txt} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename .changelog/{3501.txt => 3516.txt} (98%) diff --git a/.changelog/3501.txt b/.changelog/3516.txt similarity index 98% rename from .changelog/3501.txt rename to .changelog/3516.txt index 00434afadb..253705c36a 100644 --- a/.changelog/3501.txt +++ b/.changelog/3516.txt @@ -4,4 +4,4 @@ resource/hyperdrive_config: Add support for creating Hyperdrive over Access conf ```release-note:enhancement resource/hyperdrive_config: Add support for max_age and stale_while_revalidate in Hyperdrive Config caching settings -``` \ No newline at end of file +``` From d1e69b92ce5eb275606f76fa3cd648a2735e8553 Mon Sep 17 00:00:00 2001 From: Jacob Bednarz Date: Mon, 29 Jul 2024 12:45:57 +1000 Subject: [PATCH 24/27] test: parameter-ise the Hyperdrive configuration options Ensures that when we want to run these tests, the configuration is just an environment variable away. --- internal/acctest/acctest.go | 34 +++++ .../hyperdrive_config/resource_test.go | 132 ++++++++++++------ 2 files changed, 120 insertions(+), 46 deletions(-) diff --git a/internal/acctest/acctest.go b/internal/acctest/acctest.go index f711f21c6f..9f52ef57b6 100644 --- a/internal/acctest/acctest.go +++ b/internal/acctest/acctest.go @@ -67,6 +67,40 @@ func TestAccPreCheck_Account(t *testing.T) { // function. } +func TestAccPreCheck_Hyperdrive(t *testing.T) { + if v := os.Getenv("CLOUDFLARE_HYPERDRIVE_DATABASE_NAME"); v == "" { + t.Fatal("CLOUDFLARE_HYPERDRIVE_DATABASE_NAME must be set for this acceptance test") + } + + if v := os.Getenv("CLOUDFLARE_HYPERDRIVE_DATABASE_HOSTNAME"); v == "" { + t.Fatal("CLOUDFLARE_HYPERDRIVE_DATABASE_HOSTNAME must be set for this acceptance test") + } + + if v := os.Getenv("CLOUDFLARE_HYPERDRIVE_DATABASE_PORT"); v == "" { + t.Fatal("CLOUDFLARE_HYPERDRIVE_DATABASE_PORT must be set for this acceptance test") + } + + if v := os.Getenv("CLOUDFLARE_HYPERDRIVE_DATABASE_USER"); v == "" { + t.Fatal("CLOUDFLARE_HYPERDRIVE_DATABASE_USER must be set for this acceptance test") + } + + if v := os.Getenv("CLOUDFLARE_HYPERDRIVE_DATABASE_PASSWORD"); v == "" { + t.Fatal("CLOUDFLARE_HYPERDRIVE_DATABASE_PASSWORD must be set for this acceptance test") + } +} + +func TestAccPreCheck_HyperdriveWithAccess(t *testing.T) { + TestAccPreCheck_Hyperdrive(t) + + if v := os.Getenv("CLOUDFLARE_HYPERDRIVE_ACCESS_CLIENT_ID"); v == "" { + t.Fatal("CLOUDFLARE_HYPERDRIVE_ACCESS_CLIENT_ID must be set for this acceptance test") + } + + if v := os.Getenv("CLOUDFLARE_HYPERDRIVE_ACCESS_CLIENT_SECRET"); v == "" { + t.Fatal("CLOUDFLARE_HYPERDRIVE_ACCESS_CLIENT_SECRET must be set for this acceptance test") + } +} + func TestAccSkipForDefaultZone(t *testing.T, reason string) { if os.Getenv("CLOUDFLARE_ZONE_ID") == testAccCloudflareZoneID { t.Skipf("Skipping acceptance test for default zone (%s). %s", testAccCloudflareZoneID, reason) diff --git a/internal/framework/service/hyperdrive_config/resource_test.go b/internal/framework/service/hyperdrive_config/resource_test.go index 3d1806c538..55847fbaeb 100644 --- a/internal/framework/service/hyperdrive_config/resource_test.go +++ b/internal/framework/service/hyperdrive_config/resource_test.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "os" + "strconv" "testing" cfv1 "github.com/cloudflare/cloudflare-go" @@ -52,14 +53,21 @@ func TestAccCloudflareHyperdriveConfig_Basic(t *testing.T) { rnd := utils.GenerateRandomResourceName() accountID := os.Getenv("CLOUDFLARE_ACCOUNT_ID") + databaseName := os.Getenv("CLOUDFLARE_HYPERDRIVE_DATABASE_NAME") + databaseHostname := os.Getenv("CLOUDFLARE_HYPERDRIVE_DATABASE_HOSTNAME") + databasePort := os.Getenv("CLOUDFLARE_HYPERDRIVE_DATABASE_PORT") + port, _ := strconv.Atoi(databasePort) + databaseUser := os.Getenv("CLOUDFLARE_HYPERDRIVE_DATABASE_USER") + databasePassword := os.Getenv("CLOUDFLARE_HYPERDRIVE_DATABASE_PASSWORD") + resourceName := "cloudflare_hyperdrive_config." + rnd var origin = cfv1.HyperdriveConfigOrigin{ - Database: "database", - Host: "host.example.com", - Port: 5432, + Database: databaseName, + Host: databaseHostname, + Port: port, Scheme: "postgres", - User: "user", + User: databaseUser, } var disabled = true @@ -69,7 +77,10 @@ func TestAccCloudflareHyperdriveConfig_Basic(t *testing.T) { } resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.TestAccPreCheck(t) }, + PreCheck: func() { + acctest.TestAccPreCheck(t) + acctest.TestAccPreCheck_Hyperdrive(t) + }, ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, Steps: []resource.TestStep{ { @@ -77,19 +88,19 @@ func TestAccCloudflareHyperdriveConfig_Basic(t *testing.T) { rnd, accountID, rnd, - "password", + databasePassword, origin, caching, ), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr(resourceName, "name", rnd), resource.TestCheckResourceAttr(resourceName, "account_id", accountID), - resource.TestCheckResourceAttr(resourceName, "origin.database", "database"), - resource.TestCheckResourceAttr(resourceName, "origin.host", "host.example.com"), - resource.TestCheckResourceAttr(resourceName, "origin.port", "5432"), + resource.TestCheckResourceAttr(resourceName, "origin.database", databaseName), + resource.TestCheckResourceAttr(resourceName, "origin.host", databaseHostname), + resource.TestCheckResourceAttr(resourceName, "origin.port", databasePort), resource.TestCheckResourceAttr(resourceName, "origin.scheme", "postgres"), - resource.TestCheckResourceAttr(resourceName, "origin.user", "user"), - resource.TestCheckResourceAttr(resourceName, "origin.password", "password"), + resource.TestCheckResourceAttr(resourceName, "origin.user", databaseUser), + resource.TestCheckResourceAttr(resourceName, "origin.password", databasePassword), resource.TestCheckNoResourceAttr(resourceName, "origin.access_client_id"), resource.TestCheckNoResourceAttr(resourceName, "origin.access_client_secret"), resource.TestCheckResourceAttr(resourceName, "caching.disabled", "true"), @@ -113,14 +124,20 @@ func TestAccCloudflareHyperdriveConfig_CachingSettings(t *testing.T) { rnd := utils.GenerateRandomResourceName() accountID := os.Getenv("CLOUDFLARE_ACCOUNT_ID") + databaseName := os.Getenv("CLOUDFLARE_HYPERDRIVE_DATABASE_NAME") + databaseHostname := os.Getenv("CLOUDFLARE_HYPERDRIVE_DATABASE_HOSTNAME") + databasePort := os.Getenv("CLOUDFLARE_HYPERDRIVE_DATABASE_PORT") + port, _ := strconv.Atoi(databasePort) + databaseUser := os.Getenv("CLOUDFLARE_HYPERDRIVE_DATABASE_USER") + databasePassword := os.Getenv("CLOUDFLARE_HYPERDRIVE_DATABASE_PASSWORD") resourceName := "cloudflare_hyperdrive_config." + rnd var origin = cfv1.HyperdriveConfigOrigin{ - Database: "database", - Host: "host.example.com", - Port: 5432, + Database: databaseName, + Host: databaseHostname, + Port: port, Scheme: "postgres", - User: "user", + User: databaseUser, } var disabled = false @@ -132,7 +149,10 @@ func TestAccCloudflareHyperdriveConfig_CachingSettings(t *testing.T) { } resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.TestAccPreCheck(t) }, + PreCheck: func() { + acctest.TestAccPreCheck(t) + acctest.TestAccPreCheck_Hyperdrive(t) + }, ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, Steps: []resource.TestStep{ { @@ -140,19 +160,19 @@ func TestAccCloudflareHyperdriveConfig_CachingSettings(t *testing.T) { rnd, accountID, rnd, - "password", + databasePassword, origin, caching, ), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr(resourceName, "name", rnd), resource.TestCheckResourceAttr(resourceName, "account_id", accountID), - resource.TestCheckResourceAttr(resourceName, "origin.database", "database"), - resource.TestCheckResourceAttr(resourceName, "origin.host", "host.example.com"), - resource.TestCheckResourceAttr(resourceName, "origin.port", "5432"), + resource.TestCheckResourceAttr(resourceName, "origin.database", databaseName), + resource.TestCheckResourceAttr(resourceName, "origin.host", databaseHostname), + resource.TestCheckResourceAttr(resourceName, "origin.port", databasePort), resource.TestCheckResourceAttr(resourceName, "origin.scheme", "postgres"), - resource.TestCheckResourceAttr(resourceName, "origin.user", "user"), - resource.TestCheckResourceAttr(resourceName, "origin.password", "password"), + resource.TestCheckResourceAttr(resourceName, "origin.user", databaseUser), + resource.TestCheckResourceAttr(resourceName, "origin.password", databasePassword), resource.TestCheckNoResourceAttr(resourceName, "origin.access_client_id"), resource.TestCheckNoResourceAttr(resourceName, "origin.access_client_secret"), resource.TestCheckResourceAttr(resourceName, "caching.disabled", "false"), @@ -174,15 +194,23 @@ func TestAccCloudflareHyperdriveConfig_CachingSettings(t *testing.T) { func TestAccCloudflareHyperdriveConfig_HyperdriveOverAccess(t *testing.T) { rnd := utils.GenerateRandomResourceName() accountID := os.Getenv("CLOUDFLARE_ACCOUNT_ID") + databaseName := os.Getenv("CLOUDFLARE_HYPERDRIVE_DATABASE_NAME") + databaseHostname := os.Getenv("CLOUDFLARE_HYPERDRIVE_DATABASE_HOSTNAME") + databaseUser := os.Getenv("CLOUDFLARE_HYPERDRIVE_DATABASE_USER") + databasePassword := os.Getenv("CLOUDFLARE_HYPERDRIVE_DATABASE_PASSWORD") + accessClientID := os.Getenv("CLOUDFLARE_HYPERDRIVE_ACCESS_CLIENT_ID") + accessClientSecret := os.Getenv("CLOUDFLARE_HYPERDRIVE_ACCESS_CLIENT_SECRET") resourceName := "cloudflare_hyperdrive_config." + rnd var origin = cfv1.HyperdriveConfigOriginWithSecrets{ - HyperdriveConfigOrigin: cfv1.HyperdriveConfigOrigin{Database: "database", - Host: "host.example.com", + HyperdriveConfigOrigin: cfv1.HyperdriveConfigOrigin{ + Database: databaseName, + Host: databaseHostname, Scheme: "postgres", - User: "user", - AccessClientID: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.access"}, - AccessClientSecret: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + User: databaseUser, + AccessClientID: accessClientID, + }, + AccessClientSecret: accessClientSecret, } var disabled = true @@ -192,7 +220,10 @@ func TestAccCloudflareHyperdriveConfig_HyperdriveOverAccess(t *testing.T) { } resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.TestAccPreCheck(t) }, + PreCheck: func() { + acctest.TestAccPreCheck(t) + acctest.TestAccPreCheck_HyperdriveWithAccess(t) + }, ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, Steps: []resource.TestStep{ { @@ -200,21 +231,21 @@ func TestAccCloudflareHyperdriveConfig_HyperdriveOverAccess(t *testing.T) { rnd, accountID, rnd, - "password", + databasePassword, origin, caching, ), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr(resourceName, "name", rnd), resource.TestCheckResourceAttr(resourceName, "account_id", accountID), - resource.TestCheckResourceAttr(resourceName, "origin.database", "database"), - resource.TestCheckResourceAttr(resourceName, "origin.host", "host.example.com"), + resource.TestCheckResourceAttr(resourceName, "origin.database", databaseName), + resource.TestCheckResourceAttr(resourceName, "origin.host", databaseHostname), resource.TestCheckNoResourceAttr(resourceName, "origin.port"), resource.TestCheckResourceAttr(resourceName, "origin.scheme", "postgres"), - resource.TestCheckResourceAttr(resourceName, "origin.user", "user"), - resource.TestCheckResourceAttr(resourceName, "origin.password", "password"), - resource.TestCheckResourceAttr(resourceName, "origin.access_client_id", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.access"), - resource.TestCheckResourceAttr(resourceName, "origin.access_client_secret", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"), + resource.TestCheckResourceAttr(resourceName, "origin.user", databaseUser), + resource.TestCheckResourceAttr(resourceName, "origin.password", databasePassword), + resource.TestCheckResourceAttr(resourceName, "origin.access_client_id", accessClientID), + resource.TestCheckResourceAttr(resourceName, "origin.access_client_secret", accessClientSecret), resource.TestCheckResourceAttr(resourceName, "caching.disabled", "true"), ), }, @@ -234,18 +265,27 @@ func TestAccCloudflareHyperdriveConfig_Minimum(t *testing.T) { rnd := utils.GenerateRandomResourceName() accountID := os.Getenv("CLOUDFLARE_ACCOUNT_ID") + databaseName := os.Getenv("CLOUDFLARE_HYPERDRIVE_DATABASE_NAME") + databaseHostname := os.Getenv("CLOUDFLARE_HYPERDRIVE_DATABASE_HOSTNAME") + databasePort := os.Getenv("CLOUDFLARE_HYPERDRIVE_DATABASE_PORT") + port, _ := strconv.Atoi(databasePort) + databaseUser := os.Getenv("CLOUDFLARE_HYPERDRIVE_DATABASE_USER") + databasePassword := os.Getenv("CLOUDFLARE_HYPERDRIVE_DATABASE_PASSWORD") resourceName := "cloudflare_hyperdrive_config." + rnd var origin = cfv1.HyperdriveConfigOrigin{ - Database: "database", - Host: "host.example.com", - Port: 5432, + Database: databaseName, + Host: databaseHostname, + Port: port, Scheme: "postgres", - User: "user", + User: databaseUser, } resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.TestAccPreCheck(t) }, + PreCheck: func() { + acctest.TestAccPreCheck(t) + acctest.TestAccPreCheck_Hyperdrive(t) + }, ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, Steps: []resource.TestStep{ { @@ -253,18 +293,18 @@ func TestAccCloudflareHyperdriveConfig_Minimum(t *testing.T) { rnd, accountID, rnd, - "password", + databasePassword, origin, ), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr(resourceName, "name", rnd), resource.TestCheckResourceAttr(resourceName, "account_id", accountID), - resource.TestCheckResourceAttr(resourceName, "origin.database", "database"), - resource.TestCheckResourceAttr(resourceName, "origin.host", "host.example.com"), - resource.TestCheckResourceAttr(resourceName, "origin.port", "5432"), + resource.TestCheckResourceAttr(resourceName, "origin.database", databaseName), + resource.TestCheckResourceAttr(resourceName, "origin.host", databaseHostname), + resource.TestCheckResourceAttr(resourceName, "origin.port", databasePort), resource.TestCheckResourceAttr(resourceName, "origin.scheme", "postgres"), - resource.TestCheckResourceAttr(resourceName, "origin.user", "user"), - resource.TestCheckResourceAttr(resourceName, "origin.password", "password"), + resource.TestCheckResourceAttr(resourceName, "origin.user", databaseUser), + resource.TestCheckResourceAttr(resourceName, "origin.password", databasePassword), resource.TestCheckResourceAttr(resourceName, "caching.disabled", "false"), ), }, From a88cf723bf1a40dcaee9244a3b71617739ae8065 Mon Sep 17 00:00:00 2001 From: Jacob Bednarz Date: Mon, 29 Jul 2024 12:49:42 +1000 Subject: [PATCH 25/27] `make docs` --- docs/resources/hyperdrive_config.md | 9 ++++++++- internal/framework/service/hyperdrive_config/schema.go | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/docs/resources/hyperdrive_config.md b/docs/resources/hyperdrive_config.md index fd50dd894e..29776ea0ea 100644 --- a/docs/resources/hyperdrive_config.md +++ b/docs/resources/hyperdrive_config.md @@ -47,10 +47,15 @@ Required: - `database` (String) The name of your origin database. - `host` (String) The host (hostname or IP) of your origin database. - `password` (String, Sensitive) The password of the Hyperdrive configuration. -- `port` (Number) The port (default: 5432 for Postgres) of your origin database. - `scheme` (String) Specifies the URL scheme used to connect to your origin database. - `user` (String) The user of your origin database. +Optional: + +- `access_client_id` (String) Client ID associated with the Cloudflare Access Service Token used to connect via Access. +- `access_client_secret` (String) Client Secret associated with the Cloudflare Access Service Token used to connect via Access. +- `port` (Number) The port (default: 5432 for Postgres) of your origin database. + ### Nested Schema for `caching` @@ -58,6 +63,8 @@ Required: Optional: - `disabled` (Boolean) Disable caching for this Hyperdrive configuration. +- `max_age` (Number) Configure the `max_age` value of this Hyperdrive configuration. +- `stale_while_revalidate` (Number) Disable caching for this Hyperdrive configuration. ## Import diff --git a/internal/framework/service/hyperdrive_config/schema.go b/internal/framework/service/hyperdrive_config/schema.go index d718f16f39..0fc45e39ef 100644 --- a/internal/framework/service/hyperdrive_config/schema.go +++ b/internal/framework/service/hyperdrive_config/schema.go @@ -100,7 +100,7 @@ func (r *HyperdriveConfigResource) Schema(ctx context.Context, req resource.Sche Computed: true, }, "max_age": schema.Int64Attribute{ - MarkdownDescription: "Disable caching for this Hyperdrive configuration.", + MarkdownDescription: "Configure the `max_age` value of this Hyperdrive configuration.", Optional: true, Validators: []validator.Int64{ int64validator.AtLeast(0), From d3b6ab0b4b349753770e1871fee4aac84c3ba6b5 Mon Sep 17 00:00:00 2001 From: Jacob Bednarz Date: Mon, 29 Jul 2024 15:03:34 +1000 Subject: [PATCH 26/27] test: fix cloudflare_record assertions --- internal/sdkv2provider/data_source_record_test.go | 8 ++++---- ...rce_cloudflare_custom_hostname_fallback_origin_test.go | 6 +++--- .../resource_cloudflare_custom_hostname_test.go | 4 ++-- .../resource_cloudflare_spectrum_application_test.go | 4 ++-- .../resource_cloudflare_waiting_room_test.go | 4 ++-- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/internal/sdkv2provider/data_source_record_test.go b/internal/sdkv2provider/data_source_record_test.go index bb83ee92b6..cd53157888 100644 --- a/internal/sdkv2provider/data_source_record_test.go +++ b/internal/sdkv2provider/data_source_record_test.go @@ -97,7 +97,7 @@ resource "cloudflare_record" "%[1]s" { zone_id = "%[2]s" type = "A" name = "%[1]s.%[3]s" - value = "192.0.2.0" + content = "192.0.2.0" }`, rnd, zoneID, domain) } @@ -112,7 +112,7 @@ resource "cloudflare_record" "%[1]s" { zone_id = "%[2]s" type = "TXT" name = "%[1]s.%[3]s" - value = "i am a text record" + content = "i am a text record" }`, rnd, zoneID, domain) } @@ -128,14 +128,14 @@ resource "cloudflare_record" "%[1]s" { zone_id = "%[2]s" type = "MX" name = "%[1]s.%[3]s" - value = "mx1.example.com" + content = "mx1.example.com" priority = 10 } resource "cloudflare_record" "%[1]s_2" { zone_id = "%[2]s" type = "MX" name = "%[1]s.%[3]s" - value = "mx1.example.com" + content = "mx1.example.com" priority = 20 } `, rnd, zoneID, domain) diff --git a/internal/sdkv2provider/resource_cloudflare_custom_hostname_fallback_origin_test.go b/internal/sdkv2provider/resource_cloudflare_custom_hostname_fallback_origin_test.go index 77ac5ace8f..dc1800c789 100644 --- a/internal/sdkv2provider/resource_cloudflare_custom_hostname_fallback_origin_test.go +++ b/internal/sdkv2provider/resource_cloudflare_custom_hostname_fallback_origin_test.go @@ -12,7 +12,7 @@ import ( "github.com/hashicorp/terraform-plugin-testing/terraform" ) -func TestAccCloudflareCustomHostnameFallbackOrigin(t *testing.T) { +func TestAccCloudflareCustomHostname_FallbackOrigin(t *testing.T) { // Temporarily unset CLOUDFLARE_API_TOKEN if it is set as the custom hostname // fallback endpoint does not yet support the API tokens for updates and it // results in state error messages. @@ -50,7 +50,7 @@ resource "cloudflare_custom_hostname_fallback_origin" "%[2]s" { resource "cloudflare_record" "%[2]s" { zone_id = "%[1]s" name = "fallback-origin.%[2]s.%[4]s" - value = "example.com" + content = "example.com" type = "CNAME" proxied = true ttl = 1 @@ -58,7 +58,7 @@ resource "cloudflare_record" "%[2]s" { }`, zoneID, rnd, subdomain, domain) } -func TestAccCloudflareCustomHostnameFallbackOriginUpdate(t *testing.T) { +func TestAccCloudflareCustomHostname_FallbackOriginUpdate(t *testing.T) { // Temporarily unset CLOUDFLARE_API_TOKEN if it is set as the custom hostname // fallback endpoint does not yet support the API tokens for updates and it // results in state error messages. diff --git a/internal/sdkv2provider/resource_cloudflare_custom_hostname_test.go b/internal/sdkv2provider/resource_cloudflare_custom_hostname_test.go index a50eb2c5b0..bc8d015b2d 100644 --- a/internal/sdkv2provider/resource_cloudflare_custom_hostname_test.go +++ b/internal/sdkv2provider/resource_cloudflare_custom_hostname_test.go @@ -220,7 +220,7 @@ resource "cloudflare_custom_hostname" "%[2]s" { resource "cloudflare_record" "%[2]s" { zone_id = "%[1]s" name = "origin.%[2]s.terraform.cfapi.net" - value = "example.com" + content = "example.com" type = "CNAME" ttl = 3600 }`, zoneID, rnd, domain) @@ -283,7 +283,7 @@ func TestAccCloudflareCustomHostname_WithCustomSSLSettings(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "ssl.0.settings.0.http2", "off"), resource.TestCheckResourceAttr(resourceName, "ssl.0.settings.0.min_tls_version", "1.2"), resource.TestCheckResourceAttr(resourceName, "ssl.0.settings.0.ciphers.#", "2"), - resource.TestCheckResourceAttr(resourceName, "ssl.0.certificate_authority", "lets_encrypt"), + resource.TestCheckResourceAttr(resourceName, "ssl.0.certificate_authority", "google"), resource.TestCheckResourceAttrSet(resourceName, "ownership_verification.value"), resource.TestCheckResourceAttrSet(resourceName, "ownership_verification.type"), resource.TestCheckResourceAttrSet(resourceName, "ownership_verification.name"), diff --git a/internal/sdkv2provider/resource_cloudflare_spectrum_application_test.go b/internal/sdkv2provider/resource_cloudflare_spectrum_application_test.go index 7e1c355a3b..6c5ceca559 100644 --- a/internal/sdkv2provider/resource_cloudflare_spectrum_application_test.go +++ b/internal/sdkv2provider/resource_cloudflare_spectrum_application_test.go @@ -426,7 +426,7 @@ func testAccCheckCloudflareSpectrumApplicationConfigOriginDNS(zoneID, zoneName, resource "cloudflare_record" "%[3]s" { zone_id = "%[1]s" name = "%[3]s.origin" - value = "example.com" + content = "example.com" type = "CNAME" ttl = 3600 } @@ -458,7 +458,7 @@ func testAccCheckCloudflareSpectrumApplicationConfigOriginPortRange(zoneID, zone resource "cloudflare_record" "%[3]s" { zone_id = "%[1]s" name = "%[3]s.origin" - value = "example.com" + content = "example.com" type = "CNAME" ttl = 3600 } diff --git a/internal/sdkv2provider/resource_cloudflare_waiting_room_test.go b/internal/sdkv2provider/resource_cloudflare_waiting_room_test.go index 248e1fdc32..2f66d939ed 100644 --- a/internal/sdkv2provider/resource_cloudflare_waiting_room_test.go +++ b/internal/sdkv2provider/resource_cloudflare_waiting_room_test.go @@ -108,7 +108,7 @@ func testAccCloudflareWaitingRoom(resourceName, waitingRoomName, zoneID, domain, resource "cloudflare_record" "%[1]s-shop-1" { zone_id = "%[3]s" name = "shop1" - value = "192.168.0.10" + content = "192.168.0.10" type = "A" ttl = 3600 } @@ -116,7 +116,7 @@ func testAccCloudflareWaitingRoom(resourceName, waitingRoomName, zoneID, domain, resource "cloudflare_record" "%[1]s-shop-2" { zone_id = "%[3]s" name = "shop2" - value = "192.168.0.11" + content = "192.168.0.11" type = "A" ttl = 3600 } From 00727d25c14af89ececeaf954da6e0264641bb62 Mon Sep 17 00:00:00 2001 From: Jacob Bednarz Date: Tue, 30 Jul 2024 08:20:18 +1000 Subject: [PATCH 27/27] Update 3463.txt --- .changelog/3463.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changelog/3463.txt b/.changelog/3463.txt index bf0d6ea3b2..f9e06659f2 100644 --- a/.changelog/3463.txt +++ b/.changelog/3463.txt @@ -1,3 +1,3 @@ ```release-note:bug -fix resource/cloudflare_risk_behavior bug where partial definition of risk behaviors resulted in a provider error +resource/cloudflare_risk_behavior: fix bug where partial definition of risk behaviors resulted in a provider error ```