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
This commit is contained in:
chkp-orso 2019-08-21 09:53:17 +03:00 committed by Sumit Jaiswal
parent b75fc87a86
commit f1d568749f
6 changed files with 452 additions and 86 deletions

View file

@ -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),

View file

@ -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)

View file

@ -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 <http://www.gnu.org/licenses/>.
#
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = """
---
module: 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()

View file

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Or Soffer <orso@checkpoint.com>
# 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
'''

View file

@ -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

View file

@ -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'}