Skip to content

Commit

Permalink
feat: add support for ARM APIs (#182)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
apricote authored Apr 12, 2023
1 parent be38344 commit 9d5afe0
Show file tree
Hide file tree
Showing 12 changed files with 129 additions and 8 deletions.
2 changes: 1 addition & 1 deletion hcloud/__version__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
VERSION = "1.18.2"
VERSION = "1.19.0"
28 changes: 27 additions & 1 deletion hcloud/images/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ def get_list(
label_selector=None, # type: Optional[str]
bound_to=None, # type: Optional[List[str]]
type=None, # type: Optional[List[str]]
architecture=None, # type: Optional[List[str]]
sort=None, # type: Optional[List[str]]
page=None, # type: Optional[int]
per_page=None, # type: Optional[int]
Expand All @@ -186,6 +187,8 @@ def get_list(
Server Id linked to the image. Only available for images of type backup
:param type: List[str] (optional)
Choices: system snapshot backup
:param architecture: List[str] (optional)
Choices: x86 arm
:param status: List[str] (optional)
Can be used to filter images by their status. The response will only contain images matching the status.
:param sort: List[str] (optional)
Expand All @@ -207,6 +210,8 @@ def get_list(
params["bound_to"] = bound_to
if type is not None:
params["type"] = type
if architecture is not None:
params["architecture"] = architecture
if sort is not None:
params["sort"] = sort
if page is not None:
Expand All @@ -228,6 +233,7 @@ def get_all(
label_selector=None, # type: Optional[str]
bound_to=None, # type: Optional[List[str]]
type=None, # type: Optional[List[str]]
architecture=None, # type: Optional[List[str]]
sort=None, # type: Optional[List[str]]
status=None, # type: Optional[List[str]]
include_deprecated=None, # type: Optional[bool]
Expand All @@ -243,6 +249,8 @@ def get_all(
Server Id linked to the image. Only available for images of type backup
:param type: List[str] (optional)
Choices: system snapshot backup
:param architecture: List[str] (optional)
Choices: x86 arm
:param status: List[str] (optional)
Can be used to filter images by their status. The response will only contain images matching the status.
:param sort: List[str] (optional)
Expand All @@ -256,6 +264,7 @@ def get_all(
label_selector=label_selector,
bound_to=bound_to,
type=type,
architecture=architecture,
sort=sort,
status=status,
include_deprecated=include_deprecated,
Expand All @@ -265,12 +274,29 @@ def get_by_name(self, name):
# type: (str) -> BoundImage
"""Get image by name
Deprecated: Use get_by_name_and_architecture instead.
:param name: str
Used to get image by name.
:return: :class:`BoundImage <hcloud.images.client.BoundImage>`
"""
return super(ImagesClient, self).get_by_name(name)

def get_by_name_and_architecture(self, name, architecture):
# type: (str, str) -> BoundImage
"""Get image by name
:param name: str
Used to identify the image.
:param architecture: str
Used to identify the image.
:return: :class:`BoundImage <hcloud.images.client.BoundImage>`
"""
response = self.get_list(name=name, architecture=[architecture])
entities = getattr(response, self.results_list_attribute_name)
entity = entities[0] if entities else None
return entity

def update(self, image, description=None, type=None, labels=None):
# type:(Image, Optional[str], Optional[str], Optional[Dict[str, str]]) -> BoundImage
"""Updates the Image. You may change the description, convert a Backup image to a Snapshot Image or change the image labels.
Expand Down Expand Up @@ -311,7 +337,7 @@ def delete(self, image):
return True

def change_protection(self, image, delete=None):
# type: (Image, Optional[bool], Optional[bool]) -> BoundAction
# type: (Image, Optional[bool]) -> BoundAction
"""Changes the protection configuration of the image. Can only be used on snapshots.
:param image: :class:`BoundImage <hcloud.images.client.BoundImage>` or :class:`Image <hcloud.images.domain.Image>`
Expand Down
5 changes: 5 additions & 0 deletions hcloud/images/domain.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ class Image(BaseDomain, DomainIdentityMixin):
Flavor of operating system contained in the image Choices: `ubuntu`, `centos`, `debian`, `fedora`, `unknown`
:param os_version: str, None
Operating system version
:param architecture: str
CPU Architecture that the image is compatible with. Choices: `x86`, `arm`
:param rapid_deploy: bool
Indicates that rapid deploy of the image is available
:param protection: dict
Expand All @@ -50,6 +52,7 @@ class Image(BaseDomain, DomainIdentityMixin):
"bound_to",
"os_flavor",
"os_version",
"architecture",
"rapid_deploy",
"created_from",
"status",
Expand All @@ -72,6 +75,7 @@ def __init__(
bound_to=None,
os_flavor=None,
os_version=None,
architecture=None,
rapid_deploy=None,
created_from=None,
protection=None,
Expand All @@ -89,6 +93,7 @@ def __init__(
self.bound_to = bound_to
self.os_flavor = os_flavor
self.os_version = os_version
self.architecture = architecture
self.rapid_deploy = rapid_deploy
self.created_from = created_from
self.protection = protection
Expand Down
31 changes: 28 additions & 3 deletions hcloud/isos/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ def get_by_id(self, id):
def get_list(
self,
name=None, # type: Optional[str]
architecture=None, # type: Optional[List[str]]
include_wildcard_architecture=None, # type: Optional[bool]
page=None, # type: Optional[int]
per_page=None, # type: Optional[int]
):
Expand All @@ -33,6 +35,11 @@ def get_list(
:param name: str (optional)
Can be used to filter ISOs by their name.
:param architecture: List[str] (optional)
Can be used to filter ISOs by their architecture. Choices: x86 arm
:param include_wildcard_architecture: bool (optional)
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.
:param page: int (optional)
Specifies the page to fetch
:param per_page: int (optional)
Expand All @@ -42,6 +49,10 @@ def get_list(
params = {}
if name is not None:
params["name"] = name
if architecture is not None:
params["architecture"] = architecture
if include_wildcard_architecture is not None:
params["include_wildcard_architecture"] = include_wildcard_architecture
if page is not None:
params["page"] = page
if per_page is not None:
Expand All @@ -51,15 +62,29 @@ def get_list(
isos = [BoundIso(self, iso_data) for iso_data in response["isos"]]
return self._add_meta_to_result(isos, response)

def get_all(self, name=None):
# type: (Optional[str]) -> List[BoundIso]
def get_all(
self,
name=None, # type: Optional[str]
architecture=None, # type: Optional[List[str]]
include_wildcard_architecture=None, # type: Optional[bool]
):
# type: (...) -> List[BoundIso]
"""Get all ISOs
:param name: str (optional)
Can be used to filter ISOs by their name.
:param architecture: List[str] (optional)
Can be used to filter ISOs by their architecture. Choices: x86 arm
:param include_wildcard_architecture: bool (optional)
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.
:return: List[:class:`BoundIso <hcloud.isos.client.BoundIso>`]
"""
return super(IsosClient, self).get_all(name=name)
return super(IsosClient, self).get_all(
name=name,
architecture=architecture,
include_wildcard_architecture=include_wildcard_architecture,
)

def get_by_name(self, name):
# type: (str) -> BoundIso
Expand Down
13 changes: 11 additions & 2 deletions hcloud/isos/domain.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,26 @@ class Iso(BaseDomain, DomainIdentityMixin):
Description of the ISO
:param type: str
Type of the ISO. Choices: `public`, `private`
:param architecture: str, None
CPU Architecture that the ISO is compatible with. None means that the compatibility is unknown. Choices: `x86`, `arm`
:param deprecated: datetime, None
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.
"""

__slots__ = ("id", "name", "type", "description", "deprecated")
__slots__ = ("id", "name", "type", "architecture", "description", "deprecated")

def __init__(
self, id=None, name=None, type=None, description=None, deprecated=None
self,
id=None,
name=None,
type=None,
architecture=None,
description=None,
deprecated=None,
):
self.id = id
self.name = name
self.type = type
self.architecture = architecture
self.description = description
self.deprecated = isoparse(deprecated) if deprecated else None
5 changes: 5 additions & 0 deletions hcloud/server_types/domain.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ class ServerType(BaseDomain, DomainIdentityMixin):
Type of server boot drive. Local has higher speed. Network has better availability. Choices: `local`, `network`
:param cpu_type: string
Type of cpu. Choices: `shared`, `dedicated`
:param architecture: string
Architecture of cpu. Choices: `x86`, `arm`
:param deprecated: bool
True if server type is deprecated
"""
Expand All @@ -36,6 +38,7 @@ class ServerType(BaseDomain, DomainIdentityMixin):
"prices",
"storage_type",
"cpu_type",
"architecture",
"deprecated",
)

Expand All @@ -50,6 +53,7 @@ def __init__(
prices=None,
storage_type=None,
cpu_type=None,
architecture=None,
deprecated=None,
):
self.id = id
Expand All @@ -61,4 +65,5 @@ def __init__(
self.prices = prices
self.storage_type = storage_type
self.cpu_type = cpu_type
self.architecture = architecture
self.deprecated = deprecated
5 changes: 5 additions & 0 deletions tests/unit/images/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ def image_response():
"bound_to": 1,
"os_flavor": "ubuntu",
"os_version": "16.04",
"architecture": "x86",
"rapid_deploy": False,
"protection": {"delete": False},
"deprecated": "2018-02-28T00:00:00+00:00",
Expand All @@ -42,6 +43,7 @@ def two_images_response():
"bound_to": None,
"os_flavor": "ubuntu",
"os_version": "16.04",
"architecture": "x86",
"rapid_deploy": False,
"protection": {"delete": False},
"deprecated": "2018-02-28T00:00:00+00:00",
Expand All @@ -60,6 +62,7 @@ def two_images_response():
"bound_to": None,
"os_flavor": "ubuntu",
"os_version": "16.04",
"architecture": "x86",
"rapid_deploy": False,
"protection": {"delete": False},
"deprecated": "2018-02-28T00:00:00+00:00",
Expand All @@ -86,6 +89,7 @@ def one_images_response():
"bound_to": None,
"os_flavor": "ubuntu",
"os_version": "16.04",
"architecture": "x86",
"rapid_deploy": False,
"protection": {"delete": False},
"deprecated": "2018-02-28T00:00:00+00:00",
Expand All @@ -111,6 +115,7 @@ def response_update_image():
"bound_to": None,
"os_flavor": "ubuntu",
"os_version": "16.04",
"architecture": "arm",
"rapid_deploy": False,
"protection": {"delete": False},
"deprecated": "2018-02-28T00:00:00+00:00",
Expand Down
16 changes: 16 additions & 0 deletions tests/unit/images/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ def test_bound_image_init(self, image_response):
)
assert bound_image.os_flavor == "ubuntu"
assert bound_image.os_version == "16.04"
assert bound_image.architecture == "x86"
assert bound_image.rapid_deploy is False
assert bound_image.deprecated == datetime.datetime(
2018, 2, 28, 0, 0, tzinfo=tzoffset(None, 0)
Expand Down Expand Up @@ -223,6 +224,21 @@ def test_get_by_name(self, images_client, one_images_response):
assert image.id == 4711
assert image.name == "ubuntu-20.04"

def test_get_by_name_and_architecture(self, images_client, one_images_response):
images_client._client.request.return_value = one_images_response
image = images_client.get_by_name_and_architecture("ubuntu-20.04", "x86")

params = {"name": "ubuntu-20.04", "architecture": ["x86"]}

images_client._client.request.assert_called_with(
url="/images", method="GET", params=params
)

assert image._client is images_client
assert image.id == 4711
assert image.name == "ubuntu-20.04"
assert image.architecture == "x86"

@pytest.mark.parametrize(
"image", [Image(id=1), BoundImage(mock.MagicMock(), dict(id=1))]
)
Expand Down
4 changes: 4 additions & 0 deletions tests/unit/isos/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ def iso_response():
"name": "FreeBSD-11.0-RELEASE-amd64-dvd1",
"description": "FreeBSD 11.0 x64",
"type": "public",
"architecture": "x86",
"deprecated": "2018-02-28T00:00:00+00:00",
}
}
Expand All @@ -23,13 +24,15 @@ def two_isos_response():
"name": "FreeBSD-11.0-RELEASE-amd64-dvd1",
"description": "FreeBSD 11.0 x64",
"type": "public",
"architecture": "x86",
"deprecated": "2018-02-28T00:00:00+00:00",
},
{
"id": 4712,
"name": "FreeBSD-11.0-RELEASE-amd64-dvd1",
"description": "FreeBSD 11.0 x64",
"type": "public",
"architecture": "x86",
"deprecated": "2018-02-28T00:00:00+00:00",
},
]
Expand All @@ -45,6 +48,7 @@ def one_isos_response():
"name": "FreeBSD-11.0-RELEASE-amd64-dvd1",
"description": "FreeBSD 11.0 x64",
"type": "public",
"architecture": "x86",
"deprecated": "2018-02-28T00:00:00+00:00",
}
]
Expand Down
1 change: 1 addition & 0 deletions tests/unit/isos/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ def test_bound_iso_init(self, iso_response):
assert bound_iso.name == "FreeBSD-11.0-RELEASE-amd64-dvd1"
assert bound_iso.description == "FreeBSD 11.0 x64"
assert bound_iso.type == "public"
assert bound_iso.architecture == "x86"
assert bound_iso.deprecated == datetime.datetime(
2018, 2, 28, 0, 0, tzinfo=tzoffset(None, 0)
)
Expand Down
4 changes: 4 additions & 0 deletions tests/unit/server_types/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ def server_type_response():
],
"storage_type": "local",
"cpu_type": "shared",
"architecture": "x86",
}
}

Expand Down Expand Up @@ -56,6 +57,7 @@ def two_server_types_response():
],
"storage_type": "local",
"cpu_type": "shared",
"architecture": "x86",
},
{
"id": 2,
Expand Down Expand Up @@ -90,6 +92,7 @@ def two_server_types_response():
],
"storage_type": "local",
"cpu_type": "shared",
"architecture": "x86",
},
]
}
Expand Down Expand Up @@ -121,6 +124,7 @@ def one_server_types_response():
],
"storage_type": "local",
"cpu_type": "shared",
"architecture": "x86",
}
]
}
Loading

0 comments on commit 9d5afe0

Please sign in to comment.