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