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_switch_controller_switch_interface_tag
27short_description: Configure switch object tags 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 switch_controller feature and switch_interface_tag 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    switch_controller_switch_interface_tag:
76        description:
77            - Configure switch object tags.
78        default: null
79        type: dict
80        suboptions:
81            name:
82                description:
83                    - Tag name.
84                required: true
85                type: str
86'''
87
88EXAMPLES = '''
89- hosts: fortigates
90  collections:
91    - fortinet.fortios
92  connection: httpapi
93  vars:
94   vdom: "root"
95   ansible_httpapi_use_ssl: yes
96   ansible_httpapi_validate_certs: no
97   ansible_httpapi_port: 443
98  tasks:
99  - name: Configure switch object tags.
100    fortios_switch_controller_switch_interface_tag:
101      vdom:  "{{ vdom }}"
102      state: "present"
103      access_token: "<your_own_value>"
104      switch_controller_switch_interface_tag:
105        name: "default_name_3"
106
107'''
108
109RETURN = '''
110build:
111  description: Build number of the fortigate image
112  returned: always
113  type: str
114  sample: '1547'
115http_method:
116  description: Last method used to provision the content into FortiGate
117  returned: always
118  type: str
119  sample: 'PUT'
120http_status:
121  description: Last result given by FortiGate on last operation applied
122  returned: always
123  type: str
124  sample: "200"
125mkey:
126  description: Master key (id) used in the last call to FortiGate
127  returned: success
128  type: str
129  sample: "id"
130name:
131  description: Name of the table used to fulfill the request
132  returned: always
133  type: str
134  sample: "urlfilter"
135path:
136  description: Path of the table used to fulfill the request
137  returned: always
138  type: str
139  sample: "webfilter"
140revision:
141  description: Internal revision number
142  returned: always
143  type: str
144  sample: "17.0.2.10658"
145serial:
146  description: Serial number of the unit
147  returned: always
148  type: str
149  sample: "FGVMEVYYQT3AB5352"
150status:
151  description: Indication of the operation's result
152  returned: always
153  type: str
154  sample: "success"
155vdom:
156  description: Virtual domain used
157  returned: always
158  type: str
159  sample: "root"
160version:
161  description: Version of the FortiGate
162  returned: always
163  type: str
164  sample: "v5.6.3"
165
166'''
167from ansible.module_utils.basic import AnsibleModule
168from ansible.module_utils.connection import Connection
169from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import FortiOSHandler
170from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import check_legacy_fortiosapi
171from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import schema_to_module_spec
172from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import check_schema_versioning
173from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG
174from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.comparison import is_same_comparison
175from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.comparison import serialize
176
177
178def filter_switch_controller_switch_interface_tag_data(json):
179    option_list = ['name']
180    dictionary = {}
181
182    for attribute in option_list:
183        if attribute in json and json[attribute] is not None:
184            dictionary[attribute] = json[attribute]
185
186    return dictionary
187
188
189def underscore_to_hyphen(data):
190    if isinstance(data, list):
191        for i, elem in enumerate(data):
192            data[i] = underscore_to_hyphen(elem)
193    elif isinstance(data, dict):
194        new_data = {}
195        for k, v in data.items():
196            new_data[k.replace('_', '-')] = underscore_to_hyphen(v)
197        data = new_data
198
199    return data
200
201
202def switch_controller_switch_interface_tag(data, fos, check_mode=False):
203
204    vdom = data['vdom']
205
206    state = data['state']
207
208    switch_controller_switch_interface_tag_data = data['switch_controller_switch_interface_tag']
209    filtered_data = underscore_to_hyphen(filter_switch_controller_switch_interface_tag_data(switch_controller_switch_interface_tag_data))
210
211    # check_mode starts from here
212    if check_mode:
213        mkey = fos.get_mkey('system', 'interface', filtered_data, vdom=vdom)
214        current_data = fos.get('system', 'interface', vdom=vdom, mkey=mkey)
215        is_existed = current_data and current_data.get('http_status') == 200 \
216            and isinstance(current_data.get('results'), list) \
217            and len(current_data['results']) > 0
218
219        # 2. if it exists and the state is 'present' then compare current settings with desired
220        if state == 'present' or state is True:
221            if mkey is None:
222                return False, True, filtered_data
223
224            # if mkey exists then compare each other
225            # record exits and they're matched or not
226            if is_existed:
227                is_same = is_same_comparison(
228                    serialize(current_data['results'][0]), serialize(filtered_data))
229                return False, not is_same, filtered_data
230
231            # record does not exist
232            return False, True, filtered_data
233
234        if state == 'absent':
235            if mkey is None:
236                return False, False, filtered_data
237
238            if is_existed:
239                return False, True, filtered_data
240            return False, False, filtered_data
241
242        return True, False, {'reason: ': 'Must provide state parameter'}
243
244    if state == "present" or state is True:
245        return fos.set('switch-controller',
246                       'switch-interface-tag',
247                       data=filtered_data,
248                       vdom=vdom)
249
250    elif state == "absent":
251        return fos.delete('switch-controller',
252                          'switch-interface-tag',
253                          mkey=filtered_data['name'],
254                          vdom=vdom)
255    else:
256        fos._module.fail_json(msg='state must be present or absent!')
257
258
259def is_successful_status(status):
260    return status['status'] == "success" or \
261        status['http_method'] == "DELETE" and status['http_status'] == 404
262
263
264def fortios_switch_controller(data, fos, check_mode):
265
266    if data['switch_controller_switch_interface_tag']:
267        resp = switch_controller_switch_interface_tag(data, fos, check_mode)
268    else:
269        fos._module.fail_json(msg='missing task body: %s' % ('switch_controller_switch_interface_tag'))
270    if check_mode:
271        return resp
272    return not is_successful_status(resp), \
273        resp['status'] == "success" and \
274        (resp['revision_changed'] if 'revision_changed' in resp else True), \
275        resp
276
277
278versioned_schema = {
279    "type": "list",
280    "children": {
281        "name": {
282            "type": "string",
283            "revisions": {
284                "v6.0.0": True,
285                "v7.0.0": True,
286                "v6.0.5": True,
287                "v6.4.4": True,
288                "v6.4.0": True,
289                "v6.4.1": True,
290                "v6.2.0": True,
291                "v6.2.3": True,
292                "v6.2.5": True,
293                "v6.2.7": True,
294                "v6.0.11": True
295            }
296        }
297    },
298    "revisions": {
299        "v6.0.0": True,
300        "v7.0.0": True,
301        "v6.0.5": True,
302        "v6.4.4": True,
303        "v6.4.0": True,
304        "v6.4.1": True,
305        "v6.2.0": True,
306        "v6.2.3": True,
307        "v6.2.5": True,
308        "v6.2.7": True,
309        "v6.0.11": True
310    }
311}
312
313
314def main():
315    module_spec = schema_to_module_spec(versioned_schema)
316    mkeyname = 'name'
317    fields = {
318        "access_token": {"required": False, "type": "str", "no_log": True},
319        "enable_log": {"required": False, "type": bool},
320        "vdom": {"required": False, "type": "str", "default": "root"},
321        "state": {"required": True, "type": "str",
322                  "choices": ["present", "absent"]},
323        "switch_controller_switch_interface_tag": {
324            "required": False, "type": "dict", "default": None,
325            "options": {
326            }
327        }
328    }
329    for attribute_name in module_spec['options']:
330        fields["switch_controller_switch_interface_tag"]['options'][attribute_name] = module_spec['options'][attribute_name]
331        if mkeyname and mkeyname == attribute_name:
332            fields["switch_controller_switch_interface_tag"]['options'][attribute_name]['required'] = True
333
334    check_legacy_fortiosapi()
335    module = AnsibleModule(argument_spec=fields,
336                           supports_check_mode=True)
337
338    versions_check_result = None
339    if module._socket_path:
340        connection = Connection(module._socket_path)
341        if 'access_token' in module.params:
342            connection.set_option('access_token', module.params['access_token'])
343
344        if 'enable_log' in module.params:
345            connection.set_option('enable_log', module.params['enable_log'])
346        else:
347            connection.set_option('enable_log', False)
348        fos = FortiOSHandler(connection, module, mkeyname)
349        versions_check_result = check_schema_versioning(fos, versioned_schema, "switch_controller_switch_interface_tag")
350
351        is_error, has_changed, result = fortios_switch_controller(module.params, fos, module.check_mode)
352
353    else:
354        module.fail_json(**FAIL_SOCKET_MSG)
355
356    if versions_check_result and versions_check_result['matched'] is False:
357        module.warn("Ansible has detected version mismatch between FortOS system and your playbook, see more details by specifying option -vvv")
358
359    if not is_error:
360        if versions_check_result and versions_check_result['matched'] is False:
361            module.exit_json(changed=has_changed, version_check_warning=versions_check_result, meta=result)
362        else:
363            module.exit_json(changed=has_changed, meta=result)
364    else:
365        if versions_check_result and versions_check_result['matched'] is False:
366            module.fail_json(msg="Error in repo", version_check_warning=versions_check_result, meta=result)
367        else:
368            module.fail_json(msg="Error in repo", meta=result)
369
370
371if __name__ == '__main__':
372    main()
373