Skip to content

Commit 9d5afe0

Browse files
authored
feat: add support for ARM APIs (#182)
* feat: add field Architecture to ISO, ServerType & Image * feat: add architecture filter to image & iso clients * feat(image): add new method to get image by name&architecture * chore: prepare release 1.19.0
1 parent be38344 commit 9d5afe0

File tree

12 files changed

+129
-8
lines changed

12 files changed

+129
-8
lines changed

hcloud/__version__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
VERSION = "1.18.2"
1+
VERSION = "1.19.0"

hcloud/images/client.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ def get_list(
169169
label_selector=None, # type: Optional[str]
170170
bound_to=None, # type: Optional[List[str]]
171171
type=None, # type: Optional[List[str]]
172+
architecture=None, # type: Optional[List[str]]
172173
sort=None, # type: Optional[List[str]]
173174
page=None, # type: Optional[int]
174175
per_page=None, # type: Optional[int]
@@ -186,6 +187,8 @@ def get_list(
186187
Server Id linked to the image. Only available for images of type backup
187188
:param type: List[str] (optional)
188189
Choices: system snapshot backup
190+
:param architecture: List[str] (optional)
191+
Choices: x86 arm
189192
:param status: List[str] (optional)
190193
Can be used to filter images by their status. The response will only contain images matching the status.
191194
:param sort: List[str] (optional)
@@ -207,6 +210,8 @@ def get_list(
207210
params["bound_to"] = bound_to
208211
if type is not None:
209212
params["type"] = type
213+
if architecture is not None:
214+
params["architecture"] = architecture
210215
if sort is not None:
211216
params["sort"] = sort
212217
if page is not None:
@@ -228,6 +233,7 @@ def get_all(
228233
label_selector=None, # type: Optional[str]
229234
bound_to=None, # type: Optional[List[str]]
230235
type=None, # type: Optional[List[str]]
236+
architecture=None, # type: Optional[List[str]]
231237
sort=None, # type: Optional[List[str]]
232238
status=None, # type: Optional[List[str]]
233239
include_deprecated=None, # type: Optional[bool]
@@ -243,6 +249,8 @@ def get_all(
243249
Server Id linked to the image. Only available for images of type backup
244250
:param type: List[str] (optional)
245251
Choices: system snapshot backup
252+
:param architecture: List[str] (optional)
253+
Choices: x86 arm
246254
:param status: List[str] (optional)
247255
Can be used to filter images by their status. The response will only contain images matching the status.
248256
:param sort: List[str] (optional)
@@ -256,6 +264,7 @@ def get_all(
256264
label_selector=label_selector,
257265
bound_to=bound_to,
258266
type=type,
267+
architecture=architecture,
259268
sort=sort,
260269
status=status,
261270
include_deprecated=include_deprecated,
@@ -265,12 +274,29 @@ def get_by_name(self, name):
265274
# type: (str) -> BoundImage
266275
"""Get image by name
267276
277+
Deprecated: Use get_by_name_and_architecture instead.
278+
268279
:param name: str
269280
Used to get image by name.
270281
:return: :class:`BoundImage <hcloud.images.client.BoundImage>`
271282
"""
272283
return super(ImagesClient, self).get_by_name(name)
273284

285+
def get_by_name_and_architecture(self, name, architecture):
286+
# type: (str, str) -> BoundImage
287+
"""Get image by name
288+
289+
:param name: str
290+
Used to identify the image.
291+
:param architecture: str
292+
Used to identify the image.
293+
:return: :class:`BoundImage <hcloud.images.client.BoundImage>`
294+
"""
295+
response = self.get_list(name=name, architecture=[architecture])
296+
entities = getattr(response, self.results_list_attribute_name)
297+
entity = entities[0] if entities else None
298+
return entity
299+
274300
def update(self, image, description=None, type=None, labels=None):
275301
# type:(Image, Optional[str], Optional[str], Optional[Dict[str, str]]) -> BoundImage
276302
"""Updates the Image. You may change the description, convert a Backup image to a Snapshot Image or change the image labels.
@@ -311,7 +337,7 @@ def delete(self, image):
311337
return True
312338

313339
def change_protection(self, image, delete=None):
314-
# type: (Image, Optional[bool], Optional[bool]) -> BoundAction
340+
# type: (Image, Optional[bool]) -> BoundAction
315341
"""Changes the protection configuration of the image. Can only be used on snapshots.
316342
317343
:param image: :class:`BoundImage <hcloud.images.client.BoundImage>` or :class:`Image <hcloud.images.domain.Image>`

hcloud/images/domain.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ class Image(BaseDomain, DomainIdentityMixin):
3030
Flavor of operating system contained in the image Choices: `ubuntu`, `centos`, `debian`, `fedora`, `unknown`
3131
:param os_version: str, None
3232
Operating system version
33+
:param architecture: str
34+
CPU Architecture that the image is compatible with. Choices: `x86`, `arm`
3335
:param rapid_deploy: bool
3436
Indicates that rapid deploy of the image is available
3537
:param protection: dict
@@ -50,6 +52,7 @@ class Image(BaseDomain, DomainIdentityMixin):
5052
"bound_to",
5153
"os_flavor",
5254
"os_version",
55+
"architecture",
5356
"rapid_deploy",
5457
"created_from",
5558
"status",
@@ -72,6 +75,7 @@ def __init__(
7275
bound_to=None,
7376
os_flavor=None,
7477
os_version=None,
78+
architecture=None,
7579
rapid_deploy=None,
7680
created_from=None,
7781
protection=None,
@@ -89,6 +93,7 @@ def __init__(
8993
self.bound_to = bound_to
9094
self.os_flavor = os_flavor
9195
self.os_version = os_version
96+
self.architecture = architecture
9297
self.rapid_deploy = rapid_deploy
9398
self.created_from = created_from
9499
self.protection = protection

hcloud/isos/client.py

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ def get_by_id(self, id):
2525
def get_list(
2626
self,
2727
name=None, # type: Optional[str]
28+
architecture=None, # type: Optional[List[str]]
29+
include_wildcard_architecture=None, # type: Optional[bool]
2830
page=None, # type: Optional[int]
2931
per_page=None, # type: Optional[int]
3032
):
@@ -33,6 +35,11 @@ def get_list(
3335
3436
:param name: str (optional)
3537
Can be used to filter ISOs by their name.
38+
:param architecture: List[str] (optional)
39+
Can be used to filter ISOs by their architecture. Choices: x86 arm
40+
:param include_wildcard_architecture: bool (optional)
41+
Custom ISOs do not have an architecture set. You must also set this flag to True if you are filtering by
42+
architecture and also want custom ISOs.
3643
:param page: int (optional)
3744
Specifies the page to fetch
3845
:param per_page: int (optional)
@@ -42,6 +49,10 @@ def get_list(
4249
params = {}
4350
if name is not None:
4451
params["name"] = name
52+
if architecture is not None:
53+
params["architecture"] = architecture
54+
if include_wildcard_architecture is not None:
55+
params["include_wildcard_architecture"] = include_wildcard_architecture
4556
if page is not None:
4657
params["page"] = page
4758
if per_page is not None:
@@ -51,15 +62,29 @@ def get_list(
5162
isos = [BoundIso(self, iso_data) for iso_data in response["isos"]]
5263
return self._add_meta_to_result(isos, response)
5364

54-
def get_all(self, name=None):
55-
# type: (Optional[str]) -> List[BoundIso]
65+
def get_all(
66+
self,
67+
name=None, # type: Optional[str]
68+
architecture=None, # type: Optional[List[str]]
69+
include_wildcard_architecture=None, # type: Optional[bool]
70+
):
71+
# type: (...) -> List[BoundIso]
5672
"""Get all ISOs
5773
5874
:param name: str (optional)
5975
Can be used to filter ISOs by their name.
76+
:param architecture: List[str] (optional)
77+
Can be used to filter ISOs by their architecture. Choices: x86 arm
78+
:param include_wildcard_architecture: bool (optional)
79+
Custom ISOs do not have an architecture set. You must also set this flag to True if you are filtering by
80+
architecture and also want custom ISOs.
6081
:return: List[:class:`BoundIso <hcloud.isos.client.BoundIso>`]
6182
"""
62-
return super(IsosClient, self).get_all(name=name)
83+
return super(IsosClient, self).get_all(
84+
name=name,
85+
architecture=architecture,
86+
include_wildcard_architecture=include_wildcard_architecture,
87+
)
6388

6489
def get_by_name(self, name):
6590
# type: (str) -> BoundIso

hcloud/isos/domain.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,26 @@ class Iso(BaseDomain, DomainIdentityMixin):
1414
Description of the ISO
1515
:param type: str
1616
Type of the ISO. Choices: `public`, `private`
17+
:param architecture: str, None
18+
CPU Architecture that the ISO is compatible with. None means that the compatibility is unknown. Choices: `x86`, `arm`
1719
:param deprecated: datetime, None
1820
ISO 8601 timestamp of deprecation, None if ISO is still available. After the deprecation time it will no longer be possible to attach the ISO to servers.
1921
"""
2022

21-
__slots__ = ("id", "name", "type", "description", "deprecated")
23+
__slots__ = ("id", "name", "type", "architecture", "description", "deprecated")
2224

2325
def __init__(
24-
self, id=None, name=None, type=None, description=None, deprecated=None
26+
self,
27+
id=None,
28+
name=None,
29+
type=None,
30+
architecture=None,
31+
description=None,
32+
deprecated=None,
2533
):
2634
self.id = id
2735
self.name = name
2836
self.type = type
37+
self.architecture = architecture
2938
self.description = description
3039
self.deprecated = isoparse(deprecated) if deprecated else None

hcloud/server_types/domain.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ class ServerType(BaseDomain, DomainIdentityMixin):
2222
Type of server boot drive. Local has higher speed. Network has better availability. Choices: `local`, `network`
2323
:param cpu_type: string
2424
Type of cpu. Choices: `shared`, `dedicated`
25+
:param architecture: string
26+
Architecture of cpu. Choices: `x86`, `arm`
2527
:param deprecated: bool
2628
True if server type is deprecated
2729
"""
@@ -36,6 +38,7 @@ class ServerType(BaseDomain, DomainIdentityMixin):
3638
"prices",
3739
"storage_type",
3840
"cpu_type",
41+
"architecture",
3942
"deprecated",
4043
)
4144

@@ -50,6 +53,7 @@ def __init__(
5053
prices=None,
5154
storage_type=None,
5255
cpu_type=None,
56+
architecture=None,
5357
deprecated=None,
5458
):
5559
self.id = id
@@ -61,4 +65,5 @@ def __init__(
6165
self.prices = prices
6266
self.storage_type = storage_type
6367
self.cpu_type = cpu_type
68+
self.architecture = architecture
6469
self.deprecated = deprecated

tests/unit/images/conftest.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ def image_response():
1717
"bound_to": 1,
1818
"os_flavor": "ubuntu",
1919
"os_version": "16.04",
20+
"architecture": "x86",
2021
"rapid_deploy": False,
2122
"protection": {"delete": False},
2223
"deprecated": "2018-02-28T00:00:00+00:00",
@@ -42,6 +43,7 @@ def two_images_response():
4243
"bound_to": None,
4344
"os_flavor": "ubuntu",
4445
"os_version": "16.04",
46+
"architecture": "x86",
4547
"rapid_deploy": False,
4648
"protection": {"delete": False},
4749
"deprecated": "2018-02-28T00:00:00+00:00",
@@ -60,6 +62,7 @@ def two_images_response():
6062
"bound_to": None,
6163
"os_flavor": "ubuntu",
6264
"os_version": "16.04",
65+
"architecture": "x86",
6366
"rapid_deploy": False,
6467
"protection": {"delete": False},
6568
"deprecated": "2018-02-28T00:00:00+00:00",
@@ -86,6 +89,7 @@ def one_images_response():
8689
"bound_to": None,
8790
"os_flavor": "ubuntu",
8891
"os_version": "16.04",
92+
"architecture": "x86",
8993
"rapid_deploy": False,
9094
"protection": {"delete": False},
9195
"deprecated": "2018-02-28T00:00:00+00:00",
@@ -111,6 +115,7 @@ def response_update_image():
111115
"bound_to": None,
112116
"os_flavor": "ubuntu",
113117
"os_version": "16.04",
118+
"architecture": "arm",
114119
"rapid_deploy": False,
115120
"protection": {"delete": False},
116121
"deprecated": "2018-02-28T00:00:00+00:00",

tests/unit/images/test_client.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ def test_bound_image_init(self, image_response):
2929
)
3030
assert bound_image.os_flavor == "ubuntu"
3131
assert bound_image.os_version == "16.04"
32+
assert bound_image.architecture == "x86"
3233
assert bound_image.rapid_deploy is False
3334
assert bound_image.deprecated == datetime.datetime(
3435
2018, 2, 28, 0, 0, tzinfo=tzoffset(None, 0)
@@ -223,6 +224,21 @@ def test_get_by_name(self, images_client, one_images_response):
223224
assert image.id == 4711
224225
assert image.name == "ubuntu-20.04"
225226

227+
def test_get_by_name_and_architecture(self, images_client, one_images_response):
228+
images_client._client.request.return_value = one_images_response
229+
image = images_client.get_by_name_and_architecture("ubuntu-20.04", "x86")
230+
231+
params = {"name": "ubuntu-20.04", "architecture": ["x86"]}
232+
233+
images_client._client.request.assert_called_with(
234+
url="/images", method="GET", params=params
235+
)
236+
237+
assert image._client is images_client
238+
assert image.id == 4711
239+
assert image.name == "ubuntu-20.04"
240+
assert image.architecture == "x86"
241+
226242
@pytest.mark.parametrize(
227243
"image", [Image(id=1), BoundImage(mock.MagicMock(), dict(id=1))]
228244
)

tests/unit/isos/conftest.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ def iso_response():
99
"name": "FreeBSD-11.0-RELEASE-amd64-dvd1",
1010
"description": "FreeBSD 11.0 x64",
1111
"type": "public",
12+
"architecture": "x86",
1213
"deprecated": "2018-02-28T00:00:00+00:00",
1314
}
1415
}
@@ -23,13 +24,15 @@ def two_isos_response():
2324
"name": "FreeBSD-11.0-RELEASE-amd64-dvd1",
2425
"description": "FreeBSD 11.0 x64",
2526
"type": "public",
27+
"architecture": "x86",
2628
"deprecated": "2018-02-28T00:00:00+00:00",
2729
},
2830
{
2931
"id": 4712,
3032
"name": "FreeBSD-11.0-RELEASE-amd64-dvd1",
3133
"description": "FreeBSD 11.0 x64",
3234
"type": "public",
35+
"architecture": "x86",
3336
"deprecated": "2018-02-28T00:00:00+00:00",
3437
},
3538
]
@@ -45,6 +48,7 @@ def one_isos_response():
4548
"name": "FreeBSD-11.0-RELEASE-amd64-dvd1",
4649
"description": "FreeBSD 11.0 x64",
4750
"type": "public",
51+
"architecture": "x86",
4852
"deprecated": "2018-02-28T00:00:00+00:00",
4953
}
5054
]

tests/unit/isos/test_client.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ def test_bound_iso_init(self, iso_response):
1818
assert bound_iso.name == "FreeBSD-11.0-RELEASE-amd64-dvd1"
1919
assert bound_iso.description == "FreeBSD 11.0 x64"
2020
assert bound_iso.type == "public"
21+
assert bound_iso.architecture == "x86"
2122
assert bound_iso.deprecated == datetime.datetime(
2223
2018, 2, 28, 0, 0, tzinfo=tzoffset(None, 0)
2324
)

tests/unit/server_types/conftest.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ def server_type_response():
2626
],
2727
"storage_type": "local",
2828
"cpu_type": "shared",
29+
"architecture": "x86",
2930
}
3031
}
3132

@@ -56,6 +57,7 @@ def two_server_types_response():
5657
],
5758
"storage_type": "local",
5859
"cpu_type": "shared",
60+
"architecture": "x86",
5961
},
6062
{
6163
"id": 2,
@@ -90,6 +92,7 @@ def two_server_types_response():
9092
],
9193
"storage_type": "local",
9294
"cpu_type": "shared",
95+
"architecture": "x86",
9396
},
9497
]
9598
}
@@ -121,6 +124,7 @@ def one_server_types_response():
121124
],
122125
"storage_type": "local",
123126
"cpu_type": "shared",
127+
"architecture": "x86",
124128
}
125129
]
126130
}

0 commit comments

Comments
 (0)