1
0
Fork 0
mirror of https://github.com/ansible-collections/community.general.git synced 2024-09-14 20:13:21 +02:00

Refactors bigip device sshd (#33473)

* Refactors bigip device sshd

Fixes coding conventions for currect f5 conventions

* Fixes upstream errors
This commit is contained in:
Tim Rupp 2017-12-01 20:27:41 -08:00 committed by GitHub
parent a3b3dbe220
commit 76135a500e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 346 additions and 202 deletions

View file

@ -3,17 +3,20 @@
# #
# Copyright (c) 2017 F5 Networks Inc. # Copyright (c) 2017 F5 Networks Inc.
# GNU General Public License v3.0 (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # 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', ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'], 'status': ['preview'],
'supported_by': 'community'} 'supported_by': 'community'}
DOCUMENTATION = ''' DOCUMENTATION = r'''
--- ---
module: bigip_device_sshd module: bigip_device_sshd
short_description: Manage the SSHD settings of a BIG-IP short_description: Manage the SSHD settings of a BIG-IP
description: description:
- Manage the SSHD settings of a BIG-IP - Manage the SSHD settings of a BIG-IP.
version_added: "2.2" version_added: "2.2"
options: options:
allow: allow:
@ -28,7 +31,6 @@ options:
banner: banner:
description: description:
- Whether to enable the banner or not. - Whether to enable the banner or not.
required: false
choices: choices:
- enabled - enabled
- disabled - disabled
@ -36,12 +38,10 @@ options:
description: description:
- Specifies the text to include on the pre-login banner that displays - Specifies the text to include on the pre-login banner that displays
when a user attempts to login to the system using SSH. when a user attempts to login to the system using SSH.
required: false
inactivity_timeout: inactivity_timeout:
description: description:
- Specifies the number of seconds before inactivity causes an SSH - Specifies the number of seconds before inactivity causes an SSH
session to log out. session to log out.
required: false
log_level: log_level:
description: description:
- Specifies the minimum SSHD message level to include in the system log. - Specifies the minimum SSHD message level to include in the system log.
@ -62,11 +62,9 @@ options:
choices: choices:
- enabled - enabled
- disabled - disabled
required: false
port: port:
description: description:
- Port that you want the SSH daemon to run on. - Port that you want the SSH daemon to run on.
required: false
notes: notes:
- Requires the f5-sdk Python package on the host This is as easy as pip - Requires the f5-sdk Python package on the host This is as easy as pip
install f5-sdk. install f5-sdk.
@ -78,35 +76,35 @@ author:
- Tim Rupp (@caphrim007) - Tim Rupp (@caphrim007)
''' '''
EXAMPLES = ''' EXAMPLES = r'''
- name: Set the banner for the SSHD service from a string - name: Set the banner for the SSHD service from a string
bigip_device_sshd: bigip_device_sshd:
banner: "enabled" banner: enabled
banner_text: "banner text goes here" banner_text: banner text goes here
password: "secret" password: secret
server: "lb.mydomain.com" server: lb.mydomain.com
user: "admin" user: admin
delegate_to: localhost delegate_to: localhost
- name: Set the banner for the SSHD service from a file - name: Set the banner for the SSHD service from a file
bigip_device_sshd: bigip_device_sshd:
banner: "enabled" banner: enabled
banner_text: "{{ lookup('file', '/path/to/file') }}" banner_text: "{{ lookup('file', '/path/to/file') }}"
password: "secret" password: secret
server: "lb.mydomain.com" server: lb.mydomain.com
user: "admin" user: admin
delegate_to: localhost delegate_to: localhost
- name: Set the SSHD service to run on port 2222 - name: Set the SSHD service to run on port 2222
bigip_device_sshd: bigip_device_sshd:
password: "secret" password: secret
port: 2222 port: 2222
server: "lb.mydomain.com" server: lb.mydomain.com
user: "admin" user: admin
delegate_to: localhost delegate_to: localhost
''' '''
RETURN = ''' RETURN = r'''
allow: allow:
description: > description: >
Specifies, if you have enabled SSH access, the IP address or address Specifies, if you have enabled SSH access, the IP address or address
@ -114,31 +112,31 @@ allow:
system. system.
returned: changed returned: changed
type: string type: string
sample: "192.0.2.*" sample: 192.0.2.*
banner: banner:
description: Whether the banner is enabled or not. description: Whether the banner is enabled or not.
returned: changed returned: changed
type: string type: string
sample: "true" sample: true
banner_text: banner_text:
description: > description: >
Specifies the text included on the pre-login banner that Specifies the text included on the pre-login banner that
displays when a user attempts to login to the system using SSH. displays when a user attempts to login to the system using SSH.
returned: changed and success returned: changed and success
type: string type: string
sample: "This is a corporate device. Connecting to it without..." sample: This is a corporate device. Connecting to it without...
inactivity_timeout: inactivity_timeout:
description: > description: >
The number of seconds before inactivity causes an SSH. The number of seconds before inactivity causes an SSH
session to log out. session to log out.
returned: changed returned: changed
type: int type: int
sample: "10" sample: 10
log_level: log_level:
description: The minimum SSHD message level to include in the system log. description: The minimum SSHD message level to include in the system log.
returned: changed returned: changed
type: string type: string
sample: "debug" sample: debug
login: login:
description: Specifies that the system accepts SSH communications or not. description: Specifies that the system accepts SSH communications or not.
returned: changed returned: changed
@ -151,186 +149,205 @@ port:
sample: 22 sample: 22
''' '''
from ansible.module_utils.f5_utils import AnsibleF5Client
from ansible.module_utils.f5_utils import AnsibleF5Parameters
from ansible.module_utils.f5_utils import HAS_F5SDK
from ansible.module_utils.f5_utils import F5ModuleError
try: try:
from f5.bigip import ManagementRoot from ansible.module_utils.f5_utils import iControlUnexpectedHTTPError
from icontrol.session import iControlUnexpectedHTTPError
HAS_F5SDK = True
except ImportError: except ImportError:
HAS_F5SDK = False HAS_F5SDK = False
CHOICES = ['enabled', 'disabled']
LEVELS = ['debug', 'debug1', 'debug2', 'debug3', 'error', 'fatal', 'info',
'quiet', 'verbose']
class Parameters(AnsibleF5Parameters):
api_map = {
'bannerText': 'banner_text',
'inactivityTimeout': 'inactivity_timeout',
'logLevel': 'log_level'
}
class BigIpDeviceSshd(object): api_attributes = [
def __init__(self, *args, **kwargs): 'allow', 'banner', 'bannerText', 'inactivityTimeout', 'logLevel',
if not HAS_F5SDK: 'login', 'port'
raise F5ModuleError("The python f5-sdk module is required") ]
# The params that change in the module updatables = [
self.cparams = dict() 'allow', 'banner', 'banner_text', 'inactivity_timeout', 'log_level',
'login', 'port'
]
# Stores the params that are sent to the module returnables = [
self.params = kwargs 'allow', 'banner', 'banner_text', 'inactivity_timeout', 'log_level',
self.api = ManagementRoot(kwargs['server'], 'login', 'port'
kwargs['user'], ]
kwargs['password'],
port=kwargs['server_port'])
def update(self): def to_return(self):
changed = False result = {}
current = self.read() for returnable in self.returnables:
params = dict() result[returnable] = getattr(self, returnable)
result = self._filter_params(result)
return result
allow = self.params['allow'] def api_params(self):
banner = self.params['banner'] result = {}
banner_text = self.params['banner_text'] for api_attribute in self.api_attributes:
timeout = self.params['inactivity_timeout'] if self.api_map is not None and api_attribute in self.api_map:
log_level = self.params['log_level'] result[api_attribute] = getattr(self, self.api_map[api_attribute])
login = self.params['login']
port = self.params['port']
check_mode = self.params['check_mode']
if allow:
if 'allow' in current:
items = set(allow)
if items != current['allow']:
params['allow'] = list(items)
else: else:
params['allow'] = allow result[api_attribute] = getattr(self, api_attribute)
result = self._filter_params(result)
return result
if banner: @property
if 'banner' in current: def inactivity_timeout(self):
if banner != current['banner']: if self._values['inactivity_timeout'] is None:
params['banner'] = banner return None
else: return int(self._values['inactivity_timeout'])
params['banner'] = banner
if banner_text: @property
if 'banner_text' in current: def port(self):
if banner_text != current['banner_text']: if self._values['port'] is None:
params['bannerText'] = banner_text return None
else: return int(self._values['port'])
params['bannerText'] = banner_text
if timeout: @property
if 'inactivity_timeout' in current: def allow(self):
if timeout != current['inactivity_timeout']: if self._values['allow'] is None:
params['inactivityTimeout'] = timeout return None
else: allow = self._values['allow']
params['inactivityTimeout'] = timeout return list(set([str(x) for x in allow]))
if log_level:
if 'log_level' in current:
if log_level != current['log_level']:
params['logLevel'] = log_level
else:
params['logLevel'] = log_level
if login: class ModuleManager(object):
if 'login' in current: def __init__(self, client):
if login != current['login']: self.client = client
params['login'] = login self.have = None
else: self.want = Parameters(self.client.module.params)
params['login'] = login self.changes = Parameters()
if port: def _update_changed_options(self):
if 'port' in current: changed = {}
if port != current['port']: for key in Parameters.updatables:
params['port'] = port if getattr(self.want, key) is not None:
else: attr1 = getattr(self.want, key)
params['port'] = port attr2 = getattr(self.have, key)
if attr1 != attr2:
changed[key] = attr1
if changed:
self.changes = Parameters(changed)
return True
return False
if params: def exec_module(self):
changed = True
if check_mode:
return changed
self.cparams = camel_dict_to_snake_dict(params)
else:
return changed
r = self.api.tm.sys.sshd.load()
r.update(**params)
r.refresh()
return changed
def read(self):
"""Read information and transform it
The values that are returned by BIG-IP in the f5-sdk can have encoding
attached to them as well as be completely missing in some cases.
Therefore, this method will transform the data from the BIG-IP into a
format that is more easily consumable by the rest of the class and the
parameters that are supported by the module.
"""
p = dict()
r = self.api.tm.sys.sshd.load()
if hasattr(r, 'allow'):
# Deliberately using sets to suppress duplicates
p['allow'] = set([str(x) for x in r.allow])
if hasattr(r, 'banner'):
p['banner'] = str(r.banner)
if hasattr(r, 'bannerText'):
p['banner_text'] = str(r.bannerText)
if hasattr(r, 'inactivityTimeout'):
p['inactivity_timeout'] = str(r.inactivityTimeout)
if hasattr(r, 'logLevel'):
p['log_level'] = str(r.logLevel)
if hasattr(r, 'login'):
p['login'] = str(r.login)
if hasattr(r, 'port'):
p['port'] = int(r.port)
return p
def flush(self):
result = dict() result = dict()
changed = False
try: try:
changed = self.update() changed = self.update()
except iControlUnexpectedHTTPError as e: except iControlUnexpectedHTTPError as e:
raise F5ModuleError(str(e)) raise F5ModuleError(str(e))
result.update(**self.cparams) changes = self.changes.to_return()
result.update(**changes)
result.update(dict(changed=changed)) result.update(dict(changed=changed))
return result return result
def read_current_from_device(self):
resource = self.client.api.tm.sys.sshd.load()
result = resource.attrs
return Parameters(result)
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 should_update(self):
result = self._update_changed_options()
if result:
return True
return False
def update_on_device(self):
params = self.want.api_params()
resource = self.client.api.tm.sys.sshd.load()
resource.update(**params)
class ArgumentSpec(object):
def __init__(self):
self.choices = ['enabled', 'disabled']
self.levels = [
'debug', 'debug1', 'debug2', 'debug3', 'error', 'fatal', 'info',
'quiet', 'verbose'
]
self.supports_check_mode = True
self.argument_spec = dict(
allow=dict(
required=False,
default=None,
type='list'
),
banner=dict(
required=False,
default=None,
choices=self.choices
),
banner_text=dict(
required=False,
default=None
),
inactivity_timeout=dict(
required=False,
default=None,
type='int'
),
log_level=dict(
required=False,
default=None,
choices=self.levels
),
login=dict(
required=False,
default=None,
choices=self.choices
),
port=dict(
required=False,
default=None,
type='int'
),
state=dict(
default='present',
choices=['present']
)
)
self.f5_product_name = 'bigip'
def main(): def main():
argument_spec = f5_argument_spec() if not HAS_F5SDK:
raise F5ModuleError("The python f5-sdk module is required")
meta_args = dict( spec = ArgumentSpec()
allow=dict(required=False, default=None, type='list'),
banner=dict(required=False, default=None, choices=CHOICES),
banner_text=dict(required=False, default=None),
inactivity_timeout=dict(required=False, default=None, type='int'),
log_level=dict(required=False, default=None, choices=LEVELS),
login=dict(required=False, default=None, choices=CHOICES),
port=dict(required=False, default=None, type='int'),
state=dict(default='present', choices=['present'])
)
argument_spec.update(meta_args)
module = AnsibleModule( client = AnsibleF5Client(
argument_spec=argument_spec, argument_spec=spec.argument_spec,
supports_check_mode=True supports_check_mode=spec.supports_check_mode,
f5_product_name=spec.f5_product_name
) )
try: try:
obj = BigIpDeviceSshd(check_mode=module.check_mode, **module.params) mm = ModuleManager(client)
result = obj.flush() results = mm.exec_module()
client.module.exit_json(**results)
module.exit_json(**result)
except F5ModuleError as e: except F5ModuleError as e:
module.fail_json(msg=str(e)) client.module.fail_json(msg=str(e))
from ansible.module_utils.basic import *
from ansible.module_utils.ec2 import camel_dict_to_snake_dict
from ansible.module_utils.f5_utils import *
if __name__ == '__main__': if __name__ == '__main__':
main() main()

View file

@ -0,0 +1,127 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2017 F5 Networks Inc.
# 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
import os
import json
import sys
from nose.plugins.skip import SkipTest
if sys.version_info < (2, 7):
raise SkipTest("F5 Ansible modules require Python >= 2.7")
from ansible.compat.tests import unittest
from ansible.compat.tests.mock import Mock
from ansible.compat.tests.mock import patch
from ansible.module_utils.f5_utils import AnsibleF5Client
try:
from library.bigip_device_sshd import Parameters
from library.bigip_device_sshd import ModuleManager
from library.bigip_device_sshd import ArgumentSpec
from ansible.module_utils.f5_utils import iControlUnexpectedHTTPError
from test.unit.modules.utils import set_module_args
except ImportError:
try:
from ansible.modules.network.f5.bigip_device_sshd import Parameters
from ansible.modules.network.f5.bigip_device_sshd import ModuleManager
from ansible.modules.network.f5.bigip_device_sshd import ArgumentSpec
from ansible.module_utils.f5_utils import iControlUnexpectedHTTPError
from units.modules.utils import set_module_args
except ImportError:
raise SkipTest("F5 Ansible modules require the f5-sdk Python library")
fixture_path = os.path.join(os.path.dirname(__file__), 'fixtures')
fixture_data = {}
def load_fixture(name):
path = os.path.join(fixture_path, name)
if path in fixture_data:
return fixture_data[path]
with open(path) as f:
data = f.read()
try:
data = json.loads(data)
except Exception:
pass
fixture_data[path] = data
return data
class TestParameters(unittest.TestCase):
def test_module_parameters(self):
args = dict(
allow=['all'],
banner='enabled',
banner_text='asdf',
inactivity_timeout='100',
log_level='debug',
login='enabled',
port=1010,
server='localhost',
user='admin',
password='password'
)
p = Parameters(args)
assert p.allow == ['all']
assert p.banner == 'enabled'
assert p.banner_text == 'asdf'
assert p.inactivity_timeout == 100
assert p.log_level == 'debug'
assert p.login == 'enabled'
assert p.port == 1010
class TestManager(unittest.TestCase):
def setUp(self):
self.spec = ArgumentSpec()
@patch('ansible.module_utils.f5_utils.AnsibleF5Client._get_mgmt_root',
return_value=True)
def test_update_settings(self, *args):
set_module_args(dict(
allow=['all'],
banner='enabled',
banner_text='asdf',
inactivity_timeout='100',
log_level='debug',
login='enabled',
port=1010,
server='localhost',
user='admin',
password='password'
))
# Configure the parameters that would be returned by querying the
# remote device
current = Parameters(
dict(
allow=['172.27.1.1']
)
)
client = AnsibleF5Client(
argument_spec=self.spec.argument_spec,
supports_check_mode=self.spec.supports_check_mode,
f5_product_name=self.spec.f5_product_name
)
mm = ModuleManager(client)
# Override methods to force specific logic in the module to happen
mm.update_on_device = Mock(return_value=True)
mm.read_current_from_device = Mock(return_value=current)
results = mm.exec_module()
assert results['changed'] is True
assert results['allow'] == ['all']