Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ language: python
python:
- "2.7"
- "3.5"
install:
- pip install azure==2.0.0a1
script:
- export PYTHONPATH=$PATHONPATH:./src
- python -m unittest discover -s src/azure/cli/tests
3 changes: 3 additions & 0 deletions azure-cli.pyproj
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,16 @@
<PtvsTargetsFile>$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Python Tools\Microsoft.PythonTools.targets</PtvsTargetsFile>
</PropertyGroup>
<ItemGroup>
<Compile Include="azure\cli\commands\account.py" />
<Compile Include="azure\cli\commands\login.py" />
<Compile Include="azure\cli\commands\logout.py" />
<Compile Include="azure\cli\commands\storage.py" />
<Compile Include="azure\cli\commands\__init__.py" />
<Compile Include="azure\cli\main.py" />
<Compile Include="azure\cli\tests\test_argparse.py">
<SubType>Code</SubType>
</Compile>
<Compile Include="azure\cli\tests\test_profile.py" />
<Compile Include="azure\cli\_argparse.py" />
<Compile Include="azure\cli\_logging.py" />
<Compile Include="azure\cli\_profile.py" />
Expand Down
105 changes: 91 additions & 14 deletions src/azure/cli/_profile.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,99 @@
from msrest.authentication import BasicTokenAuthentication

from .main import CONFIG
import collections

class Profile(object):

def update(self, subscriptions, access_token):
subscriptions[0]['active'] = True
CONFIG['subscriptions'] = subscriptions
CONFIG['access_token'] = access_token
def __init__(self, storage=CONFIG):
self._storage = storage

@staticmethod
def normalize_properties(user, subscriptions):
consolidated = []
for s in subscriptions:
consolidated.append({
'id': s.id.split('/')[-1],
'name': s.display_name,
'state': s.state,
'user': user,
'active': False
})
return consolidated

def set_subscriptions(self, new_subscriptions, access_token):
existing_ones = self.load_subscriptions()
active_one = next((x for x in existing_ones if x['active']), None)
active_subscription_id = active_one['id'] if active_one else None

#merge with existing ones
dic = collections.OrderedDict((x['id'], x) for x in existing_ones)
dic.update((x['id'], x) for x in new_subscriptions)
subscriptions = list(dic.values())

if active_one:
new_active_one = next(
(x for x in new_subscriptions if x['id'] == active_subscription_id), None)

def get_credentials(self):
subscriptions = CONFIG['subscriptions']
sub = [x for x in subscriptions if x['active'] == True ]
if not sub and subscriptions:
sub = subscriptions
for s in subscriptions:
s['active'] = False

if sub:
return (BasicTokenAuthentication({ 'access_token': CONFIG['access_token']}),
sub[0]['id'] )
if new_active_one:
new_active_one['active'] = True
else:
new_subscriptions[0]['active'] = True
else:
raise ValueError('you need to login to')
new_subscriptions[0]['active'] = True

#before adal/python is available, persist tokens with other profile info
for s in new_subscriptions:
s['access_token'] = access_token

self._save_subscriptions(subscriptions)

def get_login_credentials(self):
subscriptions = self.load_subscriptions()
if not subscriptions:
raise ValueError('Please run login to setup account.')

active = [x for x in subscriptions if x['active']]
if len(active) != 1:
raise ValueError('Please run "account set" to select active account.')

return BasicTokenAuthentication(
{'access_token': active[0]['access_token']}), active[0]['id']

def set_active_subscription(self, subscription_id_or_name):
subscriptions = self.load_subscriptions()

subscription_id_or_name = subscription_id_or_name.lower()
result = [x for x in subscriptions
if subscription_id_or_name == x['id'].lower() or
subscription_id_or_name == x['name'].lower()]

if len(result) != 1:
raise ValueError('The subscription of "{}" does not exist or has more than'
' one match.'.format(subscription_id_or_name))

for s in subscriptions:
s['active'] = False
result[0]['active'] = True

self._save_subscriptions(subscriptions)

def logout(self, user):
subscriptions = self.load_subscriptions()
result = [x for x in subscriptions if user.lower() == x['user'].lower()]
subscriptions = [x for x in subscriptions if x not in result]

#reset the active subscription if needed
result = [x for x in subscriptions if x['active']]
if not result and subscriptions:
subscriptions[0]['active'] = True

self._save_subscriptions(subscriptions)

def load_subscriptions(self):
return self._storage.get('subscriptions') or []

def _save_subscriptions(self, subscriptions):
self._storage['subscriptions'] = subscriptions
2 changes: 2 additions & 0 deletions src/azure/cli/commands/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
# TODO: Alternatively, simply scan the directory for all modules
COMMAND_MODULES = [
'login',
'logout',
'account',
'storage',
]

Expand Down
29 changes: 29 additions & 0 deletions src/azure/cli/commands/account.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from .._profile import Profile
from .._util import TableOutput
from ..commands import command, description, option

@command('account list')
@description(_('List the imported subscriptions.'))
def list_subscriptions(args, unexpected):
profile = Profile()
subscriptions = profile.load_subscriptions()

with TableOutput() as to:
for subscription in subscriptions:
to.cell('Name', subscription['name'])
to.cell('Active', bool(subscription['active']))
to.cell('User', subscription['user'])
to.cell('Subscription Id', subscription['id'])
to.cell('State', subscription['state'])
to.end_row()

@command('account set')
@description(_('Set the current subscription'))
@option('--subscription-id -n <subscription-id>', _('Subscription Id, unique name also works.'))
def set_active_subscription(args, unexpected):
id = args.get('subscription-id')
if not id:
raise ValueError(_('Please provide subscription id or unique name.'))

profile = Profile()
profile.set_active_subscription(id)
22 changes: 7 additions & 15 deletions src/azure/cli/commands/login.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
from azure.mgmt.resource.subscriptions import SubscriptionClient, \
from msrestazure.azure_active_directory import UserPassCredentials
from azure.mgmt.resource.subscriptions import SubscriptionClient, \
SubscriptionClientConfiguration
from msrestazure.azure_active_directory import UserPassCredentials

from .._logging import logger
from .._profile import Profile
from .._util import TableOutput
from ..commands import command, description, option

CLIENT_ID = '04b07795-8ddb-461a-bbee-02f9e1bf7b46'

@command('login')
@description('log in to an Azure subscription using Active Directory Organization Id')
@description(_('log in to an Azure subscription using Active Directory Organization Id'))
@option('--username -u <username>', _('organization Id. Microsoft Account is not yet supported.'))
@option('--password -p <password>', _('user password, will prompt if not given.'))
def login(args, unexpected):
Expand All @@ -26,22 +25,14 @@ def login(args, unexpected):
subscriptions = client.subscriptions.list()

if not subscriptions:
raise RuntimeError(_("No subscriptions found for this account"))
raise RuntimeError(_('No subscriptions found for this account.'))

#keep useful properties and not json serializable
consolidated = []
for s in subscriptions:
subscription = {};
subscription['id'] = s.id.split('/')[-1]
subscription['name'] = s.display_name
subscription['state'] = s.state
subscription['user'] = username
consolidated.append(subscription)

profile = Profile()
profile.update(consolidated, credentials.token['access_token'])
consolidated = Profile.normalize_properties(username, subscriptions)
profile.set_subscriptions(consolidated, credentials.token['access_token'])

#TODO, replace with JSON display
with TableOutput() as to:
for subscription in consolidated:
to.cell('Name', subscription['name'])
Expand All @@ -50,3 +41,4 @@ def login(args, unexpected):
to.cell('Subscription Id', subscription['id'])
to.cell('State', subscription['state'])
to.end_row()

13 changes: 13 additions & 0 deletions src/azure/cli/commands/logout.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from .._profile import Profile
from ..commands import command, description, option

@command('logout')
@description(_('Log out from Azure subscription using Active Directory.'))
@option('--username -u <username>', _('User name used to log out from Azure Active Directory.'))
def logout(args, unexpected):
username = args.get('username')
if not username:
raise ValueError(_('Please provide a valid username to logout.'))

profile = Profile()
profile.logout(username)
13 changes: 6 additions & 7 deletions src/azure/cli/commands/storage.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,21 @@
from ..main import CONFIG, SESSION
from .._logging import logger
from ..main import SESSION
from .._logging import logging
from .._util import TableOutput
from ..commands import command, description, option
from .._profile import Profile

@command('storage account list')
@description('List storage accounts')
@option('--resource-group -g <resourceGroup>', _("the resource group name"))
@option('--subscription -s <id>', _("the subscription id"))
@description(_('List storage accounts'))
@option('--resource-group -g <resourceGroup>', _('the resource group name'))
@option('--subscription -s <id>', _('the subscription id'))
def list_accounts(args, unexpected):
from azure.mgmt.storage import StorageManagementClient, StorageManagementClientConfiguration
from azure.mgmt.storage.models import StorageAccount
from msrestazure.azure_active_directory import UserPassCredentials

profile = Profile()
#credentials, subscription_id = profile.get_credentials()
smc = StorageManagementClient(StorageManagementClientConfiguration(
*profile.get_credentials()
*profile.get_login_credentials(),
))

group = args.get('resource-group')
Expand Down
12 changes: 6 additions & 6 deletions src/azure/cli/tests/test_argparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,12 @@ def test_args(self):
self.assertIsNone(res)

res, other = p.execute('n1 -b -a x'.split())
self.assertEquals(res.b, '-a')
self.assertEqual(res.b, '-a')
self.assertSequenceEqual(res.positional, ['x'])
self.assertRaises(IncorrectUsageError, lambda: res.arg)

res, other = p.execute('n1 -b:-a x'.split())
self.assertEquals(res.b, '-a')
self.assertEqual(res.b, '-a')
self.assertSequenceEqual(res.positional, ['x'])
self.assertRaises(IncorrectUsageError, lambda: res.arg)

Expand All @@ -73,16 +73,16 @@ def test_unexpected_args(self):

res, other = p.execute('n1 -b=2'.split())
self.assertFalse(res)
self.assertEquals('2', other.b)
self.assertEqual('2', other.b)

res, other = p.execute('n1 -b.c.d=2'.split())
self.assertFalse(res)
self.assertEquals('2', other.b.c.d)
self.assertEqual('2', other.b.c.d)

res, other = p.execute('n1 -b.c.d 2 -b.c.e:3'.split())
self.assertFalse(res)
self.assertEquals('2', other.b.c.d)
self.assertEquals('3', other.b.c.e)
self.assertEqual('2', other.b.c.d)
self.assertEqual('3', other.b.c.e)

if __name__ == '__main__':
unittest.main()
Loading