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_netconf 19version_added: "2.1" 20author: "Peter Sprygada (@privateip)" 21short_description: Configures the Junos Netconf system service 22description: 23 - This module provides an abstraction that enables and configures 24 the netconf system service running on Junos devices. This module 25 can be used to easily enable the Netconf API. Netconf provides 26 a programmatic interface for working with configuration and state 27 resources as defined in RFC 6242. If the C(netconf_port) is not 28 mentioned in the task by default netconf will be enabled on port 830 29 only. 30extends_documentation_fragment: junos 31options: 32 netconf_port: 33 description: 34 - This argument specifies the port the netconf service should 35 listen on for SSH connections. The default port as defined 36 in RFC 6242 is 830. 37 required: false 38 default: 830 39 aliases: ['listens_on'] 40 version_added: "2.2" 41 state: 42 description: 43 - Specifies the state of the C(junos_netconf) resource on 44 the remote device. If the I(state) argument is set to 45 I(present) the netconf service will be configured. If the 46 I(state) argument is set to I(absent) the netconf service 47 will be removed from the configuration. 48 required: false 49 default: present 50 choices: ['present', 'absent'] 51notes: 52 - Tested against vSRX JUNOS version 15.1X49-D15.4, vqfx-10000 JUNOS Version 15.1X53-D60.4. 53 - Recommended connection is C(network_cli). See L(the Junos OS Platform Options,../network/user_guide/platform_junos.html). 54 - This module also works with C(local) connections for legacy playbooks. 55 - If C(netconf_port) value is not mentioned in task by default it will be enabled on port 830 only. 56 Although C(netconf_port) value can be from 1 through 65535, avoid configuring access on a port 57 that is normally assigned for another service. This practice avoids potential resource conflicts. 58""" 59 60EXAMPLES = """ 61- name: enable netconf service on port 830 62 junos_netconf: 63 listens_on: 830 64 state: present 65 66- name: disable netconf service 67 junos_netconf: 68 state: absent 69""" 70 71RETURN = """ 72commands: 73 description: Returns the command sent to the remote device 74 returned: when changed is True 75 type: str 76 sample: 'set system services netconf ssh port 830' 77""" 78import re 79 80from ansible.module_utils._text import to_text 81from ansible.module_utils.connection import ConnectionError 82from ansible.module_utils.basic import AnsibleModule 83from ansible.module_utils.network.junos.junos import junos_argument_spec, get_connection 84from ansible.module_utils.network.common.utils import to_list 85from ansible.module_utils.six import iteritems 86 87USE_PERSISTENT_CONNECTION = True 88 89 90def map_obj_to_commands(updates, module): 91 want, have = updates 92 commands = list() 93 94 if want['state'] == 'absent': 95 if have['state'] == 'present': 96 commands.append('delete system services netconf') 97 else: 98 if have['state'] == 'absent' or want['netconf_port'] != have.get('netconf_port'): 99 commands.append( 100 'set system services netconf ssh port %s' % want['netconf_port'] 101 ) 102 103 return commands 104 105 106def parse_port(config): 107 match = re.search(r'port (\d+)', config) 108 if match: 109 return int(match.group(1)) 110 111 112def map_config_to_obj(module): 113 conn = get_connection(module) 114 out = conn.get(command='show configuration system services netconf') 115 if out is None: 116 module.fail_json(msg='unable to retrieve current config') 117 config = str(out).strip() 118 119 obj = {'state': 'absent'} 120 if 'ssh' in config: 121 obj.update({ 122 'state': 'present', 123 'netconf_port': parse_port(config) 124 }) 125 return obj 126 127 128def validate_netconf_port(value, module): 129 if not 1 <= value <= 65535: 130 module.fail_json(msg='netconf_port must be between 1 and 65535') 131 132 133def map_params_to_obj(module): 134 obj = { 135 'netconf_port': module.params['netconf_port'], 136 'state': module.params['state'] 137 } 138 139 for key, value in iteritems(obj): 140 # validate the param value (if validator func exists) 141 validator = globals().get('validate_%s' % key) 142 if callable(validator): 143 validator(value, module) 144 145 return obj 146 147 148def load_config(module, config, commit=False): 149 conn = get_connection(module) 150 try: 151 resp = conn.edit_config(to_list(config) + ['top'], commit) 152 except ConnectionError as exc: 153 module.fail_json(msg=to_text(exc, errors='surrogate_then_replace')) 154 155 diff = resp.get('diff', '') 156 return to_text(diff, errors='surrogate_then_replace').strip() 157 158 159def main(): 160 """main entry point for module execution 161 """ 162 argument_spec = dict( 163 netconf_port=dict(type='int', default=830, aliases=['listens_on']), 164 state=dict(default='present', choices=['present', 'absent']), 165 ) 166 167 argument_spec.update(junos_argument_spec) 168 169 module = AnsibleModule(argument_spec=argument_spec, 170 supports_check_mode=True) 171 172 warnings = list() 173 result = {'changed': False, 'warnings': warnings} 174 175 want = map_params_to_obj(module) 176 have = map_config_to_obj(module) 177 178 commands = map_obj_to_commands((want, have), module) 179 result['commands'] = commands 180 181 if commands: 182 commit = not module.check_mode 183 diff = load_config(module, commands, commit=commit) 184 if diff: 185 if module._diff: 186 result['diff'] = {'prepared': diff} 187 result['changed'] = True 188 189 module.exit_json(**result) 190 191 192if __name__ == '__main__': 193 main() 194