Skip to content

Commit 6c5fd4f

Browse files
authored
Merge pull request #739 from linode/dev
Release 5.47.0
2 parents b3a0c5a + 280efee commit 6c5fd4f

25 files changed

Lines changed: 666 additions & 56 deletions

‎.github/workflows/publish-pypi.yaml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,4 @@ jobs:
2828
LINODE_SDK_VERSION: ${{ github.event.release.tag_name }}
2929

3030
- name: Publish the release artifacts to PyPI
31-
uses: pypa/gh-action-pypi-publish@ba38be9e461d3875417946c167d0b5f3d385a247 # pin@release/v1.14.1
31+
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # pin@release/v1.14.2

‎linode_api4/groups/vpc.py‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
VPCIPv6RangeOptions,
1212
)
1313
from linode_api4.objects.base import _flatten_request_body_recursive
14+
from linode_api4.objects.vpc import VPCType
1415
from linode_api4.paginated_list import PaginatedList
1516
from linode_api4.util import drop_null_keys
1617

@@ -44,6 +45,7 @@ def create(
4445
subnets: Optional[List[Dict[str, Any]]] = None,
4546
ipv6: Optional[List[Union[VPCIPv6RangeOptions, Dict[str, Any]]]] = None,
4647
ipv4: Optional[List[Union[VPCIPv4RangeOptions, Dict[str, Any]]]] = None,
48+
vpc_type: Optional[Union[VPCType, str]] = None,
4749
**kwargs,
4850
) -> VPC:
4951
"""
@@ -61,6 +63,11 @@ def create(
6163
:type subnets: List[Dict[str, Any]]
6264
:param ipv6: The IPv6 address ranges for this VPC.
6365
:type ipv6: List[Union[VPCIPv6RangeOptions, Dict[str, Any]]]
66+
:param vpc_type: The type of VPC to create. Defaults to ``regular`` on
67+
the API side. Set to ``rdma`` to create a GPUDirect
68+
RDMA VPC (requires the ``GPUDirect RDMA`` account
69+
capability).
70+
:type vpc_type: Optional[Union[VPCType, str]]
6471
:param ipv4: The IPv4 address ranges for this VPC. Note that IPv4 VPCs may not currently be available to all users.
6572
:type ipv4: List[Union[VPCIPv4RangeOptions, Dict[str, Any]]]
6673
@@ -74,6 +81,7 @@ def create(
7481
"ipv4": ipv4,
7582
"ipv6": ipv6,
7683
"subnets": subnets,
84+
"vpc_type": vpc_type,
7785
}
7886

7987
if subnets is not None and len(subnets) > 0:

‎linode_api4/login_client.py‎

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,11 @@ class OAuthScopes:
3030
Lists of OAuth Scopes are accepted when calling the :any:`generate_login_url`
3131
method of the :any:`LinodeLoginClient`.
3232
33-
All contained enumerations of OAuth Scopes have two levels, "read_only" and
33+
Most contained enumerations of OAuth Scopes have two levels, "read_only" and
3434
"read_write". "read_only" access grants you the ability to get resources and
3535
of that type, but not to change, create, or delete them. "read_write" access
36-
allows to full access to resources of the requested type. In the above
36+
allows to full access to resources of the requested type. :any:`OAuthScopes.VPC`
37+
is the exception and exposes only "read_write" and "all". In the above
3738
example, you are requesting access to view, modify, create, and delete
3839
Linodes, and to view Domains.
3940
"""
@@ -254,6 +255,47 @@ def __repr__(self):
254255
return "longview:*"
255256
return "longview:{}".format(self.name)
256257

258+
class Images(Enum):
259+
"""
260+
Access to Images
261+
"""
262+
263+
read_only = 0
264+
read_write = 1
265+
all = 2
266+
267+
def __repr__(self):
268+
if self.name == "all":
269+
return "images:*"
270+
return "images:{}".format(self.name)
271+
272+
class Databases(Enum):
273+
"""
274+
Access to Managed Databases
275+
"""
276+
277+
read_only = 0
278+
read_write = 1
279+
all = 2
280+
281+
def __repr__(self):
282+
if self.name == "all":
283+
return "databases:*"
284+
return "databases:{}".format(self.name)
285+
286+
class VPC(Enum):
287+
"""
288+
Access to VPCs and subnets
289+
"""
290+
291+
read_write = 1
292+
all = 2
293+
294+
def __repr__(self):
295+
if self.name == "all":
296+
return "vpc:*"
297+
return "vpc:{}".format(self.name)
298+
257299
_scope_families = {
258300
"linodes": Linodes,
259301
"domains": Domains,
@@ -271,6 +313,9 @@ def __repr__(self):
271313
"object_storage": ObjectStorage,
272314
"nodebalancers": NodeBalancers,
273315
"longview": Longview,
316+
"images": Images,
317+
"databases": Databases,
318+
"vpc": VPC,
274319
}
275320

276321
@staticmethod

‎linode_api4/objects/linode.py‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2115,10 +2115,22 @@ def interface_create(
21152115
:param vpc: The VPC-specific configuration of the new interface.
21162116
If set, the new instance will be a VPC interface.
21172117
2118+
.. note::
2119+
RDMA VPC interfaces (``rdma_vpc``) cannot be added via this
2120+
endpoint. They may only be specified at instance creation time via
2121+
:func:`linode_api4.LinodeGroup.instance_create`.
2122+
21182123
:returns: The newly created Linode Interface.
21192124
:rtype: LinodeInterface
21202125
"""
21212126

2127+
if kwargs.get("rdma_vpc") is not None:
2128+
raise ValueError(
2129+
"RDMA VPC interfaces (rdma_vpc) cannot be added via "
2130+
"interface_create(). They may only be specified at instance "
2131+
"creation time via LinodeGroup.instance_create()."
2132+
)
2133+
21222134
params = {
21232135
"firewall_id": firewall,
21242136
"default_route": default_route,

‎linode_api4/objects/linode_interfaces.py‎

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,47 @@ class LinodeInterfaceVLANOptions(JSONObject):
187187
ipam_address: Optional[str] = None
188188

189189

190+
@dataclass
191+
class LinodeInterfaceRDMAVPCIPv4AddressOptions(JSONObject):
192+
"""
193+
Options accepted for a single address when creating or updating the IPv4
194+
configuration of an RDMA VPC Linode Interface.
195+
196+
Only one address is supported per RDMA VPC interface, and it must be
197+
marked as primary.
198+
"""
199+
200+
address: Optional[str] = None
201+
primary: Optional[bool] = None
202+
203+
204+
@dataclass
205+
class LinodeInterfaceRDMAVPCIPv4Options(JSONObject):
206+
"""
207+
Options accepted when creating or updating the IPv4 configuration of an
208+
RDMA VPC Linode Interface.
209+
210+
The ``addresses`` list MUST contain exactly one element. If omitted, the
211+
API defaults to a single primary ``auto`` address.
212+
"""
213+
214+
addresses: Optional[List[LinodeInterfaceRDMAVPCIPv4AddressOptions]] = None
215+
216+
217+
@dataclass
218+
class LinodeInterfaceRDMAVPCOptions(JSONObject):
219+
"""
220+
RDMA-VPC-exclusive options accepted when creating or updating a Linode
221+
Interface.
222+
223+
Used for GPUDirect RDMA interfaces. Default routes and NAT 1:1 addresses
224+
are not supported on RDMA VPC interfaces.
225+
"""
226+
227+
subnet_id: int = 0
228+
ipv4: Optional[LinodeInterfaceRDMAVPCIPv4Options] = None
229+
230+
190231
@dataclass
191232
class LinodeInterfaceOptions(JSONObject):
192233
"""
@@ -204,6 +245,7 @@ class LinodeInterfaceOptions(JSONObject):
204245
vpc: Optional[LinodeInterfaceVPCOptions] = None
205246
public: Optional[LinodeInterfacePublicOptions] = None
206247
vlan: Optional[LinodeInterfaceVLANOptions] = None
248+
rdma_vpc: Optional[LinodeInterfaceRDMAVPCOptions] = None
207249

208250

209251
# Interface GET Response
@@ -409,6 +451,45 @@ class LinodeInterfaceVLAN(JSONObject):
409451
ipam_address: Optional[str] = None
410452

411453

454+
@dataclass
455+
class LinodeInterfaceRDMAVPCIPv4Address(JSONObject):
456+
"""
457+
A single address under the IPv4 configuration of an RDMA VPC Linode Interface.
458+
"""
459+
460+
put_class = LinodeInterfaceRDMAVPCIPv4AddressOptions
461+
462+
address: str = ""
463+
primary: bool = False
464+
465+
466+
@dataclass
467+
class LinodeInterfaceRDMAVPCIPv4(JSONObject):
468+
"""
469+
The IPv4 configuration of an RDMA VPC Linode Interface.
470+
"""
471+
472+
put_class = LinodeInterfaceRDMAVPCIPv4Options
473+
474+
addresses: List[LinodeInterfaceRDMAVPCIPv4Address] = field(
475+
default_factory=list
476+
)
477+
478+
479+
@dataclass
480+
class LinodeInterfaceRDMAVPC(JSONObject):
481+
"""
482+
RDMA VPC-specific configuration field for a Linode Interface.
483+
"""
484+
485+
put_class = LinodeInterfaceRDMAVPCOptions
486+
487+
vpc_id: int = 0
488+
subnet_id: int = 0
489+
490+
ipv4: Optional[LinodeInterfaceRDMAVPCIPv4] = None
491+
492+
412493
class LinodeInterface(DerivedBase):
413494
"""
414495
A Linode's network interface.
@@ -449,6 +530,7 @@ class LinodeInterface(DerivedBase):
449530
"public": Property(mutable=True, json_object=LinodeInterfacePublic),
450531
"vlan": Property(mutable=True, json_object=LinodeInterfaceVLAN),
451532
"vpc": Property(mutable=True, json_object=LinodeInterfaceVPC),
533+
"rdma_vpc": Property(mutable=True, json_object=LinodeInterfaceRDMAVPC),
452534
}
453535

454536
def firewalls(self, *filters) -> List[Firewall]:

‎linode_api4/objects/region.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ class Capability(StrEnum):
6767
ruleset = "Cloud Firewall Rule Set"
6868
prefixlists = "Cloud Firewall Prefix Lists"
6969
current_prefixlists = "Cloud Firewall Prefix List Current References"
70+
gpudirect_rdma = "GPUDirect RDMA"
7071

7172

7273
@dataclass

‎linode_api4/objects/vpc.py‎

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,20 @@
55
from linode_api4.objects import Base, DerivedBase, Property, Region
66
from linode_api4.objects.base import _flatten_request_body_recursive
77
from linode_api4.objects.networking import VPCIPAddress
8-
from linode_api4.objects.serializable import JSONObject
8+
from linode_api4.objects.serializable import JSONObject, StrEnum
99
from linode_api4.paginated_list import PaginatedList
1010
from linode_api4.util import drop_null_keys
1111

1212

13+
class VPCType(StrEnum):
14+
"""
15+
VPCType represents the supported VPC types.
16+
"""
17+
18+
regular = "regular"
19+
rdma = "rdma"
20+
21+
1322
@dataclass
1423
class VPCIPv4DefaultRange(JSONObject):
1524
"""
@@ -119,6 +128,7 @@ class VPCSubnet(DerivedBase):
119128
"ipv6": Property(json_object=VPCSubnetIPv6Range, unordered=True),
120129
"linodes": Property(json_object=VPCSubnetLinode, unordered=True),
121130
"databases": Property(json_object=VPCSubnetDatabase, unordered=True),
131+
"vpc_type": Property(),
122132
"created": Property(is_datetime=True),
123133
"updated": Property(is_datetime=True),
124134
}
@@ -144,6 +154,7 @@ class VPC(Base):
144154
),
145155
"ipv6": Property(json_object=VPCIPv6Range, unordered=True),
146156
"subnets": Property(derived_class=VPCSubnet),
157+
"vpc_type": Property(),
147158
"created": Property(is_datetime=True),
148159
"updated": Property(is_datetime=True),
149160
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"id": 999,
3+
"mac_address": "22:00:f2:9e:d3:48",
4+
"created": "2026-03-12T09:54:34",
5+
"updated": "2026-03-12T09:54:35",
6+
"default_route": {
7+
"ipv4": false,
8+
"ipv6": false
9+
},
10+
"version": 1,
11+
"public": null,
12+
"vpc": null,
13+
"vlan": null,
14+
"rdma_vpc": {
15+
"vpc_id": 7,
16+
"subnet_id": 8,
17+
"ipv4": {
18+
"addresses": [
19+
{
20+
"address": "10.0.0.2",
21+
"primary": true
22+
}
23+
]
24+
}
25+
}
26+
}
27+

‎test/fixtures/vpcs.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"id": 123456,
66
"description": "A very real VPC.",
77
"region": "us-southeast",
8+
"vpc_type": "regular",
89
"ipv4": [
910
{
1011
"range": "10.0.0.0/8"

‎test/fixtures/vpcs_123456.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"id": 123456,
44
"description": "A very real VPC.",
55
"region": "us-southeast",
6+
"vpc_type": "regular",
67
"ipv4": [
78
{
89
"range": "10.0.0.0/8"

0 commit comments

Comments
 (0)