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: ios_system 27version_added: "2.3" 28author: "Peter Sprygada (@privateip)" 29short_description: Manage the system attributes on Cisco IOS devices 30description: 31 - This module provides declarative management of node system attributes 32 on Cisco IOS devices. It provides an option to configure host system 33 parameters or remove those parameters from the device active 34 configuration. 35extends_documentation_fragment: ios 36notes: 37 - Tested against IOS 15.6 38options: 39 hostname: 40 description: 41 - Configure the device hostname parameter. This option takes an ASCII string value. 42 domain_name: 43 description: 44 - Configure the IP domain name 45 on the remote device to the provided value. Value 46 should be in the dotted name form and will be 47 appended to the C(hostname) to create a fully-qualified 48 domain name. 49 domain_search: 50 description: 51 - Provides the list of domain suffixes to 52 append to the hostname for the purpose of doing name resolution. 53 This argument accepts a list of names and will be reconciled 54 with the current active configuration on the running node. 55 lookup_source: 56 description: 57 - Provides one or more source 58 interfaces to use for performing DNS lookups. The interface 59 provided in C(lookup_source) must be a valid interface configured 60 on the device. 61 lookup_enabled: 62 description: 63 - Administrative control 64 for enabling or disabling DNS lookups. When this argument is 65 set to True, lookups are performed and when it is set to False, 66 lookups are not performed. 67 type: bool 68 name_servers: 69 description: 70 - List of DNS name servers by IP address to use to perform name resolution 71 lookups. This argument accepts either a list of DNS servers See 72 examples. 73 state: 74 description: 75 - State of the configuration 76 values in the device's current active configuration. When set 77 to I(present), the values should be configured in the device active 78 configuration and when set to I(absent) the values should not be 79 in the device active configuration 80 default: present 81 choices: ['present', 'absent'] 82""" 83 84EXAMPLES = """ 85- name: configure hostname and domain name 86 ios_system: 87 hostname: ios01 88 domain_name: test.example.com 89 domain_search: 90 - ansible.com 91 - redhat.com 92 - cisco.com 93 94- name: remove configuration 95 ios_system: 96 state: absent 97 98- name: configure DNS lookup sources 99 ios_system: 100 lookup_source: MgmtEth0/0/CPU0/0 101 lookup_enabled: yes 102 103- name: configure name servers 104 ios_system: 105 name_servers: 106 - 8.8.8.8 107 - 8.8.4.4 108""" 109 110RETURN = """ 111commands: 112 description: The list of configuration mode commands to send to the device 113 returned: always 114 type: list 115 sample: 116 - hostname ios01 117 - ip domain name test.example.com 118""" 119import re 120 121from ansible.module_utils.basic import AnsibleModule 122from ansible.module_utils.network.ios.ios import get_config, load_config 123from ansible.module_utils.network.ios.ios import ios_argument_spec, check_args 124from ansible.module_utils.network.common.utils import ComplexList 125 126_CONFIGURED_VRFS = None 127 128 129def has_vrf(module, vrf): 130 global _CONFIGURED_VRFS 131 if _CONFIGURED_VRFS is not None: 132 return vrf in _CONFIGURED_VRFS 133 config = get_config(module) 134 _CONFIGURED_VRFS = re.findall(r'vrf definition (\S+)', config) 135 return vrf in _CONFIGURED_VRFS 136 137 138def requires_vrf(module, vrf): 139 if not has_vrf(module, vrf): 140 module.fail_json(msg='vrf %s is not configured' % vrf) 141 142 143def diff_list(want, have): 144 adds = [w for w in want if w not in have] 145 removes = [h for h in have if h not in want] 146 return (adds, removes) 147 148 149def map_obj_to_commands(want, have, module): 150 commands = list() 151 state = module.params['state'] 152 153 def needs_update(x): 154 return want.get(x) is not None and (want.get(x) != have.get(x)) 155 156 if state == 'absent': 157 if have['hostname'] != 'Router': 158 commands.append('no hostname') 159 160 if have['lookup_source']: 161 commands.append('no ip domain lookup source-interface %s' % have['lookup_source']) 162 163 if have['lookup_enabled'] is False: 164 commands.append('ip domain lookup') 165 166 vrfs = set() 167 for item in have['domain_name']: 168 if item['vrf'] and item['vrf'] not in vrfs: 169 vrfs.add(item['vrf']) 170 commands.append('no ip domain name vrf %s' % item['vrf']) 171 elif None not in vrfs: 172 vrfs.add(None) 173 commands.append('no ip domain name') 174 175 vrfs = set() 176 for item in have['domain_search']: 177 if item['vrf'] and item['vrf'] not in vrfs: 178 vrfs.add(item['vrf']) 179 commands.append('no ip domain list vrf %s' % item['vrf']) 180 elif None not in vrfs: 181 vrfs.add(None) 182 commands.append('no ip domain list') 183 184 vrfs = set() 185 for item in have['name_servers']: 186 if item['vrf'] and item['vrf'] not in vrfs: 187 vrfs.add(item['vrf']) 188 commands.append('no ip name-server vrf %s' % item['vrf']) 189 elif None not in vrfs: 190 vrfs.add(None) 191 commands.append('no ip name-server') 192 193 elif state == 'present': 194 if needs_update('hostname'): 195 commands.append('hostname %s' % want['hostname']) 196 197 if needs_update('lookup_source'): 198 commands.append('ip domain lookup source-interface %s' % want['lookup_source']) 199 200 if needs_update('lookup_enabled'): 201 cmd = 'ip domain lookup' 202 if want['lookup_enabled'] is False: 203 cmd = 'no %s' % cmd 204 commands.append(cmd) 205 206 if want['domain_name']: 207 adds, removes = diff_list(want['domain_name'], have['domain_name']) 208 for item in removes: 209 if item['vrf']: 210 commands.append('no ip domain name vrf %s %s' % (item['vrf'], item['name'])) 211 else: 212 commands.append('no ip domain name %s' % item['name']) 213 for item in adds: 214 if item['vrf']: 215 requires_vrf(module, item['vrf']) 216 commands.append('ip domain name vrf %s %s' % (item['vrf'], item['name'])) 217 else: 218 commands.append('ip domain name %s' % item['name']) 219 220 if want['domain_search']: 221 adds, removes = diff_list(want['domain_search'], have['domain_search']) 222 for item in removes: 223 if item['vrf']: 224 commands.append('no ip domain list vrf %s %s' % (item['vrf'], item['name'])) 225 else: 226 commands.append('no ip domain list %s' % item['name']) 227 for item in adds: 228 if item['vrf']: 229 requires_vrf(module, item['vrf']) 230 commands.append('ip domain list vrf %s %s' % (item['vrf'], item['name'])) 231 else: 232 commands.append('ip domain list %s' % item['name']) 233 234 if want['name_servers']: 235 adds, removes = diff_list(want['name_servers'], have['name_servers']) 236 for item in removes: 237 if item['vrf']: 238 commands.append('no ip name-server vrf %s %s' % (item['vrf'], item['server'])) 239 else: 240 commands.append('no ip name-server %s' % item['server']) 241 for item in adds: 242 if item['vrf']: 243 requires_vrf(module, item['vrf']) 244 commands.append('ip name-server vrf %s %s' % (item['vrf'], item['server'])) 245 else: 246 commands.append('ip name-server %s' % item['server']) 247 248 return commands 249 250 251def parse_hostname(config): 252 match = re.search(r'^hostname (\S+)', config, re.M) 253 return match.group(1) 254 255 256def parse_domain_name(config): 257 match = re.findall(r'^ip domain[- ]name (?:vrf (\S+) )*(\S+)', config, re.M) 258 matches = list() 259 for vrf, name in match: 260 if not vrf: 261 vrf = None 262 matches.append({'name': name, 'vrf': vrf}) 263 return matches 264 265 266def parse_domain_search(config): 267 match = re.findall(r'^ip domain[- ]list (?:vrf (\S+) )*(\S+)', config, re.M) 268 matches = list() 269 for vrf, name in match: 270 if not vrf: 271 vrf = None 272 matches.append({'name': name, 'vrf': vrf}) 273 return matches 274 275 276def parse_name_servers(config): 277 match = re.findall(r'^ip name-server (?:vrf (\S+) )*(.*)', config, re.M) 278 matches = list() 279 for vrf, servers in match: 280 if not vrf: 281 vrf = None 282 for server in servers.split(): 283 matches.append({'server': server, 'vrf': vrf}) 284 return matches 285 286 287def parse_lookup_source(config): 288 match = re.search(r'ip domain[- ]lookup source-interface (\S+)', config, re.M) 289 if match: 290 return match.group(1) 291 292 293def map_config_to_obj(module): 294 config = get_config(module) 295 return { 296 'hostname': parse_hostname(config), 297 'domain_name': parse_domain_name(config), 298 'domain_search': parse_domain_search(config), 299 'lookup_source': parse_lookup_source(config), 300 'lookup_enabled': 'no ip domain lookup' not in config and 'no ip domain-lookup' not in config, 301 'name_servers': parse_name_servers(config) 302 } 303 304 305def map_params_to_obj(module): 306 obj = { 307 'hostname': module.params['hostname'], 308 'lookup_source': module.params['lookup_source'], 309 'lookup_enabled': module.params['lookup_enabled'], 310 } 311 312 domain_name = ComplexList(dict( 313 name=dict(key=True), 314 vrf=dict() 315 ), module) 316 317 domain_search = ComplexList(dict( 318 name=dict(key=True), 319 vrf=dict() 320 ), module) 321 322 name_servers = ComplexList(dict( 323 server=dict(key=True), 324 vrf=dict() 325 ), module) 326 327 for arg, cast in [('domain_name', domain_name), 328 ('domain_search', domain_search), 329 ('name_servers', name_servers)]: 330 331 if module.params[arg]: 332 obj[arg] = cast(module.params[arg]) 333 else: 334 obj[arg] = None 335 336 return obj 337 338 339def main(): 340 """ Main entry point for Ansible module execution 341 """ 342 argument_spec = dict( 343 hostname=dict(), 344 345 domain_name=dict(type='list'), 346 domain_search=dict(type='list'), 347 name_servers=dict(type='list'), 348 349 lookup_source=dict(), 350 lookup_enabled=dict(type='bool'), 351 352 state=dict(choices=['present', 'absent'], default='present') 353 ) 354 355 argument_spec.update(ios_argument_spec) 356 357 module = AnsibleModule(argument_spec=argument_spec, 358 supports_check_mode=True) 359 360 result = {'changed': False} 361 362 warnings = list() 363 check_args(module, warnings) 364 result['warnings'] = warnings 365 366 want = map_params_to_obj(module) 367 have = map_config_to_obj(module) 368 369 commands = map_obj_to_commands(want, have, module) 370 result['commands'] = commands 371 372 if commands: 373 if not module.check_mode: 374 load_config(module, commands) 375 result['changed'] = True 376 377 module.exit_json(**result) 378 379 380if __name__ == "__main__": 381 main() 382