diff --git a/src/azure-cli-core/azure/cli/core/_profile.py b/src/azure-cli-core/azure/cli/core/_profile.py index 12b57c3cce0..a14692e0d26 100644 --- a/src/azure-cli-core/azure/cli/core/_profile.py +++ b/src/azure-cli-core/azure/cli/core/_profile.py @@ -534,7 +534,10 @@ def _try_parse_msi_account_name(account): return parts[0], (None if len(parts) <= 1 else parts[1]) return None, None - def get_login_credentials(self, resource=None, subscription_id=None, aux_subscriptions=None): + def get_login_credentials(self, resource=None, subscription_id=None, aux_subscriptions=None, aux_tenants=None): + if aux_tenants and aux_subscriptions: + raise CLIError("Please specify only one of aux_subscriptions and aux_tenants, not both") + account = self.get_subscription(subscription_id) user_type = account[_USER_ENTITY][_USER_TYPE] username_or_sp_id = account[_USER_ENTITY][_USER_NAME] @@ -543,12 +546,14 @@ def get_login_credentials(self, resource=None, subscription_id=None, aux_subscri identity_type, identity_id = Profile._try_parse_msi_account_name(account) external_tenants_info = [] - ext_subs = [aux_sub for aux_sub in (aux_subscriptions or []) if aux_sub != subscription_id] - for ext_sub in ext_subs: - sub = self.get_subscription(ext_sub) - if sub[_TENANT_ID] != account[_TENANT_ID]: - # external_tenants_info.append((sub[_USER_ENTITY][_USER_NAME], sub[_TENANT_ID])) - external_tenants_info.append(sub) + if aux_tenants: + external_tenants_info = [tenant for tenant in aux_tenants if tenant != account[_TENANT_ID]] + if aux_subscriptions: + ext_subs = [aux_sub for aux_sub in aux_subscriptions if aux_sub != subscription_id] + for ext_sub in ext_subs: + sub = self.get_subscription(ext_sub) + if sub[_TENANT_ID] != account[_TENANT_ID]: + external_tenants_info.append(sub[_TENANT_ID]) if identity_type is None: def _retrieve_token(): @@ -564,13 +569,13 @@ def _retrieve_token(): def _retrieve_tokens_from_external_tenants(): external_tokens = [] - for s in external_tenants_info: + for sub_tenant_id in external_tenants_info: if user_type == _USER: external_tokens.append(self._creds_cache.retrieve_token_for_user( - username_or_sp_id, s[_TENANT_ID], resource)) + username_or_sp_id, sub_tenant_id, resource)) else: external_tokens.append(self._creds_cache.retrieve_token_for_service_principal( - username_or_sp_id, resource, s[_TENANT_ID], resource)) + username_or_sp_id, resource, sub_tenant_id, resource)) return external_tokens from azure.cli.core.adal_authentication import AdalAuthentication diff --git a/src/azure-cli-core/azure/cli/core/commands/client_factory.py b/src/azure-cli-core/azure/cli/core/commands/client_factory.py index 04893c7303c..6bec4216ada 100644 --- a/src/azure-cli-core/azure/cli/core/commands/client_factory.py +++ b/src/azure-cli-core/azure/cli/core/commands/client_factory.py @@ -44,7 +44,7 @@ def resolve_client_arg_name(operation, kwargs): def get_mgmt_service_client(cli_ctx, client_or_resource_type, subscription_id=None, api_version=None, - aux_subscriptions=None, **kwargs): + aux_subscriptions=None, aux_tenants=None, **kwargs): """ :params subscription_id: the current account's subscription :param aux_subscriptions: mainly for cross tenant scenarios, say vnet peering. @@ -66,6 +66,7 @@ def get_mgmt_service_client(cli_ctx, client_or_resource_type, subscription_id=No client, _ = _get_mgmt_service_client(cli_ctx, client_type, subscription_id=subscription_id, api_version=api_version, sdk_profile=sdk_profile, aux_subscriptions=aux_subscriptions, + aux_tenants=aux_tenants, **kwargs) return client @@ -118,13 +119,15 @@ def _get_mgmt_service_client(cli_ctx, resource=None, sdk_profile=None, aux_subscriptions=None, + aux_tenants=None, **kwargs): from azure.cli.core._profile import Profile logger.debug('Getting management service client client_type=%s', client_type.__name__) resource = resource or cli_ctx.cloud.endpoints.active_directory_resource_id profile = Profile(cli_ctx=cli_ctx) cred, subscription_id, _ = profile.get_login_credentials(subscription_id=subscription_id, resource=resource, - aux_subscriptions=aux_subscriptions) + aux_subscriptions=aux_subscriptions, + aux_tenants=aux_tenants) client_kwargs = {} if base_url_bound: diff --git a/src/azure-cli-core/azure/cli/core/tests/test_profile.py b/src/azure-cli-core/azure/cli/core/tests/test_profile.py index 0dba7cd77d6..f2ad84f6e24 100644 --- a/src/azure-cli-core/azure/cli/core/tests/test_profile.py +++ b/src/azure-cli-core/azure/cli/core/tests/test_profile.py @@ -590,6 +590,60 @@ def test_get_login_credentials_aux_subscriptions(self, mock_get_token, mock_read self.assertEqual(mock_get_token.call_count, 2) + @mock.patch('azure.cli.core._profile._load_tokens_from_file', autospec=True) + @mock.patch('azure.cli.core._profile.CredsCache.retrieve_token_for_user', autospec=True) + def test_get_login_credentials_aux_tenants(self, mock_get_token, mock_read_cred_file): + cli = DummyCli() + raw_token2 = 'some...secrets2' + token_entry2 = { + "resource": "https://management.core.windows.net/", + "tokenType": "Bearer", + "_authority": "https://login.microsoftonline.com/common", + "accessToken": raw_token2, + } + some_token_type = 'Bearer' + mock_read_cred_file.return_value = [TestProfile.token_entry1, token_entry2] + mock_get_token.side_effect = [(some_token_type, TestProfile.raw_token1), (some_token_type, raw_token2)] + # setup + storage_mock = {'subscriptions': None} + profile = Profile(cli_ctx=cli, storage=storage_mock, use_global_creds_cache=False, async_persist=False) + test_subscription_id = '12345678-1bf0-4dda-aec3-cb9272f09590' + test_subscription_id2 = '12345678-1bf0-4dda-aec3-cb9272f09591' + test_tenant_id = '12345678-38d6-4fb2-bad9-b7b93a3e1234' + test_tenant_id2 = '12345678-38d6-4fb2-bad9-b7b93a3e4321' + test_subscription = SubscriptionStub('/subscriptions/{}'.format(test_subscription_id), + 'MSI-DEV-INC', self.state1, test_tenant_id) + test_subscription2 = SubscriptionStub('/subscriptions/{}'.format(test_subscription_id2), + 'MSI-DEV-INC2', self.state1, test_tenant_id2) + consolidated = profile._normalize_properties(self.user1, + [test_subscription, test_subscription2], + False) + profile._set_subscriptions(consolidated) + # test only input aux_tenants + cred, subscription_id, _ = profile.get_login_credentials(subscription_id=test_subscription_id, + aux_tenants=[test_tenant_id2]) + + # verify + self.assertEqual(subscription_id, test_subscription_id) + + # verify the cred._tokenRetriever is a working lambda + token_type, token = cred._token_retriever() + self.assertEqual(token, self.raw_token1) + self.assertEqual(some_token_type, token_type) + + token2 = cred._external_tenant_token_retriever() + self.assertEqual(len(token2), 1) + self.assertEqual(token2[0][1], raw_token2) + + self.assertEqual(mock_get_token.call_count, 2) + + # test input aux_tenants and aux_subscriptions + with self.assertRaisesRegexp(CLIError, + "Please specify only one of aux_subscriptions and aux_tenants, not both"): + cred, subscription_id, _ = profile.get_login_credentials(subscription_id=test_subscription_id, + aux_subscriptions=[test_subscription_id2], + aux_tenants=[test_tenant_id2]) + @mock.patch('azure.cli.core._profile._load_tokens_from_file', autospec=True) @mock.patch('msrestazure.azure_active_directory.MSIAuthentication', autospec=True) def test_get_login_credentials_msi_system_assigned(self, mock_msi_auth, mock_read_cred_file): diff --git a/src/azure-cli/azure/cli/command_modules/resource/_params.py b/src/azure-cli/azure/cli/command_modules/resource/_params.py index 0a4a27703a1..051c066775f 100644 --- a/src/azure-cli/azure/cli/command_modules/resource/_params.py +++ b/src/azure-cli/azure/cli/command_modules/resource/_params.py @@ -191,8 +191,11 @@ def load_arguments(self, _): with self.argument_context('group deployment create') as c: c.argument('deployment_name', arg_type=deployment_create_name_type) c.argument('handle_extended_json_format', arg_type=extended_json_format_type) - c.argument('aux_subscriptions', nargs='*', options_list=['--aux-subs'], - help='Auxiliary subscriptions which will be used during deployment across tenants.') + c.argument('aux_subscriptions', nargs='+', options_list=['--aux-subs'], + help='Auxiliary subscriptions which will be used during deployment across tenants.', + deprecate_info=c.deprecate(target='--aux-subs', redirect='--aux-tenants')) + c.argument('aux_tenants', nargs='+', options_list=['--aux-tenants'], + help='Auxiliary tenants which will be used during deployment across tenants.') with self.argument_context('group deployment validate') as c: c.argument('handle_extended_json_format', arg_type=extended_json_format_type) @@ -238,8 +241,11 @@ def load_arguments(self, _): with self.argument_context('deployment group create') as c: c.argument('deployment_name', arg_type=deployment_create_name_type) c.argument('handle_extended_json_format', arg_type=extended_json_format_type) - c.argument('aux_subscriptions', nargs='*', options_list=['--aux-subs'], - help='Auxiliary subscriptions which will be used during deployment across tenants.') + c.argument('aux_subscriptions', nargs='+', options_list=['--aux-subs'], + help='Auxiliary subscriptions which will be used during deployment across tenants.', + deprecate_info=c.deprecate(target='--aux-subs', redirect='--aux-tenants')) + c.argument('aux_tenants', nargs='+', options_list=['--aux-tenants'], + help='Auxiliary tenants which will be used during deployment across tenants.') with self.argument_context('deployment group validate') as c: c.argument('deployment_name', arg_type=deployment_create_name_type) diff --git a/src/azure-cli/azure/cli/command_modules/resource/custom.py b/src/azure-cli/azure/cli/command_modules/resource/custom.py index 1cf336a0eee..3d22a2da227 100644 --- a/src/azure-cli/azure/cli/command_modules/resource/custom.py +++ b/src/azure-cli/azure/cli/command_modules/resource/custom.py @@ -255,7 +255,7 @@ def _urlretrieve(url): def _deploy_arm_template_core(cli_ctx, resource_group_name, template_file=None, template_uri=None, deployment_name=None, parameters=None, mode=None, rollback_on_error=None, validate_only=False, - no_wait=False, aux_subscriptions=None): + no_wait=False, aux_subscriptions=None, aux_tenants=None): DeploymentProperties, TemplateLink, OnErrorDeployment = get_sdk(cli_ctx, ResourceType.MGMT_RESOURCE_RESOURCES, 'DeploymentProperties', 'TemplateLink', 'OnErrorDeployment', mod='models') @@ -287,7 +287,8 @@ def _deploy_arm_template_core(cli_ctx, resource_group_name, properties = DeploymentProperties(template=template, template_link=template_link, parameters=parameters, mode=mode, on_error_deployment=on_error_deployment) - smc = get_mgmt_service_client(cli_ctx, ResourceType.MGMT_RESOURCE_RESOURCES, aux_subscriptions=aux_subscriptions) + smc = get_mgmt_service_client(cli_ctx, ResourceType.MGMT_RESOURCE_RESOURCES, aux_subscriptions=aux_subscriptions, + aux_tenants=aux_tenants) validation_result = smc.deployments.validate(resource_group_name=resource_group_name, deployment_name=deployment_name, properties=properties) @@ -312,7 +313,7 @@ def _remove_comments_from_json(template): def _deploy_arm_template_core_unmodified(cli_ctx, resource_group_name, template_file=None, template_uri=None, deployment_name=None, parameters=None, mode=None, rollback_on_error=None, validate_only=False, no_wait=False, - aux_subscriptions=None): + aux_subscriptions=None, aux_tenants=None): DeploymentProperties, TemplateLink, OnErrorDeployment = get_sdk(cli_ctx, ResourceType.MGMT_RESOURCE_RESOURCES, 'DeploymentProperties', 'TemplateLink', 'OnErrorDeployment', mod='models') @@ -343,7 +344,8 @@ def _deploy_arm_template_core_unmodified(cli_ctx, resource_group_name, template_ properties = DeploymentProperties(template=template_content, template_link=template_link, parameters=parameters, mode=mode, on_error_deployment=on_error_deployment) - smc = get_mgmt_service_client(cli_ctx, ResourceType.MGMT_RESOURCE_RESOURCES, aux_subscriptions=aux_subscriptions) + smc = get_mgmt_service_client(cli_ctx, ResourceType.MGMT_RESOURCE_RESOURCES, aux_subscriptions=aux_subscriptions, + aux_tenants=aux_tenants) deployment_client = smc.deployments # This solves the multi-api for you @@ -473,14 +475,14 @@ def deploy_arm_template_at_resource_group(cmd, template_file=None, template_uri=None, parameters=None, deployment_name=None, mode=None, rollback_on_error=None, no_wait=False, handle_extended_json_format=False, - aux_subscriptions=None): + aux_subscriptions=None, aux_tenants=None): return _deploy_arm_template_at_resource_group(cli_ctx=cmd.cli_ctx, resource_group_name=resource_group_name, template_file=template_file, template_uri=template_uri, parameters=parameters, deployment_name=deployment_name, mode=mode, rollback_on_error=rollback_on_error, validate_only=False, no_wait=no_wait, handle_extended_json_format=handle_extended_json_format, - aux_subscriptions=aux_subscriptions) + aux_subscriptions=aux_subscriptions, aux_tenants=aux_tenants) def validate_arm_template_at_resource_group(cmd, @@ -502,7 +504,7 @@ def _deploy_arm_template_at_resource_group(cli_ctx, deployment_name=None, mode=None, rollback_on_error=None, validate_only=False, no_wait=False, handle_extended_json_format=False, - aux_subscriptions=None): + aux_subscriptions=None, aux_tenants=None): deployment_properties = None if handle_extended_json_format: deployment_properties = _prepare_deployment_properties_unmodified(cli_ctx=cli_ctx, template_file=template_file, @@ -515,7 +517,8 @@ def _deploy_arm_template_at_resource_group(cli_ctx, parameters=parameters, mode=mode, rollback_on_error=rollback_on_error) - mgmt_client = _get_deployment_management_client(cli_ctx, handle_extended_json_format=handle_extended_json_format, aux_subscriptions=aux_subscriptions) + mgmt_client = _get_deployment_management_client(cli_ctx, handle_extended_json_format=handle_extended_json_format, + aux_subscriptions=aux_subscriptions, aux_tenants=aux_tenants) validation_result = mgmt_client.validate(resource_group_name=resource_group_name, deployment_name=deployment_name, properties=deployment_properties) @@ -702,8 +705,10 @@ def _prepare_deployment_properties(cli_ctx, template_file=None, template_uri=Non return properties -def _get_deployment_management_client(cli_ctx, handle_extended_json_format=False, aux_subscriptions=None): - smc = get_mgmt_service_client(cli_ctx, ResourceType.MGMT_RESOURCE_RESOURCES, aux_subscriptions) +def _get_deployment_management_client(cli_ctx, handle_extended_json_format=False, + aux_subscriptions=None, aux_tenants=None): + smc = get_mgmt_service_client(cli_ctx, ResourceType.MGMT_RESOURCE_RESOURCES, aux_subscriptions=aux_subscriptions, + aux_tenants=aux_tenants) deployment_client = smc.deployments # This solves the multi-api for you if handle_extended_json_format: @@ -1230,18 +1235,18 @@ def delete_deployment_at_tenant_scope(cmd, deployment_name): def deploy_arm_template(cmd, resource_group_name, template_file=None, template_uri=None, deployment_name=None, parameters=None, mode=None, rollback_on_error=None, no_wait=False, - handle_extended_json_format=False, aux_subscriptions=None): + handle_extended_json_format=False, aux_subscriptions=None, aux_tenants=None): if handle_extended_json_format: return _deploy_arm_template_core_unmodified(cmd.cli_ctx, resource_group_name=resource_group_name, template_file=template_file, template_uri=template_uri, deployment_name=deployment_name, parameters=parameters, mode=mode, rollback_on_error=rollback_on_error, no_wait=no_wait, - aux_subscriptions=aux_subscriptions) + aux_subscriptions=aux_subscriptions, aux_tenants=aux_tenants) return _deploy_arm_template_core(cmd.cli_ctx, resource_group_name=resource_group_name, template_file=template_file, template_uri=template_uri, deployment_name=deployment_name, parameters=parameters, mode=mode, rollback_on_error=rollback_on_error, - no_wait=no_wait, aux_subscriptions=aux_subscriptions) + no_wait=no_wait, aux_subscriptions=aux_subscriptions, aux_tenants=aux_tenants) def validate_arm_template(cmd, resource_group_name, template_file=None, template_uri=None, diff --git a/src/azure-cli/azure/cli/command_modules/resource/tests/latest/test_resource.py b/src/azure-cli/azure/cli/command_modules/resource/tests/latest/test_resource.py index 992140d304b..a9ffde4cf43 100644 --- a/src/azure-cli/azure/cli/command_modules/resource/tests/latest/test_resource.py +++ b/src/azure-cli/azure/cli/command_modules/resource/tests/latest/test_resource.py @@ -1600,7 +1600,7 @@ def test_group_deployment_crossrg(self, resource_group, resource_group_cross): class CrossTenantDeploymentScenarioTest(LiveScenarioTest): @ResourceGroupPreparer(name_prefix='cli_test_cross_tenant_deploy', location='eastus') - def test_group_deployment_crosstenant(self, resource_group): + def test_group_deployment_cross_tenant(self, resource_group): # Prepare Network Interface self.kwargs.update({ 'vm_rg': resource_group, @@ -1626,8 +1626,9 @@ def test_group_deployment_crosstenant(self, resource_group): 'image': self.create_random_name('cli_crosstenantimage', 40), 'version': '1.1.2', 'captured': self.create_random_name('cli_crosstenantmanagedimage', 40), - 'aux_sub': '685ba005-af8d-4b04-8f16-a7bf38b2eb5a', + 'aux_sub': 'bead59b7-f469-4601-803a-790729c5213d', 'rg': self.create_random_name('cli_test_cross_tenant_rg', 40), + 'aux_tenant': '72f988bf-86f1-41af-91ab-2d7cd011db47' }) self.cmd('group create -g {rg} --location {location} --subscription {aux_sub}', checks=self.check('name', self.kwargs['rg'])) @@ -1657,11 +1658,15 @@ def test_group_deployment_crosstenant(self, resource_group): self.kwargs.update({ 'tf': os.path.join(curr_dir, 'crosstenant_vm_deploy.json').replace('\\', '\\\\'), 'dn': self.create_random_name('cli-crosstenantdeployment', 40), + 'dn1': self.create_random_name('cli-crosstenantdeployment1', 40), + 'dn2': self.create_random_name('cli-crosstenantdeployment2', 40), + 'dn3': self.create_random_name('cli-crosstenantdeployment3', 40) }) self.cmd('group deployment validate -g {vm_rg} --template-file "{tf}" --parameters SIG_ImageVersion_id={sig_id} NIC_id={nic_id}', checks=[ self.check('properties.provisioningState', 'Succeeded') ]) + self.cmd('group deployment create -g {vm_rg} -n {dn} --template-file "{tf}" --parameters SIG_ImageVersion_id={sig_id} NIC_id={nic_id} --aux-subs "{aux_sub}"', checks=[ self.check('properties.provisioningState', 'Succeeded'), self.check('resourceGroup', '{vm_rg}') @@ -1675,6 +1680,171 @@ def test_group_deployment_crosstenant(self, resource_group): self.check('resourceGroup', '{vm_rg}') ]) + self.cmd('group deployment create -g {vm_rg} -n {dn1} --template-file "{tf}" --parameters SIG_ImageVersion_id={sig_id} NIC_id={nic_id} --aux-tenants "{aux_tenant}"', checks=[ + self.check('properties.provisioningState', 'Succeeded'), + self.check('resourceGroup', '{vm_rg}') + ]) + self.cmd('group deployment list -g {vm_rg}', checks=[ + self.check('[0].name', '{dn1}'), + self.check('[0].resourceGroup', '{vm_rg}') + ]) + self.cmd('group deployment show -g {vm_rg} -n {dn1}', checks=[ + self.check('name', '{dn1}'), + self.check('resourceGroup', '{vm_rg}') + ]) + + self.cmd('group deployment create -g {vm_rg} -n {dn2} --template-file "{tf}" --parameters SIG_ImageVersion_id={sig_id} NIC_id={nic_id} --aux-subs "{aux_sub}" -j', checks=[ + self.check('properties.provisioningState', 'Succeeded'), + self.check('resourceGroup', '{vm_rg}') + ]) + self.cmd('group deployment list -g {vm_rg}', checks=[ + self.check('[0].name', '{dn2}'), + self.check('[0].resourceGroup', '{vm_rg}') + ]) + self.cmd('group deployment show -g {vm_rg} -n {dn2}', checks=[ + self.check('name', '{dn2}'), + self.check('resourceGroup', '{vm_rg}') + ]) + + self.cmd('group deployment create -g {vm_rg} -n {dn3} --template-file "{tf}" --parameters SIG_ImageVersion_id={sig_id} NIC_id={nic_id} --aux-tenants "{aux_tenant}" -j', checks=[ + self.check('properties.provisioningState', 'Succeeded'), + self.check('resourceGroup', '{vm_rg}') + ]) + self.cmd('group deployment list -g {vm_rg}', checks=[ + self.check('[0].name', '{dn3}'), + self.check('[0].resourceGroup', '{vm_rg}') + ]) + self.cmd('group deployment show -g {vm_rg} -n {dn3}', checks=[ + self.check('name', '{dn3}'), + self.check('resourceGroup', '{vm_rg}') + ]) + + with self.assertRaises(AssertionError): + self.cmd('group deployment create -g {vm_rg} -n {dn} --template-file "{tf}" --parameters SIG_ImageVersion_id={sig_id} NIC_id={nic_id} --aux-tenants "{aux_tenant}" --aux-subs "{aux_sub}"') + + @ResourceGroupPreparer(name_prefix='cli_test_deployment_group_cross_tenant', location='eastus') + def test_deployment_group_cross_tenant(self, resource_group): + # Prepare Network Interface + self.kwargs.update({ + 'vm_rg': resource_group, + 'vnet': 'clivmVNET', + 'subnet': 'clivmSubnet', + 'nsg': 'clivmNSG', + 'ip': 'clivmPublicIp', + 'nic': 'clivmVMNic' + }) + self.cmd('network vnet create -n {vnet} -g {vm_rg} --subnet-name {subnet}') + self.cmd('network nsg create -n {nsg} -g {vm_rg}') + self.cmd('network public-ip create -n {ip} -g {vm_rg} --allocation-method Dynamic') + res = self.cmd('network nic create -n {nic} -g {vm_rg} --subnet {subnet} --vnet {vnet} --network-security-group {nsg} --public-ip-address {ip}').get_output_in_json() + self.kwargs.update({ + 'nic_id': res['NewNIC']['id'] + }) + + # Prepare SIG in another tenant + self.kwargs.update({ + 'location': 'eastus', + 'vm': self.create_random_name('cli_crosstenantvm', 40), + 'gallery': self.create_random_name('cli_crosstenantgallery', 40), + 'image': self.create_random_name('cli_crosstenantimage', 40), + 'version': '1.1.2', + 'captured': self.create_random_name('cli_crosstenantmanagedimage', 40), + 'aux_sub': 'bead59b7-f469-4601-803a-790729c5213d', + 'rg': self.create_random_name('cli_test_cross_tenant_rg', 40), + 'aux_tenant': '72f988bf-86f1-41af-91ab-2d7cd011db47' + }) + self.cmd('group create -g {rg} --location {location} --subscription {aux_sub}', + checks=self.check('name', self.kwargs['rg'])) + self.cmd('sig create -g {rg} --gallery-name {gallery} --subscription {aux_sub}', checks=self.check('name', self.kwargs['gallery'])) + self.cmd('sig image-definition create -g {rg} --gallery-name {gallery} --gallery-image-definition {image} --os-type linux -p publisher1 -f offer1 -s sku1 --subscription {aux_sub}', + checks=self.check('name', self.kwargs['image'])) + self.cmd('sig image-definition show -g {rg} --gallery-name {gallery} --gallery-image-definition {image} --subscription {aux_sub}', + checks=self.check('name', self.kwargs['image'])) + + self.cmd('vm create -g {rg} -n {vm} --image ubuntults --admin-username clitest1 --generate-ssh-key --subscription {aux_sub}') + self.cmd( + 'vm run-command invoke -g {rg} -n {vm} --command-id RunShellScript --scripts "echo \'sudo waagent -deprovision+user --force\' | at -M now + 1 minutes" --subscription {aux_sub}') + time.sleep(70) + + self.cmd('vm deallocate -g {rg} -n {vm} --subscription {aux_sub}') + self.cmd('vm generalize -g {rg} -n {vm} --subscription {aux_sub}') + self.cmd('image create -g {rg} -n {captured} --source {vm} --subscription {aux_sub}') + res = self.cmd( + 'sig image-version create -g {rg} --gallery-name {gallery} --gallery-image-definition {image} --gallery-image-version {version} --managed-image {captured} --replica-count 1 --subscription {aux_sub}').get_output_in_json() + self.kwargs.update({ + 'sig_id': res['id'] + }) + + # Cross tenant deploy + curr_dir = os.path.dirname(os.path.realpath(__file__)) + + self.kwargs.update({ + 'tf': os.path.join(curr_dir, 'crosstenant_vm_deploy.json').replace('\\', '\\\\'), + 'dn': self.create_random_name('cli-crosstenantdeployment', 40), + 'dn1': self.create_random_name('cli-crosstenantdeployment1', 40), + 'dn2': self.create_random_name('cli-crosstenantdeployment2', 40), + 'dn3': self.create_random_name('cli-crosstenantdeployment3', 40) + }) + + self.cmd('deployment group validate -g {vm_rg} --template-file "{tf}" --parameters SIG_ImageVersion_id={sig_id} NIC_id={nic_id}', checks=[ + self.check('properties.provisioningState', 'Succeeded') + ]) + + self.cmd('deployment group create -g {vm_rg} -n {dn} --template-file "{tf}" --parameters SIG_ImageVersion_id={sig_id} NIC_id={nic_id} --aux-subs "{aux_sub}"', checks=[ + self.check('properties.provisioningState', 'Succeeded'), + self.check('resourceGroup', '{vm_rg}') + ]) + self.cmd('deployment group list -g {vm_rg}', checks=[ + self.check('[0].name', '{dn}'), + self.check('[0].resourceGroup', '{vm_rg}') + ]) + self.cmd('deployment group show -g {vm_rg} -n {dn}', checks=[ + self.check('name', '{dn}'), + self.check('resourceGroup', '{vm_rg}') + ]) + + self.cmd('deployment group create -g {vm_rg} -n {dn1} --template-file "{tf}" --parameters SIG_ImageVersion_id={sig_id} NIC_id={nic_id} --aux-tenants "{aux_tenant}"', checks=[ + self.check('properties.provisioningState', 'Succeeded'), + self.check('resourceGroup', '{vm_rg}') + ]) + self.cmd('deployment group list -g {vm_rg}', checks=[ + self.check('[0].name', '{dn1}'), + self.check('[0].resourceGroup', '{vm_rg}') + ]) + self.cmd('deployment group show -g {vm_rg} -n {dn1}', checks=[ + self.check('name', '{dn1}'), + self.check('resourceGroup', '{vm_rg}') + ]) + + self.cmd('deployment group create -g {vm_rg} -n {dn2} --template-file "{tf}" --parameters SIG_ImageVersion_id={sig_id} NIC_id={nic_id} --aux-subs "{aux_sub}" -j', checks=[ + self.check('properties.provisioningState', 'Succeeded'), + self.check('resourceGroup', '{vm_rg}') + ]) + self.cmd('deployment group list -g {vm_rg}', checks=[ + self.check('[0].name', '{dn2}'), + self.check('[0].resourceGroup', '{vm_rg}') + ]) + self.cmd('deployment group show -g {vm_rg} -n {dn2}', checks=[ + self.check('name', '{dn2}'), + self.check('resourceGroup', '{vm_rg}') + ]) + + self.cmd('deployment group create -g {vm_rg} -n {dn3} --template-file "{tf}" --parameters SIG_ImageVersion_id={sig_id} NIC_id={nic_id} --aux-tenants "{aux_tenant}" -j', checks=[ + self.check('properties.provisioningState', 'Succeeded'), + self.check('resourceGroup', '{vm_rg}') + ]) + self.cmd('deployment group list -g {vm_rg}', checks=[ + self.check('[0].name', '{dn3}'), + self.check('[0].resourceGroup', '{vm_rg}') + ]) + self.cmd('deployment group show -g {vm_rg} -n {dn3}', checks=[ + self.check('name', '{dn3}'), + self.check('resourceGroup', '{vm_rg}') + ]) + + with self.assertRaises(AssertionError): + self.cmd('deployment group create -g {vm_rg} -n {dn} --template-file "{tf}" --parameters SIG_ImageVersion_id={sig_id} NIC_id={nic_id} --aux-tenants "{aux_tenant}" --aux-subs "{aux_sub}"') + class InvokeActionTest(ScenarioTest): @ResourceGroupPreparer(name_prefix='cli_test_invoke_action')