From 86796cd4111f6db65d8d2a94c76e974b32317fbc Mon Sep 17 00:00:00 2001 From: Yash Vohra Date: Wed, 23 Sep 2026 10:24:31 +0530 Subject: [PATCH 1/8] managedcleanroom: update collaboration commands for 2026-09-30-preview --- .../managedcleanroom/collaboration/_create.py | 87 ++- .../managedcleanroom/collaboration/_update.py | 500 +++++++----------- .../azext_managedcleanroom/commands.py | 5 +- .../azext_managedcleanroom/custom.py | 31 ++ .../tests/latest/test_custom.py | 66 +++ 5 files changed, 360 insertions(+), 329 deletions(-) create mode 100644 src/managedcleanroom/azext_managedcleanroom/tests/latest/test_custom.py diff --git a/src/managedcleanroom/azext_managedcleanroom/aaz/latest/managedcleanroom/collaboration/_create.py b/src/managedcleanroom/azext_managedcleanroom/aaz/latest/managedcleanroom/collaboration/_create.py index bf636ad057c..4a985e57540 100644 --- a/src/managedcleanroom/azext_managedcleanroom/aaz/latest/managedcleanroom/collaboration/_create.py +++ b/src/managedcleanroom/azext_managedcleanroom/aaz/latest/managedcleanroom/collaboration/_create.py @@ -9,7 +9,6 @@ # flake8: noqa from azure.cli.core.aaz import * -from ..private_endpoint_util import PrivateEndpointUtil @register_command( @@ -23,9 +22,9 @@ class Create(AAZCommand): """ _aaz_info = { - "version": "2026-04-30-preview", + "version": "2026-09-30-preview", "resources": [ - ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.cleanroom/collaborations/{}", "2026-04-30-preview"], + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.cleanroom/collaborations/{}", "2026-09-30-preview"], ] } @@ -71,6 +70,11 @@ def _build_arguments_schema(cls, *args, **kwargs): arg_group="Properties", help="Gets or sets the resource location for the collaboration.", ) + _args_schema.target_resource_configuration = AAZObjectArg( + options=["--target-resource-configuration"], + arg_group="Properties", + help="Create-time configuration for the Azure resources that host the collaboration.", + ) collaborators = cls._args_schema.collaborators collaborators.Element = AAZObjectArg() @@ -89,6 +93,45 @@ def _build_arguments_schema(cls, *args, **kwargs): help="User identifier of the collaborator. This can be specified as an email (no OID/TID should be specified) or an SPN (OID/TID required).", ) + target_resource_configuration = cls._args_schema.target_resource_configuration + target_resource_configuration.aks_sku = AAZStrArg( + options=["aks-sku"], + help="Virtual machine SKU used by the Azure Kubernetes Service node pool for a VN2OnAKS collaboration. The default is Standard_D4ds_v5.", + default="Standard_D4ds_v5", + enum={"Standard_D16ds_v5": "Standard_D16ds_v5", "Standard_D32ds_v5": "Standard_D32ds_v5", "Standard_D4ds_v5": "Standard_D4ds_v5", "Standard_D8ds_v5": "Standard_D8ds_v5"}, + ) + target_resource_configuration.i_p_tag_configuration = AAZObjectArg( + options=["i-p-tag-configuration"], + help="Tag applied to the target resources.", + ) + target_resource_configuration.node_pool_size = AAZIntArg( + options=["node-pool-size"], + help="Number of nodes in the Azure Kubernetes Service node pool for a VN2OnAKS collaboration. The default is 3.", + default=3, + fmt=AAZIntArgFormat( + maximum=10, + minimum=3, + ), + ) + + i_p_tag_configuration = cls._args_schema.target_resource_configuration.i_p_tag_configuration + i_p_tag_configuration.type = AAZStrArg( + options=["type"], + help="Tag type applied to the target resources.", + required=True, + fmt=AAZStrArgFormat( + min_length=1, + ), + ) + i_p_tag_configuration.value = AAZStrArg( + options=["value"], + help="Tag value applied to the target resources.", + required=True, + fmt=AAZStrArgFormat( + min_length=1, + ), + ) + # define Arg Group "Resource" _args_schema = cls._args_schema @@ -165,7 +208,7 @@ def __call__(self, *args, **kwargs): @property def url(self): return self.client.format_url( - f"/subscriptions/{{subscriptionId}}/resourceGroups/{{resourceGroupName}}/providers/{PrivateEndpointUtil.get_configured_namespace()}/collaborations/{{collaborationName}}", + "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.CleanRoom/collaborations/{collaborationName}", **self.url_parameters ) @@ -199,7 +242,7 @@ def url_parameters(self): def query_parameters(self): parameters = { **self.serialize_query_param( - "api-version", "2026-04-30-preview", + "api-version", "2026-09-30-preview", required=True, ), } @@ -233,6 +276,7 @@ def content(self): if properties is not None: properties.set_prop("collaborators", AAZListType, ".collaborators") properties.set_prop("resourceLocation", AAZStrType, ".resource_location") + properties.set_prop("targetResourceConfiguration", AAZObjectType, ".target_resource_configuration") collaborators = _builder.get(".properties.collaborators") if collaborators is not None: @@ -244,6 +288,17 @@ def content(self): _elements.set_prop("tenantId", AAZStrType, ".tenant_id") _elements.set_prop("userIdentifier", AAZStrType, ".user_identifier") + target_resource_configuration = _builder.get(".properties.targetResourceConfiguration") + if target_resource_configuration is not None: + target_resource_configuration.set_prop("aksSku", AAZStrType, ".aks_sku") + target_resource_configuration.set_prop("iPTagConfiguration", AAZObjectType, ".i_p_tag_configuration") + target_resource_configuration.set_prop("nodePoolSize", AAZIntType, ".node_pool_size") + + i_p_tag_configuration = _builder.get(".properties.targetResourceConfiguration.iPTagConfiguration") + if i_p_tag_configuration is not None: + i_p_tag_configuration.set_prop("type", AAZStrType, ".type", typ_kwargs={"flags": {"required": True}}) + i_p_tag_configuration.set_prop("value", AAZStrType, ".value", typ_kwargs={"flags": {"required": True}}) + tags = _builder.get(".tags") if tags is not None: tags.set_elements(AAZStrType, ".") @@ -318,6 +373,9 @@ def _build_schema_on_200_201(cls): properties.resource_location = AAZStrType( serialized_name="resourceLocation", ) + properties.target_resource_configuration = AAZObjectType( + serialized_name="targetResourceConfiguration", + ) properties.workloads = AAZListType( flags={"read_only": True}, ) @@ -371,6 +429,25 @@ def _build_schema_on_200_201(cls): _element = cls._schema_on_200_201.properties.managed_on_behalf_of_configuration.mobo_broker_resources.Element _element.id = AAZStrType() + target_resource_configuration = cls._schema_on_200_201.properties.target_resource_configuration + target_resource_configuration.aks_sku = AAZStrType( + serialized_name="aksSku", + ) + target_resource_configuration.i_p_tag_configuration = AAZObjectType( + serialized_name="iPTagConfiguration", + ) + target_resource_configuration.node_pool_size = AAZIntType( + serialized_name="nodePoolSize", + ) + + i_p_tag_configuration = cls._schema_on_200_201.properties.target_resource_configuration.i_p_tag_configuration + i_p_tag_configuration.type = AAZStrType( + flags={"required": True}, + ) + i_p_tag_configuration.value = AAZStrType( + flags={"required": True}, + ) + workloads = cls._schema_on_200_201.properties.workloads workloads.Element = AAZObjectType() diff --git a/src/managedcleanroom/azext_managedcleanroom/aaz/latest/managedcleanroom/collaboration/_update.py b/src/managedcleanroom/azext_managedcleanroom/aaz/latest/managedcleanroom/collaboration/_update.py index 966a1672b0c..9c8b2c728f8 100644 --- a/src/managedcleanroom/azext_managedcleanroom/aaz/latest/managedcleanroom/collaboration/_update.py +++ b/src/managedcleanroom/azext_managedcleanroom/aaz/latest/managedcleanroom/collaboration/_update.py @@ -9,30 +9,24 @@ # flake8: noqa from azure.cli.core.aaz import * -from ..private_endpoint_util import PrivateEndpointUtil @register_command( "managedcleanroom collaboration update", ) class Update(AAZCommand): - """Update a collaboration. - - :example: Update Collaboration - az managedcleanroom collaboration update --resource-group testrg --collaboration-name ContosoCollaboration --tags "env=testing" + """Update tags on a collaboration. """ _aaz_info = { - "version": "2026-04-30-preview", + "version": "2026-09-30-preview", "resources": [ - ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.cleanroom/collaborations/{}", "2026-04-30-preview"], + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.cleanroom/collaborations/{}", "2026-09-30-preview"], ] } AZ_SUPPORT_NO_WAIT = True - AZ_SUPPORT_GENERIC_UPDATE = True - def _handler(self, command_args): super()._handler(command_args) return self.build_lro_poller(self._execute_operations, self._output) @@ -63,38 +57,20 @@ def _build_arguments_schema(cls, *args, **kwargs): # define Arg Group "Properties" - _args_schema = cls._args_schema - _args_schema.resource_location = AAZStrArg( - options=["--resource-location"], - arg_group="Properties", - help="Gets or sets the resource location for the collaboration.", - nullable=True, - ) - - # define Arg Group "Resource" - _args_schema = cls._args_schema _args_schema.tags = AAZDictArg( options=["--tags"], - arg_group="Resource", + arg_group="Properties", help="Resource tags.", - nullable=True, ) tags = cls._args_schema.tags - tags.Element = AAZStrArg( - nullable=True, - ) + tags.Element = AAZStrArg() return cls._args_schema def _execute_operations(self): self.pre_operations() - self.CollaborationsGet(ctx=self.ctx)() - self.pre_instance_update(self.ctx.vars.instance) - self.InstanceUpdateByJson(ctx=self.ctx)() - self.InstanceUpdateByGeneric(ctx=self.ctx)() - self.post_instance_update(self.ctx.vars.instance) - yield self.CollaborationsCreate(ctx=self.ctx)() + yield self.CollaborationsUpdate(ctx=self.ctx)() self.post_operations() @register_callback @@ -105,102 +81,11 @@ def pre_operations(self): def post_operations(self): pass - @register_callback - def pre_instance_update(self, instance): - pass - - @register_callback - def post_instance_update(self, instance): - pass - def _output(self, *args, **kwargs): result = self.deserialize_output(self.ctx.vars.instance, client_flatten=True) return result - class CollaborationsGet(AAZHttpOperation): - CLIENT_TYPE = "MgmtClient" - - def __call__(self, *args, **kwargs): - request = self.make_request() - session = self.client.send_request(request=request, stream=False, **kwargs) - if session.http_response.status_code in [200]: - return self.on_200(session) - - return self.on_error(session.http_response) - - @property - def url(self): - return self.client.format_url( - f"/subscriptions/{{subscriptionId}}/resourceGroups/{{resourceGroupName}}/providers/{PrivateEndpointUtil.get_configured_namespace()}/collaborations/{{collaborationName}}", - **self.url_parameters - ) - - @property - def method(self): - return "GET" - - @property - def error_format(self): - return "MgmtErrorFormat" - - @property - def url_parameters(self): - parameters = { - **self.serialize_url_param( - "collaborationName", self.ctx.args.collaboration_name, - required=True, - ), - **self.serialize_url_param( - "resourceGroupName", self.ctx.args.resource_group, - required=True, - ), - **self.serialize_url_param( - "subscriptionId", self.ctx.subscription_id, - required=True, - ), - } - return parameters - - @property - def query_parameters(self): - parameters = { - **self.serialize_query_param( - "api-version", "2026-04-30-preview", - required=True, - ), - } - return parameters - - @property - def header_parameters(self): - parameters = { - **self.serialize_header_param( - "Accept", "application/json", - ), - } - return parameters - - def on_200(self, session): - data = self.deserialize_http_content(session) - self.ctx.set_var( - "instance", - data, - schema_builder=self._build_schema_on_200 - ) - - _schema_on_200 = None - - @classmethod - def _build_schema_on_200(cls): - if cls._schema_on_200 is not None: - return cls._schema_on_200 - - cls._schema_on_200 = AAZObjectType() - _UpdateHelper._build_schema_collaboration_read(cls._schema_on_200) - - return cls._schema_on_200 - - class CollaborationsCreate(AAZHttpOperation): + class CollaborationsUpdate(AAZHttpOperation): CLIENT_TYPE = "MgmtClient" def __call__(self, *args, **kwargs): @@ -210,18 +95,18 @@ def __call__(self, *args, **kwargs): return self.client.build_lro_polling( self.ctx.args.no_wait, session, - self.on_200_201, + self.on_200, self.on_error, - lro_options={"final-state-via": "azure-async-operation"}, + lro_options={"final-state-via": "location"}, path_format_arguments=self.url_parameters, ) - if session.http_response.status_code in [200, 201]: + if session.http_response.status_code in [200]: return self.client.build_lro_polling( self.ctx.args.no_wait, session, - self.on_200_201, + self.on_200, self.on_error, - lro_options={"final-state-via": "azure-async-operation"}, + lro_options={"final-state-via": "location"}, path_format_arguments=self.url_parameters, ) @@ -230,7 +115,7 @@ def __call__(self, *args, **kwargs): @property def url(self): return self.client.format_url( - f"/subscriptions/{{subscriptionId}}/resourceGroups/{{resourceGroupName}}/providers/{PrivateEndpointUtil.get_configured_namespace()}/collaborations/{{collaborationName}}", + "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.CleanRoom/collaborations/{collaborationName}", **self.url_parameters ) @@ -264,7 +149,7 @@ def url_parameters(self): def query_parameters(self): parameters = { **self.serialize_query_param( - "api-version", "2026-04-30-preview", + "api-version", "2026-09-30-preview", required=True, ), } @@ -286,234 +171,203 @@ def header_parameters(self): def content(self): _content_value, _builder = self.new_content_builder( self.ctx.args, - value=self.ctx.vars.instance, + typ=AAZObjectType, + typ_kwargs={"flags": {"required": True, "client_flatten": True}} ) + _builder.set_prop("tags", AAZDictType, ".tags") + + tags = _builder.get(".tags") + if tags is not None: + tags.set_elements(AAZStrType, ".") return self.serialize_content(_content_value) - def on_200_201(self, session): + def on_200(self, session): data = self.deserialize_http_content(session) self.ctx.set_var( "instance", data, - schema_builder=self._build_schema_on_200_201 + schema_builder=self._build_schema_on_200 ) - _schema_on_200_201 = None + _schema_on_200 = None @classmethod - def _build_schema_on_200_201(cls): - if cls._schema_on_200_201 is not None: - return cls._schema_on_200_201 - - cls._schema_on_200_201 = AAZObjectType() - _UpdateHelper._build_schema_collaboration_read(cls._schema_on_200_201) - - return cls._schema_on_200_201 - - class InstanceUpdateByJson(AAZJsonInstanceUpdateOperation): + def _build_schema_on_200(cls): + if cls._schema_on_200 is not None: + return cls._schema_on_200 - def __call__(self, *args, **kwargs): - self._update_instance(self.ctx.vars.instance) + cls._schema_on_200 = AAZObjectType() - def _update_instance(self, instance): - _instance_value, _builder = self.new_content_builder( - self.ctx.args, - value=instance, - typ=AAZObjectType + _schema_on_200 = cls._schema_on_200 + _schema_on_200.id = AAZStrType( + flags={"read_only": True}, + ) + _schema_on_200.kind = AAZStrType() + _schema_on_200.location = AAZStrType( + flags={"required": True}, + ) + _schema_on_200.name = AAZStrType( + flags={"read_only": True}, + ) + _schema_on_200.properties = AAZObjectType( + flags={"client_flatten": True}, + ) + _schema_on_200.system_data = AAZObjectType( + serialized_name="systemData", + flags={"read_only": True}, + ) + _schema_on_200.tags = AAZDictType() + _schema_on_200.type = AAZStrType( + flags={"read_only": True}, ) - _builder.set_prop("properties", AAZObjectType, typ_kwargs={"flags": {"client_flatten": True}}) - _builder.set_prop("tags", AAZDictType, ".tags") - - properties = _builder.get(".properties") - if properties is not None: - properties.set_prop("resourceLocation", AAZStrType, ".resource_location") - - tags = _builder.get(".tags") - if tags is not None: - tags.set_elements(AAZStrType, ".") - - return _instance_value - - class InstanceUpdateByGeneric(AAZGenericInstanceUpdateOperation): - def __call__(self, *args, **kwargs): - self._update_instance_by_generic( - self.ctx.vars.instance, - self.ctx.generic_update_args + properties = cls._schema_on_200.properties + properties.cluster_endpoint = AAZStrType( + serialized_name="clusterEndpoint", + flags={"read_only": True}, + ) + properties.collaboration_state = AAZStrType( + serialized_name="collaborationState", + flags={"read_only": True}, + ) + properties.collaborators = AAZListType() + properties.consortium_arm_id = AAZStrType( + serialized_name="consortiumArmId", + flags={"read_only": True}, + ) + properties.health = AAZObjectType( + flags={"read_only": True}, + ) + properties.managed_on_behalf_of_configuration = AAZObjectType( + serialized_name="managedOnBehalfOfConfiguration", + flags={"read_only": True}, + ) + properties.provisioning_state = AAZStrType( + serialized_name="provisioningState", + flags={"read_only": True}, + ) + properties.resource_location = AAZStrType( + serialized_name="resourceLocation", + ) + properties.target_resource_configuration = AAZObjectType( + serialized_name="targetResourceConfiguration", + ) + properties.workloads = AAZListType( + flags={"read_only": True}, ) + collaborators = cls._schema_on_200.properties.collaborators + collaborators.Element = AAZObjectType() -class _UpdateHelper: - """Helper class for Update""" + _element = cls._schema_on_200.properties.collaborators.Element + _element.is_collaboration_owner = AAZBoolType( + serialized_name="isCollaborationOwner", + flags={"read_only": True}, + ) + _element.object_id = AAZStrType( + serialized_name="objectId", + ) + _element.tenant_id = AAZStrType( + serialized_name="tenantId", + ) + _element.user_identifier = AAZStrType( + serialized_name="userIdentifier", + ) - _schema_collaboration_read = None + health = cls._schema_on_200.properties.health + health.health_issues = AAZListType( + serialized_name="healthIssues", + ) + health.health_state = AAZStrType( + serialized_name="healthState", + flags={"required": True}, + ) - @classmethod - def _build_schema_collaboration_read(cls, _schema): - if cls._schema_collaboration_read is not None: - _schema.id = cls._schema_collaboration_read.id - _schema.kind = cls._schema_collaboration_read.kind - _schema.location = cls._schema_collaboration_read.location - _schema.name = cls._schema_collaboration_read.name - _schema.properties = cls._schema_collaboration_read.properties - _schema.system_data = cls._schema_collaboration_read.system_data - _schema.tags = cls._schema_collaboration_read.tags - _schema.type = cls._schema_collaboration_read.type - return - - cls._schema_collaboration_read = _schema_collaboration_read = AAZObjectType() - - collaboration_read = _schema_collaboration_read - collaboration_read.id = AAZStrType( - flags={"read_only": True}, - ) - collaboration_read.kind = AAZStrType() - collaboration_read.location = AAZStrType( - flags={"required": True}, - ) - collaboration_read.name = AAZStrType( - flags={"read_only": True}, - ) - collaboration_read.properties = AAZObjectType( - flags={"client_flatten": True}, - ) - collaboration_read.system_data = AAZObjectType( - serialized_name="systemData", - flags={"read_only": True}, - ) - collaboration_read.tags = AAZDictType() - collaboration_read.type = AAZStrType( - flags={"read_only": True}, - ) + health_issues = cls._schema_on_200.properties.health.health_issues + health_issues.Element = AAZObjectType() - properties = _schema_collaboration_read.properties - properties.cluster_endpoint = AAZStrType( - serialized_name="clusterEndpoint", - flags={"read_only": True}, - ) - properties.collaboration_state = AAZStrType( - serialized_name="collaborationState", - flags={"read_only": True}, - ) - properties.collaborators = AAZListType() - properties.consortium_arm_id = AAZStrType( - serialized_name="consortiumArmId", - flags={"read_only": True}, - ) - properties.health = AAZObjectType( - flags={"read_only": True}, - ) - properties.managed_on_behalf_of_configuration = AAZObjectType( - serialized_name="managedOnBehalfOfConfiguration", - flags={"read_only": True}, - ) - properties.provisioning_state = AAZStrType( - serialized_name="provisioningState", - flags={"read_only": True}, - ) - properties.resource_location = AAZStrType( - serialized_name="resourceLocation", - ) - properties.workloads = AAZListType( - flags={"read_only": True}, - ) + _element = cls._schema_on_200.properties.health.health_issues.Element + _element.code = AAZStrType( + flags={"required": True}, + ) + _element.message = AAZStrType( + flags={"required": True}, + ) - collaborators = _schema_collaboration_read.properties.collaborators - collaborators.Element = AAZObjectType() + managed_on_behalf_of_configuration = cls._schema_on_200.properties.managed_on_behalf_of_configuration + managed_on_behalf_of_configuration.mobo_broker_resources = AAZListType( + serialized_name="moboBrokerResources", + ) - _element = _schema_collaboration_read.properties.collaborators.Element - _element.is_collaboration_owner = AAZBoolType( - serialized_name="isCollaborationOwner", - flags={"read_only": True}, - ) - _element.object_id = AAZStrType( - serialized_name="objectId", - ) - _element.tenant_id = AAZStrType( - serialized_name="tenantId", - ) - _element.user_identifier = AAZStrType( - serialized_name="userIdentifier", - ) + mobo_broker_resources = cls._schema_on_200.properties.managed_on_behalf_of_configuration.mobo_broker_resources + mobo_broker_resources.Element = AAZObjectType() - health = _schema_collaboration_read.properties.health - health.health_issues = AAZListType( - serialized_name="healthIssues", - ) - health.health_state = AAZStrType( - serialized_name="healthState", - flags={"required": True}, - ) + _element = cls._schema_on_200.properties.managed_on_behalf_of_configuration.mobo_broker_resources.Element + _element.id = AAZStrType() - health_issues = _schema_collaboration_read.properties.health.health_issues - health_issues.Element = AAZObjectType() + target_resource_configuration = cls._schema_on_200.properties.target_resource_configuration + target_resource_configuration.aks_sku = AAZStrType( + serialized_name="aksSku", + ) + target_resource_configuration.i_p_tag_configuration = AAZObjectType( + serialized_name="iPTagConfiguration", + ) + target_resource_configuration.node_pool_size = AAZIntType( + serialized_name="nodePoolSize", + ) - _element = _schema_collaboration_read.properties.health.health_issues.Element - _element.code = AAZStrType( - flags={"required": True}, - ) - _element.message = AAZStrType( - flags={"required": True}, - ) + i_p_tag_configuration = cls._schema_on_200.properties.target_resource_configuration.i_p_tag_configuration + i_p_tag_configuration.type = AAZStrType( + flags={"required": True}, + ) + i_p_tag_configuration.value = AAZStrType( + flags={"required": True}, + ) - managed_on_behalf_of_configuration = _schema_collaboration_read.properties.managed_on_behalf_of_configuration - managed_on_behalf_of_configuration.mobo_broker_resources = AAZListType( - serialized_name="moboBrokerResources", - ) + workloads = cls._schema_on_200.properties.workloads + workloads.Element = AAZObjectType() - mobo_broker_resources = _schema_collaboration_read.properties.managed_on_behalf_of_configuration.mobo_broker_resources - mobo_broker_resources.Element = AAZObjectType() + _element = cls._schema_on_200.properties.workloads.Element + _element.endpoint = AAZStrType( + flags={"required": True}, + ) + _element.namespace = AAZStrType( + flags={"required": True}, + ) + _element.workload_type = AAZStrType( + serialized_name="workloadType", + flags={"required": True}, + ) - _element = _schema_collaboration_read.properties.managed_on_behalf_of_configuration.mobo_broker_resources.Element - _element.id = AAZStrType() + system_data = cls._schema_on_200.system_data + system_data.created_at = AAZStrType( + serialized_name="createdAt", + ) + system_data.created_by = AAZStrType( + serialized_name="createdBy", + ) + system_data.created_by_type = AAZStrType( + serialized_name="createdByType", + ) + system_data.last_modified_at = AAZStrType( + serialized_name="lastModifiedAt", + ) + system_data.last_modified_by = AAZStrType( + serialized_name="lastModifiedBy", + ) + system_data.last_modified_by_type = AAZStrType( + serialized_name="lastModifiedByType", + ) - workloads = _schema_collaboration_read.properties.workloads - workloads.Element = AAZObjectType() + tags = cls._schema_on_200.tags + tags.Element = AAZStrType() - _element = _schema_collaboration_read.properties.workloads.Element - _element.endpoint = AAZStrType( - flags={"required": True}, - ) - _element.namespace = AAZStrType( - flags={"required": True}, - ) - _element.workload_type = AAZStrType( - serialized_name="workloadType", - flags={"required": True}, - ) + return cls._schema_on_200 - system_data = _schema_collaboration_read.system_data - system_data.created_at = AAZStrType( - serialized_name="createdAt", - ) - system_data.created_by = AAZStrType( - serialized_name="createdBy", - ) - system_data.created_by_type = AAZStrType( - serialized_name="createdByType", - ) - system_data.last_modified_at = AAZStrType( - serialized_name="lastModifiedAt", - ) - system_data.last_modified_by = AAZStrType( - serialized_name="lastModifiedBy", - ) - system_data.last_modified_by_type = AAZStrType( - serialized_name="lastModifiedByType", - ) - tags = _schema_collaboration_read.tags - tags.Element = AAZStrType() - - _schema.id = cls._schema_collaboration_read.id - _schema.kind = cls._schema_collaboration_read.kind - _schema.location = cls._schema_collaboration_read.location - _schema.name = cls._schema_collaboration_read.name - _schema.properties = cls._schema_collaboration_read.properties - _schema.system_data = cls._schema_collaboration_read.system_data - _schema.tags = cls._schema_collaboration_read.tags - _schema.type = cls._schema_collaboration_read.type +class _UpdateHelper: + """Helper class for Update""" __all__ = ["Update"] diff --git a/src/managedcleanroom/azext_managedcleanroom/commands.py b/src/managedcleanroom/azext_managedcleanroom/commands.py index b0d842e4993..6e6a611c752 100644 --- a/src/managedcleanroom/azext_managedcleanroom/commands.py +++ b/src/managedcleanroom/azext_managedcleanroom/commands.py @@ -12,4 +12,7 @@ def load_command_table(self, _): # pylint: disable=unused-argument - pass + from .custom import CollaborationCreate, CollaborationUpdate + + self.command_table["managedcleanroom collaboration create"] = CollaborationCreate(loader=self) + self.command_table["managedcleanroom collaboration update"] = CollaborationUpdate(loader=self) diff --git a/src/managedcleanroom/azext_managedcleanroom/custom.py b/src/managedcleanroom/azext_managedcleanroom/custom.py index 86df1e48ef5..80e9af5ff5f 100644 --- a/src/managedcleanroom/azext_managedcleanroom/custom.py +++ b/src/managedcleanroom/azext_managedcleanroom/custom.py @@ -10,5 +10,36 @@ from knack.log import get_logger +from .aaz.latest.managedcleanroom.collaboration import Create as _CollaborationCreate +from .aaz.latest.managedcleanroom.collaboration import Update as _CollaborationUpdate +from .aaz.latest.managedcleanroom.private_endpoint_util import PrivateEndpointUtil + logger = get_logger(__name__) + + +class _PrivateNamespaceOperationMixin: + + @property + def url(self): + return self.client.format_url( + f"/subscriptions/{{subscriptionId}}/resourceGroups/{{resourceGroupName}}/providers/" + f"{PrivateEndpointUtil.get_configured_namespace()}/collaborations/{{collaborationName}}", + **self.url_parameters + ) + + +class CollaborationCreate(_CollaborationCreate): + + class CollaborationsCreate( + _PrivateNamespaceOperationMixin, + _CollaborationCreate.CollaborationsCreate): + pass + + +class CollaborationUpdate(_CollaborationUpdate): + + class CollaborationsUpdate( + _PrivateNamespaceOperationMixin, + _CollaborationUpdate.CollaborationsUpdate): + pass diff --git a/src/managedcleanroom/azext_managedcleanroom/tests/latest/test_custom.py b/src/managedcleanroom/azext_managedcleanroom/tests/latest/test_custom.py new file mode 100644 index 00000000000..f4c014625d6 --- /dev/null +++ b/src/managedcleanroom/azext_managedcleanroom/tests/latest/test_custom.py @@ -0,0 +1,66 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import os +import unittest +from unittest.mock import patch + +from azext_managedcleanroom.custom import CollaborationCreate, CollaborationUpdate + + +class _Client: + + @staticmethod + def format_url(url, **parameters): + return url.format(**parameters) + + +class TestCollaborationCommands(unittest.TestCase): + + _PARAMETERS = { + "subscriptionId": "subscription", + "resourceGroupName": "resource-group", + "collaborationName": "collaboration", + } + + @staticmethod + def _operation_url(operation_type): + class TestOperation(operation_type): + + @property + def client(self): + return _Client() + + @property + def url_parameters(self): + return TestCollaborationCommands._PARAMETERS + + return object.__new__(TestOperation).url + + def test_operations_use_public_namespace_by_default(self): + with patch.dict(os.environ, {}, clear=True): + for operation_type in ( + CollaborationCreate.CollaborationsCreate, + CollaborationUpdate.CollaborationsUpdate): + with self.subTest(operation_type=operation_type): + self.assertIn( + "/providers/Microsoft.CleanRoom/collaborations/", + self._operation_url(operation_type), + ) + + def test_operations_use_private_namespace_when_configured(self): + with patch.dict(os.environ, {"UsePrivateCleanRoomNamespace": "true"}, clear=True): + for operation_type in ( + CollaborationCreate.CollaborationsCreate, + CollaborationUpdate.CollaborationsUpdate): + with self.subTest(operation_type=operation_type): + self.assertIn( + "/providers/Private.CleanRoom/collaborations/", + self._operation_url(operation_type), + ) + + +if __name__ == "__main__": + unittest.main() From 7fc425cf42d7a8306541ad9b2d93665826ad3219 Mon Sep 17 00:00:00 2001 From: Yash Vohra Date: Wed, 23 Sep 2026 15:32:34 +0530 Subject: [PATCH 2/8] managedcleanroom: silence too-few-public-methods on private-namespace mixin --- src/managedcleanroom/azext_managedcleanroom/custom.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/managedcleanroom/azext_managedcleanroom/custom.py b/src/managedcleanroom/azext_managedcleanroom/custom.py index 80e9af5ff5f..a20806e8e49 100644 --- a/src/managedcleanroom/azext_managedcleanroom/custom.py +++ b/src/managedcleanroom/azext_managedcleanroom/custom.py @@ -18,7 +18,7 @@ logger = get_logger(__name__) -class _PrivateNamespaceOperationMixin: +class _PrivateNamespaceOperationMixin: # pylint: disable=too-few-public-methods @property def url(self): From 19672d3f9cfb26a27e80d76c6e01d25c6c05b585 Mon Sep 17 00:00:00 2001 From: Yash Vohra Date: Thu, 24 Sep 2026 14:25:12 +0530 Subject: [PATCH 3/8] managedcleanroom: add --target-config alias and consortium-view contract wait command --- .../managedcleanroom/collaboration/_create.py | 2 +- .../consortium_view/contract/__init__.py | 1 + .../consortium_view/contract/_wait.py | 92 +++++++++++++++++++ 3 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 src/managedcleanroom/azext_managedcleanroom/aaz/latest/managedcleanroom/consortium_view/contract/_wait.py diff --git a/src/managedcleanroom/azext_managedcleanroom/aaz/latest/managedcleanroom/collaboration/_create.py b/src/managedcleanroom/azext_managedcleanroom/aaz/latest/managedcleanroom/collaboration/_create.py index 4a985e57540..ba5720d43a9 100644 --- a/src/managedcleanroom/azext_managedcleanroom/aaz/latest/managedcleanroom/collaboration/_create.py +++ b/src/managedcleanroom/azext_managedcleanroom/aaz/latest/managedcleanroom/collaboration/_create.py @@ -71,7 +71,7 @@ def _build_arguments_schema(cls, *args, **kwargs): help="Gets or sets the resource location for the collaboration.", ) _args_schema.target_resource_configuration = AAZObjectArg( - options=["--target-resource-configuration"], + options=["--target-resource-configuration", "--target-config"], arg_group="Properties", help="Create-time configuration for the Azure resources that host the collaboration.", ) diff --git a/src/managedcleanroom/azext_managedcleanroom/aaz/latest/managedcleanroom/consortium_view/contract/__init__.py b/src/managedcleanroom/azext_managedcleanroom/aaz/latest/managedcleanroom/consortium_view/contract/__init__.py index 6f7f59271a2..87ce69842ab 100644 --- a/src/managedcleanroom/azext_managedcleanroom/aaz/latest/managedcleanroom/consortium_view/contract/__init__.py +++ b/src/managedcleanroom/azext_managedcleanroom/aaz/latest/managedcleanroom/consortium_view/contract/__init__.py @@ -12,3 +12,4 @@ from ._list import * from ._propose_template import * from ._show import * +from ._wait import * diff --git a/src/managedcleanroom/azext_managedcleanroom/aaz/latest/managedcleanroom/consortium_view/contract/_wait.py b/src/managedcleanroom/azext_managedcleanroom/aaz/latest/managedcleanroom/consortium_view/contract/_wait.py new file mode 100644 index 00000000000..6c70b0fd571 --- /dev/null +++ b/src/managedcleanroom/azext_managedcleanroom/aaz/latest/managedcleanroom/consortium_view/contract/_wait.py @@ -0,0 +1,92 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: skip-file +# flake8: noqa + +from azure.cli.core.aaz import * +from ._show import Show + + +@register_command( + "managedcleanroom consortium-view contract wait", +) +class Wait(AAZWaitCommand): + """Place the CLI in a waiting state until a condition is met. + """ + + _aaz_info = { + "resources": [ + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.cleanroom/consortiumviews/{}/contracts/{}", "2026-04-30-preview"], + ] + } + + def _handler(self, command_args): + super()._handler(command_args) + self._execute_operations() + return self._output() + + _args_schema = None + + @classmethod + def _build_arguments_schema(cls, *args, **kwargs): + if cls._args_schema is not None: + return cls._args_schema + cls._args_schema = super()._build_arguments_schema(*args, **kwargs) + + # define Arg Group "" + + _args_schema = cls._args_schema + _args_schema.consortium_view_name = AAZStrArg( + options=["--consortium-view-name"], + help="Name of the consortium view.", + required=True, + id_part="name", + fmt=AAZStrArgFormat( + pattern="^[a-zA-Z0-9]$|^[a-zA-Z0-9][-_a-zA-Z0-9]{0,61}[a-zA-Z0-9]$", + ), + ) + _args_schema.contract_name = AAZStrArg( + options=["-n", "--name", "--contract-name"], + help="Name of the contract.", + required=True, + id_part="child_name_1", + fmt=AAZStrArgFormat( + pattern="^[a-zA-Z0-9]$|^[a-zA-Z0-9][-_a-zA-Z0-9]{0,61}[a-zA-Z0-9]$", + ), + ) + _args_schema.resource_group = AAZResourceGroupNameArg( + required=True, + ) + return cls._args_schema + + def _execute_operations(self): + self.pre_operations() + self.ConsortiumViewContractsGet(ctx=self.ctx)() + self.post_operations() + + @register_callback + def pre_operations(self): + pass + + @register_callback + def post_operations(self): + pass + + def _output(self, *args, **kwargs): + result = self.deserialize_output(self.ctx.vars.instance, client_flatten=False) + return result + + class ConsortiumViewContractsGet(Show.ConsortiumViewContractsGet): + pass + + +class _WaitHelper: + """Helper class for Wait""" + + +__all__ = ["Wait"] From abcf295b3446f5e0065d9606a7d2508e59287716 Mon Sep 17 00:00:00 2001 From: Yash Vohra Date: Thu, 24 Sep 2026 14:25:13 +0530 Subject: [PATCH 4/8] managedcleanroom: add frontend collaborator list and query run cancel commands --- src/managedcleanroom/HISTORY.rst | 8 + .../_frontend_commands.py | 6 + .../_frontend_custom.py | 30 +++ .../azext_managedcleanroom/_help.py | 21 ++ .../azext_managedcleanroom/_params.py | 13 ++ .../aio/operations/_operations.py | 159 +++++++++++++ .../operations/_operations.py | 211 ++++++++++++++++++ .../latest/test_frontend_collaboration.py | 20 ++ .../tests/latest/test_frontend_query.py | 19 ++ 9 files changed, 487 insertions(+) diff --git a/src/managedcleanroom/HISTORY.rst b/src/managedcleanroom/HISTORY.rst index ec2ce509b0b..39094e78d69 100644 --- a/src/managedcleanroom/HISTORY.rst +++ b/src/managedcleanroom/HISTORY.rst @@ -3,6 +3,14 @@ Release History =============== +1.0.0b7 ++++++++ +* Update commands to reflect new API version 2026-09-30-preview +* Added: ``--target-config`` alias for ``az managedcleanroom collaboration create --target-resource-configuration`` +* Added: ``az managedcleanroom consortium-view contract wait`` +* Added: ``az managedcleanroom frontend collaborator list`` to list collaborators in a collaboration +* Added: ``az managedcleanroom frontend analytics query runhistory cancel`` to cancel a query run + 1.0.0b6 +++++++ * Update commands to reflect new API version 2026-04-30-preview diff --git a/src/managedcleanroom/azext_managedcleanroom/_frontend_commands.py b/src/managedcleanroom/azext_managedcleanroom/_frontend_commands.py index 361a60ebdd7..fcb5737833a 100644 --- a/src/managedcleanroom/azext_managedcleanroom/_frontend_commands.py +++ b/src/managedcleanroom/azext_managedcleanroom/_frontend_commands.py @@ -47,6 +47,10 @@ def load_frontend_command_table(loader, _): g.custom_show_command( 'show', 'frontend_collaboration_oidc_issuerinfo_show') + # Collaborator commands + with loader.command_group('managedcleanroom frontend collaborator', custom_command_type=frontend_custom) as g: + g.custom_command('list', 'frontend_collaboration_collaborators_list') + # Invitation commands with loader.command_group('managedcleanroom frontend invitation', custom_command_type=frontend_custom) as g: g.custom_command('list', 'frontend_collaboration_invitation_list') @@ -81,6 +85,8 @@ def load_frontend_command_table(loader, _): custom_command_type=frontend_custom) as g: g.custom_command( 'list', 'frontend_collaboration_query_runhistory_list') + g.custom_command( + 'cancel', 'frontend_collaboration_query_runhistory_cancel') # Query run result commands with loader.command_group( diff --git a/src/managedcleanroom/azext_managedcleanroom/_frontend_custom.py b/src/managedcleanroom/azext_managedcleanroom/_frontend_custom.py index 19bb5d4e450..b0cb1a0634b 100644 --- a/src/managedcleanroom/azext_managedcleanroom/_frontend_custom.py +++ b/src/managedcleanroom/azext_managedcleanroom/_frontend_custom.py @@ -63,6 +63,18 @@ def frontend_collaboration_report_show(cmd, collaboration_id, api_version=None): return client.collaboration.report_get(collaboration_id) +def frontend_collaboration_collaborators_list(cmd, collaboration_id, api_version=None): + """List collaborators in a collaboration + + :param cmd: CLI command context + :param collaboration_id: Collaboration identifier + :param api_version: API version to use for this request + :return: Collaborators object with array of collaborator details + """ + client = get_frontend_client(cmd, api_version=api_version) + return client.collaboration.collaborators_get(collaboration_id) + + # ============================================================================ # Analytics Commands # ============================================================================ @@ -850,6 +862,24 @@ def frontend_collaboration_query_runhistory_list( ) +def frontend_collaboration_query_runhistory_cancel( + cmd, collaboration_id, document_id, run_id, api_version=None +): + """Cancel a query run + + :param cmd: CLI command context + :param collaboration_id: Collaboration identifier + :param document_id: Query document identifier + :param run_id: Query run identifier + :param api_version: API version to use for this request + :return: Cancellation result with id and status + """ + client = get_frontend_client(cmd, api_version=api_version) + return client.collaboration.analytics_queries_document_id_runs_run_id_cancel_post( + collaboration_id, document_id, run_id + ) + + def frontend_collaboration_query_runresult_show( cmd, collaboration_id, job_id, api_version=None ): diff --git a/src/managedcleanroom/azext_managedcleanroom/_help.py b/src/managedcleanroom/azext_managedcleanroom/_help.py index 1576cdd4bc1..da2fe768743 100644 --- a/src/managedcleanroom/azext_managedcleanroom/_help.py +++ b/src/managedcleanroom/azext_managedcleanroom/_help.py @@ -269,6 +269,19 @@ # Invitation Help # ============================================================================ +helps['managedcleanroom frontend collaborator'] = """ + type: group + short-summary: View collaborators in a collaboration +""" + +helps['managedcleanroom frontend collaborator list'] = """ + type: command + short-summary: List collaborators in a collaboration + examples: + - name: List collaborators + text: az managedcleanroom frontend collaborator list -c +""" + helps['managedcleanroom frontend invitation'] = """ type: group short-summary: Manage collaboration invitations @@ -710,6 +723,14 @@ text: az managedcleanroom frontend analytics query runhistory list -c --document-id """ +helps['managedcleanroom frontend analytics query runhistory cancel'] = """ + type: command + short-summary: Cancel a query run + examples: + - name: Cancel a query run + text: az managedcleanroom frontend analytics query runhistory cancel -c --document-id --run-id +""" + helps['managedcleanroom frontend analytics query runresult'] = """ type: group short-summary: View query run results diff --git a/src/managedcleanroom/azext_managedcleanroom/_params.py b/src/managedcleanroom/azext_managedcleanroom/_params.py index 89618e94a13..acfc51da160 100644 --- a/src/managedcleanroom/azext_managedcleanroom/_params.py +++ b/src/managedcleanroom/azext_managedcleanroom/_params.py @@ -44,6 +44,11 @@ def load_arguments(self, _): # pylint: disable=unused-argument help='Query job identifier (for run results)' ) + run_id_type = CLIArgumentType( + options_list=['--run-id', '-r'], + help='Query run identifier' + ) + consent_action_type = CLIArgumentType( options_list=['--consent-action', '-a'], help="Consent action: 'enable' or 'disable'" @@ -93,6 +98,10 @@ def load_arguments(self, _): # pylint: disable=unused-argument with self.argument_context('managedcleanroom frontend oidc keys') as c: c.argument('collaboration_id', collaboration_id_type) + # Collaborator context + with self.argument_context('managedcleanroom frontend collaborator') as c: + c.argument('collaboration_id', collaboration_id_type) + # Invitation context with self.argument_context('managedcleanroom frontend invitation') as c: c.argument('collaboration_id', collaboration_id_type) @@ -301,6 +310,10 @@ def load_arguments(self, _): # pylint: disable=unused-argument with self.argument_context('managedcleanroom frontend analytics query runhistory list') as c: c.argument('document_id', document_id_type) + with self.argument_context('managedcleanroom frontend analytics query runhistory cancel') as c: + c.argument('document_id', document_id_type) + c.argument('run_id', run_id_type) + # Query runresult context with self.argument_context('managedcleanroom frontend analytics query runresult') as c: c.argument('collaboration_id', collaboration_id_type) diff --git a/src/managedcleanroom/azext_managedcleanroom/analytics_frontend_api/aio/operations/_operations.py b/src/managedcleanroom/azext_managedcleanroom/analytics_frontend_api/aio/operations/_operations.py index 23c6695aa8c..d3b6d6ef9a8 100644 --- a/src/managedcleanroom/azext_managedcleanroom/analytics_frontend_api/aio/operations/_operations.py +++ b/src/managedcleanroom/azext_managedcleanroom/analytics_frontend_api/aio/operations/_operations.py @@ -34,11 +34,13 @@ build_collaboration_analytics_queries_document_id_publish_post_request, build_collaboration_analytics_queries_document_id_run_post_request, build_collaboration_analytics_queries_document_id_runs_get_request, + build_collaboration_analytics_queries_document_id_runs_run_id_cancel_post_request, build_collaboration_analytics_queries_document_id_vote_post_request, build_collaboration_analytics_queries_list_get_request, build_collaboration_analytics_runs_job_id_get_request, build_collaboration_analytics_secrets_secret_name_put_request, build_collaboration_analytics_skr_policy_get_request, + build_collaboration_collaborators_get_request, build_collaboration_consent_document_id_get_request, build_collaboration_consent_document_id_put_request, build_collaboration_id_get_request, @@ -915,6 +917,84 @@ async def oidc_keys_get( return cast(JSON, deserialized) # type: ignore + @distributed_trace_async + async def collaborators_get(self, collaboration_id: str, **kwargs: Any) -> Optional[JSON]: + """List all collaborators in a collaboration. + + List all collaborators in a collaboration. + + :param collaboration_id: Required. + :type collaboration_id: str + :return: JSON object or None + :rtype: JSON or None + :raises ~azure.core.exceptions.HttpResponseError: + + Example: + .. code-block:: python + + # response body for status code(s): 200 + response == { + "collaborators": [ + { + "isOwner": bool, + "objectId": "str", + "tenantId": "str", + "userIdentifier": "str" + } + ] + } + # response body for status code(s): 422 + response == { + "loc": [ + {} + ], + "msg": "str", + "type": "str" + } + """ + error_map: MutableMapping = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) + + _headers = kwargs.pop("headers", {}) or {} + _params = kwargs.pop("params", {}) or {} + + cls: ClsType[Optional[JSON]] = kwargs.pop("cls", None) + + _request = build_collaboration_collaborators_get_request( + collaboration_id=collaboration_id, + api_version=self._config.api_version, + headers=_headers, + params=_params, + ) + _request.url = self._client.format_url(_request.url) + + _stream = False + pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + + response = pipeline_response.http_response + + if response.status_code not in [200, 404, 422]: + map_error(status_code=response.status_code, response=response, error_map=error_map) + raise HttpResponseError(response=response) + + deserialized = None + if response.content: + deserialized = response.json() + else: + deserialized = None + + if cls: + return cls(pipeline_response, deserialized, {}) # type: ignore + + return deserialized # type: ignore + @distributed_trace_async async def invitations_get( self, @@ -2999,6 +3079,85 @@ async def analytics_queries_document_id_runs_get( return cast(Union[list[JSON], JSON], deserialized) # type: ignore + @distributed_trace_async + async def analytics_queries_document_id_runs_run_id_cancel_post( # pylint: disable=name-too-long + self, collaboration_id: str, document_id: str, run_id: str, **kwargs: Any + ) -> JSON: + """Cancel query run by run id. + + Cancel query run by run id. + + :param collaboration_id: Required. + :type collaboration_id: str + :param document_id: Required. + :type document_id: str + :param run_id: Required. + :type run_id: str + :return: JSON object + :rtype: JSON + :raises ~azure.core.exceptions.HttpResponseError: + + Example: + .. code-block:: python + + # response body for status code(s): 200 + response == { + "id": "str", + "status": "str" + } + # response body for status code(s): 422 + response == { + "loc": [ + {} + ], + "msg": "str", + "type": "str" + } + """ + error_map: MutableMapping = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) + + _headers = kwargs.pop("headers", {}) or {} + _params = kwargs.pop("params", {}) or {} + + cls: ClsType[JSON] = kwargs.pop("cls", None) + + _request = build_collaboration_analytics_queries_document_id_runs_run_id_cancel_post_request( + collaboration_id=collaboration_id, + document_id=document_id, + run_id=run_id, + api_version=self._config.api_version, + headers=_headers, + params=_params, + ) + _request.url = self._client.format_url(_request.url) + + _stream = False + pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + + response = pipeline_response.http_response + + if response.status_code not in [200, 422]: + map_error(status_code=response.status_code, response=response, error_map=error_map) + raise HttpResponseError(response=response) + + if response.content: + deserialized = response.json() + else: + deserialized = None + + if cls: + return cls(pipeline_response, cast(JSON, deserialized), {}) # type: ignore + + return cast(JSON, deserialized) # type: ignore + @distributed_trace_async async def analytics_datasets_document_id_queries_get( # pylint: disable=name-too-long self, collaboration_id: str, document_id: str, **kwargs: Any diff --git a/src/managedcleanroom/azext_managedcleanroom/analytics_frontend_api/operations/_operations.py b/src/managedcleanroom/azext_managedcleanroom/analytics_frontend_api/operations/_operations.py index 11c357f6ebd..f3651cff2ac 100644 --- a/src/managedcleanroom/azext_managedcleanroom/analytics_frontend_api/operations/_operations.py +++ b/src/managedcleanroom/azext_managedcleanroom/analytics_frontend_api/operations/_operations.py @@ -315,6 +315,32 @@ def build_collaboration_oidc_keys_get_request( # pylint: disable=name-too-long **kwargs) +def build_collaboration_collaborators_get_request( # pylint: disable=name-too-long + collaboration_id: str, **kwargs: Any +) -> HttpRequest: + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) + + api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2026-03-01-preview")) + accept = _headers.pop("Accept", "application/json") + + # Construct URL + _url = "/collaborations/{collaborationId}/collaborators" + path_format_arguments = { + "collaborationId": _SERIALIZER.url("collaboration_id", collaboration_id, "str"), + } + + _url: str = _url.format(**path_format_arguments) # type: ignore + + # Construct parameters + _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") + + # Construct headers + _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") + + return HttpRequest(method="GET", url=_url, params=_params, headers=_headers, **kwargs) + + def build_collaboration_invitations_get_request( # pylint: disable=name-too-long collaboration_id: str, *, pending_only: bool = False, **kwargs: Any ) -> HttpRequest: @@ -882,6 +908,34 @@ def build_collaboration_analytics_queries_document_id_runs_get_request( # pylin **kwargs) +def build_collaboration_analytics_queries_document_id_runs_run_id_cancel_post_request( # pylint: disable=name-too-long + collaboration_id: str, document_id: str, run_id: str, **kwargs: Any +) -> HttpRequest: + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) + + api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2026-03-01-preview")) + accept = _headers.pop("Accept", "application/json") + + # Construct URL + _url = "/collaborations/{collaborationId}/analytics/queries/{documentId}/runs/{runId}/cancel" + path_format_arguments = { + "collaborationId": _SERIALIZER.url("collaboration_id", collaboration_id, "str"), + "documentId": _SERIALIZER.url("document_id", document_id, "str"), + "runId": _SERIALIZER.url("run_id", run_id, "str"), + } + + _url: str = _url.format(**path_format_arguments) # type: ignore + + # Construct parameters + _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") + + # Construct headers + _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") + + return HttpRequest(method="POST", url=_url, params=_params, headers=_headers, **kwargs) + + def build_collaboration_analytics_datasets_document_id_queries_get_request( # pylint: disable=name-too-long collaboration_id: str, document_id: str, **kwargs: Any ) -> HttpRequest: @@ -1854,6 +1908,84 @@ def oidc_keys_get(self, collaboration_id: str, **kwargs: Any) -> JSON: return cast(JSON, deserialized) # type: ignore + @distributed_trace + def collaborators_get(self, collaboration_id: str, **kwargs: Any) -> Optional[JSON]: + """List all collaborators in a collaboration. + + List all collaborators in a collaboration. + + :param collaboration_id: Required. + :type collaboration_id: str + :return: JSON object or None + :rtype: JSON or None + :raises ~azure.core.exceptions.HttpResponseError: + + Example: + .. code-block:: python + + # response body for status code(s): 200 + response == { + "collaborators": [ + { + "isOwner": bool, + "objectId": "str", + "tenantId": "str", + "userIdentifier": "str" + } + ] + } + # response body for status code(s): 422 + response == { + "loc": [ + {} + ], + "msg": "str", + "type": "str" + } + """ + error_map: MutableMapping = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) + + _headers = kwargs.pop("headers", {}) or {} + _params = kwargs.pop("params", {}) or {} + + cls: ClsType[Optional[JSON]] = kwargs.pop("cls", None) + + _request = build_collaboration_collaborators_get_request( + collaboration_id=collaboration_id, + api_version=self._config.api_version, + headers=_headers, + params=_params, + ) + _request.url = self._client.format_url(_request.url) + + _stream = False + pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + + response = pipeline_response.http_response + + if response.status_code not in [200, 404, 422]: + map_error(status_code=response.status_code, response=response, error_map=error_map) + raise HttpResponseError(response=response) + + deserialized = None + if response.content: + deserialized = response.json() + else: + deserialized = None + + if cls: + return cls(pipeline_response, deserialized, {}) # type: ignore + + return deserialized # type: ignore + @distributed_trace def invitations_get( self, @@ -3945,6 +4077,85 @@ def analytics_queries_document_id_runs_get( return cast(Union[list[JSON], JSON], deserialized) # type: ignore + @distributed_trace + def analytics_queries_document_id_runs_run_id_cancel_post( # pylint: disable=name-too-long + self, collaboration_id: str, document_id: str, run_id: str, **kwargs: Any + ) -> JSON: + """Cancel query run by run id. + + Cancel query run by run id. + + :param collaboration_id: Required. + :type collaboration_id: str + :param document_id: Required. + :type document_id: str + :param run_id: Required. + :type run_id: str + :return: JSON object + :rtype: JSON + :raises ~azure.core.exceptions.HttpResponseError: + + Example: + .. code-block:: python + + # response body for status code(s): 200 + response == { + "id": "str", + "status": "str" + } + # response body for status code(s): 422 + response == { + "loc": [ + {} + ], + "msg": "str", + "type": "str" + } + """ + error_map: MutableMapping = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) + + _headers = kwargs.pop("headers", {}) or {} + _params = kwargs.pop("params", {}) or {} + + cls: ClsType[JSON] = kwargs.pop("cls", None) + + _request = build_collaboration_analytics_queries_document_id_runs_run_id_cancel_post_request( + collaboration_id=collaboration_id, + document_id=document_id, + run_id=run_id, + api_version=self._config.api_version, + headers=_headers, + params=_params, + ) + _request.url = self._client.format_url(_request.url) + + _stream = False + pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + + response = pipeline_response.http_response + + if response.status_code not in [200, 422]: + map_error(status_code=response.status_code, response=response, error_map=error_map) + raise HttpResponseError(response=response) + + if response.content: + deserialized = response.json() + else: + deserialized = None + + if cls: + return cls(pipeline_response, cast(JSON, deserialized), {}) # type: ignore + + return cast(JSON, deserialized) # type: ignore + @distributed_trace def analytics_datasets_document_id_queries_get( # pylint: disable=name-too-long self, collaboration_id: str, document_id: str, **kwargs: Any diff --git a/src/managedcleanroom/azext_managedcleanroom/tests/latest/test_frontend_collaboration.py b/src/managedcleanroom/azext_managedcleanroom/tests/latest/test_frontend_collaboration.py index 5cbae64dc84..06035c1a503 100644 --- a/src/managedcleanroom/azext_managedcleanroom/tests/latest/test_frontend_collaboration.py +++ b/src/managedcleanroom/azext_managedcleanroom/tests/latest/test_frontend_collaboration.py @@ -19,6 +19,7 @@ frontend_collaboration_oidc_set_issuer_url, frontend_collaboration_oidc_keys_show, frontend_collaboration_report_show, + frontend_collaboration_collaborators_list, frontend_collaboration_dataset_queries_list ) from azext_managedcleanroom.tests.latest.test_utils import ( @@ -33,6 +34,25 @@ class TestFrontendCollaboration(unittest.TestCase): # List Collaborations Tests + @patch('azext_managedcleanroom._frontend_custom.get_frontend_client') + def test_list_collaborators_success(self, mock_get_client): + """Test listing collaborators in a collaboration""" + mock_client = Mock() + mock_client.collaboration.collaborators_get.return_value = { + "collaborators": [ + {"userIdentifier": "user1@contoso.com", "isOwner": True}, + {"userIdentifier": "user2@contoso.com", "isOwner": False} + ] + } + mock_get_client.return_value = mock_client + + result = frontend_collaboration_collaborators_list( + cmd=Mock(), collaboration_id="collab-1" + ) + + self.assertEqual(len(result["collaborators"]), 2) + mock_client.collaboration.collaborators_get.assert_called_once_with("collab-1") + @patch('azext_managedcleanroom._frontend_custom.get_frontend_client') def test_list_collaborations_success(self, mock_get_client): """Test listing collaborations returns correct data""" diff --git a/src/managedcleanroom/azext_managedcleanroom/tests/latest/test_frontend_query.py b/src/managedcleanroom/azext_managedcleanroom/tests/latest/test_frontend_query.py index e7e320b9e4c..2553c19a402 100644 --- a/src/managedcleanroom/azext_managedcleanroom/tests/latest/test_frontend_query.py +++ b/src/managedcleanroom/azext_managedcleanroom/tests/latest/test_frontend_query.py @@ -18,6 +18,7 @@ frontend_collaboration_query_run, frontend_collaboration_query_vote, frontend_collaboration_query_runhistory_list, + frontend_collaboration_query_runhistory_cancel, frontend_collaboration_query_runresult_show ) @@ -27,6 +28,24 @@ class TestFrontendQuery(unittest.TestCase): # Query CRUD Tests + @patch('azext_managedcleanroom._frontend_custom.get_frontend_client') + def test_runhistory_cancel_success(self, mock_get_client): + """Test cancelling a query run""" + mock_client = Mock() + mock_client.collaboration.analytics_queries_document_id_runs_run_id_cancel_post.return_value = { + "id": "run-1", "status": "cancelled" + } + mock_get_client.return_value = mock_client + + result = frontend_collaboration_query_runhistory_cancel( + cmd=Mock(), collaboration_id="collab-1", document_id="query-1", run_id="run-1" + ) + + self.assertEqual(result["status"], "cancelled") + mock_client.collaboration.analytics_queries_document_id_runs_run_id_cancel_post.assert_called_once_with( + "collab-1", "query-1", "run-1" + ) + @patch('azext_managedcleanroom._frontend_custom.get_frontend_client') def test_list_queries_success(self, mock_get_client): """Test listing queries for a collaboration""" From 7a365bcbd0be559c4303112348f7edae036b970a Mon Sep 17 00:00:00 2001 From: Yash Vohra Date: Mon, 28 Sep 2026 10:20:15 +0530 Subject: [PATCH 5/8] managedcleanroom: add example for collaboration update --- .../aaz/latest/managedcleanroom/collaboration/_update.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/managedcleanroom/azext_managedcleanroom/aaz/latest/managedcleanroom/collaboration/_update.py b/src/managedcleanroom/azext_managedcleanroom/aaz/latest/managedcleanroom/collaboration/_update.py index 9c8b2c728f8..efdcf81a499 100644 --- a/src/managedcleanroom/azext_managedcleanroom/aaz/latest/managedcleanroom/collaboration/_update.py +++ b/src/managedcleanroom/azext_managedcleanroom/aaz/latest/managedcleanroom/collaboration/_update.py @@ -16,6 +16,9 @@ ) class Update(AAZCommand): """Update tags on a collaboration. + + :example: Update collaboration tags + az managedcleanroom collaboration update --resource-group testrg --collaboration-name ContosoCollaboration --tags env=test """ _aaz_info = { From 845aa6d53c3eb388075a2e5f692f6805dc9606a4 Mon Sep 17 00:00:00 2001 From: Yash Vohra Date: Mon, 28 Sep 2026 12:15:51 +0530 Subject: [PATCH 6/8] managedcleanroom: bump version to 1.0.0b9 (resolve collision with main b7/b8) --- src/managedcleanroom/HISTORY.rst | 2 +- src/managedcleanroom/setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/managedcleanroom/HISTORY.rst b/src/managedcleanroom/HISTORY.rst index 037761a7501..7e286239489 100644 --- a/src/managedcleanroom/HISTORY.rst +++ b/src/managedcleanroom/HISTORY.rst @@ -3,7 +3,7 @@ Release History =============== -1.0.0b7 +1.0.0b9 +++++++ * Update commands to reflect new API version 2026-09-30-preview * Added: ``--target-config`` alias for ``az managedcleanroom collaboration create --target-resource-configuration`` diff --git a/src/managedcleanroom/setup.py b/src/managedcleanroom/setup.py index 3d293e15d72..3d2f7acc318 100644 --- a/src/managedcleanroom/setup.py +++ b/src/managedcleanroom/setup.py @@ -10,7 +10,7 @@ # HISTORY.rst entry. -VERSION = '1.0.0b8' +VERSION = '1.0.0b9' # The full list of classifiers is available at # https://pypi.python.org/pypi?%3Aaction=list_classifiers From 1005500e7d34904e24716c3d8f030e24f9e1eb82 Mon Sep 17 00:00:00 2001 From: Yash Vohra Date: Tue, 29 Sep 2026 15:49:46 +0530 Subject: [PATCH 7/8] managedcleanroom: restore PrivateEndpointUtil hook in collaboration create/update; rename query runhistory cancel -> query cancel-run --- src/managedcleanroom/HISTORY.rst | 2 +- .../azext_managedcleanroom/_frontend_commands.py | 3 +-- .../azext_managedcleanroom/_frontend_custom.py | 2 +- src/managedcleanroom/azext_managedcleanroom/_help.py | 4 ++-- src/managedcleanroom/azext_managedcleanroom/_params.py | 3 ++- .../aaz/latest/managedcleanroom/collaboration/_create.py | 3 ++- .../aaz/latest/managedcleanroom/collaboration/_update.py | 3 ++- 7 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/managedcleanroom/HISTORY.rst b/src/managedcleanroom/HISTORY.rst index 7e286239489..330760bb4c1 100644 --- a/src/managedcleanroom/HISTORY.rst +++ b/src/managedcleanroom/HISTORY.rst @@ -9,7 +9,7 @@ Release History * Added: ``--target-config`` alias for ``az managedcleanroom collaboration create --target-resource-configuration`` * Added: ``az managedcleanroom consortium-view contract wait`` * Added: ``az managedcleanroom frontend collaborator list`` to list collaborators in a collaboration -* Added: ``az managedcleanroom frontend analytics query runhistory cancel`` to cancel a query run +* Added: ``az managedcleanroom frontend analytics query cancel-run`` to cancel a query run 1.0.0b6 +++++++ diff --git a/src/managedcleanroom/azext_managedcleanroom/_frontend_commands.py b/src/managedcleanroom/azext_managedcleanroom/_frontend_commands.py index fcb5737833a..5bbfbd1bf39 100644 --- a/src/managedcleanroom/azext_managedcleanroom/_frontend_commands.py +++ b/src/managedcleanroom/azext_managedcleanroom/_frontend_commands.py @@ -77,6 +77,7 @@ def load_frontend_command_table(loader, _): g.custom_show_command('show', 'frontend_collaboration_query_show') g.custom_command('publish', 'frontend_collaboration_query_publish') g.custom_command('run', 'frontend_collaboration_query_run') + g.custom_command('cancel-run', 'frontend_collaboration_query_cancel_run') g.custom_command('vote', 'frontend_collaboration_query_vote') # Query run history commands @@ -85,8 +86,6 @@ def load_frontend_command_table(loader, _): custom_command_type=frontend_custom) as g: g.custom_command( 'list', 'frontend_collaboration_query_runhistory_list') - g.custom_command( - 'cancel', 'frontend_collaboration_query_runhistory_cancel') # Query run result commands with loader.command_group( diff --git a/src/managedcleanroom/azext_managedcleanroom/_frontend_custom.py b/src/managedcleanroom/azext_managedcleanroom/_frontend_custom.py index b0cb1a0634b..da7de804b09 100644 --- a/src/managedcleanroom/azext_managedcleanroom/_frontend_custom.py +++ b/src/managedcleanroom/azext_managedcleanroom/_frontend_custom.py @@ -862,7 +862,7 @@ def frontend_collaboration_query_runhistory_list( ) -def frontend_collaboration_query_runhistory_cancel( +def frontend_collaboration_query_cancel_run( cmd, collaboration_id, document_id, run_id, api_version=None ): """Cancel a query run diff --git a/src/managedcleanroom/azext_managedcleanroom/_help.py b/src/managedcleanroom/azext_managedcleanroom/_help.py index da2fe768743..6319568078f 100644 --- a/src/managedcleanroom/azext_managedcleanroom/_help.py +++ b/src/managedcleanroom/azext_managedcleanroom/_help.py @@ -723,12 +723,12 @@ text: az managedcleanroom frontend analytics query runhistory list -c --document-id """ -helps['managedcleanroom frontend analytics query runhistory cancel'] = """ +helps['managedcleanroom frontend analytics query cancel-run'] = """ type: command short-summary: Cancel a query run examples: - name: Cancel a query run - text: az managedcleanroom frontend analytics query runhistory cancel -c --document-id --run-id + text: az managedcleanroom frontend analytics query cancel-run -c --document-id --run-id """ helps['managedcleanroom frontend analytics query runresult'] = """ diff --git a/src/managedcleanroom/azext_managedcleanroom/_params.py b/src/managedcleanroom/azext_managedcleanroom/_params.py index acfc51da160..b7264db1446 100644 --- a/src/managedcleanroom/azext_managedcleanroom/_params.py +++ b/src/managedcleanroom/azext_managedcleanroom/_params.py @@ -310,7 +310,8 @@ def load_arguments(self, _): # pylint: disable=unused-argument with self.argument_context('managedcleanroom frontend analytics query runhistory list') as c: c.argument('document_id', document_id_type) - with self.argument_context('managedcleanroom frontend analytics query runhistory cancel') as c: + with self.argument_context('managedcleanroom frontend analytics query cancel-run') as c: + c.argument('collaboration_id', collaboration_id_type) c.argument('document_id', document_id_type) c.argument('run_id', run_id_type) diff --git a/src/managedcleanroom/azext_managedcleanroom/aaz/latest/managedcleanroom/collaboration/_create.py b/src/managedcleanroom/azext_managedcleanroom/aaz/latest/managedcleanroom/collaboration/_create.py index ba5720d43a9..a6c8eac256a 100644 --- a/src/managedcleanroom/azext_managedcleanroom/aaz/latest/managedcleanroom/collaboration/_create.py +++ b/src/managedcleanroom/azext_managedcleanroom/aaz/latest/managedcleanroom/collaboration/_create.py @@ -9,6 +9,7 @@ # flake8: noqa from azure.cli.core.aaz import * +from ..private_endpoint_util import PrivateEndpointUtil @register_command( @@ -208,7 +209,7 @@ def __call__(self, *args, **kwargs): @property def url(self): return self.client.format_url( - "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.CleanRoom/collaborations/{collaborationName}", + f"/subscriptions/{{subscriptionId}}/resourceGroups/{{resourceGroupName}}/providers/{PrivateEndpointUtil.get_configured_namespace()}/collaborations/{{collaborationName}}", **self.url_parameters ) diff --git a/src/managedcleanroom/azext_managedcleanroom/aaz/latest/managedcleanroom/collaboration/_update.py b/src/managedcleanroom/azext_managedcleanroom/aaz/latest/managedcleanroom/collaboration/_update.py index efdcf81a499..c40d247f93c 100644 --- a/src/managedcleanroom/azext_managedcleanroom/aaz/latest/managedcleanroom/collaboration/_update.py +++ b/src/managedcleanroom/azext_managedcleanroom/aaz/latest/managedcleanroom/collaboration/_update.py @@ -9,6 +9,7 @@ # flake8: noqa from azure.cli.core.aaz import * +from ..private_endpoint_util import PrivateEndpointUtil @register_command( @@ -118,7 +119,7 @@ def __call__(self, *args, **kwargs): @property def url(self): return self.client.format_url( - "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.CleanRoom/collaborations/{collaborationName}", + f"/subscriptions/{{subscriptionId}}/resourceGroups/{{resourceGroupName}}/providers/{PrivateEndpointUtil.get_configured_namespace()}/collaborations/{{collaborationName}}", **self.url_parameters ) From 5adaaf7a1326cfb2ffb1257f4b66f6bbcff30754 Mon Sep 17 00:00:00 2001 From: Yash Vohra Date: Tue, 29 Sep 2026 18:02:48 +0530 Subject: [PATCH 8/8] managedcleanroom: update unit test for query cancel-run rename --- .../tests/latest/test_frontend_query.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/managedcleanroom/azext_managedcleanroom/tests/latest/test_frontend_query.py b/src/managedcleanroom/azext_managedcleanroom/tests/latest/test_frontend_query.py index 2553c19a402..1be94809e71 100644 --- a/src/managedcleanroom/azext_managedcleanroom/tests/latest/test_frontend_query.py +++ b/src/managedcleanroom/azext_managedcleanroom/tests/latest/test_frontend_query.py @@ -18,7 +18,7 @@ frontend_collaboration_query_run, frontend_collaboration_query_vote, frontend_collaboration_query_runhistory_list, - frontend_collaboration_query_runhistory_cancel, + frontend_collaboration_query_cancel_run, frontend_collaboration_query_runresult_show ) @@ -29,7 +29,7 @@ class TestFrontendQuery(unittest.TestCase): # Query CRUD Tests @patch('azext_managedcleanroom._frontend_custom.get_frontend_client') - def test_runhistory_cancel_success(self, mock_get_client): + def test_cancel_run_success(self, mock_get_client): """Test cancelling a query run""" mock_client = Mock() mock_client.collaboration.analytics_queries_document_id_runs_run_id_cancel_post.return_value = { @@ -37,7 +37,7 @@ def test_runhistory_cancel_success(self, mock_get_client): } mock_get_client.return_value = mock_client - result = frontend_collaboration_query_runhistory_cancel( + result = frontend_collaboration_query_cancel_run( cmd=Mock(), collaboration_id="collab-1", document_id="query-1", run_id="run-1" )