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