Skip to content

Commit b80d98e

Browse files
TPT-4479: Added support for Configurable VPC IPv4 Prefixes (#2402) (#2406)
* Added datasource for GET vpcs/default_ranges * Added support for IPv4 in VPC resource/datasource and VPCs datasource * Fix docs * Address CoPilot suggestion * Address PR comments * Remove LKE fix since it is out of scope * Fix tests * More fixes * More fixes * Remove custom type from ResourceSchemaIPv4NestedObject range attribute * Address CoPilot suggestions
1 parent 6b718b3 commit b80d98e

25 files changed

Lines changed: 706 additions & 7 deletions

.github/workflows/integration_tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ jobs:
7070
echo "LINODE_CONSUMER_TOKEN=${{ secrets.LINODE_TOKEN_USER_2 }}" >> $GITHUB_ENV
7171
;;
7272
"USER_2")
73-
echo "TEST_SUITE=databasemysqlv2,firewall,firewallsettings,firewalltemplate,firewalltemplates,firewalldevice,firewalls,image,images,instancenetworking,instancesharedips,instancetype,instancetypes,ipv6range,ipv6ranges,kernel,kernels,lock,locks,nb,nbconfig,nbconfigs,nbnode,nbs,nbvpc,nbvpcs,sshkey,sshkeys,vlan,volume,volumes,vpc,vpcs,vpcsubnets,vpcips" >> $GITHUB_ENV
73+
echo "TEST_SUITE=databasemysqlv2,firewall,firewallsettings,firewalltemplate,firewalltemplates,firewalldevice,firewalls,image,images,instancenetworking,instancesharedips,instancetype,instancetypes,ipv6range,ipv6ranges,kernel,kernels,lock,locks,nb,nbconfig,nbconfigs,nbnode,nbs,nbvpc,nbvpcs,sshkey,sshkeys,vlan,volume,volumes,vpc,vpcs,vpcsubnets,vpcips,vpcdefaultranges" >> $GITHUB_ENV
7474
echo "LINODE_TOKEN=${{ secrets.LINODE_TOKEN_USER_2 }}" >> $GITHUB_ENV
7575
;;
7676
"USER_3")

docs/data-sources/vpc.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ In addition to all arguments above, the following attributes are exported:
3939

4040
* `ipv6` - A list of IPv6 allocations under this VPC.
4141

42+
* `ipv4` - A list of IPv4 ranges under this VPC.
43+
4244
* `region` - The region where the VPC is deployed.
4345

4446
* `created` - The date and time when the VPC was created.
@@ -53,6 +55,14 @@ Contains information about a single IPv6 allocation under this VPC.
5355

5456
* `range` - The allocated range in CIDR format.
5557

58+
## IPv4
59+
60+
-> **Limited Availability** Custom VPC IPv4 Ranges may not currently be available to all users.
61+
62+
Contains information about a single IPv4 range under this VPC.
63+
64+
* `range` - The IPv4 range in CIDR format.
65+
5666
### Subnets Reference
5767

5868
To list all subnets under a VPC, please refer to the [linode_vpc_subnets](vpc_subnets.html.markdown) data source.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
---
2+
page_title: "Linode: linode_vpc_default_ranges"
3+
description: |-
4+
Provides details about the default and forbidden IPv4 address ranges for VPCs.
5+
---
6+
7+
# Data Source: linode\_vpc\_default\_ranges
8+
9+
Provides information about the default and forbidden IPv4 address ranges for VPCs.
10+
For more information, see the [Linode APIv4 docs](https://techdocs.akamai.com/linode-api/reference/get-vpcs-default-ranges).
11+
12+
## Example Usage
13+
14+
The following example shows how one might use this data source to access information about the default and forbidden IPv4 address ranges for VPCs.
15+
16+
```hcl
17+
data "linode_vpc_default_ranges" "foo" {}
18+
19+
output "vpc_default_ranges" {
20+
value = data.linode_vpc_default_ranges.foo
21+
}
22+
```
23+
24+
## Attributes Reference
25+
26+
In addition to all arguments above, the following attributes are exported:
27+
28+
* `default_ipv4_ranges` - The default IPv4 address ranges (CIDR blocks) used by the system when creating a VPC if no custom ranges are provided.
29+
30+
* `forbidden_ipv4_ranges` - IPv4 address ranges (CIDR blocks) that are forbidden and can't be used as part of a VPC's address space.

docs/data-sources/vpcs.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ Each Linode VPC will be stored in the `vpcs` attribute and will export the follo
5252

5353
* `ipv6` - A list of IPv6 allocations under this VPC.
5454

55+
* `ipv4` - A list of IPv4 ranges under this VPC.
56+
5557
* `label` - The label of the VPC.
5658

5759
* `region` - The region where the VPC is deployed.
@@ -66,6 +68,14 @@ Contains information about a single IPv6 allocation under a VPC.
6668

6769
* `range` - The allocated range in CIDR format.
6870

71+
## IPv4
72+
73+
-> **Limited Availability** Custom VPC IPv4 Ranges may not currently be available to all users.
74+
75+
Contains information about a single IPv4 range under this VPC.
76+
77+
* `range` - The IPv4 range in CIDR format.
78+
6979
## Filterable Fields
7080

7181
* `id`

docs/resources/vpc.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,22 @@ resource "linode_vpc" "test" {
3939
}
4040
```
4141

42+
Create a VPC with a custom IPv4 range:
43+
44+
```terraform
45+
# NOTE: Custom VPC IPv4 Ranges may not currently be available to all users.
46+
resource "linode_vpc" "test" {
47+
label = "test-vpc"
48+
region = "us-iad"
49+
50+
ipv4 = [
51+
{
52+
range = "10.0.0.0/8"
53+
}
54+
]
55+
}
56+
```
57+
4258
## Argument Reference
4359

4460
The following arguments are supported:
@@ -51,6 +67,8 @@ The following arguments are supported:
5167

5268
* [`ipv6`](#ipv6) - (Optional) A list of IPv6 allocations under this VPC.
5369

70+
* [`ipv4`](#ipv4) - (Optional) A list of IPv4 ranges under this VPC.
71+
5472
## IPv6
5573

5674
-> **Limited Availability** IPv6 VPCs may not currently be available to all users.
@@ -63,6 +81,14 @@ Configures a single IPv6 range under this VPC.
6381

6482
* `allocated_range` - (Read-Only) The value of range computed by the API. This is necessary when needing to access the range for an implicit allocation.
6583

84+
## IPv4
85+
86+
-> **Limited Availability** Custom VPC IPv4 Ranges may not currently be available to all users.
87+
88+
Configures a single IPv4 range under this VPC. Unlike IPv6, IPv4 ranges can be updated in-place without requiring resource replacement.
89+
90+
* `range` - (Required) The IPv4 range in CIDR format to assign to this VPC (e.g. `10.0.0.0/8`).
91+
6692
## Attributes Reference
6793

6894
In addition to all the arguments above, the following attributes are exported.

linode/framework_provider.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ import (
117117
"github.com/linode/terraform-provider-linode/v4/linode/volumes"
118118
"github.com/linode/terraform-provider-linode/v4/linode/volumetypes"
119119
"github.com/linode/terraform-provider-linode/v4/linode/vpc"
120+
"github.com/linode/terraform-provider-linode/v4/linode/vpcdefaultranges"
120121
"github.com/linode/terraform-provider-linode/v4/linode/vpcips"
121122
"github.com/linode/terraform-provider-linode/v4/linode/vpcs"
122123
"github.com/linode/terraform-provider-linode/v4/linode/vpcsubnet"
@@ -400,5 +401,6 @@ func (p *FrameworkProvider) DataSources(ctx context.Context) []func() datasource
400401
monitorlogsstreamhistory.NewDataSource,
401402
monitorlogsstreams.NewDataSource,
402403
monitoralertchannels.NewDataSource,
404+
vpcdefaultranges.NewDataSource,
403405
}
404406
}

linode/vpc/datasource_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,3 +102,50 @@ func TestAccDataSourceVPC_dualStack(t *testing.T) {
102102
},
103103
})
104104
}
105+
106+
func TestAccDataSourceVPC_ipv4(t *testing.T) {
107+
t.Parallel()
108+
109+
resourceName := "data.linode_vpc.foo"
110+
vpcLabel := acctest.RandomWithPrefix("tf-test")
111+
112+
targetRegion, err := acceptance.GetRandomRegionWithCaps([]linodego.RegionCapability{
113+
linodego.CapabilityVPCs,
114+
linodego.CapabilityVPCCustomIPv4Ranges,
115+
}, "core")
116+
if err != nil {
117+
log.Fatal(err)
118+
}
119+
120+
resource.Test(t, resource.TestCase{
121+
PreCheck: func() { acceptance.PreCheck(t) },
122+
ProtoV6ProviderFactories: acceptance.ProtoV6ProviderFactories,
123+
Steps: []resource.TestStep{
124+
{
125+
Config: tmpl.DataIPv4(t, vpcLabel, targetRegion, "10.0.0.0/8"),
126+
ConfigStateChecks: []statecheck.StateCheck{
127+
statecheck.ExpectKnownValue(
128+
resourceName,
129+
tfjsonpath.New("label"),
130+
knownvalue.StringExact(vpcLabel),
131+
),
132+
statecheck.ExpectKnownValue(
133+
resourceName,
134+
tfjsonpath.New("region"),
135+
knownvalue.NotNull(),
136+
),
137+
statecheck.ExpectKnownValue(
138+
resourceName,
139+
tfjsonpath.New("ipv4"),
140+
knownvalue.ListSizeExact(1),
141+
),
142+
statecheck.ExpectKnownValue(
143+
resourceName,
144+
tfjsonpath.New("ipv4").AtSliceIndex(0).AtMapKey("range"),
145+
knownvalue.StringExact("10.0.0.0/8"),
146+
),
147+
},
148+
},
149+
},
150+
})
151+
}

linode/vpc/framework_models.go

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ type BaseModel struct {
2323
Region types.String `tfsdk:"region"`
2424
Created timetypes.RFC3339 `tfsdk:"created"`
2525
Updated timetypes.RFC3339 `tfsdk:"updated"`
26+
IPv4 types.List `tfsdk:"ipv4"`
2627
}
2728

2829
func (m *BaseModel) FlattenVPC(ctx context.Context, vpc *linodego.VPC, preserveKnown bool) diag.Diagnostics {
@@ -47,7 +48,6 @@ func (m *BaseModel) FlattenVPC(ctx context.Context, vpc *linodego.VPC, preserveK
4748

4849
func (m *BaseModel) CopyFrom(ctx context.Context, other BaseModel, preserveKnown bool) {
4950
m.ID = helper.KeepOrUpdateValue(m.ID, other.ID, preserveKnown)
50-
5151
m.Description = helper.KeepOrUpdateValue(m.Description, other.Description, preserveKnown)
5252
m.Created = helper.KeepOrUpdateValue(m.Created, other.Created, preserveKnown)
5353
m.Updated = helper.KeepOrUpdateValue(m.Updated, other.Updated, preserveKnown)
@@ -70,9 +70,14 @@ type ResourceModelIPv6 struct {
7070
AllocationClass types.String `tfsdk:"allocation_class"`
7171
}
7272

73+
type ResourceModelIPv4 struct {
74+
Range types.String `tfsdk:"range"`
75+
}
76+
7377
func (m *ResourceModel) FlattenVPC(ctx context.Context, vpc *linodego.VPC, preserveKnown bool) diag.Diagnostics {
7478
m.BaseModel.FlattenVPC(ctx, vpc, preserveKnown)
7579

80+
// Flatten IPv6
7681
ipv6Models := helper.MapSlice(vpc.IPv6,
7782
func(r linodego.VPCIPv6Range) ResourceModelIPv6 {
7883
return ResourceModelIPv6{
@@ -94,12 +99,33 @@ func (m *ResourceModel) FlattenVPC(ctx context.Context, vpc *linodego.VPC, prese
9499
false,
95100
)
96101

102+
// Flatten IPv4
103+
ipv4Models := helper.MapSlice(vpc.IPv4,
104+
func(r linodego.VPCIPv4Range) ResourceModelIPv4 {
105+
return ResourceModelIPv4{
106+
Range: types.StringValue(r.Range),
107+
}
108+
},
109+
)
110+
111+
ipv4List, ipv4Diags := types.ListValueFrom(ctx, ResourceSchemaIPv4NestedObject.Type(), ipv4Models)
112+
if ipv4Diags.HasError() {
113+
return ipv4Diags
114+
}
115+
116+
m.IPv4 = helper.KeepOrUpdateValue(
117+
m.IPv4,
118+
ipv4List,
119+
false,
120+
)
121+
97122
return nil
98123
}
99124

100125
func (m *ResourceModel) CopyFrom(ctx context.Context, other ResourceModel, preserveKnown bool) {
101126
m.BaseModel.CopyFrom(ctx, other.BaseModel, preserveKnown)
102127
m.IPv6 = helper.KeepOrUpdateValue(m.IPv6, other.IPv6, preserveKnown)
128+
m.IPv4 = helper.KeepOrUpdateValue(m.IPv4, other.IPv4, preserveKnown)
103129
}
104130

105131
/*
@@ -115,9 +141,14 @@ type DataSourceModelIPv6 struct {
115141
Range customtypes.LinodeAutoAllocRangeValue `tfsdk:"range"`
116142
}
117143

144+
type DataSourceModelIPv4 struct {
145+
Range types.String `tfsdk:"range"`
146+
}
147+
118148
func (m *DataSourceModel) FlattenVPC(ctx context.Context, vpc *linodego.VPC, preserveKnown bool) diag.Diagnostics {
119149
m.BaseModel.FlattenVPC(ctx, vpc, preserveKnown)
120150

151+
// Flatten IPv6
121152
ipv6Models := helper.MapSlice(
122153
vpc.IPv6,
123154
func(r linodego.VPCIPv6Range) DataSourceModelIPv6 {
@@ -138,10 +169,32 @@ func (m *DataSourceModel) FlattenVPC(ctx context.Context, vpc *linodego.VPC, pre
138169
preserveKnown,
139170
)
140171

172+
// Flatten IPv4
173+
ipv4Models := helper.MapSlice(
174+
vpc.IPv4,
175+
func(r linodego.VPCIPv4Range) DataSourceModelIPv4 {
176+
return DataSourceModelIPv4{
177+
Range: types.StringValue(r.Range),
178+
}
179+
},
180+
)
181+
182+
ipv4List, ipv4Diags := types.ListValueFrom(ctx, DataSourceSchemaIPv4NestedObject.Type(), ipv4Models)
183+
if ipv4Diags.HasError() {
184+
return ipv4Diags
185+
}
186+
187+
m.IPv4 = helper.KeepOrUpdateValue(
188+
m.IPv4,
189+
ipv4List,
190+
preserveKnown,
191+
)
192+
141193
return nil
142194
}
143195

144196
func (m *DataSourceModel) CopyFrom(ctx context.Context, other DataSourceModel, preserveKnown bool) {
145197
m.BaseModel.CopyFrom(ctx, other.BaseModel, preserveKnown)
146198
m.IPv6 = helper.KeepOrUpdateValue(m.IPv6, other.IPv6, preserveKnown)
199+
m.IPv4 = helper.KeepOrUpdateValue(m.IPv4, other.IPv4, preserveKnown)
147200
}

linode/vpc/framework_resource.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,24 @@ func (r *Resource) Create(
7070
)
7171
}
7272

73+
if !data.IPv4.IsNull() && !data.IPv4.IsUnknown() {
74+
modelIPv4s := make([]ResourceModelIPv4, len(data.IPv4.Elements()))
75+
76+
resp.Diagnostics.Append(data.IPv4.ElementsAs(ctx, &modelIPv4s, true)...)
77+
if resp.Diagnostics.HasError() {
78+
return
79+
}
80+
81+
vpcCreateOpts.IPv4 = helper.MapSlice(
82+
modelIPv4s,
83+
func(m ResourceModelIPv4) linodego.VPCCreateOptionsIPv4 {
84+
return linodego.VPCCreateOptionsIPv4{
85+
Range: m.Range.ValueStringPointer(),
86+
}
87+
},
88+
)
89+
}
90+
7391
tflog.Debug(ctx, "client.CreateVPC(...)", map[string]any{
7492
"options": vpcCreateOpts,
7593
})
@@ -83,6 +101,7 @@ func (r *Resource) Create(
83101
}
84102

85103
ipv6Configured := !data.IPv6.IsNull()
104+
ipv4Configured := !data.IPv4.IsNull() && !data.IPv4.IsUnknown()
86105

87106
resp.Diagnostics.Append(data.FlattenVPC(ctx, vpc, true)...)
88107
if resp.Diagnostics.HasError() {
@@ -103,6 +122,15 @@ func (r *Resource) Create(
103122
"Please ensure the current user has access to the VPC IPv6 feature.",
104123
)
105124
}
125+
126+
if ipv4Configured && vpc.IPv4 == nil {
127+
resp.Diagnostics.AddAttributeError(
128+
path.Root("ipv4"),
129+
"Value Mismatch",
130+
"The `ipv4` field was configured but was not found in the API's response. "+
131+
"Please ensure the current user has access to the VPC IPv4 feature.",
132+
)
133+
}
106134
}
107135

108136
func (r *Resource) Read(
@@ -187,6 +215,31 @@ func (r *Resource) Update(
187215
updateOpts.Label = plan.Label.ValueString()
188216
}
189217

218+
if !plan.IPv4.IsUnknown() && !state.IPv4.Equal(plan.IPv4) {
219+
shouldUpdate = true
220+
221+
if !plan.IPv4.IsNull() {
222+
modelIPv4s := make([]ResourceModelIPv4, len(plan.IPv4.Elements()))
223+
224+
resp.Diagnostics.Append(plan.IPv4.ElementsAs(ctx, &modelIPv4s, true)...)
225+
if resp.Diagnostics.HasError() {
226+
return
227+
}
228+
229+
updateOpts.IPv4 = helper.MapSlice(
230+
modelIPv4s,
231+
func(m ResourceModelIPv4) linodego.VPCUpdateOptionsIPv4 {
232+
return linodego.VPCUpdateOptionsIPv4{
233+
Range: m.Range.ValueStringPointer(),
234+
}
235+
},
236+
)
237+
} else {
238+
// Explicitly set to empty slice to clear all IPv4 ranges
239+
updateOpts.IPv4 = []linodego.VPCUpdateOptionsIPv4{}
240+
}
241+
}
242+
190243
if shouldUpdate {
191244
id := helper.FrameworkSafeStringToInt(plan.ID.ValueString(), &resp.Diagnostics)
192245
if resp.Diagnostics.HasError() {

0 commit comments

Comments
 (0)