Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion linode_api4/groups/vpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@

from linode_api4.errors import UnexpectedResponseError
from linode_api4.groups import Group
from linode_api4.objects import VPC, Region, VPCIPAddress, VPCIPv6RangeOptions
from linode_api4.objects import (
VPC,
Region,
VPCIPAddress,
VPCIPv4DefaultRange,
VPCIPv4RangeOptions,
VPCIPv6RangeOptions,
)
from linode_api4.objects.base import _flatten_request_body_recursive
from linode_api4.paginated_list import PaginatedList
from linode_api4.util import drop_null_keys
Expand Down Expand Up @@ -36,6 +43,7 @@ def create(
description: Optional[str] = None,
subnets: Optional[List[Dict[str, Any]]] = None,
ipv6: Optional[List[Union[VPCIPv6RangeOptions, Dict[str, Any]]]] = None,
ipv4: Optional[List[Union[VPCIPv4RangeOptions, Dict[str, Any]]]] = None,
**kwargs,
) -> VPC:
"""
Expand All @@ -53,6 +61,8 @@ def create(
:type subnets: List[Dict[str, Any]]
:param ipv6: The IPv6 address ranges for this VPC.
:type ipv6: List[Union[VPCIPv6RangeOptions, Dict[str, Any]]]
:param ipv4: The IPv4 address ranges for this VPC. Note that IPv4 VPCs may not currently be available to all users.
:type ipv4: List[Union[VPCIPv4RangeOptions, Dict[str, Any]]]

:returns: The new VPC object.
:rtype: VPC
Expand All @@ -61,6 +71,7 @@ def create(
"label": label,
"region": region.id if isinstance(region, Region) else region,
"description": description,
"ipv4": ipv4,
"ipv6": ipv6,
"subnets": subnets,
}
Expand Down Expand Up @@ -108,3 +119,15 @@ def ips(self, *filters) -> PaginatedList:
return self.client._get_and_filter(
VPCIPAddress, *filters, endpoint="/vpcs/ips"
)

def default_ranges(self) -> VPCIPv4DefaultRange:
"""
Retrieve the default settings for the internal and forbidden IPv4 address ranges in VPCs.

API Documentation: TODO

:returns: The default IPv4 ranges for VPCs.
:rtype: VPCIPv4DefaultRange
"""
result = self.client.get("/vpcs/default-ranges")
return VPCIPv4DefaultRange.from_json(result)
34 changes: 34 additions & 0 deletions linode_api4/objects/vpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,36 @@
from linode_api4.util import drop_null_keys


@dataclass
class VPCIPv4DefaultRange(JSONObject):
"""
VPCIPv4DefaultRange represents the default settings for the internal and forbidden IPv4 address ranges in VPCs.
"""

default_ipv4_ranges: Optional[List[str]] = None
forbidden_ipv4_ranges: Optional[List[str]] = None


@dataclass
class VPCIPv4RangeOptions(JSONObject):
"""
VPCIPv4RangeOptions is used to specify an IPv4 range when creating or updating a VPC.
"""

range: Optional[str] = None


@dataclass
class VPCIPv4Range(JSONObject):
"""
VPCIPv4Range represents a single VPC IPv4 range.
"""

put_class = VPCIPv4RangeOptions

range: str = ""


@dataclass
class VPCIPv6RangeOptions(JSONObject):
"""
Expand Down Expand Up @@ -108,6 +138,10 @@ class VPC(Base):
"label": Property(mutable=True),
"description": Property(mutable=True),
"region": Property(slug_relationship=Region),
# Note that IPv4 VPCs may not currently be available to all users.
"ipv4": Property(
json_object=VPCIPv4Range, mutable=True, unordered=True
),
"ipv6": Property(json_object=VPCIPv6Range, unordered=True),
"subnets": Property(derived_class=VPCSubnet),
"created": Property(is_datetime=True),
Expand Down
5 changes: 5 additions & 0 deletions test/fixtures/vpcs.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@
"id": 123456,
"description": "A very real VPC.",
"region": "us-southeast",
"ipv4": [
{
"range": "10.0.0.0/8"
}
],
"ipv6": [
{
"range": "fd71:1140:a9d0::/52"
Expand Down
5 changes: 5 additions & 0 deletions test/fixtures/vpcs_123456.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
"id": 123456,
"description": "A very real VPC.",
"region": "us-southeast",
"ipv4": [
{
"range": "10.0.0.0/8"
}
],
"ipv6": [
{
"range": "fd71:1140:a9d0::/52"
Expand Down
10 changes: 10 additions & 0 deletions test/fixtures/vpcs_default-ranges.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"default_ipv4_ranges": [
"10.0.0.0/8",
"192.168.0.0/17"
],
"forbidden_ipv4_ranges": [
"172.17.0.0/16"
]
}

16 changes: 16 additions & 0 deletions test/integration/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,22 @@ def create_vpc_with_subnet_and_linode(
instance.delete()


@pytest.fixture
def create_vpc_with_ipv4(test_linode_client):
client = test_linode_client

vpc = client.vpcs.create(
label=get_test_label(length=10),
region=get_region(client, {"VPCs", "Custom VPC IPv4 Ranges"}),
description="integration test vpc with ipv4",
ipv4=[{"range": "10.0.0.0/8"}],
)

yield vpc

vpc.delete()


@pytest.fixture(scope="session")
def create_multiple_vpcs(test_linode_client):
client = test_linode_client
Expand Down
35 changes: 34 additions & 1 deletion test/integration/models/vpc/test_vpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import pytest

from linode_api4 import VPC, ApiError, VPCSubnet
from linode_api4 import VPC, ApiError, VPCIPv4DefaultRange, VPCSubnet


@pytest.mark.smoke
Expand Down Expand Up @@ -138,3 +138,36 @@ def test_get_vpc_ipv6s(test_linode_client):
assert "vpc_id" in ipv6
assert isinstance(ipv6["ipv6_range"], str)
assert isinstance(ipv6["ipv6_addresses"], list)


def test_get_vpc_default_ranges(test_linode_client):
"""
Tests that VPC default IPv4 ranges can be retrieved.
"""
result = test_linode_client.vpcs.default_ranges()

assert isinstance(result, VPCIPv4DefaultRange)
assert isinstance(result.default_ipv4_ranges, list)
assert len(result.default_ipv4_ranges) > 0
assert isinstance(result.forbidden_ipv4_ranges, list)
assert len(result.forbidden_ipv4_ranges) > 0


def test_vpc_with_ipv4(test_linode_client, create_vpc_with_ipv4):
client = test_linode_client
vpc = create_vpc_with_ipv4

assert vpc.id is not None
assert vpc.ipv4[0].range == "10.0.0.0/8"

loaded_vpc = client.load(VPC, vpc.id)
assert loaded_vpc.ipv4[0].range == "10.0.0.0/8"

all_vpcs = client.vpcs()
assert vpc.id in [v.id for v in all_vpcs]

vpc.ipv4 = [{"range": "192.168.0.0/17"}]
vpc.save()

updated_vpc = client.load(VPC, vpc.id)
assert updated_vpc.ipv4[0].range == "192.168.0.0/17"
19 changes: 18 additions & 1 deletion test/unit/groups/vpc_test.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import datetime
from test.unit.base import ClientBaseCase

from linode_api4 import DATE_FORMAT, VPC, VPCSubnet
from linode_api4 import DATE_FORMAT, VPC, VPCIPv4DefaultRange, VPCSubnet


class VPCTest(ClientBaseCase):
Expand Down Expand Up @@ -95,6 +95,8 @@ def validate_vpc_123456(self, vpc: VPC):
self.assertEqual(vpc.created, expected_dt)
self.assertEqual(vpc.updated, expected_dt)

self.assertEqual(vpc.ipv4[0].range, "10.0.0.0/8")

def validate_vpc_subnet_789(self, subnet: VPCSubnet):
expected_dt = datetime.datetime.strptime(
"2018-01-01T00:01:01", DATE_FORMAT
Expand All @@ -105,3 +107,18 @@ def validate_vpc_subnet_789(self, subnet: VPCSubnet):
self.assertEqual(subnet.linodes[0].id, 12345)
self.assertEqual(subnet.created, expected_dt)
self.assertEqual(subnet.updated, expected_dt)

def test_default_ranges(self):
"""
Tests that VPC default ranges can be retrieved.
"""

with self.mock_get("/vpcs/default-ranges") as m:
result = self.client.vpcs.default_ranges()

self.assertEqual(m.call_url, "/vpcs/default-ranges")
self.assertIsInstance(result, VPCIPv4DefaultRange)
self.assertEqual(
result.default_ipv4_ranges, ["10.0.0.0/8", "192.168.0.0/17"]
)
self.assertEqual(result.forbidden_ipv4_ranges, ["172.17.0.0/16"])
1 change: 1 addition & 0 deletions test/unit/objects/vpc_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ def validate_vpc_123456(self, vpc: VPC):
self.assertEqual(vpc.created, expected_dt)
self.assertEqual(vpc.updated, expected_dt)

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

def validate_vpc_subnet_789(self, subnet: VPCSubnet):
Expand Down