fmgr_device

Metadata

Name: fmgr_device

Description: Add or remove a device or list of devices from FortiManager Device Manager using JSON RPC API.

Author(s):

  • Luke Weighall (github: @lweighall)
  • Andrew Welsh (github: @Ghilli3)
  • Jim Huber (github: @p4r4n0y1ng)

Ansible Version Added/Required: 2.8

Dev Status: COMPLETED/MERGED

Owning Developer: Luke Weighall

Module Github Link

Parameters

adom

  • Description: The ADOM the configuration should belong to.
  • Required: True
  • default: root

blind_add

  • Description: When adding a device, module will check if it exists, and skip if it does.

    If enabled, this option will stop the module from checking if it already exists, and blindly add the device.

  • Required: False

  • default: disable

  • choices: [‘enable’, ‘disable’]

device_ip

  • Description: The IP of the device being added to FortiManager. Supports both IPv4 and IPv6.
  • Required: False

device_password

  • Description: The password of the device being added to FortiManager.
  • Required: False

device_serial

  • Description: The serial number of the device being added to FortiManager.
  • Required: False

device_unique_name

  • Description: The desired “friendly” name of the device being added to FortiManager.
  • Required: False

device_username

  • Description: The username of the device being added to FortiManager.
  • Required: False

mode

  • Description: The desired mode of the specified object.
  • Required: False
  • default: add
  • choices: [‘add’, ‘delete’]

Functions

  • discover_device
def discover_device(fmgr, paramgram):
    """
    This method is used to discover devices before adding them to FMGR

    :param fmgr: The fmgr object instance from fmgr_utils.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 = {
        "odd_request_form": "True",
        "device": {"adm_usr": paramgram["device_username"],
                   "adm_pass": paramgram["device_password"],
                   "ip": paramgram["device_ip"]}
    }

    url = '/dvm/cmd/discover/device/'

    response = fmgr.process_request(url, datagram, FMGRMethods.EXEC)
    return response
  • add_device
def add_device(fmgr, paramgram):
    """
    This method is used to add devices to the FMGR

    :param fmgr: The fmgr object instance from fmgr_utils.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 = {
        "adom": paramgram["adom"],
        "flags": ["create_task", "nonblocking"],
        "odd_request_form": "True",
        "device": {"adm_usr": paramgram["device_username"], "adm_pass": paramgram["device_password"],
                   "ip": paramgram["device_ip"], "name": paramgram["device_unique_name"],
                   "sn": paramgram["device_serial"], "mgmt_mode": "fmgfaz", "flags": 24}
    }

    url = '/dvm/cmd/add/device/'
    response = fmgr.process_request(url, datagram, FMGRMethods.EXEC)
    return response
  • delete_device
def delete_device(fmgr, paramgram):
    """
    This method deletes a device from the FMGR

    :param fmgr: The fmgr object instance from fmgr_utils.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 = {
        "adom": paramgram["adom"],
        "flags": ["create_task", "nonblocking"],
        "device": paramgram["device_unique_name"],
    }

    url = '/dvm/cmd/del/device/'
    response = fmgr.process_request(url, datagram, FMGRMethods.EXEC)
    return response
  • get_device
def get_device(fmgr, paramgram):
    """
    This method attempts to find the firewall on FortiManager to see if it already exists.

    :param fmgr: The fmgr object instance from fmgr_utils.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 = {
        "adom": paramgram["adom"],
        "filter": ["name", "==", paramgram["device_unique_name"]],
    }

    url = '/dvmdb/adom/{adom}/device/{name}'.format(adom=paramgram["adom"],
                                                    name=paramgram["device_unique_name"])
    response = fmgr.process_request(url, datagram, FMGRMethods.GET)
    return response
  • main
def main():
    argument_spec = dict(
        adom=dict(required=False, type="str", default="root"),
        mode=dict(choices=["add", "delete"], type="str", default="add"),
        blind_add=dict(choices=["enable", "disable"], type="str", default="disable"),
        device_ip=dict(required=False, type="str"),
        device_username=dict(required=False, type="str"),
        device_password=dict(required=False, type="str", no_log=True),
        device_unique_name=dict(required=True, type="str"),
        device_serial=dict(required=False, type="str")
    )

    # BUILD MODULE OBJECT SO WE CAN BUILD THE PARAMGRAM
    module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, )

    # BUILD THE PARAMGRAM
    paramgram = {
        "device_ip": module.params["device_ip"],
        "device_username": module.params["device_username"],
        "device_password": module.params["device_password"],
        "device_unique_name": module.params["device_unique_name"],
        "device_serial": module.params["device_serial"],
        "adom": module.params["adom"],
        "mode": module.params["mode"]
    }

    # INSERT THE PARAMGRAM INTO THE MODULE SO WHEN WE PASS IT TO MOD_UTILS.FortiManagerHandler IT HAS THAT INFO
    module.paramgram = paramgram

    # TRY TO INIT THE CONNECTION SOCKET PATH AND FortiManagerHandler OBJECT AND TOOLS
    fmgr = None
    if module._socket_path:
        connection = Connection(module._socket_path)
        fmgr = FortiManagerHandler(connection, module)
        fmgr.tools = FMGRCommon()
    else:
        module.fail_json(**FAIL_SOCKET_MSG)

    # BEGIN MODULE-SPECIFIC LOGIC -- THINGS NEED TO HAPPEN DEPENDING ON THE ENDPOINT AND OPERATION
    results = DEFAULT_RESULT_OBJ
    try:
        if paramgram["mode"] == "add":
            # CHECK IF DEVICE EXISTS
            if module.params["blind_add"] == "disable":
                exists_results = get_device(fmgr, paramgram)
                fmgr.govern_response(module=module, results=exists_results, good_codes=(0, -3), changed=False,
                                     ansible_facts=fmgr.construct_ansible_facts(exists_results,
                                                                                module.params, paramgram))

            discover_results = discover_device(fmgr, paramgram)
            fmgr.govern_response(module=module, results=discover_results, stop_on_success=False,
                                 ansible_facts=fmgr.construct_ansible_facts(discover_results,
                                                                            module.params, paramgram))

            if discover_results[0] == 0:
                results = add_device(fmgr, paramgram)
                fmgr.govern_response(module=module, results=discover_results, stop_on_success=True,
                                     changed_if_success=True,
                                     ansible_facts=fmgr.construct_ansible_facts(discover_results,
                                                                                module.params, paramgram))

        if paramgram["mode"] == "delete":
            results = delete_device(fmgr, paramgram)
            fmgr.govern_response(module=module, results=results,
                                 ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram))

    except Exception as err:
        raise FMGBaseException(err)

    return module.exit_json(**results[1])

Module Source Code

#!/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 <http://www.gnu.org/licenses/>.
#

from __future__ import absolute_import, division, print_function

__metaclass__ = type

ANSIBLE_METADATA = {
    "metadata_version": "1.1",
    "status": ["preview"],
    "supported_by": "community"
}

DOCUMENTATION = '''
---
module: fmgr_device
version_added: "2.8"
notes:
    - Full Documentation at U(https://ftnt-ansible-docs.readthedocs.io/en/latest/).
author:
    - Luke Weighall (@lweighall)
    - Andrew Welsh (@Ghilli3)
    - Jim Huber (@p4r4n0y1ng)
short_description: Add or remove device from FortiManager.
description:
  - Add or remove a device or list of devices from FortiManager Device Manager using JSON RPC API.

options:
  adom:
    description:
      - The ADOM the configuration should belong to.
    required: true
    default: root

  mode:
    description:
      - The desired mode of the specified object.
    required: false
    default: add
    choices: ["add", "delete"]

  blind_add:
    description:
      - When adding a device, module will check if it exists, and skip if it does.
      - If enabled, this option will stop the module from checking if it already exists, and blindly add the device.
    required: false
    default: "disable"
    choices: ["enable", "disable"]

  device_username:
    description:
      - The username of the device being added to FortiManager.
    required: false

  device_password:
    description:
      - The password of the device being added to FortiManager.
    required: false

  device_ip:
    description:
      - The IP of the device being added to FortiManager. Supports both IPv4 and IPv6.
    required: false

  device_unique_name:
    description:
      - The desired "friendly" name of the device being added to FortiManager.
    required: false

  device_serial:
    description:
      - The serial number of the device being added to FortiManager.
    required: false
'''

EXAMPLES = '''
- name: DISCOVER AND ADD DEVICE FGT1
  fmgr_device:
    adom: "root"
    device_username: "admin"
    device_password: "admin"
    device_ip: "10.10.24.201"
    device_unique_name: "FGT1"
    device_serial: "FGVM000000117994"
    mode: "add"
    blind_add: "enable"

- name: DISCOVER AND ADD DEVICE FGT2
  fmgr_device:
    adom: "root"
    device_username: "admin"
    device_password: "admin"
    device_ip: "10.10.24.202"
    device_unique_name: "FGT2"
    device_serial: "FGVM000000117992"
    mode: "delete"
'''

RETURN = """
api_result:
  description: full API response, includes status code and message
  returned: always
  type: str
"""

from ansible.module_utils.basic import AnsibleModule
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 discover_device(fmgr, paramgram):
    """
    This method is used to discover devices before adding them to FMGR

    :param fmgr: The fmgr object instance from fmgr_utils.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 = {
        "odd_request_form": "True",
        "device": {"adm_usr": paramgram["device_username"],
                   "adm_pass": paramgram["device_password"],
                   "ip": paramgram["device_ip"]}
    }

    url = '/dvm/cmd/discover/device/'

    response = fmgr.process_request(url, datagram, FMGRMethods.EXEC)
    return response


def add_device(fmgr, paramgram):
    """
    This method is used to add devices to the FMGR

    :param fmgr: The fmgr object instance from fmgr_utils.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 = {
        "adom": paramgram["adom"],
        "flags": ["create_task", "nonblocking"],
        "odd_request_form": "True",
        "device": {"adm_usr": paramgram["device_username"], "adm_pass": paramgram["device_password"],
                   "ip": paramgram["device_ip"], "name": paramgram["device_unique_name"],
                   "sn": paramgram["device_serial"], "mgmt_mode": "fmgfaz", "flags": 24}
    }

    url = '/dvm/cmd/add/device/'
    response = fmgr.process_request(url, datagram, FMGRMethods.EXEC)
    return response


def delete_device(fmgr, paramgram):
    """
    This method deletes a device from the FMGR

    :param fmgr: The fmgr object instance from fmgr_utils.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 = {
        "adom": paramgram["adom"],
        "flags": ["create_task", "nonblocking"],
        "device": paramgram["device_unique_name"],
    }

    url = '/dvm/cmd/del/device/'
    response = fmgr.process_request(url, datagram, FMGRMethods.EXEC)
    return response


def get_device(fmgr, paramgram):
    """
    This method attempts to find the firewall on FortiManager to see if it already exists.

    :param fmgr: The fmgr object instance from fmgr_utils.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 = {
        "adom": paramgram["adom"],
        "filter": ["name", "==", paramgram["device_unique_name"]],
    }

    url = '/dvmdb/adom/{adom}/device/{name}'.format(adom=paramgram["adom"],
                                                    name=paramgram["device_unique_name"])
    response = fmgr.process_request(url, datagram, FMGRMethods.GET)
    return response


def main():
    argument_spec = dict(
        adom=dict(required=False, type="str", default="root"),
        mode=dict(choices=["add", "delete"], type="str", default="add"),
        blind_add=dict(choices=["enable", "disable"], type="str", default="disable"),
        device_ip=dict(required=False, type="str"),
        device_username=dict(required=False, type="str"),
        device_password=dict(required=False, type="str", no_log=True),
        device_unique_name=dict(required=True, type="str"),
        device_serial=dict(required=False, type="str")
    )

    # BUILD MODULE OBJECT SO WE CAN BUILD THE PARAMGRAM
    module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, )

    # BUILD THE PARAMGRAM
    paramgram = {
        "device_ip": module.params["device_ip"],
        "device_username": module.params["device_username"],
        "device_password": module.params["device_password"],
        "device_unique_name": module.params["device_unique_name"],
        "device_serial": module.params["device_serial"],
        "adom": module.params["adom"],
        "mode": module.params["mode"]
    }

    # INSERT THE PARAMGRAM INTO THE MODULE SO WHEN WE PASS IT TO MOD_UTILS.FortiManagerHandler IT HAS THAT INFO
    module.paramgram = paramgram

    # TRY TO INIT THE CONNECTION SOCKET PATH AND FortiManagerHandler OBJECT AND TOOLS
    fmgr = None
    if module._socket_path:
        connection = Connection(module._socket_path)
        fmgr = FortiManagerHandler(connection, module)
        fmgr.tools = FMGRCommon()
    else:
        module.fail_json(**FAIL_SOCKET_MSG)

    # BEGIN MODULE-SPECIFIC LOGIC -- THINGS NEED TO HAPPEN DEPENDING ON THE ENDPOINT AND OPERATION
    results = DEFAULT_RESULT_OBJ
    try:
        if paramgram["mode"] == "add":
            # CHECK IF DEVICE EXISTS
            if module.params["blind_add"] == "disable":
                exists_results = get_device(fmgr, paramgram)
                fmgr.govern_response(module=module, results=exists_results, good_codes=(0, -3), changed=False,
                                     ansible_facts=fmgr.construct_ansible_facts(exists_results,
                                                                                module.params, paramgram))

            discover_results = discover_device(fmgr, paramgram)
            fmgr.govern_response(module=module, results=discover_results, stop_on_success=False,
                                 ansible_facts=fmgr.construct_ansible_facts(discover_results,
                                                                            module.params, paramgram))

            if discover_results[0] == 0:
                results = add_device(fmgr, paramgram)
                fmgr.govern_response(module=module, results=discover_results, stop_on_success=True,
                                     changed_if_success=True,
                                     ansible_facts=fmgr.construct_ansible_facts(discover_results,
                                                                                module.params, paramgram))

        if paramgram["mode"] == "delete":
            results = delete_device(fmgr, paramgram)
            fmgr.govern_response(module=module, results=results,
                                 ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram))

    except Exception as err:
        raise FMGBaseException(err)

    return module.exit_json(**results[1])


if __name__ == "__main__":
    main()