Repository: Azure/azure-cli

Issue:
Azure CLI Feature: Use default location and sku when specifying a resource group
### Environment summary

**Install Method:** How did you install the CLI? (e.g. pip, interactive script, apt-get, Docker, MSI, nightly)  
Answer here: pip

**CLI Version:** What version of the CLI and modules are installed? (Use `az --version`)  
Answer here: azure-cli (2.0.3)

**OS Version:** What OS and version are you using?  
Answer here: macOS 10.12.3

**Shell Type:** What shell are you using? (e.g. bash, cmd.exe, Bash on Windows)  
Answer here: bash on macOS

---

### Description

It would be great if when we create a storage account (or other resources) that if we specify a resource group, we can default to use its location. Otherwise we do this ...
```
az group create -n PapaResourceGroup -l eastus

az storage account create -n papastorage -g PapaResourceGroup --sku Standard_LRS -l eastus
```

Notice we type the location twice. If I omit the location in the second command, it fails. We also should be able to default the sku. A quicker way would look like this ...

```
az group create -n PapaResourceGroup -l eastus

az storage account create -n papastorage -g PapaResourceGroup 

```

cc // @SyntaxC4 

Discussion:

Patch:
diff --git a/src/azure-cli-core/azure/cli/core/_profile.py b/src/azure-cli-core/azure/cli/core/_profile.py
index e0d45196a..9788066e4 100644
--- a/src/azure-cli-core/azure/cli/core/_profile.py
+++ b/src/azure-cli-core/azure/cli/core/_profile.py
@@ -106,6 +106,7 @@ class Profile(object):
                                     password,
                                     is_service_principal,
                                     tenant,
+                                    allow_no_subscriptions=False,
                                     subscription_finder=None):
         from azure.cli.core._debug import allow_debug_adal_connection
         allow_debug_adal_connection()
@@ -128,18 +129,26 @@ class Profile(object):
                 subscriptions = subscription_finder.find_from_user_account(
                     username, password, tenant, self._ad_resource_uri)
 
-        if not subscriptions:
-            raise CLIError('No subscriptions found for this account.')
+        if not allow_no_subscriptions and not subscriptions:
+            raise CLIError("No subscriptions were found for '{}'. If this is expected, use "
+                           "'--allow-no-subscriptions' to have tenant level accesses".format(
+                               username))
 
         if is_service_principal:
             self._creds_cache.save_service_principal_cred(sp_auth.get_entry_to_persist(username,
                                                                                        tenant))
-
         if self._creds_cache.adal_token_cache.has_state_changed:
             self._creds_cache.persist_cached_creds()
+
+        if allow_no_subscriptions:
+            t_list = [s.tenant_id for s in subscriptions]
+            bare_tenants = [t for t in subscription_finder.tenants if t not in t_list]
+            subscriptions = Profile._build_tenant_level_accounts(bare_tenants)
+
         consolidated = Profile._normalize_properties(subscription_finder.user_id,
                                                      subscriptions,
                                                      is_service_principal)
+
         self._set_subscriptions(consolidated)
         # use deepcopy as we don't want to persist these changes to file.
         return deepcopy(consolidated)
@@ -162,6 +171,24 @@ class Profile(object):
             })
         return consolidated
 
+    @staticmethod
+    def _build_tenant_level_accounts(tenants):
+        from azure.cli.core.profiles import get_sdk, ResourceType
+        SubscriptionType = get_sdk(ResourceType.MGMT_RESOURCE_SUBSCRIPTIONS,
+                                   'Subscription', mod='models')
+        StateType = get_sdk(ResourceType.MGMT_RESOURCE_SUBSCRIPTIONS,
+                            'SubscriptionState', mod='models')
+        result = []
+        for t in tenants:
+            s = SubscriptionType()
+            s.id = '/subscriptions/' + t
+            s.subscription = t
+            s.tenant_id = t
+            s.display_name = 'N/A(tenant level account)'
+            s.state = StateType.enabled
+            result.append(s)
+        return result
+
     def _set_subscriptions(self, new_subscriptions):
         existing_ones = self.load_cached_subscriptions(all_clouds=True)
         active_one = next((x for x in existing_ones if x.get(_IS_DEFAULT_SUBSCRIPTION)), None)
@@ -345,6 +372,7 @@ class SubscriptionFinder(object):
                     config, base_url=CLOUD.endpoints.resource_manager))
 
         self._arm_client_factory = create_arm_client_factory
+        self.tenants = []
 
     def find_from_user_account(self, username, password, tenant, resource):
         context = self._create_auth_context(tenant or _COMMON_TENANT)
@@ -379,6 +407,7 @@ class SubscriptionFinder(object):
         token_entry = sp_auth.acquire_token(context, resource, client_id)
         self.user_id = client_id
         result = self._find_using_specific_tenant(tenant, token_entry[_ACCESS_TOKEN])
+        self.tenants = [tenant]
         return result
 
     def _create_auth_context(self, tenant, use_token_cache=True):
@@ -410,6 +439,7 @@ class SubscriptionFinder(object):
                 temp_credentials[_ACCESS_TOKEN])
             all_subscriptions.extend(subscriptions)
 
+        self.tenants = tenants
         return all_subscriptions
 
     def _find_using_specific_tenant(self, tenant, access_token):
@@ -422,6 +452,7 @@ class SubscriptionFinder(object):
         for s in subscriptions:
             setattr(s, 'tenant_id', tenant)
             all_subscriptions.append(s)
+        self.tenants = [tenant]
         return all_subscriptions
 
 
diff --git a/src/command_modules/azure-cli-profile/HISTORY.rst b/src/command_modules/azure-cli-profile/HISTORY.rst
index 3392ef64a..5b8cee30e 100644
--- a/src/command_modules/azure-cli-profile/HISTORY.rst
+++ b/src/command_modules/azure-cli-profile/HISTORY.rst
@@ -2,6 +2,9 @@
 
 Release History
 ===============
+2.0.4 (unreleased)
+++++++++++++++++++
+* Support login when there are no subscriptions found (#2560)
 
 2.0.3 (2017-04-17)
 ++++++++++++++++++
diff --git a/src/command_modules/azure-cli-profile/azure/cli/command_modules/profile/_params.py b/src/command_modules/azure-cli-profile/azure/cli/command_modules/profile/_params.py
index 494a62dbb..2b552e0d6 100644
--- a/src/command_modules/azure-cli-profile/azure/cli/command_modules/profile/_params.py
+++ b/src/command_modules/azure-cli-profile/azure/cli/command_modules/profile/_params.py
@@ -21,6 +21,7 @@ register_cli_argument('login', 'password', options_list=('--password', '-p'), he
 register_cli_argument('login', 'service_principal', action='store_true', help='The credential representing a service principal.')
 register_cli_argument('login', 'username', options_list=('--username', '-u'), help='Organization id or service principal')
 register_cli_argument('login', 'tenant', options_list=('--tenant', '-t'), help='The AAD tenant, must provide when using service principals.')
+register_cli_argument('login', 'allow_no_subscriptions', action='store_true', help="Support access tenants without subscriptions. It's uncommon but useful to run tenant level commands, such as 'az ad'")
 
 register_cli_argument('logout', 'username', help='account user, if missing, logout the current active account')
 
diff --git a/src/command_modules/azure-cli-profile/azure/cli/command_modules/profile/custom.py b/src/command_modules/azure-cli-profile/azure/cli/command_modules/profile/custom.py
index f163f8122..089fdc1fe 100644
--- a/src/command_modules/azure-cli-profile/azure/cli/command_modules/profile/custom.py
+++ b/src/command_modules/azure-cli-profile/azure/cli/command_modules/profile/custom.py
@@ -54,7 +54,8 @@ def account_clear():
     profile.logout_all()
 
 
-def login(username=None, password=None, service_principal=None, tenant=None):
+def login(username=None, password=None, service_principal=None, tenant=None,
+          allow_no_subscriptions=False):
     """Log in to access Azure subscriptions"""
     from adal.adal_error import AdalError
     import requests
@@ -76,7 +77,8 @@ def login(username=None, password=None, service_principal=None, tenant=None):
             username,
             password,
             service_principal,
-            tenant)
+            tenant,
+            allow_no_subscriptions=allow_no_subscriptions)
     except AdalError as err:
         # try polish unfriendly server errors
         if username:
diff --git a/src/command_modules/azure-cli-storage/HISTORY.rst b/src/command_modules/azure-cli-storage/HISTORY.rst
index 0f6cf0717..f7d5140f8 100644
--- a/src/command_modules/azure-cli-storage/HISTORY.rst
+++ b/src/command_modules/azure-cli-storage/HISTORY.rst
@@ -3,6 +3,11 @@
 Release History
 ===============
 
+unreleased
+++++++++++++++++++
+
+* Default location to resource group location for `storage account create`.
+
 2.0.3 (2017-04-17)
 ++++++++++++++++++
 
diff --git a/src/command_modules/azure-cli-storage/azure/cli/command_modules/storage/_params.py b/src/command_modules/azure-cli-storage/azure/cli/command_modules/storage/_params.py
index 242334575..ed58494ad 100644
--- a/src/command_modules/azure-cli-storage/azure/cli/command_modules/storage/_params.py
+++ b/src/command_modules/azure-cli-storage/azure/cli/command_modules/storage/_params.py
@@ -11,7 +11,8 @@ from six import u as unicode_string
 from azure.cli.core._config import az_config
 from azure.cli.core.commands.parameters import \
     (ignore_type, tags_type, file_type, get_resource_name_completion_list, enum_choice_list,
-     model_choice_list, enum_default)
+     model_choice_list, enum_default, location_type)
+from azure.cli.core.commands.validators import get_default_location_from_resource_group
 import azure.cli.core.commands.arm  # pylint: disable=unused-import
 from azure.cli.core.commands import register_cli_argument, register_extra_cli_argument, CliArgumentType
 
@@ -283,6 +284,7 @@ register_cli_argument('storage account show-connection-string', 'key_name', opti
 for item in ['blob', 'file', 'queue', 'table']:
     register_cli_argument('storage account show-connection-string', '{}_endpoint'.format(item), help='Custom endpoint for {}s.'.format(item))
 
+register_cli_argument('storage account create', 'location', location_type, validator=get_default_location_from_resource_group)
 register_cli_argument('storage account create', 'account_type', help='The storage account type', **model_choice_list(ResourceType.MGMT_STORAGE, 'AccountType'))
 
 register_cli_argument('storage account create', 'account_name', account_name_type, options_list=('--name', '-n'), completer=None)
diff --git a/src/command_modules/azure-cli-storage/azure/cli/command_modules/storage/custom.py b/src/command_modules/azure-cli-storage/azure/cli/command_modules/storage/custom.py
index 8018c9b4e..266f15f07 100644
--- a/src/command_modules/azure-cli-storage/azure/cli/command_modules/storage/custom.py
+++ b/src/command_modules/azure-cli-storage/azure/cli/command_modules/storage/custom.py
@@ -52,7 +52,7 @@ def _update_progress(current, total):
 
 # CUSTOM METHODS
 
-def create_storage_account(resource_group_name, account_name, sku, location,
+def create_storage_account(resource_group_name, account_name, sku, location=None,
                            kind=None, tags=None, custom_domain=None,
                            encryption=None, access_tier=None):
     StorageAccountCreateParameters, Kind, Sku, CustomDomain, AccessTier = get_sdk(
@@ -75,7 +75,8 @@ def create_storage_account(resource_group_name, account_name, sku, location,
     return scf.storage_accounts.create(resource_group_name, account_name, params)
 
 
-def create_storage_account_with_account_type(resource_group_name, account_name, location, account_type, tags=None):
+def create_storage_account_with_account_type(resource_group_name, account_name, account_type,
+                                             location=None, tags=None):
     StorageAccountCreateParameters, AccountType = get_sdk(
         ResourceType.MGMT_STORAGE,
         'StorageAccountCreateParameters',


Test patch:
diff --git a/src/azure-cli-core/tests/test_profile.py b/src/azure-cli-core/tests/test_profile.py
index e344826e2..7c092e55f 100644
--- a/src/azure-cli-core/tests/test_profile.py
+++ b/src/azure-cli-core/tests/test_profile.py
@@ -243,6 +243,7 @@ class Test_Profile(unittest.TestCase):  # pylint: disable=too-many-public-method
                                             'my-secret',
                                             True,
                                             self.tenant_id,
+                                            False,
                                             finder)
         # action
         extended_info = profile.get_expanded_subscription_info()
@@ -253,6 +254,35 @@ class Test_Profile(unittest.TestCase):  # pylint: disable=too-many-public-method
         self.assertEqual('https://login.microsoftonline.com',
                          extended_info['endpoints'].active_directory)
 
+    @mock.patch('adal.AuthenticationContext', autospec=True)
+    def test_create_account_without_subscriptions(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 = []
+        finder = SubscriptionFinder(lambda _, _2: mock_auth_context,
+                                    None,
+                                    lambda _: mock_arm_client)
+
+        storage_mock = {'subscriptions': []}
+        profile = Profile(storage_mock)
+        profile._management_resource_uri = 'https://management.core.windows.net/'
+
+        # action
+        result = profile.find_subscriptions_on_login(False,
+                                                     '1234',
+                                                     'my-secret',
+                                                     True,
+                                                     self.tenant_id,
+                                                     allow_no_subscriptions=True,
+                                                     subscription_finder=finder)
+
+        # assert
+        self.assertTrue(1, len(result))
+        self.assertEqual(result[0]['id'], self.tenant_id)
+        self.assertEqual(result[0]['state'], 'Enabled')
+        self.assertEqual(result[0]['tenantId'], self.tenant_id)
+        self.assertEqual(result[0]['name'], 'N/A(tenant level account)')
+
     @mock.patch('azure.cli.core._profile._load_tokens_from_file', autospec=True)
     def test_get_current_account_user(self, mock_read_cred_file):
         # setup