From 76c8e81788776f8ee341d6626cb2ecda30742961 Mon Sep 17 00:00:00 2001 From: Paymaun Date: Mon, 13 Mar 2023 13:23:25 -0700 Subject: [PATCH] Set of testing tweaks & min CLI increment (#643) - assign_role_assignment() will take a set of service principal KPIs into account. Useful when user principal names or service principal names are used for role assignment. - This operation will no longer return anything and works on a best-attempt basis. - Fixes an unhandled exception case if a role assignment already exists and `output` is still None. - Raise minCLIVersion to 2.37.0 to have consistent MS graph experience. - Refactors how an assertion works for ADU deployment creation, investigating change in existing model. - Revert az dt rbac test change. --- .../templates/set-testenv-sentinel.yml | 1 + HISTORY.rst | 16 ++++- azext_iot/azext_metadata.json | 2 +- azext_iot/constants.py | 2 +- azext_iot/tests/deviceupdate/conftest.py | 2 +- .../tests/deviceupdate/test_adu_update_int.py | 9 ++- .../test_dt_resource_lifecycle_int.py | 64 +++++++++++-------- azext_iot/tests/helpers.py | 24 ++++--- pytest.ini.example | 1 + 9 files changed, 80 insertions(+), 41 deletions(-) diff --git a/.azure-devops/templates/set-testenv-sentinel.yml b/.azure-devops/templates/set-testenv-sentinel.yml index e1c9d9056..74a70a827 100644 --- a/.azure-devops/templates/set-testenv-sentinel.yml +++ b/.azure-devops/templates/set-testenv-sentinel.yml @@ -52,6 +52,7 @@ steps: f.writelines(envvars_sentinel) f.write("markers =\n") f.write(" adu_infrastructure:\n") + f.write(" hub_infrastructure:\n") f.close() env: diff --git a/HISTORY.rst b/HISTORY.rst index 6c14724f7..a4e3d4ace 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,13 +3,25 @@ Release History =============== + unreleased +++++++++++++++ + +0.21.0 ++++++++++++++++ + +**General updates** + +* The Azure IoT CLI extension min core CLI version incremented to `2.37.0`. + **Digital Twins updates** -* Addition of new parameter `--max-models-per-batch` for `az dt model create` to let user adjust batch size when directory exceeds - 250 models. The parameter will be removed once the DTDLParserError is fixed when models created exceed single page API limit in ubuntu. +* Fix to ensure policy key retreival during Digital Twin endpoint creation works. Affected commands are: - `az dt endpoint create *`. + +* Addition of new temporary experimental parameter `--max-models-per-batch` for `az dt model create` to let user adjust batch size when directory exceeds + 250 models. + 0.20.0 +++++++++++++++ diff --git a/azext_iot/azext_metadata.json b/azext_iot/azext_metadata.json index ddc7e0461..fb043f28f 100644 --- a/azext_iot/azext_metadata.json +++ b/azext_iot/azext_metadata.json @@ -1,3 +1,3 @@ { - "azext.minCliCoreVersion": "2.32.0" + "azext.minCliCoreVersion": "2.37.0" } diff --git a/azext_iot/constants.py b/azext_iot/constants.py index 4c57b7e35..d6233e2ad 100644 --- a/azext_iot/constants.py +++ b/azext_iot/constants.py @@ -7,7 +7,7 @@ import os -VERSION = "0.20.0" +VERSION = "0.21.0" EXTENSION_NAME = "azure-iot" EXTENSION_ROOT = os.path.dirname(os.path.abspath(__file__)) EXTENSION_CONFIG_ROOT_KEY = "iotext" diff --git a/azext_iot/tests/deviceupdate/conftest.py b/azext_iot/tests/deviceupdate/conftest.py index 069362dc5..df5267d3a 100644 --- a/azext_iot/tests/deviceupdate/conftest.py +++ b/azext_iot/tests/deviceupdate/conftest.py @@ -30,7 +30,7 @@ ACCOUNT_RG = settings.env.azext_iot_testrg VALID_IDENTITY_MAP = {"system": 1, "user": 1} -DEFAULT_ADU_RBAC_SLEEP_SEC = 105 +DEFAULT_ADU_RBAC_SLEEP_SEC = 90 # Manifest v4 will work with deviceUpdateModel;[1|2] but v5 only with deviceUpdateModel;2 ADU_CLIENT_DTMI = "dtmi:azure:iot:deviceUpdateModel;2" diff --git a/azext_iot/tests/deviceupdate/test_adu_update_int.py b/azext_iot/tests/deviceupdate/test_adu_update_int.py index 9bc1d27f8..32e0541c8 100644 --- a/azext_iot/tests/deviceupdate/test_adu_update_int.py +++ b/azext_iot/tests/deviceupdate/test_adu_update_int.py @@ -330,7 +330,14 @@ def test_instance_update_lifecycle(provisioned_instances_module: Dict[str, dict] f"--group-id {device_group_id} --start-time {start_date_time_iso} --update-name {simple_manifest_id['name']} " f"--update-provider {simple_manifest_id['provider']} --update-version {simple_manifest_id['version']}").as_json() assert basic_create_deployment["deploymentId"] == basic_deployment_id - assert basic_create_deployment["update"]["updateId"] == simple_manifest_id + + assert basic_create_deployment["update"]["updateId"] + for update_kpi in ["name", "provider", "version"]: + assert ( + update_kpi in basic_create_deployment["update"]["updateId"] + and basic_create_deployment["update"]["updateId"][update_kpi] == simple_manifest_id[update_kpi] + ) + assert device_class_id in basic_create_deployment["deviceClassSubgroups"] assert basic_create_deployment["groupId"] == device_group_id assert basic_create_deployment["isRetried"] is None diff --git a/azext_iot/tests/digitaltwins/test_dt_resource_lifecycle_int.py b/azext_iot/tests/digitaltwins/test_dt_resource_lifecycle_int.py index bbf4e0e9c..12b27b63d 100644 --- a/azext_iot/tests/digitaltwins/test_dt_resource_lifecycle_int.py +++ b/azext_iot/tests/digitaltwins/test_dt_resource_lifecycle_int.py @@ -12,7 +12,7 @@ from azext_iot.common.utility import unpack_msrest_error from azext_iot.digitaltwins.common import ADTEndpointAuthType, ADTEndpointType, IdentityType from azext_iot.tests.digitaltwins.dt_helpers import assert_system_data_attributes -from azext_iot.tests.helpers import assign_role_assignment, delete_role_assignment, get_role_assignments +from azext_iot.tests.helpers import assign_role_assignment, get_role_assignments from . import DTLiveScenarioTest from . import ( EP_RG, @@ -252,62 +252,74 @@ def test_dt_rbac(self): assert ( len( - get_role_assignments(scope=rbac_instance["id"]) + self.cmd( + "dt role-assignment list -n {}".format(rbac_instance_name) + ).get_output_in_json() ) == 0 ) - assign_output = assign_role_assignment( - role=self.role_map["owner"], - scope=rbac_instance["id"], - assignee=rbac_assignee_owner) - - assert assign_output and assign_output.success() + assign_output = self.cmd( + "dt role-assignment create -n {} --assignee {} --role '{}'".format( + rbac_instance_name, rbac_assignee_owner, self.role_map["owner"] + ) + ).get_output_in_json() assert_common_rbac_attributes( - assign_output.as_json(), + assign_output, rbac_instance_name, "owner", rbac_assignee_owner, ) - assign_output = assign_role_assignment( - role=self.role_map["reader"], - scope=rbac_instance["id"], - assignee=rbac_assignee_reader) - - assert assign_output and assign_output.success() + assign_output = self.cmd( + "dt role-assignment create -n {} --assignee {} --role '{}' -g {}".format( + rbac_instance_name, + rbac_assignee_reader, + self.role_map["reader"], + self.rg, + ) + ).get_output_in_json() assert_common_rbac_attributes( - assign_output.as_json(), + assign_output, rbac_instance_name, "reader", rbac_assignee_reader, ) - list_assigned_output = get_role_assignments(scope=rbac_instance["id"]) + list_assigned_output = self.cmd( + "dt role-assignment list -n {}".format(rbac_instance_name) + ).get_output_in_json() assert len(list_assigned_output) == 2 # Remove specific role assignment (reader) for assignee # Role-assignment delete does not currently return output - delete_role_assignment( - scope=rbac_instance["id"], - assignee=rbac_assignee_reader, - role=self.role_map["reader"] + self.cmd( + "dt role-assignment delete -n {} --assignee {} --role '{}'".format( + rbac_instance_name, + rbac_assignee_owner, + self.role_map["reader"], + ) ) - list_assigned_output = get_role_assignments(scope=rbac_instance["id"]) + list_assigned_output = self.cmd( + "dt role-assignment list -n {} -g {}".format(rbac_instance_name, self.rg) + ).get_output_in_json() assert len(list_assigned_output) == 1 # Remove all role assignments for assignee - delete_role_assignment( - scope=rbac_instance["id"], - assignee=rbac_assignee_owner + self.cmd( + "dt role-assignment delete -n {} --assignee {}".format( + rbac_instance_name, rbac_assignee_reader + ) ) - list_assigned_output = get_role_assignments(scope=rbac_instance["id"]) + list_assigned_output = self.cmd( + "dt role-assignment list -n {} -g {}".format(rbac_instance_name, self.rg) + ).get_output_in_json() assert len(list_assigned_output) == 0 diff --git a/azext_iot/tests/helpers.py b/azext_iot/tests/helpers.py index e87b35e30..ccb78cb1d 100644 --- a/azext_iot/tests/helpers.py +++ b/azext_iot/tests/helpers.py @@ -13,7 +13,7 @@ from azext_iot.common.utility import ensure_azure_namespace_path from azext_iot.common.utility import read_file_content from azext_iot.tests.settings import DynamoSettings -from typing import TypeVar +from typing import TypeVar, List ensure_azure_namespace_path() @@ -166,7 +166,7 @@ def get_role_assignments( scope: str, assignee: str = None, role: str = None, -) -> json: +) -> List[dict]: """ Get rbac permissions of resource. """ @@ -189,28 +189,34 @@ def assign_role_assignment( scope: str, assignee: str, max_tries=10, - wait=10 + wait=10, ): """ Assign rbac permissions to resource. """ output = None tries = 0 + principal_kpis = ["name", "principalId", "principalName"] while tries < max_tries: + flat_assignment_kpis = [] role_assignments = get_role_assignments(scope=scope, role=role) - role_assignment_principal_ids = [assignment["principalId"] for assignment in role_assignments] - if assignee in role_assignment_principal_ids: + logger.info(f"Role assignments for the role of '{role}' against scope '{scope}': {role_assignments}") + for role_assignment in role_assignments: + for principal_kpi in principal_kpis: + if principal_kpi in role_assignment and role_assignment[principal_kpi]: + flat_assignment_kpis.append(role_assignment[principal_kpi]) + if assignee in flat_assignment_kpis: break # else assign role to scope and check again output = cli.invoke( f'role assignment create --assignee "{assignee}" --role "{role}" --scope "{scope}"' ) + if not output.success(): + logger.warning(f"Failed to assign '{assignee}' the role of '{role}' against scope '{scope}'.") + break + sleep(wait) tries += 1 - if not output.success(): - logger.warning(f"Failed to assign '{assignee}' the role of '{role}' against scope '{scope}'.") - - return output def delete_role_assignment( diff --git a/pytest.ini.example b/pytest.ini.example index 4e6d6e31a..e1ff0b7d4 100644 --- a/pytest.ini.example +++ b/pytest.ini.example @@ -42,3 +42,4 @@ env = markers = adu_infrastructure: desired customizations for ADU integration tests. + hub_infrastructure: desired customizations for IoT Hub integration tests.