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_system_ipv6_tunnel
27short_description: Configure IPv6/IPv4 in IPv6 tunnel 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 system feature and ipv6_tunnel 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    system_ipv6_tunnel:
76        description:
77            - Configure IPv6/IPv4 in IPv6 tunnel.
78        default: null
79        type: dict
80        suboptions:
81            auto_asic_offload:
82                description:
83                    - Enable/disable tunnel ASIC offloading.
84                type: str
85                choices:
86                    - enable
87                    - disable
88            destination:
89                description:
90                    - Remote IPv6 address of the tunnel.
91                type: str
92            interface:
93                description:
94                    - Interface name. Source system.interface.name.
95                type: str
96            name:
97                description:
98                    - IPv6 tunnel name.
99                required: true
100                type: str
101            source:
102                description:
103                    - Local IPv6 address of the tunnel.
104                type: str
105            use_sdwan:
106                description:
107                    - Enable/disable use of SD-WAN to reach remote gateway.
108                type: str
109                choices:
110                    - disable
111                    - enable
112'''
113
114EXAMPLES = '''
115- hosts: fortigates
116  collections:
117    - fortinet.fortios
118  connection: httpapi
119  vars:
120   vdom: "root"
121   ansible_httpapi_use_ssl: yes
122   ansible_httpapi_validate_certs: no
123   ansible_httpapi_port: 443
124  tasks:
125  - name: Configure IPv6/IPv4 in IPv6 tunnel.
126    fortios_system_ipv6_tunnel:
127      vdom:  "{{ vdom }}"
128      state: "present"
129      access_token: "<your_own_value>"
130      system_ipv6_tunnel:
131        auto_asic_offload: "enable"
132        destination: "<your_own_value>"
133        interface: "<your_own_value> (source system.interface.name)"
134        name: "default_name_6"
135        source: "<your_own_value>"
136        use_sdwan: "disable"
137
138'''
139
140RETURN = '''
141build:
142  description: Build number of the fortigate image
143  returned: always
144  type: str
145  sample: '1547'
146http_method:
147  description: Last method used to provision the content into FortiGate
148  returned: always
149  type: str
150  sample: 'PUT'
151http_status:
152  description: Last result given by FortiGate on last operation applied
153  returned: always
154  type: str
155  sample: "200"
156mkey:
157  description: Master key (id) used in the last call to FortiGate
158  returned: success
159  type: str
160  sample: "id"
161name:
162  description: Name of the table used to fulfill the request
163  returned: always
164  type: str
165  sample: "urlfilter"
166path:
167  description: Path of the table used to fulfill the request
168  returned: always
169  type: str
170  sample: "webfilter"
171revision:
172  description: Internal revision number
173  returned: always
174  type: str
175  sample: "17.0.2.10658"
176serial:
177  description: Serial number of the unit
178  returned: always
179  type: str
180  sample: "FGVMEVYYQT3AB5352"
181status:
182  description: Indication of the operation's result
183  returned: always
184  type: str
185  sample: "success"
186vdom:
187  description: Virtual domain used
188  returned: always
189  type: str
190  sample: "root"
191version:
192  description: Version of the FortiGate
193  returned: always
194  type: str
195  sample: "v5.6.3"
196
197'''
198from ansible.module_utils.basic import AnsibleModule
199from ansible.module_utils.connection import Connection
200from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import FortiOSHandler
201from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import check_legacy_fortiosapi
202from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import schema_to_module_spec
203from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import check_schema_versioning
204from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG
205from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.comparison import is_same_comparison
206from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.comparison import serialize
207
208
209def filter_system_ipv6_tunnel_data(json):
210    option_list = ['auto_asic_offload', 'destination', 'interface',
211                   'name', 'source', 'use_sdwan']
212    dictionary = {}
213
214    for attribute in option_list:
215        if attribute in json and json[attribute] is not None:
216            dictionary[attribute] = json[attribute]
217
218    return dictionary
219
220
221def underscore_to_hyphen(data):
222    if isinstance(data, list):
223        for i, elem in enumerate(data):
224            data[i] = underscore_to_hyphen(elem)
225    elif isinstance(data, dict):
226        new_data = {}
227        for k, v in data.items():
228            new_data[k.replace('_', '-')] = underscore_to_hyphen(v)
229        data = new_data
230
231    return data
232
233
234def system_ipv6_tunnel(data, fos, check_mode=False):
235
236    vdom = data['vdom']
237
238    state = data['state']
239
240    system_ipv6_tunnel_data = data['system_ipv6_tunnel']
241    filtered_data = underscore_to_hyphen(filter_system_ipv6_tunnel_data(system_ipv6_tunnel_data))
242
243    # check_mode starts from here
244    if check_mode:
245        mkey = fos.get_mkey('system', 'interface', filtered_data, vdom=vdom)
246        current_data = fos.get('system', 'interface', vdom=vdom, mkey=mkey)
247        is_existed = current_data and current_data.get('http_status') == 200 \
248            and isinstance(current_data.get('results'), list) \
249            and len(current_data['results']) > 0
250
251        # 2. if it exists and the state is 'present' then compare current settings with desired
252        if state == 'present' or state is True:
253            if mkey is None:
254                return False, True, filtered_data
255
256            # if mkey exists then compare each other
257            # record exits and they're matched or not
258            if is_existed:
259                is_same = is_same_comparison(
260                    serialize(current_data['results'][0]), serialize(filtered_data))
261                return False, not is_same, filtered_data
262
263            # record does not exist
264            return False, True, filtered_data
265
266        if state == 'absent':
267            if mkey is None:
268                return False, False, filtered_data
269
270            if is_existed:
271                return False, True, filtered_data
272            return False, False, filtered_data
273
274        return True, False, {'reason: ': 'Must provide state parameter'}
275
276    if state == "present" or state is True:
277        return fos.set('system',
278                       'ipv6-tunnel',
279                       data=filtered_data,
280                       vdom=vdom)
281
282    elif state == "absent":
283        return fos.delete('system',
284                          'ipv6-tunnel',
285                          mkey=filtered_data['name'],
286                          vdom=vdom)
287    else:
288        fos._module.fail_json(msg='state must be present or absent!')
289
290
291def is_successful_status(status):
292    return status['status'] == "success" or \
293        status['http_method'] == "DELETE" and status['http_status'] == 404
294
295
296def fortios_system(data, fos, check_mode):
297
298    if data['system_ipv6_tunnel']:
299        resp = system_ipv6_tunnel(data, fos, check_mode)
300    else:
301        fos._module.fail_json(msg='missing task body: %s' % ('system_ipv6_tunnel'))
302    if check_mode:
303        return resp
304    return not is_successful_status(resp), \
305        resp['status'] == "success" and \
306        (resp['revision_changed'] if 'revision_changed' in resp else True), \
307        resp
308
309
310versioned_schema = {
311    "type": "list",
312    "children": {
313        "use_sdwan": {
314            "type": "string",
315            "options": [
316                {
317                    "value": "disable",
318                    "revisions": {
319                        "v7.0.0": True
320                    }
321                },
322                {
323                    "value": "enable",
324                    "revisions": {
325                        "v7.0.0": True
326                    }
327                }
328            ],
329            "revisions": {
330                "v7.0.0": True
331            }
332        },
333        "name": {
334            "type": "string",
335            "revisions": {
336                "v6.0.0": True,
337                "v7.0.0": True,
338                "v6.0.5": True,
339                "v6.4.4": True,
340                "v6.4.0": True,
341                "v6.4.1": True,
342                "v6.2.0": True,
343                "v6.2.3": True,
344                "v6.2.5": True,
345                "v6.2.7": True,
346                "v6.0.11": True
347            }
348        },
349        "destination": {
350            "type": "string",
351            "revisions": {
352                "v6.0.0": True,
353                "v7.0.0": True,
354                "v6.0.5": True,
355                "v6.4.4": True,
356                "v6.4.0": True,
357                "v6.4.1": True,
358                "v6.2.0": True,
359                "v6.2.3": True,
360                "v6.2.5": True,
361                "v6.2.7": True,
362                "v6.0.11": True
363            }
364        },
365        "auto_asic_offload": {
366            "type": "string",
367            "options": [
368                {
369                    "value": "enable",
370                    "revisions": {
371                        "v6.0.0": True,
372                        "v7.0.0": True,
373                        "v6.0.5": True,
374                        "v6.4.4": True,
375                        "v6.4.0": True,
376                        "v6.4.1": True,
377                        "v6.2.0": True,
378                        "v6.2.3": True,
379                        "v6.2.5": True,
380                        "v6.2.7": True,
381                        "v6.0.11": True
382                    }
383                },
384                {
385                    "value": "disable",
386                    "revisions": {
387                        "v6.0.0": True,
388                        "v7.0.0": True,
389                        "v6.0.5": True,
390                        "v6.4.4": True,
391                        "v6.4.0": True,
392                        "v6.4.1": True,
393                        "v6.2.0": True,
394                        "v6.2.3": True,
395                        "v6.2.5": True,
396                        "v6.2.7": True,
397                        "v6.0.11": True
398                    }
399                }
400            ],
401            "revisions": {
402                "v6.0.0": True,
403                "v7.0.0": False,
404                "v6.0.5": True,
405                "v6.4.4": True,
406                "v6.4.0": True,
407                "v6.4.1": True,
408                "v6.2.0": True,
409                "v6.2.3": True,
410                "v6.2.5": True,
411                "v6.2.7": True,
412                "v6.0.11": True
413            }
414        },
415        "source": {
416            "type": "string",
417            "revisions": {
418                "v6.0.0": True,
419                "v7.0.0": True,
420                "v6.0.5": True,
421                "v6.4.4": True,
422                "v6.4.0": True,
423                "v6.4.1": True,
424                "v6.2.0": True,
425                "v6.2.3": True,
426                "v6.2.5": True,
427                "v6.2.7": True,
428                "v6.0.11": True
429            }
430        },
431        "interface": {
432            "type": "string",
433            "revisions": {
434                "v6.0.0": True,
435                "v7.0.0": True,
436                "v6.0.5": True,
437                "v6.4.4": True,
438                "v6.4.0": True,
439                "v6.4.1": True,
440                "v6.2.0": True,
441                "v6.2.3": True,
442                "v6.2.5": True,
443                "v6.2.7": True,
444                "v6.0.11": True
445            }
446        }
447    },
448    "revisions": {
449        "v6.0.0": True,
450        "v7.0.0": True,
451        "v6.0.5": True,
452        "v6.4.4": True,
453        "v6.4.0": True,
454        "v6.4.1": True,
455        "v6.2.0": True,
456        "v6.2.3": True,
457        "v6.2.5": True,
458        "v6.2.7": True,
459        "v6.0.11": True
460    }
461}
462
463
464def main():
465    module_spec = schema_to_module_spec(versioned_schema)
466    mkeyname = 'name'
467    fields = {
468        "access_token": {"required": False, "type": "str", "no_log": True},
469        "enable_log": {"required": False, "type": bool},
470        "vdom": {"required": False, "type": "str", "default": "root"},
471        "state": {"required": True, "type": "str",
472                  "choices": ["present", "absent"]},
473        "system_ipv6_tunnel": {
474            "required": False, "type": "dict", "default": None,
475            "options": {
476            }
477        }
478    }
479    for attribute_name in module_spec['options']:
480        fields["system_ipv6_tunnel"]['options'][attribute_name] = module_spec['options'][attribute_name]
481        if mkeyname and mkeyname == attribute_name:
482            fields["system_ipv6_tunnel"]['options'][attribute_name]['required'] = True
483
484    check_legacy_fortiosapi()
485    module = AnsibleModule(argument_spec=fields,
486                           supports_check_mode=True)
487
488    versions_check_result = None
489    if module._socket_path:
490        connection = Connection(module._socket_path)
491        if 'access_token' in module.params:
492            connection.set_option('access_token', module.params['access_token'])
493
494        if 'enable_log' in module.params:
495            connection.set_option('enable_log', module.params['enable_log'])
496        else:
497            connection.set_option('enable_log', False)
498        fos = FortiOSHandler(connection, module, mkeyname)
499        versions_check_result = check_schema_versioning(fos, versioned_schema, "system_ipv6_tunnel")
500
501        is_error, has_changed, result = fortios_system(module.params, fos, module.check_mode)
502
503    else:
504        module.fail_json(**FAIL_SOCKET_MSG)
505
506    if versions_check_result and versions_check_result['matched'] is False:
507        module.warn("Ansible has detected version mismatch between FortOS system and your playbook, see more details by specifying option -vvv")
508
509    if not is_error:
510        if versions_check_result and versions_check_result['matched'] is False:
511            module.exit_json(changed=has_changed, version_check_warning=versions_check_result, meta=result)
512        else:
513            module.exit_json(changed=has_changed, meta=result)
514    else:
515        if versions_check_result and versions_check_result['matched'] is False:
516            module.fail_json(msg="Error in repo", version_check_warning=versions_check_result, meta=result)
517        else:
518            module.fail_json(msg="Error in repo", meta=result)
519
520
521if __name__ == '__main__':
522    main()
523