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_physical_switch
27short_description: Configure physical switches 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 physical_switch 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_physical_switch:
76        description:
77            - Configure physical switches.
78        default: null
79        type: dict
80        suboptions:
81            age_enable:
82                description:
83                    - Enable/disable layer 2 age timer.
84                type: str
85                choices:
86                    - enable
87                    - disable
88            age_val:
89                description:
90                    - Layer 2 table age timer Value.
91                type: int
92            name:
93                description:
94                    - Name.
95                required: true
96                type: str
97            port:
98                description:
99                    - Configure member ports.
100                type: list
101                suboptions:
102                    name:
103                        description:
104                            - Physical port name.
105                        required: true
106                        type: str
107                    speed:
108                        description:
109                            - Speed.
110                        type: str
111                        choices:
112                            - auto
113                            - 10full
114                            - 10half
115                            - 100full
116                            - 100half
117                            - 1000full
118                            - 1000half
119                            - 1000auto
120                            - 10000full
121                            - 10000auto
122                            - 40000full
123                            - 100Gfull
124                    status:
125                        description:
126                            - Interface status.
127                        type: str
128                        choices:
129                            - up
130                            - down
131'''
132
133EXAMPLES = '''
134- hosts: fortigates
135  collections:
136    - fortinet.fortios
137  connection: httpapi
138  vars:
139   vdom: "root"
140   ansible_httpapi_use_ssl: yes
141   ansible_httpapi_validate_certs: no
142   ansible_httpapi_port: 443
143  tasks:
144  - name: Configure physical switches.
145    fortios_system_physical_switch:
146      vdom:  "{{ vdom }}"
147      state: "present"
148      access_token: "<your_own_value>"
149      system_physical_switch:
150        age_enable: "enable"
151        age_val: "4"
152        name: "default_name_5"
153        port:
154         -
155            name: "default_name_7"
156            speed: "auto"
157            status: "up"
158
159'''
160
161RETURN = '''
162build:
163  description: Build number of the fortigate image
164  returned: always
165  type: str
166  sample: '1547'
167http_method:
168  description: Last method used to provision the content into FortiGate
169  returned: always
170  type: str
171  sample: 'PUT'
172http_status:
173  description: Last result given by FortiGate on last operation applied
174  returned: always
175  type: str
176  sample: "200"
177mkey:
178  description: Master key (id) used in the last call to FortiGate
179  returned: success
180  type: str
181  sample: "id"
182name:
183  description: Name of the table used to fulfill the request
184  returned: always
185  type: str
186  sample: "urlfilter"
187path:
188  description: Path of the table used to fulfill the request
189  returned: always
190  type: str
191  sample: "webfilter"
192revision:
193  description: Internal revision number
194  returned: always
195  type: str
196  sample: "17.0.2.10658"
197serial:
198  description: Serial number of the unit
199  returned: always
200  type: str
201  sample: "FGVMEVYYQT3AB5352"
202status:
203  description: Indication of the operation's result
204  returned: always
205  type: str
206  sample: "success"
207vdom:
208  description: Virtual domain used
209  returned: always
210  type: str
211  sample: "root"
212version:
213  description: Version of the FortiGate
214  returned: always
215  type: str
216  sample: "v5.6.3"
217
218'''
219from ansible.module_utils.basic import AnsibleModule
220from ansible.module_utils.connection import Connection
221from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import FortiOSHandler
222from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import check_legacy_fortiosapi
223from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import schema_to_module_spec
224from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import check_schema_versioning
225from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG
226from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.comparison import is_same_comparison
227from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.comparison import serialize
228
229
230def filter_system_physical_switch_data(json):
231    option_list = ['age_enable', 'age_val', 'name',
232                   'port']
233    dictionary = {}
234
235    for attribute in option_list:
236        if attribute in json and json[attribute] is not None:
237            dictionary[attribute] = json[attribute]
238
239    return dictionary
240
241
242def underscore_to_hyphen(data):
243    if isinstance(data, list):
244        for i, elem in enumerate(data):
245            data[i] = underscore_to_hyphen(elem)
246    elif isinstance(data, dict):
247        new_data = {}
248        for k, v in data.items():
249            new_data[k.replace('_', '-')] = underscore_to_hyphen(v)
250        data = new_data
251
252    return data
253
254
255def system_physical_switch(data, fos, check_mode=False):
256
257    vdom = data['vdom']
258
259    state = data['state']
260
261    system_physical_switch_data = data['system_physical_switch']
262    filtered_data = underscore_to_hyphen(filter_system_physical_switch_data(system_physical_switch_data))
263
264    # check_mode starts from here
265    if check_mode:
266        mkey = fos.get_mkey('system', 'interface', filtered_data, vdom=vdom)
267        current_data = fos.get('system', 'interface', vdom=vdom, mkey=mkey)
268        is_existed = current_data and current_data.get('http_status') == 200 \
269            and isinstance(current_data.get('results'), list) \
270            and len(current_data['results']) > 0
271
272        # 2. if it exists and the state is 'present' then compare current settings with desired
273        if state == 'present' or state is True:
274            if mkey is None:
275                return False, True, filtered_data
276
277            # if mkey exists then compare each other
278            # record exits and they're matched or not
279            if is_existed:
280                is_same = is_same_comparison(
281                    serialize(current_data['results'][0]), serialize(filtered_data))
282                return False, not is_same, filtered_data
283
284            # record does not exist
285            return False, True, filtered_data
286
287        if state == 'absent':
288            if mkey is None:
289                return False, False, filtered_data
290
291            if is_existed:
292                return False, True, filtered_data
293            return False, False, filtered_data
294
295        return True, False, {'reason: ': 'Must provide state parameter'}
296
297    if state == "present" or state is True:
298        return fos.set('system',
299                       'physical-switch',
300                       data=filtered_data,
301                       vdom=vdom)
302
303    elif state == "absent":
304        return fos.delete('system',
305                          'physical-switch',
306                          mkey=filtered_data['name'],
307                          vdom=vdom)
308    else:
309        fos._module.fail_json(msg='state must be present or absent!')
310
311
312def is_successful_status(status):
313    return status['status'] == "success" or \
314        status['http_method'] == "DELETE" and status['http_status'] == 404
315
316
317def fortios_system(data, fos, check_mode):
318
319    if data['system_physical_switch']:
320        resp = system_physical_switch(data, fos, check_mode)
321    else:
322        fos._module.fail_json(msg='missing task body: %s' % ('system_physical_switch'))
323    if check_mode:
324        return resp
325    return not is_successful_status(resp), \
326        resp['status'] == "success" and \
327        (resp['revision_changed'] if 'revision_changed' in resp else True), \
328        resp
329
330
331versioned_schema = {
332    "type": "list",
333    "children": {
334        "age_enable": {
335            "type": "string",
336            "options": [
337                {
338                    "value": "enable",
339                    "revisions": {
340                        "v6.0.0": True,
341                        "v7.0.0": True,
342                        "v6.0.5": True,
343                        "v6.4.4": True,
344                        "v6.4.1": True,
345                        "v6.2.0": True,
346                        "v6.2.3": True,
347                        "v6.2.5": True,
348                        "v6.2.7": True,
349                        "v6.0.11": True
350                    }
351                },
352                {
353                    "value": "disable",
354                    "revisions": {
355                        "v6.0.0": True,
356                        "v7.0.0": True,
357                        "v6.0.5": True,
358                        "v6.4.4": True,
359                        "v6.4.1": True,
360                        "v6.2.0": True,
361                        "v6.2.3": True,
362                        "v6.2.5": True,
363                        "v6.2.7": True,
364                        "v6.0.11": True
365                    }
366                }
367            ],
368            "revisions": {
369                "v6.0.0": True,
370                "v7.0.0": True,
371                "v6.0.5": True,
372                "v6.4.4": True,
373                "v6.4.1": True,
374                "v6.2.0": True,
375                "v6.2.3": True,
376                "v6.2.5": True,
377                "v6.2.7": True,
378                "v6.0.11": True
379            }
380        },
381        "name": {
382            "type": "string",
383            "revisions": {
384                "v6.0.0": True,
385                "v7.0.0": True,
386                "v6.0.5": True,
387                "v6.4.4": True,
388                "v6.4.1": True,
389                "v6.2.0": True,
390                "v6.2.3": True,
391                "v6.2.5": True,
392                "v6.2.7": True,
393                "v6.0.11": True
394            }
395        },
396        "age_val": {
397            "type": "integer",
398            "revisions": {
399                "v6.0.0": True,
400                "v7.0.0": True,
401                "v6.0.5": True,
402                "v6.4.4": True,
403                "v6.4.1": True,
404                "v6.2.0": True,
405                "v6.2.3": True,
406                "v6.2.5": True,
407                "v6.2.7": True,
408                "v6.0.11": True
409            }
410        },
411        "port": {
412            "type": "list",
413            "children": {
414                "status": {
415                    "type": "string",
416                    "options": [
417                        {
418                            "value": "up",
419                            "revisions": {
420                                "v6.0.11": True,
421                                "v6.0.0": True,
422                                "v6.2.3": True,
423                                "v6.0.5": True
424                            }
425                        },
426                        {
427                            "value": "down",
428                            "revisions": {
429                                "v6.0.11": True,
430                                "v6.0.0": True,
431                                "v6.2.3": True,
432                                "v6.0.5": True
433                            }
434                        }
435                    ],
436                    "revisions": {
437                        "v6.0.11": True,
438                        "v6.0.0": True,
439                        "v6.2.3": True,
440                        "v6.0.5": True
441                    }
442                },
443                "speed": {
444                    "type": "string",
445                    "options": [
446                        {
447                            "value": "auto",
448                            "revisions": {
449                                "v6.0.11": True,
450                                "v6.0.0": True,
451                                "v6.2.3": True,
452                                "v6.0.5": True
453                            }
454                        },
455                        {
456                            "value": "10full",
457                            "revisions": {
458                                "v6.0.11": True,
459                                "v6.0.0": True,
460                                "v6.2.3": True,
461                                "v6.0.5": True
462                            }
463                        },
464                        {
465                            "value": "10half",
466                            "revisions": {
467                                "v6.0.11": True,
468                                "v6.0.0": True,
469                                "v6.2.3": True,
470                                "v6.0.5": True
471                            }
472                        },
473                        {
474                            "value": "100full",
475                            "revisions": {
476                                "v6.0.11": True,
477                                "v6.0.0": True,
478                                "v6.2.3": True,
479                                "v6.0.5": True
480                            }
481                        },
482                        {
483                            "value": "100half",
484                            "revisions": {
485                                "v6.0.11": True,
486                                "v6.0.0": True,
487                                "v6.2.3": True,
488                                "v6.0.5": True
489                            }
490                        },
491                        {
492                            "value": "1000full",
493                            "revisions": {
494                                "v6.0.11": True,
495                                "v6.0.0": True,
496                                "v6.2.3": True,
497                                "v6.0.5": True
498                            }
499                        },
500                        {
501                            "value": "1000half",
502                            "revisions": {
503                                "v6.0.11": True,
504                                "v6.0.0": True,
505                                "v6.2.3": True,
506                                "v6.0.5": True
507                            }
508                        },
509                        {
510                            "value": "1000auto",
511                            "revisions": {
512                                "v6.0.11": True,
513                                "v6.0.0": True,
514                                "v6.2.3": True,
515                                "v6.0.5": True
516                            }
517                        },
518                        {
519                            "value": "10000full",
520                            "revisions": {
521                                "v6.0.11": True,
522                                "v6.0.0": True,
523                                "v6.2.3": True,
524                                "v6.0.5": True
525                            }
526                        },
527                        {
528                            "value": "10000auto",
529                            "revisions": {
530                                "v6.0.11": True,
531                                "v6.0.0": True,
532                                "v6.2.3": True,
533                                "v6.0.5": True
534                            }
535                        },
536                        {
537                            "value": "40000full",
538                            "revisions": {
539                                "v6.0.11": True,
540                                "v6.0.0": True,
541                                "v6.2.3": True,
542                                "v6.0.5": True
543                            }
544                        },
545                        {
546                            "value": "100Gfull",
547                            "revisions": {
548                                "v6.0.11": True,
549                                "v6.0.0": True,
550                                "v6.2.3": True,
551                                "v6.0.5": True
552                            }
553                        }
554                    ],
555                    "revisions": {
556                        "v6.0.11": True,
557                        "v6.0.0": True,
558                        "v6.2.3": True,
559                        "v6.0.5": True
560                    }
561                },
562                "name": {
563                    "type": "string",
564                    "revisions": {
565                        "v6.0.11": True,
566                        "v6.0.0": True,
567                        "v6.2.3": True,
568                        "v6.0.5": True
569                    }
570                }
571            },
572            "revisions": {
573                "v6.0.0": True,
574                "v7.0.0": False,
575                "v6.0.5": True,
576                "v6.4.4": False,
577                "v6.4.1": False,
578                "v6.2.0": False,
579                "v6.2.3": True,
580                "v6.2.5": False,
581                "v6.2.7": False,
582                "v6.0.11": True
583            }
584        }
585    },
586    "revisions": {
587        "v6.0.0": True,
588        "v7.0.0": True,
589        "v6.0.5": True,
590        "v6.4.4": True,
591        "v6.4.1": True,
592        "v6.2.0": True,
593        "v6.2.3": True,
594        "v6.2.5": True,
595        "v6.2.7": True,
596        "v6.0.11": True
597    }
598}
599
600
601def main():
602    module_spec = schema_to_module_spec(versioned_schema)
603    mkeyname = 'name'
604    fields = {
605        "access_token": {"required": False, "type": "str", "no_log": True},
606        "enable_log": {"required": False, "type": bool},
607        "vdom": {"required": False, "type": "str", "default": "root"},
608        "state": {"required": True, "type": "str",
609                  "choices": ["present", "absent"]},
610        "system_physical_switch": {
611            "required": False, "type": "dict", "default": None,
612            "options": {
613            }
614        }
615    }
616    for attribute_name in module_spec['options']:
617        fields["system_physical_switch"]['options'][attribute_name] = module_spec['options'][attribute_name]
618        if mkeyname and mkeyname == attribute_name:
619            fields["system_physical_switch"]['options'][attribute_name]['required'] = True
620
621    check_legacy_fortiosapi()
622    module = AnsibleModule(argument_spec=fields,
623                           supports_check_mode=True)
624
625    versions_check_result = None
626    if module._socket_path:
627        connection = Connection(module._socket_path)
628        if 'access_token' in module.params:
629            connection.set_option('access_token', module.params['access_token'])
630
631        if 'enable_log' in module.params:
632            connection.set_option('enable_log', module.params['enable_log'])
633        else:
634            connection.set_option('enable_log', False)
635        fos = FortiOSHandler(connection, module, mkeyname)
636        versions_check_result = check_schema_versioning(fos, versioned_schema, "system_physical_switch")
637
638        is_error, has_changed, result = fortios_system(module.params, fos, module.check_mode)
639
640    else:
641        module.fail_json(**FAIL_SOCKET_MSG)
642
643    if versions_check_result and versions_check_result['matched'] is False:
644        module.warn("Ansible has detected version mismatch between FortOS system and your playbook, see more details by specifying option -vvv")
645
646    if not is_error:
647        if versions_check_result and versions_check_result['matched'] is False:
648            module.exit_json(changed=has_changed, version_check_warning=versions_check_result, meta=result)
649        else:
650            module.exit_json(changed=has_changed, meta=result)
651    else:
652        if versions_check_result and versions_check_result['matched'] is False:
653            module.fail_json(msg="Error in repo", version_check_warning=versions_check_result, meta=result)
654        else:
655            module.fail_json(msg="Error in repo", meta=result)
656
657
658if __name__ == '__main__':
659    main()
660