diff --git a/azure-cli.pyproj b/azure-cli.pyproj
index c58774488..f6e2c9069 100644
--- a/azure-cli.pyproj
+++ b/azure-cli.pyproj
@@ -12,7 +12,7 @@
.
{888888a0-9f3d-457c-b088-3a5042f75d52}
Standard Python launcher
- {1dd9c42b-5980-42ce-a2c3-46d3bf0eede4}
+ {54f4b6dc-0859-46dc-99bb-b275c9d0aca3}
3.5
False
@@ -36,6 +36,10 @@
+
+
+ Code
+
@@ -178,6 +182,19 @@
+
+
+ {54f4b6dc-0859-46dc-99bb-b275c9d0aca3}
+ {2af0f10d-7135-4994-9156-5d01c9c11b7e}
+ 3.5
+ env (Python 3.5)
+ Scripts\python.exe
+ Scripts\pythonw.exe
+ Lib\
+ PYTHONPATH
+ X86
+
+
\ No newline at end of file
diff --git a/setup.py b/setup.py
index d14b04370..72951f40a 100644
--- a/setup.py
+++ b/setup.py
@@ -21,13 +21,14 @@ from codecs import open
from setuptools import setup
VERSION = '0.0.32'
-INSTALL_FROM_PUBLIC = False
PRIVATE_PYPI_URL_ENV_NAME = 'AZURE_CLI_PRIVATE_PYPI_URL'
PRIVATE_PYPI_URL = os.environ.get(PRIVATE_PYPI_URL_ENV_NAME)
PRIVATE_PYPI_HOST_ENV_NAME = 'AZURE_CLI_PRIVATE_PYPI_HOST'
PRIVATE_PYPI_HOST = os.environ.get(PRIVATE_PYPI_HOST_ENV_NAME)
+INSTALL_FROM_PRIVATE = bool(PRIVATE_PYPI_URL and PRIVATE_PYPI_HOST)
+
# If we have source, validate that our version numbers match
# This should prevent uploading releases with mismatched versions.
try:
@@ -83,10 +84,7 @@ def _post_install(dir):
from subprocess import check_call
# Upgrade/update will install if it doesn't exist.
# We do this so these components are updated when the user updates the CLI.
- if INSTALL_FROM_PUBLIC:
- pip.main(['install', '--upgrade', 'azure-cli-component', '--disable-pip-version-check'])
- check_call(['az', 'component', 'update', '-n', 'profile'])
- else:
+ if INSTALL_FROM_PRIVATE:
# use private PyPI server.
if not PRIVATE_PYPI_URL:
raise RuntimeError('{} environment variable not set.'.format(PRIVATE_PYPI_URL_ENV_NAME))
@@ -96,6 +94,9 @@ def _post_install(dir):
PRIVATE_PYPI_URL, '--trusted-host', PRIVATE_PYPI_HOST,
'--disable-pip-version-check'])
check_call(['az', 'component', 'update', '-n', 'profile', '-p'])
+ else:
+ pip.main(['install', '--upgrade', 'azure-cli-component', '--disable-pip-version-check'])
+ check_call(['az', 'component', 'update', '-n', 'profile'])
class OnInstall(install):
def run(self):
diff --git a/src/azure/cli/_profile.py b/src/azure/cli/_profile.py
index 5945913f4..50fcb5b71 100644
--- a/src/azure/cli/_profile.py
+++ b/src/azure/cli/_profile.py
@@ -326,15 +326,15 @@ class CredsCache(object):
self.adal_token_cache = adal.TokenCache(json.dumps(real_token))
return self.adal_token_cache
- def save_service_principal_cred(self, client_id, secret, tenant):
+ def save_service_principal_cred(self, service_principal_id, secret, tenant):
entry = {
- _SERVICE_PRINCIPAL_ID: client_id,
+ _SERVICE_PRINCIPAL_ID: service_principal_id,
_SERVICE_PRINCIPAL_TENANT: tenant,
_ACCESS_TOKEN: secret
}
matched = [x for x in self._service_principal_creds
- if client_id == x[_SERVICE_PRINCIPAL_ID] and
+ if service_principal_id == x[_SERVICE_PRINCIPAL_ID] and
tenant == x[_SERVICE_PRINCIPAL_TENANT]]
state_changed = False
if matched:
diff --git a/src/azure/cli/tests/test_profile.py b/src/azure/cli/tests/test_profile.py
index 2116e98ff..8d8f5b1db 100644
--- a/src/azure/cli/tests/test_profile.py
+++ b/src/azure/cli/tests/test_profile.py
@@ -226,26 +226,181 @@ class Test_Profile(unittest.TestCase):
self.assertEqual(mock_read_cred_file.call_count, 1)
self.assertEqual(mock_persist_creds.call_count, 1)
- def test_find_subscriptions_thru_username_password(self):
- finder = SubscriptionFinder(lambda _,_2:AuthenticationContextStub(Test_Profile),
+ @mock.patch('adal.AuthenticationContext', autospec=True)
+ def test_find_subscriptions_thru_username_password(self, mock_auth_context):
+ mock_auth_context.acquire_token_with_username_password.return_value = self.token_entry1
+ mock_auth_context.acquire_token.return_value = self.token_entry1
+ mock_arm_client = mock.MagicMock()
+ mock_arm_client.tenants.list.return_value = [TenantStub(self.tenant_id)]
+ mock_arm_client.subscriptions.list.return_value = [self.subscription1]
+ finder = SubscriptionFinder(lambda _,_2: mock_auth_context,
None,
- lambda _: ArmClientStub(Test_Profile))
- subs = finder.find_from_user_account('foo', 'bar')
- self.assertEqual([self.subscription1], subs)
+ lambda _: mock_arm_client)
- def test_find_through_interactive_flow(self):
- finder = SubscriptionFinder(lambda _,_2:AuthenticationContextStub(Test_Profile),
+ #action
+ subs = finder.find_from_user_account(self.user1, 'bar')
+
+ #assert
+ self.assertEqual([self.subscription1], subs)
+ mock_auth_context.acquire_token_with_username_password.assert_called_once_with(
+ 'https://management.core.windows.net/', self.user1, 'bar', mock.ANY)
+ mock_auth_context.acquire_token.assert_called_once_with(
+ 'https://management.core.windows.net/', self.user1, mock.ANY)
+
+ @mock.patch('adal.AuthenticationContext', autospec=True)
+ def test_find_subscriptions_through_interactive_flow(self, mock_auth_context):
+ test_nonsense_code = {'message':'magic code for you'}
+ mock_auth_context.acquire_user_code.return_value = test_nonsense_code
+ mock_auth_context.acquire_token_with_device_code.return_value = self.token_entry1
+ mock_arm_client = mock.MagicMock()
+ mock_arm_client.tenants.list.return_value = [TenantStub(self.tenant_id)]
+ mock_arm_client.subscriptions.list.return_value = [self.subscription1]
+ finder = SubscriptionFinder(lambda _,_2: mock_auth_context,
None,
- lambda _: ArmClientStub(Test_Profile))
+ lambda _: mock_arm_client)
+
+ #action
subs = finder.find_through_interactive_flow()
+
+ #assert
self.assertEqual([self.subscription1], subs)
-
- def test_find_from_service_principal_id(self):
- finder = SubscriptionFinder(lambda _,_2:AuthenticationContextStub(Test_Profile),
+ mock_auth_context.acquire_user_code.assert_called_once_with(
+ 'https://management.core.windows.net/', mock.ANY)
+ mock_auth_context.acquire_token_with_device_code.assert_called_once_with(
+ 'https://management.core.windows.net/', test_nonsense_code, mock.ANY)
+ mock_auth_context.acquire_token.assert_called_once_with(
+ 'https://management.core.windows.net/', self.user1, mock.ANY)
+
+ @mock.patch('adal.AuthenticationContext', autospec=True)
+ def test_find_subscriptions_from_service_principal_id(self, mock_auth_context):
+ mock_auth_context.acquire_token_with_client_credentials.return_value = self.token_entry1
+ mock_arm_client = mock.MagicMock()
+ mock_arm_client.subscriptions.list.return_value = [self.subscription1]
+ finder = SubscriptionFinder(lambda _,_2:mock_auth_context,
None,
- lambda _: ArmClientStub(Test_Profile))
+ lambda _: mock_arm_client)
+ #action
subs = finder.find_from_service_principal_id('my app', 'my secret', self.tenant_id)
+
+ #assert
self.assertEqual([self.subscription1], subs)
+ mock_arm_client.tenants.list.assert_not_called()
+ mock_auth_context.acquire_token.assert_not_called()
+ mock_auth_context.acquire_token_with_client_credentials.assert_called_once_with(
+ 'https://management.core.windows.net/', 'my app', 'my secret')
+
+ @mock.patch('azure.cli._profile._read_file_content', autospec=True)
+ def test_credscache_load_tokens_and_sp_creds(self, mock_read_file):
+ test_sp = {
+ "servicePrincipalId": "myapp",
+ "servicePrincipalTenant": "mytenant",
+ "accessToken": "Secret"
+ }
+ mock_read_file.return_value = json.dumps([self.token_entry1, test_sp])
+
+ #action
+ creds_cache = CredsCache()
+
+ #assert
+ token_entries = [entry for _, entry in creds_cache.adal_token_cache.read_items()]
+ self.assertEqual(token_entries, [self.token_entry1])
+ self.assertEqual(creds_cache._service_principal_creds,[test_sp])
+
+ @mock.patch('azure.cli._profile._read_file_content', autospec=True)
+ @mock.patch('azure.cli._profile.codecs_open', autospec=True)
+ def test_credscache_add_new_sp_creds(self, mock_open_for_write, mock_read_file):
+ test_sp = {
+ "servicePrincipalId": "myapp",
+ "servicePrincipalTenant": "mytenant",
+ "accessToken": "Secret"
+ }
+ test_sp2 = {
+ "servicePrincipalId": "myapp2",
+ "servicePrincipalTenant": "mytenant2",
+ "accessToken": "Secret2"
+ }
+ mock_open_for_write.return_value = FileHandleStub()
+ mock_read_file.return_value = json.dumps([self.token_entry1, test_sp])
+ creds_cache = CredsCache()
+
+ #action
+ creds_cache.save_service_principal_cred(
+ test_sp2['servicePrincipalId'],
+ test_sp2['accessToken'],
+ test_sp2['servicePrincipalTenant'])
+
+ #assert
+ token_entries = [entry for _, entry in creds_cache.adal_token_cache.read_items()]
+ self.assertEqual(token_entries, [self.token_entry1])
+ self.assertEqual(creds_cache._service_principal_creds,[test_sp, test_sp2])
+ mock_open_for_write.assert_called_with(mock.ANY, 'w', encoding='ascii')
+
+ @mock.patch('azure.cli._profile._read_file_content', autospec=True)
+ @mock.patch('azure.cli._profile.codecs_open', autospec=True)
+ def test_credscache_remove_creds(self, mock_open_for_write, mock_read_file):
+ test_sp = {
+ "servicePrincipalId": "myapp",
+ "servicePrincipalTenant": "mytenant",
+ "accessToken": "Secret"
+ }
+ mock_open_for_write.return_value = FileHandleStub()
+ mock_read_file.return_value = json.dumps([self.token_entry1, test_sp])
+ creds_cache = CredsCache()
+
+ #action #1, logout a user
+ creds_cache.remove_cached_creds(self.user1)
+
+ #assert #1
+ token_entries = [entry for _, entry in creds_cache.adal_token_cache.read_items()]
+ self.assertEqual(token_entries, [])
+
+ #action #2 logout a service principal
+ creds_cache.remove_cached_creds('myapp')
+
+ #assert #2
+ self.assertEqual(creds_cache._service_principal_creds,[])
+
+ mock_open_for_write.assert_called_with(mock.ANY, 'w', encoding='ascii')
+ self.assertEqual(mock_open_for_write.call_count, 2)
+
+ @mock.patch('azure.cli._profile._read_file_content', autospec=True)
+ @mock.patch('azure.cli._profile.codecs_open', autospec=True)
+ @mock.patch('adal.AuthenticationContext', autospec=True)
+ def test_credscache_new_token_added_by_adal(self, mock_adal_auth_context, mock_open_for_write, mock_read_file):
+ token_entry2 = {
+ "accessToken": "new token",
+ "userId": self.user1
+ }
+ def acquire_token_side_effect(*args):
+ creds_cache.adal_token_cache.has_state_changed = True
+ return token_entry2
+ def get_auth_context(authority, **kwargs):
+ mock_adal_auth_context.cache = kwargs['cache']
+ return mock_adal_auth_context
+
+ mock_adal_auth_context.acquire_token.side_effect = acquire_token_side_effect
+ mock_open_for_write.return_value = FileHandleStub()
+ mock_read_file.return_value = json.dumps([self.token_entry1])
+ creds_cache = CredsCache(auth_ctx_factory=get_auth_context)
+ token = creds_cache.retrieve_token_for_user(self.user1, self.tenant_id)
+
+ #action
+ mock_adal_auth_context.acquire_token.assert_called_once_with(
+ 'https://management.core.windows.net/',
+ self.user1,
+ mock.ANY)
+
+ #assert
+ mock_open_for_write.assert_called_with(mock.ANY, 'w', encoding='ascii')
+ self.assertEqual(token, 'new token')
+
+class FileHandleStub:
+ def write(self, content):
+ pass
+ def __enter__(self):
+ return self
+ def __exit__(self, _2, _3, _4):
+ pass
class SubscriptionStub:
def __init__(self, id, display_name, state, tenant_id):
@@ -254,47 +409,9 @@ class SubscriptionStub:
self.state = state
self.tenant_id = tenant_id
-class AuthenticationContextStub:
- def __init__(self, test_profile_cls, return_token1=True):
- #we need to reference some pre-defined test artifacts in Test_Profile
- self._test_profile_cls = test_profile_cls
- if not return_token1:
- raise ValueError('Please update to return other test tokens')
-
- def acquire_token_with_username_password(self, _, _2, _3, _4):
- return self._test_profile_cls.token_entry1
-
- def acquire_token_with_device_code(self, _, _2, _3):
- return self._test_profile_cls.token_entry1
-
- def acquire_token_with_client_credentials(self, _, _2, _3):
- return self._test_profile_cls.token_entry1
-
- def acquire_token(self, _, _2, _3):
- return self._test_profile_cls.token_entry1
-
- def acquire_user_code(self, _, _2):
- return {'message': 'secret code for you'}
-
-class ArmClientStub:
- class TenantStub:
- def __init__(self, tenant_id):
- self.tenant_id = tenant_id
-
- class OperationsStub:
- def __init__(self, list_result):
- self._list_result = list_result
-
- def list(self):
- return self._list_result
-
- def __init__(self, test_profile_cls, use_tenant1_and_subscription1=True):
- self._test_profile_cls = test_profile_cls
- if use_tenant1_and_subscription1:
- self.tenants = ArmClientStub.OperationsStub([ArmClientStub.TenantStub(test_profile_cls.tenant_id)])
- self.subscriptions = ArmClientStub.OperationsStub([test_profile_cls.subscription1])
- else:
- raise ValueError('Please update to return other test subscriptions')
+class TenantStub:
+ def __init__(self, tenant_id):
+ self.tenant_id = tenant_id
if __name__ == '__main__':
unittest.main()
diff --git a/src/command_modules/azure-cli-resource/azure/cli/command_modules/resource/__init__.py b/src/command_modules/azure-cli-resource/azure/cli/command_modules/resource/__init__.py
index 0dccadbfc..95fa914cc 100644
--- a/src/command_modules/azure-cli-resource/azure/cli/command_modules/resource/__init__.py
+++ b/src/command_modules/azure-cli-resource/azure/cli/command_modules/resource/__init__.py
@@ -6,8 +6,8 @@ from azure.cli._locale import L
command_table = CommandTable()
def _resource_client_factory(*args): # pylint: disable=unused-argument
- from azure.mgmt.resource.resources import (ResourceManagementClient,
- ResourceManagementClientConfiguration)
+from azure.mgmt.resource.resources import (ResourceManagementClient,
+ ResourceManagementClientConfiguration)
return get_mgmt_service_client(ResourceManagementClient, ResourceManagementClientConfiguration)
@command_table.command('resource group list', description=L('List resource groups'))
@@ -38,7 +38,7 @@ def list_groups(args):
help=L('the resource type in format: /'),
required=True)
@command_table.option('--api-version -o', help=L('the API version of the resource provider'))
-@command_table.option('--parent',
+@command_table.option('--parent', default='',
help=L('the name of the parent resource (if needed), ' + \
'in / format'))
def show_resource(args):
@@ -56,9 +56,8 @@ def show_resource(args):
raise IncorrectUsageError(
L('API version is required and could not be resolved for resource {}'
.format(full_type)))
-
results = rmc.resources.get(
- resource_group_name=args.get('resource_group'),
+ resource_group_name=args.get('resourcegroup'),
resource_name=args.get('name'),
resource_provider_namespace=provider_namespace,
resource_type=resource_type,
@@ -88,18 +87,12 @@ def _resolve_api_version(args, rmc):
raise IncorrectUsageError('Parameter --parent must be in / format.')
resource_type = "{}/{}".format(parent_type, resource_type)
- else:
- resource_type = resource_type
provider = rmc.providers.get(provider_namespace)
- for t in provider.resource_types:
- if t.resource_type == resource_type:
- # Return first non-preview version
- for version in t.api_versions:
- if not version.find('preview'):
- return version
- # No non-preview version found. Take first preview version
- try:
- return t.api_versions[0]
- except IndexError:
- return None
+
+ rt = [t for t in provider.resource_types if t.resource_type == resource_type]
+ if not rt:
+ raise IncorrectUsageError('Resource type {} not found.'.format(full_type))
+ if len(rt) == 1 and rt[0].api_versions:
+ npv = [v for v in rt[0].api_versions if "preview" not in v]
+ return npv[0] if npv else rt[0].api_versions[0]
return None
diff --git a/src/command_modules/azure-cli-resource/azure/cli/command_modules/resource/tests/test_api_check.py b/src/command_modules/azure-cli-resource/azure/cli/command_modules/resource/tests/test_api_check.py
new file mode 100644
index 000000000..c3d346d93
--- /dev/null
+++ b/src/command_modules/azure-cli-resource/azure/cli/command_modules/resource/tests/test_api_check.py
@@ -0,0 +1,64 @@
+import unittest
+try:
+ from unittest.mock import MagicMock
+except ImportError:
+ from mock import MagicMock
+
+from azure.cli.command_modules.resource import _resolve_api_version as resolve_api_version
+
+class TestApiCheck(unittest.TestCase):
+
+ @classmethod
+ def setUpClass(cls):
+ pass
+
+ @classmethod
+ def tearDownClass(cls):
+ pass
+
+ def setUp(self):
+ pass
+
+ def tearDown(self):
+ pass
+
+ def test_resolve_api_max_priority_option(self):
+ """ Verifies the --api-version parameter has maximum priority. """
+ args = {'api-version': '2015-01-01', 'resource-type': 'Mock/test'}
+ self.assertEqual(resolve_api_version(args, self._get_mock_client()), "2015-01-01")
+
+ def test_resolve_api_provider_backup(self):
+ """ Verifies provider is used as backup if api-version not specified. """
+ args = {'resource-type': 'Mock/test'}
+ self.assertEqual(resolve_api_version(args, self._get_mock_client()), "2016-01-01")
+
+ def test_resolve_api_provider_with_parent_backup(self):
+ """ Verifies provider (with parent) is used as backup if api-version not specified. """
+ args = {'resource-type': 'Mock/bar', 'parent': 'foo/testfoo123'}
+ self.assertEqual(resolve_api_version(args, self._get_mock_client()), "1999-01-01")
+
+ def test_resolve_api_all_previews(self):
+ """ Verifies most recent preview version returned only if there are no non-preview versions. """
+ args = {'resource-type': 'Mock/preview'}
+ self.assertEqual(resolve_api_version(args, self._get_mock_client()), "2005-01-01-preview")
+
+ def _get_mock_client(self):
+ client = MagicMock()
+ provider = MagicMock()
+ provider.resource_types = [
+ self._get_mock_resource_type('skip', ['2000-01-01-preview', '2000-01-01']),
+ self._get_mock_resource_type('test', ['2016-01-01-preview', '2016-01-01']),
+ self._get_mock_resource_type('foo/bar', ['1999-01-01-preview', '1999-01-01']),
+ self._get_mock_resource_type('preview', ['2005-01-01-preview', '2004-01-01-preview'])
+ ]
+ client.providers.get.return_value = provider
+ return client
+
+ def _get_mock_resource_type(self, name, api_versions):
+ rt = MagicMock()
+ rt.resource_type = name
+ rt.api_versions = api_versions
+ return rt
+
+if __name__ == '__main__':
+ unittest.main()