1#!/usr/bin/python
2# -*- coding: utf-8 -*-
3#
4# Ansible module to manage PaloAltoNetworks Firewall
5# (c) 2019, Tomi Raittinen <tomi.raittinen@gmail.com>
6# (c) 2016, techbizdev <techbizdev@paloaltonetworks.com>
7#
8# This file is part of Ansible
9#
10# Ansible is free software: you can redistribute it and/or modify
11# it under the terms of the GNU General Public License as published by
12# the Free Software Foundation, either version 3 of the License, or
13# (at your option) any later version.
14#
15# Ansible is distributed in the hope that it will be useful,
16# but WITHOUT ANY WARRANTY; without even the implied warranty of
17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18# GNU General Public License for more details.
19#
20# You should have received a copy of the GNU General Public License
21# along with Ansible.  If not, see <http://www.gnu.org/licenses/>.
22
23DOCUMENTATION = '''
24---
25module: panos_commit
26short_description: commit firewall's candidate configuration
27description:
28    - PanOS module that will commit firewall's candidate configuration on
29    - the device. The new configuration will become active immediately.
30author:
31    - Luigi Mori (@jtschichold)
32    - Ivan Bojer (@ivanbojer)
33    - Tomi Raittinen (@traittinen)
34version_added: "2.3"
35requirements:
36    - pan-python
37deprecated:
38    alternative: Use U(https://galaxy.ansible.com/PaloAltoNetworks/paloaltonetworks) instead.
39    removed_in: "2.12"
40    why: Consolidating code base.
41options:
42    ip_address:
43        description:
44            - IP address (or hostname) of PAN-OS device.
45        required: true
46    password:
47        description:
48            - Password for authentication. If the value is not specified in the
49              task, the value of environment variable C(ANSIBLE_NET_PASSWORD)
50              will be used instead.
51        required: true
52    username:
53        description:
54            - Username for authentication. If the value is not specified in the
55              task, the value of environment variable C(ANSIBLE_NET_USERNAME)
56              will be used instead if defined. C(admin) will be used if nothing
57              above is defined.
58        default: admin
59    interval:
60        description:
61            - interval for checking commit job
62        default: 0.5
63    timeout:
64        description:
65            - timeout for commit job
66    sync:
67        description:
68            - if commit should be synchronous
69        type: bool
70        default: 'yes'
71    description:
72        description:
73            - Commit description/comment
74        type: str
75        version_added: "2.8"
76    commit_changes_by:
77        description:
78            - Commit changes made by specified admin
79        type: list
80        version_added: "2.8"
81    commit_vsys:
82        description:
83            - Commit changes for specified VSYS
84        type: list
85        version_added: "2.8"
86'''
87
88EXAMPLES = '''
89# Commit candidate config on 192.168.1.1 in sync mode
90- panos_commit:
91    ip_address: "192.168.1.1"
92    username: "admin"
93    password: "admin"
94'''
95
96RETURN = '''
97panos_commit:
98    description: Information about commit job.
99    returned: always
100    type: complex
101    version_added: 2.8
102    contains:
103        job_id:
104            description: Palo Alto job ID for the commit operation. Only returned if commit job is launched on device.
105            returned: always
106            type: str
107            sample: "139"
108        status_code:
109            description: Palo Alto API status code. Null if commit is successful.
110            returned: always
111            type: str
112            sample: 19
113        status_detail:
114            description: Palo Alto API detailed status message.
115            returned: always
116            type: str
117            sample: Configuration committed successfully
118        status_text:
119            description: Palo Alto API status text.
120            returned: always
121            type: str
122            sample: success
123'''
124
125ANSIBLE_METADATA = {'metadata_version': '1.1',
126                    'status': ['deprecated'],
127                    'supported_by': 'community'}
128
129
130from ansible.module_utils.basic import AnsibleModule, env_fallback
131import xml.etree.ElementTree as etree
132
133try:
134    import pan.xapi
135    HAS_LIB = True
136except ImportError:
137    HAS_LIB = False
138
139
140def main():
141    argument_spec = dict(
142        ip_address=dict(required=True, type='str'),
143        password=dict(fallback=(env_fallback, ['ANSIBLE_NET_PASSWORD']), no_log=True),
144        username=dict(fallback=(env_fallback, ['ANSIBLE_NET_USERNAME']), default="admin"),
145        interval=dict(default=0.5),
146        timeout=dict(),
147        sync=dict(type='bool', default=True),
148        description=dict(type='str'),
149        commit_changes_by=dict(type='list'),
150        commit_vsys=dict(type='list')
151    )
152    module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False)
153
154    if not HAS_LIB:
155        module.fail_json(msg='pan-python is required for this module')
156
157    ip_address = module.params["ip_address"]
158    if not ip_address:
159        module.fail_json(msg="ip_address should be specified")
160
161    password = module.params["password"]
162    if not password:
163        module.fail_json(msg="password is required")
164
165    username = module.params['username']
166    if not username:
167        module.fail_json(msg="username is required")
168
169    interval = module.params['interval']
170    timeout = module.params['timeout']
171    sync = module.params['sync']
172
173    xapi = pan.xapi.PanXapi(
174        hostname=ip_address,
175        api_username=username,
176        api_password=password
177    )
178
179    cmd = "<commit>"
180
181    description = module.params["description"]
182    if description:
183        cmd += "<description>" + description + "</description>"
184
185    commit_changes_by = module.params["commit_changes_by"]
186    commit_vsys = module.params["commit_vsys"]
187
188    if commit_changes_by or commit_vsys:
189
190        cmd += "<partial>"
191
192        if commit_changes_by:
193            cmd += "<admin>"
194            for admin in commit_changes_by:
195                cmd += "<member>" + admin + "</member>"
196            cmd += "</admin>"
197
198        if commit_vsys:
199            cmd += "<vsys>"
200            for vsys in commit_vsys:
201                cmd += "<member>" + vsys + "</member>"
202            cmd += "</vsys>"
203
204        cmd += "</partial><force></force>"
205
206    cmd += "</commit>"
207
208    xapi.commit(
209        cmd=cmd,
210        sync=sync,
211        interval=interval,
212        timeout=timeout
213    )
214
215    try:
216        result = xapi.xml_root().encode('utf-8')
217        root = etree.fromstring(result)
218        job_id = root.find('./result/job/id').text
219    except AttributeError:
220        job_id = None
221
222    panos_commit_details = dict(
223        status_text=xapi.status,
224        status_code=xapi.status_code,
225        status_detail=xapi.status_detail,
226        job_id=job_id
227    )
228
229    if "Commit failed" in xapi.status_detail:
230        module.fail_json(msg=xapi.status_detail, panos_commit=panos_commit_details)
231
232    if job_id:
233        module.exit_json(changed=True, msg="Commit successful.", panos_commit=panos_commit_details)
234    else:
235        module.exit_json(changed=False, msg="No changes to commit.", panos_commit=panos_commit_details)
236
237
238if __name__ == '__main__':
239    main()
240