diff --git a/lib/ansible/module_utils/eos.py b/lib/ansible/module_utils/eos.py
index 28bcfb5a6cf..d44710eed09 100644
--- a/lib/ansible/module_utils/eos.py
+++ b/lib/ansible/module_utils/eos.py
@@ -42,7 +42,7 @@ _DEVICE_CONNECTION = None
eos_argument_spec = {
'host': dict(),
'port': dict(type='int'),
- 'username': dict(fallback=(env_fallback, ['ANSIBLE_NET_USERNAME'])),
+ 'username': dict(fallback=(env_fallback, ['ANSIBLE_NET_USERNAME']), aliases=['name']),
'password': dict(fallback=(env_fallback, ['ANSIBLE_NET_PASSWORD']), no_log=True),
'ssh_keyfile': dict(fallback=(env_fallback, ['ANSIBLE_NET_SSH_KEYFILE']), type='path'),
diff --git a/lib/ansible/modules/network/eos/eos_user.py b/lib/ansible/modules/network/eos/eos_user.py
index a802b8bb027..9cc6cb964fd 100644
--- a/lib/ansible/modules/network/eos/eos_user.py
+++ b/lib/ansible/modules/network/eos/eos_user.py
@@ -143,6 +143,7 @@ session_name:
type: str
sample: ansible_1479315771
"""
+
import re
from functools import partial
@@ -165,10 +166,16 @@ def map_obj_to_commands(updates, module):
want, have = update
needs_update = lambda x: want.get(x) and (want.get(x) != have.get(x))
- add = lambda x: commands.append('username %s %s' % (want['username'], x))
+ if 'name' in want:
+ add = lambda x: commands.append('username %s %s' % (want['name'], x))
+ else:
+ add = lambda x: commands.append('username %s %s' % (want['username'], x))
if want['state'] == 'absent':
- commands.append('no username %s' % want['username'])
+ if 'name' in want:
+ commands.append('no username %s' % want['name'])
+ else:
+ commands.append('no username %s' % want['username'])
continue
if needs_update('role'):
@@ -188,7 +195,10 @@ def map_obj_to_commands(updates, module):
if want['nopassword']:
add('nopassword')
else:
- add('no username %s nopassword' % want['username'])
+ if 'name' in want:
+ add('no username %s nopassword' % want['name'])
+ else:
+ add('no username %s nopassword' % want['username'])
return commands
@@ -266,7 +276,7 @@ def map_params_to_obj(module):
for item in users:
if not isinstance(item, dict):
collection.append({'username': item})
- elif 'username' not in item:
+ elif all(u not in item for u in ['username', 'name']):
module.fail_json(msg='username is required')
else:
collection.append(item)
@@ -288,7 +298,11 @@ def map_params_to_obj(module):
def update_objects(want, have):
updates = list()
for entry in want:
- item = next((i for i in have if i['username'] == entry['username']), None)
+ if 'name' in entry:
+ item = next((i for i in have if i['username'] == entry['name']), None)
+ else:
+ item = next((i for i in have if i['username'] == entry['username']), None)
+
if all((item is None, entry['state'] == 'present')):
updates.append((entry, {}))
elif item:
@@ -301,8 +315,8 @@ def main():
""" main entry point for module execution
"""
argument_spec = dict(
- users=dict(type='list'),
- username=dict(),
+ users=dict(type='list', aliases=['collection']),
+ username=dict(aliases=['name']),
password=dict(no_log=True),
nopassword=dict(type='bool'),
@@ -318,7 +332,6 @@ def main():
)
argument_spec.update(eos_argument_spec)
-
mutually_exclusive = [('username', 'users')]
module = AnsibleModule(argument_spec=argument_spec,
@@ -338,7 +351,7 @@ def main():
commands = map_obj_to_commands(update_objects(want, have), module)
if module.params['purge']:
- want_users = [x['username'] for x in want]
+ want_users = [x['username'] if 'username' in x else x['name'] for x in want]
have_users = [x['username'] for x in have]
for item in set(have_users).difference(want_users):
if item != 'admin':
diff --git a/lib/ansible/modules/network/junos/junos_user.py b/lib/ansible/modules/network/junos/junos_user.py
index 932b34b6839..3738e88e348 100644
--- a/lib/ansible/modules/network/junos/junos_user.py
+++ b/lib/ansible/modules/network/junos/junos_user.py
@@ -220,7 +220,7 @@ def main():
""" main entry point for module execution
"""
argument_spec = dict(
- users=dict(type='list'),
+ users=dict(type='list', aliases=['collection']),
name=dict(),
full_name=dict(),
diff --git a/lib/ansible/modules/network/net_user.py b/lib/ansible/modules/network/net_user.py
new file mode 100644
index 00000000000..d1923d7793e
--- /dev/null
+++ b/lib/ansible/modules/network/net_user.py
@@ -0,0 +1,138 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# (c) 2017, Ansible by Red Hat, inc
+#
+# This file is part of Ansible by Red Hat
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see .
+#
+
+ANSIBLE_METADATA = {'metadata_version': '1.0',
+ 'status': ['preview'],
+ 'supported_by': 'core'}
+
+DOCUMENTATION = """
+---
+module: net_user
+version_added: "2.4"
+author: "Trishna Guha (@trishnag)"
+short_description: Manage the collection of local users on network device
+description:
+ - This module provides declarative management of the local usernames
+ configured on network devices. It allows playbooks to manage
+ either individual usernames or the collection of usernames in the
+ current running config. It also supports purging usernames from the
+ configuration that are not explicitly defined.
+options:
+ collection:
+ description:
+ - The set of username objects to be configured on the remote
+ network 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.
+ name:
+ description:
+ - The username to be configured on the remote network device.
+ This argument accepts a string value and is mutually exclusive
+ with the C(collection) argument.
+ Please note that this option is not same as C(provider username).
+ password:
+ description:
+ - The password to be configured on the remote network device. The
+ password needs to be provided in clear and it will be encrypted
+ on the device.
+ Please note that this option is not same as C(provider password).
+ update_password:
+ description:
+ - Since passwords are encrypted in the device running config, 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']
+ privilege:
+ description:
+ - The C(privilege) argument configures the privilege level of the
+ user when logged into the system. This argument accepts integer
+ values in the range of 1 to 15.
+ role:
+ description:
+ - Configures the role for the username in the
+ device running configuration. The argument accepts a string value
+ defining the role name. This argument does not check if the role
+ has been configured on the device.
+ sshkey:
+ description:
+ - Specifies the SSH public key to configure
+ for the given username. This argument accepts a valid SSH key value.
+ 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:
+ - Instructs the module to consider the
+ resource definition absolute. It will remove any previously
+ configured usernames on the device with the exception of the
+ `admin` user (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']
+"""
+
+EXAMPLES = """
+- name: create a new user
+ net_user:
+ name: ansible
+ sshkey: "{{ lookup('file', '~/.ssh/id_rsa.pub') }}"
+ state: present
+- name: remove all users except admin
+ net_user:
+ purge: yes
+- name: set multiple users to privilege level 15
+ net_user:
+ collection:
+ - name: netop
+ - name: netend
+ privilege: 15
+ state: present
+- name: Change Password for User netop
+ net_user:
+ name: netop
+ password: "{{ new_password }}"
+ update_password: always
+ state: present
+"""
+
+RETURN = """
+commands:
+ description: The list of configuration mode commands to send to the device
+ returned: always
+ type: list
+ sample:
+ - username ansible secret password
+ - username admin secret admin
+"""
diff --git a/lib/ansible/modules/network/nxos/nxos_user.py b/lib/ansible/modules/network/nxos/nxos_user.py
index 9e0495352bf..b86228c2388 100644
--- a/lib/ansible/modules/network/nxos/nxos_user.py
+++ b/lib/ansible/modules/network/nxos/nxos_user.py
@@ -298,13 +298,13 @@ def main():
""" main entry point for module execution
"""
argument_spec = dict(
- users=dict(type='list', no_log=True),
+ users=dict(type='list', no_log=True, aliases=['collection']),
name=dict(),
password=dict(no_log=True),
update_password=dict(default='always', choices=['on_create', 'always']),
- roles=dict(type='list'),
+ roles=dict(type='list', aliases=['role']),
sshkey=dict(),
diff --git a/lib/ansible/plugins/action/net_user.py b/lib/ansible/plugins/action/net_user.py
new file mode 100644
index 00000000000..a4ee4db0b6e
--- /dev/null
+++ b/lib/ansible/plugins/action/net_user.py
@@ -0,0 +1,26 @@
+# (c) 2017, Ansible Inc,
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see .
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+from ansible.plugins.action.net_base import ActionModule as _ActionModule
+
+
+class ActionModule(_ActionModule):
+ def run(self, tmp=None, task_vars=None):
+ result = super(ActionModule, self).run(tmp, task_vars)
+ return result
diff --git a/test/integration/platform_agnostic.yaml b/test/integration/platform_agnostic.yaml
index b263ffcd0ea..da11626c072 100644
--- a/test/integration/platform_agnostic.yaml
+++ b/test/integration/platform_agnostic.yaml
@@ -11,3 +11,4 @@
- { role: net_system, when: "limit_to in ['*', 'net_system']" }
- { role: net_banner, when: "limit_to in ['*', 'net_banner']" }
- { role: net_command, when: "limit_to in ['*', 'net_command']" }
+ - { role: net_user, when: "limit_to_in ['*', 'net_user']" }
diff --git a/test/integration/targets/net_user/aliases b/test/integration/targets/net_user/aliases
new file mode 100644
index 00000000000..93151a8d9df
--- /dev/null
+++ b/test/integration/targets/net_user/aliases
@@ -0,0 +1 @@
+network/ci
diff --git a/test/integration/targets/net_user/defaults/main.yaml b/test/integration/targets/net_user/defaults/main.yaml
new file mode 100644
index 00000000000..5f709c5aac1
--- /dev/null
+++ b/test/integration/targets/net_user/defaults/main.yaml
@@ -0,0 +1,2 @@
+---
+testcase: "*"
diff --git a/test/integration/targets/net_user/tasks/cli.yaml b/test/integration/targets/net_user/tasks/cli.yaml
new file mode 100644
index 00000000000..46d86dd6988
--- /dev/null
+++ b/test/integration/targets/net_user/tasks/cli.yaml
@@ -0,0 +1,16 @@
+---
+- name: collect all cli test cases
+ find:
+ paths: "{{ role_path }}/tests/cli"
+ patterns: "{{ testcase }}.yaml"
+ register: test_cases
+ delegate_to: localhost
+
+- name: set test_items
+ set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}"
+
+- name: run test case
+ include: "{{ test_case_to_run }}"
+ with_items: "{{ test_items }}"
+ loop_control:
+ loop_var: test_case_to_run
diff --git a/test/integration/targets/net_user/tasks/main.yaml b/test/integration/targets/net_user/tasks/main.yaml
new file mode 100644
index 00000000000..415c99d8b12
--- /dev/null
+++ b/test/integration/targets/net_user/tasks/main.yaml
@@ -0,0 +1,2 @@
+---
+- { include: cli.yaml, tags: ['cli'] }
diff --git a/test/integration/targets/net_user/tests/cli/userprivilege.yaml b/test/integration/targets/net_user/tests/cli/userprivilege.yaml
new file mode 100644
index 00000000000..3a2ae58d61a
--- /dev/null
+++ b/test/integration/targets/net_user/tests/cli/userprivilege.yaml
@@ -0,0 +1,3 @@
+---
+- include: "{{ role_path }}/tests/eos/userprivilege.yaml"
+ when: hostvars[inventory_hostname]['ansible_network_os'] == 'eos'
diff --git a/test/integration/targets/net_user/tests/cli/userrole.yaml b/test/integration/targets/net_user/tests/cli/userrole.yaml
new file mode 100644
index 00000000000..bc2254280c5
--- /dev/null
+++ b/test/integration/targets/net_user/tests/cli/userrole.yaml
@@ -0,0 +1,6 @@
+---
+- include: "{{ role_path }}/tests/nxos/userrole.yaml"
+ when: hostvars[inventory_hostname]['ansible_network_os'] == 'nxos'
+
+- include: "{{ role_path }}/tests/eos/userrole.yaml"
+ when: hostvars[inventory_hostname]['ansible_network_os'] == 'eos'
diff --git a/test/integration/targets/net_user/tests/cli/users.yaml b/test/integration/targets/net_user/tests/cli/users.yaml
new file mode 100644
index 00000000000..bd3de7894bc
--- /dev/null
+++ b/test/integration/targets/net_user/tests/cli/users.yaml
@@ -0,0 +1,6 @@
+---
+- include: "{{ role_path }}/tests/nxos/users.yaml"
+ when: hostvars[inventory_hostname]['ansible_network_os'] == 'nxos'
+
+- include: "{{ role_path }}/tests/eos/users.yaml"
+ when: hostvars[inventory_hostname]['ansible_network_os'] == 'eos'
diff --git a/test/integration/targets/net_user/tests/eos/userprivilege.yaml b/test/integration/targets/net_user/tests/eos/userprivilege.yaml
new file mode 100644
index 00000000000..9ad6b67bbfb
--- /dev/null
+++ b/test/integration/targets/net_user/tests/eos/userprivilege.yaml
@@ -0,0 +1,19 @@
+---
+- name: Set user to privilege level 15
+ net_user:
+ name: netop
+ privilege: 15
+ state: present
+ authorize: yes
+ provider: "{{ cli }}"
+ register: result
+
+- assert:
+ that:
+ - 'result.changed == true'
+ - 'result.commands == ["username netop privilege 15"]'
+
+- name: tearDown
+ net_user:
+ purge: yes
+ provider: "{{ cli }}"
diff --git a/test/integration/targets/net_user/tests/eos/userrole.yaml b/test/integration/targets/net_user/tests/eos/userrole.yaml
new file mode 100644
index 00000000000..b85806a08fa
--- /dev/null
+++ b/test/integration/targets/net_user/tests/eos/userrole.yaml
@@ -0,0 +1,22 @@
+---
+- name: Set multiple users role
+ net_user:
+ collection:
+ - name: netop
+ - name: netend
+ role: network-operator
+ state: present
+ authorize: yes
+ provider: "{{ cli }}"
+ register: result
+
+- assert:
+ that:
+ - 'result.changed == true'
+ - 'result.commands == ["username netop role network-operator", "username netend role network-operator"]'
+
+- name: tearDown
+ net_user:
+ purge: yes
+ authorize: yes
+ provider: "{{ cli }}"
diff --git a/test/integration/targets/net_user/tests/eos/users.yaml b/test/integration/targets/net_user/tests/eos/users.yaml
new file mode 100644
index 00000000000..2719173d9e6
--- /dev/null
+++ b/test/integration/targets/net_user/tests/eos/users.yaml
@@ -0,0 +1,23 @@
+---
+- name: Create user
+ net_user:
+ name: netop
+ state: present
+ provider: "{{ cli }}"
+ register: result
+
+- assert:
+ that:
+ - 'result.changed == true'
+ - 'result.commands == ["username netop"]'
+
+- name: Purge users
+ net_user:
+ purge: yes
+ provider: "{{ cli }}"
+ register: result
+
+- assert:
+ that:
+ - 'result.changed == true'
+ - 'result.commands == ["no username netop"]'
diff --git a/test/integration/targets/net_user/tests/nxos/userrole.yaml b/test/integration/targets/net_user/tests/nxos/userrole.yaml
new file mode 100644
index 00000000000..cd49e8e36da
--- /dev/null
+++ b/test/integration/targets/net_user/tests/nxos/userrole.yaml
@@ -0,0 +1,20 @@
+---
+- name: Set multiple users role
+ net_user:
+ collection:
+ - name: netop
+ - name: netend
+ role: network-operator
+ state: present
+ provider: "{{ cli }}"
+ register: result
+
+- assert:
+ that:
+ - 'result.changed == true'
+ - '"role network-operator" in result.commands'
+
+- name: tearDown
+ net_user:
+ purge: yes
+ provider: "{{ cli }}"
diff --git a/test/integration/targets/net_user/tests/nxos/users.yaml b/test/integration/targets/net_user/tests/nxos/users.yaml
new file mode 100644
index 00000000000..2719173d9e6
--- /dev/null
+++ b/test/integration/targets/net_user/tests/nxos/users.yaml
@@ -0,0 +1,23 @@
+---
+- name: Create user
+ net_user:
+ name: netop
+ state: present
+ provider: "{{ cli }}"
+ register: result
+
+- assert:
+ that:
+ - 'result.changed == true'
+ - 'result.commands == ["username netop"]'
+
+- name: Purge users
+ net_user:
+ purge: yes
+ provider: "{{ cli }}"
+ register: result
+
+- assert:
+ that:
+ - 'result.changed == true'
+ - 'result.commands == ["no username netop"]'