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_federated_upgrade
27short_description: Coordinate federated upgrades within the Security Fabric 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 federated_upgrade 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_federated_upgrade:
68        description:
69            - Coordinate federated upgrades within the Security Fabric.
70        default: null
71        type: dict
72        suboptions:
73            node_list:
74                description:
75                    - Nodes which will be included in the upgrade.
76                type: list
77                suboptions:
78                    coordinating_fortigate:
79                        description:
80                            - The serial of the FortiGate that controls this device
81                        type: str
82                    device_type:
83                        description:
84                            - What type of device this node represents.
85                        type: str
86                        choices:
87                            - fortigate
88                            - fortiswitch
89                            - fortiap
90                    serial:
91                        description:
92                            - Serial number of the node to include.
93                        required: true
94                        type: str
95                    setup_time:
96                        description:
97                            - 'When the upgrade was configured. Format hh:mm yyyy/mm/dd UTC.'
98                        type: str
99                    time:
100                        description:
101                            - 'Scheduled time for the upgrade. Format hh:mm yyyy/mm/dd UTC.'
102                        type: str
103                    timing:
104                        description:
105                            - Whether the upgrade should be run immediately, or at a scheduled time.
106                        type: str
107                        choices:
108                            - immediate
109                            - scheduled
110                    upgrade_path:
111                        description:
112                            - Image IDs to upgrade through.
113                        type: str
114            status:
115                description:
116                    - Current status of the upgrade.
117                type: str
118                choices:
119                    - disabled
120                    - initialized
121                    - downloading
122                    - download-failed
123                    - device-disconnected
124                    - ready
125                    - staging
126                    - cancelled
127                    - confirmed
128                    - done
129                    - failed
130            upgrade_id:
131                description:
132                    - Unique identifier for this upgrade.
133                type: int
134'''
135
136EXAMPLES = '''
137- hosts: fortigates
138  collections:
139    - fortinet.fortios
140  connection: httpapi
141  vars:
142   vdom: "root"
143   ansible_httpapi_use_ssl: yes
144   ansible_httpapi_validate_certs: no
145   ansible_httpapi_port: 443
146  tasks:
147  - name: Coordinate federated upgrades within the Security Fabric.
148    fortios_system_federated_upgrade:
149      vdom:  "{{ vdom }}"
150      system_federated_upgrade:
151        node_list:
152         -
153            coordinating_fortigate: "<your_own_value>"
154            device_type: "fortigate"
155            serial: "<your_own_value>"
156            setup_time: "<your_own_value>"
157            time: "<your_own_value>"
158            timing: "immediate"
159            upgrade_path: "<your_own_value>"
160        status: "disabled"
161        upgrade_id: "12"
162
163'''
164
165RETURN = '''
166build:
167  description: Build number of the fortigate image
168  returned: always
169  type: str
170  sample: '1547'
171http_method:
172  description: Last method used to provision the content into FortiGate
173  returned: always
174  type: str
175  sample: 'PUT'
176http_status:
177  description: Last result given by FortiGate on last operation applied
178  returned: always
179  type: str
180  sample: "200"
181mkey:
182  description: Master key (id) used in the last call to FortiGate
183  returned: success
184  type: str
185  sample: "id"
186name:
187  description: Name of the table used to fulfill the request
188  returned: always
189  type: str
190  sample: "urlfilter"
191path:
192  description: Path of the table used to fulfill the request
193  returned: always
194  type: str
195  sample: "webfilter"
196revision:
197  description: Internal revision number
198  returned: always
199  type: str
200  sample: "17.0.2.10658"
201serial:
202  description: Serial number of the unit
203  returned: always
204  type: str
205  sample: "FGVMEVYYQT3AB5352"
206status:
207  description: Indication of the operation's result
208  returned: always
209  type: str
210  sample: "success"
211vdom:
212  description: Virtual domain used
213  returned: always
214  type: str
215  sample: "root"
216version:
217  description: Version of the FortiGate
218  returned: always
219  type: str
220  sample: "v5.6.3"
221
222'''
223from ansible.module_utils.basic import AnsibleModule
224from ansible.module_utils.connection import Connection
225from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import FortiOSHandler
226from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import check_legacy_fortiosapi
227from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import schema_to_module_spec
228from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import check_schema_versioning
229from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG
230from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.comparison import is_same_comparison
231from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.comparison import serialize
232
233
234def filter_system_federated_upgrade_data(json):
235    option_list = ['node_list', 'status', 'upgrade_id']
236    dictionary = {}
237
238    for attribute in option_list:
239        if attribute in json and json[attribute] is not None:
240            dictionary[attribute] = json[attribute]
241
242    return dictionary
243
244
245def underscore_to_hyphen(data):
246    if isinstance(data, list):
247        for i, elem in enumerate(data):
248            data[i] = underscore_to_hyphen(elem)
249    elif isinstance(data, dict):
250        new_data = {}
251        for k, v in data.items():
252            new_data[k.replace('_', '-')] = underscore_to_hyphen(v)
253        data = new_data
254
255    return data
256
257
258def system_federated_upgrade(data, fos):
259    vdom = data['vdom']
260    system_federated_upgrade_data = data['system_federated_upgrade']
261    filtered_data = underscore_to_hyphen(filter_system_federated_upgrade_data(system_federated_upgrade_data))
262
263    return fos.set('system',
264                   'federated-upgrade',
265                   data=filtered_data,
266                   vdom=vdom)
267
268
269def is_successful_status(status):
270    return status['status'] == "success" or \
271        status['http_method'] == "DELETE" and status['http_status'] == 404
272
273
274def fortios_system(data, fos):
275
276    if data['system_federated_upgrade']:
277        resp = system_federated_upgrade(data, fos)
278    else:
279        fos._module.fail_json(msg='missing task body: %s' % ('system_federated_upgrade'))
280
281    return not is_successful_status(resp), \
282        resp['status'] == "success" and \
283        (resp['revision_changed'] if 'revision_changed' in resp else True), \
284        resp
285
286
287versioned_schema = {
288    "type": "dict",
289    "children": {
290        "status": {
291            "type": "string",
292            "options": [
293                {
294                    "value": "disabled",
295                    "revisions": {
296                        "v7.0.0": True
297                    }
298                },
299                {
300                    "value": "initialized",
301                    "revisions": {
302                        "v7.0.0": True
303                    }
304                },
305                {
306                    "value": "downloading",
307                    "revisions": {
308                        "v7.0.0": True
309                    }
310                },
311                {
312                    "value": "download-failed",
313                    "revisions": {
314                        "v7.0.0": True
315                    }
316                },
317                {
318                    "value": "device-disconnected",
319                    "revisions": {
320                        "v7.0.0": True
321                    }
322                },
323                {
324                    "value": "ready",
325                    "revisions": {
326                        "v7.0.0": True
327                    }
328                },
329                {
330                    "value": "staging",
331                    "revisions": {
332                        "v7.0.0": True
333                    }
334                },
335                {
336                    "value": "cancelled",
337                    "revisions": {
338                        "v7.0.0": True
339                    }
340                },
341                {
342                    "value": "confirmed",
343                    "revisions": {
344                        "v7.0.0": True
345                    }
346                },
347                {
348                    "value": "done",
349                    "revisions": {
350                        "v7.0.0": True
351                    }
352                },
353                {
354                    "value": "failed",
355                    "revisions": {
356                        "v7.0.0": True
357                    }
358                }
359            ],
360            "revisions": {
361                "v7.0.0": True
362            }
363        },
364        "node_list": {
365            "type": "list",
366            "children": {
367                "coordinating_fortigate": {
368                    "type": "string",
369                    "revisions": {
370                        "v7.0.0": True
371                    }
372                },
373                "upgrade_path": {
374                    "type": "string",
375                    "revisions": {
376                        "v7.0.0": True
377                    }
378                },
379                "setup_time": {
380                    "type": "string",
381                    "revisions": {
382                        "v7.0.0": True
383                    }
384                },
385                "device_type": {
386                    "type": "string",
387                    "options": [
388                        {
389                            "value": "fortigate",
390                            "revisions": {
391                                "v7.0.0": True
392                            }
393                        },
394                        {
395                            "value": "fortiswitch",
396                            "revisions": {
397                                "v7.0.0": True
398                            }
399                        },
400                        {
401                            "value": "fortiap",
402                            "revisions": {
403                                "v7.0.0": True
404                            }
405                        }
406                    ],
407                    "revisions": {
408                        "v7.0.0": True
409                    }
410                },
411                "time": {
412                    "type": "string",
413                    "revisions": {
414                        "v7.0.0": True
415                    }
416                },
417                "timing": {
418                    "type": "string",
419                    "options": [
420                        {
421                            "value": "immediate",
422                            "revisions": {
423                                "v7.0.0": True
424                            }
425                        },
426                        {
427                            "value": "scheduled",
428                            "revisions": {
429                                "v7.0.0": True
430                            }
431                        }
432                    ],
433                    "revisions": {
434                        "v7.0.0": True
435                    }
436                },
437                "serial": {
438                    "type": "string",
439                    "revisions": {
440                        "v7.0.0": True
441                    }
442                }
443            },
444            "revisions": {
445                "v7.0.0": True
446            }
447        },
448        "upgrade_id": {
449            "type": "integer",
450            "revisions": {
451                "v7.0.0": True
452            }
453        }
454    },
455    "revisions": {
456        "v7.0.0": True
457    }
458}
459
460
461def main():
462    module_spec = schema_to_module_spec(versioned_schema)
463    mkeyname = None
464    fields = {
465        "access_token": {"required": False, "type": "str", "no_log": True},
466        "enable_log": {"required": False, "type": bool},
467        "vdom": {"required": False, "type": "str", "default": "root"},
468        "system_federated_upgrade": {
469            "required": False, "type": "dict", "default": None,
470            "options": {
471            }
472        }
473    }
474    for attribute_name in module_spec['options']:
475        fields["system_federated_upgrade"]['options'][attribute_name] = module_spec['options'][attribute_name]
476        if mkeyname and mkeyname == attribute_name:
477            fields["system_federated_upgrade"]['options'][attribute_name]['required'] = True
478
479    check_legacy_fortiosapi()
480    module = AnsibleModule(argument_spec=fields,
481                           supports_check_mode=False)
482
483    versions_check_result = None
484    if module._socket_path:
485        connection = Connection(module._socket_path)
486        if 'access_token' in module.params:
487            connection.set_option('access_token', module.params['access_token'])
488
489        if 'enable_log' in module.params:
490            connection.set_option('enable_log', module.params['enable_log'])
491        else:
492            connection.set_option('enable_log', False)
493        fos = FortiOSHandler(connection, module, mkeyname)
494        versions_check_result = check_schema_versioning(fos, versioned_schema, "system_federated_upgrade")
495
496        is_error, has_changed, result = fortios_system(module.params, fos)
497
498    else:
499        module.fail_json(**FAIL_SOCKET_MSG)
500
501    if versions_check_result and versions_check_result['matched'] is False:
502        module.warn("Ansible has detected version mismatch between FortOS system and your playbook, see more details by specifying option -vvv")
503
504    if not is_error:
505        if versions_check_result and versions_check_result['matched'] is False:
506            module.exit_json(changed=has_changed, version_check_warning=versions_check_result, meta=result)
507        else:
508            module.exit_json(changed=has_changed, meta=result)
509    else:
510        if versions_check_result and versions_check_result['matched'] is False:
511            module.fail_json(msg="Error in repo", version_check_warning=versions_check_result, meta=result)
512        else:
513            module.fail_json(msg="Error in repo", meta=result)
514
515
516if __name__ == '__main__':
517    main()
518