1#!/usr/bin/python 2# 3# Copyright: Ansible Team 4# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 5 6from __future__ import absolute_import, division, print_function 7__metaclass__ = type 8 9 10ANSIBLE_METADATA = {'metadata_version': '1.1', 11 'status': ['preview'], 12 'supported_by': 'community'} 13 14DOCUMENTATION = """ 15--- 16module: aireos_config 17version_added: "2.4" 18author: "James Mighion (@jmighion)" 19short_description: Manage Cisco WLC configurations 20description: 21 - AireOS does not use a block indent file syntax, so there are no sections or parents. 22 This module provides an implementation for working with AireOS configurations in 23 a deterministic way. 24extends_documentation_fragment: aireos 25options: 26 lines: 27 description: 28 - The ordered set of commands that should be configured. 29 The commands must be the exact same commands as found 30 in the device run-config. Be sure to note the configuration 31 command syntax as some commands are automatically modified by the 32 device config parser. 33 aliases: ['commands'] 34 src: 35 description: 36 - Specifies the source path to the file that contains the configuration 37 or configuration template to load. The path to the source file can 38 either be the full path on the Ansible control host or a relative 39 path from the playbook or role root directory. This argument is mutually 40 exclusive with I(lines). 41 before: 42 description: 43 - The ordered set of commands to push on to the command stack if 44 a change needs to be made. This allows the playbook designer 45 the opportunity to perform configuration commands prior to pushing 46 any changes without affecting how the set of commands are matched 47 against the system. 48 after: 49 description: 50 - The ordered set of commands to append to the end of the command 51 stack if a change needs to be made. Just like with I(before) this 52 allows the playbook designer to append a set of commands to be 53 executed after the command set. 54 match: 55 description: 56 - Instructs the module on the way to perform the matching of 57 the set of commands against the current device config. If 58 match is set to I(line), commands are matched line by line. 59 If match is set to I(none), the module will not attempt to 60 compare the source configuration with the running 61 configuration on the remote device. 62 default: line 63 choices: ['line', 'none'] 64 backup: 65 description: 66 - This argument will cause the module to create a full backup of 67 the current C(running-config) from the remote device before any 68 changes are made. If the C(backup_options) value is not given, 69 the backup file is written to the C(backup) folder in the playbook 70 root directory. If the directory does not exist, it is created. 71 type: bool 72 default: 'no' 73 running_config: 74 description: 75 - The module, by default, will connect to the remote device and 76 retrieve the current running-config to use as a base for comparing 77 against the contents of source. There are times when it is not 78 desirable to have the task get the current running-config for 79 every task in a playbook. The I(running_config) argument allows the 80 implementer to pass in the configuration to use as the base 81 config for comparison. 82 aliases: ['config'] 83 save: 84 description: 85 - The C(save) argument instructs the module to save the 86 running-config to startup-config. This operation is performed 87 after any changes are made to the current running config. If 88 no changes are made, the configuration is still saved to the 89 startup config. This option will always cause the module to 90 return changed. This argument is mutually exclusive with I(save_when). 91 - This option is deprecated as of Ansible 2.7, use C(save_when) 92 type: bool 93 default: 'no' 94 save_when: 95 description: 96 - When changes are made to the device running-configuration, the 97 changes are not copied to non-volatile storage by default. Using 98 this argument will change that. If the argument is set to 99 I(always), then the running-config will always be copied to the 100 startup-config and the module will always return as changed. 101 If the argument is set to I(never), the running-config will never 102 be copied to the startup-config. If the argument is set to I(changed), 103 then the running-config will only be copied to the startup-config if 104 the task has made a change. 105 default: never 106 choices: ['always', 'never', 'changed'] 107 version_added: "2.7" 108 diff_against: 109 description: 110 - When using the C(ansible-playbook --diff) command line argument 111 the module can generate diffs against different sources. 112 - When this option is configured as I(intended), the module will 113 return the diff of the running-config against the configuration 114 provided in the C(intended_config) argument. 115 - When this option is configured as I(running), the module will 116 return the before and after diff of the running-config with respect 117 to any changes made to the device configuration. 118 choices: ['intended', 'running'] 119 diff_ignore_lines: 120 description: 121 - Use this argument to specify one or more lines that should be 122 ignored during the diff. This is used for lines in the configuration 123 that are automatically updated by the system. This argument takes 124 a list of regular expressions or exact line matches. 125 intended_config: 126 description: 127 - The C(intended_config) provides the master configuration that 128 the node should conform to and is used to check the final 129 running-config against. This argument will not modify any settings 130 on the remote device and is strictly used to check the compliance 131 of the current device's configuration against. When specifying this 132 argument, the task should also modify the C(diff_against) value and 133 set it to I(intended). 134 backup_options: 135 description: 136 - This is a dict object containing configurable options related to backup file path. 137 The value of this option is read only when C(backup) is set to I(yes), if C(backup) is set 138 to I(no) this option will be silently ignored. 139 suboptions: 140 filename: 141 description: 142 - The filename to be used to store the backup configuration. If the the filename 143 is not given it will be generated based on the hostname, current time and date 144 in format defined by <hostname>_config.<current-date>@<current-time> 145 dir_path: 146 description: 147 - This option provides the path ending with directory name in which the backup 148 configuration file will be stored. If the directory does not exist it will be first 149 created and the filename is either the value of C(filename) or default filename 150 as described in C(filename) options description. If the path value is not given 151 in that case a I(backup) directory will be created in the current working directory 152 and backup configuration will be copied in C(filename) within I(backup) directory. 153 type: path 154 type: dict 155 version_added: "2.8" 156""" 157 158EXAMPLES = """ 159- name: configure configuration 160 aireos_config: 161 lines: sysname testDevice 162 163- name: diff the running-config against a provided config 164 aireos_config: 165 diff_against: intended 166 intended: "{{ lookup('file', 'master.cfg') }}" 167 168- name: load new acl into device 169 aireos_config: 170 lines: 171 - acl create testACL 172 - acl rule protocol testACL 1 any 173 - acl rule direction testACL 3 in 174 before: acl delete testACL 175 176- name: configurable backup path 177 aireos_config: 178 backup: yes 179 lines: sysname testDevice 180 backup_options: 181 filename: backup.cfg 182 dir_path: /home/user 183""" 184 185RETURN = """ 186commands: 187 description: The set of commands that will be pushed to the remote device 188 returned: always 189 type: list 190 sample: ['hostname foo', 'vlan 1', 'name default'] 191updates: 192 description: The set of commands that will be pushed to the remote device 193 returned: always 194 type: list 195 sample: ['hostname foo', 'vlan 1', 'name default'] 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/aireos_config.2016-07-16@22:28:34 201""" 202from ansible.module_utils.network.aireos.aireos import run_commands, get_config, load_config 203from ansible.module_utils.network.aireos.aireos import aireos_argument_spec 204from ansible.module_utils.network.aireos.aireos import check_args as aireos_check_args 205from ansible.module_utils.basic import AnsibleModule 206from ansible.module_utils.network.common.config import NetworkConfig, dumps 207 208 209def get_running_config(module, config=None): 210 contents = module.params['running_config'] 211 if not contents: 212 if config: 213 contents = config 214 else: 215 contents = get_config(module) 216 return NetworkConfig(indent=1, contents=contents) 217 218 219def get_candidate(module): 220 candidate = NetworkConfig(indent=1) 221 222 if module.params['src']: 223 candidate.load(module.params['src']) 224 elif module.params['lines']: 225 candidate.add(module.params['lines']) 226 return candidate 227 228 229def save_config(module, result): 230 result['changed'] = True 231 if not module.check_mode: 232 command = {"command": "save config", "prompt": "Are you sure you want to save", "answer": "y"} 233 run_commands(module, command) 234 else: 235 module.warn('Skipping command `save config` due to check_mode. Configuration not copied to ' 236 'non-volatile storage') 237 238 239def main(): 240 """ main entry point for module execution 241 """ 242 backup_spec = dict( 243 filename=dict(), 244 dir_path=dict(type='path') 245 ) 246 argument_spec = dict( 247 src=dict(type='path'), 248 249 lines=dict(aliases=['commands'], type='list'), 250 251 before=dict(type='list'), 252 after=dict(type='list'), 253 254 match=dict(default='line', choices=['line', 'none']), 255 256 running_config=dict(aliases=['config']), 257 intended_config=dict(), 258 259 backup=dict(type='bool', default=False), 260 backup_options=dict(type='dict', options=backup_spec), 261 262 # save is deprecated as of 2.7, use save_when instead 263 save=dict(type='bool', default=False, removed_in_version='2.11'), 264 save_when=dict(choices=['always', 'never', 'changed'], default='never'), 265 266 diff_against=dict(choices=['running', 'intended']), 267 diff_ignore_lines=dict(type='list') 268 ) 269 270 argument_spec.update(aireos_argument_spec) 271 272 mutually_exclusive = [('lines', 'src'), 273 ('save', 'save_when')] 274 275 required_if = [('diff_against', 'intended', ['intended_config'])] 276 277 module = AnsibleModule(argument_spec=argument_spec, 278 mutually_exclusive=mutually_exclusive, 279 required_if=required_if, 280 supports_check_mode=True) 281 282 warnings = list() 283 aireos_check_args(module, warnings) 284 result = {'changed': False, 'warnings': warnings} 285 286 config = None 287 288 if module.params['backup'] or (module._diff and module.params['diff_against'] == 'running'): 289 contents = get_config(module) 290 config = NetworkConfig(indent=1, contents=contents) 291 if module.params['backup']: 292 result['__backup__'] = contents 293 294 if any((module.params['src'], module.params['lines'])): 295 match = module.params['match'] 296 297 candidate = get_candidate(module) 298 299 if match != 'none': 300 config = get_running_config(module, config) 301 configobjs = candidate.difference(config, match=match) 302 else: 303 configobjs = candidate.items 304 305 if configobjs: 306 commands = dumps(configobjs, 'commands').split('\n') 307 308 if module.params['before']: 309 commands[:0] = module.params['before'] 310 311 if module.params['after']: 312 commands.extend(module.params['after']) 313 314 result['commands'] = commands 315 result['updates'] = commands 316 317 if not module.check_mode: 318 load_config(module, commands) 319 320 result['changed'] = True 321 322 diff_ignore_lines = module.params['diff_ignore_lines'] 323 324 if module.params['save_when'] == 'always' or module.params['save']: 325 save_config(module, result) 326 elif module.params['save_when'] == 'changed' and result['changed']: 327 save_config(module, result) 328 329 if module._diff: 330 output = run_commands(module, 'show run-config commands') 331 contents = output[0] 332 333 # recreate the object in order to process diff_ignore_lines 334 running_config = NetworkConfig(indent=1, contents=contents, ignore_lines=diff_ignore_lines) 335 336 if module.params['diff_against'] == 'running': 337 if module.check_mode: 338 module.warn("unable to perform diff against running-config due to check mode") 339 contents = None 340 else: 341 contents = config.config_text 342 elif module.params['diff_against'] == 'intended': 343 contents = module.params['intended_config'] 344 345 if contents is not None: 346 base_config = NetworkConfig(indent=1, contents=contents, ignore_lines=diff_ignore_lines) 347 348 if running_config.sha1 != base_config.sha1: 349 result.update({ 350 'changed': True, 351 'diff': {'before': str(base_config), 'after': str(running_config)} 352 }) 353 354 module.exit_json(**result) 355 356 357if __name__ == '__main__': 358 main() 359