Skip to content

Commit

Permalink
ios_user_global - Take in count no username question
Browse files Browse the repository at this point in the history
Currently, on IOS devices, when you enter a `no username` command, the
system request you a question to confirm the suppression.

To handle this part, I have done some change:
- I have "created" the module IosNetworkTemplate, a override class of
  NetworkTemplate that take care the case of commands with a specific
  prompt
- The ios_user_global module use this class for the User_globalTemplate

Second point, this module allow you to create the username by using the
old command line (C(username)) or the new one (C(user-name)).

I have done this part in a super class to allow other template module to
use this possibility.

I don't have done a super class for the assert function in tests module.
If you want, I can try to do it also.
  • Loading branch information
earendilfr committed Aug 23, 2023
1 parent fc60ee7 commit cf1e56d
Show file tree
Hide file tree
Showing 7 changed files with 468 additions and 108 deletions.
45 changes: 27 additions & 18 deletions plugins/module_utils/network/ios/argspec/user_global/user_global.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

from __future__ import absolute_import, division, print_function


__metaclass__ = type

#############################################
Expand All @@ -30,7 +29,8 @@


class User_globalArgs(object): # pylint: disable=R0903
"""The arg spec for the ios_user_global module"""
"""The arg spec for the ios_user_global module
"""

argument_spec = {
"config": {
Expand All @@ -52,9 +52,8 @@ class User_globalArgs(object): # pylint: disable=R0903
"default": 0,
"type": "int",
},
"value": {"type": "str", "required": True},
"value": {"type": "str"},
},
"no_log": True,
},
"level": {"type": "int"},
},
Expand All @@ -64,25 +63,35 @@ class User_globalArgs(object): # pylint: disable=R0903
"elements": "dict",
"options": {
"name": {"type": "str", "required": True},
"description": {"type": "str"},
"password": {
"command": {
"choices": ["new", "old"],
"default": "old",
"type": "str",
},
"parameters": {
"type": "dict",
"options": {
"type": {
"choices": ["password", "secret"],
"default": "secret",
"type": "str",
},
"hash": {
"choices": [0, 5, 6, 7, 8, 9],
"default": 0,
"type": "int",
"nopassword": {"type": "bool"},
"password": {
"type": "dict",
"options": {
"type": {
"choices": ["password", "secret"],
"default": "secret",
"type": "str",
},
"hash": {
"choices": [0, 5, 6, 7, 8, 9],
"default": 0,
"type": "int",
},
"value": {"type": "str"},
},
},
"value": {"type": "str", "required": True},
"privilege": {"type": "int"},
"view": {"type": "str"},
},
"no_log": True,
},
"privilege": {"type": "int"},
},
"type": "list",
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,11 @@ def __init__(self, module):
self.parsers = []
self.list_parsers = [
"enable",
"users",
]
self.list_complex_parsers = {
"old": "users",
"new": "user-name",
}

def execute_module(self):
"""Execute the module
Expand Down Expand Up @@ -89,6 +92,7 @@ def _compare(self, want, have):
"""
self.compare(parsers=self.parsers, want=want, have=have)
self._compare_lists_attrs(want, have)
self._compare_list_complex_parsers(want=want.get("users", {}), have=have.get("users", {}))

def _compare_lists_attrs(self, want, have):
"""Compare list of dict"""
Expand All @@ -104,6 +108,56 @@ def _compare_lists_attrs(self, want, have):
for key, haveing in iteritems(i_have):
self.addcmd(haveing, _parser, negate=True)

def _compare_list_complex_parsers(self, want, have):
"""Compare complex dict users with two different parsers"""
for key, wanting in iteritems(want):
haveing = have.pop(key, {})
w_command = wanting.get("command", "old")
w_parser = self.list_complex_parsers[w_command]
h_command = haveing.get("command", "old")
h_parser = self.list_complex_parsers[h_command]
if wanting != haveing:
if haveing and (
w_command != h_command
or h_command == "old"
and self.state in ["overridden", "replaced"]
):
self.addcmd(
haveing,
h_parser + ".name" if h_command == "new" else h_parser,
negate=True,
)
if wanting["command"] == "new":
self.addcmd(wanting, w_parser + ".name")
for k in wanting["parameters"].keys():
self.addcmd(wanting, w_parser + "." + k)
else:
self.addcmd(wanting, w_parser)
elif haveing and w_command == h_command and w_command == "new":
self.addcmd(wanting, w_parser + ".name")
for k, k_wanting in wanting["parameters"].items():
k_haveing = haveing["parameters"].pop(k, None)
if not k_haveing or k_wanting != k_haveing:
self.addcmd(wanting, w_parser + "." + k)
if self.state in ["overridden", "replaced"]:
for k, k_haveing in haveing["parameters"].items():
self.addcmd(haveing, h_parser + "." + k, negate=True)
else:
if w_command == "new":
self.addcmd(wanting, w_parser + ".name")
for k in wanting["parameters"].keys():
self.addcmd(wanting, w_parser + "." + k)
else:
self.addcmd(wanting, w_parser)
for key, haveing in have.items():
h_command = haveing.get("command", "old")
h_parser = self.list_complex_parsers[h_command]
self.addcmd(
haveing,
h_parser + ".name" if h_command == "new" else h_parser,
negate=True,
)

def _users_list_to_dict(self, data):
"""Convert all list of dicts to dicts of dicts"""
p_key = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ def __init__(self, module, subspec="config", options="options"):
self.argument_spec = User_globalArgs.argument_spec

def get_users_data(self, connection):
return connection.get("show running-config | section ^username|^enable")
return connection.get("show running-config | section ^username|^user-name|^enable")

def sort_list_dicts(self, objs):
p_key = {
Expand Down Expand Up @@ -69,6 +69,7 @@ def populate_facts(self, connection, ansible_facts, data=None):
user_global_parser = User_globalTemplate(lines=data.splitlines(), module=self._module)
objs = user_global_parser.parse()

objs["users"] = list(objs["users"].values())
if objs:
self.sort_list_dicts(objs)

Expand Down
64 changes: 64 additions & 0 deletions plugins/module_utils/network/ios/rm_templates/generic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# -*- coding: utf-8 -*-
# Copyright 2023 Red Hat
# 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

"""
The User_global parser templates file. This contains
a list of parser definitions and associated functions that
facilitates both facts gathering and native command generation for
the given network resource.
"""

from copy import deepcopy

from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.network_template import (
NetworkTemplate,
)


class IosNetworkTemplate(NetworkTemplate):
def __init__(self, lines=None, tmplt=None, prefix=None, module=None):
super(IosNetworkTemplate, self).__init__(
lines=lines,
tmplt=tmplt,
prefix=prefix,
module=module,
)

def _render(self, tmplt, data, negate):
if isinstance(tmplt, dict) and "command" in tmplt:
res = deepcopy(tmplt)
try:
if callable(res["command"]):
res["command"] = res["command"](data)
else:
res["command"] = self._template(
value=res["command"],
variables=data,
fail_on_undefined=False,
)
except KeyError:
return None

if res:
if negate:
rem = "{0} ".format(self._prefix.get("remove", "no"))
if isinstance(res["command"], list):
res["command"] = list(map(rem, res["command"]))
else:
res["command"] = rem + res["command"]
return res
else:
set_cmd = "{0} ".format(self._prefix.get("set", ""))
if isinstance(res["command"], list):
res["command"] = list(map(set_cmd, res["command"]))
else:
res["command"] = set_cmd + res["command"]
return res
return super(IosNetworkTemplate, self)._render(tmplt, data, negate)
Loading

0 comments on commit cf1e56d

Please sign in to comment.