1#!/usr/bin/python
2# -*- coding: utf-8 -*-
3
4# (c) 2017, Ansible by Red Hat, inc
5# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
6
7from __future__ import absolute_import, division, print_function
8__metaclass__ = type
9
10
11ANSIBLE_METADATA = {'metadata_version': '1.1',
12                    'status': ['preview'],
13                    'supported_by': 'network'}
14
15
16DOCUMENTATION = """
17---
18module: junos_logging
19version_added: "2.4"
20author: "Ganesh Nalawade (@ganeshrn)"
21short_description: Manage logging on network devices
22description:
23  - This module provides declarative management of logging
24    on Juniper JUNOS devices.
25options:
26  dest:
27    description:
28      - Destination of the logs.
29    choices: ['console', 'host', 'file', 'user']
30  name:
31    description:
32      - If value of C(dest) is I(file) it indicates file-name,
33        for I(user) it indicates username and for I(host) indicates
34        the host name to be notified.
35  facility:
36    description:
37      - Set logging facility.
38  level:
39    description:
40      - Set logging severity levels.
41  aggregate:
42    description: List of logging definitions.
43  state:
44    description:
45      - State of the logging configuration.
46    default: present
47    choices: ['present', 'absent']
48  active:
49    description:
50      - Specifies whether or not the configuration is active or deactivated
51    default: True
52    type: bool
53  rotate_frequency:
54    description:
55      - Rotate log frequency in minutes, this is applicable if value
56        of I(dest) is C(file). The acceptable value is in range of 1 to 59.
57        This controls the frequency after which log file is rotated.
58    required: false
59  size:
60    description:
61      - Size of the file in archive, this is applicable if value
62        of I(dest) is C(file). The acceptable value is in range from 65536 to
63        1073741824 bytes.
64    required: false
65  files:
66    description:
67      - Number of files to be archived, this is applicable if value
68        of I(dest) is C(file). The acceptable value is in range from 1 to 1000.
69    required: false
70requirements:
71  - ncclient (>=v0.5.2)
72notes:
73  - This module requires the netconf system service be enabled on
74    the remote device being managed.
75  - Tested against vSRX JUNOS version 15.1X49-D15.4, vqfx-10000 JUNOS Version 15.1X53-D60.4.
76  - Recommended connection is C(netconf). See L(the Junos OS Platform Options,../network/user_guide/platform_junos.html).
77  - This module also works with C(local) connections for legacy playbooks.
78extends_documentation_fragment: junos
79"""
80
81EXAMPLES = """
82- name: configure console logging
83  junos_logging:
84    dest: console
85    facility: any
86    level: critical
87
88- name: remove console logging configuration
89  junos_logging:
90    dest: console
91    state: absent
92
93- name: configure file logging
94  junos_logging:
95    dest: file
96    name: test
97    facility: pfe
98    level: error
99
100- name: configure logging parameter
101  junos_logging:
102    files: 30
103    size: 65536
104    rotate_frequency: 10
105
106- name: Configure file logging using aggregate
107  junos_logging:
108    dest: file
109    aggregate:
110    - name: test-1
111      facility: pfe
112      level: critical
113    - name: test-2
114      facility: kernel
115      level: emergency
116    active: True
117
118- name: Delete file logging using aggregate
119  junos_logging:
120    aggregate:
121    - { dest: file, name: test-1,  facility: pfe, level: critical }
122    - { dest: file, name: test-2,  facility: kernel, level: emergency }
123    state: absent
124"""
125
126RETURN = """
127diff.prepared:
128  description: Configuration difference before and after applying change.
129  returned: when configuration is changed and diff option is enabled.
130  type: str
131  sample: >
132          [edit system syslog]
133          +    [edit system syslog]
134               file interactive-commands { ... }
135          +    file test {
136          +        pfe critical;
137          +    }
138"""
139import collections
140
141from copy import deepcopy
142
143from ansible.module_utils.basic import AnsibleModule
144from ansible.module_utils.network.common.utils import remove_default_spec
145from ansible.module_utils.network.junos.junos import junos_argument_spec, tostring
146from ansible.module_utils.network.junos.junos import load_config, map_params_to_obj, map_obj_to_ele, to_param_list
147from ansible.module_utils.network.junos.junos import commit_configuration, discard_changes, locked_config
148
149USE_PERSISTENT_CONNECTION = True
150
151
152def validate_files(value, module):
153    if value and not 1 <= value <= 1000:
154        module.fail_json(msg='files must be between 1 and 1000')
155
156
157def validate_size(value, module):
158    if value and not 65536 <= value <= 1073741824:
159        module.fail_json(msg='size must be between 65536 and 1073741824')
160
161
162def validate_rotate_frequency(value, module):
163    if value and not 1 <= value <= 59:
164        module.fail_json(msg='rotate_frequency must be between 1 and 59')
165
166
167def validate_param_values(module, obj, param=None):
168    if not param:
169        param = module.params
170    for key in obj:
171        # validate the param value (if validator func exists)
172        validator = globals().get('validate_%s' % key)
173        if callable(validator):
174            validator(param.get(key), module)
175
176
177def main():
178    """ main entry point for module execution
179    """
180    element_spec = dict(
181        dest=dict(choices=['console', 'host', 'file', 'user']),
182        name=dict(),
183        facility=dict(),
184        level=dict(),
185        rotate_frequency=dict(type='int'),
186        size=dict(type='int'),
187        files=dict(type='int'),
188        src_addr=dict(),
189        state=dict(default='present', choices=['present', 'absent']),
190        active=dict(default=True, type='bool')
191    )
192
193    aggregate_spec = deepcopy(element_spec)
194
195    # remove default in aggregate spec, to handle common arguments
196    remove_default_spec(aggregate_spec)
197
198    argument_spec = dict(
199        aggregate=dict(type='list', elements='dict', options=aggregate_spec),
200    )
201
202    argument_spec.update(element_spec)
203    argument_spec.update(junos_argument_spec)
204
205    required_if = [('dest', 'host', ['name', 'facility', 'level']),
206                   ('dest', 'file', ['name', 'facility', 'level']),
207                   ('dest', 'user', ['name', 'facility', 'level']),
208                   ('dest', 'console', ['facility', 'level'])]
209
210    module = AnsibleModule(argument_spec=argument_spec,
211                           required_if=required_if,
212                           supports_check_mode=True)
213
214    warnings = list()
215    result = {'changed': False}
216
217    if warnings:
218        result['warnings'] = warnings
219
220    params = to_param_list(module)
221
222    requests = list()
223    for param in params:
224        # if key doesn't exist in the item, get it from module.params
225        for key in param:
226            if param.get(key) is None:
227                param[key] = module.params[key]
228
229        module._check_required_if(required_if, param)
230
231        item = param.copy()
232        dest = item.get('dest')
233        if dest == 'console' and item.get('name'):
234            module.fail_json(msg="%s and %s are mutually exclusive" % ('console', 'name'))
235
236        top = 'system/syslog'
237        is_facility_key = False
238        field_top = None
239        if dest:
240            if dest == 'console':
241                field_top = dest
242                is_facility_key = True
243            else:
244                field_top = dest + '/contents'
245                is_facility_key = False
246
247        param_to_xpath_map = collections.OrderedDict()
248        param_to_xpath_map.update([
249            ('name', {'xpath': 'name', 'is_key': True, 'top': dest}),
250            ('facility', {'xpath': 'name', 'is_key': is_facility_key, 'top': field_top}),
251            ('size', {'xpath': 'size', 'leaf_only': True, 'is_key': True, 'top': 'archive'}),
252            ('files', {'xpath': 'files', 'leaf_only': True, 'is_key': True, 'top': 'archive'}),
253            ('rotate_frequency', {'xpath': 'log-rotate-frequency', 'leaf_only': True}),
254        ])
255
256        if item.get('level'):
257            param_to_xpath_map['level'] = {'xpath': item.get('level'), 'tag_only': True, 'top': field_top}
258
259        validate_param_values(module, param_to_xpath_map, param=item)
260
261        want = map_params_to_obj(module, param_to_xpath_map, param=item)
262        requests.append(map_obj_to_ele(module, want, top, param=item))
263
264    diff = None
265    with locked_config(module):
266        for req in requests:
267            diff = load_config(module, tostring(req), warnings, action='merge')
268
269        commit = not module.check_mode
270        if diff:
271            if commit:
272                commit_configuration(module)
273            else:
274                discard_changes(module)
275            result['changed'] = True
276
277            if module._diff:
278                result['diff'] = {'prepared': diff}
279
280    module.exit_json(**result)
281
282
283if __name__ == "__main__":
284    main()
285