diff --git a/lib/ansible/modules/network/fortimanager/fmgr_script.py b/lib/ansible/modules/network/fortimanager/fmgr_script.py index 671faa5dde..25ea16dcd8 100644 --- a/lib/ansible/modules/network/fortimanager/fmgr_script.py +++ b/lib/ansible/modules/network/fortimanager/fmgr_script.py @@ -28,6 +28,8 @@ DOCUMENTATION = ''' --- module: fmgr_script version_added: "2.5" +notes: + - Full Documentation at U(https://ftnt-ansible-docs.readthedocs.io/en/latest/). author: Andrew Welsh (@Ghilli3) short_description: Add/Edit/Delete and execute scripts description: Create/edit/delete scripts and execute the scripts on the FortiManager using jsonrpc API @@ -37,54 +39,49 @@ options: description: - The administrative domain (admon) the configuration belongs to required: true + vdom: description: - The virtual domain (vdom) the configuration belongs to - host: + + mode: description: - - The FortiManager's Address. - required: true - username: - description: - - The username to log into the FortiManager - required: true - password: - description: - - The password associated with the username account. + - The desired mode of the specified object. Execute will run the script. required: false - state: - description: - - The desired state of the specified object. - - present - will create a script. - - execute - execute the scipt. - - delete - delete the script. - required: false - default: present - choices: ["present", "execute", "delete"] + default: "add" + choices: ["add", "delete", "execute", "set"] + version_added: "2.8" + script_name: description: - The name of the script. required: True + script_type: description: - The type of script (CLI or TCL). required: false + script_target: description: - The target of the script to be run. required: false + script_description: description: - The description of the script. required: false + script_content: description: - The script content that will be executed. required: false + script_scope: description: - (datasource) The devices that the script will run on, can have both device member and device group member. required: false + script_package: description: - (datasource) Policy package object to run the script against @@ -94,9 +91,6 @@ options: EXAMPLES = ''' - name: CREATE SCRIPT fmgr_script: - host: "{{inventory_hostname}}" - username: "{{ username }}" - password: "{{ password }}" adom: "root" script_name: "TestScript" script_type: "cli" @@ -106,22 +100,16 @@ EXAMPLES = ''' - name: EXECUTE SCRIPT fmgr_script: - host: "{{inventory_hostname}}" - username: "{{ username }}" - password: "{{ password }}" adom: "root" script_name: "TestScript" - state: "execute" + mode: "execute" script_scope: "FGT1,FGT2" - name: DELETE SCRIPT fmgr_script: - host: "{{inventory_hostname}}" - username: "{{ username }}" - password: "{{ password }}" adom: "root" script_name: "TestScript" - state: "delete" + mode: "delete" ''' RETURN = """ @@ -132,80 +120,90 @@ api_result: """ from ansible.module_utils.basic import AnsibleModule, env_fallback -from ansible.module_utils.network.fortimanager.fortimanager import AnsibleFortiManager - -# check for pyFMG lib -try: - from pyFMG.fortimgr import FortiManager - HAS_PYFMGR = True -except ImportError: - HAS_PYFMGR = False +from ansible.module_utils.connection import Connection +from ansible.module_utils.network.fortimanager.fortimanager import FortiManagerHandler +from ansible.module_utils.network.fortimanager.common import FMGBaseException +from ansible.module_utils.network.fortimanager.common import FMGRCommon +from ansible.module_utils.network.fortimanager.common import FMGRMethods +from ansible.module_utils.network.fortimanager.common import DEFAULT_RESULT_OBJ +from ansible.module_utils.network.fortimanager.common import FAIL_SOCKET_MSG -def set_script(fmg, script_name, script_type, script_content, script_desc, script_target, adom): +def set_script(fmgr, paramgram): """ - This method sets a script. + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict """ datagram = { - 'content': script_content, - 'desc': script_desc, - 'name': script_name, - 'target': script_target, - 'type': script_type, + 'content': paramgram["script_content"], + 'desc': paramgram["script_description"], + 'name': paramgram["script_name"], + 'target': paramgram["script_target"], + 'type': paramgram["script_type"], } - url = '/dvmdb/adom/{adom}/script/'.format(adom=adom) - response = fmg.set(url, datagram) + url = '/dvmdb/adom/{adom}/script/'.format(adom=paramgram["adom"]) + response = fmgr.process_request(url, datagram, FMGRMethods.SET) return response -def delete_script(fmg, script_name, adom): +def delete_script(fmgr, paramgram): """ - This method deletes a script. + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict """ datagram = { - 'name': script_name, + 'name': paramgram["script_name"], } - url = '/dvmdb/adom/{adom}/script/{script_name}'.format(adom=adom, script_name=script_name) - response = fmg.delete(url, datagram) + url = '/dvmdb/adom/{adom}/script/{script_name}'.format(adom=paramgram["adom"], script_name=paramgram["script_name"]) + response = fmgr.process_request(url, datagram, FMGRMethods.DELETE) return response -def execute_script(fmg, script_name, scope, package, adom, vdom): +def execute_script(fmgr, paramgram): """ - This method will execute a specific script. + :param fmgr: The fmgr object instance from fortimanager.py + :type fmgr: class object + :param paramgram: The formatted dictionary of options to process + :type paramgram: dict + :return: The response from the FortiManager + :rtype: dict """ scope_list = list() - scope = scope.replace(' ', '') + scope = paramgram["script_scope"].replace(' ', '') scope = scope.split(',') for dev_name in scope: - scope_list.append({'name': dev_name, 'vdom': vdom}) + scope_list.append({'name': dev_name, 'vdom': paramgram["vdom"]}) datagram = { - 'adom': adom, - 'script': script_name, - 'package': package, + 'adom': paramgram["adom"], + 'script': paramgram["script_name"], + 'package': paramgram["script_package"], 'scope': scope_list, } - url = '/dvmdb/adom/{adom}/script/execute'.format(adom=adom) - response = fmg.execute(url, datagram) + url = '/dvmdb/adom/{adom}/script/execute'.format(adom=paramgram["adom"]) + response = fmgr.process_request(url, datagram, FMGRMethods.EXEC) return response def main(): argument_spec = dict( - adom=dict(required=False, type="str"), - vdom=dict(required=False, type="str"), - host=dict(required=True, type="str"), - password=dict(fallback=(env_fallback, ["ANSIBLE_NET_PASSWORD"]), no_log=True), - username=dict(fallback=(env_fallback, ["ANSIBLE_NET_USERNAME"])), - state=dict(choices=["execute", "delete", "present"], type="str"), - + adom=dict(required=False, type="str", default="root"), + vdom=dict(required=False, type="str", default="root"), + mode=dict(choices=["add", "execute", "set", "delete"], type="str", default="add"), script_name=dict(required=True, type="str"), script_type=dict(required=False, type="str"), script_target=dict(required=False, type="str"), @@ -215,58 +213,55 @@ def main(): script_package=dict(required=False, type="str"), ) - module = AnsibleModule(argument_spec, supports_check_mode=True,) - - # check if params are set - if module.params["host"] is None or module.params["username"] is None: - module.fail_json(msg="Host and username are required for connection") - - # check if login failed - fmg = AnsibleFortiManager(module, module.params["host"], module.params["username"], module.params["password"]) - response = fmg.login() - - if "FortiManager instance connnected" not in str(response): - module.fail_json(msg="Connection to FortiManager Failed") + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, ) + paramgram = { + "script_name": module.params["script_name"], + "script_type": module.params["script_type"], + "script_target": module.params["script_target"], + "script_description": module.params["script_description"], + "script_content": module.params["script_content"], + "script_scope": module.params["script_scope"], + "script_package": module.params["script_package"], + "adom": module.params["adom"], + "vdom": module.params["vdom"], + "mode": module.params["mode"], + } + module.paramgram = paramgram + fmgr = None + if module._socket_path: + connection = Connection(module._socket_path) + fmgr = FortiManagerHandler(connection, module) + fmgr.tools = FMGRCommon() else: - adom = module.params["adom"] - if adom is None: - adom = "root" - vdom = module.params["vdom"] - if vdom is None: - vdom = "root" - state = module.params["state"] - if state is None: - state = "present" + module.fail_json(**FAIL_SOCKET_MSG) - script_name = module.params["script_name"] - script_type = module.params["script_type"] - script_target = module.params["script_target"] - script_description = module.params["script_description"] - script_content = module.params["script_content"] - script_scope = module.params["script_scope"] - script_package = module.params["script_package"] + results = DEFAULT_RESULT_OBJ - # if state is present (default), then add the script - if state == "present": - results = set_script(fmg, script_name, script_type, script_content, script_description, script_target, adom) - if results[0] != 0: - if isinstance(results[1], list): - module.fail_json(msg="Adding Script Failed", **results) - else: - module.fail_json(msg="Adding Script Failed") - elif state == "execute": - results = execute_script(fmg, script_name, script_scope, script_package, adom, vdom) - if results[0] != 0: - module.fail_json(msg="Script Execution Failed", **results) - elif state == "delete": - results = delete_script(fmg, script_name, adom) - if results[0] != 0: - module.fail_json(msg="Script Deletion Failed", **results) + try: + if paramgram["mode"] in ['add', 'set']: + results = set_script(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, msg="Operation Finished", + ansible_facts=fmgr.construct_ansible_facts(results, module.params, module.params)) + except Exception as err: + raise FMGBaseException(err) - fmg.logout() + try: + if paramgram["mode"] == "execute": + results = execute_script(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, msg="Operation Finished", + ansible_facts=fmgr.construct_ansible_facts(results, module.params, module.params)) + except Exception as err: + raise FMGBaseException(err) - # results is returned as a tuple - return module.exit_json(**results[1]) + try: + if paramgram["mode"] == "delete": + results = delete_script(fmgr, paramgram) + fmgr.govern_response(module=module, results=results, msg="Operation Finished", + ansible_facts=fmgr.construct_ansible_facts(results, module.params, module.params)) + except Exception as err: + raise FMGBaseException(err) + + return module.exit_json(**results[1]) if __name__ == "__main__": diff --git a/test/units/modules/network/fortimanager/fixtures/test_fmgr_script.json b/test/units/modules/network/fortimanager/fixtures/test_fmgr_script.json new file mode 100644 index 0000000000..5ce77021eb --- /dev/null +++ b/test/units/modules/network/fortimanager/fixtures/test_fmgr_script.json @@ -0,0 +1,92 @@ +{ + "delete_script": [ + { + "raw_response": { + "status": { + "message": "OK", + "code": 0 + }, + "url": "/dvmdb/adom/ansible/script/TestScript" + }, + "datagram_sent": { + "name": "TestScript" + }, + "paramgram_used": { + "vdom": "root", + "script_target": null, + "script_content": null, + "adom": "ansible", + "script_description": null, + "script_package": null, + "mode": "delete", + "script_scope": null, + "script_name": "TestScript", + "script_type": null + }, + "post_method": "delete" + } + ], + "set_script": [ + { + "raw_response": { + "status": { + "message": "OK", + "code": 0 + }, + "url": "/dvmdb/adom/ansible/script/" + }, + "datagram_sent": { + "content": "get system status", + "type": "cli", + "target": "remote_device", + "name": "TestScript", + "desc": "Create by Ansible" + }, + "paramgram_used": { + "script_content": "get system status", + "adom": "ansible", + "script_scope": null, + "script_name": "TestScript", + "script_target": "remote_device", + "mode": "add", + "script_description": "Create by Ansible", + "script_package": null, + "vdom": "root", + "script_type": "cli" + }, + "post_method": "set" + } + ], + "execute_script": [ + { + "url": "/dvmdb/adom/ansible/script/execute", + "paramgram_used": { + "script_content": null, + "adom": "ansible", + "script_scope": "FGT1", + "script_name": "TestScript", + "script_target": null, + "mode": "execute", + "script_description": null, + "script_package": null, + "vdom": "root", + "script_type": null + }, + "datagram_sent": { + "scope": [ + { + "name": "FGT1", + "vdom": "root" + } + ], + "adom": "ansible", + "script": "TestScript", + "package": null + }, + "raw_response": { + "task": 277 + }, + "post_method": "exec" + } + ] +} diff --git a/test/units/modules/network/fortimanager/test_fmgr_script.py b/test/units/modules/network/fortimanager/test_fmgr_script.py index d0a9c7467c..6810497bb1 100644 --- a/test/units/modules/network/fortimanager/test_fmgr_script.py +++ b/test/units/modules/network/fortimanager/test_fmgr_script.py @@ -1,61 +1,129 @@ -# (c) 2016 Red Hat Inc. +# Copyright 2018 Fortinet, Inc. # -# This file is part of Ansible -# -# Ansible is free software: you can redistribute it and/or modify +# This program 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, +# This program 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 . +# along with Ansible. If not, see . # Make coding more python3-ish from __future__ import (absolute_import, division, print_function) __metaclass__ = type +import os +import json +from ansible.module_utils.network.fortimanager.fortimanager import FortiManagerHandler import pytest -pytestmark = [] try: from ansible.modules.network.fortimanager import fmgr_script - from .fortimanager_module import TestFortimanagerModule - from units.modules.utils import set_module_args except ImportError: - pytestmark.append(pytest.mark.skip("Could not load required modules for testing")) - -try: - from pyFMG.fortimgr import FortiManager -except ImportError: - pytestmark.append(pytest.mark.skip("FortiManager tests require pyFMG package")) + pytest.skip("Could not load required modules for testing", allow_module_level=True) -class TestFmgrScriptModule(TestFortimanagerModule): +def load_fixtures(): + fixture_path = os.path.join(os.path.dirname(__file__), 'fixtures') + "/{filename}.json".format( + filename=os.path.splitext(os.path.basename(__file__))[0]) + try: + with open(fixture_path, "r") as fixture_file: + fixture_data = json.load(fixture_file) + except IOError: + return [] + return [fixture_data] - module = fmgr_script - def test_fmg_script_fail_connect(self): - set_module_args(dict(host='1.1.1.1', username='admin', password='admin', adom='root', script_name='TestScript', - script_type='cli', script_target='remote_device', script_description='AnsibleTest', - script_content='get system status')) - result = self.execute_module(failed=True) - self.assertEqual(result['msg'], 'Connection to FortiManager Failed') +@pytest.fixture(autouse=True) +def module_mock(mocker): + connection_class_mock = mocker.patch('ansible.module_utils.basic.AnsibleModule') + return connection_class_mock - def test_fmg_script_login_fail_host(self): - set_module_args(dict(username='admin', password='admin', adom='root', script_name='TestScript', - script_type='cli', script_target='remote_device', script_description='AnsibleTest', - script_content='get system status')) - result = self.execute_module(failed=True) - self.assertEqual(result['msg'], 'missing required arguments: host') - def test_fmg_script_login_fail_username(self): - set_module_args(dict(host='1.1.1.1', password='admin', adom='root', script_name='TestScript', - script_type='cli', script_target='remote_device', script_description='AnsibleTest', - script_content='get system status')) - result = self.execute_module(failed=True) - self.assertEqual(result['msg'], 'Host and username are required for connection') +@pytest.fixture(autouse=True) +def connection_mock(mocker): + connection_class_mock = mocker.patch('ansible.modules.network.fortimanager.fmgr_script.Connection') + return connection_class_mock + + +@pytest.fixture(scope="function", params=load_fixtures()) +def fixture_data(request): + func_name = request.function.__name__.replace("test_", "") + return request.param.get(func_name, None) + + +fmg_instance = FortiManagerHandler(connection_mock, module_mock) + + +def test_set_script(fixture_data, mocker): + mocker.patch("ansible.module_utils.network.fortimanager.fortimanager.FortiManagerHandler.process_request", + side_effect=fixture_data) + # Fixture sets used:########################### + + ################################################## + # script_content: get system status + # adom: ansible + # script_scope: None + # script_name: TestScript + # script_target: remote_device + # mode: set + # script_description: Create by Ansible + # script_package: None + # vdom: root + # script_type: cli + ################################################## + + # Test using fixture 1 # + output = fmgr_script.set_script(fmg_instance, fixture_data[0]['paramgram_used']) + assert output['raw_response']['status']['code'] == 0 + + +def test_delete_script(fixture_data, mocker): + mocker.patch("ansible.module_utils.network.fortimanager.fortimanager.FortiManagerHandler.process_request", + side_effect=fixture_data) + # Fixture sets used:########################### + + ################################################## + # vdom: root + # script_target: None + # script_content: None + # adom: ansible + # script_description: None + # script_package: None + # mode: delete + # script_scope: None + # script_name: TestScript + # script_type: None + ################################################## + + # Test using fixture 1 # + output = fmgr_script.delete_script(fmg_instance, fixture_data[0]['paramgram_used']) + assert output['raw_response']['status']['code'] == 0 + + +def test_execute_script(fixture_data, mocker): + mocker.patch("ansible.module_utils.network.fortimanager.fortimanager.FortiManagerHandler.process_request", + side_effect=fixture_data) + # Fixture sets used:########################### + + ################################################## + # script_content: None + # adom: ansible + # script_scope: FGT1 + # script_name: TestScript + # script_target: None + # mode: exec + # script_description: None + # script_package: None + # vdom: root + # script_type: None + ################################################## + + # Test using fixture 1 # + output = fmgr_script.execute_script(fmg_instance, fixture_data[0]['paramgram_used']) + assert isinstance(output['raw_response'], dict) is True