Skip to content

Project: Linode Interfaces #540

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 7 commits into
base: dev
Choose a base branch
from
Draft
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
56 changes: 45 additions & 11 deletions linode_api4/groups/linode.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import base64
import os
from collections.abc import Iterable
from typing import Any, Dict, Optional, Union
from typing import Any, Dict, List, Optional, Union

from linode_api4.common import load_and_validate_keys
from linode_api4.errors import UnexpectedResponseError
from linode_api4.groups import Group
from linode_api4.objects import (
ConfigInterface,
Firewall,
Instance,
InstanceDiskEncryptionType,
Expand All @@ -21,8 +19,13 @@
from linode_api4.objects.linode import (
Backup,
InstancePlacementGroupAssignment,
InterfaceGeneration,
NetworkInterface,
_expand_placement_group_assignment,
)
from linode_api4.objects.linode_interfaces import (
LinodeInterfaceOptions,
)
from linode_api4.util import drop_null_keys


Expand Down Expand Up @@ -153,6 +156,13 @@ def instance_create(
int,
]
] = None,
interfaces: Optional[
List[
Union[LinodeInterfaceOptions, NetworkInterface, Dict[str, Any]],
]
] = None,
interface_generation: Optional[Union[InterfaceGeneration, str]] = None,
network_helper: Optional[bool] = None,
**kwargs,
):
"""
Expand Down Expand Up @@ -230,6 +240,30 @@ def instance_create(
"us-east",
backup=snapshot)

**Create an Instance with explicit interfaces:**

To create a new Instance with explicit interfaces, provide list of
LinodeInterfaceOptions objects or dicts to the "interfaces" field::

linode, password = client.linode.instance_create(
"g6-standard-1",
"us-mia",
image="linode/ubuntu24.04",

# This can be configured as an account-wide default
interface_generation=InterfaceGeneration.LINODE,

interfaces=[
LinodeInterfaceOptions(
default_route=LinodeInterfaceDefaultRouteOptions(
ipv4=True,
ipv6=True
),
public=LinodeInterfacePublicOptions
)
]
)

**Create an empty Instance**

If you want to create an empty Instance that you will configure manually,
Expand Down Expand Up @@ -293,9 +327,13 @@ def instance_create(
:type disk_encryption: InstanceDiskEncryptionType or str
:param interfaces: An array of Network Interfaces to add to this Linode’s Configuration Profile.
At least one and up to three Interface objects can exist in this array.
:type interfaces: list[ConfigInterface] or list[dict[str, Any]]
:type interfaces: List[LinodeInterfaceOptions], List[NetworkInterface], or List[dict[str, Any]]
:param placement_group: A Placement Group to create this Linode under.
:type placement_group: Union[InstancePlacementGroupAssignment, PlacementGroup, Dict[str, Any], int]
:param interface_generation: The generation of network interfaces this Linode uses.
:type interface_generation: InterfaceGeneration or str
:param network_helper: Whether this instance should have Network Helper enabled.
:type network_helper: bool

:returns: A new Instance object, or a tuple containing the new Instance and
the generated password.
Expand All @@ -311,13 +349,6 @@ def instance_create(
ret_pass = Instance.generate_root_password()
kwargs["root_pass"] = ret_pass

interfaces = kwargs.get("interfaces", None)
if interfaces is not None and isinstance(interfaces, Iterable):
kwargs["interfaces"] = [
i._serialize() if isinstance(i, ConfigInterface) else i
for i in interfaces
]

params = {
"type": ltype,
"region": region,
Expand All @@ -336,6 +367,9 @@ def instance_create(
if placement_group
else None
),
"interfaces": interfaces,
"interface_generation": interface_generation,
"network_helper": network_helper,
}

params.update(kwargs)
Expand Down
120 changes: 118 additions & 2 deletions linode_api4/groups/networking.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
from typing import Any, Dict, Optional, Union

from linode_api4.errors import UnexpectedResponseError
from linode_api4.groups import Group
from linode_api4.objects import (
VLAN,
Base,
Firewall,
FirewallCreateDevicesOptions,
FirewallSettings,
FirewallTemplate,
Instance,
IPAddress,
IPv6Pool,
IPv6Range,
NetworkTransferPrice,
Region,
)
from linode_api4.objects.base import _flatten_request_body_recursive
from linode_api4.util import drop_null_keys


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

def firewall_create(self, label, rules, **kwargs):
def firewall_create(
self,
label: str,
rules: Dict[str, Any],
devices: Optional[
Union[FirewallCreateDevicesOptions, Dict[str, Any]]
] = None,
**kwargs,
):
"""
Creates a new Firewall, either in the given Region or
attached to the given Instance.
Expand All @@ -44,6 +59,8 @@ def firewall_create(self, label, rules, **kwargs):
:type label: str
:param rules: The rules to apply to the new Firewall. For more information on Firewall rules, see our `Firewalls Documentation`_.
:type rules: dict
:param devices: Represents devices to create created alongside a Linode Firewall.
:type devices: Optional[Union[FirewallCreateDevicesOptions, Dict[str, Any]]]

:returns: The new Firewall.
:rtype: Firewall
Expand Down Expand Up @@ -81,10 +98,14 @@ def firewall_create(self, label, rules, **kwargs):
params = {
"label": label,
"rules": rules,
"devices": devices,
}
params.update(kwargs)

result = self.client.post("/networking/firewalls", data=params)
result = self.client.post(
"/networking/firewalls",
data=drop_null_keys(_flatten_request_body_recursive(params)),
)

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

def firewall_templates(self, *filters):
"""
Returns a list of Firewall Templates available to the current user.

API Documentation: https://techdocs.akamai.com/linode-api/reference/get-firewall-templates

NOTE: This feature may not currently be available to all users.

:param filters: Any number of filters to apply to this query.
See :doc:`Filtering Collections</linode_api4/objects/filtering>`
for more details on filtering.

:returns: A list of Firewall Templates available to the current user.
:rtype: PaginatedList of FirewallTemplate
"""
return self.client._get_and_filter(FirewallTemplate, *filters)

def firewall_settings(self) -> FirewallSettings:
"""
Returns an object representing the Linode Firewall settings for the current user.

API Documentation: https://techdocs.akamai.com/linode-api/reference/get-firewall-settings

NOTE: This feature may not currently be available to all users.
:returns: An object representing the Linode Firewall settings for the current user.
:rtype: FirewallSettings
"""
result = self.client.get("/networking/firewalls/settings")

if "default_firewall_ids" not in result:
raise UnexpectedResponseError(
"Unexpected response when getting firewall settings!",
json=result,
)

return FirewallSettings(self.client, None, result)

def ips(self, *filters):
"""
Returns a list of IP addresses on this account, excluding private addresses.
Expand Down Expand Up @@ -124,6 +182,64 @@ def ipv6_ranges(self, *filters):
"""
return self.client._get_and_filter(IPv6Range, *filters)

def ipv6_range_allocate(
self,
prefix_length: int,
route_target: Optional[str] = None,
linode: Optional[Union[Instance, int]] = None,
**kwargs,
) -> IPv6Range:
"""
Creates an IPv6 Range and assigns it based on the provided Linode or route target IPv6 SLAAC address.

API Documentation: https://techdocs.akamai.com/linode-api/reference/post-ipv6-range

Create an IPv6 range assigned to a Linode by ID::

range = client.networking.ipv6_range_allocate(64, linode_id=123)


Create an IPv6 range assigned to a Linode by SLAAC::

range = client.networking.ipv6_range_allocate(
64,
route_target=instance.ipv6.split("/")[0]
)

:param prefix_length: The prefix length of the IPv6 range.
:type prefix_length: int
:param route_target: The IPv6 SLAAC address to assign this range to. Required if linode is not specified.
:type route_target: str
:param linode: The ID of the Linode to assign this range to.
The SLAAC address for the provided Linode is used as the range's route_target.
Required if linode is not specified.
:type linode: Instance or int

:returns: The new IPAddress.
:rtype: IPAddress
"""

params = {
"prefix_length": prefix_length,
"route_target": route_target,
"linode_id": linode,
}

params.update(**kwargs)

result = self.client.post(
"/networking/ipv6/ranges",
data=drop_null_keys(_flatten_request_body_recursive(params)),
)

if not "range" in result:
raise UnexpectedResponseError(
"Unexpected response when allocating IPv6 range!", json=result
)

result = IPv6Range(self.client, result["range"], result)
return result

def ipv6_pools(self, *filters):
"""
Returns a list of IPv6 pools on this account.
Expand Down
1 change: 1 addition & 0 deletions linode_api4/objects/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from .region import Region
from .image import Image
from .linode import *
from .linode_interfaces import *
from .volume import *
from .domain import *
from .account import *
Expand Down
20 changes: 20 additions & 0 deletions linode_api4/objects/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from linode_api4.objects.networking import Firewall
from linode_api4.objects.nodebalancer import NodeBalancer
from linode_api4.objects.profile import PersonalAccessToken
from linode_api4.objects.serializable import StrEnum
from linode_api4.objects.support import SupportTicket
from linode_api4.objects.volume import Volume
from linode_api4.objects.vpc import VPC
Expand Down Expand Up @@ -179,6 +180,24 @@ class Login(Base):
}


class AccountSettingsInterfacesForNewLinodes(StrEnum):
"""
A string enum corresponding to valid values
for the AccountSettings(...).interfaces_for_new_linodes field.

NOTE: This feature may not currently be available to all users.
"""

legacy_config_only = "legacy_config_only"
legacy_config_default_but_linode_allowed = (
"legacy_config_default_but_linode_allowed"
)
linode_default_but_legacy_config_allowed = (
"linode_default_but_legacy_config_allowed"
)
linode_only = "linode_only"


class AccountSettings(Base):
"""
Information related to your Account settings.
Expand All @@ -197,6 +216,7 @@ class AccountSettings(Base):
),
"object_storage": Property(),
"backups_enabled": Property(mutable=True),
"interfaces_for_new_linodes": Property(mutable=True),
}


Expand Down
1 change: 1 addition & 0 deletions linode_api4/objects/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ def __setattr__(self, name, value):
"""
Enforces allowing editing of only Properties defined as mutable
"""

if name in type(self).properties.keys():
if not type(self).properties[name].mutable:
raise AttributeError(
Expand Down
Loading