mirror of
				https://github.com/ansible-collections/community.general.git
				synced 2025-10-26 21:59:38 -07:00 
			
		
		
		
	* Add configurable backup path option for network config modules Fixes #50283 Fixes #32724 * Add back_options in network config module argspec * Handle backup path options in network action plugin * Fix review comments * Add integration tests * Update changelog
		
			
				
	
	
		
			310 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			310 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| #!/usr/bin/python
 | |
| # -*- coding: utf-8 -*-
 | |
| #
 | |
| # (C) 2017 Red Hat Inc.
 | |
| # Copyright (C) 2017 Lenovo.
 | |
| #
 | |
| # GNU General Public License v3.0+
 | |
| #
 | |
| # This program is distributed in the hope that it will be useful,
 | |
| # but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | |
| # GNU General Public License for more details.
 | |
| #
 | |
| # (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
 | |
| #
 | |
| # Module to configure Lenovo Switches.
 | |
| # Lenovo Networking
 | |
| #
 | |
| from __future__ import absolute_import, division, print_function
 | |
| __metaclass__ = type
 | |
| 
 | |
| 
 | |
| ANSIBLE_METADATA = {'metadata_version': '1.1',
 | |
|                     'status': ['preview'],
 | |
|                     'supported_by': 'community'}
 | |
| 
 | |
| 
 | |
| DOCUMENTATION = """
 | |
| ---
 | |
| module: enos_config
 | |
| version_added: "2.5"
 | |
| author: "Anil Kumar Muraleedharan (@amuraleedhar)"
 | |
| short_description: Manage Lenovo ENOS configuration sections
 | |
| description:
 | |
|   - Lenovo ENOS configurations use a simple block indent file syntax
 | |
|     for segmenting configuration into sections.  This module provides
 | |
|     an implementation for working with ENOS configuration sections in
 | |
|     a deterministic way.
 | |
| extends_documentation_fragment: enos
 | |
| notes:
 | |
|   - Tested against ENOS 8.4.1
 | |
| options:
 | |
|   lines:
 | |
|     description:
 | |
|       - The ordered set of commands that should be configured in the
 | |
|         section.  The commands must be the exact same commands as found
 | |
|         in the device running-config.  Be sure to note the configuration
 | |
|         command syntax as some commands are automatically modified by the
 | |
|         device config parser.
 | |
|     aliases: ['commands']
 | |
|   parents:
 | |
|     description:
 | |
|       - The ordered set of parents that uniquely identify the section
 | |
|         the commands should be checked against.  If the parents argument
 | |
|         is omitted, the commands are checked against the set of top
 | |
|         level or global commands.
 | |
|   src:
 | |
|     description:
 | |
|       - Specifies the source path to the file that contains the configuration
 | |
|         or configuration template to load.  The path to the source file can
 | |
|         either be the full path on the Ansible control host or a relative
 | |
|         path from the playbook or role root directory.  This argument is
 | |
|         mutually exclusive with I(lines), I(parents).
 | |
|   before:
 | |
|     description:
 | |
|       - The ordered set of commands to push on to the command stack if
 | |
|         a change needs to be made.  This allows the playbook designer
 | |
|         the opportunity to perform configuration commands prior to pushing
 | |
|         any changes without affecting how the set of commands are matched
 | |
|         against the system.
 | |
|   after:
 | |
|     description:
 | |
|       - The ordered set of commands to append to the end of the command
 | |
|         stack if a change needs to be made.  Just like with I(before) this
 | |
|         allows the playbook designer to append a set of commands to be
 | |
|         executed after the command set.
 | |
|   match:
 | |
|     description:
 | |
|       - Instructs the module on the way to perform the matching of
 | |
|         the set of commands against the current device config.  If
 | |
|         match is set to I(line), commands are matched line by line.  If
 | |
|         match is set to I(strict), command lines are matched with respect
 | |
|         to position.  If match is set to I(exact), command lines
 | |
|         must be an equal match.  Finally, if match is set to I(none), the
 | |
|         module will not attempt to compare the source configuration with
 | |
|         the running configuration on the remote device.
 | |
|     default: line
 | |
|     choices: ['line', 'strict', 'exact', 'none']
 | |
|   replace:
 | |
|     description:
 | |
|       - Instructs the module on the way to perform the configuration
 | |
|         on the device.  If the replace argument is set to I(line) then
 | |
|         the modified lines are pushed to the device in configuration
 | |
|         mode.  If the replace argument is set to I(block) then the entire
 | |
|         command block is pushed to the device in configuration mode if any
 | |
|         line is not correct.
 | |
|     default: line
 | |
|     choices: ['line', 'block', 'config']
 | |
|   config:
 | |
|     description:
 | |
|       - The module, by default, will connect to the remote device and
 | |
|         retrieve the current running-config to use as a base for comparing
 | |
|         against the contents of source.  There are times when it is not
 | |
|         desirable to have the task get the current running-config for
 | |
|         every task in a playbook.  The I(config) argument allows the
 | |
|         implementer to pass in the configuration to use as the base
 | |
|         config for comparison.
 | |
|   backup:
 | |
|     description:
 | |
|       - This argument will cause the module to create a full backup of
 | |
|         the current C(running-config) from the remote device before any
 | |
|         changes are made. If the C(backup_options) value is not given,
 | |
|         the backup file is written to the C(backup) folder in the playbook
 | |
|         root directory. If the directory does not exist, it is created.
 | |
|     type: bool
 | |
|     default: 'no'
 | |
|   comment:
 | |
|     description:
 | |
|       - Allows a commit description to be specified to be included
 | |
|         when the configuration is committed.  If the configuration is
 | |
|         not changed or committed, this argument is ignored.
 | |
|     default: 'configured by enos_config'
 | |
|   admin:
 | |
|     description:
 | |
|       - Enters into administration configuration mode for making config
 | |
|         changes to the device.
 | |
|     type: bool
 | |
|     default: 'no'
 | |
|   backup_options:
 | |
|     description:
 | |
|       - This is a dict object containing configurable options related to backup file path.
 | |
|         The value of this option is read only when C(backup) is set to I(yes), if C(backup) is set
 | |
|         to I(no) this option will be silently ignored.
 | |
|     suboptions:
 | |
|       filename:
 | |
|         description:
 | |
|           - The filename to be used to store the backup configuration. If the the filename
 | |
|             is not given it will be generated based on the hostname, current time and date
 | |
|             in format defined by <hostname>_config.<current-date>@<current-time>
 | |
|       dir_path:
 | |
|         description:
 | |
|           - This option provides the path ending with directory name in which the backup
 | |
|             configuration file will be stored. If the directory does not exist it will be first
 | |
|             created and the filename is either the value of C(filename) or default filename
 | |
|             as described in C(filename) options description. If the path value is not given
 | |
|             in that case a I(backup) directory will be created in the current working directory
 | |
|             and backup configuration will be copied in C(filename) within I(backup) directory.
 | |
|         type: path
 | |
|     type: dict
 | |
|     version_added: "2.8"
 | |
| """
 | |
| 
 | |
| EXAMPLES = """
 | |
| - name: configure top level configuration
 | |
|   enos_config:
 | |
|     "lines: hostname {{ inventory_hostname }}"
 | |
| 
 | |
| - name: configure interface settings
 | |
|   enos_config:
 | |
|     lines:
 | |
|       - enable
 | |
|       - ip ospf enable
 | |
|     parents: interface ip 13
 | |
| 
 | |
| - name: load a config from disk and replace the current config
 | |
|   enos_config:
 | |
|     src: config.cfg
 | |
|     backup: yes
 | |
| 
 | |
| - name: configurable backup path
 | |
|   enos_config:
 | |
|     src: config.cfg
 | |
|     backup: yes
 | |
|     backup_options:
 | |
|       filename: backup.cfg
 | |
|       dir_path: /home/user
 | |
| """
 | |
| 
 | |
| RETURN = """
 | |
| updates:
 | |
|   description: The set of commands that will be pushed to the remote device
 | |
|   returned: Only when lines is specified.
 | |
|   type: list
 | |
|   sample: ['...', '...']
 | |
| backup_path:
 | |
|   description: The full path to the backup file
 | |
|   returned: when backup is yes
 | |
|   type: str
 | |
|   sample: /playbooks/ansible/backup/enos01.2016-07-16@22:28:34
 | |
| """
 | |
| from ansible.module_utils.basic import AnsibleModule
 | |
| from ansible.module_utils.network.enos.enos import load_config, get_config
 | |
| from ansible.module_utils.network.enos.enos import enos_argument_spec
 | |
| from ansible.module_utils.network.enos.enos import check_args
 | |
| from ansible.module_utils.network.common.config import NetworkConfig, dumps
 | |
| 
 | |
| 
 | |
| DEFAULT_COMMIT_COMMENT = 'configured by enos_config'
 | |
| 
 | |
| 
 | |
| def get_running_config(module):
 | |
|     contents = module.params['config']
 | |
|     if not contents:
 | |
|         contents = get_config(module)
 | |
|     return NetworkConfig(indent=1, contents=contents)
 | |
| 
 | |
| 
 | |
| def get_candidate(module):
 | |
|     candidate = NetworkConfig(indent=1)
 | |
|     if module.params['src']:
 | |
|         candidate.load(module.params['src'])
 | |
|     elif module.params['lines']:
 | |
|         parents = module.params['parents'] or list()
 | |
|         candidate.add(module.params['lines'], parents=parents)
 | |
|     return candidate
 | |
| 
 | |
| 
 | |
| def run(module, result):
 | |
|     match = module.params['match']
 | |
|     replace = module.params['replace']
 | |
|     replace_config = replace == 'config'
 | |
|     path = module.params['parents']
 | |
|     comment = module.params['comment']
 | |
|     admin = module.params['admin']
 | |
|     check_mode = module.check_mode
 | |
| 
 | |
|     candidate = get_candidate(module)
 | |
| 
 | |
|     if match != 'none' and replace != 'config':
 | |
|         contents = get_running_config(module)
 | |
|         configobj = NetworkConfig(contents=contents, indent=1)
 | |
|         commands = candidate.difference(configobj, path=path, match=match,
 | |
|                                         replace=replace)
 | |
|     else:
 | |
|         commands = candidate.items
 | |
| 
 | |
|     if commands:
 | |
|         commands = dumps(commands, 'commands').split('\n')
 | |
| 
 | |
|         if any((module.params['lines'], module.params['src'])):
 | |
|             if module.params['before']:
 | |
|                 commands[:0] = module.params['before']
 | |
| 
 | |
|             if module.params['after']:
 | |
|                 commands.extend(module.params['after'])
 | |
| 
 | |
|             result['commands'] = commands
 | |
| 
 | |
|         diff = load_config(module, commands)
 | |
|         if diff:
 | |
|             result['diff'] = dict(prepared=diff)
 | |
|             result['changed'] = True
 | |
| 
 | |
| 
 | |
| def main():
 | |
|     """main entry point for module execution
 | |
|     """
 | |
|     backup_spec = dict(
 | |
|         filename=dict(),
 | |
|         dir_path=dict(type='path')
 | |
|     )
 | |
|     argument_spec = dict(
 | |
|         src=dict(type='path'),
 | |
| 
 | |
|         lines=dict(aliases=['commands'], type='list'),
 | |
|         parents=dict(type='list'),
 | |
| 
 | |
|         before=dict(type='list'),
 | |
|         after=dict(type='list'),
 | |
| 
 | |
|         match=dict(default='line', choices=['line', 'strict', 'exact', 'none']),
 | |
|         replace=dict(default='line', choices=['line', 'block', 'config']),
 | |
| 
 | |
|         config=dict(),
 | |
|         backup=dict(type='bool', default=False),
 | |
|         backup_options=dict(type='dict', options=backup_spec),
 | |
|         comment=dict(default=DEFAULT_COMMIT_COMMENT),
 | |
|         admin=dict(type='bool', default=False)
 | |
|     )
 | |
| 
 | |
|     argument_spec.update(enos_argument_spec)
 | |
| 
 | |
|     mutually_exclusive = [('lines', 'src'),
 | |
|                           ('parents', 'src')]
 | |
| 
 | |
|     required_if = [('match', 'strict', ['lines']),
 | |
|                    ('match', 'exact', ['lines']),
 | |
|                    ('replace', 'block', ['lines']),
 | |
|                    ('replace', 'config', ['src'])]
 | |
| 
 | |
|     module = AnsibleModule(argument_spec=argument_spec,
 | |
|                            mutually_exclusive=mutually_exclusive,
 | |
|                            required_if=required_if,
 | |
|                            supports_check_mode=True)
 | |
| 
 | |
|     warnings = list()
 | |
|     check_args(module, warnings)
 | |
| 
 | |
|     result = dict(changed=False, warnings=warnings)
 | |
| 
 | |
|     if module.params['backup']:
 | |
|         result['__backup__'] = get_config(module)
 | |
| 
 | |
|     run(module, result)
 | |
| 
 | |
|     module.exit_json(**result)
 | |
| 
 | |
| 
 | |
| if __name__ == '__main__':
 | |
|     main()
 |