diff --git a/lib/ansible/module_utils/network/fortios/argspec/__init__.py b/lib/ansible/module_utils/network/fortios/argspec/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/lib/ansible/module_utils/network/fortios/argspec/facts/__init__.py b/lib/ansible/module_utils/network/fortios/argspec/facts/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/lib/ansible/module_utils/network/fortios/argspec/facts/facts.py b/lib/ansible/module_utils/network/fortios/argspec/facts/facts.py
new file mode 100644
index 00000000000..2f3e341810e
--- /dev/null
+++ b/lib/ansible/module_utils/network/fortios/argspec/facts/facts.py
@@ -0,0 +1,45 @@
+from __future__ import (absolute_import, division, print_function)
+# Copyright 2019 Fortinet, Inc.
+#
+# 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.
+#
+# 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 this program. If not, see .
+
+__metaclass__ = type
+
+"""
+The arg spec for the fortios monitor module.
+"""
+
+
+class FactsArgs(object):
+ """ The arg spec for the fortios monitor module
+ """
+
+ def __init__(self, **kwargs):
+ pass
+
+ argument_spec = {
+ "host": {"required": False, "type": "str"},
+ "username": {"required": False, "type": "str"},
+ "password": {"required": False, "type": "str", "no_log": True},
+ "vdom": {"required": False, "type": "str", "default": "root"},
+ "https": {"required": False, "type": "bool", "default": True},
+ "ssl_verify": {"required": False, "type": "bool", "default": False},
+ "gather_subset": {
+ "required": True, "type": "list", "elements": "dict",
+ "options": {
+ "fact": {"required": True, "type": "str"},
+ "filters": {"required": False, "type": "list", "elements": "dict"}
+ }
+ }
+ }
diff --git a/lib/ansible/module_utils/network/fortios/argspec/system/__init__.py b/lib/ansible/module_utils/network/fortios/argspec/system/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/lib/ansible/module_utils/network/fortios/argspec/system/system.py b/lib/ansible/module_utils/network/fortios/argspec/system/system.py
new file mode 100644
index 00000000000..76454f9d9ec
--- /dev/null
+++ b/lib/ansible/module_utils/network/fortios/argspec/system/system.py
@@ -0,0 +1,28 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Fortinet, Inc.
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The arg spec for the fortios_facts module
+"""
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+class SystemArgs(object):
+ """The arg spec for the fortios_facts module
+ """
+
+ FACT_SYSTEM_SUBSETS = frozenset([
+ 'system_current-admins_select',
+ 'system_firmware_select',
+ 'system_fortimanager_status',
+ 'system_ha-checksums_select',
+ 'system_interface_select',
+ 'system_status_select',
+ 'system_time_select',
+ ])
+
+ def __init__(self, **kwargs):
+ pass
diff --git a/lib/ansible/module_utils/network/fortios/facts/__init__.py b/lib/ansible/module_utils/network/fortios/facts/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/lib/ansible/module_utils/network/fortios/facts/facts.py b/lib/ansible/module_utils/network/fortios/facts/facts.py
new file mode 100644
index 00000000000..a881b5aeda1
--- /dev/null
+++ b/lib/ansible/module_utils/network/fortios/facts/facts.py
@@ -0,0 +1,92 @@
+from __future__ import (absolute_import, division, print_function)
+# Copyright 2019 Fortinet, Inc.
+#
+# 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.
+#
+# 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 this program. If not, see .
+
+__metaclass__ = type
+
+"""
+The facts class for fortios
+this file validates each subset of monitor and selectively
+calls the appropriate facts gathering and monitoring function
+"""
+
+from ansible.module_utils.network.fortios.argspec.facts.facts import FactsArgs
+from ansible.module_utils.network.fortios.argspec.system.system import SystemArgs
+from ansible.module_utils.network.common.facts.facts import FactsBase
+from ansible.module_utils.network.fortios.facts.system.system import SystemFacts
+
+
+class Facts(FactsBase):
+ """ The facts class for fortios
+ """
+
+ FACT_SUBSETS = {
+ "system": SystemFacts
+ }
+
+ def __init__(self, module, fos=None, subset=None):
+ super(Facts, self).__init__(module)
+ self._fos = fos
+ self._subset = subset
+
+ def gen_runable(self, subsets, valid_subsets):
+ """ Generate the runable subset
+
+ :param module: The module instance
+ :param subsets: The provided subsets
+ :param valid_subsets: The valid subsets
+ :rtype: list
+ :returns: The runable subsets
+ """
+ runable_subsets = []
+ FACT_DETAIL_SUBSETS = []
+ FACT_DETAIL_SUBSETS.extend(SystemArgs.FACT_SYSTEM_SUBSETS)
+
+ for subset in subsets:
+ if subset['fact'] not in FACT_DETAIL_SUBSETS:
+ self._module.fail_json(msg='Subset must be one of [%s], got %s' %
+ (', '.join(sorted([item for item in FACT_DETAIL_SUBSETS])), subset['fact']))
+
+ for valid_subset in frozenset(self.FACT_SUBSETS.keys()):
+ if subset['fact'].startswith(valid_subset):
+ runable_subsets.append((subset, valid_subset))
+
+ return runable_subsets
+
+ def get_network_legacy_facts(self, fact_legacy_obj_map, legacy_facts_type=None):
+ if not legacy_facts_type:
+ legacy_facts_type = self._gather_subset
+
+ runable_subsets = self.gen_runable(legacy_facts_type, frozenset(fact_legacy_obj_map.keys()))
+ if runable_subsets:
+ self.ansible_facts['ansible_net_gather_subset'] = []
+
+ instances = list()
+ for (subset, valid_subset) in runable_subsets:
+ instances.append(fact_legacy_obj_map[valid_subset](self._module, self._fos, subset))
+
+ for inst in instances:
+ inst.populate_facts(self._connection, self.ansible_facts)
+
+ def get_facts(self, facts_type=None, data=None):
+ """ Collect the facts for fortios
+ :param facts_type: List of facts types
+ :param data: previously collected conf
+ :rtype: dict
+ :return: the facts gathered
+ """
+ self.get_network_legacy_facts(self.FACT_SUBSETS, facts_type)
+
+ return self.ansible_facts, self._warnings
diff --git a/lib/ansible/module_utils/network/fortios/facts/system/__init__.py b/lib/ansible/module_utils/network/fortios/facts/system/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/lib/ansible/module_utils/network/fortios/facts/system/system.py b/lib/ansible/module_utils/network/fortios/facts/system/system.py
new file mode 100644
index 00000000000..5731a0985b1
--- /dev/null
+++ b/lib/ansible/module_utils/network/fortios/facts/system/system.py
@@ -0,0 +1,63 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Fortinet, Inc.
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The fortios system facts class
+It is in this file the runtime information is collected from the device
+for a given resource, parsed, and the facts tree is populated
+based on the configuration.
+"""
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+import re
+from ansible.module_utils.network.common import utils
+from ansible.module_utils.network.fortios.argspec.system.system import SystemArgs
+
+
+class SystemFacts(object):
+ """ The fortios system facts class
+ """
+
+ def __init__(self, module, fos=None, subset=None, subspec='config', options='options'):
+ self._module = module
+ self._fos = fos
+ self._subset = subset
+
+ def populate_facts(self, connection, ansible_facts, data=None):
+ """ Populate the facts for system
+ :param connection: the device connection
+ :param ansible_facts: Facts dictionary
+ :rtype: dictionary
+ :returns: facts
+ """
+ ansible_facts['ansible_network_resources'].pop('system', None)
+ facts = {}
+ if self._subset['fact'].startswith(tuple(SystemArgs.FACT_SYSTEM_SUBSETS)):
+ gather_method = getattr(self, self._subset['fact'].replace('-', '_'), self.system_fact)
+ resp = gather_method()
+ facts.update({self._subset['fact']: resp})
+
+ ansible_facts['ansible_network_resources'].update(facts)
+ return ansible_facts
+
+ def system_fact(self):
+ fos = self._fos
+ vdom = self._module.params['vdom']
+ return fos.monitor('system', self._subset['fact'][len('system_'):].replace('_', '/'), vdom=vdom)
+
+ def system_interface_select(self):
+ fos = self._fos
+ vdom = self._module.params['vdom']
+
+ query_string = '?vdom=' + vdom
+ system_interface_select_param = self._subset['filters']
+ if system_interface_select_param:
+ for filter in system_interface_select_param:
+ for key, val in filter.items():
+ if val:
+ query_string += '&' + str(key) + '=' + str(val)
+
+ return fos.monitor('system', self._subset['fact'][len('system_'):].replace('_', '/') + query_string, vdom=None)
diff --git a/lib/ansible/modules/network/fortios/fortios_facts.py b/lib/ansible/modules/network/fortios/fortios_facts.py
new file mode 100644
index 00000000000..72527e82b08
--- /dev/null
+++ b/lib/ansible/modules/network/fortios/fortios_facts.py
@@ -0,0 +1,282 @@
+#!/usr/bin/python
+from __future__ import (absolute_import, division, print_function)
+# Copyright 2019 Fortinet, Inc.
+#
+# 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.
+#
+# 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 this program. If not, see .
+
+__metaclass__ = type
+
+ANSIBLE_METADATA = {'status': ['preview'],
+ 'supported_by': 'community',
+ 'metadata_version': '1.1'}
+
+DOCUMENTATION = '''
+---
+module: fortios_facts
+version_added: "2.9"
+short_description: Get facts about fortios devices.
+description:
+ - Collects facts from network devices running the fortios operating
+ system. This module places the facts gathered in the fact tree keyed by the
+ respective resource name. This facts module will only collect those
+ facts which user specified in playbook.
+author:
+ - Don Yao (@fortinetps)
+ - Miguel Angel Munoz (@mamunozgonzalez)
+ - Nicolas Thomas (@thomnico)
+notes:
+ - Support both legacy mode (local_action) and httpapi
+ - Legacy mode run as a local_action in your playbook, requires fortiosapi library developed by Fortinet
+ - httpapi mode is the new recommend way for network modules
+requirements:
+ - fortiosapi>=0.9.8
+options:
+ host:
+ description:
+ - FortiOS or FortiGate IP address.
+ type: str
+ required: false
+ username:
+ description:
+ - FortiOS or FortiGate username.
+ type: str
+ required: false
+ password:
+ description:
+ - FortiOS or FortiGate password.
+ type: str
+ default: ""
+ required: false
+ vdom:
+ description:
+ - Virtual domain, among those defined previously. A vdom is a
+ virtual instance of the FortiGate that can be configured and
+ used as a different unit.
+ type: str
+ default: root
+ required: false
+ https:
+ description:
+ - Indicates if the requests towards FortiGate must use HTTPS protocol.
+ type: bool
+ default: true
+ required: false
+ ssl_verify:
+ description:
+ - Ensures FortiGate certificate must be verified by a proper CA.
+ type: bool
+ default: false
+ required: false
+ gather_subset:
+ description:
+ - When supplied, this argument will restrict the facts collected
+ to a given subset. Possible values for this argument include
+ system_current-admins_select, system_firmware_select,
+ system_fortimanager_status, system_ha-checksums_select,
+ system_interface_select, system_status_select and system_time_select
+ type: list
+ elements: dict
+ required: true
+ suboptions:
+ fact:
+ description:
+ - Name of the facts to gather
+ type: str
+ required: true
+ filters:
+ description:
+ - Filters apply when gathering facts
+ type: list
+ elements: dict
+ required: false
+'''
+
+EXAMPLES = '''
+- hosts: localhost
+ vars:
+ host: "192.168.122.40"
+ username: "admin"
+ password: ""
+ vdom: "root"
+ ssl_verify: "False"
+
+ tasks:
+ - name: gather basic system status facts
+ fortios_facts:
+ host: "{{ host }}"
+ username: "{{ username }}"
+ password: "{{ password }}"
+ vdom: "{{ vdom }}"
+ gather_subset:
+ - fact: 'system_status_select'
+
+ - name: gather all pysical interfaces status facts
+ fortios_facts:
+ host: "{{ host }}"
+ username: "{{ username }}"
+ password: "{{ password }}"
+ vdom: "{{ vdom }}"
+ gather_subset:
+ - fact: 'system_interface_select'
+
+ - name: gather gather all pysical and vlan interfaces status facts
+ fortios_facts:
+ host: "{{ host }}"
+ username: "{{ username }}"
+ password: "{{ password }}"
+ vdom: "{{ vdom }}"
+ gather_subset:
+ - fact: 'system_interface_select'
+ filters:
+ - include_vlan: true
+
+ - name: gather basic system info and physical interface port3 status facts
+ fortios_facts:
+ host: "{{ host }}"
+ username: "{{ username }}"
+ password: "{{ password }}"
+ vdom: "{{ vdom }}"
+ gather_subset:
+ - fact: 'system_status_select'
+ - fact: 'system_interface_select'
+ filters:
+ - interface_name: 'port3'
+'''
+
+RETURN = '''
+build:
+ description: Build number of the fortigate image
+ returned: always
+ type: str
+ sample: '1547'
+http_method:
+ description: Last method used to provision the content into FortiGate
+ returned: always
+ type: str
+ sample: 'GET'
+name:
+ description: Name of the table used to fulfill the request
+ returned: always
+ type: str
+ sample: "firmware"
+path:
+ description: Path of the table used to fulfill the request
+ returned: always
+ type: str
+ sample: "system"
+revision:
+ description: Internal revision number
+ returned: always
+ type: str
+ sample: "17.0.2.10658"
+serial:
+ description: Serial number of the unit
+ returned: always
+ type: str
+ sample: "FGVMEVYYQT3AB5352"
+status:
+ description: Indication of the operation's result
+ returned: always
+ type: str
+ sample: "success"
+vdom:
+ description: Virtual domain used
+ returned: always
+ type: str
+ sample: "root"
+version:
+ description: Version of the FortiGate
+ returned: always
+ type: str
+ sample: "v5.6.3"
+ansible_facts:
+ description: The list of fact subsets collected from the device
+ returned: always
+ type: dict
+
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.connection import Connection
+from ansible.module_utils.network.fortios.fortios import FortiOSHandler
+from ansible.module_utils.network.fortimanager.common import FAIL_SOCKET_MSG
+from ansible.module_utils.network.fortios.argspec.facts.facts import FactsArgs
+from ansible.module_utils.network.fortios.facts.facts import Facts
+
+
+def login(data, fos):
+ host = data['host']
+ username = data['username']
+ password = data['password']
+ ssl_verify = data['ssl_verify']
+
+ fos.debug('on')
+ if 'https' in data and not data['https']:
+ fos.https('off')
+ else:
+ fos.https('on')
+
+ fos.login(host, username, password, verify=ssl_verify)
+
+
+def main():
+ """ Main entry point for AnsibleModule
+ """
+ argument_spec = FactsArgs.argument_spec
+
+ module = AnsibleModule(argument_spec=argument_spec,
+ supports_check_mode=False)
+
+ # legacy_mode refers to using fortiosapi instead of HTTPAPI
+ legacy_mode = 'host' in module.params and module.params['host'] is not None and \
+ 'username' in module.params and module.params['username'] is not None and \
+ 'password' in module.params and module.params['password'] is not None
+
+ if not legacy_mode:
+ if module._socket_path:
+ warnings = []
+ connection = Connection(module._socket_path)
+ module._connection = connection
+ fos = FortiOSHandler(connection)
+
+ result = Facts(module, fos).get_facts()
+
+ ansible_facts, additional_warnings = result
+ warnings.extend(additional_warnings)
+
+ module.exit_json(ansible_facts=ansible_facts, warnings=warnings)
+ else:
+ module.fail_json(**FAIL_SOCKET_MSG)
+ else:
+ try:
+ from fortiosapi import FortiOSAPI
+ except ImportError:
+ module.fail_json(msg="fortiosapi module is required")
+
+ warnings = []
+
+ fos = FortiOSAPI()
+ login(module.params, fos)
+ module._connection = fos
+
+ result = Facts(module, fos).get_facts()
+
+ ansible_facts, additional_warnings = result
+ warnings.extend(additional_warnings)
+
+ module.exit_json(ansible_facts=ansible_facts, warnings=warnings)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/test/units/modules/network/fortios/test_fortios_facts.py b/test/units/modules/network/fortios/test_fortios_facts.py
new file mode 100644
index 00000000000..8b9a4145d72
--- /dev/null
+++ b/test/units/modules/network/fortios/test_fortios_facts.py
@@ -0,0 +1,103 @@
+# Copyright 2019 Fortinet, Inc.
+#
+# 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.
+#
+# 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 .
+
+# Make coding more python3-ish
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+import os
+import json
+import pytest
+from mock import ANY
+from units.modules.utils import exit_json, fail_json
+from units.compat import unittest
+from units.compat.mock import patch
+from ansible.module_utils import basic
+from ansible.module_utils.network.fortios.fortios import FortiOSHandler
+
+try:
+ from ansible.module_utils.network.fortios.facts.facts import Facts
+ from ansible.modules.network.fortios import fortios_facts
+except ImportError:
+ pytest.skip("Could not load required modules for testing", allow_module_level=True)
+
+
+@pytest.fixture(autouse=True)
+def connection_mock(mocker):
+ connection_class_mock = mocker.patch('ansible.modules.network.fortios.fortios_facts.Connection')
+ return connection_class_mock
+
+
+fos_instance = FortiOSHandler(connection_mock)
+
+
+def test_facts_get(mocker):
+ monitor_method_result = {'status': 'success', 'http_method': 'GET', 'http_status': 200}
+ monitor_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.monitor', return_value=monitor_method_result)
+ mock_module = patch.multiple(basic.AnsibleModule, exit_json=exit_json, fail_json=fail_json)
+ mock_module._connection = connection_mock
+
+ # test case 01, args with single gather_subset
+ args = {
+ 'vdom': 'root',
+ 'gather_subset': [
+ {'fact': 'system_status_select'},
+ ]
+ }
+ mock_module.params = args
+
+ response, ignore = Facts(mock_module, fos_instance).get_facts()
+
+ monitor_method_mock.assert_called_with('system', 'status/select', vdom='root')
+ assert response['ansible_network_resources']['system_status_select']['status'] == 'success'
+ assert response['ansible_network_resources']['system_status_select']['http_status'] == 200
+
+ # test case 02, args with single gather_subset with filters
+ args = {
+ 'vdom': 'root',
+ 'gather_subset': [
+ {'fact': 'system_interface_select', 'filters': [{'include_vlan': 'true'}, {'interface_name': 'port3'}]},
+ ]
+ }
+
+ mock_module.params = args
+
+ response, ignore = Facts(mock_module, fos_instance).get_facts()
+
+ monitor_method_mock.assert_called_with('system', 'interface/select?vdom=root&include_vlan=true&interface_name=port3', vdom=None)
+ assert response['ansible_network_resources']['system_interface_select']['status'] == 'success'
+ assert response['ansible_network_resources']['system_interface_select']['http_status'] == 200
+
+ # test case 03, args with multiple gather_subset
+ args = {
+ 'vdom': 'root',
+ 'gather_subset': [
+ {'fact': 'system_current-admins_select'},
+ {'fact': 'system_firmware_select'},
+ {'fact': 'system_fortimanager_status'},
+ {'fact': 'system_ha-checksums_select'},
+ ]
+ }
+
+ mock_module.params = args
+
+ response, ignore = Facts(mock_module, fos_instance).get_facts()
+
+ monitor_method_mock.assert_any_call('system', 'current-admins/select', vdom='root')
+ monitor_method_mock.assert_any_call('system', 'firmware/select', vdom='root')
+ monitor_method_mock.assert_any_call('system', 'fortimanager/status', vdom='root')
+ monitor_method_mock.assert_any_call('system', 'ha-checksums/select', vdom='root')
+ assert response['ansible_network_resources']['system_ha-checksums_select']['status'] == 'success'
+ assert response['ansible_network_resources']['system_ha-checksums_select']['http_status'] == 200