From f1d568749fd24b9a874b11804fc2775cfbad8821 Mon Sep 17 00:00:00 2001 From: chkp-orso <47325598+chkp-orso@users.noreply.github.com> Date: Wed, 21 Aug 2019 09:53:17 +0300 Subject: [PATCH] fix: Checkpoint check_mode, add suboptions, return the facts (#60264) * fix: check_mode, add suboptions, return the facts * pip8, ansible_facts, get_payload * add space to create PR * remove space to create PR * test tests of cp_network * change for the test of network * fixes to pass the tests * fix tests * Update ignore.txt --- .../network/checkpoint/checkpoint.py | 268 +++++++++++++++--- .../modules/network/checkpoint/cp_network.py | 118 +++++--- .../network/checkpoint/cp_network_facts.py | 113 ++++++++ .../plugins/doc_fragments/checkpoint_facts.py | 19 ++ test/sanity/ignore.txt | 1 - .../network/checkpoint/test_cp_network.py | 19 +- 6 files changed, 452 insertions(+), 86 deletions(-) create mode 100644 lib/ansible/modules/network/checkpoint/cp_network_facts.py create mode 100644 lib/ansible/plugins/doc_fragments/checkpoint_facts.py diff --git a/lib/ansible/module_utils/network/checkpoint/checkpoint.py b/lib/ansible/module_utils/network/checkpoint/checkpoint.py index 640b83aaff5..1840f00beca 100644 --- a/lib/ansible/module_utils/network/checkpoint/checkpoint.py +++ b/lib/ansible/module_utils/network/checkpoint/checkpoint.py @@ -32,7 +32,6 @@ import time from ansible.module_utils.connection import Connection - checkpoint_argument_spec_for_objects = dict( auto_publish_session=dict(type='bool'), wait_for_task=dict(type='bool', default=True), @@ -59,20 +58,24 @@ def send_request(connection, version, url, payload=None): # get the payload from the user parameters def is_checkpoint_param(parameter): - if parameter == 'auto_publish_session' or\ - parameter == 'state' or\ - parameter == 'wait_for_task' or\ + if parameter == 'auto_publish_session' or \ + parameter == 'state' or \ + parameter == 'wait_for_task' or \ parameter == 'version': return False return True # build the payload from the parameters which has value (not None), and they are parameter of checkpoint API as well -def get_payload_from_parameters(module): +def get_payload_from_parameters(params): payload = {} - for parameter in module.params: - if module.params[parameter] and is_checkpoint_param(parameter): - payload[parameter.replace("_", "-")] = module.params[parameter] + for parameter in params: + parameter_value = params[parameter] + if parameter_value and is_checkpoint_param(parameter): + if isinstance(parameter_value, dict): + payload[parameter.replace("_", "-")] = get_payload_from_parameters(parameter_value) + else: + payload[parameter.replace("_", "-")] = parameter_value return payload @@ -131,7 +134,7 @@ def handle_publish(module, connection, version): # handle a command def api_command(module, command): - payload = get_payload_from_parameters(module) + payload = get_payload_from_parameters(module.params) connection = Connection(module._socket_path) # if user insert a specific version, we add it to the url version = ('v' + module.params['version'] + '/') if module.params.get('version') else '' @@ -154,10 +157,34 @@ def api_command(module, command): return result +# handle api call facts +def api_call_facts(module, api_call_object, api_call_object_plural_version): + payload = get_payload_from_parameters(module.params) + connection = Connection(module._socket_path) + # if user insert a specific version, we add it to the url + version = ('v' + module.params['version'] + '/') if module.params['version'] else '' + + # if there is neither name nor uid, the API command will be in plural version (e.g. show-hosts instead of show-host) + if payload.get("name") is None and payload.get("uid") is None: + api_call_object = api_call_object_plural_version + + code, response = send_request(connection, version, 'show-' + api_call_object, payload) + if code != 200: + module.fail_json(msg='Checkpoint device returned error {0} with message {1}'.format(code, response)) + + result = {api_call_object: response} + return result + + # handle api call def api_call(module, api_call_object): - payload = get_payload_from_parameters(module) + payload = get_payload_from_parameters(module.params) connection = Connection(module._socket_path) + + result = {'changed': False, 'checkpoint_session_uid': connection.get_session_uid()} + if module.check_mode: + return result + # if user insert a specific version, we add it to the url version = ('v' + module.params['version'] + '/') if module.params.get('version') else '' @@ -166,25 +193,11 @@ def api_call(module, api_call_object): # if code is 400 (bad request) or 500 (internal error) - fail if equals_code == 400 or equals_code == 500: module.fail_json(msg=equals_response) - result = {'changed': False} if module.params['state'] == 'present': - if not module.check_mode: - if equals_code == 200: - if not equals_response['equals']: - code, response = send_request(connection, version, 'set-' + api_call_object, payload) - if code != 200: - module.fail_json(msg=response) - - handle_publish(module, connection, version) - - result['changed'] = True - result[api_call_object] = response - else: - # objects are equals and there is no need for set request - pass - elif equals_code == 404: - code, response = send_request(connection, version, 'add-' + api_call_object, payload) + if equals_code == 200: + if not equals_response['equals']: + code, response = send_request(connection, version, 'set-' + api_call_object, payload) if code != 200: module.fail_json(msg=response) @@ -192,24 +205,213 @@ def api_call(module, api_call_object): result['changed'] = True result[api_call_object] = response + else: + # objects are equals and there is no need for set request + pass + elif equals_code == 404: + code, response = send_request(connection, version, 'add-' + api_call_object, payload) + if code != 200: + module.fail_json(msg=response) + + handle_publish(module, connection, version) + + result['changed'] = True + result[api_call_object] = response elif module.params['state'] == 'absent': - if not module.check_mode: - if equals_code == 200: - code, response = send_request(connection, version, 'delete-' + api_call_object, payload) + if equals_code == 200: + code, response = send_request(connection, version, 'delete-' + api_call_object, payload) + if code != 200: + module.fail_json(msg=response) + + handle_publish(module, connection, version) + + result['changed'] = True + elif equals_code == 404: + # no need to delete because object dose not exist + pass + + return result + + +# get the position in integer format +def get_number_from_position(payload, connection, version): + if 'position' in payload: + position = payload['position'] + else: + return None + + # This code relevant if we will decide to support 'top' and 'bottom' in position + + # position_number = None + # # if position is not int, convert it to int. There are several cases: "top" + # if position == 'top': + # position_number = 1 + # elif position == 'bottom': + # payload_for_show_access_rulebase = {'name': payload['layer'], 'limit': 0} + # code, response = send_request(connection, version, 'show-access-rulebase', payload_for_show_access_rulebase) + # position_number = response['total'] + # elif isinstance(position, str): + # # here position is a number in format str (e.g. "5" and not 5) + # position_number = int(position) + # else: + # # here position suppose to be int + # position_number = position + # + # return position_number + + return int(position) + + +# is the param position (if the user inserted it) equals between the object and the user input +def is_equals_with_position_param(payload, connection, version, api_call_object): + position_number = get_number_from_position(payload, connection, version) + + # if there is no position param, then it's equals in vacuous truth + if position_number is None: + return True + + payload_for_show_access_rulebase = {'name': payload['layer'], 'offset': position_number - 1, 'limit': 1} + rulebase_command = 'show-' + api_call_object.split('-')[0] + '-rulebase' + code, response = send_request(connection, version, rulebase_command, payload_for_show_access_rulebase) + + # if true, it means there is no rule in the position that the user inserted, so I return false, and when we will try to set + # the rule, the API server will get throw relevant error + if response['total'] < position_number: + return False + + rule = response['rulebase'][0] + while 'rulebase' in rule: + rule = rule['rulebase'][0] + + # if the names of the exist rule and the user input rule are equals, then it's means that their positions are equals so I + # return True. and there is no way that there is another rule with this name cause otherwise the 'equals' command would fail + if rule['name'] == payload['name']: + return True + else: + return False + + +# get copy of the payload without some of the params +def get_copy_payload_without_some_params(payload, params_to_remove): + copy_payload = dict(payload) + for param in params_to_remove: + if param in copy_payload: + del copy_payload[param] + return copy_payload + + +# get copy of the payload with only some of the params +def get_copy_payload_with_some_params(payload, params_to_insert): + copy_payload = {} + for param in params_to_insert: + if param in payload: + copy_payload[param] = payload[param] + return copy_payload + + +# is equals with all the params including action and position +def is_equals_with_all_params(payload, connection, version, api_call_object, is_access_rule): + if is_access_rule and 'action' in payload: + payload_for_show = get_copy_payload_with_some_params(payload, ['name', 'uid', 'layer']) + code, response = send_request(connection, version, 'show-' + api_call_object, payload_for_show) + exist_action = response['action']['name'] + if exist_action != payload['action']: + return False + if not is_equals_with_position_param(payload, connection, version, api_call_object): + return False + + return True + + +# handle api call for rule +def api_call_for_rule(module, api_call_object): + is_access_rule = True if 'access' in api_call_object else False + payload = get_payload_from_parameters(module.params) + connection = Connection(module._socket_path) + + result = {'changed': False, 'checkpoint_session_uid': connection.get_session_uid()} + if module.check_mode: + return result + + # if user insert a specific version, we add it to the url + version = ('v' + module.params['version'] + '/') if module.params.get('version') else '' + + if is_access_rule: + copy_payload_without_some_params = get_copy_payload_without_some_params(payload, ['action', 'position']) + else: + copy_payload_without_some_params = get_copy_payload_without_some_params(payload, ['position']) + payload_for_equals = {'type': api_call_object, 'params': copy_payload_without_some_params} + equals_code, equals_response = send_request(connection, version, 'equals', payload_for_equals) + # if code is 400 (bad request) or 500 (internal error) - fail + if equals_code == 400 or equals_code == 500: + module.fail_json(msg=equals_response) + + if module.params['state'] == 'present': + if equals_code == 200: + if equals_response['equals']: + if not is_equals_with_all_params(payload, connection, version, api_call_object, is_access_rule): + equals_response['equals'] = False + if not equals_response['equals']: + # if user insert param 'position' and needed to use the 'set' command, change the param name to 'new-position' + if 'position' in payload: + payload['new-position'] = payload['position'] + del payload['position'] + code, response = send_request(connection, version, 'set-' + api_call_object, payload) if code != 200: module.fail_json(msg=response) handle_publish(module, connection, version) result['changed'] = True - elif equals_code == 404: - # no need to delete because object dose not exist + result[api_call_object] = response + else: + # objects are equals and there is no need for set request pass + elif equals_code == 404: + code, response = send_request(connection, version, 'add-' + api_call_object, payload) + if code != 200: + module.fail_json(msg=response) + + handle_publish(module, connection, version) + + result['changed'] = True + result[api_call_object] = response + elif module.params['state'] == 'absent': + if equals_code == 200: + code, response = send_request(connection, version, 'delete-' + api_call_object, payload) + if code != 200: + module.fail_json(msg=response) + + handle_publish(module, connection, version) + + result['changed'] = True + elif equals_code == 404: + # no need to delete because object dose not exist + pass - result['checkpoint_session_uid'] = connection.get_session_uid() return result +# handle api call facts for rule +def api_call_facts_for_rule(module, api_call_object, api_call_object_plural_version): + payload = get_payload_from_parameters(module.params) + connection = Connection(module._socket_path) + # if user insert a specific version, we add it to the url + version = ('v' + module.params['version'] + '/') if module.params['version'] else '' + + # if there is neither name nor uid, the API command will be in plural version (e.g. show-hosts instead of show-host) + if payload.get("layer") is None: + api_call_object = api_call_object_plural_version + + code, response = send_request(connection, version, 'show-' + api_call_object, payload) + if code != 200: + module.fail_json(msg='Checkpoint device returned error {0} with message {1}'.format(code, response)) + + result = {api_call_object: response} + return result + + +# The code from here till EOF will be deprecated when Rikis' modules will be deprecated checkpoint_argument_spec = dict(auto_publish_session=dict(type='bool', default=True), policy_package=dict(type='str', default='standard'), auto_install_policy=dict(type='bool', default=True), diff --git a/lib/ansible/modules/network/checkpoint/cp_network.py b/lib/ansible/modules/network/checkpoint/cp_network.py index e2de41e986f..f3f62796a4d 100644 --- a/lib/ansible/modules/network/checkpoint/cp_network.py +++ b/lib/ansible/modules/network/checkpoint/cp_network.py @@ -39,6 +39,7 @@ options: description: - Object name. type: str + required: True subnet: description: - IPv4 or IPv6 network address. If both addresses are required use subnet4 and subnet6 fields explicitly. @@ -71,7 +72,40 @@ options: nat_settings: description: - NAT settings. - type: dict + type: list + suboptions: + auto_rule: + description: + - Whether to add automatic address translation rules. + type: bool + ip_address: + description: + - IPv4 or IPv6 address. If both addresses are required use ipv4-address and ipv6-address fields + explicitly. This parameter is not required in case "method" parameter is "hide" and "hide-behind" parameter + is "gateway". + type: str + ipv4_address: + description: + - IPv4 address. + type: str + ipv6_address: + description: + - IPv6 address. + type: str + hide_behind: + description: + - Hide behind method. This parameter is not required in case "method" parameter is "static". + type: str + choices: ['gateway', 'ip-address'] + install_on: + description: + - Which gateway should apply the NAT translation. + type: str + method: + description: + - NAT translation method. + type: str + choices: ['hide', 'static'] tags: description: - Collection of tag identifiers. @@ -80,27 +114,26 @@ options: description: - Allow broadcast address inclusion. type: str - choices: - - disallow - - allow + choices: ['disallow', 'allow'] color: description: - Color of the object. Should be one of existing colors. - choices: ['aquamarine', 'black', 'blue', 'crete blue', 'burlywood', 'cyan', 'dark green', 'khaki', 'orchid', - 'dark orange', 'dark sea green', 'pink', 'turquoise', 'dark blue', 'firebrick', 'brown', 'forest green', - 'gold', 'dark gold', 'gray', 'dark gray', 'light green', 'lemon chiffon', 'coral', 'sea green', - 'sky blue', 'magenta', 'purple', 'slate blue', 'violet red', 'navy blue', 'olive', 'orange', 'red', - 'sienna', 'yellow'] + type: str + choices: ['aquamarine', 'black', 'blue', 'crete blue', 'burlywood', 'cyan', 'dark green', 'khaki', + 'orchid', 'dark orange', 'dark sea green', 'pink', 'turquoise', 'dark blue', 'firebrick', 'brown', + 'forest green', 'gold', 'dark gold', 'gray', 'dark gray', 'light green', 'lemon chiffon', 'coral', + 'sea green', 'sky blue', 'magenta', 'purple', 'slate blue', 'violet red', 'navy blue', 'olive', 'orange', + 'red', 'sienna', 'yellow'] comments: description: - Comments string. type: str details_level: description: - - The level of detail for some of the fields in the response can vary from showing only the UID value of the - object to a fully detailed representation of the object. + - The level of detail for some of the fields in the response can vary from showing only the UID value of + the object to a fully detailed representation of the object. type: str - choices: [uid, standard, full] + choices: ['uid', 'standard', 'full'] groups: description: - Collection of group identifiers. @@ -111,13 +144,9 @@ options: type: bool ignore_errors: description: - - Apply changes ignoring errors. You won't be able to publish such a changes. If ignore-warnings flag was omitted - - warnings will also be ignored. + - Apply changes ignoring errors. You won't be able to publish such a changes. If ignore-warnings flag was + omitted - warnings will also be ignored. type: bool - uid: - description: - - Object unique identifier. - type: str new_name: description: - New name of the object. @@ -130,10 +159,10 @@ EXAMPLES = """ cp_network: name: New Network 3 nat_settings: - auto-rule: true - hide-behind: ip-address - install-on: All - ip-address: 192.0.2.1 + auto_rule: true + hide_behind: ip-address + install_on: All + ip_address: 192.0.2.1 method: static state: present subnet: 192.0.2.1 @@ -141,17 +170,17 @@ EXAMPLES = """ - name: set-network cp_network: - name : New Network 1 - new-name : New Network 2 - color : green - subnet : 192.0.0.0 - mask-length : 16 - groups : New Group 1 + color: green + groups: New Group 1 + mask_length: 16 + name: New Network 1 + new_name: New Network 2 state: present + subnet: 192.0.0.0 - name: delete-network cp_network: - name : New Network 2 + name: New Network 2 state: absent """ @@ -168,7 +197,7 @@ from ansible.module_utils.network.checkpoint.checkpoint import checkpoint_argume def main(): argument_spec = dict( - name=dict(type='str'), + name=dict(type='str', required=True), subnet=dict(type='str'), subnet4=dict(type='str'), subnet6=dict(type='str'), @@ -176,29 +205,34 @@ def main(): mask_length4=dict(type='int'), mask_length6=dict(type='int'), subnet_mask=dict(type='str'), - nat_settings=dict(type='dict'), + nat_settings=dict(type='list', options=dict( + auto_rule=dict(type='bool'), + ip_address=dict(type='str'), + ipv4_address=dict(type='str'), + ipv6_address=dict(type='str'), + hide_behind=dict(type='str', choices=['gateway', 'ip-address']), + install_on=dict(type='str'), + method=dict(type='str', choices=['hide', 'static']) + )), tags=dict(type='list'), broadcast=dict(type='str', choices=['disallow', 'allow']), - color=dict(type='str', choices=['aquamarine', 'black', 'blue', 'crete blue', 'burlywood', 'cyan', 'dark green', - 'khaki', 'orchid', 'dark orange', 'dark sea green', 'pink', 'turquoise', - 'dark blue', 'firebrick', 'brown', 'forest green', 'gold', 'dark gold', 'gray', - 'dark gray', 'light green', 'lemon chiffon', 'coral', 'sea green', 'sky blue', - 'magenta', 'purple', 'slate blue', 'violet red', 'navy blue', 'olive', 'orange', - 'red', 'sienna', 'yellow']), + color=dict(type='str', choices=['aquamarine', 'black', 'blue', + 'crete blue', 'burlywood', 'cyan', 'dark green', 'khaki', 'orchid', + 'dark orange', 'dark sea green', 'pink', 'turquoise', 'dark blue', 'firebrick', + 'brown', 'forest green', 'gold', 'dark gold', 'gray', 'dark gray', + 'light green', 'lemon chiffon', 'coral', 'sea green', 'sky blue', 'magenta', + 'purple', 'slate blue', 'violet red', 'navy blue', 'olive', 'orange', 'red', + 'sienna', 'yellow']), comments=dict(type='str'), details_level=dict(type='str', choices=['uid', 'standard', 'full']), groups=dict(type='list'), ignore_warnings=dict(type='bool'), ignore_errors=dict(type='bool'), - uid=dict(type='str'), new_name=dict(type='str') ) argument_spec.update(checkpoint_argument_spec_for_objects) - module = AnsibleModule(argument_spec=argument_spec, required_one_of=[['name', 'uid']], - mutually_exclusive=[['name', 'uid']], - supports_check_mode=True) - + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) api_call_object = 'network' result = api_call(module, api_call_object) diff --git a/lib/ansible/modules/network/checkpoint/cp_network_facts.py b/lib/ansible/modules/network/checkpoint/cp_network_facts.py new file mode 100644 index 00000000000..81e9b51f3cf --- /dev/null +++ b/lib/ansible/modules/network/checkpoint/cp_network_facts.py @@ -0,0 +1,113 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Ansible module to manage CheckPoint Firewall (c) 2019 +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) + +__metaclass__ = type + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community'} + +DOCUMENTATION = """ +--- +module: cp_network_facts +short_description: Get network objects facts on Checkpoint over Web Services API +description: + - Get network objects facts on Checkpoint devices. + All operations are performed over Web Services API. + This module handles both operations, get a specific object and get several objects. + For getting a specific object use the parameter 'name'. +version_added: "2.9" +author: "Or Soffer (@chkp-orso)" +options: + name: + description: + - Object name. + type: str + details_level: + description: + - The level of detail for some of the fields in the response can vary from showing only the UID value of + the object to a fully detailed representation of the object. + type: str + choices: ['uid', 'standard', 'full'] + limit: + description: + - No more than that many results will be returned. + type: int + offset: + description: + - Skip that many results before beginning to return them. + type: int + order: + description: + - Sorts results by the given field. By default the results are sorted in the ascending order by name. + type: list + show_membership: + description: + - Indicates whether to calculate and show "groups" field for every object in reply. + type: bool +extends_documentation_fragment: checkpoint_facts +""" + +EXAMPLES = """ +- name: show-network + cp_network_facts: + name: New Network 1 + +- name: show-networks + cp_network_facts: + details_level: standard + limit: 50 + offset: 0 +""" + +RETURN = """ +ansible_facts: + description: The checkpoint object facts. + returned: always. + type: dict +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.network.checkpoint.checkpoint import checkpoint_argument_spec_for_facts, api_call_facts + + +def main(): + argument_spec = dict( + name=dict(type='str'), + details_level=dict(type='str', choices=['uid', 'standard', 'full']), + limit=dict(type='int'), + offset=dict(type='int'), + order=dict(type='list'), + show_membership=dict(type='bool') + ) + argument_spec.update(checkpoint_argument_spec_for_facts) + + module = AnsibleModule(argument_spec=argument_spec) + + api_call_object = "network" + api_call_object_plural_version = "networks" + + result = api_call_facts(module, api_call_object, api_call_object_plural_version) + module.exit_json(ansible_facts=result) + + +if __name__ == '__main__': + main() diff --git a/lib/ansible/plugins/doc_fragments/checkpoint_facts.py b/lib/ansible/plugins/doc_fragments/checkpoint_facts.py new file mode 100644 index 00000000000..90afb52a059 --- /dev/null +++ b/lib/ansible/plugins/doc_fragments/checkpoint_facts.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- + +# Copyright: (c) 2019, Or Soffer +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +class ModuleDocFragment(object): + + # Standard files documentation fragment + DOCUMENTATION = r''' +options: + version: + description: + - Version of checkpoint. If not given one, the latest version taken. + type: str +''' diff --git a/test/sanity/ignore.txt b/test/sanity/ignore.txt index 0b3d3161f2a..ca7184da84e 100644 --- a/test/sanity/ignore.txt +++ b/test/sanity/ignore.txt @@ -2866,7 +2866,6 @@ lib/ansible/modules/network/bigswitch/bigmon_policy.py validate-modules:E326 lib/ansible/modules/network/bigswitch/bigmon_policy.py validate-modules:E337 lib/ansible/modules/network/bigswitch/bigmon_policy.py validate-modules:E338 lib/ansible/modules/network/checkpoint/checkpoint_object_facts.py validate-modules:E337 -lib/ansible/modules/network/checkpoint/cp_network.py validate-modules:E337 lib/ansible/modules/network/cli/cli_command.py validate-modules:E337 lib/ansible/modules/network/cli/cli_config.py validate-modules:E337 lib/ansible/modules/network/cli/cli_config.py validate-modules:E338 diff --git a/test/units/modules/network/checkpoint/test_cp_network.py b/test/units/modules/network/checkpoint/test_cp_network.py index d53d918e492..1cd7c091ab3 100644 --- a/test/units/modules/network/checkpoint/test_cp_network.py +++ b/test/units/modules/network/checkpoint/test_cp_network.py @@ -25,20 +25,19 @@ from ansible.module_utils import basic from ansible.module_utils.network.checkpoint.checkpoint import api_call from ansible.modules.network.checkpoint import cp_network - -OBJECT = {'name': 'test_network', 'nat_settings': {'auto-rule': True, - 'hide-behind': 'ip-address', - 'ip-address': '192.168.1.111'}, +OBJECT = {'name': 'test_network', 'nat_settings': [{'auto_rule': True, + 'hide_behind': 'ip-address', + 'ip_address': '192.168.1.111'}], 'subnet': '192.0.2.1', 'subnet_mask': '255.255.255.0', 'state': 'present'} -CREATE_PAYLOAD = {'name': 'test_network', 'nat_settings': {'auto-rule': True, - 'hide-behind': 'ip-address', - 'ip-address': '192.168.1.111'}, +CREATE_PAYLOAD = {'name': 'test_network', 'nat_settings': [{'auto_rule': True, + 'hide_behind': 'ip-address', + 'ip_address': '192.168.1.111'}], 'subnet': '192.168.1.0', 'subnet_mask': '255.255.255.0', 'state': 'present'} -UPDATE_PAYLOAD = {'name': 'test_new_network', 'nat_settings': {'auto-rule': True, - 'hide-behind': 'ip-address', - 'ip-address': '192.168.1.111'}, +UPDATE_PAYLOAD = {'name': 'test_new_network', 'nat_settings': [{'auto_rule': True, + 'hide_behind': 'ip-address', + 'ip_address': '192.168.1.111'}], 'subnet': '192.168.1.0', 'subnet_mask': '255.255.255.0', 'state': 'present'} DELETE_PAYLOAD = {'name': 'test_new_network', 'state': 'absent'}