1#!/usr/bin/python 2# 3# This file is part of Ansible 4# 5# Ansible 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# Ansible 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 Ansible. If not, see <http://www.gnu.org/licenses/>. 17# 18 19ANSIBLE_METADATA = {'metadata_version': '1.1', 20 'status': ['preview'], 21 'supported_by': 'network'} 22 23 24DOCUMENTATION = """ 25--- 26module: nxos_system 27extends_documentation_fragment: nxos 28version_added: "2.3" 29author: "Peter Sprygada (@privateip)" 30short_description: Manage the system attributes on Cisco NXOS devices 31description: 32 - This module provides declarative management of node system attributes 33 on Cisco NXOS devices. It provides an option to configure host system 34 parameters or remove those parameters from the device active 35 configuration. 36options: 37 hostname: 38 description: 39 - Configure the device hostname parameter. This option takes an ASCII string value 40 or keyword 'default' 41 domain_name: 42 description: 43 - Configures the default domain 44 name suffix to be used when referencing this node by its 45 FQDN. This argument accepts either a list of domain names or 46 a list of dicts that configure the domain name and VRF name or 47 keyword 'default'. See examples. 48 domain_lookup: 49 description: 50 - Enables or disables the DNS 51 lookup feature in Cisco NXOS. This argument accepts boolean 52 values. When enabled, the system will try to resolve hostnames 53 using DNS and when disabled, hostnames will not be resolved. 54 type: bool 55 domain_search: 56 description: 57 - Configures a list of domain 58 name suffixes to search when performing DNS name resolution. 59 This argument accepts either a list of domain names or 60 a list of dicts that configure the domain name and VRF name or 61 keyword 'default'. See examples. 62 name_servers: 63 description: 64 - List of DNS name servers by IP address to use to perform name resolution 65 lookups. This argument accepts either a list of DNS servers or 66 a list of hashes that configure the name server and VRF name or 67 keyword 'default'. See examples. 68 system_mtu: 69 description: 70 - Specifies the mtu, must be an integer or keyword 'default'. 71 state: 72 description: 73 - State of the configuration 74 values in the device's current active configuration. When set 75 to I(present), the values should be configured in the device active 76 configuration and when set to I(absent) the values should not be 77 in the device active configuration 78 default: present 79 choices: ['present', 'absent'] 80""" 81 82EXAMPLES = """ 83- name: configure hostname and domain-name 84 nxos_system: 85 hostname: nxos01 86 domain_name: test.example.com 87 88- name: remove configuration 89 nxos_system: 90 state: absent 91 92- name: configure name servers 93 nxos_system: 94 name_servers: 95 - 8.8.8.8 96 - 8.8.4.4 97 98- name: configure name servers with VRF support 99 nxos_system: 100 name_servers: 101 - { server: 8.8.8.8, vrf: mgmt } 102 - { server: 8.8.4.4, vrf: mgmt } 103""" 104 105RETURN = """ 106commands: 107 description: The list of configuration mode commands to send to the device 108 returned: always 109 type: list 110 sample: 111 - hostname nxos01 112 - ip domain-name test.example.com 113""" 114import re 115 116from ansible.module_utils.network.nxos.nxos import get_config, load_config 117from ansible.module_utils.network.nxos.nxos import nxos_argument_spec, check_args 118from ansible.module_utils.basic import AnsibleModule 119from ansible.module_utils.six import iteritems 120from ansible.module_utils.network.common.config import NetworkConfig 121from ansible.module_utils.network.common.utils import ComplexList 122 123_CONFIGURED_VRFS = None 124 125 126def has_vrf(module, vrf): 127 global _CONFIGURED_VRFS 128 if _CONFIGURED_VRFS is not None: 129 return vrf in _CONFIGURED_VRFS 130 config = get_config(module) 131 _CONFIGURED_VRFS = re.findall(r'vrf context (\S+)', config) 132 return vrf in _CONFIGURED_VRFS 133 134 135def map_obj_to_commands(want, have, module): 136 commands = list() 137 state = module.params['state'] 138 139 def needs_update(x): 140 return want.get(x) and (want.get(x) != have.get(x)) 141 142 def difference(x, y, z): 143 return [item for item in x[z] if item not in y[z]] 144 145 def remove(cmd, commands, vrf=None): 146 if vrf: 147 commands.append('vrf context %s' % vrf) 148 commands.append(cmd) 149 if vrf: 150 commands.append('exit') 151 152 def add(cmd, commands, vrf=None): 153 if vrf: 154 if not has_vrf(module, vrf): 155 module.fail_json(msg='invalid vrf name %s' % vrf) 156 return remove(cmd, commands, vrf) 157 158 if state == 'absent': 159 if have['hostname']: 160 commands.append('no hostname') 161 162 for item in have['domain_name']: 163 cmd = 'no ip domain-name %s' % item['name'] 164 remove(cmd, commands, item['vrf']) 165 166 for item in have['domain_search']: 167 cmd = 'no ip domain-list %s' % item['name'] 168 remove(cmd, commands, item['vrf']) 169 170 for item in have['name_servers']: 171 cmd = 'no ip name-server %s' % item['server'] 172 remove(cmd, commands, item['vrf']) 173 174 if have['system_mtu']: 175 commands.append('no system jumbomtu') 176 177 if state == 'present': 178 if needs_update('hostname'): 179 if want['hostname'] == 'default': 180 if have['hostname']: 181 commands.append('no hostname') 182 else: 183 commands.append('hostname %s' % want['hostname']) 184 185 if want.get('domain_lookup') is not None: 186 if have.get('domain_lookup') != want.get('domain_lookup'): 187 cmd = 'ip domain-lookup' 188 if want['domain_lookup'] is False: 189 cmd = 'no %s' % cmd 190 commands.append(cmd) 191 192 if want['domain_name']: 193 if want.get('domain_name')[0]['name'] == 'default': 194 if have['domain_name']: 195 for item in have['domain_name']: 196 cmd = 'no ip domain-name %s' % item['name'] 197 remove(cmd, commands, item['vrf']) 198 else: 199 for item in difference(have, want, 'domain_name'): 200 cmd = 'no ip domain-name %s' % item['name'] 201 remove(cmd, commands, item['vrf']) 202 for item in difference(want, have, 'domain_name'): 203 cmd = 'ip domain-name %s' % item['name'] 204 add(cmd, commands, item['vrf']) 205 206 if want['domain_search']: 207 if want.get('domain_search')[0]['name'] == 'default': 208 if have['domain_search']: 209 for item in have['domain_search']: 210 cmd = 'no ip domain-list %s' % item['name'] 211 remove(cmd, commands, item['vrf']) 212 else: 213 for item in difference(have, want, 'domain_search'): 214 cmd = 'no ip domain-list %s' % item['name'] 215 remove(cmd, commands, item['vrf']) 216 for item in difference(want, have, 'domain_search'): 217 cmd = 'ip domain-list %s' % item['name'] 218 add(cmd, commands, item['vrf']) 219 220 if want['name_servers']: 221 if want.get('name_servers')[0]['server'] == 'default': 222 if have['name_servers']: 223 for item in have['name_servers']: 224 cmd = 'no ip name-server %s' % item['server'] 225 remove(cmd, commands, item['vrf']) 226 else: 227 for item in difference(have, want, 'name_servers'): 228 cmd = 'no ip name-server %s' % item['server'] 229 remove(cmd, commands, item['vrf']) 230 for item in difference(want, have, 'name_servers'): 231 cmd = 'ip name-server %s' % item['server'] 232 add(cmd, commands, item['vrf']) 233 234 if needs_update('system_mtu'): 235 if want['system_mtu'] == 'default': 236 if have['system_mtu']: 237 commands.append('no system jumbomtu') 238 else: 239 commands.append('system jumbomtu %s' % want['system_mtu']) 240 241 return commands 242 243 244def parse_hostname(config): 245 match = re.search(r'^hostname (\S+)', config, re.M) 246 if match: 247 return match.group(1) 248 249 250def parse_domain_name(config, vrf_config): 251 objects = list() 252 regex = re.compile(r'ip domain-name (\S+)') 253 254 match = regex.search(config, re.M) 255 if match: 256 objects.append({'name': match.group(1), 'vrf': None}) 257 258 for vrf, cfg in iteritems(vrf_config): 259 match = regex.search(cfg, re.M) 260 if match: 261 objects.append({'name': match.group(1), 'vrf': vrf}) 262 263 return objects 264 265 266def parse_domain_search(config, vrf_config): 267 objects = list() 268 269 for item in re.findall(r'^ip domain-list (\S+)', config, re.M): 270 objects.append({'name': item, 'vrf': None}) 271 272 for vrf, cfg in iteritems(vrf_config): 273 for item in re.findall(r'ip domain-list (\S+)', cfg, re.M): 274 objects.append({'name': item, 'vrf': vrf}) 275 276 return objects 277 278 279def parse_name_servers(config, vrf_config, vrfs): 280 objects = list() 281 282 match = re.search('^ip name-server (.+)$', config, re.M) 283 if match and 'use-vrf' not in match.group(1): 284 for addr in match.group(1).split(' '): 285 objects.append({'server': addr, 'vrf': None}) 286 287 for vrf, cfg in iteritems(vrf_config): 288 vrf_match = re.search('ip name-server (.+)', cfg, re.M) 289 if vrf_match: 290 for addr in vrf_match.group(1).split(' '): 291 objects.append({'server': addr, 'vrf': vrf}) 292 293 return objects 294 295 296def parse_system_mtu(config): 297 match = re.search(r'^system jumbomtu (\d+)', config, re.M) 298 if match: 299 return match.group(1) 300 301 302def map_config_to_obj(module): 303 config = get_config(module) 304 configobj = NetworkConfig(indent=2, contents=config) 305 306 vrf_config = {} 307 308 vrfs = re.findall(r'^vrf context (\S+)$', config, re.M) 309 for vrf in vrfs: 310 config_data = configobj.get_block_config(path=['vrf context %s' % vrf]) 311 vrf_config[vrf] = config_data 312 313 return { 314 'hostname': parse_hostname(config), 315 'domain_lookup': 'no ip domain-lookup' not in config, 316 'domain_name': parse_domain_name(config, vrf_config), 317 'domain_search': parse_domain_search(config, vrf_config), 318 'name_servers': parse_name_servers(config, vrf_config, vrfs), 319 'system_mtu': parse_system_mtu(config) 320 } 321 322 323def map_params_to_obj(module): 324 obj = { 325 'hostname': module.params['hostname'], 326 'domain_lookup': module.params['domain_lookup'], 327 'system_mtu': module.params['system_mtu'] 328 } 329 330 domain_name = ComplexList(dict( 331 name=dict(key=True), 332 vrf=dict() 333 ), module) 334 335 domain_search = ComplexList(dict( 336 name=dict(key=True), 337 vrf=dict() 338 ), module) 339 340 name_servers = ComplexList(dict( 341 server=dict(key=True), 342 vrf=dict() 343 ), module) 344 345 for arg, cast in [('domain_name', domain_name), ('domain_search', domain_search), 346 ('name_servers', name_servers)]: 347 if module.params[arg] is not None: 348 obj[arg] = cast(module.params[arg]) 349 else: 350 obj[arg] = None 351 352 return obj 353 354 355def main(): 356 """ main entry point for module execution 357 """ 358 argument_spec = dict( 359 hostname=dict(), 360 domain_lookup=dict(type='bool'), 361 362 # { name: <str>, vrf: <str> } 363 domain_name=dict(type='list'), 364 365 # {name: <str>, vrf: <str> } 366 domain_search=dict(type='list'), 367 368 # { server: <str>; vrf: <str> } 369 name_servers=dict(type='list'), 370 371 system_mtu=dict(type='str'), 372 state=dict(default='present', choices=['present', 'absent']) 373 ) 374 375 argument_spec.update(nxos_argument_spec) 376 377 module = AnsibleModule(argument_spec=argument_spec, 378 supports_check_mode=True) 379 380 warnings = list() 381 check_args(module, warnings) 382 383 result = {'changed': False} 384 if warnings: 385 result['warnings'] = warnings 386 387 want = map_params_to_obj(module) 388 have = map_config_to_obj(module) 389 390 commands = map_obj_to_commands(want, have, module) 391 result['commands'] = commands 392 393 if commands: 394 if not module.check_mode: 395 load_config(module, commands) 396 result['changed'] = True 397 398 module.exit_json(**result) 399 400 401if __name__ == '__main__': 402 main() 403