Skip to content

Commit 3335ef6

Browse files
committed
vpc_firewall_rules: fix provider produced invalid plan
Added acceptance tests to surface the issue reported in #453. See #453 (comment) for an understanding of what's happening. Removed the computed attributes from the firewall rules since those computed fields were being updated for all of the rules, even when Terraform did not expect a rule to be updated. Unfortunately all of the computed attributes had to be removed for this to work and keep the update in-place logic.
1 parent 4336e1e commit 3335ef6

File tree

3 files changed

+251
-186
lines changed

3 files changed

+251
-186
lines changed

.changelog/0.11.0.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[[breaking]]
2-
title = ""
3-
description = ""
2+
title = "Removed computed attributes from `vpc_firewall_rules`"
3+
description = "Removed all computed attributes from the `rules` attribute within the `vpc_firewall_rules` resource to fix an invalid plan error while maintaining in-place update of VPC firewall rules. [#456](https://github.com/oxidecomputer/terraform-provider-oxide/pull/456)"
44

55
[[features]]
66
title = ""
@@ -12,4 +12,4 @@ description = ""
1212

1313
[[bugs]]
1414
title = ""
15-
description = ""
15+
description = ""

internal/provider/resource_vpc_firewall_rules.go

Lines changed: 54 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -44,24 +44,30 @@ type vpcFirewallRulesResource struct {
4444

4545
type vpcFirewallRulesResourceModel struct {
4646
// This ID is specific to Terraform only
47-
ID types.String `tfsdk:"id"`
48-
Rules []vpcFirewallRulesResourceRuleModel `tfsdk:"rules"`
49-
Timeouts timeouts.Value `tfsdk:"timeouts"`
50-
VPCID types.String `tfsdk:"vpc_id"`
47+
ID types.String `tfsdk:"id"`
48+
Rules []vpcFirewallRulesResourceRuleModel `tfsdk:"rules"`
49+
Timeouts timeouts.Value `tfsdk:"timeouts"`
50+
VPCID types.String `tfsdk:"vpc_id"`
51+
52+
// Populated from the same fields within [vpcFirewallRulesResourceRuleModel].
53+
TimeCreated types.String `tfsdk:"time_created"`
54+
TimeModified types.String `tfsdk:"time_modified"`
5155
}
5256

5357
type vpcFirewallRulesResourceRuleModel struct {
54-
Action types.String `tfsdk:"action"`
55-
Description types.String `tfsdk:"description"`
56-
Direction types.String `tfsdk:"direction"`
57-
Filters *vpcFirewallRulesResourceRuleFiltersModel `tfsdk:"filters"`
58-
ID types.String `tfsdk:"id"`
59-
Name types.String `tfsdk:"name"`
60-
Priority types.Int64 `tfsdk:"priority"`
61-
Status types.String `tfsdk:"status"`
62-
Targets []vpcFirewallRulesResourceRuleTargetModel `tfsdk:"targets"`
63-
TimeCreated types.String `tfsdk:"time_created"`
64-
TimeModified types.String `tfsdk:"time_modified"`
58+
Action types.String `tfsdk:"action"`
59+
Description types.String `tfsdk:"description"`
60+
Direction types.String `tfsdk:"direction"`
61+
Filters *vpcFirewallRulesResourceRuleFiltersModel `tfsdk:"filters"`
62+
Name types.String `tfsdk:"name"`
63+
Priority types.Int64 `tfsdk:"priority"`
64+
Status types.String `tfsdk:"status"`
65+
Targets []vpcFirewallRulesResourceRuleTargetModel `tfsdk:"targets"`
66+
67+
// Used to retrieve the timestamps from the API and populate the same fields
68+
// within [vpcFirewallRulesResourceModel].
69+
TimeCreated types.String `tfsdk:"-"`
70+
TimeModified types.String `tfsdk:"-"`
6571
}
6672

6773
type vpcFirewallRulesResourceRuleTargetModel struct {
@@ -110,6 +116,10 @@ func (r *vpcFirewallRulesResource) Schema(ctx context.Context, _ resource.Schema
110116
stringplanmodifier.RequiresReplace(),
111117
},
112118
},
119+
// The rules attribute cannot contain computed attributes since the upstream API
120+
// returns updated attributes for every rule, irrespective of which rules actually
121+
// change. See https://github.com/oxidecomputer/terraform-provider-oxide/issues/453
122+
// for more information.
113123
"rules": schema.SetNestedAttribute{
114124
Required: true,
115125
Description: "Associated firewall rules.",
@@ -202,10 +212,6 @@ func (r *vpcFirewallRulesResource) Schema(ctx context.Context, _ resource.Schema
202212
},
203213
},
204214
},
205-
"id": schema.StringAttribute{
206-
Computed: true,
207-
Description: "Unique, immutable, system-controlled identifier of the firewall rule.",
208-
},
209215
"name": schema.StringAttribute{
210216
Required: true,
211217
Description: "Name of the VPC firewall rule.",
@@ -255,14 +261,6 @@ func (r *vpcFirewallRulesResource) Schema(ctx context.Context, _ resource.Schema
255261
},
256262
},
257263
},
258-
"time_created": schema.StringAttribute{
259-
Computed: true,
260-
Description: "Timestamp of when this VPC firewall rule was created.",
261-
},
262-
"time_modified": schema.StringAttribute{
263-
Computed: true,
264-
Description: "Timestamp of when this VPC firewall rule was last modified.",
265-
},
266264
},
267265
},
268266
},
@@ -276,6 +274,14 @@ func (r *vpcFirewallRulesResource) Schema(ctx context.Context, _ resource.Schema
276274
Computed: true,
277275
Description: "Unique, immutable, system-controlled identifier of the firewall rules. Specific only to Terraform.",
278276
},
277+
"time_created": schema.StringAttribute{
278+
Computed: true,
279+
Description: "Timestamp of when the VPC firewall rules were last created.",
280+
},
281+
"time_modified": schema.StringAttribute{
282+
Computed: true,
283+
Description: "Timestamp of when the VPC firewall rules were last modified.",
284+
},
279285
},
280286
}
281287
}
@@ -327,6 +333,13 @@ func (r *vpcFirewallRulesResource) Create(ctx context.Context, req resource.Crea
327333
return
328334
}
329335

336+
plan.TimeCreated = types.StringNull()
337+
plan.TimeModified = types.StringNull()
338+
if len(plan.Rules) > 0 {
339+
plan.TimeCreated = plan.Rules[0].TimeCreated
340+
plan.TimeModified = plan.Rules[0].TimeModified
341+
}
342+
330343
// Save plan into Terraform state
331344
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
332345
if resp.Diagnostics.HasError() {
@@ -384,6 +397,13 @@ func (r *vpcFirewallRulesResource) Read(ctx context.Context, req resource.ReadRe
384397

385398
state.Rules = rules
386399

400+
state.TimeCreated = types.StringNull()
401+
state.TimeModified = types.StringNull()
402+
if len(state.Rules) > 0 {
403+
state.TimeCreated = state.Rules[0].TimeCreated
404+
state.TimeModified = state.Rules[0].TimeModified
405+
}
406+
387407
// Save updated data into Terraform state
388408
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
389409
if resp.Diagnostics.HasError() {
@@ -447,6 +467,13 @@ func (r *vpcFirewallRulesResource) Update(ctx context.Context, req resource.Upda
447467
return
448468
}
449469

470+
plan.TimeCreated = types.StringNull()
471+
plan.TimeModified = types.StringNull()
472+
if len(plan.Rules) > 0 {
473+
plan.TimeCreated = plan.Rules[0].TimeCreated
474+
plan.TimeModified = plan.Rules[0].TimeModified
475+
}
476+
450477
// Save plan into Terraform state
451478
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
452479
if resp.Diagnostics.HasError() {
@@ -536,7 +563,6 @@ func newVPCFirewallRulesModel(rules []oxide.VpcFirewallRule) ([]vpcFirewallRules
536563
Action: types.StringValue(string(rule.Action)),
537564
Description: types.StringValue(rule.Description),
538565
Direction: types.StringValue(string(rule.Direction)),
539-
ID: types.StringValue(rule.Id),
540566
Name: types.StringValue(string(rule.Name)),
541567
// We can safely dereference rule.Priority as it's a required field
542568
Priority: types.Int64Value(int64(*rule.Priority)),

0 commit comments

Comments
 (0)