icx: new module icx_user (#60007)

* new module

* new terminal

* new terminal

* new cliconf

* cliconf

* icx cliconf

* icx_cliconf

* icx test units module

* icx units module

* icx banner unit test

* PR changes resolved

* changes resolved

* Changes Resolved

* check_running_config changes resolved

* added notes

* removed icx rst

* new changes

* deleted icx rst

* icx .rst

* modified platform_index.rst

* modified platform_index.rst

* changes resolved

* PR comments resolved

* Update platform_index.rst

PR comment resolved

* test

* Revert "test"

This reverts commit 99b72c6614.

* new module icx_user

* new changes

* new changes

* new changes

* new changes

* new changes

* new changes

* changes

* changes

* new fixes

* new changes

* notes updated
This commit is contained in:
sushma-alethea 2019-08-27 09:25:56 +05:30 committed by Ganesh Nalawade
parent a68d91d0a7
commit b09db0ba9e
3 changed files with 596 additions and 0 deletions

View file

@ -0,0 +1,391 @@
#!/usr/bin/python
# Copyright: Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = """
---
module: icx_user
version_added: "2.9"
author: "Ruckus Wireless (@Commscope)"
short_description: Manage the user accounts on Ruckus ICX 7000 series switches.
description:
- This module creates or updates user account on network devices. It allows playbooks to manage
either individual usernames or the aggregate of usernames in the
current running config. It also supports purging usernames from the
configuration that are not explicitly defined.
notes:
- Tested against ICX 10.1.
- For information on using ICX platform, see L(the ICX OS Platform Options guide,../network/user_guide/platform_icx.html).
options:
aggregate:
description:
- The set of username objects to be configured on the remote
ICX device. The list entries can either be the username
or a hash of username and properties. This argument is mutually
exclusive with the C(name) argument.
aliases: ['users', 'collection']
type: list
suboptions:
name:
description:
- The username to be configured on the ICX device.
required: true
type: str
configured_password:
description: The password to be configured on the ICX device.
type: str
update_password:
description:
- This argument will instruct the module when to change the password. When
set to C(always), the password will always be updated in the device
and when set to C(on_create) the password will be updated only if
the username is created.
choices: ['on_create', 'always']
type: str
privilege:
description:
- The privilege level to be granted to the user
choices: ['0', '4', '5']
type: str
nopassword:
description:
- Defines the username without assigning
a password. This will allow the user to login to the system
without being authenticated by a password.
type: bool
state:
description:
- Configures the state of the username definition
as it relates to the device operational configuration. When set
to I(present), the username(s) should be configured in the device active
configuration and when set to I(absent) the username(s) should not be
in the device active configuration
choices: ['present', 'absent']
type: str
access_time:
description:
- This parameter indicates the time the file's access time should be set to.
Should be preserve when no modification is required, YYYYMMDDHHMM.SS when using default time format, or now.
Default is None meaning that preserve is the default for state=[file,directory,link,hard] and now is default for state=touch
type: str
check_running_config:
description:
- Check running configuration. This can be set as environment variable.
Module will use environment variable value(default:True), unless it is overriden, by specifying it as module parameter.
type: bool
name:
description:
- The username to be configured on the ICX device.
required: true
type: str
configured_password:
description: The password to be configured on the ICX device.
type: str
update_password:
description:
- This argument will instruct the module when to change the password. When
set to C(always), the password will always be updated in the device
and when set to C(on_create) the password will be updated only if
the username is created.
default: always
choices: ['on_create', 'always']
type: str
privilege:
description:
- The privilege level to be granted to the user
default: 0
choices: ['0', '4', '5']
type: str
nopassword:
description:
- Defines the username without assigning
a password. This will allow the user to login to the system
without being authenticated by a password.
type: bool
purge:
description:
- If set to true module will remove any previously
configured usernames on the device except the current defined set of users.
type: bool
default: false
state:
description:
- Configures the state of the username definition
as it relates to the device operational configuration. When set
to I(present), the username(s) should be configured in the device active
configuration and when set to I(absent) the username(s) should not be
in the device active configuration
default: present
choices: ['present', 'absent']
type: str
access_time:
description:
- This parameter indicates the time the file's access time should be set to.
Should be preserve when no modification is required, YYYYMMDDHHMM.SS when using default time format, or now.
Default is None meaning that preserve is the default for state=[file,directory,link,hard] and now is default for state=touch
type: str
check_running_config:
description:
- Check running configuration. This can be set as environment variable.
Module will use environment variable value(default:True), unless it is overriden, by specifying it as module parameter.
type: bool
default: yes
"""
EXAMPLES = """
- name: create a new user without password
icx_user:
name: user1
nopassword: true
- name: create a new user with password
icx_user:
name: user1
configured_password: 'newpassword'
- name: remove users
icx_user:
name: user1
state: absent
- name: set user privilege level to 5
icx_user:
name: user1
privilege: 5
"""
RETURN = """
commands:
description: The list of configuration mode commands to send to the device
returned: always
type: list
sample:
- username ansible nopassword
- username ansible password-string alethea123
- no username ansible
- username ansible privilege 5
- username ansible enable
"""
from copy import deepcopy
import re
import base64
import hashlib
from functools import partial
from ansible.module_utils.basic import AnsibleModule, env_fallback
from ansible.module_utils.network.common.utils import remove_default_spec
from ansible.module_utils.connection import exec_command
from ansible.module_utils.six import iteritems
from ansible.module_utils.network.icx.icx import get_config, load_config
def get_param_value(key, item, module):
if not item.get(key):
value = module.params[key]
else:
value_type = module.argument_spec[key].get('type', 'str')
type_checker = module._CHECK_ARGUMENT_TYPES_DISPATCHER[value_type]
type_checker(item[key])
value = item[key]
validator = globals().get('validate_%s' % key)
if all((value, validator)):
validator(value, module)
return value
def map_params_to_obj(module):
users = module.params['aggregate']
if not users:
if not module.params['name'] and module.params['purge']:
return list()
elif not module.params['name']:
module.fail_json(msg='username is required')
else:
aggregate = [{'name': module.params['name']}]
else:
aggregate = list()
for item in users:
if not isinstance(item, dict):
aggregate.append({'name': item})
elif 'name' not in item:
module.fail_json(msg='name is required')
else:
aggregate.append(item)
objects = list()
for item in aggregate:
get_value = partial(get_param_value, item=item, module=module)
item['configured_password'] = get_value('configured_password')
item['nopassword'] = get_value('nopassword')
item['privilege'] = get_value('privilege')
item['state'] = get_value('state')
objects.append(item)
return objects
def parse_privilege(data):
match = re.search(r'privilege (\S)', data, re.M)
if match:
return match.group(1)
def map_config_to_obj(module):
compare = module.params['check_running_config']
data = get_config(module, flags=['| include username'], compare=compare)
match = re.findall(r'(?:^(?:u|\s{2}u))sername (\S+)', data, re.M)
if not match:
return list()
instances = list()
for user in set(match):
regex = r'username %s .+$' % user
cfg = re.findall(regex, data, re.M)
cfg = '\n'.join(cfg)
obj = {
'name': user,
'state': 'present',
'nopassword': 'nopassword' in cfg,
'configured_password': None,
'privilege': parse_privilege(cfg)
}
instances.append(obj)
return instances
def map_obj_to_commands(updates, module):
commands = list()
state = module.params['state']
update_password = module.params['update_password']
def needs_update(want, have, x):
return want.get(x) and (want.get(x) != have.get(x))
def add(command, want, x):
command.append('username %s %s' % (want['name'], x))
for update in updates:
want, have = update
if want['state'] == 'absent':
commands.append(user_del_cmd(want['name']))
if needs_update(want, have, 'privilege'):
add(commands, want, 'privilege %s password %s' % (want['privilege'], want['configured_password']))
else:
if needs_update(want, have, 'configured_password'):
if update_password == 'always' or not have:
add(commands, want, '%spassword %s' % ('privilege ' + str(have.get('privilege')) +
" " if have.get('privilege') is not None else '', want['configured_password']))
if needs_update(want, have, 'nopassword'):
if want['nopassword']:
add(commands, want, 'nopassword')
if needs_update(want, have, 'access_time'):
add(commands, want, 'access-time %s' % want['access_time'])
if needs_update(want, have, 'expiry_days'):
add(commands, want, 'expires %s' % want['expiry_days'])
return commands
def update_objects(want, have):
updates = list()
for entry in want:
item = next((i for i in have if i['name'] == entry['name']), None)
if all((item is None, entry['state'] == 'present')):
updates.append((entry, {}))
elif all((have == [], entry['state'] == 'absent')):
for key, value in iteritems(entry):
if key not in ['update_password']:
updates.append((entry, item))
break
elif item:
for key, value in iteritems(entry):
if key not in ['update_password']:
if value is not None and value != item.get(key):
updates.append((entry, item))
break
return updates
def user_del_cmd(username):
return 'no username %s' % username
def main():
"""entry point for module execution
"""
element_spec = dict(
name=dict(),
configured_password=dict(no_log=True),
nopassword=dict(type='bool', default=False),
update_password=dict(default='always', choices=['on_create', 'always']),
privilege=dict(type='str', choices=['0', '4', '5']),
access_time=dict(type='str'),
state=dict(default='present', choices=['present', 'absent']),
check_running_config=dict(default=True, type='bool', fallback=(env_fallback, ['ANSIBLE_CHECK_ICX_RUNNING_CONFIG']))
)
aggregate_spec = deepcopy(element_spec)
aggregate_spec['name'] = dict(required=True)
remove_default_spec(aggregate_spec)
argument_spec = dict(
aggregate=dict(type='list', elements='dict', options=aggregate_spec, aliases=['users', 'collection']),
purge=dict(type='bool', default=False)
)
argument_spec.update(element_spec)
mutually_exclusive = [('name', 'aggregate')]
module = AnsibleModule(argument_spec=argument_spec,
mutually_exclusive=mutually_exclusive,
supports_check_mode=True)
result = {'changed': False}
exec_command(module, 'skip')
want = map_params_to_obj(module)
have = map_config_to_obj(module)
commands = map_obj_to_commands(update_objects(want, have), module)
if module.params['purge']:
want_users = [x['name'] for x in want]
have_users = [x['name'] for x in have]
for item in set(have_users).difference(want_users):
if item != 'admin':
commands.append(user_del_cmd(item))
result["commands"] = commands
if commands:
if not module.check_mode:
load_config(module, commands)
result['changed'] = True
module.exit_json(**result)
if __name__ == '__main__':
main()

View file

@ -0,0 +1,8 @@
username ale1 privilege 5 password .....
username ale1 expires 3
username ale2 privilege 5 password .....
username ale2 expires 3
username ale3 privilege 5 password .....
username ale3 expires 3
username ale4 privilege 5 password .....
username ale4 expires 3

View file

@ -0,0 +1,197 @@
# Copyright: (c) 2019, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
from units.compat.mock import patch
from ansible.modules.network.icx import icx_user
from units.modules.utils import set_module_args
from .icx_module import TestICXModule, load_fixture
class TestICXSCPModule(TestICXModule):
module = icx_user
def setUp(self):
super(TestICXSCPModule, self).setUp()
self.mock_get_config = patch('ansible.modules.network.icx.icx_user.get_config')
self.get_config = self.mock_get_config.start()
self.mock_load_config = patch('ansible.modules.network.icx.icx_user.load_config')
self.load_config = self.mock_load_config.start()
self.mock_exec_command = patch('ansible.modules.network.icx.icx_user.exec_command')
self.exec_command = self.mock_exec_command.start()
self.set_running_config()
def tearDown(self):
super(TestICXSCPModule, self).tearDown()
self.mock_get_config.stop()
self.mock_load_config.stop()
self.mock_exec_command.stop()
def load_fixtures(self, commands=None):
compares = None
def load_file(*args, **kwargs):
module = args
for arg in args:
if arg.params['check_running_config'] is True:
return load_fixture('show_running-config_include_username.txt').strip()
else:
return ''
self.get_config.side_effect = load_file
self.load_config.return_value = None
def test_icx_user_create_new_with_password(self):
set_module_args(dict(name='ale6', configured_password='alethea123'))
if not self.ENV_ICX_USE_DIFF:
commands = ['username ale6 password alethea123']
self.execute_module(commands=commands, changed=True)
else:
commands = ['username ale6 password alethea123']
self.execute_module(commands=commands, changed=True)
def test_icx_user_create_new_with_password_and_privilege(self):
set_module_args(dict(name='ale6', privilege="5", configured_password='alethea123'))
if not self.ENV_ICX_USE_DIFF:
commands = ['username ale6 privilege 5 password alethea123']
self.execute_module(commands=commands, changed=True)
else:
commands = ['username ale6 privilege 5 password alethea123']
self.execute_module(commands=commands, changed=True)
def test_icx_user_update_privilege(self):
set_module_args(dict(name='ale1', privilege="0", configured_password='alethea123'))
if not self.ENV_ICX_USE_DIFF:
commands = ['username ale1 privilege 0 password alethea123']
self.execute_module(commands=commands, changed=True)
else:
commands = ['username ale1 privilege 0 password alethea123']
self.execute_module(commands=commands, changed=True)
def test_icx_user_update_password(self):
set_module_args(dict(name='ale1', configured_password='alethea123'))
if not self.ENV_ICX_USE_DIFF:
commands = ['username ale1 password alethea123'] # previous privilage will be added
self.execute_module(commands=commands, changed=True)
else:
commands = ['username ale1 privilege 5 password alethea123'] # previous privilage will be added
self.execute_module(commands=commands, changed=True)
def test_icx_user_update_password_compare(self):
set_module_args(dict(name='ale1', configured_password='alethea123', check_running_config=True))
if not self.ENV_ICX_USE_DIFF:
commands = ['username ale1 privilege 5 password alethea123'] # previous privilage will be added
self.execute_module(commands=commands, changed=True)
else:
commands = ['username ale1 privilege 5 password alethea123'] # previous privilage will be added
self.execute_module(commands=commands, changed=True)
def test_icx_user_delete_user(self):
set_module_args(dict(name='ale1', state="absent"))
if not self.ENV_ICX_USE_DIFF:
commands = ['no username ale1']
self.execute_module(commands=commands, changed=True)
else:
commands = ['no username ale1']
self.execute_module(commands=commands, changed=True)
def test_icx_user_agregate(self):
set_module_args(dict(aggregate=[
{
"name": 'ale6',
"configured_password": 'alethea123'
},
{
"name": 'ale7',
"configured_password": 'alethea123'
}
]))
if not self.ENV_ICX_USE_DIFF:
commands = [
'username ale6 password alethea123',
'username ale7 password alethea123'
]
self.execute_module(commands=commands, changed=True)
else:
commands = [
'username ale6 password alethea123',
'username ale7 password alethea123'
]
self.execute_module(commands=commands, changed=True)
def test_icx_user_not_update_old_user_password(self):
set_module_args(dict(aggregate=[
{
"name": 'ale6',
"configured_password": 'alethea123'
},
{
"name": 'ale1',
"configured_password": 'alethea123',
},
],
update_password='on_create'
))
if not self.ENV_ICX_USE_DIFF:
commands = [
'username ale1 password alethea123',
'username ale6 password alethea123',
]
self.execute_module(commands=commands, changed=True)
else:
commands = [
'username ale6 password alethea123',
]
self.execute_module(commands=commands, changed=True)
def test_icx_user_only_update_changed_settings(self):
set_module_args(dict(aggregate=[
{
"name": 'ale1'
},
{
"name": 'ale2',
"privilege": 5,
"configured_password": "ale123"
},
{
"name": 'ale3',
"privilege": 4,
"configured_password": "ale123"
}
],
update_password="on_create"
))
if not self.ENV_ICX_USE_DIFF:
commands = [
'username ale2 privilege 5 password ale123',
'username ale3 privilege 4 password ale123'
]
self.execute_module(commands=commands, changed=True)
else:
commands = [
'username ale3 privilege 4 password ale123'
]
self.execute_module(commands=commands, changed=True)
def test_icx_user_purge(self):
set_module_args(dict(aggregate=[
{
"name": 'ale1'
}
],
purge=True
))
if not self.ENV_ICX_USE_DIFF:
commands = [
]
self.execute_module(commands=commands, changed=False)
else:
commands = [
'no username ale2',
'no username ale3',
'no username ale4'
]
self.execute_module(commands=commands, changed=True)