Skip to content

Commit d73884a

Browse files
Merge pull request #666 from linode/dev
Release v5.41.0
2 parents 713c9c1 + cdfcd3d commit d73884a

25 files changed

+537
-287
lines changed

.github/workflows/e2e-test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ jobs:
193193
submodules: 'recursive'
194194

195195
- name: Download test report
196-
uses: actions/download-artifact@v7
196+
uses: actions/download-artifact@v8
197197
with:
198198
name: test-report-file
199199

.github/workflows/labeler.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jobs:
2121
uses: actions/checkout@v6
2222
-
2323
name: Run Labeler
24-
uses: crazy-max/ghaction-github-labeler@24d110aa46a59976b8a7f35518cb7f14f434c916
24+
uses: crazy-max/ghaction-github-labeler@548a7c3603594ec17c819e1239f281a3b801ab4d
2525
with:
2626
github-token: ${{ secrets.GITHUB_TOKEN }}
2727
yaml-file: .github/labels.yml

docs/conf.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@
1010

1111
# If extensions (or modules to document with autodoc) are in another directory,
1212
# add these directories to sys.path here. If the directory is relative to the
13-
# documentation root, use os.path.abspath to make it absolute, like shown here.
13+
# documentation root, use Path(...).absolute() to make it absolute, like shown here.
1414
#
15-
import os
1615
import sys
17-
sys.path.insert(0, os.path.abspath('..'))
16+
from pathlib import Path
17+
sys.path.insert(0, str(Path('..').absolute()))
1818

1919

2020
# -- Project information -----------------------------------------------------

linode_api4/common.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import os
21
from dataclasses import dataclass
2+
from pathlib import Path
33

44
from linode_api4.objects import JSONObject
55

@@ -47,9 +47,9 @@ def load_and_validate_keys(authorized_keys):
4747
ret.append(k)
4848
else:
4949
# it doesn't appear to be a key.. is it a path to the key?
50-
k = os.path.expanduser(k)
51-
if os.path.isfile(k):
52-
with open(k) as f:
50+
k_path = Path(k).expanduser()
51+
if k_path.is_file():
52+
with open(k_path) as f:
5353
ret.append(f.read().rstrip())
5454
else:
5555
raise ValueError(

linode_api4/groups/linode.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import base64
2-
import os
2+
from pathlib import Path
33
from typing import Any, Dict, List, Optional, Union
44

55
from linode_api4.common import load_and_validate_keys
@@ -457,8 +457,9 @@ def stackscript_create(
457457
script_body = script
458458
if not script.startswith("#!"):
459459
# it doesn't look like a stackscript body, let's see if it's a file
460-
if os.path.isfile(script):
461-
with open(script) as f:
460+
script_path = Path(script)
461+
if script_path.is_file():
462+
with open(script_path) as f:
462463
script_body = f.read()
463464
else:
464465
raise ValueError(

linode_api4/groups/profile.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import os
21
from datetime import datetime
2+
from pathlib import Path
33

44
from linode_api4 import UnexpectedResponseError
55
from linode_api4.common import SSH_KEY_TYPES
@@ -322,9 +322,9 @@ def ssh_key_upload(self, key, label):
322322
"""
323323
if not key.startswith(SSH_KEY_TYPES):
324324
# this might be a file path - look for it
325-
path = os.path.expanduser(key)
326-
if os.path.isfile(path):
327-
with open(path) as f:
325+
key_path = Path(key).expanduser()
326+
if key_path.is_file():
327+
with open(key_path) as f:
328328
key = f.read().strip()
329329
if not key.startswith(SSH_KEY_TYPES):
330330
raise ValueError("Invalid SSH Public Key")

linode_api4/groups/region.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
from linode_api4.groups import Group
22
from linode_api4.objects import Region
3-
from linode_api4.objects.region import RegionAvailabilityEntry
3+
from linode_api4.objects.region import (
4+
RegionAvailabilityEntry,
5+
RegionVPCAvailability,
6+
)
47

58

69
class RegionGroup(Group):
@@ -43,3 +46,34 @@ def availability(self, *filters):
4346
return self.client._get_and_filter(
4447
RegionAvailabilityEntry, *filters, endpoint="/regions/availability"
4548
)
49+
50+
def vpc_availability(self, *filters):
51+
"""
52+
Returns VPC availability data for all regions.
53+
54+
NOTE: IPv6 VPCs may not currently be available to all users.
55+
56+
This endpoint supports pagination with the following parameters:
57+
- page: Page number (>= 1)
58+
- page_size: Number of items per page (25-500)
59+
60+
Pagination is handled automatically by PaginatedList. To configure page_size,
61+
set it when creating the LinodeClient:
62+
63+
client = LinodeClient(token, page_size=100)
64+
65+
API Documentation: https://techdocs.akamai.com/linode-api/reference/get-regions-vpc-availability
66+
67+
:param filters: Any number of filters to apply to this query.
68+
See :doc:`Filtering Collections</linode_api4/objects/filtering>`
69+
for more details on filtering.
70+
71+
:returns: A list of VPC availability data for regions.
72+
:rtype: PaginatedList of RegionVPCAvailability
73+
"""
74+
75+
return self.client._get_and_filter(
76+
RegionVPCAvailability,
77+
*filters,
78+
endpoint="/regions/vpc-availability",
79+
)

linode_api4/objects/database.py

Lines changed: 0 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
from dataclasses import dataclass, field
22
from typing import Optional
33

4-
from deprecated import deprecated
5-
64
from linode_api4.objects import (
75
Base,
8-
DerivedBase,
96
JSONObject,
107
MappedObject,
118
Property,
@@ -86,69 +83,6 @@ class DatabasePrivateNetwork(JSONObject):
8683
public_access: Optional[bool] = None
8784

8885

89-
@deprecated(
90-
reason="Backups are not supported for non-legacy database clusters."
91-
)
92-
class DatabaseBackup(DerivedBase):
93-
"""
94-
A generic Managed Database backup.
95-
96-
This class is not intended to be used on its own.
97-
Use the appropriate subclasses for the corresponding database engine. (e.g. MySQLDatabaseBackup)
98-
"""
99-
100-
api_endpoint = ""
101-
derived_url_path = "backups"
102-
parent_id_name = "database_id"
103-
104-
properties = {
105-
"created": Property(is_datetime=True),
106-
"id": Property(identifier=True),
107-
"label": Property(),
108-
"type": Property(),
109-
}
110-
111-
def restore(self):
112-
"""
113-
Restore a backup to a Managed Database on your Account.
114-
115-
API Documentation:
116-
117-
- MySQL: https://techdocs.akamai.com/linode-api/reference/post-databases-mysql-instance-backup-restore
118-
- PostgreSQL: https://techdocs.akamai.com/linode-api/reference/post-databases-postgre-sql-instance-backup-restore
119-
"""
120-
121-
return self._client.post(
122-
"{}/restore".format(self.api_endpoint), model=self
123-
)
124-
125-
126-
@deprecated(
127-
reason="Backups are not supported for non-legacy database clusters."
128-
)
129-
class MySQLDatabaseBackup(DatabaseBackup):
130-
"""
131-
A backup for an accessible Managed MySQL Database.
132-
133-
API Documentation: https://techdocs.akamai.com/linode-api/reference/get-databases-mysql-instance-backup
134-
"""
135-
136-
api_endpoint = "/databases/mysql/instances/{database_id}/backups/{id}"
137-
138-
139-
@deprecated(
140-
reason="Backups are not supported for non-legacy database clusters."
141-
)
142-
class PostgreSQLDatabaseBackup(DatabaseBackup):
143-
"""
144-
A backup for an accessible Managed PostgreSQL Database.
145-
146-
API Documentation: https://techdocs.akamai.com/linode-api/reference/get-databases-postgresql-instance-backup
147-
"""
148-
149-
api_endpoint = "/databases/postgresql/instances/{database_id}/backups/{id}"
150-
151-
15286
@dataclass
15387
class MySQLDatabaseConfigMySQLOptions(JSONObject):
15488
"""
@@ -296,15 +230,13 @@ class MySQLDatabase(Base):
296230
"id": Property(identifier=True),
297231
"label": Property(mutable=True),
298232
"allow_list": Property(mutable=True, unordered=True),
299-
"backups": Property(derived_class=MySQLDatabaseBackup),
300233
"cluster_size": Property(mutable=True),
301234
"created": Property(is_datetime=True),
302235
"encrypted": Property(),
303236
"engine": Property(),
304237
"hosts": Property(),
305238
"port": Property(),
306239
"region": Property(),
307-
"replication_type": Property(),
308240
"ssl_connection": Property(),
309241
"status": Property(volatile=True),
310242
"type": Property(mutable=True),
@@ -393,28 +325,6 @@ def patch(self):
393325
"{}/patch".format(MySQLDatabase.api_endpoint), model=self
394326
)
395327

396-
@deprecated(
397-
reason="Backups are not supported for non-legacy database clusters."
398-
)
399-
def backup_create(self, label, **kwargs):
400-
"""
401-
Creates a snapshot backup of a Managed MySQL Database.
402-
403-
API Documentation: https://techdocs.akamai.com/linode-api/reference/post-databases-mysql-instance-backup
404-
"""
405-
406-
params = {
407-
"label": label,
408-
}
409-
params.update(kwargs)
410-
411-
self._client.post(
412-
"{}/backups".format(MySQLDatabase.api_endpoint),
413-
model=self,
414-
data=params,
415-
)
416-
self.invalidate()
417-
418328
def invalidate(self):
419329
"""
420330
Clear out cached properties.
@@ -464,16 +374,13 @@ class PostgreSQLDatabase(Base):
464374
"id": Property(identifier=True),
465375
"label": Property(mutable=True),
466376
"allow_list": Property(mutable=True, unordered=True),
467-
"backups": Property(derived_class=PostgreSQLDatabaseBackup),
468377
"cluster_size": Property(mutable=True),
469378
"created": Property(is_datetime=True),
470379
"encrypted": Property(),
471380
"engine": Property(),
472381
"hosts": Property(),
473382
"port": Property(),
474383
"region": Property(),
475-
"replication_commit_type": Property(),
476-
"replication_type": Property(),
477384
"ssl_connection": Property(),
478385
"status": Property(volatile=True),
479386
"type": Property(mutable=True),
@@ -563,28 +470,6 @@ def patch(self):
563470
"{}/patch".format(PostgreSQLDatabase.api_endpoint), model=self
564471
)
565472

566-
@deprecated(
567-
reason="Backups are not supported for non-legacy database clusters."
568-
)
569-
def backup_create(self, label, **kwargs):
570-
"""
571-
Creates a snapshot backup of a Managed PostgreSQL Database.
572-
573-
API Documentation: https://techdocs.akamai.com/linode-api/reference/post-databases-postgre-sql-instance-backup
574-
"""
575-
576-
params = {
577-
"label": label,
578-
}
579-
params.update(kwargs)
580-
581-
self._client.post(
582-
"{}/backups".format(PostgreSQLDatabase.api_endpoint),
583-
model=self,
584-
data=params,
585-
)
586-
self.invalidate()
587-
588473
def invalidate(self):
589474
"""
590475
Clear out cached properties.

linode_api4/objects/linode.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,12 @@
4040
from linode_api4.objects.serializable import JSONObject, StrEnum
4141
from linode_api4.objects.vpc import VPC, VPCSubnet
4242
from linode_api4.paginated_list import PaginatedList
43-
from linode_api4.util import drop_null_keys
43+
from linode_api4.util import drop_null_keys, generate_device_suffixes
4444

4545
PASSWORD_CHARS = string.ascii_letters + string.digits + string.punctuation
46+
MIN_DEVICE_LIMIT = 8
47+
MB_PER_GB = 1024
48+
MAX_DEVICE_LIMIT = 64
4649

4750

4851
class InstanceDiskEncryptionType(StrEnum):
@@ -1272,9 +1275,19 @@ def config_create(
12721275
from .volume import Volume # pylint: disable=import-outside-toplevel
12731276

12741277
hypervisor_prefix = "sd" if self.hypervisor == "kvm" else "xvd"
1278+
1279+
device_limit = int(
1280+
max(
1281+
MIN_DEVICE_LIMIT,
1282+
min(self.specs.memory // MB_PER_GB, MAX_DEVICE_LIMIT),
1283+
)
1284+
)
1285+
12751286
device_names = [
1276-
hypervisor_prefix + string.ascii_lowercase[i] for i in range(0, 8)
1287+
hypervisor_prefix + suffix
1288+
for suffix in generate_device_suffixes(device_limit)
12771289
]
1290+
12781291
device_map = {
12791292
device_names[i]: None for i in range(0, len(device_names))
12801293
}

linode_api4/objects/nodebalancer.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import os
1+
from pathlib import Path
22
from urllib import parse
33

44
from linode_api4.common import Price, RegionPrice
@@ -220,12 +220,14 @@ def load_ssl_data(self, cert_file, key_file):
220220

221221
# we're disabling warnings here because these attributes are defined dynamically
222222
# through linode.objects.Base, and pylint isn't privy
223-
if os.path.isfile(os.path.expanduser(cert_file)):
224-
with open(os.path.expanduser(cert_file)) as f:
223+
cert_path = Path(cert_file).expanduser()
224+
if cert_path.is_file():
225+
with open(cert_path) as f:
225226
self.ssl_cert = f.read()
226227

227-
if os.path.isfile(os.path.expanduser(key_file)):
228-
with open(os.path.expanduser(key_file)) as f:
228+
key_path = Path(key_file).expanduser()
229+
if key_path.is_file():
230+
with open(key_path) as f:
229231
self.ssl_key = f.read()
230232

231233

0 commit comments

Comments
 (0)