From 7bc266001766635bb48fed101a7b9976d3f66cf0 Mon Sep 17 00:00:00 2001 From: Ghilli3 Date: Thu, 30 Aug 2018 07:25:17 -0600 Subject: [PATCH] fortimanager/fmgr_provisioning.py (#35743) * Initial commit for new provisioning module --- .../network/fortimanager/fmgr_provisioning.py | 372 ++++++++++++++++++ test/runner/requirements/units.txt | 2 +- .../fortimanager/test_fmgr_provisioning.py | 63 +++ 3 files changed, 436 insertions(+), 1 deletion(-) create mode 100644 lib/ansible/modules/network/fortimanager/fmgr_provisioning.py create mode 100644 test/units/modules/network/fortimanager/test_fmgr_provisioning.py diff --git a/lib/ansible/modules/network/fortimanager/fmgr_provisioning.py b/lib/ansible/modules/network/fortimanager/fmgr_provisioning.py new file mode 100644 index 0000000000..8231527d33 --- /dev/null +++ b/lib/ansible/modules/network/fortimanager/fmgr_provisioning.py @@ -0,0 +1,372 @@ +#!/usr/bin/python +# +# 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 + +ANSIBLE_METADATA = {'status': ['preview'], + 'supported_by': 'community', + 'metadata_version': '1.1'} + +DOCUMENTATION = ''' +--- +module: fmgr_provisioning +version_added: "2.7" +author: Andrew Welsh +short_description: Provision devices via FortiMananger +description: + - Add model devices on the FortiManager using jsonrpc API and have them pre-configured, + so when central management is configured, the configuration is pushed down to the + registering devices + +options: + adom: + description: + - The administrative domain (admon) the configuration belongs to + required: true + vdom: + description: + - The virtual domain (vdom) the configuration belongs to + host: + 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. + required: false + + policy_package: + description: + - The name of the policy package to be assigned to the device. + required: True + name: + description: + - The name of the device to be provisioned. + required: True + group: + description: + - The name of the device group the provisioned device can belong to. + required: False + serial: + description: + - The serial number of the device that will be provisioned. + required: True + platform: + description: + - The platform of the device, such as model number or VM. + required: True + description: + description: + - Description of the device to be provisioned. + required: False + os_version: + description: + - The Fortinet OS version to be used for the device, such as 5.0 or 6.0. + required: True + minor_release: + description: + - The minor release number such as 6.X.1, as X being the minor release. + required: False + patch_release: + description: + - The patch release number such as 6.0.X, as X being the patch release. + required: False + os_type: + description: + - The Fortinet OS type to be pushed to the device, such as 'FOS' for FortiOS. + required: True +''' + +EXAMPLES = ''' +- name: Create Model Device + hosts: FortiManager + connection: local + gather_facts: False + + tasks: + + - name: Create FGT1 Model Device + fmgr_provision: + host: "{{ inventory_hostname }}" + username: "{{ username }}" + password: "{{ password }}" + adom: "root" + vdom: "root" + policy_package: "default" + name: "FGT1" + group: "Ansible" + serial: "FGVM000000117994" + platform: "FortiGate-VM64" + description: "Provisioned by Ansible" + os_version: '6.0' + minor_release: 0 + patch_release: 0 + os_type: 'fos' + + + - name: Create FGT2 Model Device + fmgr_provision: + host: "{{ inventory_hostname }}" + username: "{{ username }}" + password: "{{ password }}" + adom: "root" + vdom: "root" + policy_package: "test_pp" + name: "FGT2" + group: "Ansible" + serial: "FGVM000000117992" + platform: "FortiGate-VM64" + description: "Provisioned by Ansible" + os_version: '5.0' + minor_release: 6 + patch_release: 0 + os_type: 'fos' + +''' + +RETURN = """ +api_result: + description: full API response, includes status code and message + returned: always + type: string +""" + +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 + + +def dev_group_exists(fmg, dev_grp_name, adom): + datagram = { + 'adom': adom, + 'name': dev_grp_name, + } + + url = '/dvmdb/adom/{adom}/group/{dev_grp_name}'.format(adom=adom, dev_grp_name=dev_grp_name) + response = fmg.get(url, datagram) + return response + + +def prov_template_exists(fmg, prov_template, adom, vdom): + datagram = { + 'name': prov_template, + 'adom': adom, + } + + url = '/pm/devprof/adom/{adom}/devprof/{name}'.format(adom=adom, name=prov_template) + response = fmg.get(url, datagram) + return response + + +def create_model_device(fmg, name, serial, group, platform, os_version, + os_type, minor_release, patch_release=0, adom='root'): + datagram = { + 'adom': adom, + 'flags': ['create_task', 'nonblocking'], + 'groups': [{'name': group, 'vdom': 'root'}], + 'device': { + 'mr': minor_release, + 'name': name, + 'sn': serial, + 'mgmt_mode': 'fmg', + 'device action': 'add_model', + 'platform_str': platform, + 'os_ver': os_version, + 'os_type': os_type, + 'patch': patch_release, + 'desc': 'Provisioned by Ansible', + } + } + + url = '/dvm/cmd/add/device' + response = fmg.execute(url, datagram) + return response + + +def update_flags(fmg, name): + datagram = { + 'flags': ['is_model', 'linked_to_model'] + } + url = 'dvmdb/device/{name}'.format(name=name) + response = fmg.update(url, datagram) + return response + + +def assign_provision_template(fmg, template, adom, target): + datagram = { + 'name': template, + 'type': 'devprof', + 'description': 'Provisioned by Ansible', + 'scope member': [{'name': target}] + } + url = "/pm/devprof/adom/{adom}".format(adom=adom) + response = fmg.update(url, datagram) + return response + + +def set_devprof_scope(self, provisioning_template, adom, provision_targets): + """ + GET the DevProf (check to see if exists) + """ + fields = dict() + targets = [] + fields["name"] = provisioning_template + fields["type"] = "devprof" + fields["description"] = "CreatedByAnsible" + + for target in provision_targets.strip().split(","): + # split the host on the space to get the mask out + new_target = {"name": target} + targets.append(new_target) + + fields["scope member"] = targets + + body = {"method": "set", "params": [{"url": "/pm/devprof/adom/{adom}".format(adom=adom), + "data": fields, "session": self.session}]} + response = self.make_request(body).json() + return response + + +def assign_dev_grp(fmg, grp_name, device_name, vdom, adom): + datagram = { + 'name': device_name, + 'vdom': vdom, + } + url = "/dvmdb/adom/{adom}/group/{grp_name}/object member".format(adom=adom, grp_name=grp_name) + response = fmg.set(url, datagram) + return response + + +def update_install_target(fmg, device, pp='default', vdom='root', adom='root'): + datagram = { + 'scope member': [{'name': device, 'vdom': vdom}], + 'type': 'pkg' + } + url = '/pm/pkg/adom/{adom}/{pkg_name}'.format(adom=adom, pkg_name=pp) + response = fmg.update(url, datagram) + return response + + +def install_pp(fmg, device, pp='default', vdom='root', adom='root'): + datagram = { + 'adom': adom, + 'flags': 'nonblocking', + 'pkg': pp, + 'scope': [{'name': device, 'vdom': vdom}], + } + url = 'securityconsole/install/package' + response = fmg.execute(url, datagram) + 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"]), no_log=True), + + policy_package=dict(required=False, type="str"), + name=dict(required=False, type="str"), + group=dict(required=False, type="str"), + serial=dict(required=True, type="str"), + platform=dict(required=True, type="str"), + description=dict(required=False, type="str"), + os_version=dict(required=True, type="str"), + minor_release=dict(required=False, type="str"), + patch_release=dict(required=False, type="str"), + os_type=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") + else: + + if module.params["policy_package"] is None: + module.params["policy_package"] = 'default' + if module.params["adom"] is None: + module.params["adom"] = 'root' + if module.params["vdom"] is None: + module.params["vdom"] = 'root' + if module.params["platform"] is None: + module.params["platform"] = 'FortiGate-VM64' + if module.params["os_type"] is None: + module.params["os_type"] = 'fos' + + results = create_model_device(fmg, + module.params["name"], + module.params["serial"], + module.params["group"], + module.params["platform"], + module.params["os_ver"], + module.params["os_type"], + module.params["minor_release"], + module.params["patch_release"], + module.params["adom"]) + if not results[0] == 0: + module.fail_json(msg="Create model failed", **results) + + results = update_flags(fmg, module.params["name"]) + if not results[0] == 0: + module.fail_json(msg="Update device flags failed", **results) + + # results = assign_dev_grp(fmg, 'Ansible', 'FGVM000000117992', 'root', 'root') + # if not results[0] == 0: + # module.fail_json(msg="Setting device group failed", **results) + + results = update_install_target(fmg, module.params["name"], module.params["policy_package"]) + if not results[0] == 0: + module.fail_json(msg="Adding device target to package failed", **results) + + results = install_pp(fmg, module.params["name"], module.params["policy_package"]) + if not results[0] == 0: + module.fail_json(msg="Installing policy package failed", **results) + + fmg.logout() + + # results is returned as a tuple + return module.exit_json(**results[1]) + + +if __name__ == "__main__": + main() diff --git a/test/runner/requirements/units.txt b/test/runner/requirements/units.txt index d412d4383e..83f89d76de 100644 --- a/test/runner/requirements/units.txt +++ b/test/runner/requirements/units.txt @@ -24,7 +24,7 @@ f5-icontrol-rest ; python_version >= '2.7' deepdiff # requirement for Fortinet specific modules -pyfmg +pyFMG # requirement for aci_rest module xmljson diff --git a/test/units/modules/network/fortimanager/test_fmgr_provisioning.py b/test/units/modules/network/fortimanager/test_fmgr_provisioning.py new file mode 100644 index 0000000000..b774bb223c --- /dev/null +++ b/test/units/modules/network/fortimanager/test_fmgr_provisioning.py @@ -0,0 +1,63 @@ +# (c) 2016 Red Hat 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 . + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from nose.plugins.skip import SkipTest + +try: + from ansible.modules.network.fortimanager import fmgr_provisioning + from .fortimanager_module import TestFortimanagerModule + from units.modules.utils import set_module_args +except ImportError: + raise SkipTest("Could not load required modules for testing") + +try: + from pyFMG.fortimgr import FortiManager +except ImportError: + raise SkipTest("FortiManager tests require pyFMG package") + + +class TestFmgrProvisioningModule(TestFortimanagerModule): + + module = fmgr_provisioning + + def test_fmg_script_fail_connect(self): + set_module_args(dict(host='1.1.1.1', username='admin', password='admin', adom='root', + vdom='root', policy_package='root', name='FGT1', serial='FGVM000000117992', + platform='FortiGate-VM64', os_version='5.0', minor_release='6', + patch_release='0', os_type='fos')) + result = self.execute_module(failed=True) + self.assertEqual(result['msg'], 'Connection to FortiManager Failed') + + def test_fmg_script_login_fail_host(self): + set_module_args(dict(username='admin', password='admin', adom='root', + vdom='root', policy_package='root', name='FGT1', serial='FGVM000000117992', + platform='FortiGate-VM64', os_version='5.0', minor_release='6', + patch_release='0', os_type='fos')) + 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', + vdom='root', policy_package='root', name='FGT1', serial='FGVM000000117992', + platform='FortiGate-VM64', os_version='5.0', minor_release='6', + patch_release='0', os_type='fos')) + result = self.execute_module(failed=True) + self.assertEqual(result['msg'], 'Host and username are required for connection')