Skip to content

Commit a6f0b01

Browse files
Merge pull request #611 from linode/dev
Release v5.37.0
2 parents dcb43b1 + 4f74fae commit a6f0b01

32 files changed

+983
-221
lines changed

linode_api4/groups/database.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from linode_api4.objects import (
1010
Database,
1111
DatabaseEngine,
12+
DatabasePrivateNetwork,
1213
DatabaseType,
1314
MySQLDatabase,
1415
PostgreSQLDatabase,
@@ -126,6 +127,7 @@ def mysql_create(
126127
engine,
127128
ltype,
128129
engine_config: Union[MySQLDatabaseConfigOptions, Dict[str, Any]] = None,
130+
private_network: Union[DatabasePrivateNetwork, Dict[str, Any]] = None,
129131
**kwargs,
130132
):
131133
"""
@@ -159,6 +161,8 @@ def mysql_create(
159161
:type ltype: str or Type
160162
:param engine_config: The configuration options for this MySQL cluster
161163
:type engine_config: Dict[str, Any] or MySQLDatabaseConfigOptions
164+
:param private_network: The private network settings to use for this cluster
165+
:type private_network: Dict[str, Any] or DatabasePrivateNetwork
162166
"""
163167

164168
params = {
@@ -167,6 +171,7 @@ def mysql_create(
167171
"engine": engine,
168172
"type": ltype,
169173
"engine_config": engine_config,
174+
"private_network": private_network,
170175
}
171176
params.update(kwargs)
172177

@@ -262,6 +267,7 @@ def postgresql_create(
262267
engine_config: Union[
263268
PostgreSQLDatabaseConfigOptions, Dict[str, Any]
264269
] = None,
270+
private_network: Union[DatabasePrivateNetwork, Dict[str, Any]] = None,
265271
**kwargs,
266272
):
267273
"""
@@ -295,6 +301,8 @@ def postgresql_create(
295301
:type ltype: str or Type
296302
:param engine_config: The configuration options for this PostgreSQL cluster
297303
:type engine_config: Dict[str, Any] or PostgreSQLDatabaseConfigOptions
304+
:param private_network: The private network settings to use for this cluster
305+
:type private_network: Dict[str, Any] or DatabasePrivateNetwork
298306
"""
299307

300308
params = {
@@ -303,6 +311,7 @@ def postgresql_create(
303311
"engine": engine,
304312
"type": ltype,
305313
"engine_config": engine_config,
314+
"private_network": private_network,
306315
}
307316
params.update(kwargs)
308317

linode_api4/groups/vpc.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22

33
from linode_api4.errors import UnexpectedResponseError
44
from linode_api4.groups import Group
5-
from linode_api4.objects import VPC, Region, VPCIPAddress
5+
from linode_api4.objects import VPC, Region, VPCIPAddress, VPCIPv6RangeOptions
6+
from linode_api4.objects.base import _flatten_request_body_recursive
67
from linode_api4.paginated_list import PaginatedList
8+
from linode_api4.util import drop_null_keys
79

810

911
class VPCGroup(Group):
@@ -33,6 +35,7 @@ def create(
3335
region: Union[Region, str],
3436
description: Optional[str] = None,
3537
subnets: Optional[List[Dict[str, Any]]] = None,
38+
ipv6: Optional[List[Union[VPCIPv6RangeOptions, Dict[str, Any]]]] = None,
3639
**kwargs,
3740
) -> VPC:
3841
"""
@@ -48,30 +51,33 @@ def create(
4851
:type description: Optional[str]
4952
:param subnets: A list of subnets to create under this VPC.
5053
:type subnets: List[Dict[str, Any]]
54+
:param ipv6: The IPv6 address ranges for this VPC.
55+
:type ipv6: List[Union[VPCIPv6RangeOptions, Dict[str, Any]]]
5156
5257
:returns: The new VPC object.
5358
:rtype: VPC
5459
"""
5560
params = {
5661
"label": label,
5762
"region": region.id if isinstance(region, Region) else region,
63+
"description": description,
64+
"ipv6": ipv6,
65+
"subnets": subnets,
5866
}
5967

60-
if description is not None:
61-
params["description"] = description
62-
6368
if subnets is not None and len(subnets) > 0:
6469
for subnet in subnets:
6570
if not isinstance(subnet, dict):
6671
raise ValueError(
6772
f"Unsupported type for subnet: {type(subnet)}"
6873
)
6974

70-
params["subnets"] = subnets
71-
7275
params.update(kwargs)
7376

74-
result = self.client.post("/vpcs", data=params)
77+
result = self.client.post(
78+
"/vpcs",
79+
data=drop_null_keys(_flatten_request_body_recursive(params)),
80+
)
7581

7682
if not "id" in result:
7783
raise UnexpectedResponseError(

linode_api4/objects/database.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,18 @@ def invalidate(self):
7474
Base.invalidate(self)
7575

7676

77+
@dataclass
78+
class DatabasePrivateNetwork(JSONObject):
79+
"""
80+
DatabasePrivateNetwork is used to specify
81+
a Database Cluster's private network settings during its creation.
82+
"""
83+
84+
vpc_id: Optional[int] = None
85+
subnet_id: Optional[int] = None
86+
public_access: Optional[bool] = None
87+
88+
7789
@deprecated(
7890
reason="Backups are not supported for non-legacy database clusters."
7991
)
@@ -304,6 +316,9 @@ class MySQLDatabase(Base):
304316
"engine_config": Property(
305317
mutable=True, json_object=MySQLDatabaseConfigOptions
306318
),
319+
"private_network": Property(
320+
mutable=True, json_object=DatabasePrivateNetwork, nullable=True
321+
),
307322
}
308323

309324
@property
@@ -470,6 +485,9 @@ class PostgreSQLDatabase(Base):
470485
"engine_config": Property(
471486
mutable=True, json_object=PostgreSQLDatabaseConfigOptions
472487
),
488+
"private_network": Property(
489+
mutable=True, json_object=DatabasePrivateNetwork, nullable=True
490+
),
473491
}
474492

475493
@property
@@ -636,6 +654,9 @@ class Database(Base):
636654
"updated": Property(),
637655
"updates": Property(),
638656
"version": Property(),
657+
"private_network": Property(
658+
json_object=DatabasePrivateNetwork, nullable=True
659+
),
639660
}
640661

641662
@property

linode_api4/objects/linode.py

Lines changed: 99 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -300,10 +300,83 @@ def _populate(self, json):
300300

301301
@dataclass
302302
class ConfigInterfaceIPv4(JSONObject):
303+
"""
304+
ConfigInterfaceIPv4 represents the IPv4 configuration of a VPC interface.
305+
"""
306+
303307
vpc: str = ""
304308
nat_1_1: str = ""
305309

306310

311+
@dataclass
312+
class ConfigInterfaceIPv6SLAACOptions(JSONObject):
313+
"""
314+
ConfigInterfaceIPv6SLAACOptions is used to set a single IPv6 SLAAC configuration of a VPC interface.
315+
"""
316+
317+
range: str = ""
318+
319+
320+
@dataclass
321+
class ConfigInterfaceIPv6RangeOptions(JSONObject):
322+
"""
323+
ConfigInterfaceIPv6RangeOptions is used to set a single IPv6 range configuration of a VPC interface.
324+
"""
325+
326+
range: str = ""
327+
328+
329+
@dataclass
330+
class ConfigInterfaceIPv6Options(JSONObject):
331+
"""
332+
ConfigInterfaceIPv6Options is used to set the IPv6 configuration of a VPC interface.
333+
"""
334+
335+
slaac: List[ConfigInterfaceIPv6SLAACOptions] = field(
336+
default_factory=lambda: []
337+
)
338+
ranges: List[ConfigInterfaceIPv6RangeOptions] = field(
339+
default_factory=lambda: []
340+
)
341+
is_public: bool = False
342+
343+
344+
@dataclass
345+
class ConfigInterfaceIPv6SLAAC(JSONObject):
346+
"""
347+
ConfigInterfaceIPv6SLAAC represents a single SLAAC address under a VPC interface's IPv6 configuration.
348+
"""
349+
350+
put_class = ConfigInterfaceIPv6SLAACOptions
351+
352+
range: str = ""
353+
address: str = ""
354+
355+
356+
@dataclass
357+
class ConfigInterfaceIPv6Range(JSONObject):
358+
"""
359+
ConfigInterfaceIPv6Range represents a single IPv6 address under a VPC interface's IPv6 configuration.
360+
"""
361+
362+
put_class = ConfigInterfaceIPv6RangeOptions
363+
364+
range: str = ""
365+
366+
367+
@dataclass
368+
class ConfigInterfaceIPv6(JSONObject):
369+
"""
370+
ConfigInterfaceIPv6 represents the IPv6 configuration of a VPC interface.
371+
"""
372+
373+
put_class = ConfigInterfaceIPv6Options
374+
375+
slaac: List[ConfigInterfaceIPv6SLAAC] = field(default_factory=lambda: [])
376+
ranges: List[ConfigInterfaceIPv6Range] = field(default_factory=lambda: [])
377+
is_public: bool = False
378+
379+
307380
class NetworkInterface(DerivedBase):
308381
"""
309382
This class represents a Configuration Profile's network interface object.
@@ -329,6 +402,7 @@ class NetworkInterface(DerivedBase):
329402
"vpc_id": Property(id_relationship=VPC),
330403
"subnet_id": Property(),
331404
"ipv4": Property(mutable=True, json_object=ConfigInterfaceIPv4),
405+
"ipv6": Property(mutable=True, json_object=ConfigInterfaceIPv6),
332406
"ip_ranges": Property(mutable=True),
333407
}
334408

@@ -400,7 +474,10 @@ class ConfigInterface(JSONObject):
400474
# VPC-specific
401475
vpc_id: Optional[int] = None
402476
subnet_id: Optional[int] = None
477+
403478
ipv4: Optional[Union[ConfigInterfaceIPv4, Dict[str, Any]]] = None
479+
ipv6: Optional[Union[ConfigInterfaceIPv6, Dict[str, Any]]] = None
480+
404481
ip_ranges: Optional[List[str]] = None
405482

406483
# Computed
@@ -409,7 +486,7 @@ class ConfigInterface(JSONObject):
409486
def __repr__(self):
410487
return f"Interface: {self.purpose}"
411488

412-
def _serialize(self, *args, **kwargs):
489+
def _serialize(self, is_put: bool = False):
413490
purpose_formats = {
414491
"public": {"purpose": "public", "primary": self.primary},
415492
"vlan": {
@@ -421,11 +498,8 @@ def _serialize(self, *args, **kwargs):
421498
"purpose": "vpc",
422499
"primary": self.primary,
423500
"subnet_id": self.subnet_id,
424-
"ipv4": (
425-
self.ipv4.dict
426-
if isinstance(self.ipv4, ConfigInterfaceIPv4)
427-
else self.ipv4
428-
),
501+
"ipv4": self.ipv4,
502+
"ipv6": self.ipv6,
429503
"ip_ranges": self.ip_ranges,
430504
},
431505
}
@@ -435,11 +509,14 @@ def _serialize(self, *args, **kwargs):
435509
f"Unknown interface purpose: {self.purpose}",
436510
)
437511

438-
return {
439-
k: v
440-
for k, v in purpose_formats[self.purpose].items()
441-
if v is not None
442-
}
512+
return _flatten_request_body_recursive(
513+
{
514+
k: v
515+
for k, v in purpose_formats[self.purpose].items()
516+
if v is not None
517+
},
518+
is_put=is_put,
519+
)
443520

444521

445522
class Config(DerivedBase):
@@ -580,6 +657,7 @@ def interface_create_vpc(
580657
subnet: Union[int, VPCSubnet],
581658
primary=False,
582659
ipv4: Union[Dict[str, Any], ConfigInterfaceIPv4] = None,
660+
ipv6: Union[Dict[str, Any], ConfigInterfaceIPv6Options] = None,
583661
ip_ranges: Optional[List[str]] = None,
584662
) -> NetworkInterface:
585663
"""
@@ -593,6 +671,8 @@ def interface_create_vpc(
593671
:type primary: bool
594672
:param ipv4: The IPv4 configuration of the interface for the associated subnet.
595673
:type ipv4: Dict or ConfigInterfaceIPv4
674+
:param ipv6: The IPv6 configuration of the interface for the associated subnet.
675+
:type ipv6: Dict or ConfigInterfaceIPv6Options
596676
:param ip_ranges: A list of IPs or IP ranges in the VPC subnet.
597677
Packets to these CIDRs are routed through the
598678
VPC network interface.
@@ -603,19 +683,16 @@ def interface_create_vpc(
603683
"""
604684
params = {
605685
"purpose": "vpc",
606-
"subnet_id": subnet.id if isinstance(subnet, VPCSubnet) else subnet,
686+
"subnet_id": subnet,
607687
"primary": primary,
688+
"ipv4": ipv4,
689+
"ipv6": ipv6,
690+
"ip_ranges": ip_ranges,
608691
}
609692

610-
if ipv4 is not None:
611-
params["ipv4"] = (
612-
ipv4.dict if isinstance(ipv4, ConfigInterfaceIPv4) else ipv4
613-
)
614-
615-
if ip_ranges is not None:
616-
params["ip_ranges"] = ip_ranges
617-
618-
return self._interface_create(params)
693+
return self._interface_create(
694+
drop_null_keys(_flatten_request_body_recursive(params))
695+
)
619696

620697
def interface_reorder(self, interfaces: List[Union[int, NetworkInterface]]):
621698
"""
@@ -2018,6 +2095,7 @@ def linode_interfaces(self) -> Optional[list[LinodeInterface]]:
20182095

20192096
if self.interface_generation != InterfaceGeneration.LINODE:
20202097
return None
2098+
20212099
if not hasattr(self, "_interfaces"):
20222100
result = self._client.get(
20232101
"{}/interfaces".format(Instance.api_endpoint),

0 commit comments

Comments
 (0)