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