1#!/usr/bin/python 2# 3# (c) 2015 Peter Sprygada, <psprygada@ansible.com> 4# Copyright (c) 2016 Dell 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': 'community'} 14 15 16DOCUMENTATION = """ 17--- 18module: dellos9_config 19version_added: "2.2" 20author: "Dhivya P (@dhivyap)" 21short_description: Manage Dell EMC Networking OS9 configuration sections 22description: 23 - OS9 configurations use a simple block indent file syntax 24 for segmenting configuration into sections. This module provides 25 an implementation for working with OS9 configuration sections in 26 a deterministic way. 27extends_documentation_fragment: dellos9 28options: 29 lines: 30 description: 31 - The ordered set of commands that should be configured in the 32 section. The commands must be the exact same commands as found 33 in the device running-config. Be sure to note the configuration 34 command syntax as some commands are automatically modified by the 35 device config parser. This argument is mutually exclusive with I(src). 36 aliases: ['commands'] 37 parents: 38 description: 39 - The ordered set of parents that uniquely identify the section or hierarchy 40 the commands should be checked against. If the parents argument 41 is omitted, the commands are checked against the set of top 42 level or global commands. 43 src: 44 description: 45 - Specifies the source path to the file that contains the configuration 46 or configuration template to load. The path to the source file can 47 either be the full path on the Ansible control host or a relative 48 path from the playbook or role root directory. This argument is 49 mutually exclusive with I(lines). 50 before: 51 description: 52 - The ordered set of commands to push on to the command stack if 53 a change needs to be made. This allows the playbook designer 54 the opportunity to perform configuration commands prior to pushing 55 any changes without affecting how the set of commands are matched 56 against the system. 57 after: 58 description: 59 - The ordered set of commands to append to the end of the command 60 stack if a change needs to be made. Just like with I(before) this 61 allows the playbook designer to append a set of commands to be 62 executed after the command set. 63 match: 64 description: 65 - Instructs the module on the way to perform the matching of 66 the set of commands against the current device config. If 67 match is set to I(line), commands are matched line by line. If 68 match is set to I(strict), command lines are matched with respect 69 to position. If match is set to I(exact), command lines 70 must be an equal match. Finally, if match is set to I(none), the 71 module will not attempt to compare the source configuration with 72 the running configuration on the remote device. 73 default: line 74 choices: ['line', 'strict', 'exact', 'none'] 75 replace: 76 description: 77 - Instructs the module on the way to perform the configuration 78 on the device. If the replace argument is set to I(line) then 79 the modified lines are pushed to the device in configuration 80 mode. If the replace argument is set to I(block) then the entire 81 command block is pushed to the device in configuration mode if any 82 line is not correct. 83 default: line 84 choices: ['line', 'block'] 85 update: 86 description: 87 - The I(update) argument controls how the configuration statements 88 are processed on the remote device. Valid choices for the I(update) 89 argument are I(merge) and I(check). When you set this argument to 90 I(merge), the configuration changes merge with the current 91 device running configuration. When you set this argument to I(check) 92 the configuration updates are determined but not actually configured 93 on the remote device. 94 default: merge 95 choices: ['merge', 'check'] 96 save: 97 description: 98 - The C(save) argument instructs the module to save the running- 99 config to the startup-config at the conclusion of the module 100 running. If check mode is specified, this argument is ignored. 101 type: bool 102 default: no 103 config: 104 description: 105 - The module, by default, will connect to the remote device and 106 retrieve the current running-config to use as a base for comparing 107 against the contents of source. There are times when it is not 108 desirable to have the task get the current running-config for 109 every task in a playbook. The I(config) argument allows the 110 implementer to pass in the configuration to use as the base 111 config for comparison. 112 backup: 113 description: 114 - This argument will cause the module to create a full backup of 115 the current C(running-config) from the remote device before any 116 changes are made. If the C(backup_options) value is not given, 117 the backup file is written to the C(backup) folder in the playbook 118 root directory. If the directory does not exist, it is created. 119 type: bool 120 default: 'no' 121 backup_options: 122 description: 123 - This is a dict object containing configurable options related to backup file path. 124 The value of this option is read only when C(backup) is set to I(yes), if C(backup) is set 125 to I(no) this option will be silently ignored. 126 suboptions: 127 filename: 128 description: 129 - The filename to be used to store the backup configuration. If the the filename 130 is not given it will be generated based on the hostname, current time and date 131 in format defined by <hostname>_config.<current-date>@<current-time> 132 dir_path: 133 description: 134 - This option provides the path ending with directory name in which the backup 135 configuration file will be stored. If the directory does not exist it will be first 136 created and the filename is either the value of C(filename) or default filename 137 as described in C(filename) options description. If the path value is not given 138 in that case a I(backup) directory will be created in the current working directory 139 and backup configuration will be copied in C(filename) within I(backup) directory. 140 type: path 141 type: dict 142 version_added: "2.8" 143notes: 144 - This module requires Dell OS9 version 9.10.0.1P13 or above. 145 146 - This module requires to increase the ssh connection rate limit. 147 Use the following command I(ip ssh connection-rate-limit 60) 148 to configure the same. This can also be done with the 149 M(dellos9_config) module. 150""" 151 152EXAMPLES = """ 153- dellos9_config: 154 lines: ['hostname {{ inventory_hostname }}'] 155 provider: "{{ cli }}" 156 157- dellos9_config: 158 lines: 159 - 10 permit ip host 1.1.1.1 any log 160 - 20 permit ip host 2.2.2.2 any log 161 - 30 permit ip host 3.3.3.3 any log 162 - 40 permit ip host 4.4.4.4 any log 163 - 50 permit ip host 5.5.5.5 any log 164 parents: ['ip access-list extended test'] 165 before: ['no ip access-list extended test'] 166 match: exact 167 168- dellos9_config: 169 lines: 170 - 10 permit ip host 1.1.1.1 any log 171 - 20 permit ip host 2.2.2.2 any log 172 - 30 permit ip host 3.3.3.3 any log 173 - 40 permit ip host 4.4.4.4 any log 174 parents: ['ip access-list extended test'] 175 before: ['no ip access-list extended test'] 176 replace: block 177 178- dellos9_config: 179 lines: ['hostname {{ inventory_hostname }}'] 180 provider: "{{ cli }}" 181 backup: yes 182 backup_options: 183 filename: backup.cfg 184 dir_path: /home/user 185""" 186 187RETURN = """ 188updates: 189 description: The set of commands that will be pushed to the remote device. 190 returned: always 191 type: list 192 sample: ['hostname foo', 'router bgp 1', 'bgp router-id 1.1.1.1'] 193commands: 194 description: The set of commands that will be pushed to the remote device 195 returned: always 196 type: list 197 sample: ['hostname foo', 'router bgp 1', 'bgp router-id 1.1.1.1'] 198saved: 199 description: Returns whether the configuration is saved to the startup 200 configuration or not. 201 returned: When not check_mode. 202 type: bool 203 sample: True 204backup_path: 205 description: The full path to the backup file 206 returned: when backup is yes 207 type: str 208 sample: /playbooks/ansible/backup/dellos9_config.2016-07-16@22:28:34 209""" 210from ansible.module_utils.basic import AnsibleModule 211from ansible.module_utils.network.dellos9.dellos9 import get_config, get_sublevel_config 212from ansible.module_utils.network.dellos9.dellos9 import dellos9_argument_spec, check_args 213from ansible.module_utils.network.dellos9.dellos9 import load_config, run_commands 214from ansible.module_utils.network.dellos9.dellos9 import WARNING_PROMPTS_RE 215from ansible.module_utils.network.common.config import NetworkConfig, dumps 216 217 218def get_candidate(module): 219 candidate = NetworkConfig(indent=1) 220 if module.params['src']: 221 candidate.load(module.params['src']) 222 elif module.params['lines']: 223 parents = module.params['parents'] or list() 224 commands = module.params['lines'][0] 225 if (isinstance(commands, dict)) and (isinstance(commands['command'], list)): 226 candidate.add(commands['command'], parents=parents) 227 elif (isinstance(commands, dict)) and (isinstance(commands['command'], str)): 228 candidate.add([commands['command']], parents=parents) 229 else: 230 candidate.add(module.params['lines'], parents=parents) 231 return candidate 232 233 234def get_running_config(module): 235 contents = module.params['config'] 236 if not contents: 237 contents = get_config(module) 238 return contents 239 240 241def main(): 242 243 backup_spec = dict( 244 filename=dict(), 245 dir_path=dict(type='path') 246 ) 247 argument_spec = dict( 248 lines=dict(aliases=['commands'], type='list'), 249 parents=dict(type='list'), 250 251 src=dict(type='path'), 252 253 before=dict(type='list'), 254 after=dict(type='list'), 255 256 match=dict(default='line', 257 choices=['line', 'strict', 'exact', 'none']), 258 replace=dict(default='line', choices=['line', 'block']), 259 260 update=dict(choices=['merge', 'check'], default='merge'), 261 save=dict(type='bool', default=False), 262 config=dict(), 263 backup=dict(type='bool', default=False), 264 backup_options=dict(type='dict', options=backup_spec) 265 ) 266 267 argument_spec.update(dellos9_argument_spec) 268 269 mutually_exclusive = [('lines', 'src'), 270 ('parents', 'src')] 271 module = AnsibleModule(argument_spec=argument_spec, 272 mutually_exclusive=mutually_exclusive, 273 supports_check_mode=True) 274 275 parents = module.params['parents'] or list() 276 277 match = module.params['match'] 278 replace = module.params['replace'] 279 280 warnings = list() 281 check_args(module, warnings) 282 283 result = dict(changed=False, saved=False, warnings=warnings) 284 285 candidate = get_candidate(module) 286 287 if module.params['backup']: 288 if not module.check_mode: 289 result['__backup__'] = get_config(module) 290 commands = list() 291 292 if any((module.params['lines'], module.params['src'])): 293 if match != 'none': 294 config = get_running_config(module) 295 if parents: 296 contents = get_sublevel_config(config, module) 297 config = NetworkConfig(contents=contents, indent=1) 298 else: 299 config = NetworkConfig(contents=config, indent=1) 300 configobjs = candidate.difference(config, match=match, replace=replace) 301 else: 302 configobjs = candidate.items 303 304 if configobjs: 305 commands = dumps(configobjs, 'commands') 306 if ((isinstance(module.params['lines'], list)) and 307 (isinstance(module.params['lines'][0], dict)) and 308 set(['prompt', 'answer']).issubset(module.params['lines'][0])): 309 310 cmd = {'command': commands, 311 'prompt': module.params['lines'][0]['prompt'], 312 'answer': module.params['lines'][0]['answer']} 313 commands = [module.jsonify(cmd)] 314 else: 315 commands = commands.split('\n') 316 317 if module.params['before']: 318 commands[:0] = module.params['before'] 319 320 if module.params['after']: 321 commands.extend(module.params['after']) 322 323 if not module.check_mode and module.params['update'] == 'merge': 324 load_config(module, commands) 325 326 result['changed'] = True 327 result['commands'] = commands 328 result['updates'] = commands 329 330 if module.params['save']: 331 result['changed'] = True 332 if not module.check_mode: 333 cmd = {'command': 'copy running-config startup-config', 334 'prompt': r'\[confirm yes/no\]:\s?$', 'answer': 'yes'} 335 run_commands(module, [cmd]) 336 result['saved'] = True 337 else: 338 module.warn('Skipping command `copy running-config startup-config`' 339 'due to check_mode. Configuration not copied to ' 340 'non-volatile storage') 341 342 module.exit_json(**result) 343 344 345if __name__ == '__main__': 346 main() 347