Skip to content

Commit 58a6eb0

Browse files
zliang-akamailgarber-akamaiCopilot
authored
Project: Linode Interfaces (#540)
* Enhanced Interfaces: Add support for Firewall templates (#529) * Add support for Firewall Templates * oops * Add LA notices * Enhanced Interfaces: Add account-related fields (#525) * Enhanced Interfaces: Add account-related fields * Add setting enum * Add LA notice * Drop residual print * Enhanced Interfaces: Implement endpoints & fields related to VPCs and non-interface networking (#526) * Implement endpoints & fields related to VPCs and non-interface networking * Add LA notices * Enhanced Interfaces: Add support for Linode-related endpoints and fields (#533) * Add support for Linode-related endpoints and fields * oops * tiny fixes * fix docsa * Add docs examples * Docs fixes * oops * Remove irrelevant test * Add LA notices * Fill in API documentation URLs * Add return types * Enable `include_none_values` in FirewallSettingsDefaultFirewallIDs (#558) * Linode Interfaces: Allow specifying ExplicitNullValue for LinodeInterfaceOptions firewall ID (#565) * Add ExplicitNullValue support * Fix failing integration tests * Add unit tests * Add docs disclaimer * Fix * Update test/fixtures/linode_instances.json Co-authored-by: Copilot <[email protected]> * Update test/unit/objects/linode_test.py Co-authored-by: Copilot <[email protected]> * Update test/integration/conftest.py Co-authored-by: Copilot <[email protected]> * Update linode_api4/objects/linode.py Co-authored-by: Copilot <[email protected]> * More fixes * lint * Apply suggestion from @Copilot Co-authored-by: Copilot <[email protected]> * Apply suggestion from @Copilot Co-authored-by: Copilot <[email protected]> * Remove unnecessary local imports * Fix IPv6 addresses * Apply suggestion from @Copilot Co-authored-by: Copilot <[email protected]> * Update test/unit/objects/networking_test.py Co-authored-by: Copilot <[email protected]> * Update test/unit/objects/linode_test.py Co-authored-by: Copilot <[email protected]> --------- Co-authored-by: Lena Garber <[email protected]> Co-authored-by: Lena Garber <[email protected]> Co-authored-by: Copilot <[email protected]>
1 parent 195e2cd commit 58a6eb0

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+3189
-84
lines changed

linode_api4/groups/linode.py

Lines changed: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
import base64
22
import os
3-
from collections.abc import Iterable
4-
from typing import Any, Dict, Optional, Union
3+
from typing import Any, Dict, List, Optional, Union
54

65
from linode_api4.common import load_and_validate_keys
76
from linode_api4.errors import UnexpectedResponseError
87
from linode_api4.groups import Group
98
from linode_api4.objects import (
10-
ConfigInterface,
119
Firewall,
1210
Instance,
1311
InstanceDiskEncryptionType,
@@ -21,8 +19,13 @@
2119
from linode_api4.objects.linode import (
2220
Backup,
2321
InstancePlacementGroupAssignment,
22+
InterfaceGeneration,
23+
NetworkInterface,
2424
_expand_placement_group_assignment,
2525
)
26+
from linode_api4.objects.linode_interfaces import (
27+
LinodeInterfaceOptions,
28+
)
2629
from linode_api4.util import drop_null_keys
2730

2831

@@ -153,6 +156,13 @@ def instance_create(
153156
int,
154157
]
155158
] = None,
159+
interfaces: Optional[
160+
List[
161+
Union[LinodeInterfaceOptions, NetworkInterface, Dict[str, Any]],
162+
]
163+
] = None,
164+
interface_generation: Optional[Union[InterfaceGeneration, str]] = None,
165+
network_helper: Optional[bool] = None,
156166
maintenance_policy: Optional[str] = None,
157167
**kwargs,
158168
):
@@ -231,6 +241,30 @@ def instance_create(
231241
"us-east",
232242
backup=snapshot)
233243
244+
**Create an Instance with explicit interfaces:**
245+
246+
To create a new Instance with explicit interfaces, provide list of
247+
LinodeInterfaceOptions objects or dicts to the "interfaces" field::
248+
249+
linode, password = client.linode.instance_create(
250+
"g6-standard-1",
251+
"us-mia",
252+
image="linode/ubuntu24.04",
253+
254+
# This can be configured as an account-wide default
255+
interface_generation=InterfaceGeneration.LINODE,
256+
257+
interfaces=[
258+
LinodeInterfaceOptions(
259+
default_route=LinodeInterfaceDefaultRouteOptions(
260+
ipv4=True,
261+
ipv6=True
262+
),
263+
public=LinodeInterfacePublicOptions
264+
)
265+
]
266+
)
267+
234268
**Create an empty Instance**
235269
236270
If you want to create an empty Instance that you will configure manually,
@@ -294,9 +328,13 @@ def instance_create(
294328
:type disk_encryption: InstanceDiskEncryptionType or str
295329
:param interfaces: An array of Network Interfaces to add to this Linode’s Configuration Profile.
296330
At least one and up to three Interface objects can exist in this array.
297-
:type interfaces: list[ConfigInterface] or list[dict[str, Any]]
331+
:type interfaces: List[LinodeInterfaceOptions], List[NetworkInterface], or List[dict[str, Any]]
298332
:param placement_group: A Placement Group to create this Linode under.
299333
:type placement_group: Union[InstancePlacementGroupAssignment, PlacementGroup, Dict[str, Any], int]
334+
:param interface_generation: The generation of network interfaces this Linode uses.
335+
:type interface_generation: InterfaceGeneration or str
336+
:param network_helper: Whether this instance should have Network Helper enabled.
337+
:type network_helper: bool
300338
:param maintenance_policy: The slug of the maintenance policy to apply during maintenance.
301339
If not provided, the default policy (linode/migrate) will be applied.
302340
NOTE: This field is in beta and may only
@@ -317,13 +355,6 @@ def instance_create(
317355
ret_pass = Instance.generate_root_password()
318356
kwargs["root_pass"] = ret_pass
319357

320-
interfaces = kwargs.get("interfaces", None)
321-
if interfaces is not None and isinstance(interfaces, Iterable):
322-
kwargs["interfaces"] = [
323-
i._serialize() if isinstance(i, ConfigInterface) else i
324-
for i in interfaces
325-
]
326-
327358
params = {
328359
"type": ltype,
329360
"region": region,
@@ -343,6 +374,9 @@ def instance_create(
343374
if placement_group
344375
else None
345376
),
377+
"interfaces": interfaces,
378+
"interface_generation": interface_generation,
379+
"network_helper": network_helper,
346380
}
347381

348382
params.update(kwargs)

linode_api4/groups/networking.py

Lines changed: 118 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,23 @@
1+
from typing import Any, Dict, Optional, Union
2+
13
from linode_api4.errors import UnexpectedResponseError
24
from linode_api4.groups import Group
35
from linode_api4.objects import (
46
VLAN,
57
Base,
68
Firewall,
9+
FirewallCreateDevicesOptions,
10+
FirewallSettings,
11+
FirewallTemplate,
712
Instance,
813
IPAddress,
914
IPv6Pool,
1015
IPv6Range,
1116
NetworkTransferPrice,
1217
Region,
1318
)
19+
from linode_api4.objects.base import _flatten_request_body_recursive
20+
from linode_api4.util import drop_null_keys
1421

1522

1623
class NetworkingGroup(Group):
@@ -33,7 +40,15 @@ def firewalls(self, *filters):
3340
"""
3441
return self.client._get_and_filter(Firewall, *filters)
3542

36-
def firewall_create(self, label, rules, **kwargs):
43+
def firewall_create(
44+
self,
45+
label: str,
46+
rules: Dict[str, Any],
47+
devices: Optional[
48+
Union[FirewallCreateDevicesOptions, Dict[str, Any]]
49+
] = None,
50+
**kwargs,
51+
):
3752
"""
3853
Creates a new Firewall, either in the given Region or
3954
attached to the given Instance.
@@ -44,6 +59,8 @@ def firewall_create(self, label, rules, **kwargs):
4459
:type label: str
4560
:param rules: The rules to apply to the new Firewall. For more information on Firewall rules, see our `Firewalls Documentation`_.
4661
:type rules: dict
62+
:param devices: Represents devices to create created alongside a Linode Firewall.
63+
:type devices: Optional[Union[FirewallCreateDevicesOptions, Dict[str, Any]]]
4764
4865
:returns: The new Firewall.
4966
:rtype: Firewall
@@ -81,10 +98,14 @@ def firewall_create(self, label, rules, **kwargs):
8198
params = {
8299
"label": label,
83100
"rules": rules,
101+
"devices": devices,
84102
}
85103
params.update(kwargs)
86104

87-
result = self.client.post("/networking/firewalls", data=params)
105+
result = self.client.post(
106+
"/networking/firewalls",
107+
data=drop_null_keys(_flatten_request_body_recursive(params)),
108+
)
88109

89110
if not "id" in result:
90111
raise UnexpectedResponseError(
@@ -94,6 +115,43 @@ def firewall_create(self, label, rules, **kwargs):
94115
f = Firewall(self.client, result["id"], result)
95116
return f
96117

118+
def firewall_templates(self, *filters):
119+
"""
120+
Returns a list of Firewall Templates available to the current user.
121+
122+
API Documentation: https://techdocs.akamai.com/linode-api/reference/get-firewall-templates
123+
124+
NOTE: This feature may not currently be available to all users.
125+
126+
:param filters: Any number of filters to apply to this query.
127+
See :doc:`Filtering Collections</linode_api4/objects/filtering>`
128+
for more details on filtering.
129+
130+
:returns: A list of Firewall Templates available to the current user.
131+
:rtype: PaginatedList of FirewallTemplate
132+
"""
133+
return self.client._get_and_filter(FirewallTemplate, *filters)
134+
135+
def firewall_settings(self) -> FirewallSettings:
136+
"""
137+
Returns an object representing the Linode Firewall settings for the current user.
138+
139+
API Documentation: https://techdocs.akamai.com/linode-api/reference/get-firewall-settings
140+
141+
NOTE: This feature may not currently be available to all users.
142+
:returns: An object representing the Linode Firewall settings for the current user.
143+
:rtype: FirewallSettings
144+
"""
145+
result = self.client.get("/networking/firewalls/settings")
146+
147+
if "default_firewall_ids" not in result:
148+
raise UnexpectedResponseError(
149+
"Unexpected response when getting firewall settings!",
150+
json=result,
151+
)
152+
153+
return FirewallSettings(self.client, None, result)
154+
97155
def ips(self, *filters):
98156
"""
99157
Returns a list of IP addresses on this account, excluding private addresses.
@@ -124,6 +182,64 @@ def ipv6_ranges(self, *filters):
124182
"""
125183
return self.client._get_and_filter(IPv6Range, *filters)
126184

185+
def ipv6_range_allocate(
186+
self,
187+
prefix_length: int,
188+
route_target: Optional[str] = None,
189+
linode: Optional[Union[Instance, int]] = None,
190+
**kwargs,
191+
) -> IPv6Range:
192+
"""
193+
Creates an IPv6 Range and assigns it based on the provided Linode or route target IPv6 SLAAC address.
194+
195+
API Documentation: https://techdocs.akamai.com/linode-api/reference/post-ipv6-range
196+
197+
Create an IPv6 range assigned to a Linode by ID::
198+
199+
range = client.networking.ipv6_range_allocate(64, linode_id=123)
200+
201+
202+
Create an IPv6 range assigned to a Linode by SLAAC::
203+
204+
range = client.networking.ipv6_range_allocate(
205+
64,
206+
route_target=instance.ipv6.split("/")[0]
207+
)
208+
209+
:param prefix_length: The prefix length of the IPv6 range.
210+
:type prefix_length: int
211+
:param route_target: The IPv6 SLAAC address to assign this range to. Required if linode is not specified.
212+
:type route_target: str
213+
:param linode: The ID of the Linode to assign this range to.
214+
The SLAAC address for the provided Linode is used as the range's route_target.
215+
Required if linode is not specified.
216+
:type linode: Instance or int
217+
218+
:returns: The new IPAddress.
219+
:rtype: IPAddress
220+
"""
221+
222+
params = {
223+
"prefix_length": prefix_length,
224+
"route_target": route_target,
225+
"linode_id": linode,
226+
}
227+
228+
params.update(**kwargs)
229+
230+
result = self.client.post(
231+
"/networking/ipv6/ranges",
232+
data=drop_null_keys(_flatten_request_body_recursive(params)),
233+
)
234+
235+
if not "range" in result:
236+
raise UnexpectedResponseError(
237+
"Unexpected response when allocating IPv6 range!", json=result
238+
)
239+
240+
result = IPv6Range(self.client, result["range"], result)
241+
return result
242+
127243
def ipv6_pools(self, *filters):
128244
"""
129245
Returns a list of IPv6 pools on this account.

linode_api4/objects/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from .region import Region
77
from .image import Image
88
from .linode import *
9+
from .linode_interfaces import *
910
from .volume import *
1011
from .domain import *
1112
from .account import *

linode_api4/objects/account.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from linode_api4.objects.networking import Firewall
1818
from linode_api4.objects.nodebalancer import NodeBalancer
1919
from linode_api4.objects.profile import PersonalAccessToken
20+
from linode_api4.objects.serializable import StrEnum
2021
from linode_api4.objects.support import SupportTicket
2122
from linode_api4.objects.volume import Volume
2223
from linode_api4.objects.vpc import VPC
@@ -180,6 +181,24 @@ class Login(Base):
180181
}
181182

182183

184+
class AccountSettingsInterfacesForNewLinodes(StrEnum):
185+
"""
186+
A string enum corresponding to valid values
187+
for the AccountSettings(...).interfaces_for_new_linodes field.
188+
189+
NOTE: This feature may not currently be available to all users.
190+
"""
191+
192+
legacy_config_only = "legacy_config_only"
193+
legacy_config_default_but_linode_allowed = (
194+
"legacy_config_default_but_linode_allowed"
195+
)
196+
linode_default_but_legacy_config_allowed = (
197+
"linode_default_but_legacy_config_allowed"
198+
)
199+
linode_only = "linode_only"
200+
201+
183202
class AccountSettings(Base):
184203
"""
185204
Information related to your Account settings.
@@ -198,6 +217,7 @@ class AccountSettings(Base):
198217
),
199218
"object_storage": Property(),
200219
"backups_enabled": Property(mutable=True),
220+
"interfaces_for_new_linodes": Property(mutable=True),
201221
"maintenance_policy": Property(
202222
mutable=True
203223
), # Note: This field is only available when using v4beta.

linode_api4/objects/base.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,7 @@ def __setattr__(self, name, value):
239239
"""
240240
Enforces allowing editing of only Properties defined as mutable
241241
"""
242+
242243
if name in type(self).properties.keys():
243244
if not type(self).properties[name].mutable:
244245
raise AttributeError(

0 commit comments

Comments
 (0)