Skip to content

Commit d93050b

Browse files
authored
vpc_firewall_rules: fix provider produced invalid plan (#456)
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 d93050b

File tree

4 files changed

+250
-188
lines changed

4 files changed

+250
-188
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 = ""

docs/resources/oxide_vpc_firewall_rules.md

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ resource "oxide_vpc_firewall_rules" "example" {
6161
### Read-Only
6262

6363
- `id` (String) Unique, immutable, system-controlled identifier of the firewall rules. Specific only to Terraform.
64+
- `time_created` (String) Timestamp of when the VPC firewall rules were created.
65+
- `time_modified` (String) Timestamp of when the VPC firewall rules were last modified.
6466

6567
<a id="nestedatt--rules"></a>
6668

@@ -77,12 +79,6 @@ Required:
7779
- `status` (String) Whether this rule is in effect. Possible values are: enabled or disabled.
7880
- `targets` (Set) Sets of instances that the rule applies to. (see [below for nested schema](#nestedatt--targets))
7981

80-
Read-Only:
81-
82-
- `id` (String) Unique, immutable, system-controlled identifier of the firewall rule.
83-
- `time_created` (String) Timestamp of when this firewall rule was created.
84-
- `time_modified` (String) Timestamp of when this firewall rule was last modified.
85-
8682
<a id="nestedatt--filters"></a>
8783

8884
### Nested Schema for `filters`

internal/provider/resource_vpc_firewall_rules.go

Lines changed: 51 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -48,20 +48,27 @@ type vpcFirewallRulesResourceModel struct {
4848
Rules []vpcFirewallRulesResourceRuleModel `tfsdk:"rules"`
4949
Timeouts timeouts.Value `tfsdk:"timeouts"`
5050
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]. The `tfsdk:"-"` struct field tag is used
69+
// to tell Terraform not to populate these values in the schema.
70+
TimeCreated types.String `tfsdk:"-"`
71+
TimeModified types.String `tfsdk:"-"`
6572
}
6673

6774
type vpcFirewallRulesResourceRuleTargetModel struct {
@@ -110,6 +117,10 @@ func (r *vpcFirewallRulesResource) Schema(ctx context.Context, _ resource.Schema
110117
stringplanmodifier.RequiresReplace(),
111118
},
112119
},
120+
// The rules attribute cannot contain computed attributes since the upstream API
121+
// returns updated attributes for every rule, irrespective of which rules actually
122+
// change. See https://github.com/oxidecomputer/terraform-provider-oxide/issues/453
123+
// for more information.
113124
"rules": schema.SetNestedAttribute{
114125
Required: true,
115126
Description: "Associated firewall rules.",
@@ -202,10 +213,6 @@ func (r *vpcFirewallRulesResource) Schema(ctx context.Context, _ resource.Schema
202213
},
203214
},
204215
},
205-
"id": schema.StringAttribute{
206-
Computed: true,
207-
Description: "Unique, immutable, system-controlled identifier of the firewall rule.",
208-
},
209216
"name": schema.StringAttribute{
210217
Required: true,
211218
Description: "Name of the VPC firewall rule.",
@@ -255,14 +262,6 @@ func (r *vpcFirewallRulesResource) Schema(ctx context.Context, _ resource.Schema
255262
},
256263
},
257264
},
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-
},
266265
},
267266
},
268267
},
@@ -276,6 +275,14 @@ func (r *vpcFirewallRulesResource) Schema(ctx context.Context, _ resource.Schema
276275
Computed: true,
277276
Description: "Unique, immutable, system-controlled identifier of the firewall rules. Specific only to Terraform.",
278277
},
278+
"time_created": schema.StringAttribute{
279+
Computed: true,
280+
Description: "Timestamp of when the VPC firewall rules were last created.",
281+
},
282+
"time_modified": schema.StringAttribute{
283+
Computed: true,
284+
Description: "Timestamp of when the VPC firewall rules were last modified.",
285+
},
279286
},
280287
}
281288
}
@@ -327,6 +334,13 @@ func (r *vpcFirewallRulesResource) Create(ctx context.Context, req resource.Crea
327334
return
328335
}
329336

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

385399
state.Rules = rules
386400

401+
state.TimeCreated = types.StringNull()
402+
state.TimeModified = types.StringNull()
403+
if len(state.Rules) > 0 {
404+
state.TimeCreated = state.Rules[0].TimeCreated
405+
state.TimeModified = state.Rules[0].TimeModified
406+
}
407+
387408
// Save updated data into Terraform state
388409
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
389410
if resp.Diagnostics.HasError() {
@@ -447,6 +468,13 @@ func (r *vpcFirewallRulesResource) Update(ctx context.Context, req resource.Upda
447468
return
448469
}
449470

471+
plan.TimeCreated = types.StringNull()
472+
plan.TimeModified = types.StringNull()
473+
if len(plan.Rules) > 0 {
474+
plan.TimeCreated = plan.Rules[0].TimeCreated
475+
plan.TimeModified = plan.Rules[0].TimeModified
476+
}
477+
450478
// Save plan into Terraform state
451479
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
452480
if resp.Diagnostics.HasError() {
@@ -536,7 +564,6 @@ func newVPCFirewallRulesModel(rules []oxide.VpcFirewallRule) ([]vpcFirewallRules
536564
Action: types.StringValue(string(rule.Action)),
537565
Description: types.StringValue(rule.Description),
538566
Direction: types.StringValue(string(rule.Direction)),
539-
ID: types.StringValue(rule.Id),
540567
Name: types.StringValue(string(rule.Name)),
541568
// We can safely dereference rule.Priority as it's a required field
542569
Priority: types.Int64Value(int64(*rule.Priority)),

0 commit comments

Comments
 (0)