Skip to content

Commit f84bc22

Browse files
authored
Validate subnet cidrs. (#622)
* Add a subnet cidr validator, which optionally accepts an address type. * Move existing ip config validator to utils and backfill tests. Note: this isn't an attempt to validate all ip address and cidr fields in the provider. If we find these validators useful, we can expand validation coverage in a separate patch. ----- ### Pull request checklist - [ ] Add changelog entry for this change.
1 parent b50bcbf commit f84bc22

8 files changed

Lines changed: 93 additions & 84 deletions

File tree

docs/resources/instance.md

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -186,10 +186,6 @@ Optional:
186186
- `ip_version` (String) IP version to use when multiple default pools exist. Conflicts with `pool_id`.
187187
- `pool_id` (String) ID of the IP pool to allocate from. Conflicts with `ip_version`.
188188

189-
Read-Only:
190-
191-
- `ip` (String) The external ephemeral IP attached to the instance.
192-
193189

194190
<a id="nestedatt--external_ips--floating"></a>
195191
### Nested Schema for `external_ips.floating`
@@ -198,11 +194,6 @@ Required:
198194

199195
- `id` (String) The external floating IP ID.
200196

201-
Read-Only:
202-
203-
- `ip` (String) The external floating IP attached to the instance.
204-
- `name` (String) The name of the external floating IP attached to the instance.
205-
206197

207198

208199
<a id="nestedatt--network_interfaces"></a>

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ tool (
1313
require (
1414
github.com/google/uuid v1.6.0
1515
github.com/hashicorp/terraform-plugin-framework v1.17.0
16+
github.com/hashicorp/terraform-plugin-framework-nettypes v0.3.0
1617
github.com/hashicorp/terraform-plugin-framework-timeouts v0.7.0
1718
github.com/hashicorp/terraform-plugin-framework-validators v0.19.0
1819
github.com/hashicorp/terraform-plugin-go v0.29.0

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,8 @@ github.com/hashicorp/terraform-plugin-docs v0.24.0 h1:YNZYd+8cpYclQyXbl1EEngbld8
441441
github.com/hashicorp/terraform-plugin-docs v0.24.0/go.mod h1:YLg+7LEwVmRuJc0EuCw0SPLxuQXw5mW8iJ5ml/kvi+o=
442442
github.com/hashicorp/terraform-plugin-framework v1.17.0 h1:JdX50CFrYcYFY31gkmitAEAzLKoBgsK+iaJjDC8OexY=
443443
github.com/hashicorp/terraform-plugin-framework v1.17.0/go.mod h1:4OUXKdHNosX+ys6rLgVlgklfxN3WHR5VHSOABeS/BM0=
444+
github.com/hashicorp/terraform-plugin-framework-nettypes v0.3.0 h1:cEiRvdFAhFnivRm9JI/8l2g8oruzkioUAwItkEM7bmU=
445+
github.com/hashicorp/terraform-plugin-framework-nettypes v0.3.0/go.mod h1:SDIm7W2x3Bs9otNC0ysbaSQ7H4H/EPimoACTy+7Z9rU=
444446
github.com/hashicorp/terraform-plugin-framework-timeouts v0.7.0 h1:jblRy1PkLfPm5hb5XeMa3tezusnMRziUGqtT5epSYoI=
445447
github.com/hashicorp/terraform-plugin-framework-timeouts v0.7.0/go.mod h1:5jm2XK8uqrdiSRfD5O47OoxyGMCnwTcl8eoiDgSa+tc=
446448
github.com/hashicorp/terraform-plugin-framework-validators v0.19.0 h1:Zz3iGgzxe/1XBkooZCewS0nJAaCFPFPHdNJd8FgE4Ow=

internal/provider/resource_external_subnet.go

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"context"
99
"fmt"
1010

11+
"github.com/hashicorp/terraform-plugin-framework-nettypes/cidrtypes"
1112
"github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts"
1213
"github.com/hashicorp/terraform-plugin-framework-validators/int64validator"
1314
"github.com/hashicorp/terraform-plugin-framework-validators/resourcevalidator"
@@ -43,19 +44,19 @@ type externalSubnetResource struct {
4344
}
4445

4546
type externalSubnetResourceModel struct {
46-
ID types.String `tfsdk:"id"`
47-
Name types.String `tfsdk:"name"`
48-
Description types.String `tfsdk:"description"`
49-
ProjectID types.String `tfsdk:"project_id"`
50-
Subnet types.String `tfsdk:"subnet"`
51-
PrefixLen types.Int64 `tfsdk:"prefix_len"`
52-
SubnetPoolID types.String `tfsdk:"subnet_pool_id"`
53-
IPVersion types.String `tfsdk:"ip_version"`
54-
SubnetPoolMemberID types.String `tfsdk:"subnet_pool_member_id"`
55-
InstanceID types.String `tfsdk:"instance_id"`
56-
TimeCreated types.String `tfsdk:"time_created"`
57-
TimeModified types.String `tfsdk:"time_modified"`
58-
Timeouts timeouts.Value `tfsdk:"timeouts"`
47+
ID types.String `tfsdk:"id"`
48+
Name types.String `tfsdk:"name"`
49+
Description types.String `tfsdk:"description"`
50+
ProjectID types.String `tfsdk:"project_id"`
51+
Subnet cidrtypes.IPPrefix `tfsdk:"subnet"`
52+
PrefixLen types.Int64 `tfsdk:"prefix_len"`
53+
SubnetPoolID types.String `tfsdk:"subnet_pool_id"`
54+
IPVersion types.String `tfsdk:"ip_version"`
55+
SubnetPoolMemberID types.String `tfsdk:"subnet_pool_member_id"`
56+
InstanceID types.String `tfsdk:"instance_id"`
57+
TimeCreated types.String `tfsdk:"time_created"`
58+
TimeModified types.String `tfsdk:"time_modified"`
59+
Timeouts timeouts.Value `tfsdk:"timeouts"`
5960
}
6061

6162
// Metadata returns the resource type name.
@@ -133,6 +134,7 @@ func (r *externalSubnetResource) Schema(
133134
"subnet": schema.StringAttribute{
134135
Optional: true,
135136
Computed: true,
137+
CustomType: cidrtypes.IPPrefixType{},
136138
MarkdownDescription: "The subnet CIDR to reserve. Must be available in the pool. Conflicts with `prefix_len`. If unset, a subnet will be automatically allocated with the specified `prefix_len`.",
137139
PlanModifiers: []planmodifier.String{
138140
stringplanmodifier.RequiresReplaceIfConfigured(),
@@ -297,7 +299,7 @@ func (r *externalSubnetResource) Create(
297299
plan.Name = types.StringValue(string(externalSubnet.Name))
298300
plan.Description = types.StringValue(externalSubnet.Description)
299301
plan.ProjectID = types.StringValue(externalSubnet.ProjectId)
300-
plan.Subnet = types.StringValue(externalSubnet.Subnet.String())
302+
plan.Subnet = cidrtypes.NewIPPrefixValue(externalSubnet.Subnet.String())
301303
plan.SubnetPoolID = types.StringValue(externalSubnet.SubnetPoolId)
302304
plan.SubnetPoolMemberID = types.StringValue(externalSubnet.SubnetPoolMemberId)
303305
plan.InstanceID = types.StringValue(externalSubnet.InstanceId)
@@ -360,7 +362,7 @@ func (r *externalSubnetResource) Read(
360362
state.Name = types.StringValue(string(externalSubnet.Name))
361363
state.Description = types.StringValue(externalSubnet.Description)
362364
state.ProjectID = types.StringValue(externalSubnet.ProjectId)
363-
state.Subnet = types.StringValue(externalSubnet.Subnet.String())
365+
state.Subnet = cidrtypes.NewIPPrefixValue(externalSubnet.Subnet.String())
364366
state.SubnetPoolID = types.StringValue(externalSubnet.SubnetPoolId)
365367
state.SubnetPoolMemberID = types.StringValue(externalSubnet.SubnetPoolMemberId)
366368
state.InstanceID = types.StringValue(externalSubnet.InstanceId)
@@ -428,7 +430,7 @@ func (r *externalSubnetResource) Update(
428430
plan.Name = types.StringValue(string(externalSubnet.Name))
429431
plan.Description = types.StringValue(externalSubnet.Description)
430432
plan.ProjectID = types.StringValue(externalSubnet.ProjectId)
431-
plan.Subnet = types.StringValue(externalSubnet.Subnet.String())
433+
plan.Subnet = cidrtypes.NewIPPrefixValue(externalSubnet.Subnet.String())
432434
plan.SubnetPoolID = types.StringValue(externalSubnet.SubnetPoolId)
433435
plan.SubnetPoolMemberID = types.StringValue(externalSubnet.SubnetPoolMemberId)
434436
plan.InstanceID = types.StringValue(externalSubnet.InstanceId)

internal/provider/resource_floating_ip.go

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"context"
99
"fmt"
1010

11+
"github.com/hashicorp/terraform-plugin-framework-nettypes/iptypes"
1112
"github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts"
1213
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
1314
"github.com/hashicorp/terraform-plugin-framework/path"
@@ -25,17 +26,17 @@ import (
2526
// floatingIPResourceModel represents the Terraform configuration and state for
2627
// the Oxide floating IP resource.
2728
type floatingIPResourceModel struct {
28-
ID types.String `tfsdk:"id"`
29-
Name types.String `tfsdk:"name"`
30-
Description types.String `tfsdk:"description"`
31-
IP types.String `tfsdk:"ip"`
32-
InstanceID types.String `tfsdk:"instance_id"`
33-
IPPoolID types.String `tfsdk:"ip_pool_id"`
34-
IPVersion types.String `tfsdk:"ip_version"`
35-
ProjectID types.String `tfsdk:"project_id"`
36-
TimeCreated types.String `tfsdk:"time_created"`
37-
TimeModified types.String `tfsdk:"time_modified"`
38-
Timeouts timeouts.Value `tfsdk:"timeouts"`
29+
ID types.String `tfsdk:"id"`
30+
Name types.String `tfsdk:"name"`
31+
Description types.String `tfsdk:"description"`
32+
IP iptypes.IPAddress `tfsdk:"ip"`
33+
InstanceID types.String `tfsdk:"instance_id"`
34+
IPPoolID types.String `tfsdk:"ip_pool_id"`
35+
IPVersion types.String `tfsdk:"ip_version"`
36+
ProjectID types.String `tfsdk:"project_id"`
37+
TimeCreated types.String `tfsdk:"time_created"`
38+
TimeModified types.String `tfsdk:"time_modified"`
39+
Timeouts timeouts.Value `tfsdk:"timeouts"`
3940
}
4041

4142
// Compile-time assertions to check that the floatingIPResource implements the
@@ -123,6 +124,7 @@ This resource manages Oxide floating IPs.
123124
"ip": schema.StringAttribute{
124125
Optional: true,
125126
Computed: true,
127+
CustomType: iptypes.IPAddressType{},
126128
MarkdownDescription: "IP address for this floating IP. If unset an IP address will be chosen from the given `ip_pool_id`.",
127129
PlanModifiers: []planmodifier.String{
128130
stringplanmodifier.RequiresReplaceIfConfigured(),
@@ -260,7 +262,7 @@ func (f *floatingIPResource) Create(
260262
plan.ID = types.StringValue(floatingIP.Id)
261263
plan.Name = types.StringValue(string(floatingIP.Name))
262264
plan.Description = types.StringValue(floatingIP.Description)
263-
plan.IP = types.StringValue(floatingIP.Ip)
265+
plan.IP = iptypes.NewIPAddressValue(floatingIP.Ip)
264266
plan.InstanceID = types.StringValue(floatingIP.InstanceId)
265267
plan.IPPoolID = types.StringValue(floatingIP.IpPoolId)
266268
plan.ProjectID = types.StringValue(floatingIP.ProjectId)
@@ -321,7 +323,7 @@ func (f *floatingIPResource) Read(
321323
state.ID = types.StringValue(floatingIP.Id)
322324
state.Name = types.StringValue(string(floatingIP.Name))
323325
state.Description = types.StringValue(floatingIP.Description)
324-
state.IP = types.StringValue(floatingIP.Ip)
326+
state.IP = iptypes.NewIPAddressValue(floatingIP.Ip)
325327
state.InstanceID = types.StringValue(floatingIP.InstanceId)
326328
state.IPPoolID = types.StringValue(floatingIP.IpPoolId)
327329
state.ProjectID = types.StringValue(floatingIP.ProjectId)
@@ -390,7 +392,7 @@ func (f *floatingIPResource) Update(
390392
plan.ID = types.StringValue(floatingIP.Id)
391393
plan.Name = types.StringValue(string(floatingIP.Name))
392394
plan.Description = types.StringValue(floatingIP.Description)
393-
plan.IP = types.StringValue(floatingIP.Ip)
395+
plan.IP = iptypes.NewIPAddressValue(floatingIP.Ip)
394396
plan.InstanceID = types.StringValue(floatingIP.InstanceId)
395397
plan.IPPoolID = types.StringValue(floatingIP.IpPoolId)
396398
plan.ProjectID = types.StringValue(floatingIP.ProjectId)

internal/provider/resource_subnet_pool_member.go

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"fmt"
1010
"strings"
1111

12+
"github.com/hashicorp/terraform-plugin-framework-nettypes/cidrtypes"
1213
"github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts"
1314
"github.com/hashicorp/terraform-plugin-framework/path"
1415
"github.com/hashicorp/terraform-plugin-framework/resource"
@@ -39,13 +40,13 @@ type subnetPoolMemberResource struct {
3940
}
4041

4142
type subnetPoolMemberResourceModel struct {
42-
ID types.String `tfsdk:"id"`
43-
SubnetPoolID types.String `tfsdk:"subnet_pool_id"`
44-
Subnet types.String `tfsdk:"subnet"`
45-
MinPrefixLength types.Int64 `tfsdk:"min_prefix_length"`
46-
MaxPrefixLength types.Int64 `tfsdk:"max_prefix_length"`
47-
TimeCreated types.String `tfsdk:"time_created"`
48-
Timeouts timeouts.Value `tfsdk:"timeouts"`
43+
ID types.String `tfsdk:"id"`
44+
SubnetPoolID types.String `tfsdk:"subnet_pool_id"`
45+
Subnet cidrtypes.IPPrefix `tfsdk:"subnet"`
46+
MinPrefixLength types.Int64 `tfsdk:"min_prefix_length"`
47+
MaxPrefixLength types.Int64 `tfsdk:"max_prefix_length"`
48+
TimeCreated types.String `tfsdk:"time_created"`
49+
Timeouts timeouts.Value `tfsdk:"timeouts"`
4950
}
5051

5152
// Metadata returns the resource type name.
@@ -107,6 +108,7 @@ func (r *subnetPoolMemberResource) Schema(
107108
},
108109
"subnet": schema.StringAttribute{
109110
Required: true,
111+
CustomType: cidrtypes.IPPrefixType{},
110112
Description: "The subnet CIDR to add to the pool (e.g., '10.0.0.0/16').",
111113
PlanModifiers: []planmodifier.String{
112114
stringplanmodifier.RequiresReplace(),
@@ -292,7 +294,7 @@ func (r *subnetPoolMemberResource) Read(
292294
)
293295

294296
state.ID = types.StringValue(foundMember.Id)
295-
state.Subnet = types.StringValue(foundMember.Subnet.String())
297+
state.Subnet = cidrtypes.NewIPPrefixValue(foundMember.Subnet.String())
296298
state.MinPrefixLength = types.Int64Value(int64(*foundMember.MinPrefixLength))
297299
state.MaxPrefixLength = types.Int64Value(int64(*foundMember.MaxPrefixLength))
298300
state.TimeCreated = types.StringValue(foundMember.TimeCreated.String())

internal/provider/resource_switch_port_settings.go

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import (
88
"context"
99
"fmt"
1010

11+
"github.com/hashicorp/terraform-plugin-framework-nettypes/cidrtypes"
12+
"github.com/hashicorp/terraform-plugin-framework-nettypes/iptypes"
1113
"github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts"
1214
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
1315
"github.com/hashicorp/terraform-plugin-framework/diag"
@@ -49,9 +51,9 @@ type switchPortSettingsAddressModel struct {
4951
}
5052

5153
type switchPortSettingsAddressAddressModel struct {
52-
Address types.String `tfsdk:"address"`
53-
AddressLotID types.String `tfsdk:"address_lot_id"`
54-
VlanID types.Int32 `tfsdk:"vlan_id"`
54+
Address cidrtypes.IPPrefix `tfsdk:"address"`
55+
AddressLotID types.String `tfsdk:"address_lot_id"`
56+
VlanID types.Int32 `tfsdk:"vlan_id"`
5557
}
5658

5759
type switchPortSettingsBGPPeerModel struct {
@@ -60,7 +62,7 @@ type switchPortSettingsBGPPeerModel struct {
6062
}
6163

6264
type switchPortSettingsBGPPeerPeerModel struct {
63-
Address types.String `tfsdk:"address"`
65+
Address iptypes.IPAddress `tfsdk:"address"`
6466
AllowedExport *switchPortSettingsBGPPeerPeerAllowedExportModel `tfsdk:"allowed_export"`
6567
AllowedImport *switchPortSettingsBGPPeerPeerAllowedImportModel `tfsdk:"allowed_import"`
6668
BGPConfig types.String `tfsdk:"bgp_config"`
@@ -101,13 +103,13 @@ type switchPortSettingsLinkModel struct {
101103
}
102104

103105
type switchPortSettingsLinkLLDPModel struct {
104-
ChassisID types.String `tfsdk:"chassis_id"`
105-
Enabled types.Bool `tfsdk:"enabled"`
106-
LinkDescription types.String `tfsdk:"link_description"`
107-
LinkName types.String `tfsdk:"link_name"`
108-
ManagementIP types.String `tfsdk:"management_ip"`
109-
SystemDescription types.String `tfsdk:"system_description"`
110-
SystemName types.String `tfsdk:"system_name"`
106+
ChassisID types.String `tfsdk:"chassis_id"`
107+
Enabled types.Bool `tfsdk:"enabled"`
108+
LinkDescription types.String `tfsdk:"link_description"`
109+
LinkName types.String `tfsdk:"link_name"`
110+
ManagementIP iptypes.IPAddress `tfsdk:"management_ip"`
111+
SystemDescription types.String `tfsdk:"system_description"`
112+
SystemName types.String `tfsdk:"system_name"`
111113
}
112114

113115
type switchPortSettingsLinkTxEqModel struct {
@@ -128,10 +130,10 @@ type switchPortSettingsRouteModel struct {
128130
}
129131

130132
type switchPortSettingsRouteRouteModel struct {
131-
Dst types.String `tfsdk:"dst"`
132-
GW types.String `tfsdk:"gw"`
133-
RIBPriority types.Int32 `tfsdk:"rib_priority"`
134-
VID types.Int32 `tfsdk:"vid"`
133+
Dst types.String `tfsdk:"dst"`
134+
GW iptypes.IPAddress `tfsdk:"gw"`
135+
RIBPriority types.Int32 `tfsdk:"rib_priority"`
136+
VID types.Int32 `tfsdk:"vid"`
135137
}
136138

137139
// NewSwitchPortSettingsResource contructs a Terraform resource.
@@ -198,6 +200,7 @@ func (r *switchPortSettingsResource) Schema(
198200
Attributes: map[string]schema.Attribute{
199201
"address": schema.StringAttribute{
200202
Required: true,
203+
CustomType: cidrtypes.IPPrefixType{},
201204
Description: "IPv4 or IPv6 address, including the subnet mask.",
202205
},
203206
"address_lot_id": schema.StringAttribute{
@@ -230,6 +233,7 @@ func (r *switchPortSettingsResource) Schema(
230233
Attributes: map[string]schema.Attribute{
231234
"address": schema.StringAttribute{
232235
Required: true,
236+
CustomType: iptypes.IPAddressType{},
233237
Description: "Address of the host to peer with.",
234238
},
235239
"allowed_export": schema.SingleNestedAttribute{
@@ -395,6 +399,7 @@ func (r *switchPortSettingsResource) Schema(
395399
},
396400
"management_ip": schema.StringAttribute{
397401
Optional: true,
402+
CustomType: iptypes.IPAddressType{},
398403
Description: "LLDP management IP address.",
399404
},
400405
"system_description": schema.StringAttribute{
@@ -498,6 +503,7 @@ func (r *switchPortSettingsResource) Schema(
498503
},
499504
"gw": schema.StringAttribute{
500505
Required: true,
506+
CustomType: iptypes.IPAddressType{},
501507
Description: "Gateway IP address for this route.",
502508
},
503509
"rib_priority": schema.Int32Attribute{
@@ -827,7 +833,7 @@ func toSwitchPortSettingsModel(
827833
}
828834

829835
addressModel := switchPortSettingsAddressAddressModel{
830-
Address: types.StringValue(address.Address.String()),
836+
Address: cidrtypes.NewIPPrefixValue(address.Address.String()),
831837
AddressLotID: types.StringValue(string(address.AddressLotId)),
832838
VlanID: func() types.Int32 {
833839
if address.VlanId == nil {
@@ -865,7 +871,7 @@ func toSwitchPortSettingsModel(
865871
}
866872

867873
bgpPeerModel := switchPortSettingsBGPPeerPeerModel{
868-
Address: types.StringValue(bgpPeer.Addr),
874+
Address: iptypes.NewIPAddressValue(bgpPeer.Addr),
869875
BGPConfig: types.StringValue(string(bgpPeer.BgpConfig)),
870876
ConnectRetry: types.Int64Value(int64(*bgpPeer.ConnectRetry)),
871877
DelayOpen: types.Int64Value(int64(*bgpPeer.DelayOpen)),
@@ -1023,11 +1029,11 @@ func toSwitchPortSettingsModel(
10231029
}
10241030
return types.StringValue(link.LldpLinkConfig.LinkName)
10251031
}()
1026-
linkModel.LLDP.ManagementIP = func() types.String {
1032+
linkModel.LLDP.ManagementIP = func() iptypes.IPAddress {
10271033
if link.LldpLinkConfig.ManagementIp == "" {
1028-
return types.StringNull()
1034+
return iptypes.NewIPAddressNull()
10291035
}
1030-
return types.StringValue(link.LldpLinkConfig.ManagementIp)
1036+
return iptypes.NewIPAddressValue(link.LldpLinkConfig.ManagementIp)
10311037
}()
10321038
linkModel.LLDP.SystemDescription = func() types.String {
10331039
if link.LldpLinkConfig.SystemDescription == "" {
@@ -1099,7 +1105,7 @@ func toSwitchPortSettingsModel(
10991105

11001106
routeModel := switchPortSettingsRouteRouteModel{
11011107
Dst: types.StringValue(route.Dst.String()),
1102-
GW: types.StringValue(route.Gw),
1108+
GW: iptypes.NewIPAddressValue(route.Gw),
11031109
RIBPriority: func() types.Int32 {
11041110
if route.RibPriority != nil {
11051111
return types.Int32Value(int32(*route.RibPriority))

0 commit comments

Comments
 (0)