diff --git a/hcloud/__version__.py b/hcloud/__version__.py index cca3a688..ccdbf7c1 100644 --- a/hcloud/__version__.py +++ b/hcloud/__version__.py @@ -1 +1 @@ -VERSION = "1.18.2" +VERSION = "1.19.0" diff --git a/hcloud/images/client.py b/hcloud/images/client.py index 2a2345c7..c5a65dfd 100644 --- a/hcloud/images/client.py +++ b/hcloud/images/client.py @@ -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] @@ -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) @@ -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: @@ -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] @@ -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) @@ -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, @@ -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 ` """ 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 ` + """ + 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. @@ -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 ` or :class:`Image ` diff --git a/hcloud/images/domain.py b/hcloud/images/domain.py index 4a2c2b05..6e387b6a 100644 --- a/hcloud/images/domain.py +++ b/hcloud/images/domain.py @@ -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 @@ -50,6 +52,7 @@ class Image(BaseDomain, DomainIdentityMixin): "bound_to", "os_flavor", "os_version", + "architecture", "rapid_deploy", "created_from", "status", @@ -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, @@ -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 diff --git a/hcloud/isos/client.py b/hcloud/isos/client.py index a59b80b9..c28b632a 100644 --- a/hcloud/isos/client.py +++ b/hcloud/isos/client.py @@ -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] ): @@ -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) @@ -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: @@ -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 `] """ - 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 diff --git a/hcloud/isos/domain.py b/hcloud/isos/domain.py index e85b30dc..906b42d5 100644 --- a/hcloud/isos/domain.py +++ b/hcloud/isos/domain.py @@ -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 diff --git a/hcloud/server_types/domain.py b/hcloud/server_types/domain.py index 7f03ee47..0b0891e4 100644 --- a/hcloud/server_types/domain.py +++ b/hcloud/server_types/domain.py @@ -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 """ @@ -36,6 +38,7 @@ class ServerType(BaseDomain, DomainIdentityMixin): "prices", "storage_type", "cpu_type", + "architecture", "deprecated", ) @@ -50,6 +53,7 @@ def __init__( prices=None, storage_type=None, cpu_type=None, + architecture=None, deprecated=None, ): self.id = id @@ -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 diff --git a/tests/unit/images/conftest.py b/tests/unit/images/conftest.py index a6156e9a..7ba4dbc1 100644 --- a/tests/unit/images/conftest.py +++ b/tests/unit/images/conftest.py @@ -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", @@ -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", @@ -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", @@ -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", @@ -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", diff --git a/tests/unit/images/test_client.py b/tests/unit/images/test_client.py index 55ec9ba4..bba1416c 100644 --- a/tests/unit/images/test_client.py +++ b/tests/unit/images/test_client.py @@ -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) @@ -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))] ) diff --git a/tests/unit/isos/conftest.py b/tests/unit/isos/conftest.py index 4c27b019..76f20738 100644 --- a/tests/unit/isos/conftest.py +++ b/tests/unit/isos/conftest.py @@ -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", } } @@ -23,6 +24,7 @@ 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", }, { @@ -30,6 +32,7 @@ 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", }, ] @@ -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", } ] diff --git a/tests/unit/isos/test_client.py b/tests/unit/isos/test_client.py index 4bb4d37d..34f052e2 100644 --- a/tests/unit/isos/test_client.py +++ b/tests/unit/isos/test_client.py @@ -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) ) diff --git a/tests/unit/server_types/conftest.py b/tests/unit/server_types/conftest.py index dd2fbb2d..0588252a 100644 --- a/tests/unit/server_types/conftest.py +++ b/tests/unit/server_types/conftest.py @@ -26,6 +26,7 @@ def server_type_response(): ], "storage_type": "local", "cpu_type": "shared", + "architecture": "x86", } } @@ -56,6 +57,7 @@ def two_server_types_response(): ], "storage_type": "local", "cpu_type": "shared", + "architecture": "x86", }, { "id": 2, @@ -90,6 +92,7 @@ def two_server_types_response(): ], "storage_type": "local", "cpu_type": "shared", + "architecture": "x86", }, ] } @@ -121,6 +124,7 @@ def one_server_types_response(): ], "storage_type": "local", "cpu_type": "shared", + "architecture": "x86", } ] } diff --git a/tests/unit/server_types/test_client.py b/tests/unit/server_types/test_client.py index 10c07c2b..6ddaba17 100644 --- a/tests/unit/server_types/test_client.py +++ b/tests/unit/server_types/test_client.py @@ -2,7 +2,28 @@ from unittest import mock -from hcloud.server_types.client import ServerTypesClient +from hcloud.server_types.client import ServerTypesClient, BoundServerType + + +class TestBoundIso(object): + @pytest.fixture() + def bound_server_type(self, hetzner_client): + return BoundServerType(client=hetzner_client.server_types, data=dict(id=14)) + + def test_bound_server_type_init(self, server_type_response): + bound_server_type = BoundServerType( + client=mock.MagicMock(), data=server_type_response["server_type"] + ) + + assert bound_server_type.id == 1 + assert bound_server_type.name == "cx11" + assert bound_server_type.description == "CX11" + assert bound_server_type.cores == 1 + assert bound_server_type.memory == 1 + assert bound_server_type.disk == 25 + assert bound_server_type.storage_type == "local" + assert bound_server_type.cpu_type == "shared" + assert bound_server_type.architecture == "x86" class TestServerTypesClient(object):