1#!/usr/local/bin/python3.8 2from __future__ import (absolute_import, division, print_function) 3# Copyright 2019-2020 Fortinet, Inc. 4# 5# This program is free software: you can redistribute it and/or modify 6# it under the terms of the GNU General Public License as published by 7# the Free Software Foundation, either version 3 of the License, or 8# (at your option) any later version. 9# 10# This program is distributed in the hope that it will be useful, 11# but WITHOUT ANY WARRANTY; without even the implied warranty of 12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13# GNU General Public License for more details. 14# 15# You should have received a copy of the GNU General Public License 16# along with this program. If not, see <https://www.gnu.org/licenses/>. 17 18__metaclass__ = type 19 20ANSIBLE_METADATA = {'status': ['preview'], 21 'supported_by': 'community', 22 'metadata_version': '1.1'} 23 24DOCUMENTATION = ''' 25--- 26module: fortios_wanopt_peer 27short_description: Configure WAN optimization peers in Fortinet's FortiOS and FortiGate. 28description: 29 - This module is able to configure a FortiGate or FortiOS (FOS) device by allowing the 30 user to set and modify wanopt feature and peer category. 31 Examples include all parameters and values need to be adjusted to datasources before usage. 32 Tested with FOS v6.0.0 33version_added: "2.10" 34author: 35 - Link Zheng (@chillancezen) 36 - Jie Xue (@JieX19) 37 - Hongbin Lu (@fgtdev-hblu) 38 - Frank Shen (@frankshen01) 39 - Miguel Angel Munoz (@mamunozgonzalez) 40 - Nicolas Thomas (@thomnico) 41notes: 42 - Legacy fortiosapi has been deprecated, httpapi is the preferred way to run playbooks 43 44requirements: 45 - ansible>=2.9.0 46options: 47 access_token: 48 description: 49 - Token-based authentication. 50 Generated from GUI of Fortigate. 51 type: str 52 required: false 53 enable_log: 54 description: 55 - Enable/Disable logging for task. 56 type: bool 57 required: false 58 default: false 59 vdom: 60 description: 61 - Virtual domain, among those defined previously. A vdom is a 62 virtual instance of the FortiGate that can be configured and 63 used as a different unit. 64 type: str 65 default: root 66 67 state: 68 description: 69 - Indicates whether to create or remove the object. 70 type: str 71 required: true 72 choices: 73 - present 74 - absent 75 wanopt_peer: 76 description: 77 - Configure WAN optimization peers. 78 default: null 79 type: dict 80 suboptions: 81 ip: 82 description: 83 - Peer IP address. 84 type: str 85 peer_host_id: 86 description: 87 - Peer host ID. 88 type: str 89''' 90 91EXAMPLES = ''' 92- hosts: fortigates 93 collections: 94 - fortinet.fortios 95 connection: httpapi 96 vars: 97 vdom: "root" 98 ansible_httpapi_use_ssl: yes 99 ansible_httpapi_validate_certs: no 100 ansible_httpapi_port: 443 101 tasks: 102 - name: Configure WAN optimization peers. 103 fortios_wanopt_peer: 104 vdom: "{{ vdom }}" 105 state: "present" 106 access_token: "<your_own_value>" 107 wanopt_peer: 108 ip: "<your_own_value>" 109 peer_host_id: "myhostname" 110 111''' 112 113RETURN = ''' 114build: 115 description: Build number of the fortigate image 116 returned: always 117 type: str 118 sample: '1547' 119http_method: 120 description: Last method used to provision the content into FortiGate 121 returned: always 122 type: str 123 sample: 'PUT' 124http_status: 125 description: Last result given by FortiGate on last operation applied 126 returned: always 127 type: str 128 sample: "200" 129mkey: 130 description: Master key (id) used in the last call to FortiGate 131 returned: success 132 type: str 133 sample: "id" 134name: 135 description: Name of the table used to fulfill the request 136 returned: always 137 type: str 138 sample: "urlfilter" 139path: 140 description: Path of the table used to fulfill the request 141 returned: always 142 type: str 143 sample: "webfilter" 144revision: 145 description: Internal revision number 146 returned: always 147 type: str 148 sample: "17.0.2.10658" 149serial: 150 description: Serial number of the unit 151 returned: always 152 type: str 153 sample: "FGVMEVYYQT3AB5352" 154status: 155 description: Indication of the operation's result 156 returned: always 157 type: str 158 sample: "success" 159vdom: 160 description: Virtual domain used 161 returned: always 162 type: str 163 sample: "root" 164version: 165 description: Version of the FortiGate 166 returned: always 167 type: str 168 sample: "v5.6.3" 169 170''' 171from ansible.module_utils.basic import AnsibleModule 172from ansible.module_utils.connection import Connection 173from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import FortiOSHandler 174from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import check_legacy_fortiosapi 175from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import schema_to_module_spec 176from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import check_schema_versioning 177from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG 178from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.comparison import is_same_comparison 179from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.comparison import serialize 180 181 182def filter_wanopt_peer_data(json): 183 option_list = ['ip', 'peer_host_id'] 184 dictionary = {} 185 186 for attribute in option_list: 187 if attribute in json and json[attribute] is not None: 188 dictionary[attribute] = json[attribute] 189 190 return dictionary 191 192 193def underscore_to_hyphen(data): 194 if isinstance(data, list): 195 for i, elem in enumerate(data): 196 data[i] = underscore_to_hyphen(elem) 197 elif isinstance(data, dict): 198 new_data = {} 199 for k, v in data.items(): 200 new_data[k.replace('_', '-')] = underscore_to_hyphen(v) 201 data = new_data 202 203 return data 204 205 206def wanopt_peer(data, fos, check_mode=False): 207 208 vdom = data['vdom'] 209 210 state = data['state'] 211 212 wanopt_peer_data = data['wanopt_peer'] 213 filtered_data = underscore_to_hyphen(filter_wanopt_peer_data(wanopt_peer_data)) 214 215 # check_mode starts from here 216 if check_mode: 217 mkey = fos.get_mkey('system', 'interface', filtered_data, vdom=vdom) 218 current_data = fos.get('system', 'interface', vdom=vdom, mkey=mkey) 219 is_existed = current_data and current_data.get('http_status') == 200 \ 220 and isinstance(current_data.get('results'), list) \ 221 and len(current_data['results']) > 0 222 223 # 2. if it exists and the state is 'present' then compare current settings with desired 224 if state == 'present' or state is True: 225 if mkey is None: 226 return False, True, filtered_data 227 228 # if mkey exists then compare each other 229 # record exits and they're matched or not 230 if is_existed: 231 is_same = is_same_comparison( 232 serialize(current_data['results'][0]), serialize(filtered_data)) 233 return False, not is_same, filtered_data 234 235 # record does not exist 236 return False, True, filtered_data 237 238 if state == 'absent': 239 if mkey is None: 240 return False, False, filtered_data 241 242 if is_existed: 243 return False, True, filtered_data 244 return False, False, filtered_data 245 246 return True, False, {'reason: ': 'Must provide state parameter'} 247 248 if state == "present" or state is True: 249 return fos.set('wanopt', 250 'peer', 251 data=filtered_data, 252 vdom=vdom) 253 254 elif state == "absent": 255 return fos.delete('wanopt', 256 'peer', 257 mkey=filtered_data['peer-host-id'], 258 vdom=vdom) 259 else: 260 fos._module.fail_json(msg='state must be present or absent!') 261 262 263def is_successful_status(status): 264 return status['status'] == "success" or \ 265 status['http_method'] == "DELETE" and status['http_status'] == 404 266 267 268def fortios_wanopt(data, fos, check_mode): 269 270 if data['wanopt_peer']: 271 resp = wanopt_peer(data, fos, check_mode) 272 else: 273 fos._module.fail_json(msg='missing task body: %s' % ('wanopt_peer')) 274 if check_mode: 275 return resp 276 return not is_successful_status(resp), \ 277 resp['status'] == "success" and \ 278 (resp['revision_changed'] if 'revision_changed' in resp else True), \ 279 resp 280 281 282versioned_schema = { 283 "type": "list", 284 "children": { 285 "ip": { 286 "type": "string", 287 "revisions": { 288 "v6.0.0": True, 289 "v7.0.0": True, 290 "v6.0.5": True, 291 "v6.4.4": True, 292 "v6.4.0": True, 293 "v6.4.1": True, 294 "v6.2.0": True, 295 "v6.2.3": True, 296 "v6.2.5": True, 297 "v6.2.7": True, 298 "v6.0.11": True 299 } 300 }, 301 "peer_host_id": { 302 "type": "string", 303 "revisions": { 304 "v6.0.0": True, 305 "v7.0.0": True, 306 "v6.0.5": True, 307 "v6.4.4": True, 308 "v6.4.0": True, 309 "v6.4.1": True, 310 "v6.2.0": True, 311 "v6.2.3": True, 312 "v6.2.5": True, 313 "v6.2.7": True, 314 "v6.0.11": True 315 } 316 } 317 }, 318 "revisions": { 319 "v6.0.0": True, 320 "v7.0.0": True, 321 "v6.0.5": True, 322 "v6.4.4": True, 323 "v6.4.0": True, 324 "v6.4.1": True, 325 "v6.2.0": True, 326 "v6.2.3": True, 327 "v6.2.5": True, 328 "v6.2.7": True, 329 "v6.0.11": True 330 } 331} 332 333 334def main(): 335 module_spec = schema_to_module_spec(versioned_schema) 336 mkeyname = 'peer-host-id' 337 fields = { 338 "access_token": {"required": False, "type": "str", "no_log": True}, 339 "enable_log": {"required": False, "type": bool}, 340 "vdom": {"required": False, "type": "str", "default": "root"}, 341 "state": {"required": True, "type": "str", 342 "choices": ["present", "absent"]}, 343 "wanopt_peer": { 344 "required": False, "type": "dict", "default": None, 345 "options": { 346 } 347 } 348 } 349 for attribute_name in module_spec['options']: 350 fields["wanopt_peer"]['options'][attribute_name] = module_spec['options'][attribute_name] 351 if mkeyname and mkeyname == attribute_name: 352 fields["wanopt_peer"]['options'][attribute_name]['required'] = True 353 354 check_legacy_fortiosapi() 355 module = AnsibleModule(argument_spec=fields, 356 supports_check_mode=True) 357 358 versions_check_result = None 359 if module._socket_path: 360 connection = Connection(module._socket_path) 361 if 'access_token' in module.params: 362 connection.set_option('access_token', module.params['access_token']) 363 364 if 'enable_log' in module.params: 365 connection.set_option('enable_log', module.params['enable_log']) 366 else: 367 connection.set_option('enable_log', False) 368 fos = FortiOSHandler(connection, module, mkeyname) 369 versions_check_result = check_schema_versioning(fos, versioned_schema, "wanopt_peer") 370 371 is_error, has_changed, result = fortios_wanopt(module.params, fos, module.check_mode) 372 373 else: 374 module.fail_json(**FAIL_SOCKET_MSG) 375 376 if versions_check_result and versions_check_result['matched'] is False: 377 module.warn("Ansible has detected version mismatch between FortOS system and your playbook, see more details by specifying option -vvv") 378 379 if not is_error: 380 if versions_check_result and versions_check_result['matched'] is False: 381 module.exit_json(changed=has_changed, version_check_warning=versions_check_result, meta=result) 382 else: 383 module.exit_json(changed=has_changed, meta=result) 384 else: 385 if versions_check_result and versions_check_result['matched'] is False: 386 module.fail_json(msg="Error in repo", version_check_warning=versions_check_result, meta=result) 387 else: 388 module.fail_json(msg="Error in repo", meta=result) 389 390 391if __name__ == '__main__': 392 main() 393