Skip to content

Commit 4f7670b

Browse files
lots of unit tests plus user create and user edit-details
1 parent fbf5852 commit 4f7670b

File tree

8 files changed

+241
-3
lines changed

8 files changed

+241
-3
lines changed

SoftLayer/CLI/routes.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,9 @@
287287
('user:detail', 'SoftLayer.CLI.user.detail:cli'),
288288
('user:permissions', 'SoftLayer.CLI.user.permissions:cli'),
289289
('user:edit-permissions', 'SoftLayer.CLI.user.edit_permissions:cli'),
290+
('user:edit-details', 'SoftLayer.CLI.user.edit-details:cli'),
291+
('user:create', 'SoftLayer.CLI.user.create:cli'),
292+
290293

291294
('vlan', 'SoftLayer.CLI.vlan'),
292295
('vlan:detail', 'SoftLayer.CLI.vlan.detail:cli'),

SoftLayer/CLI/user/create.py

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
"""Creates a user """
2+
# :license: MIT, see LICENSE for more details.
3+
4+
import click
5+
import json
6+
import secrets
7+
import string
8+
9+
import SoftLayer
10+
from SoftLayer.CLI import columns as column_helper
11+
from SoftLayer.CLI import environment
12+
from SoftLayer.CLI import exceptions
13+
from SoftLayer.CLI import formatting
14+
15+
16+
from pprint import pprint as pp
17+
18+
@click.command()
19+
@click.argument('username')
20+
@click.option('--email', '-e', required=True,
21+
help="Email address for this user. Required for creation.")
22+
@click.option('--password', '-p', default=None, show_default=True,
23+
help="Password to set for this user. If no password is provided, user will be sent an email "
24+
"to generate one, which expires in 24 hours. '-p generate' will create a password for you. "
25+
"Passwords require 8+ characters, upper and lowercase, a number and a symbol.")
26+
@click.option('--from-user', '-u', default=None,
27+
help="Base user to use as a template for creating this user. "
28+
"Will default to the user running this command. Information provided in --template "
29+
"supersedes this template.")
30+
@click.option('--template', '-t', default=None,
31+
help="A json string describing https://softlayer.github.io/reference/datatypes/SoftLayer_User_Customer/")
32+
@click.option('--api-key', '-a', default=False, is_flag=True, help="Create an API key for this user.")
33+
@environment.pass_env
34+
def cli(env, username, email, password, from_user, template, api_key):
35+
"""Creates a user Users.
36+
37+
slcli user create my@email.com -e my@email.com -p generate -a -t '{"firstName": "Test", "lastName": "Testerson"}'
38+
Remember to set the permissions and access for this new user.
39+
"""
40+
41+
mgr = SoftLayer.UserManager(env.client)
42+
user_mask = ("mask[id, firstName, lastName, email, companyName, address1, city, country, postalCode, "
43+
"state, userStatusId, timezoneId]")
44+
from_user_id = None
45+
if from_user is None:
46+
user_template = mgr.get_current_user(objectmask=user_mask)
47+
from_user_id = user_template['id']
48+
else:
49+
from_user_id = helpers.resolve_id(mgr.resolve_ids, from_user, 'username')
50+
user_template = mgr.get_user(from_user_id, objectmask=user_mask)
51+
# If we send the ID back to the API, an exception will be thrown
52+
del user_template['id']
53+
54+
if template is not None:
55+
try:
56+
template_object = json.loads(template)
57+
for key in template_object:
58+
user_template[key] = template_object[key]
59+
except json.decoder.JSONDecodeError as ex:
60+
raise exceptions.ArgumentError("Unable to parse --template. %s" % ex.msg)
61+
62+
user_template['username'] = username
63+
if password == 'generate':
64+
password = generate_password()
65+
66+
user_template['email'] = email
67+
68+
if not env.skip_confirmations:
69+
table = formatting.KeyValueTable(['name', 'value'])
70+
for key in user_template:
71+
table.add_row([key, user_template[key]])
72+
table.add_row(['password', password])
73+
click.secho("You are about to create the following user...", fg='green')
74+
env.fout(table)
75+
if not formatting.confirm("Do you wish to continue?"):
76+
raise exceptions.CLIAbort("Canceling creation!")
77+
78+
result = mgr.create_user(user_template, password)
79+
new_api_key = None
80+
if api_key:
81+
click.secho("Adding API key...", fg='green')
82+
new_api_key = mgr.addApiAuthenticationKey(result['id'])
83+
84+
table = formatting.Table(['Username', 'Email', 'Password', 'API Key'])
85+
table.add_row([result['username'], result['email'], password, new_api_key])
86+
env.fout(table)
87+
88+
def generate_password():
89+
alphabet = string.ascii_letters + string.digits
90+
password = ''.join(secrets.choice(alphabet) for i in range(20)) # for a 20-character password
91+
special = ''.join(secrets.choice(string.punctuation) for i in range(3))
92+
return password + special
93+

SoftLayer/CLI/user/detail.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""List images."""
1+
"""User details."""
22
# :license: MIT, see LICENSE for more details.
33

44
import click

SoftLayer/CLI/user/edit-details.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
"""List Users."""
2+
# :license: MIT, see LICENSE for more details.
3+
4+
import click
5+
6+
import SoftLayer
7+
from SoftLayer.CLI import columns as column_helper
8+
from SoftLayer.CLI import environment
9+
from SoftLayer.CLI import formatting
10+
11+
12+
COLUMNS = [
13+
column_helper.Column('id', ('id',)),
14+
column_helper.Column('username', ('username',)),
15+
column_helper.Column('email', ('email',)),
16+
column_helper.Column('displayName', ('displayName',)),
17+
column_helper.Column('status', ('userStatus', 'name')),
18+
column_helper.Column('hardwareCount', ('hardwareCount',)),
19+
column_helper.Column('virtualGuestCount', ('virtualGuestCount',))
20+
]
21+
22+
DEFAULT_COLUMNS = [
23+
'id',
24+
'username',
25+
'email',
26+
'displayName'
27+
]
28+
29+
30+
@click.command()
31+
@click.option('--columns',
32+
callback=column_helper.get_formatter(COLUMNS),
33+
help='Columns to display. [options: %s]' % ', '.join(column.name for column in COLUMNS),
34+
default=','.join(DEFAULT_COLUMNS),
35+
show_default=True)
36+
@environment.pass_env
37+
def cli(env, columns):
38+
"""List Users."""
39+
40+
mgr = SoftLayer.UserManager(env.client)
41+
users = mgr.list_users()
42+
43+
table = formatting.Table(columns.columns)
44+
for user in users:
45+
table.add_row([value or formatting.blank()
46+
for value in columns.row(user)])
47+
48+
env.fout(table)

SoftLayer/fixtures/SoftLayer_User_Customer_CustomerPermission_Permission.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,15 @@
88
"key": "T_2",
99
"keyName": "TEST",
1010
"name": "A Testing Permission"
11+
},
12+
{
13+
"key": "T_3",
14+
"keyName": "TEST_3",
15+
"name": "A Testing Permission 3"
16+
},
17+
{
18+
"key": "T_4",
19+
"keyName": "TEST_4",
20+
"name": "A Testing Permission 4"
1121
}
1222
]

SoftLayer/managers/user.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,13 @@ def get_user(self, user_id, objectmask=None):
6666
objectmask = "mask[userStatus[name], parent[id, username]]"
6767
return self.user_service.getObject(id=user_id, mask=objectmask)
6868

69+
def get_current_user(self, objectmask=None):
70+
"""Calls SoftLayer_Account::getCurrentUser"""
71+
72+
if objectmask is None:
73+
objectmask = "mask[userStatus[name], parent[id, username]]"
74+
return self.account_service.getCurrentUser(mask=objectmask)
75+
6976
def get_all_permissions(self):
7077
"""Calls SoftLayer_User_CustomerPermissions_Permission::getAllObjects
7178
@@ -115,16 +122,19 @@ def permissions_from_user(self, user_id, from_user_id):
115122
:param int from_user_id: The use to base permissions from.
116123
:returns: True on success, Exception otherwise.
117124
"""
125+
118126
from_permissions = self.get_user_permissions(from_user_id)
119127
self.add_permissions(user_id, from_permissions)
120128
all_permissions = self.get_all_permissions()
121129
remove_permissions = []
130+
122131
for permission in all_permissions:
123132
# If permission does not exist for from_user_id add it to the list to be removed
124-
if _keyname_search(all_permissions, permission):
133+
if _keyname_search(from_permissions, permission['keyName']):
125134
continue
126135
else:
127136
remove_permissions.append({'keyName': permission['keyName']})
137+
128138
self.remove_permissions(user_id, remove_permissions)
129139
return True
130140

@@ -224,9 +234,19 @@ def format_permission_object(self, permissions):
224234
if _keyname_search(available_permissions, permission):
225235
pretty_permissions.append({'keyName': permission})
226236
else:
227-
raise exceptions.SoftLayerError("|%s| is not a valid permission" % permission)
237+
raise exceptions.SoftLayerError("'%s' is not a valid permission" % permission)
228238
return pretty_permissions
229239

240+
def create_user(self, user_object, password):
241+
"""Blindly sends user_object to SoftLayer_User_Customer::createObject
242+
243+
:param dictionary user_object: https://softlayer.github.io/reference/datatypes/SoftLayer_User_Customer/
244+
"""
245+
LOGGER.warning("Creating User %s", user_object['username'])
246+
return self.user_service.createObject(user_object, password, None)
247+
248+
def addApiAuthenticationKey(self, user_id):
249+
return self.user_service.addApiAuthenticationKey(id=user_id)
230250

231251
def _keyname_search(haystack, needle):
232252
for item in haystack:

tests/CLI/modules/user_tests.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99
import json
1010

1111

12+
#SoftLayer/CLI/user/detail.py 114-119, 128-134
13+
#SoftLayer/CLI/user/edit_permissions.py 28-29, 38
14+
#SoftLayer/CLI/user/permissions.py 25, 56
15+
1216
class UserCLITests(testing.TestCase):
1317

1418
"""User list tests"""

tests/managers/user_tests.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"""
66
import mock
77
import SoftLayer
8+
from SoftLayer import exceptions
89
from SoftLayer import testing
910

1011

@@ -89,3 +90,62 @@ def test_get_events_default(self):
8990
}
9091
}
9192
self.assert_called_with('SoftLayer_Event_Log', 'getAllObjects', filter=expected_filter)
93+
94+
def test_get_events_empty(self):
95+
event_mock = self.set_mock('SoftLayer_Event_Log', 'getAllObjects')
96+
event_mock.return_value = None
97+
result = self.manager.get_events(1234)
98+
99+
self.assert_called_with('SoftLayer_Event_Log', 'getAllObjects', filter=mock.ANY)
100+
self.assertEqual([{'eventName': 'No Events Found'}], result)
101+
102+
@mock.patch('SoftLayer.managers.user.UserManager.get_user_permissions')
103+
def test_permissions_from_user(self, user_permissions):
104+
user_permissions.return_value = [
105+
{"keyName": "TICKET_VIEW"},
106+
{"keyName": "TEST"}
107+
]
108+
removed_permissions = [
109+
{"keyName": "TEST_3"},
110+
{"keyName": "TEST_4"}
111+
]
112+
self.manager.permissions_from_user(1234, 5678)
113+
self.assert_called_with('SoftLayer_User_Customer', 'addBulkPortalPermission',
114+
args=(user_permissions.return_value,))
115+
self.assert_called_with('SoftLayer_User_Customer', 'removeBulkPortalPermission',
116+
args=(removed_permissions,))
117+
118+
def test_get_id_from_username_one_match(self):
119+
account_mock = self.set_mock('SoftLayer_Account', 'getUsers')
120+
account_mock.return_value = [{'id': 1234}]
121+
user_id = self.manager._get_id_from_username('testUser')
122+
expected_filter = {'users': {'username': {'operation': '_= testUser'}}}
123+
self.assert_called_with('SoftLayer_Account', 'getUsers', filter=expected_filter, mask="mask[id, username]")
124+
self.assertEqual([1234], user_id)
125+
126+
def test_get_id_from_username_multiple_match(self):
127+
account_mock = self.set_mock('SoftLayer_Account', 'getUsers')
128+
account_mock.return_value = [{'id': 1234}, {'id': 4567}]
129+
self.assertRaises(exceptions.SoftLayerError, self.manager._get_id_from_username, 'testUser')
130+
131+
def test_get_id_from_username_zero_match(self):
132+
account_mock = self.set_mock('SoftLayer_Account', 'getUsers')
133+
account_mock.return_value = []
134+
self.assertRaises(exceptions.SoftLayerError, self.manager._get_id_from_username, 'testUser')
135+
136+
def test_format_permission_object(self):
137+
result = self.manager.format_permission_object(['TEST'])
138+
self.assert_called_with('SoftLayer_User_Customer_CustomerPermission_Permission', 'getAllObjects')
139+
self.assertEqual([{'keyName': 'TEST'}], result)
140+
141+
def test_format_permission_object_all(self):
142+
result = self.manager.format_permission_object(['ALL'])
143+
service_name = 'SoftLayer_User_Customer_CustomerPermission_Permission'
144+
expected = [
145+
{'key': 'T_2', 'keyName': 'TEST', 'name': 'A Testing Permission'},
146+
{'key': 'T_3', 'keyName': 'TEST_3', 'name': 'A Testing Permission 3'},
147+
{'key': 'T_4', 'keyName': 'TEST_4', 'name': 'A Testing Permission 4'},
148+
{'key': 'T_1', 'keyName': 'TICKET_VIEW', 'name': 'View Tickets'}
149+
]
150+
self.assert_called_with(service_name, 'getAllObjects')
151+
self.assertEqual(expected, result)

0 commit comments

Comments
 (0)