Skip to content

Commit

Permalink
deps: update dependency hcloud to v2.1.0 (ansible-collections#531)
Browse files Browse the repository at this point in the history
  • Loading branch information
jooola authored Jul 25, 2024
1 parent e8cb780 commit 42a1438
Show file tree
Hide file tree
Showing 24 changed files with 254 additions and 106 deletions.
6 changes: 5 additions & 1 deletion plugins/module_utils/vendor/hcloud/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
from __future__ import annotations

from ._client import Client as Client # noqa pylint: disable=C0414
from ._client import ( # noqa pylint: disable=C0414
Client as Client,
constant_backoff_function as constant_backoff_function,
exponential_backoff_function as exponential_backoff_function,
)
from ._exceptions import ( # noqa pylint: disable=C0414
APIException as APIException,
HCloudException as HCloudException,
Expand Down
144 changes: 98 additions & 46 deletions plugins/module_utils/vendor/hcloud/_client.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

import time
from random import uniform
from typing import Protocol

try:
Expand Down Expand Up @@ -29,7 +30,7 @@
from .volumes import VolumesClient


class PollIntervalFunction(Protocol):
class BackoffFunction(Protocol):
def __call__(self, retries: int) -> float:
"""
Return a interval in seconds to wait between each API call.
Expand All @@ -38,20 +39,65 @@ def __call__(self, retries: int) -> float:
"""


def constant_backoff_function(interval: float) -> BackoffFunction:
"""
Return a backoff function, implementing a constant backoff.
:param interval: Constant interval to return.
"""

# pylint: disable=unused-argument
def func(retries: int) -> float:
return interval

return func


def exponential_backoff_function(
*,
base: float,
multiplier: int,
cap: float,
jitter: bool = False,
) -> BackoffFunction:
"""
Return a backoff function, implementing a truncated exponential backoff with
optional full jitter.
:param base: Base for the exponential backoff algorithm.
:param multiplier: Multiplier for the exponential backoff algorithm.
:param cap: Value at which the interval is truncated.
:param jitter: Whether to add jitter.
"""

def func(retries: int) -> float:
interval = base * multiplier**retries # Exponential backoff
interval = min(cap, interval) # Cap backoff
if jitter:
interval = uniform(base, interval) # Add jitter
return interval

return func


class Client:
"""Base Client for accessing the Hetzner Cloud API"""

_version = __version__
_retry_wait_time = 0.5
__user_agent_prefix = "hcloud-python"

_retry_interval = exponential_backoff_function(
base=1.0, multiplier=2, cap=60.0, jitter=True
)
_retry_max_retries = 5

def __init__(
self,
token: str,
api_endpoint: str = "https://api.hetzner.cloud/v1",
application_name: str | None = None,
application_version: str | None = None,
poll_interval: int | float | PollIntervalFunction = 1.0,
poll_interval: int | float | BackoffFunction = 1.0,
poll_max_retries: int = 120,
timeout: float | tuple[float, float] | None = None,
):
Expand All @@ -76,7 +122,7 @@ def __init__(
self._requests_timeout = timeout

if isinstance(poll_interval, (int, float)):
self._poll_interval_func = lambda _: poll_interval # Constant poll interval
self._poll_interval_func = constant_backoff_function(poll_interval)
else:
self._poll_interval_func = poll_interval
self._poll_max_retries = poll_max_retries
Expand Down Expand Up @@ -197,8 +243,6 @@ def request( # type: ignore[no-untyped-def]
self,
method: str,
url: str,
*,
_tries: int = 1,
**kwargs,
) -> dict:
"""Perform a request to the Hetzner Cloud API, wrapper around requests.request
Expand All @@ -208,50 +252,58 @@ def request( # type: ignore[no-untyped-def]
:param timeout: Requests timeout in seconds
:return: Response
"""
timeout = kwargs.pop("timeout", self._requests_timeout)

response = self._requests_session.request(
method=method,
url=self._api_endpoint + url,
headers=self._get_headers(),
timeout=timeout,
**kwargs,
)

correlation_id = response.headers.get("X-Correlation-Id")
payload = {}
try:
if len(response.content) > 0:
payload = response.json()
except (TypeError, ValueError) as exc:
raise APIException(
code=response.status_code,
message=response.reason,
details={"content": response.content},
correlation_id=correlation_id,
) from exc

if not response.ok:
if not payload or "error" not in payload:
kwargs.setdefault("timeout", self._requests_timeout)

url = self._api_endpoint + url
headers = self._get_headers()

retries = 0
while True:
response = self._requests_session.request(
method=method,
url=url,
headers=headers,
**kwargs,
)

correlation_id = response.headers.get("X-Correlation-Id")
payload = {}
try:
if len(response.content) > 0:
payload = response.json()
except (TypeError, ValueError) as exc:
raise APIException(
code=response.status_code,
message=response.reason,
details={"content": response.content},
correlation_id=correlation_id,
)

error: dict = payload["error"]
) from exc

if not response.ok:
if not payload or "error" not in payload:
raise APIException(
code=response.status_code,
message=response.reason,
details={"content": response.content},
correlation_id=correlation_id,
)

error: dict = payload["error"]

if (
error["code"] == "rate_limit_exceeded"
and retries < self._retry_max_retries
):
# pylint: disable=too-many-function-args
time.sleep(self._retry_interval(retries))
retries += 1
continue

if error["code"] == "rate_limit_exceeded" and _tries < 5:
time.sleep(_tries * self._retry_wait_time)
_tries = _tries + 1
return self.request(method, url, _tries=_tries, **kwargs)

raise APIException(
code=error["code"],
message=error["message"],
details=error.get("details"),
correlation_id=correlation_id,
)
raise APIException(
code=error["code"],
message=error["message"],
details=error.get("details"),
correlation_id=correlation_id,
)

return payload
return payload
2 changes: 1 addition & 1 deletion plugins/module_utils/vendor/hcloud/_version.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
from __future__ import annotations

__version__ = "2.0.1" # x-release-please-version
__version__ = "2.1.0" # x-release-please-version
3 changes: 2 additions & 1 deletion plugins/module_utils/vendor/hcloud/actions/domain.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class Action(BaseDomain):
STATUS_ERROR = "error"
"""Action Status error"""

__slots__ = (
__api_properties__ = (
"id",
"command",
"status",
Expand All @@ -44,6 +44,7 @@ class Action(BaseDomain):
"started",
"finished",
)
__slots__ = __api_properties__

def __init__(
self,
Expand Down
7 changes: 5 additions & 2 deletions plugins/module_utils/vendor/hcloud/certificates/domain.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class Certificate(BaseDomain, DomainIdentityMixin):
:param status: ManagedCertificateStatus Current status of a type managed Certificate, always none for type uploaded Certificates
"""

__slots__ = (
__api_properties__ = (
"id",
"name",
"certificate",
Expand All @@ -47,6 +47,8 @@ class Certificate(BaseDomain, DomainIdentityMixin):
"type",
"status",
)
__slots__ = __api_properties__

TYPE_UPLOADED = "uploaded"
TYPE_MANAGED = "managed"

Expand Down Expand Up @@ -122,7 +124,8 @@ class CreateManagedCertificateResponse(BaseDomain):
Shows the progress of the certificate creation
"""

__slots__ = ("certificate", "action")
__api_properties__ = ("certificate", "action")
__slots__ = __api_properties__

def __init__(
self,
Expand Down
13 changes: 7 additions & 6 deletions plugins/module_utils/vendor/hcloud/core/domain.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,22 @@


class BaseDomain:
__slots__ = ()
__api_properties__: tuple

@classmethod
def from_dict(cls, data: dict): # type: ignore[no-untyped-def]
"""
Build the domain object from the data dict.
"""
supported_data = {k: v for k, v in data.items() if k in cls.__slots__}
supported_data = {k: v for k, v in data.items() if k in cls.__api_properties__}
return cls(**supported_data)

def __repr__(self) -> str:
kwargs = [f"{key}={getattr(self, key)!r}" for key in self.__slots__] # type: ignore[var-annotated]
kwargs = [f"{key}={getattr(self, key)!r}" for key in self.__api_properties__] # type: ignore[var-annotated]
return f"{self.__class__.__qualname__}({', '.join(kwargs)})"


class DomainIdentityMixin:
__slots__ = ()

id: int | None
name: str | None
Expand Down Expand Up @@ -54,14 +53,15 @@ def has_id_or_name(self, id_or_name: int | str) -> bool:


class Pagination(BaseDomain):
__slots__ = (
__api_properties__ = (
"page",
"per_page",
"previous_page",
"next_page",
"last_page",
"total_entries",
)
__slots__ = __api_properties__

def __init__(
self,
Expand All @@ -81,7 +81,8 @@ def __init__(


class Meta(BaseDomain):
__slots__ = ("pagination",)
__api_properties__ = ("pagination",)
__slots__ = __api_properties__

def __init__(self, pagination: Pagination | None = None):
self.pagination = pagination
Expand Down
6 changes: 4 additions & 2 deletions plugins/module_utils/vendor/hcloud/datacenters/domain.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ class Datacenter(BaseDomain, DomainIdentityMixin):
:param server_types: :class:`DatacenterServerTypes <hcloud.datacenters.domain.DatacenterServerTypes>`
"""

__slots__ = ("id", "name", "description", "location", "server_types")
__api_properties__ = ("id", "name", "description", "location", "server_types")
__slots__ = __api_properties__

def __init__(
self,
Expand Down Expand Up @@ -47,7 +48,8 @@ class DatacenterServerTypes(BaseDomain):
All available for migration (change type) server types for this datacenter
"""

__slots__ = ("available", "supported", "available_for_migration")
__api_properties__ = ("available", "supported", "available_for_migration")
__slots__ = __api_properties__

def __init__(
self,
Expand Down
3 changes: 2 additions & 1 deletion plugins/module_utils/vendor/hcloud/deprecation/domain.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,11 @@ class DeprecationInfo(BaseDomain):
new servers with this image after the mentioned date.
"""

__slots__ = (
__api_properties__ = (
"announced",
"unavailable_after",
)
__slots__ = __api_properties__

def __init__(
self,
Expand Down
Loading

0 comments on commit 42a1438

Please sign in to comment.