From 11e2c31d01d618323beb9abb28ea5ac569dc0255 Mon Sep 17 00:00:00 2001 From: Otavio Jacobi Date: Thu, 29 Aug 2024 11:07:19 -0300 Subject: [PATCH 01/13] [BREAKING]: Update all API queries to use the v7 model Change-type: major --- DOCUMENTATION.md | 2 +- balena/__init__.py | 2 +- balena/settings.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md index bff65974..57c8b100 100644 --- a/DOCUMENTATION.md +++ b/DOCUMENTATION.md @@ -31,7 +31,7 @@ The Balena object can be configured with a dict of type Settings ```python balena = Balena({ "balena_host": "balena-cloud.com", - "api_version": "v6", + "api_version": "v7", "device_actions_endpoint_version": "v1", "data_directory": "/home/example/.balena", "image_cache_time": str(1 * 1000 * 60 * 60 * 24 * 7), # 1 week diff --git a/balena/__init__.py b/balena/__init__.py index 9b4e1fd4..8eac56e8 100644 --- a/balena/__init__.py +++ b/balena/__init__.py @@ -30,7 +30,7 @@ ```python balena = Balena({ "balena_host": "balena-cloud.com", - "api_version": "v6", + "api_version": "v7", "device_actions_endpoint_version": "v1", "data_directory": "/home/example/.balena", "image_cache_time": str(1 * 1000 * 60 * 60 * 24 * 7), # 1 week diff --git a/balena/settings.py b/balena/settings.py index ae2d8981..5259d7cc 100644 --- a/balena/settings.py +++ b/balena/settings.py @@ -49,7 +49,7 @@ def remove(self, key: str) -> bool: DEFAULT_SETTINGS = { # These are default config values "balena_host": "balena-cloud.com", - "api_version": "v6", + "api_version": "v7", "device_actions_endpoint_version": "v1", # cache time : 1 week in milliseconds "image_cache_time": str(1 * 1000 * 60 * 60 * 24 * 7), From a72fbe23edcca249324b5aa7243d5ac5dc325f34 Mon Sep 17 00:00:00 2001 From: Otavio Jacobi Date: Thu, 29 Aug 2024 11:08:50 -0300 Subject: [PATCH 02/13] v7 model: Drop the gateway_download resource Change-type: major --- balena/utils.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/balena/utils.py b/balena/utils.py index dd0dd833..a5355996 100644 --- a/balena/utils.py +++ b/balena/utils.py @@ -172,8 +172,7 @@ def get_current_service_details_pine_expand( def get_single_install_summary(raw_data: Any) -> Any: # TODO: Please compare me to node-sdk version """ - Builds summary data for an image install or gateway download - + Builds summary data for an image install """ image = raw_data["image"][0] @@ -207,11 +206,7 @@ def generate_current_service_details(raw_device: TypeDevice) -> TypeDeviceWithSe grouped_services[obj.pop("service_name", None)].append(obj) raw_device["current_services"] = dict(grouped_services) # type: ignore - raw_device["current_gateway_downloads"] = [ # type: ignore - get_single_install_summary(i) for i in raw_device.get("gateway_download", []) - ] raw_device.pop("image_install", None) # type: ignore - raw_device.pop("gateway_download", None) # type: ignore return raw_device # type: ignore From d990aa628eb8ed22faad2f418a075b0377a8057a Mon Sep 17 00:00:00 2001 From: Otavio Jacobi Date: Thu, 29 Aug 2024 11:11:01 -0300 Subject: [PATCH 03/13] v7 model: Drop the device state & status_sort_index sdk-only property typings Change-type: major --- DOCUMENTATION.md | 15 --------------- balena/types/models.py | 7 ------- 2 files changed, 22 deletions(-) diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md index 57c8b100..b98c77fc 100644 --- a/DOCUMENTATION.md +++ b/DOCUMENTATION.md @@ -4669,9 +4669,7 @@ The name must be a string; the optional doc argument can have any type. "os_version": str, "provisioning_progress": int, "provisioning_state": str, - "state": TypeDeviceState, "status": str, - "status_sort_index": int, "supervisor_version": str, "uuid": str, "vpn_address": str, @@ -4703,17 +4701,6 @@ The name must be a string; the optional doc argument can have any type. ``` -### TypeDeviceState - - -```python -{ - "key": str, - "name": str -} -``` - - ### TypeDeviceWithServices @@ -4748,9 +4735,7 @@ The name must be a string; the optional doc argument can have any type. "os_version": str, "provisioning_progress": int, "provisioning_state": str, - "state": TypeDeviceState, "status": str, - "status_sort_index": int, "supervisor_version": str, "uuid": str, "vpn_address": str, diff --git a/balena/types/models.py b/balena/types/models.py index e128436f..0127ca28 100644 --- a/balena/types/models.py +++ b/balena/types/models.py @@ -242,11 +242,6 @@ class DeviceTypeType(TypedDict): device_type_alias: ReverseNavigationResource["DeviceTypeAliasType"] -class TypeDeviceState(TypedDict): - key: str - name: str - - class ServiceInstanceType(TypedDict): id: int created_at: str @@ -308,9 +303,7 @@ class TypeDevice(TypedDict): os_version: str provisioning_progress: int provisioning_state: str - state: TypeDeviceState status: str - status_sort_index: int supervisor_version: str uuid: str vpn_address: str From 276f16c5e357f1a9a5c3771108ea0887fafea378 Mon Sep 17 00:00:00 2001 From: Otavio Jacobi Date: Thu, 29 Aug 2024 11:11:44 -0300 Subject: [PATCH 04/13] v7 model: Drop the device.vpn_address property Change-type: major --- DOCUMENTATION.md | 2 -- balena/types/models.py | 1 - 2 files changed, 3 deletions(-) diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md index b98c77fc..81daa9dd 100644 --- a/DOCUMENTATION.md +++ b/DOCUMENTATION.md @@ -4672,7 +4672,6 @@ The name must be a string; the optional doc argument can have any type. "status": str, "supervisor_version": str, "uuid": str, - "vpn_address": str, "api_heartbeat_state": Literal["online", "offline", "timeout", "unknown"], "memory_usage": int, "memory_total": int, @@ -4738,7 +4737,6 @@ The name must be a string; the optional doc argument can have any type. "status": str, "supervisor_version": str, "uuid": str, - "vpn_address": str, "api_heartbeat_state": Literal["online", "offline", "timeout", "unknown"], "memory_usage": int, "memory_total": int, diff --git a/balena/types/models.py b/balena/types/models.py index 0127ca28..cc2f8fbc 100644 --- a/balena/types/models.py +++ b/balena/types/models.py @@ -306,7 +306,6 @@ class TypeDevice(TypedDict): status: str supervisor_version: str uuid: str - vpn_address: str api_heartbeat_state: Literal["online", "offline", "timeout", "unknown"] memory_usage: int memory_total: int From b84ed6be0f7a62b94c3228e5c8ca4b5ecbc3d4b7 Mon Sep 17 00:00:00 2001 From: Otavio Jacobi Date: Thu, 29 Aug 2024 11:14:17 -0300 Subject: [PATCH 05/13] v7 model: Replace should_be_managed_by__supervisor_release with should_be_managed_by__release Change-type: major --- DOCUMENTATION.md | 6 ++++-- balena/models/device.py | 2 +- balena/types/models.py | 3 ++- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md index 81daa9dd..eb4b7178 100644 --- a/DOCUMENTATION.md +++ b/DOCUMENTATION.md @@ -4368,6 +4368,7 @@ The name must be a string; the optional doc argument can have any type. "should_be_running_on__application": Optional[List[TypeApplication]], "is_running_on__device": Optional[List[TypeDevice]], "should_be_running_on__device": Optional[List[TypeDevice]], + "should_manage__device": Optional[List[TypeDevice]], "release_tag": Optional[List[BaseTagType]] } ``` @@ -4428,6 +4429,7 @@ The name must be a string; the optional doc argument can have any type. "should_be_running_on__application": Optional[List[TypeApplication]], "is_running_on__device": Optional[List[TypeDevice]], "should_be_running_on__device": Optional[List[TypeDevice]], + "should_manage__device": Optional[List[TypeDevice]], "release_tag": Optional[List[BaseTagType]], "images": List[ImageBasicInfoType], "user": BasicUserInfoType @@ -4690,7 +4692,7 @@ The name must be a string; the optional doc argument can have any type. "is_running__release": Union[List[ReleaseType], PineDeferred, None], "should_be_running__release": Union[List[ReleaseType], PineDeferred, None], "is_managed_by__service_instance": Union[List[ServiceInstanceType], PineDeferred, None], - "should_be_managed_by__supervisor_release": Union[List[SupervisorReleaseType], PineDeferred, None], + "should_be_managed_by__release": Union[List[ReleaseType], PineDeferred, None], "device_config_variable": Optional[List[EnvironmentVariableBase]], "device_environment_variable": Optional[List[EnvironmentVariableBase]], "device_tag": Optional[List[BaseTagType]], @@ -4755,7 +4757,7 @@ The name must be a string; the optional doc argument can have any type. "is_running__release": Union[List[ReleaseType], PineDeferred, None], "should_be_running__release": Union[List[ReleaseType], PineDeferred, None], "is_managed_by__service_instance": Union[List[ServiceInstanceType], PineDeferred, None], - "should_be_managed_by__supervisor_release": Union[List[SupervisorReleaseType], PineDeferred, None], + "should_be_managed_by__release": Union[List[ReleaseType], PineDeferred, None], "device_config_variable": Optional[List[EnvironmentVariableBase]], "device_environment_variable": Optional[List[EnvironmentVariableBase]], "device_tag": Optional[List[BaseTagType]], diff --git a/balena/models/device.py b/balena/models/device.py index 5aa1c122..57d108bc 100644 --- a/balena/models/device.py +++ b/balena/models/device.py @@ -1740,7 +1740,7 @@ def set_supervisor_release( { "resource": "device", "id": device["id"], - "body": {"should_be_managed_by__supervisor_release": release["id"]}, + "body": {"should_be_managed_by__release": release["id"]}, } ) diff --git a/balena/types/models.py b/balena/types/models.py index cc2f8fbc..f83c4c94 100644 --- a/balena/types/models.py +++ b/balena/types/models.py @@ -327,7 +327,7 @@ class TypeDevice(TypedDict): is_running__release: OptionalNavigationResource["ReleaseType"] should_be_running__release: OptionalNavigationResource["ReleaseType"] is_managed_by__service_instance: OptionalNavigationResource[ServiceInstanceType] - should_be_managed_by__supervisor_release: OptionalNavigationResource[SupervisorReleaseType] + should_be_managed_by__release: OptionalNavigationResource["ReleaseType"] device_config_variable: ReverseNavigationResource["EnvironmentVariableBase"] device_environment_variable: ReverseNavigationResource["EnvironmentVariableBase"] device_tag: ReverseNavigationResource["BaseTagType"] @@ -501,6 +501,7 @@ class ReleaseType(TypedDict): should_be_running_on__application: ReverseNavigationResource[TypeApplication] is_running_on__device: ReverseNavigationResource[TypeDevice] should_be_running_on__device: ReverseNavigationResource[TypeDevice] + should_manage__device: ReverseNavigationResource[TypeDevice] release_tag: ReverseNavigationResource[BaseTagType] From 6aa19308550aefed0ccba65a963908c5cf1bf0f1 Mon Sep 17 00:00:00 2001 From: Otavio Jacobi Date: Thu, 29 Aug 2024 12:16:41 -0300 Subject: [PATCH 06/13] Add the os.get_supervisor_releases_for_cpu_architecture() method Change-type: minor --- DOCUMENTATION.md | 23 +++++- balena/models/device.py | 3 - balena/models/os.py | 86 ++++++++++++++++++++++- tests/functional/models/test_device_os.py | 42 +++++++++++ 4 files changed, 148 insertions(+), 6 deletions(-) diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md index eb4b7178..3c839ca5 100644 --- a/DOCUMENTATION.md +++ b/DOCUMENTATION.md @@ -281,6 +281,7 @@ hesitate to open an issue in GitHub](https://github.com/balena-io/balena-sdk-pyt - [get_config(slug_or_uuid_or_id, options)](#deviceos.get_config) ⇒ None - [get_download_size(device_type, version)](#deviceos.get_download_size) ⇒ float - [get_max_satisfying_version(device_type, version_or_range, os_type)](#deviceos.get_max_satisfying_version) ⇒ Optional[str] + - [get_supervisor_releases_for_cpu_architecture(cpu_architecture_slug_or_id, options)](#deviceos.get_supervisor_releases_for_cpu_architecture) ⇒ [List[ReleaseType]](#releasetype) - [get_supported_os_update_versions(device_type, current_version)](#deviceos.get_supported_os_update_versions) ⇒ None - [is_architecture_compatible_with(os_architecture, application_architecture)](#deviceos.is_architecture_compatible_with) ⇒ None - [is_supported_os_update(device_type, current_version, target_version)](#deviceos.is_supported_os_update) ⇒ bool @@ -1943,11 +1944,9 @@ Note a device. ### Function: set_supervisor_release(uuid_or_id, supervisor_version_or_id) ⇒ None Set a specific device to run a particular supervisor release. - #### Args: uuid_or_id (Union[str, int]): device uuid (string) or id (int) supervisor_version_or_id (Union[str, int]): the version of a released supervisor (string) or id (number) - #### Examples: ```python >>> balena.models.device.set_supervisor_release('f55dcdd9ad', 'v13.0.0') @@ -3135,6 +3134,26 @@ Get OS download size estimate. Currently only the raw (uncompressed) size is rep #### Returns: float: OS image download size, in bytes. + +### Function: get_supervisor_releases_for_cpu_architecture(cpu_architecture_slug_or_id, options) ⇒ [List[ReleaseType]](#releasetype) + +Returns the Releases of the supervisor for the CPU Architecture + +#### Args: + cpu_architecture_slug_or_id (Union[str, int]): The slug (string) or id (number) for the CPU Architecture. + options (AnyObject): extra pine options to use. + +#### Returns: + ReleaseType: release info. + + +Example: + results = balena.models.os.get_supervisor_releases_for_cpu_architecture('aarch64'); + results = balena.models.os.get_supervisor_releases_for_cpu_architecture( + 'aarch64', + { $filter: { raw_version: '12.11.0' } }, + ); + ### Function: get_supported_os_update_versions(device_type, current_version) ⇒ None diff --git a/balena/models/device.py b/balena/models/device.py index 57d108bc..d2476955 100644 --- a/balena/models/device.py +++ b/balena/models/device.py @@ -1689,11 +1689,9 @@ def set_supervisor_release( ) -> None: """ Set a specific device to run a particular supervisor release. - Args: uuid_or_id (Union[str, int]): device uuid (string) or id (int) supervisor_version_or_id (Union[str, int]): the version of a released supervisor (string) or id (number) - Examples: >>> balena.models.device.set_supervisor_release('f55dcdd9ad', 'v13.0.0') """ @@ -1735,7 +1733,6 @@ def set_supervisor_release( ensure_version_compatibility(device["supervisor_version"], MIN_SUPERVISOR_MC_API, "supervisor") ensure_version_compatibility(device["os_version"], MIN_OS_MC, "host OS") - self.__pine.patch( { "resource": "device", diff --git a/balena/models/os.py b/balena/models/os.py index 872e8376..20aefd5f 100644 --- a/balena/models/os.py +++ b/balena/models/os.py @@ -10,7 +10,8 @@ from ..hup import get_hup_action_type from ..pine import PineClient from ..types import AnyObject -from ..utils import compare, merge, normalize_balena_semver +from ..types.models import ReleaseType +from ..utils import compare, merge, normalize_balena_semver, is_id from ..settings import Settings from .application import Application from .device_type import DeviceType @@ -493,6 +494,89 @@ def is_architecture_compatible_with(self, os_architecture: str, application_arch return True + def get_supervisor_releases_for_cpu_architecture( + self, cpu_architecture_slug_or_id: Union[str, int], options: AnyObject = {} + ) -> List[ReleaseType]: + """ + Returns the Releases of the supervisor for the CPU Architecture + + Args: + cpu_architecture_slug_or_id (Union[str, int]): The slug (string) or id (number) for the CPU Architecture. + options (AnyObject): extra pine options to use. + + Returns: + ReleaseType: release info. + + + Example: + results = balena.models.os.get_supervisor_releases_for_cpu_architecture('aarch64'); + results = balena.models.os.get_supervisor_releases_for_cpu_architecture( + 'aarch64', + { $filter: { raw_version: '12.11.0' } }, + ); + """ + return self.__pine.get( + { + "resource": "release", + "options": merge( + { + "$select": ["id", "raw_version", "known_issue_list"], + "$filter": { + "status": "success", + "is_final": True, + "is_invalidated": False, + "semver_major": {"$gt": 0}, + "belongs_to__application": { + "$any": { + "$alias": "a", + "$expr": { + "$and": [ + {"a": {"slug": {"$startswith": "balena_os/"}}}, + {"a": {"slug": {"$endswith": "-supervisor"}}}, + ], + "a": { + "is_public": True, + "is_host": False, + "is_for__device_type": { + "$any": { + "$alias": "dt", + "$expr": { + "dt": { + "is_of__cpu_architecture": ( + cpu_architecture_slug_or_id + if is_id(cpu_architecture_slug_or_id) + else { + "$any": { + "$alias": "c", + "$expr": { + "c": { + "slug": cpu_architecture_slug_or_id, + }, + }, + }, + } + ), + }, + }, + }, + }, + }, + }, + }, + }, + }, + "$orderby": [ + {"semver_major": "desc"}, + {"semver_minor": "desc"}, + {"semver_patch": "desc"}, + {"revision": "desc"}, + ], + }, + options, + ), + } + ) + def __tags_to_dict(self, tags: List[Any]) -> Dict[str, str]: tag_map = {} diff --git a/tests/functional/models/test_device_os.py b/tests/functional/models/test_device_os.py index 30a53808..bd415041 100644 --- a/tests/functional/models/test_device_os.py +++ b/tests/functional/models/test_device_os.py @@ -38,6 +38,48 @@ def test_02_get_hup_action_type(self): for ver in testVersion: get_hup_action_type("", ver, ver) + def test_03_get_supervisor_releases_for_cpu_architecture(self): + # return an empty array if no image was found + svRelease = self.balena.models.os.get_supervisor_releases_for_cpu_architecture("notACpuArch") + self.assertEqual(svRelease, []) + + # by default include the id, semver and known_issue_list + dt = self.balena.models.device_type.get( + "raspberrypi4-64", {"$select": "slug", "$expand": {"is_of__cpu_architecture": {"$select": "slug"}}} + ) + + svReleases = self.balena.models.os.get_supervisor_releases_for_cpu_architecture( + dt["is_of__cpu_architecture"][0]["slug"] + ) + + self.assertGreater(len(svReleases), 0) + svRelease = svReleases[0] + self.assertListEqual(sorted(svRelease.keys()), sorted(["id", "raw_version", "known_issue_list"])) + + # return the right string when asking for raspberrypi4-64 and v12.11.0 + dt = self.balena.models.device_type.get( + "raspberrypi4-64", {"$select": "slug", "$expand": {"is_of__cpu_architecture": {"$select": "slug"}}} + ) + svReleases = self.balena.models.os.get_supervisor_releases_for_cpu_architecture( + dt["is_of__cpu_architecture"][0]["slug"], + { + "$select": "id", + "$expand": { + "release_image": { + "$select": "id", + "$expand": {"image": {"$select": "is_stored_at__image_location"}}, + }, + }, + "$filter": {"raw_version": "12.11.0"}, + }, + ) + + self.assertEqual(len(svReleases), 1) + svRelease = svReleases[0] + imageLocation = svRelease["release_image"][0]["image"][0]["is_stored_at__image_location"] + self.assertRegex(imageLocation, r"registry2\.[a-z0-9_\-.]+\.[a-z]+\/v2\/[0-9a-f]+") + self.assertEqual(imageLocation, "registry2.balena-cloud.com/v2/4ca706e1c624daff7e519b3009746b2c") + if __name__ == "__main__": unittest.main() From 1a978660a77ba581e3f8e706f6d2296e0577e1ae Mon Sep 17 00:00:00 2001 From: Otavio Jacobi Date: Mon, 9 Sep 2024 14:22:04 -0300 Subject: [PATCH 07/13] v7 model: Drop the supervisor_release resource Change-type: major --- DOCUMENTATION.md | 16 ---------------- balena/models/device.py | 24 ++++-------------------- balena/types/models.py | 11 ----------- 3 files changed, 4 insertions(+), 47 deletions(-) diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md index 3c839ca5..5ef64c79 100644 --- a/DOCUMENTATION.md +++ b/DOCUMENTATION.md @@ -4500,22 +4500,6 @@ The name must be a string; the optional doc argument can have any type. ``` -### SupervisorReleaseType - - -```python -{ - "created_at": str, - "id": int, - "supervisor_version": str, - "image_name": str, - "is_public": bool, - "note": Optional[str], - "is_for__device_type": Union[List[DeviceTypeType], PineDeferred] -} -``` - - ### TeamApplicationAccessType diff --git a/balena/models/device.py b/balena/models/device.py index d2476955..6585a1c2 100644 --- a/balena/models/device.py +++ b/balena/models/device.py @@ -1699,35 +1699,19 @@ def set_supervisor_release( uuid_or_id, { "$select": ["id", "supervisor_version", "os_version"], - "$expand": {"is_of__device_type": {"$select": "slug"}}, + "$expand": {"is_of__device_type": {"$select": "is_of__cpu_architecture"}}, }, ) - device_type_slug = device["is_of__device_type"][0]["slug"] + cpu_arch_id = device["is_of__device_type"][0]["is_of__cpu_architecture"]["__id"] release_options = { "$top": 1, "$select": "id", - "$filter": { - "is_for__device_type": { - "$any": { - "$alias": "dt", - "$expr": { - "dt": { - "slug": device_type_slug, - }, - }, - }, - }, - }, + "$filter": {"id" if is_id(supervisor_version_or_id) else "raw_version": supervisor_version_or_id}, } - if is_id(supervisor_version_or_id): - release_options["$filter"]["id"] = supervisor_version_or_id - else: - release_options["$filter"]["supervisor_version"] = supervisor_version_or_id - try: - release = self.__pine.get({"resource": "supervisor_release", "options": release_options})[0] + release = self.__device_os.get_supervisor_releases_for_cpu_architecture(cpu_arch_id, release_options)[0] except IndexError: raise Exception(f"Supervisor release not found {supervisor_version_or_id}") diff --git a/balena/types/models.py b/balena/types/models.py index f83c4c94..93cb6e52 100644 --- a/balena/types/models.py +++ b/balena/types/models.py @@ -250,17 +250,6 @@ class ServiceInstanceType(TypedDict): last_heartbeat: str -class SupervisorReleaseType(TypedDict): - created_at: str - id: int - supervisor_version: str - image_name: str - is_public: bool - note: Optional[str] - - is_for__device_type: NavigationResource[DeviceTypeType] - - class ImageInstallType(TypedDict): id: int download_progress: Optional[float] From eb9101be07c81bd273bb29ce3f35fe4001192c80 Mon Sep 17 00:00:00 2001 From: Otavio Jacobi Date: Mon, 9 Sep 2024 14:41:28 -0300 Subject: [PATCH 08/13] v7 model: Replace should_be_running__release with is_pinned_on__release Change-type: major --- DOCUMENTATION.md | 8 ++++---- balena/models/device.py | 6 +++--- balena/types/models.py | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md index 5ef64c79..5638bf5b 100644 --- a/DOCUMENTATION.md +++ b/DOCUMENTATION.md @@ -4386,7 +4386,7 @@ The name must be a string; the optional doc argument can have any type. "release_image": Optional[List[ReleaseImageType]], "should_be_running_on__application": Optional[List[TypeApplication]], "is_running_on__device": Optional[List[TypeDevice]], - "should_be_running_on__device": Optional[List[TypeDevice]], + "is_pinned_to__device": Optional[List[TypeDevice]], "should_manage__device": Optional[List[TypeDevice]], "release_tag": Optional[List[BaseTagType]] } @@ -4447,7 +4447,7 @@ The name must be a string; the optional doc argument can have any type. "release_image": Optional[List[ReleaseImageType]], "should_be_running_on__application": Optional[List[TypeApplication]], "is_running_on__device": Optional[List[TypeDevice]], - "should_be_running_on__device": Optional[List[TypeDevice]], + "is_pinned_to__device": Optional[List[TypeDevice]], "should_manage__device": Optional[List[TypeDevice]], "release_tag": Optional[List[BaseTagType]], "images": List[ImageBasicInfoType], @@ -4693,7 +4693,7 @@ The name must be a string; the optional doc argument can have any type. "belongs_to__application": Union[List[TypeApplication], PineDeferred], "belongs_to__user": Union[List[UserType], PineDeferred, None], "is_running__release": Union[List[ReleaseType], PineDeferred, None], - "should_be_running__release": Union[List[ReleaseType], PineDeferred, None], + "is_pinned_on__release": Union[List[ReleaseType], PineDeferred, None], "is_managed_by__service_instance": Union[List[ServiceInstanceType], PineDeferred, None], "should_be_managed_by__release": Union[List[ReleaseType], PineDeferred, None], "device_config_variable": Optional[List[EnvironmentVariableBase]], @@ -4758,7 +4758,7 @@ The name must be a string; the optional doc argument can have any type. "belongs_to__application": Union[List[TypeApplication], PineDeferred], "belongs_to__user": Union[List[UserType], PineDeferred, None], "is_running__release": Union[List[ReleaseType], PineDeferred, None], - "should_be_running__release": Union[List[ReleaseType], PineDeferred, None], + "is_pinned_on__release": Union[List[ReleaseType], PineDeferred, None], "is_managed_by__service_instance": Union[List[ServiceInstanceType], PineDeferred, None], "should_be_managed_by__release": Union[List[ReleaseType], PineDeferred, None], "device_config_variable": Optional[List[EnvironmentVariableBase]], diff --git a/balena/models/device.py b/balena/models/device.py index 6585a1c2..9bbfb563 100644 --- a/balena/models/device.py +++ b/balena/models/device.py @@ -1620,7 +1620,7 @@ def is_tracking_application_release(self, uuid_or_id: Union[str, int]) -> bool: bool: is tracking the current application release. """ - return not bool(self.get(uuid_or_id, {"$select": "should_be_running__release"})["should_be_running__release"]) + return not bool(self.get(uuid_or_id, {"$select": "is_pinned_on__release"})["is_pinned_on__release"]) # TODO: enable device batching def pin_to_release( @@ -1667,7 +1667,7 @@ def pin_to_release( { "resource": "device", "id": device["id"], - "body": {"should_be_running__release": release["id"]}, + "body": {"is_pinned_on__release": release["id"]}, } ) @@ -1679,7 +1679,7 @@ def track_application_release(self, uuid_or_id_or_ids: Union[str, int, List[int] uuid_or_id_or_ids (Union[str, int, List[int]]): device uuid (str) or id (int) or ids (List[int]) """ - self.__set(uuid_or_id_or_ids, {"should_be_running__release": None}) + self.__set(uuid_or_id_or_ids, {"is_pinned_on__release": None}) # TODO: enable device batching def set_supervisor_release( diff --git a/balena/types/models.py b/balena/types/models.py index 93cb6e52..14a83846 100644 --- a/balena/types/models.py +++ b/balena/types/models.py @@ -314,7 +314,7 @@ class TypeDevice(TypedDict): belongs_to__application: NavigationResource[TypeApplication] belongs_to__user: OptionalNavigationResource[UserType] is_running__release: OptionalNavigationResource["ReleaseType"] - should_be_running__release: OptionalNavigationResource["ReleaseType"] + is_pinned_on__release: OptionalNavigationResource["ReleaseType"] is_managed_by__service_instance: OptionalNavigationResource[ServiceInstanceType] should_be_managed_by__release: OptionalNavigationResource["ReleaseType"] device_config_variable: ReverseNavigationResource["EnvironmentVariableBase"] @@ -489,7 +489,7 @@ class ReleaseType(TypedDict): release_image: ReverseNavigationResource[ReleaseImageType] should_be_running_on__application: ReverseNavigationResource[TypeApplication] is_running_on__device: ReverseNavigationResource[TypeDevice] - should_be_running_on__device: ReverseNavigationResource[TypeDevice] + is_pinned_to__device: ReverseNavigationResource[TypeDevice] should_manage__device: ReverseNavigationResource[TypeDevice] release_tag: ReverseNavigationResource[BaseTagType] From 3571c84131594d1360246f967bab67377d120bd0 Mon Sep 17 00:00:00 2001 From: Otavio Jacobi Date: Fri, 30 Aug 2024 09:10:21 -0300 Subject: [PATCH 09/13] v7 model: Change user, application & device actor, to return a deferred property when selected Change-type: major --- DOCUMENTATION.md | 10 +++++----- balena/models/api_key.py | 8 ++++---- balena/types/models.py | 2 +- tests/functional/test_auth.py | 8 ++++---- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md index 5638bf5b..1510eaf6 100644 --- a/DOCUMENTATION.md +++ b/DOCUMENTATION.md @@ -4549,7 +4549,7 @@ The name must be a string; the optional doc argument can have any type. "id": int, "created_at": str, "app_name": str, - "actor": Union[List[ActorType], int], + "actor": Union[List[ActorType], PineDeferred], "slug": str, "uuid": str, "is_accessible_by_support_until__date": str, @@ -4591,7 +4591,7 @@ The name must be a string; the optional doc argument can have any type. "id": int, "created_at": str, "app_name": str, - "actor": Union[List[ActorType], int], + "actor": Union[List[ActorType], PineDeferred], "slug": str, "uuid": str, "is_accessible_by_support_until__date": str, @@ -4646,7 +4646,7 @@ The name must be a string; the optional doc argument can have any type. ```python { "id": int, - "actor": Union[List[ActorType], int], + "actor": Union[List[ActorType], PineDeferred], "created_at": str, "modified_at": str, "custom_latitude": str, @@ -4711,7 +4711,7 @@ The name must be a string; the optional doc argument can have any type. ```python { "id": int, - "actor": Union[List[ActorType], int], + "actor": Union[List[ActorType], PineDeferred], "created_at": str, "modified_at": str, "custom_latitude": str, @@ -4797,7 +4797,7 @@ The name must be a string; the optional doc argument can have any type. ```python { "id": int, - "actor": Union[List[ActorType], int], + "actor": Union[List[ActorType], PineDeferred], "created_at": str, "username": str, "organization_membership": Optional[List[OrganizationMembershipType]], diff --git a/balena/models/api_key.py b/balena/models/api_key.py index f352006b..2f9b684a 100644 --- a/balena/models/api_key.py +++ b/balena/models/api_key.py @@ -134,8 +134,8 @@ def get_provisioning_api_keys_by_application( >>> balena.models.api_key.get_provisioning_api_keys_by_application("myorg/myapp") """ - app = self.__application.get(slug_or_uuid_or_id, {"$select": "actor"}) - return self.get_all(merge({"$filter": {"is_of__actor": app.get("actor")}}, options)) + actor_id = self.__application.get(slug_or_uuid_or_id, {"$select": "actor"})["actor"]["__id"] + return self.get_all(merge({"$filter": {"is_of__actor": actor_id}}, options)) def get_device_api_keys_by_device(self, uuid_or_id: Union[str, int], options: AnyObject = {}) -> List[APIKeyType]: """ @@ -150,8 +150,8 @@ def get_device_api_keys_by_device(self, uuid_or_id: Union[str, int], options: An >>> balena.models.api_key.get_device_api_keys_by_device(1111386) """ - dev = self.__device.get(uuid_or_id, {"$select": "actor"}) - return self.get_all(merge({"$filter": {"is_of__actor": dev["actor"]}}, options)) + actor_id = self.__device.get(uuid_or_id, {"$select": "actor"})["actor"]["__id"] + return self.get_all(merge({"$filter": {"is_of__actor": actor_id}}, options)) def get_all_named_user_api_keys(self, options: AnyObject = {}) -> List[APIKeyType]: """ diff --git a/balena/types/models.py b/balena/types/models.py index 14a83846..9540fc60 100644 --- a/balena/types/models.py +++ b/balena/types/models.py @@ -9,7 +9,7 @@ class PineDeferred(TypedDict): NavigationResource = Union[List[__T], PineDeferred] ReverseNavigationResource = Union[List[__T], None] -ConceptTypeNavigationResource = Union[List[__T], int] +ConceptTypeNavigationResource = NavigationResource[__T] OptionalNavigationResource = Union[List[__T], PineDeferred, None] diff --git a/tests/functional/test_auth.py b/tests/functional/test_auth.py index 445583d2..e5abbc96 100644 --- a/tests/functional/test_auth.py +++ b/tests/functional/test_auth.py @@ -159,9 +159,9 @@ def test_17_should_login_with_device_key(self): self.assertEqual(whoami["actorType"], "device") self.assertEqual(whoami["actorTypeId"], self.app_info["device"]["id"]) self.assertEqual(whoami["uuid"], device_uuid) - self.assertEqual(whoami["id"], self.app_info["device"]["actor"]) + self.assertEqual(whoami["id"], self.app_info["device"]["actor"]["__id"]) - self.assertEqual(self.balena.auth.get_actor_id(), self.app_info["device"]["actor"]) + self.assertEqual(self.balena.auth.get_actor_id(), self.app_info["device"]["actor"]["__id"]) errMsg = "The authentication credentials in use are not of a user" with self.assertRaises(Exception) as cm: @@ -190,10 +190,10 @@ def test_18_should_login_with_app_key(self): self.assertEqual(whoami["actorType"], "application") self.assertEqual(whoami["actorTypeId"], app_id) - self.assertEqual(whoami["id"], self.app_info["app"]["actor"]) + self.assertEqual(whoami["id"], self.app_info["app"]["actor"]["__id"]) self.assertEqual(whoami["slug"], self.app_info["app"]["slug"]) - self.assertEqual(self.balena.auth.get_actor_id(), self.app_info["app"]["actor"]) + self.assertEqual(self.balena.auth.get_actor_id(), self.app_info["app"]["actor"]["__id"]) errMsg = "The authentication credentials in use are not of a user" with self.assertRaises(Exception) as cm: From 5d6540de3842429cdcb3b66569ed1d4b9bd10f2b Mon Sep 17 00:00:00 2001 From: Otavio Jacobi Date: Fri, 30 Aug 2024 09:27:55 -0300 Subject: [PATCH 10/13] v7 model: Replace device.overall_status offline & idle with disconnected, reduced-functionality & operational Change-type: major --- DOCUMENTATION.md | 4 ++-- balena/models/device.py | 13 ------------- balena/types/models.py | 11 ++++++++++- 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md index 1510eaf6..f95f4ca9 100644 --- a/DOCUMENTATION.md +++ b/DOCUMENTATION.md @@ -4687,7 +4687,7 @@ The name must be a string; the optional doc argument can have any type. "cpu_temp": int, "cpu_id": str, "is_undervolted": bool, - "overall_status": Any, + "overall_status": Literal["configuring", "inactive", "post-provisioning", "updating", "operational", "disconnected", "reduced-functionality"], "overall_progress": int, "is_of__device_type": Union[List[DeviceTypeType], PineDeferred], "belongs_to__application": Union[List[TypeApplication], PineDeferred], @@ -4752,7 +4752,7 @@ The name must be a string; the optional doc argument can have any type. "cpu_temp": int, "cpu_id": str, "is_undervolted": bool, - "overall_status": Any, + "overall_status": Literal["configuring", "inactive", "post-provisioning", "updating", "operational", "disconnected", "reduced-functionality"], "overall_progress": int, "is_of__device_type": Union[List[DeviceTypeType], PineDeferred], "belongs_to__application": Union[List[TypeApplication], PineDeferred], diff --git a/balena/models/device.py b/balena/models/device.py index 9bbfb563..c304a0d2 100644 --- a/balena/models/device.py +++ b/balena/models/device.py @@ -83,19 +83,6 @@ class SupervisorStateType(TypedDict): download_progress: str -class DeviceStatus: - """ - Balena device statuses. - """ - - IDLE = "Idle" - CONFIGURING = "Configuring" - UPDATING = "Updating" - OFFLINE = "Offline" - POST_PROVISIONING = "Post Provisioning" - INACTIVE = "Inactive" - - class Device: """ This class implements device model for balena python SDK. diff --git a/balena/types/models.py b/balena/types/models.py index 9540fc60..1a06590c 100644 --- a/balena/types/models.py +++ b/balena/types/models.py @@ -306,7 +306,16 @@ class TypeDevice(TypedDict): cpu_id: str is_undervolted: bool # This is a computed term - overall_status: Any + overall_status: Literal[ + "configuring", + "inactive", + "post-provisioning", + "updating", + "operational", + "disconnected", + "reduced-functionality", + ] + # This is a computed term overall_progress: int From db06d84c70e36cd2b68743362f59991245abf3d7 Mon Sep 17 00:00:00 2001 From: Otavio Jacobi Date: Fri, 30 Aug 2024 09:39:03 -0300 Subject: [PATCH 11/13] v7 model: Change image.image_size to str Change-type: major --- DOCUMENTATION.md | 2 +- balena/types/models.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md index f95f4ca9..beb3700b 100644 --- a/DOCUMENTATION.md +++ b/DOCUMENTATION.md @@ -4194,7 +4194,7 @@ The name must be a string; the optional doc argument can have any type. "start_timestamp": str, "end_timestamp": str, "push_timestamp": str, - "image_size": int, + "image_size": str, "dockerfile": str, "error_message": str, "is_a_build_of__service": Union[List[ServiceType], PineDeferred], diff --git a/balena/types/models.py b/balena/types/models.py index 1a06590c..0df4ccbf 100644 --- a/balena/types/models.py +++ b/balena/types/models.py @@ -432,7 +432,7 @@ class ImageType(TypedDict): start_timestamp: str end_timestamp: str push_timestamp: str - image_size: int + image_size: str dockerfile: str error_message: str is_a_build_of__service: NavigationResource["ServiceType"] From 45a652355ff3e4f2652de9ea282f391d40447eaa Mon Sep 17 00:00:00 2001 From: Otavio Jacobi Date: Fri, 30 Aug 2024 09:43:23 -0300 Subject: [PATCH 12/13] Drop the application.get_app_by_owner method Change-type: major --- DOCUMENTATION.md | 19 ------------ balena/models/application.py | 32 --------------------- tests/functional/models/test_application.py | 15 ---------- 3 files changed, 66 deletions(-) diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md index beb3700b..fe34a991 100644 --- a/DOCUMENTATION.md +++ b/DOCUMENTATION.md @@ -91,7 +91,6 @@ hesitate to open an issue in GitHub](https://github.com/balena-io/balena-sdk-pyt - [get_all_by_organization(org_handle_or_id, options)](#application.get_all_by_organization) ⇒ [List[TypeApplication]](#typeapplication) - [get_all_directly_accessible(options)](#application.get_all_directly_accessible) ⇒ [List[TypeApplication]](#typeapplication) - [get_by_name(app_name, options, context)](#application.get_by_name) ⇒ [TypeApplication](#typeapplication) - - [get_by_owner(app_name, owner, options)](#application.get_by_owner) ⇒ [TypeApplication](#typeapplication) - [get_dashboard_url(app_id)](#application.get_dashboard_url) ⇒ str - [get_directly_accessible(slug_or_uuid_or_id, options)](#application.get_directly_accessible) ⇒ [TypeApplication](#typeapplication) - [get_id(slug_or_uuid_or_id)](#application.get_id) ⇒ int @@ -503,24 +502,6 @@ Get all applications directly accessible by the user >>> balena.models.application.get("myapp") ``` - -### Function: get_by_owner(app_name, owner, options) ⇒ [TypeApplication](#typeapplication) - -Get a single application using the appname and the handle of the owning organization. - -#### Args: - app_name (str): application name. - owner (str): The handle of the owning organization. - options (AnyObject): extra pine options to use. - -#### Returns: - TypeApplication: application info. - -#### Examples: -```python ->>> balena.models.application.get_by_owner('foo', 'my_org') -``` - ### Function: get_dashboard_url(app_id) ⇒ str diff --git a/balena/models/application.py b/balena/models/application.py index 53c92935..729bdf23 100644 --- a/balena/models/application.py +++ b/balena/models/application.py @@ -2,7 +2,6 @@ from math import isinf from typing import List, Literal, Optional, Union, cast from urllib.parse import urljoin -from deprecated import deprecated from .. import exceptions from ..balena_auth import request @@ -403,37 +402,6 @@ def get_all_by_organization( } ) - @deprecated("get_by_owner will be removed in a future release, use get_all_by_organization instead") - def get_by_owner(self, app_name: str, owner: str, options: AnyObject = {}) -> TypeApplication: - """ - Get a single application using the appname and the handle of the owning organization. - - Args: - app_name (str): application name. - owner (str): The handle of the owning organization. - options (AnyObject): extra pine options to use. - - Returns: - TypeApplication: application info. - - Examples: - >>> balena.models.application.get_by_owner('foo', 'my_org') - """ - - slug = f"{owner.lower()}/{app_name.lower()}" - app = self.__pine.get( - { - "resource": "application", - "id": {"slug": slug}, - "options": options, - } - ) - - if app is None: - raise exceptions.ApplicationNotFound(slug) - - return app - def has(self, slug_or_uuid_or_id: Union[str, int]) -> bool: """ Check if an application exists. diff --git a/tests/functional/models/test_application.py b/tests/functional/models/test_application.py index cb9c65ac..23861c5b 100644 --- a/tests/functional/models/test_application.py +++ b/tests/functional/models/test_application.py @@ -72,21 +72,6 @@ def test_06_get_all_by_organization(self): self.balena.models.application.get_all_by_organization(self.org_id)[0]["app_name"], "FooBar" ) - def test_06_get_by_owner(self): - with self.assertRaises(self.helper.balena_exceptions.ApplicationNotFound): - self.balena.models.application.get_by_owner("AppNotExist", self.helper.credentials["user_id"]) - - self.assertEqual( - self.balena.models.application.get_by_owner("FooBar", self.helper.default_organization["handle"])[ - "app_name" - ], - "FooBar", - ) - - with self.assertRaises(Exception) as cm: - self.balena.models.application.get_by_owner("FooBar", "random_username") - self.assertIn("Application not found: random_username/foobar", cm.exception.message) # type: ignore - def test_07_has(self): self.assertFalse(self.balena.models.application.has("AppNotExist")) self.assertTrue(self.balena.models.application.has(self.app_slug)) From 1328499d9aa31fe2e0c456b7a1e35d951ccec5dd Mon Sep 17 00:00:00 2001 From: Otavio Jacobi Date: Mon, 9 Sep 2024 14:49:33 -0300 Subject: [PATCH 13/13] v7 model: Add the device.should_be_operated_by__release property Change-type: major --- DOCUMENTATION.md | 4 ++++ balena/types/models.py | 2 ++ 2 files changed, 6 insertions(+) diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md index fe34a991..fe2ee97e 100644 --- a/DOCUMENTATION.md +++ b/DOCUMENTATION.md @@ -4368,6 +4368,7 @@ The name must be a string; the optional doc argument can have any type. "should_be_running_on__application": Optional[List[TypeApplication]], "is_running_on__device": Optional[List[TypeDevice]], "is_pinned_to__device": Optional[List[TypeDevice]], + "should_operate__device": Optional[List[TypeDevice]], "should_manage__device": Optional[List[TypeDevice]], "release_tag": Optional[List[BaseTagType]] } @@ -4429,6 +4430,7 @@ The name must be a string; the optional doc argument can have any type. "should_be_running_on__application": Optional[List[TypeApplication]], "is_running_on__device": Optional[List[TypeDevice]], "is_pinned_to__device": Optional[List[TypeDevice]], + "should_operate__device": Optional[List[TypeDevice]], "should_manage__device": Optional[List[TypeDevice]], "release_tag": Optional[List[BaseTagType]], "images": List[ImageBasicInfoType], @@ -4676,6 +4678,7 @@ The name must be a string; the optional doc argument can have any type. "is_running__release": Union[List[ReleaseType], PineDeferred, None], "is_pinned_on__release": Union[List[ReleaseType], PineDeferred, None], "is_managed_by__service_instance": Union[List[ServiceInstanceType], PineDeferred, None], + "should_be_operated_by__release": Union[List[ReleaseType], PineDeferred, None], "should_be_managed_by__release": Union[List[ReleaseType], PineDeferred, None], "device_config_variable": Optional[List[EnvironmentVariableBase]], "device_environment_variable": Optional[List[EnvironmentVariableBase]], @@ -4741,6 +4744,7 @@ The name must be a string; the optional doc argument can have any type. "is_running__release": Union[List[ReleaseType], PineDeferred, None], "is_pinned_on__release": Union[List[ReleaseType], PineDeferred, None], "is_managed_by__service_instance": Union[List[ServiceInstanceType], PineDeferred, None], + "should_be_operated_by__release": Union[List[ReleaseType], PineDeferred, None], "should_be_managed_by__release": Union[List[ReleaseType], PineDeferred, None], "device_config_variable": Optional[List[EnvironmentVariableBase]], "device_environment_variable": Optional[List[EnvironmentVariableBase]], diff --git a/balena/types/models.py b/balena/types/models.py index 0df4ccbf..d7488062 100644 --- a/balena/types/models.py +++ b/balena/types/models.py @@ -325,6 +325,7 @@ class TypeDevice(TypedDict): is_running__release: OptionalNavigationResource["ReleaseType"] is_pinned_on__release: OptionalNavigationResource["ReleaseType"] is_managed_by__service_instance: OptionalNavigationResource[ServiceInstanceType] + should_be_operated_by__release: OptionalNavigationResource["ReleaseType"] should_be_managed_by__release: OptionalNavigationResource["ReleaseType"] device_config_variable: ReverseNavigationResource["EnvironmentVariableBase"] device_environment_variable: ReverseNavigationResource["EnvironmentVariableBase"] @@ -499,6 +500,7 @@ class ReleaseType(TypedDict): should_be_running_on__application: ReverseNavigationResource[TypeApplication] is_running_on__device: ReverseNavigationResource[TypeDevice] is_pinned_to__device: ReverseNavigationResource[TypeDevice] + should_operate__device: ReverseNavigationResource[TypeDevice] should_manage__device: ReverseNavigationResource[TypeDevice] release_tag: ReverseNavigationResource[BaseTagType]