Skip to content

Commit 4f5714b

Browse files
Sui Makmgirouard
authored andcommitted
fix: set computed_optional on various fields since these fields are always returned to prevent update in place
1 parent 0d651a4 commit 4f5714b

File tree

8 files changed

+189
-19
lines changed

8 files changed

+189
-19
lines changed

internal/services/zone_subscription/model.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ type ZoneSubscriptionResultEnvelope struct {
1515
type ZoneSubscriptionModel struct {
1616
ID types.String `tfsdk:"id" json:"-,computed"`
1717
ZoneID types.String `tfsdk:"zone_id" path:"zone_id,required"`
18-
Frequency types.String `tfsdk:"frequency" json:"frequency,optional"`
18+
Frequency types.String `tfsdk:"frequency" json:"frequency, computed_optional"`
1919
RatePlan *ZoneSubscriptionRatePlanModel `tfsdk:"rate_plan" json:"rate_plan,optional"`
2020
Currency types.String `tfsdk:"currency" json:"currency,computed"`
2121
CurrentPeriodEnd timetypes.RFC3339 `tfsdk:"current_period_end" json:"current_period_end,computed" format:"date-time"`
@@ -34,10 +34,10 @@ func (m ZoneSubscriptionModel) MarshalJSONForUpdate(state ZoneSubscriptionModel)
3434

3535
type ZoneSubscriptionRatePlanModel struct {
3636
ID types.String `tfsdk:"id" json:"id,optional"`
37-
Currency types.String `tfsdk:"currency" json:"currency,optional"`
38-
ExternallyManaged types.Bool `tfsdk:"externally_managed" json:"externally_managed,optional"`
39-
IsContract types.Bool `tfsdk:"is_contract" json:"is_contract,optional"`
40-
PublicName types.String `tfsdk:"public_name" json:"public_name,optional"`
41-
Scope types.String `tfsdk:"scope" json:"scope,optional"`
37+
Currency types.String `tfsdk:"currency" json:"currency,computed_optional"`
38+
ExternallyManaged types.Bool `tfsdk:"externally_managed" json:"externally_managed,computed_optional"`
39+
IsContract types.Bool `tfsdk:"is_contract" json:"is_contract,computed_optional"`
40+
PublicName types.String `tfsdk:"public_name" json:"public_name,computed_optional"`
41+
Scope types.String `tfsdk:"scope" json:"scope,computed_optional"`
4242
Sets *[]types.String `tfsdk:"sets" json:"sets,optional"`
4343
}

internal/services/zone_subscription/resource_test.go

Lines changed: 142 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,40 +2,75 @@ package zone_subscription_test
22

33
import (
44
"os"
5+
"regexp"
56
"testing"
67

78
"github.com/cloudflare/terraform-provider-cloudflare/internal/acctest"
89
"github.com/cloudflare/terraform-provider-cloudflare/internal/utils"
910
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
1011
"github.com/hashicorp/terraform-plugin-testing/knownvalue"
12+
"github.com/hashicorp/terraform-plugin-testing/plancheck"
1113
"github.com/hashicorp/terraform-plugin-testing/statecheck"
1214
"github.com/hashicorp/terraform-plugin-testing/tfjsonpath"
1315
)
1416

15-
// NOTE: No sweeper is needed for zone_subscription as the resource cannot be deleted
17+
// https://github.com/cloudflare/terraform-provider-cloudflare/issues/5971
18+
func TestAccCloudflareZoneSubscription_ImportNoChanges(t *testing.T) {
19+
rnd := utils.GenerateRandomResourceName()
20+
zoneID := os.Getenv("CLOUDFLARE_ZONE_ID")
21+
resourceName := "cloudflare_zone_subscription." + rnd
22+
23+
resource.Test(t, resource.TestCase{
24+
PreCheck: func() {
25+
acctest.TestAccPreCheck(t)
26+
acctest.TestAccPreCheck_ZoneID(t)
27+
},
28+
ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories,
29+
Steps: []resource.TestStep{
30+
// First create a basic configuration
31+
{
32+
Config: testAccCloudflareZoneSubscriptionImportConfig(rnd, zoneID),
33+
Check: resource.ComposeTestCheckFunc(
34+
resource.TestCheckResourceAttr(resourceName, "zone_id", zoneID),
35+
),
36+
},
37+
// Then import the resource and verify it
38+
{
39+
ResourceName: resourceName,
40+
ImportState: true,
41+
ImportStateVerify: true,
42+
ImportStateVerifyIgnore: []string{"current_period_end", "current_period_start"},
43+
},
44+
},
45+
})
46+
}
1647

1748
func TestAccCloudflareZoneSubscription_Basic(t *testing.T) {
18-
t.Skip("Step 1/3 error: After applying this test step, the refresh plan was not empty.")
1949
rnd := utils.GenerateRandomResourceName()
2050
zoneID := os.Getenv("CLOUDFLARE_ZONE_ID")
2151
resourceName := "cloudflare_zone_subscription." + rnd
2252

2353
resource.Test(t, resource.TestCase{
24-
PreCheck: func() { acctest.TestAccPreCheck(t) },
54+
PreCheck: func() {
55+
acctest.TestAccPreCheck(t)
56+
acctest.TestAccPreCheck_ZoneID(t)
57+
},
2558
ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories,
26-
// No CheckDestroy as zone_subscription cannot be deleted
2759
Steps: []resource.TestStep{
2860
{
2961
Config: testAccCloudflareZoneSubscriptionConfig(rnd, zoneID),
3062
ConfigStateChecks: []statecheck.StateCheck{
3163
statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("zone_id"), knownvalue.StringExact(zoneID)),
3264
statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("id"), knownvalue.StringExact(zoneID)),
33-
// Note: rate_plan might be nested differently, skip for now
34-
// Verify computed attributes exist
3565
statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("currency"), knownvalue.NotNull()),
3666
statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("state"), knownvalue.NotNull()),
3767
statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("price"), knownvalue.NotNull()),
38-
// current_period_end and current_period_start might not be set for enterprise plans
68+
statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("frequency"), knownvalue.StringExact("not-applicable")),
69+
},
70+
ConfigPlanChecks: resource.ConfigPlanChecks{
71+
PostApplyPostRefresh: []plancheck.PlanCheck{
72+
plancheck.ExpectEmptyPlan(), // Should show no changes
73+
},
3974
},
4075
},
4176
{
@@ -45,6 +80,12 @@ func TestAccCloudflareZoneSubscription_Basic(t *testing.T) {
4580
// Verify computed attributes still exist
4681
statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("currency"), knownvalue.NotNull()),
4782
statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("state"), knownvalue.NotNull()),
83+
statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("frequency"), knownvalue.StringExact("not-applicable")),
84+
},
85+
ConfigPlanChecks: resource.ConfigPlanChecks{
86+
PostApplyPostRefresh: []plancheck.PlanCheck{
87+
plancheck.ExpectEmptyPlan(), // Should show no changes
88+
},
4889
},
4990
},
5091
{
@@ -57,35 +98,113 @@ func TestAccCloudflareZoneSubscription_Basic(t *testing.T) {
5798
}
5899

59100
func TestAccCloudflareZoneSubscription_WithPlanChange(t *testing.T) {
60-
t.Skip("Step 1/4 error: After applying this test step, the refresh plan was not empty.")
61101
rnd := utils.GenerateRandomResourceName()
62102
zoneID := os.Getenv("CLOUDFLARE_ZONE_ID")
63103
resourceName := "cloudflare_zone_subscription." + rnd
64104

65105
resource.Test(t, resource.TestCase{
66-
PreCheck: func() { acctest.TestAccPreCheck(t) },
106+
PreCheck: func() {
107+
acctest.TestAccPreCheck(t)
108+
acctest.TestAccPreCheck_ZoneID(t)
109+
},
67110
ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories,
68111
Steps: []resource.TestStep{
69112
{
70113
Config: testAccCloudflareZoneSubscriptionWithPlan(rnd, zoneID, "enterprise"),
71114
Check: resource.ComposeTestCheckFunc(
72115
resource.TestCheckResourceAttr(resourceName, "zone_id", zoneID),
73116
resource.TestCheckResourceAttr(resourceName, "rate_plan.id", "enterprise"),
117+
resource.TestCheckResourceAttr(resourceName, "frequency", "not-applicable"),
74118
),
119+
ConfigPlanChecks: resource.ConfigPlanChecks{
120+
PostApplyPostRefresh: []plancheck.PlanCheck{
121+
plancheck.ExpectEmptyPlan(), // Should show no changes
122+
},
123+
},
75124
},
76125
{
77126
Config: testAccCloudflareZoneSubscriptionWithPlan(rnd, zoneID, "free"),
78127
Check: resource.ComposeTestCheckFunc(
79128
resource.TestCheckResourceAttr(resourceName, "zone_id", zoneID),
80129
resource.TestCheckResourceAttr(resourceName, "rate_plan.id", "free"),
130+
resource.TestCheckResourceAttr(resourceName, "frequency", "not-applicable"),
81131
),
132+
ConfigPlanChecks: resource.ConfigPlanChecks{
133+
PostApplyPostRefresh: []plancheck.PlanCheck{
134+
plancheck.ExpectEmptyPlan(), // Should show no changes
135+
},
136+
},
82137
},
83138
{
84-
Config: testAccCloudflareZoneSubscriptionWithPlan(rnd, zoneID, "business"),
139+
ResourceName: resourceName,
140+
ImportState: true,
141+
ImportStateVerify: true,
142+
},
143+
},
144+
})
145+
}
146+
147+
func TestAccCloudflareZoneSubscription_WithPlanAndComputedOptionalRatePlanFields(t *testing.T) {
148+
rnd := utils.GenerateRandomResourceName()
149+
zoneID := os.Getenv("CLOUDFLARE_ZONE_ID")
150+
resourceName := "cloudflare_zone_subscription." + rnd
151+
152+
resource.Test(t, resource.TestCase{
153+
PreCheck: func() {
154+
acctest.TestAccPreCheck(t)
155+
acctest.TestAccPreCheck_ZoneID(t)
156+
},
157+
ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories,
158+
Steps: []resource.TestStep{
159+
{
160+
Config: testAccCloudflareZoneSubscriptionWithPlanAndComputedOptionalRatePlanFields(rnd, zoneID),
85161
Check: resource.ComposeTestCheckFunc(
86162
resource.TestCheckResourceAttr(resourceName, "zone_id", zoneID),
87-
resource.TestCheckResourceAttr(resourceName, "rate_plan.id", "business"),
163+
resource.TestCheckResourceAttr(resourceName, "frequency", "not-applicable"),
164+
resource.TestCheckResourceAttr(resourceName, "rate_plan.id", "enterprise"),
165+
resource.TestCheckResourceAttr(resourceName, "rate_plan.currency", "USD"),
166+
resource.TestCheckResourceAttr(resourceName, "rate_plan.is_contract", "true"),
167+
resource.TestCheckResourceAttr(resourceName, "rate_plan.public_name", "Enterprise"),
168+
resource.TestCheckResourceAttr(resourceName, "rate_plan.externally_managed", "false"),
169+
resource.TestCheckResourceAttr(resourceName, "rate_plan.scope", "zone"),
88170
),
171+
ConfigPlanChecks: resource.ConfigPlanChecks{
172+
PostApplyPostRefresh: []plancheck.PlanCheck{
173+
plancheck.ExpectEmptyPlan(),
174+
},
175+
},
176+
},
177+
{
178+
ResourceName: resourceName,
179+
ImportState: true,
180+
ImportStateVerify: true,
181+
},
182+
},
183+
})
184+
}
185+
186+
// Free and Enterprise plans and whenfrequency is provided
187+
// will return Error: Provider produced inconsistent result after apply
188+
189+
func TestAccCloudflareZoneSubscription_WithPlanThatReturnsNotApplicableFrequencyResultingInError(t *testing.T) {
190+
rnd := utils.GenerateRandomResourceName()
191+
zoneID := os.Getenv("CLOUDFLARE_ZONE_ID")
192+
resourceName := "cloudflare_zone_subscription." + rnd
193+
194+
resource.Test(t, resource.TestCase{
195+
PreCheck: func() {
196+
acctest.TestAccPreCheck(t)
197+
acctest.TestAccPreCheck_ZoneID(t)
198+
},
199+
ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories,
200+
Steps: []resource.TestStep{
201+
{
202+
Config: testAccCloudflareZoneSubscriptionWithPlanAndFrequency(rnd, zoneID, "free", "monthly"),
203+
ExpectError: regexp.MustCompile(regexp.QuoteMeta("Error: Provider produced inconsistent result after apply")),
204+
},
205+
{
206+
Config: testAccCloudflareZoneSubscriptionWithPlanAndFrequency(rnd, zoneID, "enterprise", "monthly"),
207+
ExpectError: regexp.MustCompile(regexp.QuoteMeta("Error: Provider produced inconsistent result after apply")),
89208
},
90209
{
91210
ResourceName: resourceName,
@@ -107,3 +226,15 @@ func testAccCloudflareZoneSubscriptionConfigUpdate(rnd, zoneID string) string {
107226
func testAccCloudflareZoneSubscriptionWithPlan(rnd, zoneID, plan string) string {
108227
return acctest.LoadTestCase("with_plan.tf", rnd, zoneID, plan)
109228
}
229+
230+
func testAccCloudflareZoneSubscriptionWithPlanAndFrequency(rnd, zoneID, plan, frequency string) string {
231+
return acctest.LoadTestCase("with_plan_and_frequency.tf", rnd, zoneID, plan, frequency)
232+
}
233+
234+
func testAccCloudflareZoneSubscriptionImportConfig(rnd, zoneID string) string {
235+
return acctest.LoadTestCase("import_config.tf", rnd, zoneID)
236+
}
237+
238+
func testAccCloudflareZoneSubscriptionWithPlanAndComputedOptionalRatePlanFields(rnd, zoneID string) string {
239+
return acctest.LoadTestCase("with_plan_and_computed_optional_rate_plan_fields.tf", rnd, zoneID)
240+
}

internal/services/zone_subscription/schema.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,17 @@ func ResourceSchema(ctx context.Context) schema.Schema {
3333
"frequency": schema.StringAttribute{
3434
Description: "How often the subscription is renewed automatically.\nAvailable values: \"weekly\", \"monthly\", \"quarterly\", \"yearly\".",
3535
Optional: true,
36+
Computed: true,
37+
PlanModifiers: []planmodifier.String{
38+
stringplanmodifier.UseStateForUnknown(),
39+
},
3640
Validators: []validator.String{
3741
stringvalidator.OneOfCaseInsensitive(
3842
"weekly",
3943
"monthly",
4044
"quarterly",
4145
"yearly",
46+
"not-applicable",
4247
),
4348
},
4449
},
@@ -66,22 +71,27 @@ func ResourceSchema(ctx context.Context) schema.Schema {
6671
},
6772
"currency": schema.StringAttribute{
6873
Description: "The currency applied to the rate plan subscription.",
74+
Computed: true,
6975
Optional: true,
7076
},
7177
"externally_managed": schema.BoolAttribute{
7278
Description: "Whether this rate plan is managed externally from Cloudflare.",
79+
Computed: true,
7380
Optional: true,
7481
},
7582
"is_contract": schema.BoolAttribute{
7683
Description: "Whether a rate plan is enterprise-based (or newly adopted term contract).",
84+
Computed: true,
7785
Optional: true,
7886
},
7987
"public_name": schema.StringAttribute{
8088
Description: "The full name of the rate plan.",
89+
Computed: true,
8190
Optional: true,
8291
},
8392
"scope": schema.StringAttribute{
8493
Description: "The scope that this rate plan applies to.",
94+
Computed: true,
8595
Optional: true,
8696
},
8797
"sets": schema.ListAttribute{
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
resource "cloudflare_zone_subscription" "%[1]s" {
22
zone_id = "%[2]s"
3-
3+
44
# Keep enterprise plan but test that we can manage the resource
55
rate_plan = {
66
id = "enterprise"
77
}
8-
8+
99
# For enterprise plans, frequency might not be changeable
1010
# so we're just verifying the resource can be managed
1111
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
resource "cloudflare_zone_subscription" "%[1]s" {
2+
zone_id = "%[2]s"
3+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
resource "cloudflare_zone_subscription" "%[1]s" {
2+
zone_id = "%[2]s"
3+
rate_plan = {
4+
id = "free"
5+
}
6+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
resource "cloudflare_zone_subscription" "%[1]s" {
2+
zone_id = "%[2]s"
3+
4+
rate_plan = {
5+
id = "enterprise"
6+
currency = "USD"
7+
externally_managed = false
8+
is_contract = true
9+
public_name = "Enterprise"
10+
scope = "zone"
11+
}
12+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
resource "cloudflare_zone_subscription" "%[1]s" {
2+
zone_id = "%[2]s"
3+
4+
rate_plan = {
5+
id = "%[3]s"
6+
}
7+
frequency = "%[4]s"
8+
}

0 commit comments

Comments
 (0)