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_router_static6
27short_description: Configure IPv6 static routing tables 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 router feature and static6 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    router_static6:
76        description:
77            - Configure IPv6 static routing tables.
78        default: null
79        type: dict
80        suboptions:
81            bfd:
82                description:
83                    - Enable/disable Bidirectional Forwarding Detection (BFD).
84                type: str
85                choices:
86                    - enable
87                    - disable
88            blackhole:
89                description:
90                    - Enable/disable black hole.
91                type: str
92                choices:
93                    - enable
94                    - disable
95            comment:
96                description:
97                    - Optional comments.
98                type: str
99            device:
100                description:
101                    - Gateway out interface or tunnel. Source system.interface.name.
102                type: str
103            devindex:
104                description:
105                    - Device index (0 - 4294967295).
106                type: int
107            distance:
108                description:
109                    - Administrative distance (1 - 255).
110                type: int
111            dst:
112                description:
113                    - Destination IPv6 prefix.
114                type: str
115            dynamic_gateway:
116                description:
117                    - Enable use of dynamic gateway retrieved from Router Advertisement (RA).
118                type: str
119                choices:
120                    - enable
121                    - disable
122            gateway:
123                description:
124                    - IPv6 address of the gateway.
125                type: str
126            link_monitor_exempt:
127                description:
128                    - Enable/disable withdrawal of this static route when link monitor or health check is down.
129                type: str
130                choices:
131                    - enable
132                    - disable
133            priority:
134                description:
135                    - Administrative priority (0 - 4294967295).
136                type: int
137            sdwan:
138                description:
139                    - Enable/disable egress through the SD-WAN.
140                type: str
141                choices:
142                    - enable
143                    - disable
144            seq_num:
145                description:
146                    - Sequence number.
147                type: int
148            status:
149                description:
150                    - Enable/disable this static route.
151                type: str
152                choices:
153                    - enable
154                    - disable
155            virtual_wan_link:
156                description:
157                    - Enable/disable egress through the virtual-wan-link.
158                type: str
159                choices:
160                    - enable
161                    - disable
162'''
163
164EXAMPLES = '''
165- hosts: fortigates
166  collections:
167    - fortinet.fortios
168  connection: httpapi
169  vars:
170   vdom: "root"
171   ansible_httpapi_use_ssl: yes
172   ansible_httpapi_validate_certs: no
173   ansible_httpapi_port: 443
174  tasks:
175  - name: Configure IPv6 static routing tables.
176    fortios_router_static6:
177      vdom:  "{{ vdom }}"
178      state: "present"
179      access_token: "<your_own_value>"
180      router_static6:
181        bfd: "enable"
182        blackhole: "enable"
183        comment: "Optional comments."
184        device: "<your_own_value> (source system.interface.name)"
185        devindex: "7"
186        distance: "8"
187        dst: "<your_own_value>"
188        dynamic_gateway: "enable"
189        gateway: "<your_own_value>"
190        link_monitor_exempt: "enable"
191        priority: "13"
192        sdwan: "enable"
193        seq_num: "15"
194        status: "enable"
195        virtual_wan_link: "enable"
196
197'''
198
199RETURN = '''
200build:
201  description: Build number of the fortigate image
202  returned: always
203  type: str
204  sample: '1547'
205http_method:
206  description: Last method used to provision the content into FortiGate
207  returned: always
208  type: str
209  sample: 'PUT'
210http_status:
211  description: Last result given by FortiGate on last operation applied
212  returned: always
213  type: str
214  sample: "200"
215mkey:
216  description: Master key (id) used in the last call to FortiGate
217  returned: success
218  type: str
219  sample: "id"
220name:
221  description: Name of the table used to fulfill the request
222  returned: always
223  type: str
224  sample: "urlfilter"
225path:
226  description: Path of the table used to fulfill the request
227  returned: always
228  type: str
229  sample: "webfilter"
230revision:
231  description: Internal revision number
232  returned: always
233  type: str
234  sample: "17.0.2.10658"
235serial:
236  description: Serial number of the unit
237  returned: always
238  type: str
239  sample: "FGVMEVYYQT3AB5352"
240status:
241  description: Indication of the operation's result
242  returned: always
243  type: str
244  sample: "success"
245vdom:
246  description: Virtual domain used
247  returned: always
248  type: str
249  sample: "root"
250version:
251  description: Version of the FortiGate
252  returned: always
253  type: str
254  sample: "v5.6.3"
255
256'''
257from ansible.module_utils.basic import AnsibleModule
258from ansible.module_utils.connection import Connection
259from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import FortiOSHandler
260from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import check_legacy_fortiosapi
261from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import schema_to_module_spec
262from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import check_schema_versioning
263from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG
264from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.comparison import is_same_comparison
265from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.comparison import serialize
266
267
268def filter_router_static6_data(json):
269    option_list = ['bfd', 'blackhole', 'comment',
270                   'device', 'devindex', 'distance',
271                   'dst', 'dynamic_gateway', 'gateway',
272                   'link_monitor_exempt', 'priority', 'sdwan',
273                   'seq_num', 'status', 'virtual_wan_link']
274    dictionary = {}
275
276    for attribute in option_list:
277        if attribute in json and json[attribute] is not None:
278            dictionary[attribute] = json[attribute]
279
280    return dictionary
281
282
283def underscore_to_hyphen(data):
284    if isinstance(data, list):
285        for i, elem in enumerate(data):
286            data[i] = underscore_to_hyphen(elem)
287    elif isinstance(data, dict):
288        new_data = {}
289        for k, v in data.items():
290            new_data[k.replace('_', '-')] = underscore_to_hyphen(v)
291        data = new_data
292
293    return data
294
295
296def router_static6(data, fos, check_mode=False):
297
298    vdom = data['vdom']
299
300    state = data['state']
301
302    router_static6_data = data['router_static6']
303    filtered_data = underscore_to_hyphen(filter_router_static6_data(router_static6_data))
304
305    # check_mode starts from here
306    if check_mode:
307        mkey = fos.get_mkey('system', 'interface', filtered_data, vdom=vdom)
308        current_data = fos.get('system', 'interface', vdom=vdom, mkey=mkey)
309        is_existed = current_data and current_data.get('http_status') == 200 \
310            and isinstance(current_data.get('results'), list) \
311            and len(current_data['results']) > 0
312
313        # 2. if it exists and the state is 'present' then compare current settings with desired
314        if state == 'present' or state is True:
315            if mkey is None:
316                return False, True, filtered_data
317
318            # if mkey exists then compare each other
319            # record exits and they're matched or not
320            if is_existed:
321                is_same = is_same_comparison(
322                    serialize(current_data['results'][0]), serialize(filtered_data))
323                return False, not is_same, filtered_data
324
325            # record does not exist
326            return False, True, filtered_data
327
328        if state == 'absent':
329            if mkey is None:
330                return False, False, filtered_data
331
332            if is_existed:
333                return False, True, filtered_data
334            return False, False, filtered_data
335
336        return True, False, {'reason: ': 'Must provide state parameter'}
337
338    if state == "present" or state is True:
339        return fos.set('router',
340                       'static6',
341                       data=filtered_data,
342                       vdom=vdom)
343
344    elif state == "absent":
345        return fos.delete('router',
346                          'static6',
347                          mkey=filtered_data['seq-num'],
348                          vdom=vdom)
349    else:
350        fos._module.fail_json(msg='state must be present or absent!')
351
352
353def is_successful_status(status):
354    return status['status'] == "success" or \
355        status['http_method'] == "DELETE" and status['http_status'] == 404
356
357
358def fortios_router(data, fos, check_mode):
359
360    if data['router_static6']:
361        resp = router_static6(data, fos, check_mode)
362    else:
363        fos._module.fail_json(msg='missing task body: %s' % ('router_static6'))
364    if check_mode:
365        return resp
366    return not is_successful_status(resp), \
367        resp['status'] == "success" and \
368        (resp['revision_changed'] if 'revision_changed' in resp else True), \
369        resp
370
371
372versioned_schema = {
373    "type": "list",
374    "children": {
375        "status": {
376            "type": "string",
377            "options": [
378                {
379                    "value": "enable",
380                    "revisions": {
381                        "v6.0.0": True,
382                        "v7.0.0": True,
383                        "v6.0.5": True,
384                        "v6.4.4": True,
385                        "v6.4.0": True,
386                        "v6.4.1": True,
387                        "v6.2.0": True,
388                        "v6.2.3": True,
389                        "v6.2.5": True,
390                        "v6.2.7": True,
391                        "v6.0.11": True
392                    }
393                },
394                {
395                    "value": "disable",
396                    "revisions": {
397                        "v6.0.0": True,
398                        "v7.0.0": True,
399                        "v6.0.5": True,
400                        "v6.4.4": True,
401                        "v6.4.0": True,
402                        "v6.4.1": True,
403                        "v6.2.0": True,
404                        "v6.2.3": True,
405                        "v6.2.5": True,
406                        "v6.2.7": True,
407                        "v6.0.11": True
408                    }
409                }
410            ],
411            "revisions": {
412                "v6.0.0": True,
413                "v7.0.0": True,
414                "v6.0.5": True,
415                "v6.4.4": True,
416                "v6.4.0": True,
417                "v6.4.1": True,
418                "v6.2.0": True,
419                "v6.2.3": True,
420                "v6.2.5": True,
421                "v6.2.7": True,
422                "v6.0.11": True
423            }
424        },
425        "distance": {
426            "type": "integer",
427            "revisions": {
428                "v6.0.0": True,
429                "v7.0.0": True,
430                "v6.0.5": True,
431                "v6.4.4": True,
432                "v6.4.0": True,
433                "v6.4.1": True,
434                "v6.2.0": True,
435                "v6.2.3": True,
436                "v6.2.5": True,
437                "v6.2.7": True,
438                "v6.0.11": True
439            }
440        },
441        "dynamic_gateway": {
442            "type": "string",
443            "options": [
444                {
445                    "value": "enable",
446                    "revisions": {
447                        "v7.0.0": True
448                    }
449                },
450                {
451                    "value": "disable",
452                    "revisions": {
453                        "v7.0.0": True
454                    }
455                }
456            ],
457            "revisions": {
458                "v7.0.0": True
459            }
460        },
461        "devindex": {
462            "type": "integer",
463            "revisions": {
464                "v6.0.0": True,
465                "v7.0.0": True,
466                "v6.0.5": True,
467                "v6.4.4": True,
468                "v6.4.0": True,
469                "v6.4.1": True,
470                "v6.2.0": True,
471                "v6.2.3": True,
472                "v6.2.5": True,
473                "v6.2.7": True,
474                "v6.0.11": True
475            }
476        },
477        "sdwan": {
478            "type": "string",
479            "options": [
480                {
481                    "value": "enable",
482                    "revisions": {
483                        "v6.4.4": True,
484                        "v7.0.0": True,
485                        "v6.4.0": True,
486                        "v6.4.1": True
487                    }
488                },
489                {
490                    "value": "disable",
491                    "revisions": {
492                        "v6.4.4": True,
493                        "v7.0.0": True,
494                        "v6.4.0": True,
495                        "v6.4.1": True
496                    }
497                }
498            ],
499            "revisions": {
500                "v6.4.4": True,
501                "v7.0.0": True,
502                "v6.4.0": True,
503                "v6.4.1": True
504            }
505        },
506        "dst": {
507            "type": "string",
508            "revisions": {
509                "v6.0.0": True,
510                "v7.0.0": True,
511                "v6.0.5": True,
512                "v6.4.4": True,
513                "v6.4.0": True,
514                "v6.4.1": True,
515                "v6.2.0": True,
516                "v6.2.3": True,
517                "v6.2.5": True,
518                "v6.2.7": True,
519                "v6.0.11": True
520            }
521        },
522        "bfd": {
523            "type": "string",
524            "options": [
525                {
526                    "value": "enable",
527                    "revisions": {
528                        "v6.0.0": True,
529                        "v7.0.0": True,
530                        "v6.0.5": True,
531                        "v6.4.4": True,
532                        "v6.4.0": True,
533                        "v6.4.1": True,
534                        "v6.2.0": True,
535                        "v6.2.3": True,
536                        "v6.2.5": True,
537                        "v6.2.7": True,
538                        "v6.0.11": True
539                    }
540                },
541                {
542                    "value": "disable",
543                    "revisions": {
544                        "v6.0.0": True,
545                        "v7.0.0": True,
546                        "v6.0.5": True,
547                        "v6.4.4": True,
548                        "v6.4.0": True,
549                        "v6.4.1": True,
550                        "v6.2.0": True,
551                        "v6.2.3": True,
552                        "v6.2.5": True,
553                        "v6.2.7": True,
554                        "v6.0.11": True
555                    }
556                }
557            ],
558            "revisions": {
559                "v6.0.0": True,
560                "v7.0.0": True,
561                "v6.0.5": True,
562                "v6.4.4": True,
563                "v6.4.0": True,
564                "v6.4.1": True,
565                "v6.2.0": True,
566                "v6.2.3": True,
567                "v6.2.5": True,
568                "v6.2.7": True,
569                "v6.0.11": True
570            }
571        },
572        "seq_num": {
573            "type": "integer",
574            "revisions": {
575                "v6.0.0": True,
576                "v7.0.0": True,
577                "v6.0.5": True,
578                "v6.4.4": True,
579                "v6.4.0": True,
580                "v6.4.1": True,
581                "v6.2.0": True,
582                "v6.2.3": True,
583                "v6.2.5": True,
584                "v6.2.7": True,
585                "v6.0.11": True
586            }
587        },
588        "priority": {
589            "type": "integer",
590            "revisions": {
591                "v6.0.0": True,
592                "v7.0.0": True,
593                "v6.0.5": True,
594                "v6.4.4": True,
595                "v6.4.0": True,
596                "v6.4.1": True,
597                "v6.2.0": True,
598                "v6.2.3": True,
599                "v6.2.5": True,
600                "v6.2.7": True,
601                "v6.0.11": True
602            }
603        },
604        "virtual_wan_link": {
605            "type": "string",
606            "options": [
607                {
608                    "value": "enable",
609                    "revisions": {
610                        "v6.0.0": True,
611                        "v6.0.5": True,
612                        "v6.2.0": True,
613                        "v6.2.3": True,
614                        "v6.2.5": True,
615                        "v6.2.7": True,
616                        "v6.0.11": True
617                    }
618                },
619                {
620                    "value": "disable",
621                    "revisions": {
622                        "v6.0.0": True,
623                        "v6.0.5": True,
624                        "v6.2.0": True,
625                        "v6.2.3": True,
626                        "v6.2.5": True,
627                        "v6.2.7": True,
628                        "v6.0.11": True
629                    }
630                }
631            ],
632            "revisions": {
633                "v6.0.0": True,
634                "v7.0.0": False,
635                "v6.0.5": True,
636                "v6.4.4": False,
637                "v6.4.0": False,
638                "v6.4.1": False,
639                "v6.2.0": True,
640                "v6.2.3": True,
641                "v6.2.5": True,
642                "v6.2.7": True,
643                "v6.0.11": True
644            }
645        },
646        "blackhole": {
647            "type": "string",
648            "options": [
649                {
650                    "value": "enable",
651                    "revisions": {
652                        "v6.0.0": True,
653                        "v7.0.0": True,
654                        "v6.0.5": True,
655                        "v6.4.4": True,
656                        "v6.4.0": True,
657                        "v6.4.1": True,
658                        "v6.2.0": True,
659                        "v6.2.3": True,
660                        "v6.2.5": True,
661                        "v6.2.7": True,
662                        "v6.0.11": True
663                    }
664                },
665                {
666                    "value": "disable",
667                    "revisions": {
668                        "v6.0.0": True,
669                        "v7.0.0": True,
670                        "v6.0.5": True,
671                        "v6.4.4": True,
672                        "v6.4.0": True,
673                        "v6.4.1": True,
674                        "v6.2.0": True,
675                        "v6.2.3": True,
676                        "v6.2.5": True,
677                        "v6.2.7": True,
678                        "v6.0.11": True
679                    }
680                }
681            ],
682            "revisions": {
683                "v6.0.0": True,
684                "v7.0.0": True,
685                "v6.0.5": True,
686                "v6.4.4": True,
687                "v6.4.0": True,
688                "v6.4.1": True,
689                "v6.2.0": True,
690                "v6.2.3": True,
691                "v6.2.5": True,
692                "v6.2.7": True,
693                "v6.0.11": True
694            }
695        },
696        "device": {
697            "type": "string",
698            "revisions": {
699                "v6.0.0": True,
700                "v7.0.0": True,
701                "v6.0.5": True,
702                "v6.4.4": True,
703                "v6.4.0": True,
704                "v6.4.1": True,
705                "v6.2.0": True,
706                "v6.2.3": True,
707                "v6.2.5": True,
708                "v6.2.7": True,
709                "v6.0.11": True
710            }
711        },
712        "link_monitor_exempt": {
713            "type": "string",
714            "options": [
715                {
716                    "value": "enable",
717                    "revisions": {
718                        "v7.0.0": True,
719                        "v6.4.4": True,
720                        "v6.4.0": True,
721                        "v6.4.1": True,
722                        "v6.2.0": True,
723                        "v6.2.3": True,
724                        "v6.2.5": True,
725                        "v6.2.7": True
726                    }
727                },
728                {
729                    "value": "disable",
730                    "revisions": {
731                        "v7.0.0": True,
732                        "v6.4.4": True,
733                        "v6.4.0": True,
734                        "v6.4.1": True,
735                        "v6.2.0": True,
736                        "v6.2.3": True,
737                        "v6.2.5": True,
738                        "v6.2.7": True
739                    }
740                }
741            ],
742            "revisions": {
743                "v7.0.0": True,
744                "v6.4.4": True,
745                "v6.4.0": True,
746                "v6.4.1": True,
747                "v6.2.0": True,
748                "v6.2.3": True,
749                "v6.2.5": True,
750                "v6.2.7": True
751            }
752        },
753        "gateway": {
754            "type": "string",
755            "revisions": {
756                "v6.0.0": True,
757                "v7.0.0": True,
758                "v6.0.5": True,
759                "v6.4.4": True,
760                "v6.4.0": True,
761                "v6.4.1": True,
762                "v6.2.0": True,
763                "v6.2.3": True,
764                "v6.2.5": True,
765                "v6.2.7": True,
766                "v6.0.11": True
767            }
768        },
769        "comment": {
770            "type": "string",
771            "revisions": {
772                "v6.0.0": True,
773                "v7.0.0": True,
774                "v6.0.5": True,
775                "v6.4.4": True,
776                "v6.4.0": True,
777                "v6.4.1": True,
778                "v6.2.0": True,
779                "v6.2.3": True,
780                "v6.2.5": True,
781                "v6.2.7": True,
782                "v6.0.11": True
783            }
784        }
785    },
786    "revisions": {
787        "v6.0.0": True,
788        "v7.0.0": True,
789        "v6.0.5": True,
790        "v6.4.4": True,
791        "v6.4.0": True,
792        "v6.4.1": True,
793        "v6.2.0": True,
794        "v6.2.3": True,
795        "v6.2.5": True,
796        "v6.2.7": True,
797        "v6.0.11": True
798    }
799}
800
801
802def main():
803    module_spec = schema_to_module_spec(versioned_schema)
804    mkeyname = 'seq-num'
805    fields = {
806        "access_token": {"required": False, "type": "str", "no_log": True},
807        "enable_log": {"required": False, "type": bool},
808        "vdom": {"required": False, "type": "str", "default": "root"},
809        "state": {"required": True, "type": "str",
810                  "choices": ["present", "absent"]},
811        "router_static6": {
812            "required": False, "type": "dict", "default": None,
813            "options": {
814            }
815        }
816    }
817    for attribute_name in module_spec['options']:
818        fields["router_static6"]['options'][attribute_name] = module_spec['options'][attribute_name]
819        if mkeyname and mkeyname == attribute_name:
820            fields["router_static6"]['options'][attribute_name]['required'] = True
821
822    check_legacy_fortiosapi()
823    module = AnsibleModule(argument_spec=fields,
824                           supports_check_mode=True)
825
826    versions_check_result = None
827    if module._socket_path:
828        connection = Connection(module._socket_path)
829        if 'access_token' in module.params:
830            connection.set_option('access_token', module.params['access_token'])
831
832        if 'enable_log' in module.params:
833            connection.set_option('enable_log', module.params['enable_log'])
834        else:
835            connection.set_option('enable_log', False)
836        fos = FortiOSHandler(connection, module, mkeyname)
837        versions_check_result = check_schema_versioning(fos, versioned_schema, "router_static6")
838
839        is_error, has_changed, result = fortios_router(module.params, fos, module.check_mode)
840
841    else:
842        module.fail_json(**FAIL_SOCKET_MSG)
843
844    if versions_check_result and versions_check_result['matched'] is False:
845        module.warn("Ansible has detected version mismatch between FortOS system and your playbook, see more details by specifying option -vvv")
846
847    if not is_error:
848        if versions_check_result and versions_check_result['matched'] is False:
849            module.exit_json(changed=has_changed, version_check_warning=versions_check_result, meta=result)
850        else:
851            module.exit_json(changed=has_changed, meta=result)
852    else:
853        if versions_check_result and versions_check_result['matched'] is False:
854            module.fail_json(msg="Error in repo", version_check_warning=versions_check_result, meta=result)
855        else:
856            module.fail_json(msg="Error in repo", meta=result)
857
858
859if __name__ == '__main__':
860    main()
861