Skip to content

Commit 38e098a

Browse files
authored
feat: allow returning root_password in servers rebuild (#276)
Fixes #181 Allow returning a full response during the server rebuild by passing an option to the rebuild method. The main reason for this is to not break user land and give users time to upgrade. We also don't want to introduce a new method `rebuild2` or `rebuild_with_full_response` to keep the API clean. The deprecation timeline is the following: - Introduce the `return_response` argument and deprecated the default behavior (`return_response=False`) - Wait for the next major release to remove the ability to only return the action and deprecated the usage of the `return_response` argument. - Wait for the next+1 major release to fully remove the `return_response` argument, or simply leave it unused.
1 parent 213b661 commit 38e098a

File tree

3 files changed

+71
-15
lines changed

3 files changed

+71
-15
lines changed

hcloud/servers/client.py

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import annotations
22

3+
import warnings
34
from typing import TYPE_CHECKING, Any, NamedTuple
45

56
from ..actions import ActionsPageResult, BoundAction, ResourceActionsClient
@@ -21,6 +22,7 @@
2122
PrivateNet,
2223
PublicNetwork,
2324
PublicNetworkFirewall,
25+
RebuildResponse,
2426
RequestConsoleResponse,
2527
ResetPasswordResponse,
2628
Server,
@@ -299,13 +301,18 @@ def create_image(
299301
"""
300302
return self._client.create_image(self, description, type, labels)
301303

302-
def rebuild(self, image: Image | BoundImage) -> BoundAction:
304+
def rebuild(
305+
self,
306+
image: Image | BoundImage,
307+
*,
308+
return_response: bool = False,
309+
) -> RebuildResponse | BoundAction:
303310
"""Rebuilds a server overwriting its disk with the content of an image, thereby destroying all data on the target server.
304311
305-
:param image: :class:`BoundImage <hcloud.images.client.BoundImage>` or :class:`Image <hcloud.servers.domain.Image>`
306-
:return: :class:`BoundAction <hcloud.actions.client.BoundAction>`
312+
:param image: Image to use for the rebuilt server
313+
:param return_response: Whether to return the full response or only the action.
307314
"""
308-
return self._client.rebuild(self, image)
315+
return self._client.rebuild(self, image, return_response=return_response)
309316

310317
def change_type(
311318
self,
@@ -930,20 +937,37 @@ def rebuild(
930937
self,
931938
server: Server | BoundServer,
932939
image: Image | BoundImage,
933-
) -> BoundAction:
940+
*,
941+
return_response: bool = False,
942+
) -> RebuildResponse | BoundAction:
934943
"""Rebuilds a server overwriting its disk with the content of an image, thereby destroying all data on the target server.
935944
936-
:param server: :class:`BoundServer <hcloud.servers.client.BoundServer>` or :class:`Server <hcloud.servers.domain.Server>`
937-
:param image: :class:`BoundImage <hcloud.images.client.BoundImage>` or :class:`Image <hcloud.servers.domain.Image>`
938-
:return: :class:`BoundAction <hcloud.actions.client.BoundAction>`
945+
:param server: Server to rebuild
946+
:param image: Image to use for the rebuilt server
947+
:param return_response: Whether to return the full response or only the action.
939948
"""
940949
data: dict[str, Any] = {"image": image.id_or_name}
941950
response = self._client.request(
942951
url=f"/servers/{server.id}/actions/rebuild",
943952
method="POST",
944953
json=data,
945954
)
946-
return BoundAction(self._client.actions, response["action"])
955+
956+
rebuild_response = RebuildResponse(
957+
action=BoundAction(self._client.actions, response["action"]),
958+
root_password=response.get("root_password"),
959+
)
960+
961+
if not return_response:
962+
warnings.warn(
963+
"Returning only the 'action' is deprecated, please set the "
964+
"'return_response' keyword argument to 'True' to return the full "
965+
"rebuild response and update your code accordingly.",
966+
DeprecationWarning,
967+
stacklevel=2,
968+
)
969+
return rebuild_response.action
970+
return rebuild_response
947971

948972
def enable_backup(self, server: Server | BoundServer) -> BoundAction:
949973
"""Enables and configures the automatic daily backup option for the server. Enabling automatic backups will increase the price of the server by 20%.

hcloud/servers/domain.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,24 @@ def __init__(
244244
self.password = password
245245

246246

247+
class RebuildResponse(BaseDomain):
248+
"""Rebuild Response Domain
249+
250+
:param action: Shows the progress of the server rebuild action
251+
:param root_password: The root password of the server when not using SSH keys
252+
"""
253+
254+
__slots__ = ("action", "root_password")
255+
256+
def __init__(
257+
self,
258+
action: BoundAction,
259+
root_password: str | None,
260+
):
261+
self.action = action
262+
self.root_password = root_password
263+
264+
247265
class PublicNetwork(BaseDomain):
248266
"""Public Network Domain
249267

tests/unit/servers/test_client.py

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -307,15 +307,19 @@ def test_create_image(
307307

308308
def test_rebuild(self, hetzner_client, bound_server, generic_action):
309309
hetzner_client.request.return_value = generic_action
310-
action = bound_server.rebuild(Image(name="ubuntu-20.04"))
310+
response = bound_server.rebuild(
311+
Image(name="ubuntu-20.04"),
312+
return_response=True,
313+
)
311314
hetzner_client.request.assert_called_with(
312315
url="/servers/14/actions/rebuild",
313316
method="POST",
314317
json={"image": "ubuntu-20.04"},
315318
)
316319

317-
assert action.id == 1
318-
assert action.progress == 0
320+
assert response.action.id == 1
321+
assert response.action.progress == 0
322+
assert response.root_password is None or isinstance(response.root_password, str)
319323

320324
def test_enable_backup(self, hetzner_client, bound_server, generic_action):
321325
hetzner_client.request.return_value = generic_action
@@ -1040,15 +1044,25 @@ def test_create_image(self, servers_client, server, response_server_create_image
10401044
)
10411045
def test_rebuild(self, servers_client, server, generic_action):
10421046
servers_client._client.request.return_value = generic_action
1043-
action = servers_client.rebuild(server, Image(name="ubuntu-20.04"))
1047+
response = servers_client.rebuild(
1048+
server,
1049+
Image(name="ubuntu-20.04"),
1050+
return_response=True,
1051+
)
10441052
servers_client._client.request.assert_called_with(
10451053
url="/servers/1/actions/rebuild",
10461054
method="POST",
10471055
json={"image": "ubuntu-20.04"},
10481056
)
10491057

1050-
assert action.id == 1
1051-
assert action.progress == 0
1058+
assert response.action.id == 1
1059+
assert response.action.progress == 0
1060+
assert response.root_password is None or isinstance(response.root_password, str)
1061+
1062+
def test_rebuild_return_response_deprecation(self, servers_client, generic_action):
1063+
servers_client._client.request.return_value = generic_action
1064+
with pytest.deprecated_call():
1065+
servers_client.rebuild(Server(id=1), Image(name="ubuntu-20.04"))
10521066

10531067
@pytest.mark.parametrize(
10541068
"server", [Server(id=1), BoundServer(mock.MagicMock(), dict(id=1))]

0 commit comments

Comments
 (0)