chore(deps): update dependency hcloud to v2 (#523)

[![Mend
Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com)

This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
| [hcloud](https://togithub.com/hetznercloud/hcloud-python)
([changelog](https://togithub.com/hetznercloud/hcloud-python/blob/main/CHANGELOG.md))
| `1.35.0` -> `2.0.1` |
[![age](https://developer.mend.io/api/mc/badges/age/pypi/hcloud/2.0.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/pypi/hcloud/2.0.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/pypi/hcloud/1.35.0/2.0.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/pypi/hcloud/1.35.0/2.0.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Release Notes

<details>
<summary>hetznercloud/hcloud-python (hcloud)</summary>

###
[`v2.0.1`](https://togithub.com/hetznercloud/hcloud-python/blob/HEAD/CHANGELOG.md#201-2024-07-03)

[Compare
Source](https://togithub.com/hetznercloud/hcloud-python/compare/v2.0.0...v2.0.1)

##### Bug Fixes

- `assignee_type` is required when creating a primary ip
([#&#8203;409](https://togithub.com/hetznercloud/hcloud-python/issues/409))
([bce5e94](bce5e940e2))
- clean unused arguments in the `Client.servers.rebuild` method
([#&#8203;407](https://togithub.com/hetznercloud/hcloud-python/issues/407))
([6d33c3c](6d33c3cff5))
- details are optional in API errors
([#&#8203;411](https://togithub.com/hetznercloud/hcloud-python/issues/411))
([f1c6594](f1c6594dee))
- rename `trace_id` variable to `correlation_id`
([#&#8203;408](https://togithub.com/hetznercloud/hcloud-python/issues/408))
([66a0f54](66a0f54699))

###
[`v2.0.0`](https://togithub.com/hetznercloud/hcloud-python/blob/HEAD/CHANGELOG.md#200-2024-07-03)

[Compare
Source](https://togithub.com/hetznercloud/hcloud-python/compare/v1.35.0...v2.0.0)

##### ⚠ BREAKING CHANGES

- return full rebuild response in `Client.servers.rebuild`
([#&#8203;406](https://togithub.com/hetznercloud/hcloud-python/issues/406))
- make `datacenter` argument optional when creating a primary ip
([#&#8203;363](https://togithub.com/hetznercloud/hcloud-python/issues/363))
- remove deprecated `include_wildcard_architecture` argument in
`IsosClient.get_list` and `IsosClient.get_all`
([#&#8203;402](https://togithub.com/hetznercloud/hcloud-python/issues/402))
- make `Client.request` `tries` a private argument
([#&#8203;399](https://togithub.com/hetznercloud/hcloud-python/issues/399))
- make `Client.poll_interval` a private property
([#&#8203;398](https://togithub.com/hetznercloud/hcloud-python/issues/398))
- return empty dict on empty responses in `Client.request`
([#&#8203;400](https://togithub.com/hetznercloud/hcloud-python/issues/400))
- remove deprecated `hcloud.hcloud` module
([#&#8203;401](https://togithub.com/hetznercloud/hcloud-python/issues/401))
- move `hcloud.__version__.VERSION` to `hcloud.__version__`
([#&#8203;397](https://togithub.com/hetznercloud/hcloud-python/issues/397))

##### Features

- add `trace_id` to API exceptions
([#&#8203;404](https://togithub.com/hetznercloud/hcloud-python/issues/404))
([8375261](8375261da3))
- allow using a custom poll_interval function
([#&#8203;403](https://togithub.com/hetznercloud/hcloud-python/issues/403))
([93eb56b](93eb56ba4d))
- make `Client.poll_interval` a private property
([#&#8203;398](https://togithub.com/hetznercloud/hcloud-python/issues/398))
([d5f24db](d5f24db281))
- make `Client.request` `tries` a private argument
([#&#8203;399](https://togithub.com/hetznercloud/hcloud-python/issues/399))
([428ea7e](428ea7e3be))
- move `hcloud.__version__.VERSION` to `hcloud.__version__`
([#&#8203;397](https://togithub.com/hetznercloud/hcloud-python/issues/397))
([4e3f638](4e3f638862)),
closes
[#&#8203;234](https://togithub.com/hetznercloud/hcloud-python/issues/234)
- remove deprecated `hcloud.hcloud` module
([#&#8203;401](https://togithub.com/hetznercloud/hcloud-python/issues/401))
([db37e63](db37e633eb))
- remove deprecated `include_wildcard_architecture` argument in
`IsosClient.get_list` and `IsosClient.get_all`
([#&#8203;402](https://togithub.com/hetznercloud/hcloud-python/issues/402))
([6b977e2](6b977e2da5))
- return empty dict on empty responses in `Client.request`
([#&#8203;400](https://togithub.com/hetznercloud/hcloud-python/issues/400))
([9f46adb](9f46adb946))
- return full rebuild response in `Client.servers.rebuild`
([#&#8203;406](https://togithub.com/hetznercloud/hcloud-python/issues/406))
([1970d84](1970d84bec))

##### Bug Fixes

- make `datacenter` argument optional when creating a primary ip
([#&#8203;363](https://togithub.com/hetznercloud/hcloud-python/issues/363))
([ebef774](ebef77464c))

##### Dependencies

- update dependency coverage to >=7.5,<7.6
([#&#8203;386](https://togithub.com/hetznercloud/hcloud-python/issues/386))
([5660691](5660691ebd))
- update dependency mypy to >=1.10,<1.11
([#&#8203;387](https://togithub.com/hetznercloud/hcloud-python/issues/387))
([35c933b](35c933bd21))
- update dependency myst-parser to v3
([#&#8203;385](https://togithub.com/hetznercloud/hcloud-python/issues/385))
([9f18270](9f18270489))
- update dependency pylint to >=3,<3.3
([#&#8203;391](https://togithub.com/hetznercloud/hcloud-python/issues/391))
([4a6f005](4a6f005cb0))
- update dependency pytest to >=8,<8.3
([#&#8203;390](https://togithub.com/hetznercloud/hcloud-python/issues/390))
([584a36b](584a36b658))
- update dependency sphinx to >=7.3.4,<7.4
([#&#8203;383](https://togithub.com/hetznercloud/hcloud-python/issues/383))
([69c2e16](69c2e16073))
- update pre-commit hook asottile/pyupgrade to v3.16.0
([0ce5fbc](0ce5fbccba))
- update pre-commit hook pre-commit/pre-commit-hooks to v4.6.0
([5ef25ab](5ef25ab396))
- update pre-commit hook psf/black-pre-commit-mirror to v24.4.0
([0941fbf](0941fbfab2))
- update pre-commit hook psf/black-pre-commit-mirror to v24.4.1
([fec08c5](fec08c5323))
- update pre-commit hook psf/black-pre-commit-mirror to v24.4.2
([#&#8203;389](https://togithub.com/hetznercloud/hcloud-python/issues/389))
([2b2e21f](2b2e21f613))
- update pre-commit hook pycqa/flake8 to v7.1.0
([3bc651d](3bc651d50d))

##### Documentation

- add v2 upgrade notes
([#&#8203;405](https://togithub.com/hetznercloud/hcloud-python/issues/405))
([c77f771](c77f771e2b))
- cx11 is name, not an id
([#&#8203;381](https://togithub.com/hetznercloud/hcloud-python/issues/381))
([b745d40](b745d4049f))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR has been generated by [Mend
Renovate](https://www.mend.io/free-developer-tools/renovate/). View
repository job log
[here](https://developer.mend.io/github/ansible-collections/hetzner.hcloud).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNy40MjEuOSIsInVwZGF0ZWRJblZlciI6IjM3LjQyMS45IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: jo <ljonas@riseup.net>
This commit is contained in:
renovate[bot] 2024-07-03 15:05:12 +02:00 committed by GitHub
parent f3d697c006
commit 9adb8b3981
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 124 additions and 148 deletions

View file

@ -5,3 +5,4 @@ from ._exceptions import ( # noqa pylint: disable=C0414
APIException as APIException, APIException as APIException,
HCloudException as HCloudException, HCloudException as HCloudException,
) )
from ._version import __version__ # noqa

View file

@ -1,15 +1,15 @@
from __future__ import annotations from __future__ import annotations
import time import time
from typing import NoReturn from typing import Protocol
try: try:
import requests import requests
except ImportError: except ImportError:
requests = None requests = None
from ._version import VERSION
from ._exceptions import APIException from ._exceptions import APIException
from ._version import __version__
from .actions import ActionsClient from .actions import ActionsClient
from .certificates import CertificatesClient from .certificates import CertificatesClient
from .datacenters import DatacentersClient from .datacenters import DatacentersClient
@ -29,10 +29,19 @@ from .ssh_keys import SSHKeysClient
from .volumes import VolumesClient from .volumes import VolumesClient
class PollIntervalFunction(Protocol):
def __call__(self, retries: int) -> float:
"""
Return a interval in seconds to wait between each API call.
:param retries: Number of calls already made.
"""
class Client: class Client:
"""Base Client for accessing the Hetzner Cloud API""" """Base Client for accessing the Hetzner Cloud API"""
_version = VERSION _version = __version__
_retry_wait_time = 0.5 _retry_wait_time = 0.5
__user_agent_prefix = "hcloud-python" __user_agent_prefix = "hcloud-python"
@ -42,7 +51,8 @@ class Client:
api_endpoint: str = "https://api.hetzner.cloud/v1", api_endpoint: str = "https://api.hetzner.cloud/v1",
application_name: str | None = None, application_name: str | None = None,
application_version: str | None = None, application_version: str | None = None,
poll_interval: int = 1, poll_interval: int | float | PollIntervalFunction = 1.0,
poll_max_retries: int = 120,
timeout: float | tuple[float, float] | None = None, timeout: float | tuple[float, float] | None = None,
): ):
"""Create a new Client instance """Create a new Client instance
@ -51,7 +61,11 @@ class Client:
:param api_endpoint: Hetzner Cloud API endpoint :param api_endpoint: Hetzner Cloud API endpoint
:param application_name: Your application name :param application_name: Your application name
:param application_version: Your application _version :param application_version: Your application _version
:param poll_interval: Interval for polling information from Hetzner Cloud API in seconds :param poll_interval:
Interval in seconds to use when polling actions from the API.
You may pass a function to compute a custom poll interval.
:param poll_max_retries:
Max retries before timeout when polling actions from the API.
:param timeout: Requests timeout in seconds :param timeout: Requests timeout in seconds
""" """
self.token = token self.token = token
@ -60,7 +74,12 @@ class Client:
self._application_version = application_version self._application_version = application_version
self._requests_session = requests.Session() self._requests_session = requests.Session()
self._requests_timeout = timeout self._requests_timeout = timeout
self.poll_interval = poll_interval
if isinstance(poll_interval, (int, float)):
self._poll_interval_func = lambda _: poll_interval # Constant poll interval
else:
self._poll_interval_func = poll_interval
self._poll_max_retries = poll_max_retries
self.datacenters = DatacentersClient(self) self.datacenters = DatacentersClient(self)
"""DatacentersClient Instance """DatacentersClient Instance
@ -174,32 +193,18 @@ class Client:
} }
return headers return headers
def _raise_exception_from_response(self, response) -> NoReturn:
raise APIException(
code=response.status_code,
message=response.reason,
details={"content": response.content},
)
def _raise_exception_from_content(self, content: dict) -> NoReturn:
raise APIException(
code=content["error"]["code"],
message=content["error"]["message"],
details=content["error"]["details"],
)
def request( # type: ignore[no-untyped-def] def request( # type: ignore[no-untyped-def]
self, self,
method: str, method: str,
url: str, url: str,
tries: int = 1, *,
_tries: int = 1,
**kwargs, **kwargs,
) -> dict: ) -> dict:
"""Perform a request to the Hetzner Cloud API, wrapper around requests.request """Perform a request to the Hetzner Cloud API, wrapper around requests.request
:param method: HTTP Method to perform the Request :param method: HTTP Method to perform the Request
:param url: URL of the Endpoint :param url: URL of the Endpoint
:param tries: Tries of the request (used internally, should not be set by the user)
:param timeout: Requests timeout in seconds :param timeout: Requests timeout in seconds
:return: Response :return: Response
""" """
@ -213,24 +218,40 @@ class Client:
**kwargs, **kwargs,
) )
content = response.content correlation_id = response.headers.get("X-Correlation-Id")
payload = {}
try: try:
if len(content) > 0: if len(response.content) > 0:
content = response.json() payload = response.json()
except (TypeError, ValueError): except (TypeError, ValueError) as exc:
self._raise_exception_from_response(response) 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 response.ok:
if content: if not payload or "error" not in payload:
assert isinstance(content, dict) raise APIException(
if content["error"]["code"] == "rate_limit_exceeded" and tries < 5: code=response.status_code,
time.sleep(tries * self._retry_wait_time) message=response.reason,
tries = tries + 1 details={"content": response.content},
return self.request(method, url, tries, **kwargs) correlation_id=correlation_id,
)
self._raise_exception_from_content(content) error: dict = payload["error"]
else:
self._raise_exception_from_response(response)
# TODO: return an empty dict instead of an empty string when content == "". if error["code"] == "rate_limit_exceeded" and _tries < 5:
return content # type: ignore[return-value] 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,
)
return payload

View file

@ -10,8 +10,22 @@ class HCloudException(Exception):
class APIException(HCloudException): class APIException(HCloudException):
"""There was an error while performing an API Request""" """There was an error while performing an API Request"""
def __init__(self, code: int | str, message: str | None, details: Any): def __init__(
super().__init__(code if message is None and isinstance(code, str) else message) self,
code: int | str,
message: str,
details: Any,
*,
correlation_id: str | None = None,
):
extras = [str(code)]
if correlation_id is not None:
extras.append(correlation_id)
error = f"{message} ({', '.join(extras)})"
super().__init__(error)
self.code = code self.code = code
self.message = message self.message = message
self.details = details self.details = details
self.correlation_id = correlation_id

View file

@ -1,3 +1,3 @@
from __future__ import annotations from __future__ import annotations
VERSION = "1.35.0" # x-release-please-version __version__ = "2.0.1" # x-release-please-version

View file

@ -16,20 +16,24 @@ class BoundAction(BoundModelBase, Action):
model = Action model = Action
def wait_until_finished(self, max_retries: int = 100) -> None: def wait_until_finished(self, max_retries: int | None = None) -> None:
"""Wait until the specific action has status="finished" (set Client.poll_interval to specify a delay between checks) """Wait until the specific action has status=finished.
:param max_retries: int :param max_retries: int Specify how many retries will be performed before an ActionTimeoutException will be raised.
Specify how many retries will be performed before an ActionTimeoutException will be raised :raises: ActionFailedException when action is finished with status==error
:raises: ActionFailedException when action is finished with status=="error" :raises: ActionTimeoutException when Action is still in status==running after max_retries is reached.
:raises: ActionTimeoutException when Action is still in "running" state after max_retries reloads.
""" """
if max_retries is None:
# pylint: disable=protected-access
max_retries = self._client._client._poll_max_retries
retries = 0
while self.status == Action.STATUS_RUNNING: while self.status == Action.STATUS_RUNNING:
if max_retries > 0: if retries < max_retries:
self.reload() self.reload()
retries += 1
# pylint: disable=protected-access # pylint: disable=protected-access
time.sleep(self._client._client.poll_interval) time.sleep(self._client._client._poll_interval_func(retries))
max_retries = max_retries - 1
else: else:
raise ActionTimeoutException(action=self) raise ActionTimeoutException(action=self)

View file

@ -1,12 +0,0 @@
from __future__ import annotations
import warnings
warnings.warn(
"The 'hcloud.hcloud' module is deprecated, please import from the 'hcloud' module instead (e.g. 'from hcloud import Client').",
DeprecationWarning,
stacklevel=2,
)
# pylint: disable=wildcard-import,wrong-import-position,unused-wildcard-import
from ._client import * # noqa

View file

@ -1,7 +1,6 @@
from __future__ import annotations from __future__ import annotations
from typing import TYPE_CHECKING, Any, NamedTuple from typing import TYPE_CHECKING, Any, NamedTuple
from warnings import warn
from ..core import BoundModelBase, ClientEntityBase, Meta from ..core import BoundModelBase, ClientEntityBase, Meta
from .domain import Iso from .domain import Iso
@ -37,7 +36,6 @@ class IsosClient(ClientEntityBase):
self, self,
name: str | None = None, name: str | None = None,
architecture: list[str] | None = None, architecture: list[str] | None = None,
include_wildcard_architecture: bool | None = None,
include_architecture_wildcard: bool | None = None, include_architecture_wildcard: bool | None = None,
page: int | None = None, page: int | None = None,
per_page: int | None = None, per_page: int | None = None,
@ -48,8 +46,6 @@ class IsosClient(ClientEntityBase):
Can be used to filter ISOs by their name. Can be used to filter ISOs by their name.
:param architecture: List[str] (optional) :param architecture: List[str] (optional)
Can be used to filter ISOs by their architecture. Choices: x86 arm Can be used to filter ISOs by their architecture. Choices: x86 arm
:param include_wildcard_architecture: bool (optional)
Deprecated, please use `include_architecture_wildcard` instead.
:param include_architecture_wildcard: bool (optional) :param include_architecture_wildcard: bool (optional)
Custom ISOs do not have an architecture set. You must also set this flag to True if you are filtering by Custom ISOs do not have an architecture set. You must also set this flag to True if you are filtering by
architecture and also want custom ISOs. architecture and also want custom ISOs.
@ -59,14 +55,6 @@ class IsosClient(ClientEntityBase):
Specifies how many results are returned by page Specifies how many results are returned by page
:return: (List[:class:`BoundIso <hcloud.isos.client.BoundIso>`], :class:`Meta <hcloud.core.domain.Meta>`) :return: (List[:class:`BoundIso <hcloud.isos.client.BoundIso>`], :class:`Meta <hcloud.core.domain.Meta>`)
""" """
if include_wildcard_architecture is not None:
warn(
"The `include_wildcard_architecture` argument is deprecated, please use the `include_architecture_wildcard` argument instead.",
DeprecationWarning,
)
include_architecture_wildcard = include_wildcard_architecture
params: dict[str, Any] = {} params: dict[str, Any] = {}
if name is not None: if name is not None:
params["name"] = name params["name"] = name
@ -87,7 +75,6 @@ class IsosClient(ClientEntityBase):
self, self,
name: str | None = None, name: str | None = None,
architecture: list[str] | None = None, architecture: list[str] | None = None,
include_wildcard_architecture: bool | None = None,
include_architecture_wildcard: bool | None = None, include_architecture_wildcard: bool | None = None,
) -> list[BoundIso]: ) -> list[BoundIso]:
"""Get all ISOs """Get all ISOs
@ -96,21 +83,11 @@ class IsosClient(ClientEntityBase):
Can be used to filter ISOs by their name. Can be used to filter ISOs by their name.
:param architecture: List[str] (optional) :param architecture: List[str] (optional)
Can be used to filter ISOs by their architecture. Choices: x86 arm Can be used to filter ISOs by their architecture. Choices: x86 arm
:param include_wildcard_architecture: bool (optional)
Deprecated, please use `include_architecture_wildcard` instead.
:param include_architecture_wildcard: bool (optional) :param include_architecture_wildcard: bool (optional)
Custom ISOs do not have an architecture set. You must also set this flag to True if you are filtering by Custom ISOs do not have an architecture set. You must also set this flag to True if you are filtering by
architecture and also want custom ISOs. architecture and also want custom ISOs.
:return: List[:class:`BoundIso <hcloud.isos.client.BoundIso>`] :return: List[:class:`BoundIso <hcloud.isos.client.BoundIso>`]
""" """
if include_wildcard_architecture is not None:
warn(
"The `include_wildcard_architecture` argument is deprecated, please use the `include_architecture_wildcard` argument instead.",
DeprecationWarning,
)
include_architecture_wildcard = include_wildcard_architecture
return self._iter_pages( return self._iter_pages(
self.get_list, self.get_list,
name=name, name=name,

View file

@ -189,9 +189,8 @@ class PrimaryIPsClient(ClientEntityBase):
def create( def create(
self, self,
type: str, type: str,
# TODO: Make the datacenter argument optional
datacenter: Datacenter | BoundDatacenter | None,
name: str, name: str,
datacenter: Datacenter | BoundDatacenter | None = None,
assignee_type: str | None = "server", assignee_type: str | None = "server",
assignee_id: int | None = None, assignee_id: int | None = None,
auto_delete: bool | None = False, auto_delete: bool | None = False,
@ -199,23 +198,21 @@ class PrimaryIPsClient(ClientEntityBase):
) -> CreatePrimaryIPResponse: ) -> CreatePrimaryIPResponse:
"""Creates a new Primary IP assigned to a server. """Creates a new Primary IP assigned to a server.
:param type: str :param type: str Primary IP type Choices: ipv4, ipv6
Primary IP type Choices: ipv4, ipv6
:param assignee_type: str
:param assignee_id: int (optional)
:param datacenter: Datacenter
:param labels: Dict[str, str] (optional)
User-defined labels (key-value pairs)
:param name: str :param name: str
:param datacenter: Datacenter (optional)
:param assignee_type: str (optional)
:param assignee_id: int (optional)
:param auto_delete: bool (optional) :param auto_delete: bool (optional)
:param labels: Dict[str, str] (optional) User-defined labels (key-value pairs)
:return: :class:`CreatePrimaryIPResponse <hcloud.primary_ips.domain.CreatePrimaryIPResponse>` :return: :class:`CreatePrimaryIPResponse <hcloud.primary_ips.domain.CreatePrimaryIPResponse>`
""" """
data: dict[str, Any] = { data: dict[str, Any] = {
"name": name,
"type": type, "type": type,
"assignee_type": assignee_type, "assignee_type": assignee_type,
"auto_delete": auto_delete, "auto_delete": auto_delete,
"name": name,
} }
if datacenter is not None: if datacenter is not None:
data["datacenter"] = datacenter.id_or_name data["datacenter"] = datacenter.id_or_name

View file

@ -1,6 +1,5 @@
from __future__ import annotations from __future__ import annotations
import warnings
from datetime import datetime from datetime import datetime
from typing import TYPE_CHECKING, Any, NamedTuple from typing import TYPE_CHECKING, Any, NamedTuple
@ -336,15 +335,14 @@ class BoundServer(BoundModelBase, Server):
def rebuild( def rebuild(
self, self,
image: Image | BoundImage, image: Image | BoundImage,
*, # pylint: disable=unused-argument
return_response: bool = False, **kwargs: Any,
) -> RebuildResponse | BoundAction: ) -> RebuildResponse:
"""Rebuilds a server overwriting its disk with the content of an image, thereby destroying all data on the target server. """Rebuilds a server overwriting its disk with the content of an image, thereby destroying all data on the target server.
:param image: Image to use for the rebuilt server :param image: Image to use for the rebuilt server
:param return_response: Whether to return the full response or only the action.
""" """
return self._client.rebuild(self, image, return_response=return_response) return self._client.rebuild(self, image)
def change_type( def change_type(
self, self,
@ -1009,14 +1007,13 @@ class ServersClient(ClientEntityBase):
self, self,
server: Server | BoundServer, server: Server | BoundServer,
image: Image | BoundImage, image: Image | BoundImage,
*, # pylint: disable=unused-argument
return_response: bool = False, **kwargs: Any,
) -> RebuildResponse | BoundAction: ) -> RebuildResponse:
"""Rebuilds a server overwriting its disk with the content of an image, thereby destroying all data on the target server. """Rebuilds a server overwriting its disk with the content of an image, thereby destroying all data on the target server.
:param server: Server to rebuild :param server: Server to rebuild
:param image: Image to use for the rebuilt server :param image: Image to use for the rebuilt server
:param return_response: Whether to return the full response or only the action.
""" """
data: dict[str, Any] = {"image": image.id_or_name} data: dict[str, Any] = {"image": image.id_or_name}
response = self._client.request( response = self._client.request(
@ -1025,22 +1022,11 @@ class ServersClient(ClientEntityBase):
json=data, json=data,
) )
rebuild_response = RebuildResponse( return RebuildResponse(
action=BoundAction(self._client.actions, response["action"]), action=BoundAction(self._client.actions, response["action"]),
root_password=response.get("root_password"), root_password=response.get("root_password"),
) )
if not return_response:
warnings.warn(
"Returning only the 'action' is deprecated, please set the "
"'return_response' keyword argument to 'True' to return the full "
"rebuild response and update your code accordingly.",
DeprecationWarning,
stacklevel=2,
)
return rebuild_response.action
return rebuild_response
def enable_backup(self, server: Server | BoundServer) -> BoundAction: def enable_backup(self, server: Server | BoundServer) -> BoundAction:
"""Enables and configures the automatic daily backup option for the server. Enabling automatic backups will increase the price of the server by 20%. """Enables and configures the automatic daily backup option for the server. Enabling automatic backups will increase the price of the server by 20%.

View file

@ -172,7 +172,7 @@ class AnsibleHCloudIsoInfo(AnsibleHCloud):
else: else:
self.hcloud_iso_info = self.client.isos.get_all( self.hcloud_iso_info = self.client.isos.get_all(
architecture=self.module.params.get("architecture"), architecture=self.module.params.get("architecture"),
include_wildcard_architecture=self.module.params.get("include_wildcard_architecture"), include_architecture_wildcard=self.module.params.get("include_wildcard_architecture"),
) )
except HCloudException as exception: except HCloudException as exception:

View file

@ -214,7 +214,6 @@ class AnsibleHCloudPrimaryIP(AnsibleHCloud):
"type": self.module.params.get("type"), "type": self.module.params.get("type"),
"name": self.module.params.get("name"), "name": self.module.params.get("name"),
"auto_delete": self.module.params.get("auto_delete"), "auto_delete": self.module.params.get("auto_delete"),
"datacenter": None, # TODO: https://github.com/hetznercloud/hcloud-python/pull/363
} }
if self.module.params.get("datacenter") is not None: if self.module.params.get("datacenter") is not None:

View file

@ -868,7 +868,7 @@ class AnsibleHCloudServer(AnsibleHCloud):
if not self.module.check_mode: if not self.module.check_mode:
image = self._get_image(self.hcloud_server.server_type) image = self._get_image(self.hcloud_server.server_type)
# When we rebuild the server progress takes some more time. # When we rebuild the server progress takes some more time.
resp = self.client.servers.rebuild(self.hcloud_server, image, return_response=True) resp = self.client.servers.rebuild(self.hcloud_server, image)
resp.action.wait_until_finished(1000) resp.action.wait_until_finished(1000)
self._mark_as_changed() self._mark_as_changed()

View file

@ -22,26 +22,15 @@ from textwrap import dedent
logger = logging.getLogger("vendor") logger = logging.getLogger("vendor")
HCLOUD_SOURCE_URL = "https://github.com/hetznercloud/hcloud-python" HCLOUD_SOURCE_URL = "https://github.com/hetznercloud/hcloud-python"
HCLOUD_VERSION = "v1.35.0" HCLOUD_VERSION = "v2.0.1"
HCLOUD_VENDOR_PATH = "plugins/module_utils/vendor/hcloud" HCLOUD_VENDOR_PATH = "plugins/module_utils/vendor/hcloud"
def apply_code_modifications(source_path: Path): def apply_code_modifications(source_path: Path):
# The ansible galaxy-importer consider __version___.py to be an invalid filename in module_utils/
# Move the __version__.py file to _version.py
move(source_path / "__version__.py", source_path / "_version.py")
for file in source_path.rglob("*.py"): for file in source_path.rglob("*.py"):
content = file.read_text() content = file.read_text()
content_orig = content content_orig = content
# Move the __version__.py file to _version.py
content = re.sub(
r"from .__version__ import VERSION",
r"from ._version import VERSION",
content,
)
# Wrap requests imports # Wrap requests imports
content = re.sub( content = re.sub(
r"import requests", r"import requests",

View file

@ -102,7 +102,7 @@
that: that:
- result is failed - result is failed
- result.failure.code == "invalid_input" - result.failure.code == "invalid_input"
- result.msg == "invalid input in fields 'server', 'home_location'" - result.failure.message == "invalid input in fields 'server', 'home_location'"
- name: test create Floating IP with check mode - name: test create Floating IP with check mode
hetzner.hcloud.floating_ip: hetzner.hcloud.floating_ip:
@ -373,7 +373,7 @@
that: that:
- result is failed - result is failed
- result.failure.code == "protected" - result.failure.code == "protected"
- result.msg == "Floating IP deletion is protected" - result.failure.message == "Floating IP deletion is protected"
- name: test update Floating IP delete protection - name: test update Floating IP delete protection
hetzner.hcloud.floating_ip: hetzner.hcloud.floating_ip:
@ -462,7 +462,7 @@
that: that:
- result is failed - result is failed
- result.failure.code == "protected" - result.failure.code == "protected"
- result.msg == "Floating IP deletion is protected" - result.failure.message == "Floating IP deletion is protected"
- name: test update Floating IP delete protection - name: test update Floating IP delete protection
hetzner.hcloud.floating_ip: hetzner.hcloud.floating_ip:

View file

@ -121,7 +121,7 @@
that: that:
- result is failed - result is failed
- result.failure.code == "protected" - result.failure.code == "protected"
- result.msg == "load balancer deletion is protected" - result.failure.message == "load balancer deletion is protected"
- name: Test update delete_protection - name: Test update delete_protection
hetzner.hcloud.load_balancer: hetzner.hcloud.load_balancer:

View file

@ -100,7 +100,7 @@
that: that:
- result is failed - result is failed
- result.failure.code == "protected" - result.failure.code == "protected"
- result.msg == "network deletion is protected" - result.failure.message == "network deletion is protected"
- name: Test update delete protection - name: Test update delete protection
hetzner.hcloud.network: hetzner.hcloud.network:

View file

@ -121,7 +121,7 @@
that: that:
- result is failed - result is failed
- result.failure.code == "protected" - result.failure.code == "protected"
- result.msg == "Primary IP deletion is protected" - result.failure.message == "Primary IP deletion is protected"
- name: Test update delete protection - name: Test update delete protection
hetzner.hcloud.primary_ip: hetzner.hcloud.primary_ip:

View file

@ -316,7 +316,7 @@
that: that:
- result is failed - result is failed
- result.failure.code == "protected" - result.failure.code == "protected"
- result.msg == "server deletion is protected" - result.failure.message == "server deletion is protected"
- name: test rebuild server fails if it is protected - name: test rebuild server fails if it is protected
hetzner.hcloud.server: hetzner.hcloud.server:
@ -330,7 +330,7 @@
that: that:
- result is failed - result is failed
- result.failure.code == "protected" - result.failure.code == "protected"
- result.msg == "server rebuild is protected" - result.failure.message == "server rebuild is protected"
- name: test remove server protection - name: test remove server protection
hetzner.hcloud.server: hetzner.hcloud.server:
@ -588,7 +588,7 @@
that: that:
- result is failed - result is failed
- result.failure.code == "protected" - result.failure.code == "protected"
- result.msg == "server deletion is protected" - result.failure.message == "server deletion is protected"
- name: remove protection from server - name: remove protection from server
hetzner.hcloud.server: hetzner.hcloud.server:

View file

@ -14,7 +14,7 @@
that: that:
- result is failed - result is failed
- result.failure.code == "not_found" - result.failure.code == "not_found"
- result.msg == "firewall not-existing was not found" - result.failure.message == "firewall not-existing was not found"
- name: setup create firewalls - name: setup create firewalls
hetzner.hcloud.firewall: hetzner.hcloud.firewall:

View file

@ -35,7 +35,7 @@
that: that:
- result is failed - result is failed
- result.failure.code == "not_found" - result.failure.code == "not_found"
- result.msg == "server_type not-existing-server-type was not found" - result.failure.message == "server_type not-existing-server-type was not found"
- name: test create server with not existing image - name: test create server with not existing image
hetzner.hcloud.server: hetzner.hcloud.server:
@ -50,4 +50,4 @@
that: that:
- result is failed - result is failed
- result.failure.code == "not_found" - result.failure.code == "not_found"
- result.msg == "Image my-not-existing-image-20.04 was not found" - result.failure.message == "Image my-not-existing-image-20.04 was not found"

View file

@ -134,7 +134,7 @@
that: that:
- result is failed - result is failed
- result.failure.code == "uniqueness_error" - result.failure.code == "uniqueness_error"
- result.msg == "SSH key with the same fingerprint already exists" - result.failure.message == "SSH key with the same fingerprint already exists"
- name: test delete ssh key - name: test delete ssh key
hetzner.hcloud.ssh_key: hetzner.hcloud.ssh_key:

View file

@ -209,7 +209,7 @@
that: that:
- result is failed - result is failed
- result.failure.code == "protected" - result.failure.code == "protected"
- result.msg == "volume deletion is protected" - result.failure.message == "volume deletion is protected"
- name: test update Volume delete protection - name: test update Volume delete protection
hetzner.hcloud.volume: hetzner.hcloud.volume:
@ -256,7 +256,7 @@
that: that:
- result is failed - result is failed
- result.failure.code == "protected" - result.failure.code == "protected"
- result.msg == "volume deletion is protected" - result.failure.message == "volume deletion is protected"
- name: test update Volume delete protection - name: test update Volume delete protection
hetzner.hcloud.volume: hetzner.hcloud.volume:

View file

@ -35,7 +35,7 @@ def test_hcloud_fail_json_hcloud(module):
hcloud.fail_json_hcloud(exception) hcloud.fail_json_hcloud(exception)
module.fail_json.assert_called_with( module.fail_json.assert_called_with(
msg="invalid input in fields 'server', 'home_location'", msg="invalid input in fields 'server', 'home_location' (invalid_input)",
exception=traceback.format_exc(), exception=traceback.format_exc(),
failure={ failure={
"message": "invalid input in fields 'server', 'home_location'", "message": "invalid input in fields 'server', 'home_location'",