Various bigip_user fixes (#33495)

There was a bit of refactoring that happened for coding standards.
Additionally, a bug fix was made for changing the root password
This commit is contained in:
Tim Rupp 2017-12-02 20:43:24 -08:00 committed by GitHub
parent bcda0db7db
commit a4aa00f556
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 152 additions and 164 deletions

View file

@ -189,8 +189,7 @@ shell:
sample: tmsh sample: tmsh
''' '''
import os import re
import tempfile
from distutils.version import LooseVersion from distutils.version import LooseVersion
from ansible.module_utils.f5_utils import AnsibleF5Client from ansible.module_utils.f5_utils import AnsibleF5Client
@ -359,151 +358,151 @@ class ModuleManager(object):
class BaseManager(object): class BaseManager(object):
def __init__(self, client): def __init__(self, client):
self.client = client self.client = client
self.have = None self.have = None
self.want = Parameters(self.client.module.params) self.want = Parameters(self.client.module.params)
self.changes = Parameters() self.changes = Parameters()
def exec_module(self): def exec_module(self):
changed = False changed = False
result = dict() result = dict()
state = self.want.state state = self.want.state
try: try:
if state == "present": if state == "present":
changed = self.present() changed = self.present()
elif state == "absent": elif state == "absent":
changed = self.absent() changed = self.absent()
except iControlUnexpectedHTTPError as e: except iControlUnexpectedHTTPError as e:
raise F5ModuleError(str(e)) raise F5ModuleError(str(e))
changes = self.changes.to_return() changes = self.changes.to_return()
result.update(**changes) result.update(**changes)
result.update(dict(changed=changed)) result.update(dict(changed=changed))
return result return result
def _set_changed_options(self): def _set_changed_options(self):
changed = {} changed = {}
for key in Parameters.returnables: for key in Parameters.returnables:
if getattr(self.want, key) is not None: if getattr(self.want, key) is not None:
changed[key] = getattr(self.want, key) changed[key] = getattr(self.want, key)
if changed: if changed:
self.changes = Parameters(changed) self.changes = Parameters(changed)
def _update_changed_options(self): def _update_changed_options(self):
changed = {} changed = {}
for key in Parameters.updatables: for key in Parameters.updatables:
if getattr(self.want, key) is not None: if getattr(self.want, key) is not None:
if key == 'password_credential': if key == 'password_credential':
new_pass = getattr(self.want, key) new_pass = getattr(self.want, key)
if self.want.update_password == 'always': if self.want.update_password == 'always':
changed[key] = new_pass changed[key] = new_pass
else: else:
# We set the shell parameter to 'none' when bigip does # We set the shell parameter to 'none' when bigip does
# not return it. # not return it.
if self.want.shell == 'bash': if self.want.shell == 'bash':
self.validate_shell_parameter() self.validate_shell_parameter()
if self.want.shell == 'none' and self.have.shell is None: if self.want.shell == 'none' and self.have.shell is None:
self.have.shell = 'none' self.have.shell = 'none'
attr1 = getattr(self.want, key) attr1 = getattr(self.want, key)
attr2 = getattr(self.have, key) attr2 = getattr(self.have, key)
if attr1 != attr2: if attr1 != attr2:
changed[key] = attr1 changed[key] = attr1
if changed: if changed:
self.changes = Parameters(changed) self.changes = Parameters(changed)
return True return True
return False return False
def validate_shell_parameter(self): def validate_shell_parameter(self):
"""Method to validate shell parameters. """Method to validate shell parameters.
Raise when shell attribute is set to 'bash' with roles set to Raise when shell attribute is set to 'bash' with roles set to
either 'admin' or 'resource-admin'. either 'admin' or 'resource-admin'.
NOTE: Admin and Resource-Admin roles automatically enable access to NOTE: Admin and Resource-Admin roles automatically enable access to
all partitions, removing any other roles that the user might have all partitions, removing any other roles that the user might have
had. There are few other roles which do that but those roles, had. There are few other roles which do that but those roles,
do not allow bash. do not allow bash.
""" """
err = "Shell access is only available to " \ err = "Shell access is only available to " \
"'admin' or 'resource-admin' roles" "'admin' or 'resource-admin' roles"
permit = ['admin', 'resource-admin'] permit = ['admin', 'resource-admin']
if self.have is not None: if self.have is not None:
have = self.have.partition_access have = self.have.partition_access
if not any(r['role'] for r in have if r['role'] in permit): if not any(r['role'] for r in have if r['role'] in permit):
raise F5ModuleError(err)
# This check is needed if we want to modify shell AND
# partition_access attribute.
# This check will also trigger on create.
if self.want.partition_access is not None:
want = self.want.partition_access
if not any(r['role'] for r in want if r['role'] in permit):
raise F5ModuleError(err)
def present(self):
if self.exists():
return self.update()
else:
return self.create()
def absent(self):
if self.exists():
return self.remove()
return False
def should_update(self):
result = self._update_changed_options()
if result:
return True
return False
def validate_create_parameters(self):
"""Password credentials and partition access are mandatory,
when creating a user resource.
"""
if self.want.password_credential and \
self.want.update_password != 'on_create':
err = "The 'update_password' option " \
"needs to be set to 'on_create' when creating " \
"a resource with a password."
raise F5ModuleError(err)
if self.want.partition_access is None:
err = "The 'partition_access' option " \
"is required when creating a resource."
raise F5ModuleError(err) raise F5ModuleError(err)
def update(self): # This check is needed if we want to modify shell AND
self.have = self.read_current_from_device() # partition_access attribute.
if not self.should_update(): # This check will also trigger on create.
return False if self.want.partition_access is not None:
if self.client.check_mode: want = self.want.partition_access
return True if not any(r['role'] for r in want if r['role'] in permit):
self.update_on_device() raise F5ModuleError(err)
return True
def remove(self): def present(self):
if self.client.check_mode: if self.exists():
return True return self.update()
self.remove_from_device() else:
if self.exists(): return self.create()
raise F5ModuleError("Failed to delete the user")
return True
def create(self): def absent(self):
self.validate_create_parameters() if self.exists():
if self.want.shell == 'bash': return self.remove()
self.validate_shell_parameter() return False
self._set_changed_options()
if self.client.check_mode: def should_update(self):
return True result = self._update_changed_options()
self.create_on_device() if result:
return True return True
return False
def validate_create_parameters(self):
"""Password credentials and partition access are mandatory,
when creating a user resource.
"""
if self.want.password_credential and \
self.want.update_password != 'on_create':
err = "The 'update_password' option " \
"needs to be set to 'on_create' when creating " \
"a resource with a password."
raise F5ModuleError(err)
if self.want.partition_access is None:
err = "The 'partition_access' option " \
"is required when creating a resource."
raise F5ModuleError(err)
def update(self):
self.have = self.read_current_from_device()
if not self.should_update():
return False
if self.client.check_mode:
return True
self.update_on_device()
return True
def remove(self):
if self.client.check_mode:
return True
self.remove_from_device()
if self.exists():
raise F5ModuleError("Failed to delete the user")
return True
def create(self):
self.validate_create_parameters()
if self.want.shell == 'bash':
self.validate_shell_parameter()
self._set_changed_options()
if self.client.check_mode:
return True
self.create_on_device()
return True
class UnparitionedManager(BaseManager): class UnparitionedManager(BaseManager):
@ -621,40 +620,27 @@ class RootUserManager(BaseManager):
return True return True
def update(self): def update(self):
file = tempfile.NamedTemporaryFile() result = self.update_on_device()
self.want.update({'tempfile': os.path.basename(file.name)}) return result
self.upload_password_file_to_device()
self.update_on_device()
self.remove_password_file_from_device()
return True
def update_on_device(self): def update_on_device(self):
errors = [ escape_patterns = r'([$' + "'])"
'not confirmed', errors = ['Bad password', 'password change canceled', 'based on a dictionary word']
'change canceled'
]
cmd = '-c "cat /var/config/rest/downloads/{0} | tmsh modify auth password root"'.format(self.want.tempfile)
output = self.client.api.tm.util.bash.exec_cmd(
'run',
utilCmdArgs=cmd
)
if hasattr(output, 'commandResult'):
result = str(output.commandResult)
if any(x for x in errors if x in result):
raise F5ModuleError(result)
def upload_password_file_to_device(self):
content = "{0}\n{0}\n".format(self.want.password_credential) content = "{0}\n{0}\n".format(self.want.password_credential)
template = StringIO(content) command = re.sub(escape_patterns, r'\\\1', content)
upload = self.client.api.shared.file_transfer.uploads cmd = '-c "printf \\\"{0}\\\" | tmsh modify auth password root"'.format(command)
upload.upload_stringio(template, self.want.tempfile) try:
return True output = self.client.api.tm.util.bash.exec_cmd(
'run',
def remove_password_file_from_device(self): utilCmdArgs=cmd
self.client.api.tm.util.unix_rm.exec_cmd( )
'run', if hasattr(output, 'commandResult'):
utilCmdArgs='/var/config/rest/downloads/{0}'.format(self.want.tempfile) result = str(output.commandResult)
) if any(x for x in errors if x in result):
raise F5ModuleError(result)
return True
except iControlUnexpectedHTTPError:
return False
class ArgumentSpec(object): class ArgumentSpec(object):

View file

@ -16,10 +16,10 @@ if sys.version_info < (2, 7):
raise SkipTest("F5 Ansible modules require Python >= 2.7") raise SkipTest("F5 Ansible modules require Python >= 2.7")
from ansible.compat.tests import unittest from ansible.compat.tests import unittest
from ansible.compat.tests.mock import patch, Mock from ansible.compat.tests.mock import Mock
from ansible.compat.tests.mock import patch
from ansible.module_utils.f5_utils import AnsibleF5Client from ansible.module_utils.f5_utils import AnsibleF5Client
from ansible.module_utils.f5_utils import F5ModuleError from ansible.module_utils.f5_utils import F5ModuleError
from units.modules.utils import set_module_args
try: try:
from library.bigip_user import Parameters from library.bigip_user import Parameters
@ -28,6 +28,7 @@ try:
from library.bigip_user import UnparitionedManager from library.bigip_user import UnparitionedManager
from library.bigip_user import PartitionedManager from library.bigip_user import PartitionedManager
from ansible.module_utils.f5_utils import iControlUnexpectedHTTPError from ansible.module_utils.f5_utils import iControlUnexpectedHTTPError
from test.unit.modules.utils import set_module_args
except ImportError: except ImportError:
try: try:
from ansible.modules.network.f5.bigip_user import Parameters from ansible.modules.network.f5.bigip_user import Parameters
@ -36,6 +37,7 @@ except ImportError:
from ansible.modules.network.f5.bigip_user import UnparitionedManager from ansible.modules.network.f5.bigip_user import UnparitionedManager
from ansible.modules.network.f5.bigip_user import PartitionedManager from ansible.modules.network.f5.bigip_user import PartitionedManager
from ansible.module_utils.f5_utils import iControlUnexpectedHTTPError from ansible.module_utils.f5_utils import iControlUnexpectedHTTPError
from units.modules.utils import set_module_args
except ImportError: except ImportError:
raise SkipTest("F5 Ansible modules require the f5-sdk Python library") raise SkipTest("F5 Ansible modules require the f5-sdk Python library")