diff --git a/lib/ansible/module_utils/network/fortimanager/__init__.py b/lib/ansible/module_utils/network/fortimanager/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/lib/ansible/module_utils/network/fortimanager/fortimanager.py b/lib/ansible/module_utils/network/fortimanager/fortimanager.py
new file mode 100644
index 0000000000..b778745678
--- /dev/null
+++ b/lib/ansible/module_utils/network/fortimanager/fortimanager.py
@@ -0,0 +1,87 @@
+# This code is part of Ansible, but is an independent component.
+# This particular file snippet, and this file snippet only, is BSD licensed.
+# Modules you write using this snippet, which is embedded dynamically by Ansible
+# still belong to the author of the module, and may assign their own license
+# to the complete work.
+#
+# (c) 2017 Fortinet, Inc
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+
+# check for pyFMG lib
+try:
+ from pyFMG.fortimgr import FortiManager
+ HAS_PYFMGR = True
+except ImportError:
+ HAS_PYFMGR = False
+
+
+class AnsibleFortiManager(object):
+
+ def __init__(self, module, ip=None, username=None, passwd=None, use_ssl=True, verify_ssl=False, timeout=300):
+ self.ip = ip
+ self.username = username
+ self.passwd = passwd
+ self.use_ssl = use_ssl
+ self.verify_ssl = verify_ssl
+ self.timeout = timeout
+ self.fmgr_instance = None
+
+ if not HAS_PYFMGR:
+ module.fail_json(msg='Could not import the python library pyFMG required by this module')
+
+ self.module = module
+
+ def login(self):
+ if self.ip is not None:
+ self.fmgr_instance = FortiManager(self.ip, self.username, self.passwd, use_ssl=self.use_ssl,
+ verify_ssl=self.verify_ssl, timeout=self.timeout, debug=False,
+ disable_request_warnings=True)
+ return self.fmgr_instance.login()
+
+ def logout(self):
+ if self.fmgr_instance.sid is not None:
+ self.fmgr_instance.logout()
+
+ def get(self, url, data):
+ return self.fmgr_instance.get(url, **data)
+
+ def set(self, url, data):
+ return self.fmgr_instance.set(url, **data)
+
+ def update(self, url, data):
+ return self.fmgr_instance.update(url, **data)
+
+ def delete(self, url, data):
+ return self.fmgr_instance.delete(url, **data)
+
+ def add(self, url, data):
+ return self.fmgr_instance.add(url, **data)
+
+ def execute(self, url, data):
+ return self.fmgr_instance.execute(url, **data)
+
+ def move(self, url, data):
+ return self.fmgr_instance.move(url, **data)
+
+ def clone(self, url, data):
+ return self.fmgr_instance.clone(url, **data)
diff --git a/lib/ansible/modules/network/fortimanager/__init__.py b/lib/ansible/modules/network/fortimanager/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/lib/ansible/modules/network/fortimanager/fmgr_script.py b/lib/ansible/modules/network/fortimanager/fmgr_script.py
new file mode 100644
index 0000000000..706d03ddfe
--- /dev/null
+++ b/lib/ansible/modules/network/fortimanager/fmgr_script.py
@@ -0,0 +1,273 @@
+#!/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_script
+version_added: "2.5"
+author: Andrew Welsh
+short_description: Add/Edit/Delete and execute scripts
+description: Create/edit/delete scripts and execute the scripts on the FortiManager using jsonrpc API
+
+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
+ 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"]
+ 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
+ required: false
+'''
+
+EXAMPLES = '''
+- name: CREATE SCRIPT
+ fmgr_script:
+ host: "{{inventory_hostname}}"
+ username: "{{ username }}"
+ password: "{{ password }}"
+ adom: "root"
+ script_name: "TestScript"
+ script_type: "cli"
+ script_target: "remote_device"
+ script_description: "Create by Ansible"
+ script_content: "get system status"
+
+- name: EXECUTE SCRIPT
+ fmgr_script:
+ host: "{{inventory_hostname}}"
+ username: "{{ username }}"
+ password: "{{ password }}"
+ adom: "root"
+ script_name: "TestScript"
+ state: "execute"
+ script_scope: "FGT1,FGT2"
+
+- name: DELETE SCRIPT
+ fmgr_script:
+ host: "{{inventory_hostname}}"
+ username: "{{ username }}"
+ password: "{{ password }}"
+ adom: "root"
+ script_name: "TestScript"
+ state: "delete"
+'''
+
+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 set_script(fmg, script_name, script_type, script_content, script_desc, script_target, adom):
+ """
+ This method sets a script.
+ """
+
+ datagram = {
+ 'content': script_content,
+ 'desc': script_desc,
+ 'name': script_name,
+ 'target': script_target,
+ 'type': script_type,
+ }
+
+ url = '/dvmdb/adom/{adom}/script/'.format(adom=adom)
+ response = fmg.set(url, datagram)
+ return response
+
+
+def delete_script(fmg, script_name, adom):
+ """
+ This method deletes a script.
+ """
+
+ datagram = {
+ 'name': script_name,
+ }
+
+ url = '/dvmdb/adom/{adom}/script/{script_name}'.format(adom=adom, script_name=script_name)
+ response = fmg.delete(url, datagram)
+ return response
+
+
+def execute_script(fmg, script_name, scope, package, adom, vdom):
+ """
+ This method will execute a specific script.
+ """
+
+ scope_list = list()
+ scope = scope.replace(' ', '')
+ scope = scope.split(',')
+ for dev_name in scope:
+ scope_list.append({'name': dev_name, 'vdom': vdom})
+
+ datagram = {
+ 'adom': adom,
+ 'script': script_name,
+ 'package': package,
+ 'scope': scope_list,
+ }
+
+ url = '/dvmdb/adom/{adom}/script/execute'.format(adom=adom)
+ 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"])),
+ state=dict(choices=["execute", "delete", "present"], type="str"),
+
+ script_name=dict(required=True, type="str"),
+ script_type=dict(required=False, type="str"),
+ script_target=dict(required=False, type="str"),
+ script_description=dict(required=False, type="str"),
+ script_content=dict(required=False, type="str"),
+ script_scope=dict(required=False, type="str"),
+ 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")
+ 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"
+
+ 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"]
+
+ # 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 not 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 not results[0] == 0:
+ module.fail_json(msg="Script Execution Failed", **results)
+ elif state == "delete":
+ results = delete_script(fmg, script_name, adom)
+ if not results[0] == 0:
+ module.fail_json(msg="Script Deletion 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 8eefcd00cd..3af32de945 100644
--- a/test/runner/requirements/units.txt
+++ b/test/runner/requirements/units.txt
@@ -23,5 +23,8 @@ f5-sdk ; python_version >= '2.7'
f5-icontrol-rest ; python_version >= '2.7'
deepdiff
+# requirement for Fortinet specific modules
+pyfmg
+
# requirement for aci_rest module
xmljson
diff --git a/test/units/modules/network/fortimanager/__init__.py b/test/units/modules/network/fortimanager/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/test/units/modules/network/fortimanager/fortimanager_module.py b/test/units/modules/network/fortimanager/fortimanager_module.py
new file mode 100644
index 0000000000..b9f424ee11
--- /dev/null
+++ b/test/units/modules/network/fortimanager/fortimanager_module.py
@@ -0,0 +1,64 @@
+# (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 units.modules.utils import AnsibleExitJson, AnsibleFailJson, ModuleTestCase
+
+
+class TestFortimanagerModule(ModuleTestCase):
+
+ def execute_module(self, failed=False, changed=False, commands=None, sort=True, defaults=False):
+
+ self.load_fixtures(commands)
+
+ if failed:
+ result = self.failed()
+ self.assertTrue(result['failed'], result)
+ else:
+ result = self.changed(changed)
+ self.assertEqual(result['changed'], changed, result)
+
+ if commands is not None:
+ if sort:
+ self.assertEqual(sorted(commands), sorted(result['commands']), result['commands'])
+ else:
+ self.assertEqual(commands, result['commands'], result['commands'])
+
+ return result
+
+ def failed(self):
+ with self.assertRaises(AnsibleFailJson) as exc:
+ self.module.main()
+
+ result = exc.exception.args[0]
+ self.assertTrue(result['failed'], result)
+ return result
+
+ def changed(self, changed=False):
+ with self.assertRaises(AnsibleExitJson) as exc:
+ self.module.main()
+
+ result = exc.exception.args[0]
+ self.assertEqual(result['changed'], changed, result)
+ return result
+
+ def load_fixtures(self, commands=None):
+ pass
diff --git a/test/units/modules/network/fortimanager/test_fmgr_script.py b/test/units/modules/network/fortimanager/test_fmgr_script.py
new file mode 100644
index 0000000000..86caf02dbf
--- /dev/null
+++ b/test/units/modules/network/fortimanager/test_fmgr_script.py
@@ -0,0 +1,55 @@
+# (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_script
+ from .fortimanager_module import TestFortimanagerModule
+ from units.modules.utils import set_module_args
+except ImportError:
+ raise SkipTest("Could not load required modules for testing")
+
+
+class TestFmgrScriptModule(TestFortimanagerModule):
+
+ 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')
+
+ 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')