Skip to content

Commit f2dffdc

Browse files
Added support for Configurable VPC IPv4 Prefixes
1 parent e12230d commit f2dffdc

8 files changed

Lines changed: 144 additions & 4 deletions

File tree

linode_api4/groups/vpc.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from linode_api4.errors import UnexpectedResponseError
44
from linode_api4.groups import Group
5-
from linode_api4.objects import VPC, Region, VPCIPAddress, VPCIPv6RangeOptions
5+
from linode_api4.objects import VPC, Region, VPCIPAddress, VPCIPv4DefaultRange, VPCIPv4RangeOptions, VPCIPv6RangeOptions
66
from linode_api4.objects.base import _flatten_request_body_recursive
77
from linode_api4.paginated_list import PaginatedList
88
from linode_api4.util import drop_null_keys
@@ -36,6 +36,7 @@ def create(
3636
description: Optional[str] = None,
3737
subnets: Optional[List[Dict[str, Any]]] = None,
3838
ipv6: Optional[List[Union[VPCIPv6RangeOptions, Dict[str, Any]]]] = None,
39+
ipv4: Optional[List[Union[VPCIPv4RangeOptions, Dict[str, Any]]]] = None,
3940
**kwargs,
4041
) -> VPC:
4142
"""
@@ -53,6 +54,8 @@ def create(
5354
:type subnets: List[Dict[str, Any]]
5455
:param ipv6: The IPv6 address ranges for this VPC.
5556
:type ipv6: List[Union[VPCIPv6RangeOptions, Dict[str, Any]]]
57+
:param ipv4: The IPv4 address ranges for this VPC.
58+
:type ipv4: List[Union[VPCIPv4RangeOptions, Dict[str, Any]]]
5659
5760
:returns: The new VPC object.
5861
:rtype: VPC
@@ -61,6 +64,7 @@ def create(
6164
"label": label,
6265
"region": region.id if isinstance(region, Region) else region,
6366
"description": description,
67+
"ipv4": ipv4,
6468
"ipv6": ipv6,
6569
"subnets": subnets,
6670
}
@@ -108,3 +112,15 @@ def ips(self, *filters) -> PaginatedList:
108112
return self.client._get_and_filter(
109113
VPCIPAddress, *filters, endpoint="/vpcs/ips"
110114
)
115+
116+
def default_ranges(self) -> VPCIPv4DefaultRange:
117+
"""
118+
Retrieve the default settings for the internal and forbidden IPv4 address ranges in VPCs.
119+
120+
API Documentation: TODO
121+
122+
:returns: The default IPv4 ranges for VPCs.
123+
:rtype: VPCIPv4DefaultRange
124+
"""
125+
result = self.client.get("/vpcs/default-ranges")
126+
return VPCIPv4DefaultRange.from_json(result)

linode_api4/objects/vpc.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,34 @@
1010
from linode_api4.util import drop_null_keys
1111

1212

13+
@dataclass
14+
class VPCIPv4DefaultRange(JSONObject):
15+
"""
16+
VPCIPv4DefaultRange represents the default settings for the internal and forbidden IPv4 address ranges in VPCs.
17+
"""
18+
19+
ipv4_ranges: Optional[List[str]] = None
20+
forbidden_ipv4_ranges: Optional[List[str]] = None
21+
22+
@dataclass
23+
class VPCIPv4RangeOptions(JSONObject):
24+
"""
25+
VPCIPv4RangeOptions is used to specify an IPv4 range when creating or updating a VPC.
26+
"""
27+
28+
range: Optional[str] = None
29+
30+
31+
@dataclass
32+
class VPCIPv4Range(JSONObject):
33+
"""
34+
VPCIPv4Range represents a single VPC IPv4 range.
35+
"""
36+
37+
put_class = VPCIPv4RangeOptions
38+
39+
range: str = ""
40+
1341
@dataclass
1442
class VPCIPv6RangeOptions(JSONObject):
1543
"""
@@ -30,7 +58,6 @@ class VPCIPv6Range(JSONObject):
3058

3159
range: str = ""
3260

33-
3461
@dataclass
3562
class VPCSubnetIPv6RangeOptions(JSONObject):
3663
"""
@@ -108,6 +135,7 @@ class VPC(Base):
108135
"label": Property(mutable=True),
109136
"description": Property(mutable=True),
110137
"region": Property(slug_relationship=Region),
138+
"ipv4": Property(json_object=VPCIPv4Range, mutable=True, unordered=True),
111139
"ipv6": Property(json_object=VPCIPv6Range, unordered=True),
112140
"subnets": Property(derived_class=VPCSubnet),
113141
"created": Property(is_datetime=True),

test/fixtures/vpcs.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@
55
"id": 123456,
66
"description": "A very real VPC.",
77
"region": "us-southeast",
8+
"ipv4": [
9+
{
10+
"range": "10.0.0.0/8"
11+
}
12+
],
813
"ipv6": [
914
{
1015
"range": "fd71:1140:a9d0::/52"

test/fixtures/vpcs_123456.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@
33
"id": 123456,
44
"description": "A very real VPC.",
55
"region": "us-southeast",
6+
"ipv4": [
7+
{
8+
"range": "10.0.0.0/8"
9+
}
10+
],
611
"ipv6": [
712
{
813
"range": "fd71:1140:a9d0::/52"
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"ipv4_ranges": [
3+
"10.0.0.0/8",
4+
"192.168.0.0/17"
5+
],
6+
"forbidden_ipv4_ranges": [
7+
"172.17.0.0/16"
8+
]
9+
}
10+

test/integration/models/vpc/test_vpc.py

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
from test.integration.conftest import get_region
2+
from test.integration.helpers import get_test_label
23

34
import pytest
45

5-
from linode_api4 import VPC, ApiError, VPCSubnet
6+
from linode_api4 import VPC, ApiError, VPCIPv4DefaultRange, VPCSubnet
67

78

89
@pytest.mark.smoke
@@ -138,3 +139,61 @@ def test_get_vpc_ipv6s(test_linode_client):
138139
assert "vpc_id" in ipv6
139140
assert isinstance(ipv6["ipv6_range"], str)
140141
assert isinstance(ipv6["ipv6_addresses"], list)
142+
143+
144+
def test_get_vpc_default_ranges(test_linode_client):
145+
"""
146+
Tests that VPC default IPv4 ranges can be retrieved.
147+
"""
148+
result = test_linode_client.vpcs.default_ranges()
149+
150+
assert isinstance(result, VPCIPv4DefaultRange)
151+
assert isinstance(result.ipv4_ranges, list)
152+
assert len(result.ipv4_ranges) > 0
153+
assert isinstance(result.forbidden_ipv4_ranges, list)
154+
assert len(result.forbidden_ipv4_ranges) > 0
155+
156+
157+
def test_vpc_with_ipv4(test_linode_client):
158+
"""
159+
Tests creating a VPC with ipv4 ranges, getting/listing it,
160+
updating the ipv4, and deleting it.
161+
"""
162+
client = test_linode_client
163+
label = get_test_label(length=10)
164+
region = get_region(client, {"VPCs", "Custom VPC IPv4 Ranges"})
165+
166+
# Create
167+
vpc = client.vpcs.create(
168+
label=label,
169+
region=region,
170+
description="integration test vpc with ipv4",
171+
ipv4=[{"range": "10.0.0.0/8"}],
172+
)
173+
174+
try:
175+
assert vpc.id is not None
176+
assert vpc.label == label
177+
assert len(vpc.ipv4) > 0
178+
assert vpc.ipv4[0].range == "10.0.0.0/8"
179+
180+
# Get by ID
181+
loaded_vpc = client.load(VPC, vpc.id)
182+
assert loaded_vpc.id == vpc.id
183+
assert loaded_vpc.ipv4[0].range == "10.0.0.0/8"
184+
185+
# List and verify present
186+
all_vpcs = client.vpcs()
187+
vpc_ids = [v.id for v in all_vpcs]
188+
assert vpc.id in vpc_ids
189+
190+
# Update ipv4
191+
vpc.ipv4 = [{"range": "192.168.0.0/17"}]
192+
vpc.save()
193+
194+
updated_vpc = client.load(VPC, vpc.id)
195+
assert updated_vpc.ipv4[0].range == "192.168.0.0/17"
196+
finally:
197+
# Delete
198+
vpc.delete()
199+

test/unit/groups/vpc_test.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import datetime
22
from test.unit.base import ClientBaseCase
33

4-
from linode_api4 import DATE_FORMAT, VPC, VPCSubnet
4+
from linode_api4 import DATE_FORMAT, VPC, VPCSubnet, VPCIPv4DefaultRange
55

66

77
class VPCTest(ClientBaseCase):
@@ -95,6 +95,8 @@ def validate_vpc_123456(self, vpc: VPC):
9595
self.assertEqual(vpc.created, expected_dt)
9696
self.assertEqual(vpc.updated, expected_dt)
9797

98+
self.assertEqual(vpc.ipv4[0].range, "10.0.0.0/8")
99+
98100
def validate_vpc_subnet_789(self, subnet: VPCSubnet):
99101
expected_dt = datetime.datetime.strptime(
100102
"2018-01-01T00:01:01", DATE_FORMAT
@@ -105,3 +107,17 @@ def validate_vpc_subnet_789(self, subnet: VPCSubnet):
105107
self.assertEqual(subnet.linodes[0].id, 12345)
106108
self.assertEqual(subnet.created, expected_dt)
107109
self.assertEqual(subnet.updated, expected_dt)
110+
111+
def test_default_ranges(self):
112+
"""
113+
Tests that VPC default ranges can be retrieved.
114+
"""
115+
116+
with self.mock_get("/vpcs/default-ranges") as m:
117+
result = self.client.vpcs.default_ranges()
118+
119+
self.assertEqual(m.call_url, "/vpcs/default-ranges")
120+
self.assertIsInstance(result, VPCIPv4DefaultRange)
121+
self.assertEqual(result.ipv4_ranges, ["10.0.0.0/8", "192.168.0.0/17"])
122+
self.assertEqual(result.forbidden_ipv4_ranges, ["172.17.0.0/16"])
123+

test/unit/objects/vpc_test.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ def validate_vpc_123456(self, vpc: VPC):
113113
self.assertEqual(vpc.created, expected_dt)
114114
self.assertEqual(vpc.updated, expected_dt)
115115

116+
self.assertEqual(vpc.ipv4[0].range, "10.0.0.0/8")
116117
self.assertEqual(vpc.ipv6[0].range, "fd71:1140:a9d0::/52")
117118

118119
def validate_vpc_subnet_789(self, subnet: VPCSubnet):

0 commit comments

Comments
 (0)