{"action":"created","comment":{"url":"https://api.github.com/repos/networktocode/fortimanager-ansible/pulls/comments/173854673","pull_request_review_id":103112282,"id":173854673,"diff_hunk":"@@ -0,0 +1,1508 @@\n+#!/usr/bin/python\n+#\n+# This file is part of Ansible\n+#\n+# Ansible is free software: you can redistribute it and/or modify\n+# it under the terms of the GNU General Public License as published by\n+# the Free Software Foundation, either version 3 of the License, or\n+# (at your option) any later version.\n+#\n+# Ansible is distributed in the hope that it will be useful,\n+# but WITHOUT ANY WARRANTY; without even the implied warranty of\n+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n+# GNU General Public License for more details.\n+#\n+# You should have received a copy of the GNU General Public License\n+# along with Ansible.  If not, see <http://www.gnu.org/licenses/>.\n+#\n+\n+ANSIBLE_METADATA = {\n+    \"metadata_version\": \"1.0\",\n+    \"status\": [\"preview\"],\n+    \"supported_by\": \"community\"\n+}\n+\n+DOCUMENTATION = '''\n+---\n+module: fortimgr_jsonrpc_request\n+version_added: \"2.3\"\n+short_description: Sends generic json-rpc request\n+description:\n+  - Sends generic FortiManager json-rpc API requests\n+author: JC Sicard (@jcsicard), derived from Jacob McGill (@jmcgill298) work\n+options:\n+  host:\n+    description:\n+      - The FortiManager's Address.\n+    required: true\n+    type: str \n+  password:\n+    description:\n+      - The password associated with the username account.\n+    required: false\n+    type: str\n+  port:\n+    description:\n+      - The TCP port used to connect to the FortiManager if other than the default used by the transport\n+        method(http=80, https=443).\n+    required: false\n+    type: int\n+  provider:\n+    description:\n+      - Dictionary which acts as a collection of arguments used to define the characteristics\n+        of how to connect to the device.\n+      - Arguments hostname, username, and password must be specified in either provider or local param.\n+      - Local params take precedence, e.g. hostname is preferred to provider[\"hostname\"] when both are specified.\n+    required: false\n+    type: dict\n+  session_id:\n+    description:\n+      - The session_id of an established and active session\n+    required: false\n+    type: str\n+  use_ssl:\n+    description:\n+      - Determines whether to use HTTPS(True) or HTTP(False).\n+    required: false\n+    default: True\n+    type: bool\n+  username:\n+    description:\n+      - The username used to authenticate with the FortiManager.\n+    required: false\n+    type: str\n+  validate_certs:\n+    description:\n+      - Determines whether to validate certs against a trusted certificate file (True), or accept all certs (False)\n+    required: false\n+    default: False\n+    type: bool\n+  method:\n+    description:\n+      - The JSON-RPC request method\n+    required: true\n+    type: str\n+    choices: [\"get\", \"add\", \"set\", \"update\", \"delete\", \"move\", \"clone\", \"replace\", \"exec\"]    \n+  params:\n+    description:\n+      - JSON-RPC request parameters (as a JSON list).\n+    required: true\n+    type: list\n+'''\n+\n+EXAMPLES = '''\n+    - name: GET STATUS\n+      fortimgr_jsonrpc_request:\n+        host: \"{{ inventory_hostname }}\"\n+        username: \"{{ username }}\"\n+        password: \"{{ password }}\"\n+        method: get\n+        params: [{url: \"/sys/status/\"}]\n+      register: \n+    \n+    - name: CREATE ADOM\n+      fortimgr_jsonrpc_request:\n+        host: \"{{ inventory_hostname }}\"\n+        username: \"{{ username }}\"\n+        password: \"{{ password }}\"\n+        method: add\n+        params: [{\n+            url: \"/dvmdb/adom\",\n+            data: [{\n+              name: \"lab\",\n+              desc: \"lab adom\"\n+\t        }]\n+          }]\n+'''\n+\n+RETURN = '''\n+response:\n+    description: The json results from ADOM request.\n+    returned: Always\n+    type: list\n+    sample: [{\"result\": [{\"status\": {\"code\": 0, \"message\": \"OK\"}, \"url\": \"/dvmdb/adom\"}]}]\n+status:\n+    description: The json-rpc request's repsonse status code and message\n+    returned: Always\n+    type: dict\n+    sample: {\"code\": 0, \"message\": \"OK\"}  \n+'''\n+\n+import time\n+import requests\n+import json\n+from ansible.module_utils.basic import AnsibleModule, env_fallback, return_values\n+\n+requests.packages.urllib3.disable_warnings()\n+\n+\n+class FortiManager(object):\n+    \"\"\"\n+    This is the Base Class for FortiManager modules. All methods common across several FortiManager Classes should be\n+    defined here and inherited by the sub-class.\n+    \"\"\"\n+\n+    def __init__(self, host, user, passw, use_ssl=True, verify=False, adom=\"\", package=\"\", api_endpoint=\"\", **kwargs):\n+        \"\"\"\n+        :param host: Type str.\n+                     The IP or resolvable hostname of the FortiManager.\n+        :param user: Type str.\n+                     The username used to authenticate with the FortiManager.\n+        :param passw: Type str.\n+                      The password associated with the user account.\n+        :param use_ssl: Type bool.\n+                        The default is True, which uses HTTPS instead of HTTP.\n+        :param verify: Type bool.\n+                       The default is False, which does not verify the certificate against the list of trusted\n+                       certificates.\n+        :param adom: Type str.\n+                     The FortiManager ADOM which the configuration should belong to.\n+        :param package: Type str.\n+                        The FortiManager policy package that should be used.\n+        :param api_endpoint: Type str.\n+                             The API endpoint used for a particular configuration section.\n+        :param kwargs: Type dict. Currently supports port.\n+        :param headers: Type dict.\n+                        The headers to include in HTTP requests.\n+        :param port: Type str.\n+                     Passing the port parameter will override the default HTTP(S) port when making requests.\n+        \"\"\"\n+        self.host = host\n+        self.user = user\n+        self.passw = passw\n+        self.verify = verify\n+        # self.api_endpoint = api_endpoint\n+        # self.adom = adom\n+        # self.package = package\n+        # self.dvmdb_adom_url = \"/dvmdb/adom\"\n+        # self.dvmdb_url = \"/dvmdb/adom/{}/\".format(self._escape_params_url(self.adom))\n+        # self.obj_url = \"/pm/config/adom/{}/obj/firewall/{}\".format(self._escape_params_url(self.adom),\n+        #                                                            self._escape_params_url(self.api_endpoint))\n+        # self.pkg_url = \"/pm/config/adom/{}/pkg/{}/firewall/{}\".format(self.adom, self._escape_params_url(self.package),\n+        #                                                               self._escape_params_url(self.api_endpoint))\n+        # self.wsp_url = \"/dvmdb/adom/{}/workspace/\".format(self._escape_params_url(self.adom))\n+        self.headers = {\"Content-Type\": \"application/json\"}\n+        if \"port\" not in kwargs:\n+            self.port = \"\"\n+        else:\n+            self.port = \":{}\".format(kwargs[\"port\"])\n+\n+        if use_ssl:\n+            self.url = \"https://{fw}{port}/jsonrpc\".format(fw=self.host, port=self.port)\n+        else:\n+            self.url = \"http://{fw}{port}/jsonrpc\".format(fw=self.host, port=self.port)\n+\n+    def add_config(self, new_config):\n+        \"\"\"\n+        This method is used to submit a configuration request to the FortiManager. Only the object configuration details\n+        need to be provided; all other parameters that make up the API request body will be handled by the method.\n+\n+        :param new_config: Type list.\n+                           The \"data\" portion of the configuration to be submitted to the FortiManager.\n+        :return: The response from the API request to add the configuration.\n+        \"\"\"\n+        body = {\"method\": \"add\", \"params\": [{\"url\": self.obj_url, \"data\": new_config, \"session\": self.session}]}\n+        response = self.make_request(body)\n+\n+        return response\n+\n+    @staticmethod\n+    def cidr_to_network(network):\n+        \"\"\"\n+        Method is used to convert a network address in CIDR notation to a list with address and mask.\n+\n+        :param network: Type str.\n+                        The network address in CIDR notation.\n+\n+        :return: A list with address and mask in that order.\n+        \"\"\"\n+        cidr_mapping = {\n+            \"0\": \"0.0.0.0\",\n+            \"1\": \"128.0.0.0\",\n+            \"2\": \"192.0.0.0\",\n+            \"3\": \"224.0.0.0\",\n+            \"4\": \"240.0.0.0\",\n+            \"5\": \"248.0.0.0\",\n+            \"6\": \"252.0.0.0\",\n+            \"7\": \"254.0.0.0\",\n+            \"8\": \"255.0.0.0\",\n+            \"9\": \"255.128.0.0\",\n+            \"10\": \"255.192.0.0\",\n+            \"11\": \"255.224.0.0\",\n+            \"12\": \"255.240.0.0\",\n+            \"13\": \"255.248.0.0\",\n+            \"14\": \"255.252.0.0\",\n+            \"15\": \"255.254.0.0\",\n+            \"16\": \"255.255.0.0\",\n+            \"17\": \"255.255.128.0\",\n+            \"18\": \"255.255.192.0\",\n+            \"19\": \"255.255.224.0\",\n+            \"20\": \"255.255.240.0\",\n+            \"21\": \"255.255.248.0\",\n+            \"22\": \"255.255.252.0\",\n+            \"23\": \"255.255.254.0\",\n+            \"24\": \"255.255.255.0\",\n+            \"25\": \"255.255.255.128\",\n+            \"26\": \"255.255.255.192\",\n+            \"27\": \"255.255.255.224\",\n+            \"28\": \"255.255.255.240\",\n+            \"29\": \"255.255.255.248\",\n+            \"30\": \"255.255.255.252\",\n+            \"31\": \"255.255.255.254\",\n+            \"32\": \"255.255.255.255\"\n+        }\n+\n+        if \"/\" in network:\n+            network_address = network.split(\"/\")\n+            mask = network_address.pop()\n+\n+            if mask and int(mask) in range(0, 33):\n+                network_address.append(cidr_mapping[mask])\n+            else:\n+                network_address = []\n+        else:\n+            network_address = []\n+\n+        return network_address\n+\n+    @staticmethod\n+    def cidr_to_wildcard(wildcard):\n+        \"\"\"\n+        Method is used to convert a wildcard address in CIDR notation to a list with address and mask.\n+\n+        :param wildcard: Type str.\n+                        The wildcard address in CIDR notation.\n+\n+        :return: A list with address and mask in that order.\n+        \"\"\"\n+        cidr_mapping = {\n+            \"0\": \"255.255.255.255\",\n+            \"1\": \"127.255.255.255\",\n+            \"2\": \"63.255.255.255\",\n+            \"3\": \"31.255.255.255\",\n+            \"4\": \"15.255.255.255\",\n+            \"5\": \"7.255.255.255\",\n+            \"6\": \"3.255.255.255\",\n+            \"7\": \"1.255.255.255\",\n+            \"8\": \"0.255.255.255\",\n+            \"9\": \"0.127.255.255\",\n+            \"10\": \"0.63.255.255\",\n+            \"11\": \"0.31.255.255\",\n+            \"12\": \"0.15.255.255\",\n+            \"13\": \"0.7.255.255\",\n+            \"14\": \"0.3.255.255\",\n+            \"15\": \"0.1.255.255\",\n+            \"16\": \"0.0.255.255\",\n+            \"17\": \"0.0.127.255\",\n+            \"18\": \"0.0.63.255\",\n+            \"19\": \"0.0.31.255\",\n+            \"20\": \"0.0.15.255\",\n+            \"21\": \"0.0.7.255\",\n+            \"22\": \"0.0.3.255\",\n+            \"23\": \"0.0.1.255\",\n+            \"24\": \"0.0.0.255\",\n+            \"25\": \"0.0.0.127\",\n+            \"26\": \"0.0.0.63\",\n+            \"27\": \"0.0.0.31\",\n+            \"28\": \"0.0.0.15\",\n+            \"29\": \"0.0.0.7\",\n+            \"30\": \"0.0.0.3\",\n+            \"31\": \"0.0.0.1\",\n+            \"32\": \"0.0.0.0\"\n+        }\n+\n+        if \"/\" in wildcard:\n+            wildcard_address = wildcard.split(\"/\")\n+            mask = wildcard_address.pop()\n+\n+            if mask and int(mask) in range(0, 33):\n+                wildcard_address.append(cidr_mapping[mask])\n+            else:\n+                wildcard_address = []\n+        else:\n+            wildcard_address = []\n+\n+        return wildcard_address\n+\n+    def config_absent(self, module, proposed, existing):\n+        \"\"\"\n+        This function is used to determine the appropriate configuration to remove from the FortiManager when the\n+        \"state\" parameter is set to \"absent\" and to collect the dictionary data that will be returned by the Ansible\n+        Module.\n+\n+        :param module: The AnsibleModule instance.\n+        :param proposed: The proposed config to send to the FortiManager.\n+        :param existing: The existing configuration for the item on the FortiManager (using the \"name\" key to get item).\n+        :return: A dictionary containing the module exit values.\n+        \"\"\"\n+        changed = False\n+        config = {}\n+\n+        if existing:\n+            # check if proposed is to remove a dynamic_mapping\n+            if \"dynamic_mapping\" not in proposed:\n+                config = self.config_delete(module, proposed[\"name\"])\n+                changed = True\n+            else:\n+                diff = self.get_diff_mappings(proposed, existing)\n+                if diff:\n+                    config = self.config_update(module, diff)\n+                    changed = True\n+\n+        return {\"changed\": changed, \"config\": config, \"existing\": existing}\n+\n+    def config_delete(self, module, name):\n+        \"\"\"\n+        This method is used to handle the logic for Ansible modules when the \"state\" is set to \"absent\" and only the\n+        name is provided as input into the Ansible Module. The config_lock is used to lock the configuration if the lock\n+        param is set to True. The config_response method is used to handle the logic from the response to delete the\n+        object.\n+\n+        :param module: The Ansible Module instance started by the task.\n+        :param name: Type str.\n+                     The name of the object to be removed from the FortiManager.\n+        :return: A dictionary that corresponds to the configuration that was sent in the request body to the\n+                 FortiManager API. This dict will map to the \"config\" key returned by the Ansible Module.\n+        \"\"\"\n+        # lock config if set and module not in check mode\n+        if module.params[\"lock\"] and not module.check_mode:\n+            self.config_lock(module)\n+\n+        # configure if not in check mode\n+        if not module.check_mode:\n+            response = self.delete_config(name)\n+            self.config_response(module, response.json(), module.params[\"lock\"])\n+\n+        return {\"method\": \"delete\", \"params\": [{\"url\": self.obj_url + \"/{}\".format(name.replace('/','\\\\/'))}]}\n+\n+    def config_lock(self, module, msg=\"Unable to Lock the Configuration; Validate the ADOM is not Currently Locked.\"):\n+        \"\"\"\n+        This method is used to handle the logic for Ansible modules for locking the ADOM when \"lock\" is set to True. The\n+        lock method is used to make the request to the FortiManager.\n+\n+        :param module: The Ansible Module instance started by the task.\n+        :param msg: Type str.\n+                    A message for the module to return upon failure.\n+        :return: True if lock successful.\n+        \"\"\"\n+        lock_status = self.lock()\n+        if lock_status[\"result\"][0][\"status\"][\"code\"] != 0:\n+            # try to logout before failing\n+            self.logout()\n+            module.fail_json(msg=msg, locked=False, saved=False, unlocked=False, fortimanager_response=lock_status)\n+\n+        return True\n+\n+    def config_new(self, module, new_config):\n+        \"\"\"\n+        This method is used to handle the logic for Ansible modules when the \"state\" is set to \"present\" and their is\n+        not currently an object of the same type with the same name. The config_lock is used to lock the configuration\n+        if the lock param is set to True. The config_response method is used to handle the logic from the response to\n+        create the object.\n+\n+        :param module: The Ansible Module instance started by the task.\n+        :param new_config: Type dict.\n+                           The config dictionary with the objects configuration to send to the FortiManager API. This\n+                           corresponds to the \"data\" portion of the request body.\n+        :return: A dictionary that corresponds to the configuration that was sent in the request body to the\n+                 FortiManager API. This dict will map to the \"config\" key returned by the Ansible Module.\n+        \"\"\"\n+        # lock config if set and module not in check mode\n+        if module.params[\"lock\"] and not module.check_mode:\n+            self.config_lock(module)\n+\n+        # configure if not in check mode\n+        if not module.check_mode:\n+            response = self.add_config(new_config)\n+            self.config_response(module, response.json(), module.params[\"lock\"])\n+\n+        return {\"method\": \"add\", \"params\": [{\"url\": self.obj_url, \"data\": new_config}]}\n+\n+    def config_param_absent(self, module, proposed, existing):\n+        \"\"\"\n+        This function is used to determine the appropriate configuration to remove from the FortiManager when the\n+        \"state\" parameter is set to \"param_absent\" and to collect the dictionary data that will be returned by the\n+        Ansible Module.\n+\n+        :param module: The AnsibleModule instance.\n+        :param proposed: The proposed config to send to the FortiManager.\n+        :param existing: The existing configuration for the item on the FortiManager (using the \"name\" key to get item).\n+        :return: A dictionary containing the module exit values.\n+        \"\"\"\n+        changed = False\n+        config = {}\n+\n+        if existing:\n+            # determine what diff method to call\n+            if \"dynamic_mapping\" not in proposed:\n+                diff = self.get_diff_remove(proposed, existing)\n+            else:\n+                diff = self.get_diff_remove_map(proposed, existing)\n+\n+            if diff:\n+                config = self.config_update(module, diff)\n+                changed = True\n+\n+        return {\"changed\": changed, \"config\": config, \"existing\": existing}\n+\n+    def config_present(self, module, proposed, existing):\n+        \"\"\"\n+        This function is used to determine the appropriate configuration to send to the FortiManager API when the\n+        \"state\" parameter is set to \"present\" and to collect the dictionary data that will be returned by the Ansible\n+        Module.\n+\n+        :param module: The AnsibleModule instance.\n+        :param proposed: The proposed config to send to the FortiManager.\n+        :param existing: The existing configuration for the item on the FortiManager (using the \"name\" key to get item).\n+        :return: A dictionary containing the module exit values.\n+        \"\"\"\n+        changed = False\n+        config = {}\n+\n+        if not existing:\n+            config = self.config_new(module, proposed)\n+            changed = True\n+        else:\n+            # determine what diff method to call\n+            if \"dynamic_mapping\" not in proposed:\n+                diff = self.get_diff_add(proposed, existing)\n+            else:\n+                diff = self.get_diff_add_map(proposed, existing)\n+\n+            if diff:\n+                config = self.config_update(module, diff)\n+                changed = True\n+\n+        return {\"changed\": changed, \"config\": config, \"existing\": existing}\n+\n+    def config_response(self, module, json_response, lock):\n+        \"\"\"\n+        This method is to handle the logic for Ansible modules for handling the config request's response. If the lock\n+        parameter is set to true and the config was successful, the config_save and config_unlock methods are used to\n+        save the configuration and unlock the ADOM session. If the lock parameter is set to true and the config was\n+        unsuccessful, the config_unlock method is used to attempt to unlock the ADOM session before failing. If the lock\n+        parameter is set to False and the configuration is unsuccessful, the module will fail with the json response.\n+\n+        :param module: The Ansible Module instance started by the task.\n+        :param json_response: Type dict.\n+                              The json response from the requests module's configuration request.\n+        :param lock: Type bool.\n+                     The setting of the configuration lock. True means locking mechanism is in place.\n+        :return: True if configuration was saved and the adom unlocked.\n+        \"\"\"\n+        # save if config successful and session locked\n+        status_code = json_response[\"result\"][0][\"status\"][\"code\"]\n+        if status_code == 0 and lock:\n+            self.config_save(module)\n+            self.config_unlock(module)\n+        # attempt to unlock if config unsuccessful\n+        elif status_code != 0 and lock:\n+            self.config_unlock(module, msg=json_response, saved=False)\n+            module.fail_json(msg=\"Unable to Apply Config\", locked=True, saved=False, unlocked=True,\n+                             fortimanager_response=json_response)\n+        # fail if not using lock mode and config unsuccessful\n+        elif status_code != 0:\n+            module.fail_json(msg=\"Unable to Apply Config\", fortimanager_response=json_response)\n+\n+    def config_save(self, module, msg=\"Unable to Save Config, Successfully Unlocked\"):\n+        \"\"\"\n+        This method is used to handle the logic for Ansible modules for saving a config when \"lock\" is set to True. The\n+        save method is used to make the request to the FortiManager. If the save is unsuccessful, the module will use\n+        the config_unlock method to attempt to unlock before failing.\n+\n+        :param module: The Ansible Module instance started by the task.\n+        :param msg: Type str.\n+                    A message for the module to return upon failure.\n+        :return: True if the configuration was saved successfully.\n+        \"\"\"\n+        save_status = self.save()\n+        if save_status[\"result\"][0][\"status\"][\"code\"] != 0:\n+            self.config_unlock(module, \"Config Updated, but Unable to Save or Unlock\", False)\n+            # try to logout before failing\n+            self.logout()\n+            module.fail_json(msg=msg, locked=True, saved=False, unlocked=True, fortimanager_response=save_status)\n+\n+        return True\n+\n+    def config_unlock(self, module, msg=\"Config Saved, but Unable to Unlock\", saved=True):\n+        \"\"\"\n+        This method is used to handle the logic for Ansible modules for locking the ADOM when \"lock\" is set to True. The\n+        config_lock is used to lock the configuration if the lock param is set to True. The unlock method is used to\n+        make the request to the FortiManager.\n+\n+        :param module: The Ansible Module instance started by the task.\n+        :param msg: Type str.\n+                    A message for the module to return upon failure.\n+        :param saved: Type bool.\n+                      The save status of the configuration.\n+        :return: True if unlock successful.\n+        \"\"\"\n+        unlock_status = self.unlock()\n+        if unlock_status[\"result\"][0][\"status\"][\"code\"] != 0:\n+            # try to logout before failing\n+            self.logout()\n+            module.fail_json(msg=msg, locked=True, saved=saved, unlocked=False, fortimanager_response=unlock_status)\n+\n+        return True\n+\n+    def config_update(self, module, update_config):\n+        \"\"\"\n+        This method is used to handle the logic for Ansible modules when the \"state\" is set to \"present\" and their is\n+        not currently an object of the same type with the same name. The config_response method is used to handle the\n+        logic from the response to update the object.\n+\n+        :param module: The Ansible Module instance started by the task.\n+        :param update_config: Type dict.\n+                              The config dictionary with the objects configuration to send to the FortiManager API. Only\n+                              the keys that have updates need to be included. This corresponds to the \"data\" portion of\n+                              the request body.\n+        :return: A dictionary that corresponds to the configuration that was sent in the request body to the\n+                 FortiManager API. This dict will map to the \"config\" key returned by the Ansible Module.\n+        \"\"\"\n+        # lock config if set and module not in check mode\n+        if module.params[\"lock\"] and not module.check_mode:\n+            self.config_lock(module)\n+\n+        # configure if not in check mode\n+        if not module.check_mode:\n+            response = self.update_config(update_config)\n+            self.config_response(module, response.json(), module.params[\"lock\"])\n+\n+        return {\"method\": \"update\", \"params\": [{\"url\": self.obj_url, \"data\": update_config}]}\n+\n+    def create_revision(self, proposed):\n+        \"\"\"\n+        This method is used to create an ADOM revision on the FortiManager. The make_request method is used to make the\n+        API request to add the revision.\n+\n+        :param proposed: Type list.\n+                         The data portion of the API Request.\n+        :return: The json response data from the request to make a revision.\n+        \"\"\"\n+        rev_url = \"{}revision\".format(self.dvmdb_url)\n+        body = {\"method\": \"add\", \"params\": [{\"url\": rev_url, \"data\": proposed}], \"session\": self.session}\n+        response = self.make_request(body).json()\n+\n+        return response\n+\n+    def create_adom(self, proposed):\n+        \"\"\"\n+        This method is used to create an ADOM on the FortiManager. The make_request method is used to make the\n+        API request to add the ADOM.\n+\n+        :param proposed: Type list.\n+                         The data portion of the API Request.\n+        :return: The json response data from the request to make a revision.\n+        \"\"\"\n+        body = {\"method\": \"add\", \"params\": [{\"url\": self.dvmdb_adom_url, \"data\": proposed}], \"session\": self.session}\n+        response = self.make_request(body).json()\n+\n+        return response\n+\n+    def delete_config(self, name):\n+        \"\"\"\n+        This method is used to submit a configuration request to delete an object from the FortiManager.\n+\n+        :param name: Type str.\n+                     The name of the object to be removed from the FortiManager.\n+        :return: The response from the API request to delete the configuration.\n+        \"\"\"\n+        item_url = self.obj_url + \"/{}\".format(name.replace('/','\\\\/'))\n+        body = {\"method\": \"delete\", \"params\": [{\"url\": item_url}], \"session\": self.session}\n+        response = self.make_request(body)\n+\n+        return response\n+\n+    def delete_adom(self, name):\n+        \"\"\"\n+        This method is used to delete an ADOM from the FortiManager. The make_request method is used to submit\n+        the request to the FortiManager.\n+\n+        :param name: Type str.\n+                        The name of the ADOM to delete.\n+        :return: The json response data from the request to delete the ADOM.\n+        \"\"\"\n+        adom_url = \"{}/{}\".format(self.dvmdb_adom_url, name)\n+        body = {\"method\": \"delete\", \"params\": [{\"url\": adom_url}], \"session\": self.session}\n+        response = self.make_request(body).json()\n+\n+        return response\n+\n+    def delete_revision(self, version):\n+        \"\"\"\n+        This method is used to delete an ADOM revision from the FortiManager. The make_request method is used to submit\n+        the request to the FortiManager.\n+\n+        :param version: Type str.\n+                        The version number corresponding to the revision to delete.\n+        :return: The json response data from the request to delete the revision.\n+        \"\"\"\n+        rev_url = \"{}revision/{}\".format(self.dvmdb_url, version)\n+        body = {\"method\": \"delete\", \"params\": [{\"url\": rev_url}], \"session\": self.session}\n+        response = self.make_request(body).json()\n+\n+        return response\n+\n+    def get_adom_fields(self, adom, fields=[]):\n+        \"\"\"\n+        This method is used to get all adoms currently configured on the FortiManager. A list of fields can be passed\n+        in to limit the scope of what data is returned for the ADOM.\n+\n+        :param adom: Type str.\n+                     The name of the ADOM to retrieve the configuration for.\n+        :param fields: Type list.\n+                       A list of fields to retrieve for the ADOM.\n+        :return: The json response from the request to retrieve the configured ADOM. An empty list is returned if the\n+                 request does not return any data.\n+        \"\"\"\n+        body = dict(method=\"get\", params=[dict(url=\"/dvmdb/adom\", filter=[\"name\", \"==\", adom], fields=fields)],\n+                    verbose=1, session=self.session)\n+        response = self.make_request(body)\n+\n+        return response.json()[\"result\"][0].get(\"data\", [])\n+\n+    def get_adoms_fields(self, fields=[]):\n+        \"\"\"\n+        This method is used to get all adoms currently configured on the FortiManager. A list of fields can be passed\n+        in to limit the scope of what data is returned per ADOM.\n+\n+        :param fields: Type list.\n+                       A list of fields to retrieve for each ADOM.\n+        :return: The json response from the request to retrieve the configured ADOMs. An empty list is returned if the\n+                 request does not return any data.\n+        \"\"\"\n+        body = dict(method=\"get\", params=[dict(url=\"/dvmdb/adom\", fields=fields)], verbose=1, session=self.session)\n+        response = self.make_request(body)\n+\n+        return response.json()[\"result\"][0].get(\"data\", [])\n+\n+    def get_all(self):\n+        \"\"\"\n+        This method is used to get all objects currently configured on the FortiManager for the ADOM and API Endpoint.\n+\n+        :return: The list of configuration dictionaries for each object. An empty list is returned if the request does\n+                 not return any data.\n+        \"\"\"\n+        body = {\"method\": \"get\", \"params\": [{\"url\": self.obj_url}], \"verbose\": 1, \"session\": self.session}\n+        response = self.make_request(body)\n+\n+        return response.json()[\"result\"][0].get(\"data\", [])\n+\n+    def get_all_custom(self, url):\n+        \"\"\"\n+        This method is used to get all objects currently configured for the specified URL.\n+\n+        :param url: Type str.\n+                    The URL of the endpoint to retrieve configurations from.\n+        :return: The list of configuration dictionaries for each object. An empty list is returned if the request does\n+                 not return any data.\n+        \"\"\"\n+        body = dict(method=\"get\", params=[{\"url\": url}], verbose=1, session=self.session)\n+        response = self.make_request(body)\n+\n+        return response.json()[\"result\"][0].get(\"data\", [])\n+\n+    def get_all_fields(self, fields):\n+        \"\"\"\n+        This method is used to get all objects currently configured on the FortiManager for the ADOM and API Endpoint.\n+        The configuration fields retrieved are limited to the list defined in the fields variable.\n+\n+        :param fields: Type list.\n+                       The list of fields to return for each object.\n+        :return: The list of configuration dictionaries for each object. An empty list is returned if the request does\n+                 not return any data.\n+        \"\"\"\n+        params = [{\"url\": self.obj_url, \"fields\": fields}]\n+        body = {\"method\": \"get\", \"params\": params, \"verbose\": 1, \"session\": self.session}\n+        response = self.make_request(body)\n+\n+        return response.json()[\"result\"][0].get(\"data\", [])\n+\n+    def get_all_packages(self, adom):\n+        \"\"\"\n+        This method is used to get all packages associated with an ADOM.\n+\n+        :param adom: Type str.\n+                     The ADOM from which to retrieve packages.\n+        :return: A list of package names associated with the ADOM. If the ADOM is not found or does not have any\n+                 packages, then an empty list is returned.\n+        \"\"\"\n+        body = dict(method=\"get\", params=[{\"url\": \"/pm/pkg/adom/{}\".format(adom)}], verbose=1, session=self.session)\n+        response = self.make_request(body)\n+        package_dicts = response.json().get(\"result\", [{}])[0].get(\"data\", [])\n+\n+        packages = []\n+        for pkg in package_dicts:\n+            if pkg.get(\"name\"):\n+                packages.append(pkg[\"name\"])\n+\n+        return packages\n+\n+    def get_device_config(self, device, vdom, config_url, fields=[]):\n+        \"\"\"\n+        This method is used to retrieve the configurations from the managed device.\n+\n+        :param device: Type str.\n+                       The device to retrieve the configuration from.\n+        :param vdom: Type str.\n+                     The vdom to retrieve the configuration from.\n+        :param config_url: Type str.\n+                           The url associated with the configuration section to retrieve.\n+        :param fields: Type list.\n+                       A list of configuration fields to retrieve from the device.\n+        :return: The json response from the request to retrieve the static routes. An empty list is returned if the\n+                 request does not return any data.\n+        \"\"\"\n+        config_url = \"/pm/config/device/{}/vdom/{}/{}\".format(device, vdom, config_url)\n+        body = dict(method=\"get\", params=[dict(url=config_url, fields=fields)], verbose=1, session=self.session)\n+        response = self.make_request(body).json()[\"result\"][0].get(\"data\", [])\n+\n+        if not response:\n+            response = []\n+\n+        return response\n+\n+    def get_device_fields(self, device, fields=[]):\n+        \"\"\"\n+        This method is used to retrieve information about a managed device from FortiManager. A list of fields can be\n+        passed int o limit the scope of what data is returned for the device.\n+\n+        :param device: Type str.\n+                       The name of the device to retrieve information for.\n+        :param fields: Type list.\n+                       A list of fields to retrieve for the device.\n+        :return: The json response from the request to retrieve the configured device. An empty list is returned if the\n+                 request does not return any data.\n+        \"\"\"\n+        body = dict(method=\"get\", params=[dict(url=\"/dvmdb/device\", filter=[\"name\", \"==\", device], fields=fields)],\n+                    verbose=1, session=self.session)\n+        response = self.make_request(body)\n+\n+        return response.json()[\"result\"][0].get(\"data\", [])\n+\n+    def get_device_ha(self, device):\n+        \"\"\"\n+        This method is used to get HA information for a device managed by FortiManager.\n+\n+        :param device: The device to retrieve the HA status from.\n+        :return: The json response from the request to retrieve the HA status. An empty list is returned if the request\n+                 does not return any data.\n+        \"\"\"\n+        if not self.adom:\n+            dev_url = \"/dvmdb/device/{}/ha_slave\".format(self.adom, device)\n+        else:\n+            dev_url = \"{}device/{}/ha_slave\".format(self.dvmdb_url, device)\n+        body = dict(method=\"get\", params=[dict(url=dev_url)], verbose=1, session=self.session)\n+        response = self.make_request(body)\n+\n+        return response.json()[\"result\"][0].get(\"data\", [])\n+\n+    def get_device_vdoms(self, device):\n+        \"\"\"\n+        This method is used to retrieve the VDOMs associated with a device managed by FortiManager.\n+\n+        :param device: The device to retrieve the HA status from.\n+        :return: The json response from the request to retrieve the HA status. An empty list is returned if the request\n+                 does not return any data.\n+        \"\"\"\n+        if not self.adom:\n+            dev_url = \"/dvmdb/device/{}/vdom\".format(device)\n+        else:\n+            dev_url = \"{}device/{}/vdom\".format(self.dvmdb_url, device)\n+        body = dict(method=\"get\", params=[dict(url=dev_url)], verbose=1, session=self.session)\n+        response = self.make_request(body)\n+\n+        return response.json()[\"result\"][0].get(\"data\", [])\n+\n+    def get_devices_fields(self, fields=[], dev_filter=[]):\n+        \"\"\"\n+        This method is used to retrieve information about a managed devices from FortiManager. A list of fields can be\n+        passed int o limit the scope of what data is returned for each the device.\n+\n+        :param fields: Type list.\n+                       A list of fields to retrieve for the device.\n+        :param dev_filter: Type list.\n+                       A list matching to a filter parameter for API requests [<key>, <operator>, <value>].\n+        :return: The json response from the request to retrieve the configured devices. An empty list is returned if the\n+                 request does not return any data.\n+        \"\"\"\n+        if not self.adom:\n+            dev_url = \"/dvmdb/device\"\n+        else:\n+            dev_url = \"{}device\".format(self.dvmdb_url)\n+\n+        body = dict(method=\"get\", params=[dict(url=dev_url, fields=fields, filter=dev_filter)], verbose=1,\n+                    session=self.session)\n+        response = self.make_request(body)\n+\n+        return response.json()[\"result\"][0].get(\"data\", [])\n+\n+    @staticmethod\n+    def get_diff_add(proposed, existing):\n+        \"\"\"\n+        This method is used to get the difference between two configurations when the \"proposed\" configuration is a dict\n+        of configuration items that should exist in the configuration for the object in the FortiManager. Either the\n+        get_item or get_item_fields methods should be used to obtain the \"existing\" variable; if either of those methods\n+        return an empty dict, then you should use the add_config method to add the new object.\n+\n+        :param proposed: Type dict.\n+                         The configuration that should not exist for the object on the FortiManager.\n+        :param existing: Type dict.\n+                         The current configuration for the object that potentially needs configuration removed.\n+        :return: A dict corresponding to the \"data\" portion of an \"update\" request. This can be used to call the\n+                 update_config method.\n+        \"\"\"\n+        config = {}\n+        for field in proposed.keys():\n+            proposed_field = proposed[field]\n+            existing_field = existing.get(field)\n+            if existing_field and proposed_field != existing_field:\n+                if isinstance(existing_field, list) and not set(proposed_field).issubset(existing_field):\n+                    config[field] = list(set(proposed_field).union(existing_field))\n+                elif isinstance(existing_field, dict):\n+                    config[field] = dict(set(proposed_field.items()).union(existing_field.items()))\n+                elif isinstance(existing_field, str) or isinstance(existing_field, unicode):\n+                    config[field] = proposed_field\n+            elif field not in existing:\n+                config[field] = proposed_field\n+\n+        if config:\n+            config[\"name\"] = proposed[\"name\"]\n+\n+        return config\n+\n+    @staticmethod\n+    def get_diff_add_map(proposed, existing):\n+        \"\"\"\n+        This method is used to get the difference between two dynamic_mapping configurations when the \"proposed\"\n+        configuration is a dict of configuration items that should exist in the configuration for the object in the\n+        FortiManager. Either the get_item or get_item_fields method should be used to obtain the \"existing\" variable; if\n+        either of those methods return an empty dict, then you should use the add_config method to add the new object.\n+\n+        :param proposed: Type dict.\n+                         The configuration that should exist for the object on the FortiManager.\n+        :param existing: Type dict.\n+                         The current configuration for the object that potentially needs its configuration modified.\n+        :return: A dict corresponding to the \"data\" portion of an \"update\" request. This can be used to call the\n+                 update_config method.\n+        \"\"\"\n+        name = proposed.get(\"name\")\n+        proposed_map = proposed.get(\"dynamic_mapping\")[0]\n+        proposed_scope = proposed_map.pop(\"_scope\")[0]\n+        existing_map = existing.get(\"dynamic_mapping\")\n+        config = dict(name=name, dynamic_mapping=[])\n+        present = False\n+\n+        # check if proposed mapping already exists and make necessary updates to config\n+        if existing_map:\n+            for mapping in existing_map:\n+                if proposed_scope in mapping[\"_scope\"]:\n+                    present = True\n+                    updated_map = {}\n+                    for field in proposed_map.keys():\n+                        proposed_field = proposed_map[field]\n+                        existing_field = mapping.get(field)\n+                        # only consider relevant fields that have a difference\n+                        if existing_field and proposed_field != existing_field:\n+                            if isinstance(existing_field, list) and not set(proposed_field).issubset(existing_field):\n+                                updated_map[field] = list(set(proposed_field).union(existing_field))\n+                            elif isinstance(existing_field, dict):\n+                                updated_map[field] = dict(set(proposed_field.items()).union(existing_field.items()))\n+                            elif isinstance(existing_field, str) or isinstance(existing_field, unicode):\n+                                updated_map[field] = proposed_field\n+                        elif field not in mapping:\n+                            updated_map[field] = proposed_field\n+                    # config update if dynamic_mapping dict has any keys, need to append _scope key\n+                    if updated_map:\n+                        # add scope to updated_map and append the config to the list of other mappings\n+                        updated_map[\"_scope\"] = mapping[\"_scope\"]\n+                        config[\"dynamic_mapping\"].append(updated_map)\n+                    else:\n+                        # set config to a null dictionary if dynamic mappings are identical and exit loop\n+                        config = {}\n+                        break\n+                else:\n+                    # keep unrelated mapping in diff so that diff can be used to update FortiManager\n+                    config[\"dynamic_mapping\"].append(dict(_scope=mapping[\"_scope\"]))\n+\n+        # add mapping to config if it does not currently exist\n+        if not present:\n+            config = proposed\n+            config[\"dynamic_mapping\"][0][\"_scope\"] = [proposed_scope]\n+            if existing_map:\n+                for mapping in existing_map:\n+                    config[\"dynamic_mapping\"].append(dict(_scope=mapping[\"_scope\"]))\n+\n+        return config\n+\n+    @staticmethod\n+    def get_diff_mappings(proposed, existing):\n+        \"\"\"\n+        This method is to get the diff of just the mapped Fortigate devices.\n+        :param proposed: Type dict.\n+                         The configuration that should not exist for the object on the FortiManager.\n+        :param existing: Type dict.\n+                         The current configuration for the object that potentially needs configuration removed.\n+        :return: A dict corresponding to the \"data\" portion of an \"update\" request. This can be used to call the\n+                 update_config method.\n+        \"\"\"\n+        config = dict(name=proposed[\"name\"], dynamic_mapping=[])\n+        existing_map = existing.get(\"dynamic_mapping\")\n+        if existing_map:\n+            for mapping in existing_map:\n+                if mapping[\"_scope\"] != proposed[\"dynamic_mapping\"][0][\"_scope\"]:\n+                    config[\"dynamic_mapping\"].append(dict(_scope=mapping[\"_scope\"]))\n+\n+            if len(config[\"dynamic_mapping\"]) == len(existing_map):\n+                config = {}\n+        else:\n+            config = {}\n+\n+        return config\n+\n+    @staticmethod\n+    def get_diff_remove(proposed, existing):\n+        \"\"\"\n+        This method is used to get the difference between two configurations when the \"proposed\" configuration is a dict\n+        of configuration items that should not exist in the configuration for the object in the FortiManager. Either the\n+        get_item or get_item_fields methods should be used to obtain the \"existing\" variable; if either of those methods\n+        return an empty dict, then the object does not exist and there is no configuration to remove.\n+\n+        :param proposed: Type dict.\n+                         The configuration that should not exist for the object on the FortiManager.\n+        :param existing: Type dict.\n+                         The current configuration for the object that potentially needs configuration removed.\n+        :return: A dict corresponding to the \"data\" portion of an \"update\" request. This can be used to call the\n+                 update_config method.\n+        \"\"\"\n+        config = {}\n+        for field in proposed.keys():\n+            proposed_field = proposed[field]\n+            existing_field = existing.get(field)\n+            if existing_field and isinstance(existing_field, list):\n+                diff = list(set(existing_field).difference(proposed_field))\n+                if diff != existing_field:\n+                    config[field] = diff\n+            elif existing_field and isinstance(existing_field, dict):\n+                diff = dict(set(proposed.items()).difference(existing.items()))\n+                if diff != existing_field:\n+                    config[field] = diff\n+\n+        if config:\n+            config[\"name\"] = proposed[\"name\"]\n+\n+        return config\n+\n+    @staticmethod\n+    def get_diff_remove_map(proposed, existing):\n+        \"\"\"\n+        This method is used to get the difference between two dynamic_mapping configurations when the \"proposed\"\n+        configuration is a dict of configuration items that should not exist in the configuration for the object in the\n+        FortiManager. Either the get_item or get_item_fields method should be used to obtain the \"existing\" variable; if\n+        either of those methods return an empty dict, then the object does not exist and there is no configuration to\n+        remove.\n+\n+        :param proposed: Type dict.\n+                         The configuration that should not exist for the object on the FortiManager.\n+        :param existing: Type dict.\n+                         The current configuration for the object that potentially needs configuration removed.\n+        :return: A dict corresponding to the \"data\" portion of an \"update\" request. This can be used to call the\n+                 update_config method.\n+        \"\"\"\n+        name = proposed.get(\"name\")\n+        proposed_map = proposed.get(\"dynamic_mapping\")[0]\n+        proposed_scope = proposed_map.pop(\"_scope\")[0]\n+        existing_map = existing.get(\"dynamic_mapping\")\n+        config = dict(name=name, dynamic_mapping=[])\n+        present = False\n+\n+        # check if proposed mapping already exists and make necessary updates to config\n+        if existing_map:\n+            for mapping in existing_map:\n+                if proposed_scope in mapping[\"_scope\"]:\n+                    present = True\n+                    updated_map = {}\n+                    for field in proposed_map.keys():\n+                        proposed_field = proposed_map[field]\n+                        existing_field = mapping.get(field)\n+                        if existing_field and isinstance(existing_field, list):\n+                            diff = list(set(existing_field).difference(proposed_field))\n+                            if diff != existing_field:\n+                                updated_map[field] = diff\n+                        elif existing_field and isinstance(existing_field, dict):\n+                            diff = dict(set(proposed_map.items()).difference(mapping.items()))\n+                            if diff != existing_field:\n+                                updated_map[field] = diff\n+                    # config update if dynamic_mapping dict has any keys, need to append _scope key\n+                    if updated_map:\n+                        # add scope to updated_map and append the config to the list of other mappings\n+                        updated_map[\"_scope\"] = mapping[\"_scope\"]\n+                        config[\"dynamic_mapping\"].append(updated_map)\n+                    else:\n+                        # remove dynamic mapping from proposed if proposed matches existing config\n+                        config = {}\n+                        break\n+                else:\n+                    # keep unrelated mapping in diff so that diff can be used to update FortiManager\n+                    config[\"dynamic_mapping\"].append(dict(_scope=mapping[\"_scope\"]))\n+\n+        # set config to empty dict if mapping was not found, representing no change\n+        if not present:\n+            config = {}\n+\n+        return config\n+\n+    def get_ha(self):\n+        \"\"\"\n+        This method is used to retrieve the HA status of the FortiManager.\n+\n+        :return: The json response data from the request to retrieve the HA status.\n+        \"\"\"\n+        body = dict(method=\"get\", params=[dict(url=\"/cli/global/system/ha\")], verbose=1, session=self.session)\n+        response = self.make_request(body).json()[\"result\"][0].get(\"data\", [])\n+\n+        return response\n+\n+    def get_install_status(self, name):\n+        \"\"\"\n+        This method is used to get the config and connection status of the specified FortiGate.\n+\n+        :param name: Type str.\n+                     The name of the FortiGate from which to retrieve the current status.\n+        :return: The json response data from the request to retrieve device status.\n+        \"\"\"\n+        params = [{\"url\": \"{}device\".format(self.dvmdb_url), \"filter\": [\"name\", \"==\", name],\n+                   \"fields\": [\"name\", \"conf_status\", \"conn_status\"]}]\n+        body = {\"method\": \"get\", \"params\": params, \"verbose\": 1, \"session\": self.session}\n+        response = self.make_request(body).json()\n+\n+        return response\n+\n+    def get_item(self, name):\n+        \"\"\"\n+        This method is used to get a specific object currently configured on the FortiManager for the ADOM and API\n+        Endpoint.\n+\n+        :param name: Type str.\n+                     The name of the object to retrieve.\n+        :return: The configuration dictionary for the object. An empty dict is returned if the request does\n+                 not return any data.\n+        \"\"\"\n+        item_url = self.obj_url + \"/{}\".format(name.replace('/','\\\\/'))\n+        body = {\"method\": \"get\", \"params\": [{\"url\": item_url}], \"verbose\": 1, \"session\": self.session}\n+        response = self.make_request(body)\n+\n+        return response.json()[\"result\"][0].get(\"data\", {})\n+\n+    def get_item_fields(self, name, fields):\n+        \"\"\"\n+        This method is used to get a specific object currently configured on the FortiManager for the ADOM and API\n+        Endpoint. The configuration fields retrieved are limited to the list defined in the fields variable.\n+\n+        :param name: Type str.\n+                     The name of the object to retrieve.\n+        :param fields: Type list.\n+                       The list of fields to return for each object.\n+        :return: The list of configuration dictionaries for each object. An empty list is returned if the request does\n+                 not return any data.\n+        \"\"\"\n+        params = [{\"url\": self.obj_url, \"filter\": [\"name\", \"==\", name], \"fields\": fields}]\n+        body = {\"method\": \"get\", \"params\": params, \"verbose\": 1, \"session\": self.session}\n+        response = self.make_request(body)\n+        response_data = response.json()[\"result\"][0].get(\"data\", [{}])\n+\n+        if response_data:\n+            return response_data[0]\n+        else:\n+            return {}\n+\n+    def get_revision(self, name=\"\"):\n+        \"\"\"\n+        This method is used to retrieve ADOM revisions from the FortiManager. If name is not specified, all revisions\n+        will be returned.\n+\n+        :param name: Type str.\n+                     The name of the revision to retrieve.\n+        :return: The json response data from the request to retrieve the revision.\n+        \"\"\"\n+        params = [{\"url\": \"{}revision\".format(self.dvmdb_url)}]\n+        if name:\n+            # noinspection PyTypeChecker\n+            params[0].update({\"filter\": [\"name\", \"==\", name]})\n+\n+        body = {\"method\": \"get\", \"params\": params, \"verbose\": 1, \"session\": self.session}\n+        response = self.make_request(body).json()\n+\n+        return response\n+\n+    def get_adom(self, name=\"\"):\n+        \"\"\"\n+        This method is used to retrieve ADOMs from the FortiManager. If name is not specified, all ADOMs.\n+\n+        :param name: Type str.\n+                     The name of the adom to retrieve.\n+        :return: The json response data from the request to retrieve the revision.\n+        \"\"\"\n+        params = [{\"url\": self.dvmdb_adom_url}]\n+        if name:\n+            # noinspection PyTypeChecker\n+            params[0].update({\"filter\": [\"name\", \"==\", name]})\n+\n+        body = {\"method\": \"get\", \"params\": params, \"verbose\": 1, \"session\": self.session}\n+        response = self.make_request(body).json()\n+\n+        return response\n+\n+    def get_status(self):\n+        \"\"\"\n+        This method is used to retrieve the status of the FortiManager.\n+\n+        :return: The json response data from the request to retrieve system status.\n+        \"\"\"\n+        body = dict(method=\"get\", params=[dict(url=\"/sys/status\")], verbose=1, session=self.session)\n+        response = self.make_request(body)\n+\n+        return response.json()[\"result\"][0].get(\"data\", [])\n+\n+    def get_task(self, task, wait):\n+        \"\"\"\n+        This method is used to get the status of a task.\n+\n+        :param task: Type str.\n+                     The task id to retrieve\n+        :param wait: Type int.\n+                     The number of minutes to wait before failing.\n+        :return: The json results from the task once completed, failed, or time ran out.\n+        \"\"\"\n+        body = {\"method\": \"get\", \"params\": [{\"url\": \"task/task/{}\".format(task)}], \"verbose\": 1,\n+                \"session\": self.session}\n+        percent_complete = 0\n+        countdown = time.localtime().tm_min\n+\n+        while percent_complete != 100:\n+            response = self.make_request(body).json()\n+            if response[\"result\"][0][\"status\"][\"code\"] == 0:\n+                percent_complete = response[\"result\"][0][\"data\"][\"percent\"]\n+\n+            # limit execution time to specified time in minutes\n+            if time.localtime().tm_min - countdown > wait:\n+                break\n+            elif countdown in range((60 - wait), 61) and time.localtime().tm_min in range(wait):\n+                break\n+            else:\n+                time.sleep(15)\n+\n+        return response\n+\n+    def install_package(self, proposed):\n+        \"\"\"\n+        This method is used to install a package to the end devices.\n+\n+        :param proposed: Type list.\n+                         The data portion of the API Request.\n+        :return: The json result data from the task associated with request to make install the package.\n+        \"\"\"\n+        body = {\"method\": \"exec\", \"params\": [{\"url\": \"/securityconsole/install/package\", \"data\": proposed, \"id\": 1,\n+                                              \"session\": self.session}]}\n+\n+        response = self.make_request(body).json()\n+\n+        # collect task id\n+        if response[\"result\"][0][\"status\"][\"code\"] == 0:\n+            task = response[\"result\"][0][\"data\"][\"task\"]\n+        else:\n+            return response\n+\n+        # check for task completion\n+        task_status = self.get_task(task, 10)\n+\n+        return task_status\n+\n+    def lock(self):\n+        \"\"\"\n+        The lock method is used to lock the ADOM to enable configurations to be sent to the FortiManager when it has\n+        workspace mode enabled.\n+\n+        :return: The JSON response from the request to lock the session.\n+        \"\"\"\n+        body = {\"method\": \"exec\", \"params\": [{\"url\": self.wsp_url + \"lock\"}], \"session\": self.session}\n+        response = self.make_request(body)\n+\n+        return response.json()\n+\n+    def login(self):\n+        \"\"\"\n+        The login method is used to establish a session with the FortiManager. All necessary parameters need to be\n+        established at class instantiation.\n+\n+        :return: The response from the login request. The instance session is also set, and defaults to None if the\n+        login was not successful\n+        \"\"\"\n+        params = [{\"url\": \"/sys/login/user\", \"data\": {\"user\": self.user, \"passwd\": self.passw}}]\n+        body = {\"method\": \"exec\", \"params\": params}\n+        login = self.make_request(body)\n+\n+        self.session = login.json().get(\"session\")\n+\n+        return login\n+\n+    def logout(self):\n+        \"\"\"\n+        The login method is used to establish a session with the FortiManager. All necessary parameters need to be\n+        established at class instantiation.\n+\n+        :return: The response from the login request. The instance session is also set, and defaults to None if the\n+        login was not successful\n+        \"\"\"\n+        body = dict(method=\"exec\", params=[{\"url\": \"/sys/logout\"}], session=self.session)\n+        logout = self.make_request(body)\n+\n+        return logout\n+\n+    def make_request(self, body):\n+        \"\"\"\n+        This method is used to make a request to the FortiManager API. All requests to FortiManager use the POST method\n+        to the same URL.\n+\n+        :param body: Type dict.\n+                     The JSON body with the necessary request params.\n+        :return: The response from the API request.\n+        \"\"\"\n+        response = requests.post(self.url, json=body, headers=self.headers, verify=self.verify)\n+\n+        return response\n+\n+    def preview_install(self, package, device, vdoms, lock):\n+        \"\"\"\n+        This method is used to preview what changes will be pushed to the end device when the package is installed. The\n+        Fortimanager requires the install process be started with the preview flag in order for policy updates to be\n+        included in the preview request. This method will handle this process, and cancel the install task after the\n+        preview has been generated. This method also makes use of FortiManager's \"id\" field to keep track of the stages\n+        (install preview, generate preview, retrieve preview, cancel install) the method is currently executing, and\n+        returns the ID in the response. If the module returns early, then the \"id\" field can be used to determine where\n+        the failure occurred.\n+\n+        :param package: Type str.\n+                        The name of the package in consideration for install.\n+        :param device: Type str.\n+                       The FortiNet to preview install.\n+        :param vdoms: Type list.\n+                      The list of vdoms associated with the vdom to preview install\n+        :param lock: Type bool\n+                     Determines whether the package install preview will use the auto lock field.\n+        :return: The json response data from the request to preview install the package.\n+        \"\"\"\n+        # issue package install with preview flag to include policy in preview\n+        flags = [\"preview\"]\n+        if lock:\n+            flags.append(\"auto_lock_ws\")\n+\n+        proposed = [{\"adom\": self.adom, \"flags\": flags, \"pkg\": package, \"scope\": [device]}]\n+        response = self.install_package(proposed)\n+\n+        if response[\"result\"][0].get(\"data\", {\"state\": \"error\"}).get(\"state\") == \"done\":\n+            # generate preview request\n+            proposed = [{\"adom\": self.adom, \"device\": device, \"vdoms\": vdoms}]\n+            body = {\"method\": \"exec\", \"params\": [{\"url\": \"/securityconsole/install/preview\", \"data\": proposed}],\n+                    \"id\": 2, \"session\": self.session}\n+            response = self.make_request(body).json()\n+        else:\n+            response.update({\"id\": 1})\n+            return response\n+\n+        # collect task id\n+        if response[\"result\"][0][\"status\"][\"code\"] == 0:\n+            task = response[\"result\"][0][\"data\"][\"task\"]\n+        else:\n+            return response\n+\n+        task_status = self.get_task(task, 5)\n+        if task_status[\"result\"][0][\"data\"][\"percent\"] == 100:\n+            # cancel install task\n+            url = \"/securityconsole/package/cancel/install\"\n+            params = [{\"url\": url, \"data\": [{\"adom\": self.adom, \"device\": device}]}]\n+            body = {\"method\": \"exec\", \"params\": params, \"id\": 3, \"session\": self.session}\n+            response = self.make_request(body).json()\n+        else:\n+            task_status.update({\"id\": 2})\n+            return task_status\n+\n+        if response[\"result\"][0][\"status\"][\"code\"] == 0:\n+            # get preview result\n+            params = [{\"url\": \"/securityconsole/preview/result\", \"data\": [{\"adom\": self.adom, \"device\": device}]}]\n+            body = {\"method\": \"exec\", \"params\": params, \"id\": 4,\n+                    \"session\": self.session}\n+            response = self.make_request(body).json()\n+        else:\n+            return response\n+\n+        return response\n+\n+    def restore_revision(self, version, proposed):\n+        \"\"\"\n+        This method is used to restore an ADOM to a previous revision.\n+\n+        :param version: Type str.\n+                        The version number corresponding to the revision to delete.\n+        :param proposed: Type list.\n+                         The data portion of the API request.\n+        :return: The json response data from the request to delete the revision.\n+        \"\"\"\n+        rev_url = \"{}revision/{}\".format(self.dvmdb_url, version)\n+        body = {\"method\": \"clone\", \"params\": [{\"url\": rev_url, \"data\": proposed}], \"session\": self.session}\n+        response = self.make_request(body).json()\n+\n+        return response\n+\n+    def save(self):\n+        \"\"\"\n+        The save method is used to save the ADOM configurations during a locked session.\n+\n+        :return: The JSON response from the request to save the session.\n+        \"\"\"\n+        body = {\"method\": \"exec\", \"params\": [{\"url\": self.wsp_url + \"commit\"}], \"session\": self.session}\n+        response = self.make_request(body)\n+\n+        return response.json()\n+\n+    def unlock(self):\n+        \"\"\"\n+        The unlock method is used to lock the ADOM to enable configurations to be sent to the FortiManager when it has\n+        workspace mode enabled.\n+\n+        :return: The JSON response from the request to unlock the session.\n+        \"\"\"\n+        body = {\"method\": \"exec\", \"params\": [{\"url\": self.wsp_url + \"unlock\"}], \"session\": self.session}\n+        response = self.make_request(body)\n+\n+        return response.json()\n+\n+    def update_config(self, update_config):\n+        \"\"\"\n+        This method is used to submit a configuration update request to the FortiManager. Only the object configuration\n+        details need to be provided; all other parameters that make up the API request body will be handled by the\n+        method. Only fields that need to be updated are required to be in the \"update_config\" variable (EX: updating\n+        the comment for an address group only needs the \"name\" and \"comment\" fields in the configuration dictionary).\n+        When including a field in the configuration update, ensure that all items are included for the desired end-state\n+        (EX: adding address to an address group that already has [\"svr01\", \"svr02\"] should include all three\n+        addresses in the \"member\" list, [\"svr01\", \"svr02\", \"svr03\"]. If you want to remove part of an item's\n+        configuration, this method should be used, and the item to be removed should be left off the respective list\n+        (EX: removing an address from an address group that has [\"svr01\", \"svr02\", \"svr03\"] should have a \"member\" list\n+        like [\"svr01\", \"svr02\"] with the final state of the address group containing only svr01 and svr02).\n+\n+        :param update_config: Type list.\n+                           The \"data\" portion of the configuration to be submitted to the FortiManager.\n+        :return: The response from the API request to add the configuration.\n+        \"\"\"\n+        body = {\"method\": \"update\", \"params\": [{\"url\": self.obj_url, \"data\": update_config, \"session\": self.session}]}\n+        response = self.make_request(body)\n+\n+        return response\n+\n+    def _escape_params_url(self, url):\n+        \"\"\"\n+        This private method is used to escape slash (\"/\") characters from a url string to be provided as a json-rpc request params.\n+        Slash characters are escaped by prefixing with a backslash (\"\\\").\n+        If url is None, None is returned.\n+\n+        :param url: Type str.\n+                        The url string to process.\n+        :return: The url string with slash characters escaped with a backslash (\"\\\") or None if url is None.\n+        \"\"\"\n+        if url is not None:\n+            return url.replace('/', '\\\\/')","path":"library/fortimgr_jsonrpc_request.py","position":1413,"original_position":1413,"commit_id":"0a173b6893fa689a5e25f951a16c189b7ce65cb5","original_commit_id":"0a173b6893fa689a5e25f951a16c189b7ce65cb5","user":{"login":"jmcgill298","id":9847006,"avatar_url":"https://avatars3.githubusercontent.com/u/9847006?v=4","gravatar_id":"","url":"https://api.github.com/users/jmcgill298","html_url":"https://github.com/jmcgill298","followers_url":"https://api.github.com/users/jmcgill298/followers","following_url":"https://api.github.com/users/jmcgill298/following{/other_user}","gists_url":"https://api.github.com/users/jmcgill298/gists{/gist_id}","starred_url":"https://api.github.com/users/jmcgill298/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jmcgill298/subscriptions","organizations_url":"https://api.github.com/users/jmcgill298/orgs","repos_url":"https://api.github.com/users/jmcgill298/repos","events_url":"https://api.github.com/users/jmcgill298/events{/privacy}","received_events_url":"https://api.github.com/users/jmcgill298/received_events","type":"User","site_admin":false},"body":"needs to include the string typecast","created_at":"2018-03-12T16:13:09Z","updated_at":"2018-03-12T16:13:14Z","html_url":"https://github.com/networktocode/fortimanager-ansible/pull/53#discussion_r173854673","pull_request_url":"https://api.github.com/repos/networktocode/fortimanager-ansible/pulls/53","author_association":"COLLABORATOR","_links":{"self":{"href":"https://api.github.com/repos/networktocode/fortimanager-ansible/pulls/comments/173854673"},"html":{"href":"https://github.com/networktocode/fortimanager-ansible/pull/53#discussion_r173854673"},"pull_request":{"href":"https://api.github.com/repos/networktocode/fortimanager-ansible/pulls/53"}}},"pull_request":{"url":"https://api.github.com/repos/networktocode/fortimanager-ansible/pulls/53","id":166406401,"html_url":"https://github.com/networktocode/fortimanager-ansible/pull/53","diff_url":"https://github.com/networktocode/fortimanager-ansible/pull/53.diff","patch_url":"https://github.com/networktocode/fortimanager-ansible/pull/53.patch","issue_url":"https://api.github.com/repos/networktocode/fortimanager-ansible/issues/53","number":53,"state":"open","locked":false,"title":"Feature fortimgr jsonrpc request","user":{"login":"jcsicard","id":16283310,"avatar_url":"https://avatars2.githubusercontent.com/u/16283310?v=4","gravatar_id":"","url":"https://api.github.com/users/jcsicard","html_url":"https://github.com/jcsicard","followers_url":"https://api.github.com/users/jcsicard/followers","following_url":"https://api.github.com/users/jcsicard/following{/other_user}","gists_url":"https://api.github.com/users/jcsicard/gists{/gist_id}","starred_url":"https://api.github.com/users/jcsicard/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jcsicard/subscriptions","organizations_url":"https://api.github.com/users/jcsicard/orgs","repos_url":"https://api.github.com/users/jcsicard/repos","events_url":"https://api.github.com/users/jcsicard/events{/privacy}","received_events_url":"https://api.github.com/users/jcsicard/received_events","type":"User","site_admin":false},"body":"Add a module (and associated doc, examples and unittest) to do generic json-rpc request to the FortiManager API.\r\nUseful for actions that are not covered by another specific fortimanager-ansible module such as creating an adom, policy-package, dynamic interface...\r\n","created_at":"2018-01-31T23:36:38Z","updated_at":"2018-03-12T16:13:14Z","closed_at":null,"merged_at":null,"merge_commit_sha":"a2b64a34354d319a93a8debb4ddcc70606505200","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/networktocode/fortimanager-ansible/pulls/53/commits","review_comments_url":"https://api.github.com/repos/networktocode/fortimanager-ansible/pulls/53/comments","review_comment_url":"https://api.github.com/repos/networktocode/fortimanager-ansible/pulls/comments{/number}","comments_url":"https://api.github.com/repos/networktocode/fortimanager-ansible/issues/53/comments","statuses_url":"https://api.github.com/repos/networktocode/fortimanager-ansible/statuses/0a173b6893fa689a5e25f951a16c189b7ce65cb5","head":{"label":"jcsicard:feature-fortimgr_jsonrpc_request","ref":"feature-fortimgr_jsonrpc_request","sha":"0a173b6893fa689a5e25f951a16c189b7ce65cb5","user":{"login":"jcsicard","id":16283310,"avatar_url":"https://avatars2.githubusercontent.com/u/16283310?v=4","gravatar_id":"","url":"https://api.github.com/users/jcsicard","html_url":"https://github.com/jcsicard","followers_url":"https://api.github.com/users/jcsicard/followers","following_url":"https://api.github.com/users/jcsicard/following{/other_user}","gists_url":"https://api.github.com/users/jcsicard/gists{/gist_id}","starred_url":"https://api.github.com/users/jcsicard/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jcsicard/subscriptions","organizations_url":"https://api.github.com/users/jcsicard/orgs","repos_url":"https://api.github.com/users/jcsicard/repos","events_url":"https://api.github.com/users/jcsicard/events{/privacy}","received_events_url":"https://api.github.com/users/jcsicard/received_events","type":"User","site_admin":false},"repo":{"id":111693506,"name":"fortimanager-ansible","full_name":"jcsicard/fortimanager-ansible","owner":{"login":"jcsicard","id":16283310,"avatar_url":"https://avatars2.githubusercontent.com/u/16283310?v=4","gravatar_id":"","url":"https://api.github.com/users/jcsicard","html_url":"https://github.com/jcsicard","followers_url":"https://api.github.com/users/jcsicard/followers","following_url":"https://api.github.com/users/jcsicard/following{/other_user}","gists_url":"https://api.github.com/users/jcsicard/gists{/gist_id}","starred_url":"https://api.github.com/users/jcsicard/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jcsicard/subscriptions","organizations_url":"https://api.github.com/users/jcsicard/orgs","repos_url":"https://api.github.com/users/jcsicard/repos","events_url":"https://api.github.com/users/jcsicard/events{/privacy}","received_events_url":"https://api.github.com/users/jcsicard/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/jcsicard/fortimanager-ansible","description":"Ansible Modules to manage Fortinet FortiManager","fork":true,"url":"https://api.github.com/repos/jcsicard/fortimanager-ansible","forks_url":"https://api.github.com/repos/jcsicard/fortimanager-ansible/forks","keys_url":"https://api.github.com/repos/jcsicard/fortimanager-ansible/keys{/key_id}","collaborators_url":"https://api.github.com/repos/jcsicard/fortimanager-ansible/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/jcsicard/fortimanager-ansible/teams","hooks_url":"https://api.github.com/repos/jcsicard/fortimanager-ansible/hooks","issue_events_url":"https://api.github.com/repos/jcsicard/fortimanager-ansible/issues/events{/number}","events_url":"https://api.github.com/repos/jcsicard/fortimanager-ansible/events","assignees_url":"https://api.github.com/repos/jcsicard/fortimanager-ansible/assignees{/user}","branches_url":"https://api.github.com/repos/jcsicard/fortimanager-ansible/branches{/branch}","tags_url":"https://api.github.com/repos/jcsicard/fortimanager-ansible/tags","blobs_url":"https://api.github.com/repos/jcsicard/fortimanager-ansible/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/jcsicard/fortimanager-ansible/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/jcsicard/fortimanager-ansible/git/refs{/sha}","trees_url":"https://api.github.com/repos/jcsicard/fortimanager-ansible/git/trees{/sha}","statuses_url":"https://api.github.com/repos/jcsicard/fortimanager-ansible/statuses/{sha}","languages_url":"https://api.github.com/repos/jcsicard/fortimanager-ansible/languages","stargazers_url":"https://api.github.com/repos/jcsicard/fortimanager-ansible/stargazers","contributors_url":"https://api.github.com/repos/jcsicard/fortimanager-ansible/contributors","subscribers_url":"https://api.github.com/repos/jcsicard/fortimanager-ansible/subscribers","subscription_url":"https://api.github.com/repos/jcsicard/fortimanager-ansible/subscription","commits_url":"https://api.github.com/repos/jcsicard/fortimanager-ansible/commits{/sha}","git_commits_url":"https://api.github.com/repos/jcsicard/fortimanager-ansible/git/commits{/sha}","comments_url":"https://api.github.com/repos/jcsicard/fortimanager-ansible/comments{/number}","issue_comment_url":"https://api.github.com/repos/jcsicard/fortimanager-ansible/issues/comments{/number}","contents_url":"https://api.github.com/repos/jcsicard/fortimanager-ansible/contents/{+path}","compare_url":"https://api.github.com/repos/jcsicard/fortimanager-ansible/compare/{base}...{head}","merges_url":"https://api.github.com/repos/jcsicard/fortimanager-ansible/merges","archive_url":"https://api.github.com/repos/jcsicard/fortimanager-ansible/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/jcsicard/fortimanager-ansible/downloads","issues_url":"https://api.github.com/repos/jcsicard/fortimanager-ansible/issues{/number}","pulls_url":"https://api.github.com/repos/jcsicard/fortimanager-ansible/pulls{/number}","milestones_url":"https://api.github.com/repos/jcsicard/fortimanager-ansible/milestones{/number}","notifications_url":"https://api.github.com/repos/jcsicard/fortimanager-ansible/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/jcsicard/fortimanager-ansible/labels{/name}","releases_url":"https://api.github.com/repos/jcsicard/fortimanager-ansible/releases{/id}","deployments_url":"https://api.github.com/repos/jcsicard/fortimanager-ansible/deployments","created_at":"2017-11-22T14:21:06Z","updated_at":"2017-11-22T14:21:08Z","pushed_at":"2018-02-19T03:45:55Z","git_url":"git://github.com/jcsicard/fortimanager-ansible.git","ssh_url":"git@github.com:jcsicard/fortimanager-ansible.git","clone_url":"https://github.com/jcsicard/fortimanager-ansible.git","svn_url":"https://github.com/jcsicard/fortimanager-ansible","homepage":null,"size":810,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"open_issues_count":0,"license":null,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"networktocode:master","ref":"master","sha":"2dad7eb146d0ad8e1a5a6dc6c28eaf4f1e9d9a82","user":{"login":"networktocode","id":13106404,"avatar_url":"https://avatars0.githubusercontent.com/u/13106404?v=4","gravatar_id":"","url":"https://api.github.com/users/networktocode","html_url":"https://github.com/networktocode","followers_url":"https://api.github.com/users/networktocode/followers","following_url":"https://api.github.com/users/networktocode/following{/other_user}","gists_url":"https://api.github.com/users/networktocode/gists{/gist_id}","starred_url":"https://api.github.com/users/networktocode/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/networktocode/subscriptions","organizations_url":"https://api.github.com/users/networktocode/orgs","repos_url":"https://api.github.com/users/networktocode/repos","events_url":"https://api.github.com/users/networktocode/events{/privacy}","received_events_url":"https://api.github.com/users/networktocode/received_events","type":"Organization","site_admin":false},"repo":{"id":94484096,"name":"fortimanager-ansible","full_name":"networktocode/fortimanager-ansible","owner":{"login":"networktocode","id":13106404,"avatar_url":"https://avatars0.githubusercontent.com/u/13106404?v=4","gravatar_id":"","url":"https://api.github.com/users/networktocode","html_url":"https://github.com/networktocode","followers_url":"https://api.github.com/users/networktocode/followers","following_url":"https://api.github.com/users/networktocode/following{/other_user}","gists_url":"https://api.github.com/users/networktocode/gists{/gist_id}","starred_url":"https://api.github.com/users/networktocode/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/networktocode/subscriptions","organizations_url":"https://api.github.com/users/networktocode/orgs","repos_url":"https://api.github.com/users/networktocode/repos","events_url":"https://api.github.com/users/networktocode/events{/privacy}","received_events_url":"https://api.github.com/users/networktocode/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/networktocode/fortimanager-ansible","description":"Ansible Modules to manage Fortinet FortiManager","fork":false,"url":"https://api.github.com/repos/networktocode/fortimanager-ansible","forks_url":"https://api.github.com/repos/networktocode/fortimanager-ansible/forks","keys_url":"https://api.github.com/repos/networktocode/fortimanager-ansible/keys{/key_id}","collaborators_url":"https://api.github.com/repos/networktocode/fortimanager-ansible/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/networktocode/fortimanager-ansible/teams","hooks_url":"https://api.github.com/repos/networktocode/fortimanager-ansible/hooks","issue_events_url":"https://api.github.com/repos/networktocode/fortimanager-ansible/issues/events{/number}","events_url":"https://api.github.com/repos/networktocode/fortimanager-ansible/events","assignees_url":"https://api.github.com/repos/networktocode/fortimanager-ansible/assignees{/user}","branches_url":"https://api.github.com/repos/networktocode/fortimanager-ansible/branches{/branch}","tags_url":"https://api.github.com/repos/networktocode/fortimanager-ansible/tags","blobs_url":"https://api.github.com/repos/networktocode/fortimanager-ansible/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/networktocode/fortimanager-ansible/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/networktocode/fortimanager-ansible/git/refs{/sha}","trees_url":"https://api.github.com/repos/networktocode/fortimanager-ansible/git/trees{/sha}","statuses_url":"https://api.github.com/repos/networktocode/fortimanager-ansible/statuses/{sha}","languages_url":"https://api.github.com/repos/networktocode/fortimanager-ansible/languages","stargazers_url":"https://api.github.com/repos/networktocode/fortimanager-ansible/stargazers","contributors_url":"https://api.github.com/repos/networktocode/fortimanager-ansible/contributors","subscribers_url":"https://api.github.com/repos/networktocode/fortimanager-ansible/subscribers","subscription_url":"https://api.github.com/repos/networktocode/fortimanager-ansible/subscription","commits_url":"https://api.github.com/repos/networktocode/fortimanager-ansible/commits{/sha}","git_commits_url":"https://api.github.com/repos/networktocode/fortimanager-ansible/git/commits{/sha}","comments_url":"https://api.github.com/repos/networktocode/fortimanager-ansible/comments{/number}","issue_comment_url":"https://api.github.com/repos/networktocode/fortimanager-ansible/issues/comments{/number}","contents_url":"https://api.github.com/repos/networktocode/fortimanager-ansible/contents/{+path}","compare_url":"https://api.github.com/repos/networktocode/fortimanager-ansible/compare/{base}...{head}","merges_url":"https://api.github.com/repos/networktocode/fortimanager-ansible/merges","archive_url":"https://api.github.com/repos/networktocode/fortimanager-ansible/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/networktocode/fortimanager-ansible/downloads","issues_url":"https://api.github.com/repos/networktocode/fortimanager-ansible/issues{/number}","pulls_url":"https://api.github.com/repos/networktocode/fortimanager-ansible/pulls{/number}","milestones_url":"https://api.github.com/repos/networktocode/fortimanager-ansible/milestones{/number}","notifications_url":"https://api.github.com/repos/networktocode/fortimanager-ansible/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/networktocode/fortimanager-ansible/labels{/name}","releases_url":"https://api.github.com/repos/networktocode/fortimanager-ansible/releases{/id}","deployments_url":"https://api.github.com/repos/networktocode/fortimanager-ansible/deployments","created_at":"2017-06-15T22:38:38Z","updated_at":"2018-03-12T16:13:10Z","pushed_at":"2018-03-12T16:05:13Z","git_url":"git://github.com/networktocode/fortimanager-ansible.git","ssh_url":"git@github.com:networktocode/fortimanager-ansible.git","clone_url":"https://github.com/networktocode/fortimanager-ansible.git","svn_url":"https://github.com/networktocode/fortimanager-ansible","homepage":null,"size":266,"stargazers_count":14,"watchers_count":14,"language":"Python","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":11,"mirror_url":null,"archived":false,"open_issues_count":3,"license":null,"forks":11,"open_issues":3,"watchers":14,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/networktocode/fortimanager-ansible/pulls/53"},"html":{"href":"https://github.com/networktocode/fortimanager-ansible/pull/53"},"issue":{"href":"https://api.github.com/repos/networktocode/fortimanager-ansible/issues/53"},"comments":{"href":"https://api.github.com/repos/networktocode/fortimanager-ansible/issues/53/comments"},"review_comments":{"href":"https://api.github.com/repos/networktocode/fortimanager-ansible/pulls/53/comments"},"review_comment":{"href":"https://api.github.com/repos/networktocode/fortimanager-ansible/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/networktocode/fortimanager-ansible/pulls/53/commits"},"statuses":{"href":"https://api.github.com/repos/networktocode/fortimanager-ansible/statuses/0a173b6893fa689a5e25f951a16c189b7ce65cb5"}},"author_association":"CONTRIBUTOR"}}
{"actor":{"display_login":"jmcgill298"}}