Skip to content

Commit 1b30f94

Browse files
committed
resource(vpc_firewall_rules): support new api changes
Pulled in the Go SDK changes from oxidecomputer/oxide.go#304 which contained changes to the VPC firewall rules APIs. Updated the `oxide_vpc_firewall_rules` resource to account for these changes.
1 parent d725b5a commit 1b30f94

File tree

6 files changed

+327
-40
lines changed

6 files changed

+327
-40
lines changed

.changelog/0.13.0.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[[breaking]]
2-
title = ""
3-
description = ""
2+
title = "`oxide_vpc_firewall_rules`"
3+
description = "Updated the schema for the `protocols` attribute to allow for more control over ICMP traffic. [#474](https://github.com/oxidecomputer/terraform-provider-oxide/pull/474)"
44

55
[[features]]
66
title = ""

docs/resources/oxide_vpc_firewall_rules.md

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ rules when updating this resource.
1515

1616
## Example Usage
1717

18+
### Basic Example
19+
1820
```hcl
1921
resource "oxide_vpc_firewall_rules" "example" {
2022
vpc_id = "6556fc6a-63c0-420b-bb23-c3205410f5cc"
@@ -34,7 +36,50 @@ resource "oxide_vpc_firewall_rules" "example" {
3436
}
3537
]
3638
ports = ["443"]
37-
protocols = ["TCP"]
39+
protocols = ["tcp"]
40+
},
41+
targets = [
42+
{
43+
type = "subnet"
44+
value = "default"
45+
}
46+
]
47+
}
48+
]
49+
}
50+
```
51+
52+
### ICMP Example
53+
54+
```hcl
55+
resource "oxide_vpc_firewall_rules" "example" {
56+
vpc_id = "6556fc6a-63c0-420b-bb23-c3205410f5cc"
57+
rules = [
58+
{
59+
action = "allow"
60+
description = "Allow ICMP"
61+
name = "allow-icmp"
62+
direction = "inbound"
63+
priority = 50
64+
status = "enabled"
65+
filters = {
66+
protocols = [
67+
# All ICMP.
68+
{
69+
type = "icmp",
70+
},
71+
# Echo Reply types.
72+
{
73+
type = "icmp",
74+
icmp_type = 0
75+
},
76+
# Echo Reply types with codes 1-3.
77+
{
78+
type = "icmp",
79+
icmp_type = 0
80+
icmp_code = "1-3"
81+
},
82+
]
3883
},
3984
targets = [
4085
{
@@ -86,7 +131,7 @@ Required:
86131
Optional:
87132

88133
- `hosts` (Set) If present, the sources (if incoming) or destinations (if outgoing) this rule applies to. (see [below for nested schema](#nestedatt--hosts))
89-
- `protocols` (Array of Strings) If present, the networking protocols this rule applies to. Possible values are: TCP, UDP and ICMP.
134+
- `protocols` (Set) If present, the networking protocols this rule applies to. (see [below for nested schema](#nestedatt--protocols))
90135
- `ports` (Array of Strings) If present, the destination ports this rule applies to. Can be a mix of single ports (e.g., `"443"`) and port ranges (e.g., `"30000-32768"`).
91136

92137
<a id="nestedatt--hosts"></a>
@@ -103,6 +148,19 @@ Required:
103148
- For type ip: IP address
104149
- For type ip_net: IPv4 or IPv6 subnet
105150

151+
<a id="nestedatt--protocols"></a>
152+
153+
### Nested Schema for `protocols`
154+
155+
Required:
156+
157+
- `type` (String) The protocol type. Must be one of `tcp`, `udp`, or `icmp`.
158+
159+
Optional:
160+
161+
- `icmp_type` (Number) ICMP type (e.g., 0 for Echo Reply). Only valid when `type` is `icmp`.
162+
- `icmp_code` (String) ICMP code (e.g., 0) or range (e.g., 1-3). Omit to filter all traffic of the specified `icmp_type`. Only valid when type is `icmp` and `icmp_type` is provided.
163+
106164
<a id="nestedatt--targets"></a>
107165

108166
### Nested Schema for `targets`

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ require (
1111
github.com/hashicorp/terraform-plugin-log v0.9.0
1212
github.com/hashicorp/terraform-plugin-sdk/v2 v2.37.0
1313
github.com/hashicorp/terraform-plugin-testing v1.13.2
14-
github.com/oxidecomputer/oxide.go v0.5.0
14+
github.com/oxidecomputer/oxide.go v0.5.1-0.20250719004549-7255536641a1
1515
github.com/stretchr/testify v1.10.0
1616
)
1717

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,8 @@ github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zx
136136
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
137137
github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw=
138138
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
139-
github.com/oxidecomputer/oxide.go v0.5.0 h1:bT5FPUmczVcS84NCLdJZ5PAWHMhINz99QQVLImnd5Sc=
140-
github.com/oxidecomputer/oxide.go v0.5.0/go.mod h1:4gfHlxdBQLs/34UbChPvINd+pGNAnGlASRGEd4xIz1Y=
139+
github.com/oxidecomputer/oxide.go v0.5.1-0.20250719004549-7255536641a1 h1:fdKQaoRt2FDN3OYWT4pJGgDqqmfb2eq5KYYigN+HDIw=
140+
github.com/oxidecomputer/oxide.go v0.5.1-0.20250719004549-7255536641a1/go.mod h1:4gfHlxdBQLs/34UbChPvINd+pGNAnGlASRGEd4xIz1Y=
141141
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
142142
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
143143
github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4=

internal/provider/resource_vpc_firewall_rules.go

Lines changed: 80 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111

1212
"github.com/google/uuid"
1313
"github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts"
14+
"github.com/hashicorp/terraform-plugin-framework-validators/int32validator"
1415
"github.com/hashicorp/terraform-plugin-framework-validators/setvalidator"
1516
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
1617
"github.com/hashicorp/terraform-plugin-framework/attr"
@@ -77,16 +78,22 @@ type vpcFirewallRulesResourceRuleTargetModel struct {
7778
}
7879

7980
type vpcFirewallRulesResourceRuleFiltersModel struct {
80-
Hosts []vpcFirewallRuleHostFilterModel `tfsdk:"hosts"`
81-
Ports types.Set `tfsdk:"ports"`
82-
Protocols types.Set `tfsdk:"protocols"`
81+
Hosts []vpcFirewallRuleHostFilterModel `tfsdk:"hosts"`
82+
Ports types.Set `tfsdk:"ports"`
83+
Protocols []vpcFirewallRuleProtocolFilterModel `tfsdk:"protocols"`
8384
}
8485

8586
type vpcFirewallRuleHostFilterModel struct {
8687
Type types.String `tfsdk:"type"`
8788
Value types.String `tfsdk:"value"`
8889
}
8990

91+
type vpcFirewallRuleProtocolFilterModel struct {
92+
Type types.String `tfsdk:"type"`
93+
IcmpType types.Int32 `tfsdk:"icmp_type"`
94+
IcmpCode types.String `tfsdk:"icmp_code"`
95+
}
96+
9097
// Metadata returns the resource type name.
9198
func (r *vpcFirewallRulesResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
9299
resp.TypeName = "oxide_vpc_firewall_rules"
@@ -188,18 +195,41 @@ func (r *vpcFirewallRulesResource) Schema(ctx context.Context, _ resource.Schema
188195
setvalidator.SizeAtLeast(1),
189196
},
190197
},
191-
"protocols": schema.SetAttribute{
192-
Description: "If present, the networking protocols this rule applies to. Possible values are: TCP, UDP and ICMP.",
198+
"protocols": schema.SetNestedAttribute{
199+
Description: "The protocols in a firewall rule's filter.",
193200
Optional: true,
194-
ElementType: types.StringType,
201+
NestedObject: schema.NestedAttributeObject{
202+
Attributes: map[string]schema.Attribute{
203+
"type": schema.StringAttribute{
204+
Required: true,
205+
Description: "The protocol type. Must be one of `tcp`, `udp`, or `icmp`.",
206+
Validators: []validator.String{
207+
stringvalidator.OneOf(
208+
string(oxide.VpcFirewallRuleProtocolTypeTcp),
209+
string(oxide.VpcFirewallRuleProtocolTypeUdp),
210+
string(oxide.VpcFirewallRuleProtocolTypeIcmp),
211+
),
212+
},
213+
},
214+
"icmp_type": schema.Int32Attribute{
215+
Optional: true,
216+
Description: "ICMP type. Only valid when type is `icmp`.",
217+
Validators: []validator.Int32{
218+
int32validator.Between(0, 255),
219+
},
220+
},
221+
"icmp_code": schema.StringAttribute{
222+
Optional: true,
223+
Description: "ICMP code (e.g., 0) or range (e.g., 1-3). Omit to filter all traffic of the specified `icmp_type`. Only valid when type is `icmp` and `icmp_type` is provided.",
224+
Validators: []validator.String{
225+
stringvalidator.AlsoRequires(path.Expressions{
226+
path.MatchRelative().AtParent().AtName("icmp_type"),
227+
}...),
228+
},
229+
},
230+
},
231+
},
195232
Validators: []validator.Set{
196-
setvalidator.ValueStringsAre(stringvalidator.Any(
197-
stringvalidator.OneOf(
198-
string(oxide.VpcFirewallRuleProtocolTcp),
199-
string(oxide.VpcFirewallRuleProtocolUdp),
200-
string(oxide.VpcFirewallRuleProtocolIcmp),
201-
),
202-
)),
203233
setvalidator.SizeAtLeast(1),
204234
},
205235
},
@@ -609,14 +639,25 @@ func newFiltersModelFromResponse(filter oxide.VpcFirewallRuleFilter) (*vpcFirewa
609639
return nil, diags
610640
}
611641

612-
var protocols = []attr.Value{}
642+
var protocolModels = []vpcFirewallRuleProtocolFilterModel{}
613643
for _, protocol := range filter.Protocols {
614-
protocols = append(protocols, types.StringValue(string(protocol)))
615-
}
616-
protocolSet, diags := types.SetValue(types.StringType, protocols)
617-
diags.Append(diags...)
618-
if diags.HasError() {
619-
return nil, diags
644+
protocolModel := vpcFirewallRuleProtocolFilterModel{
645+
Type: types.StringValue(string(protocol.Type)),
646+
IcmpCode: func() types.String {
647+
if protocol.Value.Code == "" {
648+
return types.StringNull()
649+
}
650+
return types.StringValue(string(protocol.Value.Code))
651+
}(),
652+
IcmpType: func() types.Int32 {
653+
if protocol.Value.IcmpType == nil {
654+
return types.Int32Null()
655+
}
656+
return types.Int32Value(int32(*protocol.Value.IcmpType))
657+
}(),
658+
}
659+
660+
protocolModels = append(protocolModels, protocolModel)
620661
}
621662

622663
model := vpcFirewallRulesResourceRuleFiltersModel{}
@@ -631,10 +672,10 @@ func newFiltersModelFromResponse(filter oxide.VpcFirewallRuleFilter) (*vpcFirewa
631672
model.Ports = types.SetNull(types.StringType)
632673
}
633674

634-
if len(protocolSet.Elements()) > 0 {
635-
model.Protocols = protocolSet
675+
if len(protocolModels) > 0 {
676+
model.Protocols = protocolModels
636677
} else {
637-
model.Protocols = types.SetNull(types.StringType)
678+
model.Protocols = nil
638679
}
639680

640681
return &model, nil
@@ -674,9 +715,22 @@ func newFilterTypeFromModel(model *vpcFirewallRulesResourceRuleFiltersModel) oxi
674715
}
675716

676717
protocols := []oxide.VpcFirewallRuleProtocol{}
677-
for _, protocol := range model.Protocols.Elements() {
678-
p, _ := strconv.Unquote(protocol.String())
679-
protocols = append(protocols, oxide.VpcFirewallRuleProtocol(p))
718+
for _, protocolModel := range model.Protocols {
719+
protocol := oxide.VpcFirewallRuleProtocol{
720+
Type: oxide.VpcFirewallRuleProtocolType(protocolModel.Type.ValueString()),
721+
Value: oxide.VpcFirewallIcmpFilter{
722+
Code: oxide.IcmpParamRange(protocolModel.IcmpCode.ValueString()),
723+
IcmpType: func() *int {
724+
if protocolModel.IcmpType.IsNull() {
725+
return nil
726+
}
727+
728+
return oxide.NewPointer(int(protocolModel.IcmpType.ValueInt32()))
729+
}(),
730+
},
731+
}
732+
733+
protocols = append(protocols, protocol)
680734
}
681735

682736
return oxide.VpcFirewallRuleFilter{

0 commit comments

Comments
 (0)