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_smc_ntp
27short_description: Configure SMC NTP information 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 smc_ntp 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    system_smc_ntp:
68        description:
69            - Configure SMC NTP information.
70        default: null
71        type: dict
72        suboptions:
73            channel:
74                description:
75                    - SMC NTP client will send NTP packets through this channel.
76                type: int
77            ntpserver:
78                description:
79                    - Configure the FortiGate SMC to connect to an NTP server.
80                type: list
81                suboptions:
82                    id:
83                        description:
84                            - NTP server ID.
85                        required: true
86                        type: int
87                    server:
88                        description:
89                            - IP address of the NTP server.
90                        type: str
91            ntpsync:
92                description:
93                    - Enable/disable setting the FortiGate SMC system time by synchronizing with an NTP server.
94                type: str
95                choices:
96                    - enable
97                    - disable
98            syncinterval:
99                description:
100                    - SMC NTP synchronization interval (1 - 65535 secs).
101                type: int
102'''
103
104EXAMPLES = '''
105- hosts: fortigates
106  collections:
107    - fortinet.fortios
108  connection: httpapi
109  vars:
110   vdom: "root"
111   ansible_httpapi_use_ssl: yes
112   ansible_httpapi_validate_certs: no
113   ansible_httpapi_port: 443
114  tasks:
115  - name: Configure SMC NTP information.
116    fortios_system_smc_ntp:
117      vdom:  "{{ vdom }}"
118      system_smc_ntp:
119        channel: "3"
120        ntpserver:
121         -
122            id:  "5"
123            server: "192.168.100.40"
124        ntpsync: "enable"
125        syncinterval: "8"
126
127'''
128
129RETURN = '''
130build:
131  description: Build number of the fortigate image
132  returned: always
133  type: str
134  sample: '1547'
135http_method:
136  description: Last method used to provision the content into FortiGate
137  returned: always
138  type: str
139  sample: 'PUT'
140http_status:
141  description: Last result given by FortiGate on last operation applied
142  returned: always
143  type: str
144  sample: "200"
145mkey:
146  description: Master key (id) used in the last call to FortiGate
147  returned: success
148  type: str
149  sample: "id"
150name:
151  description: Name of the table used to fulfill the request
152  returned: always
153  type: str
154  sample: "urlfilter"
155path:
156  description: Path of the table used to fulfill the request
157  returned: always
158  type: str
159  sample: "webfilter"
160revision:
161  description: Internal revision number
162  returned: always
163  type: str
164  sample: "17.0.2.10658"
165serial:
166  description: Serial number of the unit
167  returned: always
168  type: str
169  sample: "FGVMEVYYQT3AB5352"
170status:
171  description: Indication of the operation's result
172  returned: always
173  type: str
174  sample: "success"
175vdom:
176  description: Virtual domain used
177  returned: always
178  type: str
179  sample: "root"
180version:
181  description: Version of the FortiGate
182  returned: always
183  type: str
184  sample: "v5.6.3"
185
186'''
187from ansible.module_utils.basic import AnsibleModule
188from ansible.module_utils.connection import Connection
189from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import FortiOSHandler
190from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import check_legacy_fortiosapi
191from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import schema_to_module_spec
192from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import check_schema_versioning
193from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG
194from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.comparison import is_same_comparison
195from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.comparison import serialize
196
197
198def filter_system_smc_ntp_data(json):
199    option_list = ['channel', 'ntpserver', 'ntpsync',
200                   'syncinterval']
201    dictionary = {}
202
203    for attribute in option_list:
204        if attribute in json and json[attribute] is not None:
205            dictionary[attribute] = json[attribute]
206
207    return dictionary
208
209
210def underscore_to_hyphen(data):
211    if isinstance(data, list):
212        for i, elem in enumerate(data):
213            data[i] = underscore_to_hyphen(elem)
214    elif isinstance(data, dict):
215        new_data = {}
216        for k, v in data.items():
217            new_data[k.replace('_', '-')] = underscore_to_hyphen(v)
218        data = new_data
219
220    return data
221
222
223def system_smc_ntp(data, fos):
224    vdom = data['vdom']
225    system_smc_ntp_data = data['system_smc_ntp']
226    filtered_data = underscore_to_hyphen(filter_system_smc_ntp_data(system_smc_ntp_data))
227
228    return fos.set('system',
229                   'smc-ntp',
230                   data=filtered_data,
231                   vdom=vdom)
232
233
234def is_successful_status(status):
235    return status['status'] == "success" or \
236        status['http_method'] == "DELETE" and status['http_status'] == 404
237
238
239def fortios_system(data, fos):
240
241    if data['system_smc_ntp']:
242        resp = system_smc_ntp(data, fos)
243    else:
244        fos._module.fail_json(msg='missing task body: %s' % ('system_smc_ntp'))
245
246    return not is_successful_status(resp), \
247        resp['status'] == "success" and \
248        (resp['revision_changed'] if 'revision_changed' in resp else True), \
249        resp
250
251
252versioned_schema = {
253    "type": "dict",
254    "children": {
255        "ntpsync": {
256            "type": "string",
257            "options": [
258                {
259                    "value": "enable",
260                    "revisions": {
261                        "v6.2.3": True
262                    }
263                },
264                {
265                    "value": "disable",
266                    "revisions": {
267                        "v6.2.3": True
268                    }
269                }
270            ],
271            "revisions": {
272                "v6.2.3": True
273            }
274        },
275        "syncinterval": {
276            "type": "integer",
277            "revisions": {
278                "v6.2.3": True
279            }
280        },
281        "ntpserver": {
282            "type": "list",
283            "children": {
284                "id": {
285                    "type": "integer",
286                    "revisions": {
287                        "v6.2.3": True
288                    }
289                },
290                "server": {
291                    "type": "string",
292                    "revisions": {
293                        "v6.2.3": True
294                    }
295                }
296            },
297            "revisions": {
298                "v6.2.3": True
299            }
300        },
301        "channel": {
302            "type": "integer",
303            "revisions": {
304                "v6.2.3": True
305            }
306        }
307    },
308    "revisions": {
309        "v6.2.3": True
310    }
311}
312
313
314def main():
315    module_spec = schema_to_module_spec(versioned_schema)
316    mkeyname = None
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        "system_smc_ntp": {
322            "required": False, "type": "dict", "default": None,
323            "options": {
324            }
325        }
326    }
327    for attribute_name in module_spec['options']:
328        fields["system_smc_ntp"]['options'][attribute_name] = module_spec['options'][attribute_name]
329        if mkeyname and mkeyname == attribute_name:
330            fields["system_smc_ntp"]['options'][attribute_name]['required'] = True
331
332    check_legacy_fortiosapi()
333    module = AnsibleModule(argument_spec=fields,
334                           supports_check_mode=False)
335
336    versions_check_result = None
337    if module._socket_path:
338        connection = Connection(module._socket_path)
339        if 'access_token' in module.params:
340            connection.set_option('access_token', module.params['access_token'])
341
342        if 'enable_log' in module.params:
343            connection.set_option('enable_log', module.params['enable_log'])
344        else:
345            connection.set_option('enable_log', False)
346        fos = FortiOSHandler(connection, module, mkeyname)
347        versions_check_result = check_schema_versioning(fos, versioned_schema, "system_smc_ntp")
348
349        is_error, has_changed, result = fortios_system(module.params, fos)
350
351    else:
352        module.fail_json(**FAIL_SOCKET_MSG)
353
354    if versions_check_result and versions_check_result['matched'] is False:
355        module.warn("Ansible has detected version mismatch between FortOS system and your playbook, see more details by specifying option -vvv")
356
357    if not is_error:
358        if versions_check_result and versions_check_result['matched'] is False:
359            module.exit_json(changed=has_changed, version_check_warning=versions_check_result, meta=result)
360        else:
361            module.exit_json(changed=has_changed, meta=result)
362    else:
363        if versions_check_result and versions_check_result['matched'] is False:
364            module.fail_json(msg="Error in repo", version_check_warning=versions_check_result, meta=result)
365        else:
366            module.fail_json(msg="Error in repo", meta=result)
367
368
369if __name__ == '__main__':
370    main()
371