Skip to content
Open
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
2 changes: 1 addition & 1 deletion .github/workflows/e2e-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ jobs:
submodules: 'recursive'

- name: Download test report
uses: actions/download-artifact@v7
uses: actions/download-artifact@v8
with:
name: test-report-file

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/labeler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
uses: actions/checkout@v6
-
name: Run Labeler
uses: crazy-max/ghaction-github-labeler@24d110aa46a59976b8a7f35518cb7f14f434c916
uses: crazy-max/ghaction-github-labeler@548a7c3603594ec17c819e1239f281a3b801ab4d
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
yaml-file: .github/labels.yml
Expand Down
6 changes: 3 additions & 3 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@

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


# -- Project information -----------------------------------------------------
Expand Down
8 changes: 4 additions & 4 deletions linode_api4/common.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import os
from dataclasses import dataclass
from pathlib import Path

from linode_api4.objects import JSONObject

Expand Down Expand Up @@ -47,9 +47,9 @@ def load_and_validate_keys(authorized_keys):
ret.append(k)
else:
# it doesn't appear to be a key.. is it a path to the key?
k = os.path.expanduser(k)
if os.path.isfile(k):
with open(k) as f:
k_path = Path(k).expanduser()
if k_path.is_file():
with open(k_path) as f:
ret.append(f.read().rstrip())
else:
raise ValueError(
Expand Down
7 changes: 4 additions & 3 deletions linode_api4/groups/linode.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import base64
import os
from pathlib import Path
from typing import Any, Dict, List, Optional, Union

from linode_api4.common import load_and_validate_keys
Expand Down Expand Up @@ -457,8 +457,9 @@ def stackscript_create(
script_body = script
if not script.startswith("#!"):
# it doesn't look like a stackscript body, let's see if it's a file
if os.path.isfile(script):
with open(script) as f:
script_path = Path(script)
if script_path.is_file():
with open(script_path) as f:
script_body = f.read()
else:
raise ValueError(
Expand Down
8 changes: 4 additions & 4 deletions linode_api4/groups/profile.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import os
from datetime import datetime
from pathlib import Path

from linode_api4 import UnexpectedResponseError
from linode_api4.common import SSH_KEY_TYPES
Expand Down Expand Up @@ -322,9 +322,9 @@ def ssh_key_upload(self, key, label):
"""
if not key.startswith(SSH_KEY_TYPES):
# this might be a file path - look for it
path = os.path.expanduser(key)
if os.path.isfile(path):
with open(path) as f:
key_path = Path(key).expanduser()
if key_path.is_file():
with open(key_path) as f:
key = f.read().strip()
if not key.startswith(SSH_KEY_TYPES):
raise ValueError("Invalid SSH Public Key")
Expand Down
115 changes: 0 additions & 115 deletions linode_api4/objects/database.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
from dataclasses import dataclass, field
from typing import Optional

from deprecated import deprecated

from linode_api4.objects import (
Base,
DerivedBase,
JSONObject,
MappedObject,
Property,
Expand Down Expand Up @@ -86,69 +83,6 @@ class DatabasePrivateNetwork(JSONObject):
public_access: Optional[bool] = None


@deprecated(
reason="Backups are not supported for non-legacy database clusters."
)
class DatabaseBackup(DerivedBase):
"""
A generic Managed Database backup.

This class is not intended to be used on its own.
Use the appropriate subclasses for the corresponding database engine. (e.g. MySQLDatabaseBackup)
"""

api_endpoint = ""
derived_url_path = "backups"
parent_id_name = "database_id"

properties = {
"created": Property(is_datetime=True),
"id": Property(identifier=True),
"label": Property(),
"type": Property(),
}

def restore(self):
"""
Restore a backup to a Managed Database on your Account.

API Documentation:

- MySQL: https://techdocs.akamai.com/linode-api/reference/post-databases-mysql-instance-backup-restore
- PostgreSQL: https://techdocs.akamai.com/linode-api/reference/post-databases-postgre-sql-instance-backup-restore
"""

return self._client.post(
"{}/restore".format(self.api_endpoint), model=self
)


@deprecated(
reason="Backups are not supported for non-legacy database clusters."
)
class MySQLDatabaseBackup(DatabaseBackup):
"""
A backup for an accessible Managed MySQL Database.

API Documentation: https://techdocs.akamai.com/linode-api/reference/get-databases-mysql-instance-backup
"""

api_endpoint = "/databases/mysql/instances/{database_id}/backups/{id}"


@deprecated(
reason="Backups are not supported for non-legacy database clusters."
)
class PostgreSQLDatabaseBackup(DatabaseBackup):
"""
A backup for an accessible Managed PostgreSQL Database.

API Documentation: https://techdocs.akamai.com/linode-api/reference/get-databases-postgresql-instance-backup
"""

api_endpoint = "/databases/postgresql/instances/{database_id}/backups/{id}"


@dataclass
class MySQLDatabaseConfigMySQLOptions(JSONObject):
"""
Expand Down Expand Up @@ -296,15 +230,13 @@ class MySQLDatabase(Base):
"id": Property(identifier=True),
"label": Property(mutable=True),
"allow_list": Property(mutable=True, unordered=True),
"backups": Property(derived_class=MySQLDatabaseBackup),
"cluster_size": Property(mutable=True),
"created": Property(is_datetime=True),
"encrypted": Property(),
"engine": Property(),
"hosts": Property(),
"port": Property(),
"region": Property(),
"replication_type": Property(),
"ssl_connection": Property(),
"status": Property(volatile=True),
"type": Property(mutable=True),
Expand Down Expand Up @@ -393,28 +325,6 @@ def patch(self):
"{}/patch".format(MySQLDatabase.api_endpoint), model=self
)

@deprecated(
reason="Backups are not supported for non-legacy database clusters."
)
def backup_create(self, label, **kwargs):
"""
Creates a snapshot backup of a Managed MySQL Database.

API Documentation: https://techdocs.akamai.com/linode-api/reference/post-databases-mysql-instance-backup
"""

params = {
"label": label,
}
params.update(kwargs)

self._client.post(
"{}/backups".format(MySQLDatabase.api_endpoint),
model=self,
data=params,
)
self.invalidate()

def invalidate(self):
"""
Clear out cached properties.
Expand Down Expand Up @@ -464,16 +374,13 @@ class PostgreSQLDatabase(Base):
"id": Property(identifier=True),
"label": Property(mutable=True),
"allow_list": Property(mutable=True, unordered=True),
"backups": Property(derived_class=PostgreSQLDatabaseBackup),
"cluster_size": Property(mutable=True),
"created": Property(is_datetime=True),
"encrypted": Property(),
"engine": Property(),
"hosts": Property(),
"port": Property(),
"region": Property(),
"replication_commit_type": Property(),
"replication_type": Property(),
"ssl_connection": Property(),
"status": Property(volatile=True),
"type": Property(mutable=True),
Expand Down Expand Up @@ -563,28 +470,6 @@ def patch(self):
"{}/patch".format(PostgreSQLDatabase.api_endpoint), model=self
)

@deprecated(
reason="Backups are not supported for non-legacy database clusters."
)
def backup_create(self, label, **kwargs):
"""
Creates a snapshot backup of a Managed PostgreSQL Database.

API Documentation: https://techdocs.akamai.com/linode-api/reference/post-databases-postgre-sql-instance-backup
"""

params = {
"label": label,
}
params.update(kwargs)

self._client.post(
"{}/backups".format(PostgreSQLDatabase.api_endpoint),
model=self,
data=params,
)
self.invalidate()

def invalidate(self):
"""
Clear out cached properties.
Expand Down
17 changes: 15 additions & 2 deletions linode_api4/objects/linode.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,12 @@
from linode_api4.objects.serializable import JSONObject, StrEnum
from linode_api4.objects.vpc import VPC, VPCSubnet
from linode_api4.paginated_list import PaginatedList
from linode_api4.util import drop_null_keys
from linode_api4.util import drop_null_keys, generate_device_suffixes

PASSWORD_CHARS = string.ascii_letters + string.digits + string.punctuation
MIN_DEVICE_LIMIT = 8
MB_PER_GB = 1024
MAX_DEVICE_LIMIT = 64


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

hypervisor_prefix = "sd" if self.hypervisor == "kvm" else "xvd"

device_limit = int(
max(
MIN_DEVICE_LIMIT,
min(self.specs.memory // MB_PER_GB, MAX_DEVICE_LIMIT),
)
)

device_names = [
hypervisor_prefix + string.ascii_lowercase[i] for i in range(0, 8)
hypervisor_prefix + suffix
for suffix in generate_device_suffixes(device_limit)
]

device_map = {
device_names[i]: None for i in range(0, len(device_names))
}
Expand Down
12 changes: 7 additions & 5 deletions linode_api4/objects/nodebalancer.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import os
from pathlib import Path
from urllib import parse

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

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

if os.path.isfile(os.path.expanduser(key_file)):
with open(os.path.expanduser(key_file)) as f:
key_path = Path(key_file).expanduser()
if key_path.is_file():
with open(key_path) as f:
self.ssl_key = f.read()


Expand Down
26 changes: 26 additions & 0 deletions linode_api4/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
Contains various utility functions.
"""

import string
from typing import Any, Dict


Expand All @@ -27,3 +28,28 @@ def recursive_helper(value: Any) -> Any:
return value

return recursive_helper(data)


def generate_device_suffixes(n: int) -> list[str]:
"""
Generate n alphabetical suffixes starting with a, b, c, etc.
After z, continue with aa, ab, ac, etc. followed by aaa, aab, etc.
Example:
generate_device_suffixes(30) ->
['a', 'b', 'c', ..., 'z', 'aa', 'ab', 'ac', 'ad']
"""
letters = string.ascii_lowercase
result = []
i = 0

while len(result) < n:
s = ""
x = i
while True:
s = letters[x % 26] + s
x = x // 26 - 1
if x < 0:
break
result.append(s)
i += 1
return result
13 changes: 0 additions & 13 deletions test/fixtures/databases_mysql_instances_123_backups.json

This file was deleted.

This file was deleted.

13 changes: 0 additions & 13 deletions test/fixtures/databases_postgresql_instances_123_backups.json

This file was deleted.

This file was deleted.

Loading
Loading