def main(): """ main entry point for module execution """ backup_spec = dict(filename=dict(), dir_path=dict(type="path")) argument_spec = dict( content=dict(aliases=["xml"], type="raw"), target=dict( choices=["auto", "candidate", "running"], default="auto", aliases=["datastore"], ), source_datastore=dict(aliases=["source"]), format=dict(choices=["xml", "text", "json"]), lock=dict( choices=["never", "always", "if-supported"], default="always" ), default_operation=dict(choices=["merge", "replace", "none"]), confirm=dict(type="int", default=0), confirm_commit=dict(type="bool", default=False), error_option=dict( choices=[ "stop-on-error", "continue-on-error", "rollback-on-error", ], default="stop-on-error", ), backup=dict(type="bool", default=False), backup_options=dict(type="dict", options=backup_spec), save=dict(type="bool", default=False), delete=dict(type="bool", default=False), commit=dict(type="bool", default=True), validate=dict(type="bool", default=False), get_filter=dict(type="raw"), ) mutually_exclusive = [ ("content", "source_datastore", "delete", "confirm_commit") ] required_one_of = [ ("content", "source_datastore", "delete", "confirm_commit") ] module = AnsibleModule( argument_spec=argument_spec, required_one_of=required_one_of, mutually_exclusive=mutually_exclusive, supports_check_mode=True, ) config = module.params["content"] target = module.params["target"] lock = module.params["lock"] source = module.params["source_datastore"] delete = module.params["delete"] confirm_commit = module.params["confirm_commit"] confirm = module.params["confirm"] validate = module.params["validate"] save = module.params["save"] filter = module.params["get_filter"] format = module.params["format"] try: filter_data, filter_type = validate_and_normalize_data(filter) except Exception as exc: module.fail_json(msg=to_text(exc)) if filter_type == "xml": filter_type = "subtree" elif filter_type == "json": try: filter = dict_to_xml(filter_data) except Exception as exc: module.fail_json(msg=to_text(exc)) filter_type = "subtree" elif filter_type == "xpath": pass elif filter_type is None: if filter_data is not None: # to maintain backward compatibility for ansible 2.9 which # defaults to "subtree" filter type filter_type = "subtree" module.warn( "The data format of get_filter option value couldn't be identified, hence set to 'subtree'" ) else: pass else: module.fail_json( msg="Invalid filter type detected %s for get_filter value %s" % (filter_type, filter) ) conn = Connection(module._socket_path) capabilities = get_capabilities(module) operations = capabilities["device_operations"] supports_commit = operations.get("supports_commit", False) supports_writable_running = operations.get( "supports_writable_running", False ) supports_startup = operations.get("supports_startup", False) # identify target datastore if target == "candidate" and not supports_commit: module.fail_json( msg=":candidate is not supported by this netconf server" ) elif target == "running" and not supports_writable_running: module.fail_json( msg=":writable-running is not supported by this netconf server" ) elif target == "auto": if supports_commit: target = "candidate" elif supports_writable_running: target = "running" else: module.fail_json( msg="neither :candidate nor :writable-running are supported by this netconf server" ) # Netconf server capability validation against input options if save and not supports_startup: module.fail_json( msg="cannot copy <%s/> to <startup/>, while :startup is not supported" % target ) if confirm_commit and not operations.get("supports_confirm_commit", False): module.fail_json( msg="confirm commit is not supported by Netconf server" ) if (confirm > 0) and not operations.get("supports_confirm_commit", False): module.fail_json( msg="confirm commit is not supported by this netconf server, given confirm timeout: %d" % confirm ) if validate and not operations.get("supports_validate", False): module.fail_json( msg="validate is not supported by this netconf server" ) if filter_type == "xpath" and not operations.get("supports_xpath", False): module.fail_json( msg="filter value '%s' of type xpath is not supported on this device" % filter ) filter_spec = (filter_type, filter) if filter_type else None if lock == "never": execute_lock = False elif target in operations.get("lock_datastore", []): # lock is requested (always/if-support) and supported => lets do it execute_lock = True else: # lock is requested (always/if-supported) but not supported => issue warning module.warn( "lock operation on '%s' source is not supported on this device" % target ) execute_lock = lock == "always" result = { "changed": False, "server_capabilities": capabilities.get("server_capabilities", []), } before = None after = None locked = False try: if module.params["backup"]: response = get_config( module, target, filter_spec, lock=execute_lock ) before = to_text( tostring(response), errors="surrogate_then_replace" ).strip() result["__backup__"] = before.strip() if validate: conn.validate(target) if source: if not module.check_mode: conn.copy(source, target) result["changed"] = True elif delete: if not module.check_mode: conn.delete(target) result["changed"] = True elif confirm_commit: if not module.check_mode: conn.commit() result["changed"] = True elif config: if module.check_mode and not supports_commit: module.warn( "check mode not supported as Netconf server doesn't support candidate capability" ) result["changed"] = True module.exit_json(**result) if execute_lock: conn.lock(target=target) locked = True if before is None: before = to_text( conn.get_config(source=target, filter=filter_spec), errors="surrogate_then_replace", ).strip() if format != "text": # check for format of type json/xml/xpath try: config_obj, config_format = validate_and_normalize_data( config, format ) except Exception as exc: module.fail_json(msg=to_text(exc)) if config_format == "json": try: config = dict_to_xml(config_obj) except Exception as exc: module.fail_json(msg=to_text(exc)) format = "xml" elif config_format is None: format = "xml" module.warn( "The data format of content option value couldn't be identified, hence set to 'xml'" ) else: format = config_format validate_config(module, config, format) kwargs = { "config": config, "target": target, "default_operation": module.params["default_operation"], "error_option": module.params["error_option"], "format": format, } conn.edit_config(**kwargs) if supports_commit and module.params["commit"]: after = to_text( conn.get_config(source="candidate", filter=filter_spec), errors="surrogate_then_replace", ).strip() if not module.check_mode: confirm_timeout = confirm if confirm > 0 else None confirmed_commit = True if confirm_timeout else False conn.commit( confirmed=confirmed_commit, timeout=confirm_timeout ) else: conn.discard_changes() if after is None: after = to_text( conn.get_config(source="running", filter=filter_spec), errors="surrogate_then_replace", ).strip() sanitized_before = sanitize_xml(before) sanitized_after = sanitize_xml(after) if sanitized_before != sanitized_after: result["changed"] = True if result["changed"]: if save and not module.check_mode: conn.copy_config(target, "startup") if module._diff: result["diff"] = { "before": sanitized_before, "after": sanitized_after, } except ConnectionError as e: module.fail_json( msg=to_text(e, errors="surrogate_then_replace").strip() ) finally: if locked: conn.unlock(target=target) module.exit_json(**result)
def main(): """ main entry point for module execution """ backup_spec = dict( filename=dict(), dir_path=dict(type='path') ) argument_spec = dict( content=dict(aliases=['xml']), target=dict(choices=['auto', 'candidate', 'running'], default='auto', aliases=['datastore']), source_datastore=dict(aliases=['source']), format=dict(choices=['xml', 'text'], default='xml'), lock=dict(choices=['never', 'always', 'if-supported'], default='always'), default_operation=dict(choices=['merge', 'replace', 'none']), confirm=dict(type='int', default=0), confirm_commit=dict(type='bool', default=False), error_option=dict(choices=['stop-on-error', 'continue-on-error', 'rollback-on-error'], default='stop-on-error'), backup=dict(type='bool', default=False), backup_options=dict(type='dict', options=backup_spec), save=dict(type='bool', default=False), delete=dict(type='bool', default=False), commit=dict(type='bool', default=True), validate=dict(type='bool', default=False), ) # deprecated options netconf_top_spec = { 'src': dict(type='path', removed_in_version=2.11), 'host': dict(removed_in_version=2.11), 'port': dict(removed_in_version=2.11, type='int', default=830), 'username': dict(fallback=(env_fallback, ['ANSIBLE_NET_USERNAME']), removed_in_version=2.11, no_log=True), 'password': dict(fallback=(env_fallback, ['ANSIBLE_NET_PASSWORD']), removed_in_version=2.11, no_log=True), 'ssh_keyfile': dict(fallback=(env_fallback, ['ANSIBLE_NET_SSH_KEYFILE']), removed_in_version=2.11, type='path'), 'hostkey_verify': dict(removed_in_version=2.11, type='bool', default=True), 'look_for_keys': dict(removed_in_version=2.11, type='bool', default=True), 'timeout': dict(removed_in_version=2.11, type='int', default=10), } argument_spec.update(netconf_top_spec) mutually_exclusive = [('content', 'src', 'source', 'delete', 'confirm_commit')] required_one_of = [('content', 'src', 'source', 'delete', 'confirm_commit')] module = AnsibleModule(argument_spec=argument_spec, required_one_of=required_one_of, mutually_exclusive=mutually_exclusive, supports_check_mode=True) if module.params['src']: module.deprecate(msg="argument 'src' has been deprecated. Use file lookup plugin instead to read file contents.", version="2.11") config = module.params['content'] or module.params['src'] target = module.params['target'] lock = module.params['lock'] source = module.params['source_datastore'] delete = module.params['delete'] confirm_commit = module.params['confirm_commit'] confirm = module.params['confirm'] validate = module.params['validate'] save = module.params['save'] conn = Connection(module._socket_path) capabilities = get_capabilities(module) operations = capabilities['device_operations'] supports_commit = operations.get('supports_commit', False) supports_writable_running = operations.get('supports_writable_running', False) supports_startup = operations.get('supports_startup', False) # identify target datastore if target == 'candidate' and not supports_commit: module.fail_json(msg=':candidate is not supported by this netconf server') elif target == 'running' and not supports_writable_running: module.fail_json(msg=':writable-running is not supported by this netconf server') elif target == 'auto': if supports_commit: target = 'candidate' elif supports_writable_running: target = 'running' else: module.fail_json(msg='neither :candidate nor :writable-running are supported by this netconf server') # Netconf server capability validation against input options if save and not supports_startup: module.fail_json(msg='cannot copy <%s/> to <startup/>, while :startup is not supported' % target) if confirm_commit and not operations.get('supports_confirm_commit', False): module.fail_json(msg='confirm commit is not supported by Netconf server') if (confirm > 0) and not operations.get('supports_confirm_commit', False): module.fail_json(msg='confirm commit is not supported by this netconf server, given confirm timeout: %d' % confirm) if validate and not operations.get('supports_validate', False): module.fail_json(msg='validate is not supported by this netconf server') if lock == 'never': execute_lock = False elif target in operations.get('lock_datastore', []): # lock is requested (always/if-support) and supported => lets do it execute_lock = True else: # lock is requested (always/if-supported) but not supported => issue warning module.warn("lock operation on '%s' source is not supported on this device" % target) execute_lock = (lock == 'always') result = {'changed': False, 'server_capabilities': capabilities.get('server_capabilities', [])} before = None after = None locked = False try: if module.params['backup']: response = get_config(module, target, lock=execute_lock) before = to_text(response, errors='surrogate_then_replace').strip() result['__backup__'] = before.strip() if validate: conn.validate(target) if source: if not module.check_mode: conn.copy(source, target) result['changed'] = True elif delete: if not module.check_mode: conn.delete(target) result['changed'] = True elif confirm_commit: if not module.check_mode: conn.commit() result['changed'] = True elif config: if module.check_mode and not supports_commit: module.warn("check mode not supported as Netconf server doesn't support candidate capability") result['changed'] = True module.exit_json(**result) if execute_lock: conn.lock(target=target) locked = True if before is None: before = to_text(conn.get_config(source=target), errors='surrogate_then_replace').strip() kwargs = { 'config': config, 'target': target, 'default_operation': module.params['default_operation'], 'error_option': module.params['error_option'], 'format': module.params['format'], } conn.edit_config(**kwargs) if supports_commit and module.params['commit']: after = to_text(conn.get_config(source='candidate'), errors='surrogate_then_replace').strip() if not module.check_mode: confirm_timeout = confirm if confirm > 0 else None confirmed_commit = True if confirm_timeout else False conn.commit(confirmed=confirmed_commit, timeout=confirm_timeout) else: conn.discard_changes() if after is None: after = to_text(conn.get_config(source='running'), errors='surrogate_then_replace').strip() sanitized_before = sanitize_xml(before) sanitized_after = sanitize_xml(after) if sanitized_before != sanitized_after: result['changed'] = True if result['changed']: if save and not module.check_mode: conn.copy_config(target, 'startup') if module._diff: result['diff'] = {'before': sanitized_before, 'after': sanitized_after} except ConnectionError as e: module.fail_json(msg=to_text(e, errors='surrogate_then_replace').strip()) finally: if locked: conn.unlock(target=target) module.exit_json(**result)
def main(): """ main entry point for module execution """ backup_spec = dict(filename=dict(), dir_path=dict(type="path")) argument_spec = dict( content=dict(aliases=["xml"]), target=dict( choices=["auto", "candidate", "running"], default="auto", aliases=["datastore"], ), source_datastore=dict(aliases=["source"]), format=dict(choices=["xml", "text"], default="xml"), lock=dict( choices=["never", "always", "if-supported"], default="always" ), default_operation=dict(choices=["merge", "replace", "none"]), confirm=dict(type="int", default=0), confirm_commit=dict(type="bool", default=False), error_option=dict( choices=[ "stop-on-error", "continue-on-error", "rollback-on-error", ], default="stop-on-error", ), backup=dict(type="bool", default=False), backup_options=dict(type="dict", options=backup_spec), save=dict(type="bool", default=False), delete=dict(type="bool", default=False), commit=dict(type="bool", default=True), validate=dict(type="bool", default=False), get_filter=dict(), ) # deprecated options netconf_top_spec = { "src": dict( type="path", removed_at_date="2020-12-01", removed_from_collection="ansible.netcommon", ), "host": dict( removed_at_date="2020-12-01", removed_from_collection="ansible.netcommon", ), "port": dict( removed_at_date="2020-12-01", removed_from_collection="ansible.netcommon", type="int", default=830, ), "username": dict( fallback=(env_fallback, ["ANSIBLE_NET_USERNAME"]), removed_at_date="2020-12-01", removed_from_collection="ansible.netcommon", no_log=True, ), "password": dict( fallback=(env_fallback, ["ANSIBLE_NET_PASSWORD"]), removed_at_date="2020-12-01", removed_from_collection="ansible.netcommon", no_log=True, ), "ssh_keyfile": dict( fallback=(env_fallback, ["ANSIBLE_NET_SSH_KEYFILE"]), removed_at_date="2020-12-01", removed_from_collection="ansible.netcommon", type="path", ), "hostkey_verify": dict( removed_at_date="2020-12-01", removed_from_collection="ansible.netcommon", type="bool", default=True, ), "look_for_keys": dict( removed_at_date="2020-12-01", removed_from_collection="ansible.netcommon", type="bool", default=True, ), "timeout": dict( removed_at_date="2020-12-01", removed_from_collection="ansible.netcommon", type="int", default=10, ), } argument_spec.update(netconf_top_spec) mutually_exclusive = [ ("content", "src", "source_datastore", "delete", "confirm_commit") ] required_one_of = [ ("content", "src", "source_datastore", "delete", "confirm_commit") ] module = AnsibleModule( argument_spec=argument_spec, required_one_of=required_one_of, mutually_exclusive=mutually_exclusive, supports_check_mode=True, ) config = module.params["content"] or module.params["src"] target = module.params["target"] lock = module.params["lock"] source = module.params["source_datastore"] delete = module.params["delete"] confirm_commit = module.params["confirm_commit"] confirm = module.params["confirm"] validate = module.params["validate"] save = module.params["save"] filter = module.params["get_filter"] format = module.params["format"] filter_type = get_filter_type(filter) conn = Connection(module._socket_path) capabilities = get_capabilities(module) operations = capabilities["device_operations"] supports_commit = operations.get("supports_commit", False) supports_writable_running = operations.get( "supports_writable_running", False ) supports_startup = operations.get("supports_startup", False) # identify target datastore if target == "candidate" and not supports_commit: module.fail_json( msg=":candidate is not supported by this netconf server" ) elif target == "running" and not supports_writable_running: module.fail_json( msg=":writable-running is not supported by this netconf server" ) elif target == "auto": if supports_commit: target = "candidate" elif supports_writable_running: target = "running" else: module.fail_json( msg="neither :candidate nor :writable-running are supported by this netconf server" ) # Netconf server capability validation against input options if save and not supports_startup: module.fail_json( msg="cannot copy <%s/> to <startup/>, while :startup is not supported" % target ) if confirm_commit and not operations.get("supports_confirm_commit", False): module.fail_json( msg="confirm commit is not supported by Netconf server" ) if (confirm > 0) and not operations.get("supports_confirm_commit", False): module.fail_json( msg="confirm commit is not supported by this netconf server, given confirm timeout: %d" % confirm ) if validate and not operations.get("supports_validate", False): module.fail_json( msg="validate is not supported by this netconf server" ) if filter_type == "xpath" and not operations.get("supports_xpath", False): module.fail_json( msg="filter value '%s' of type xpath is not supported on this device" % filter ) filter_spec = (filter_type, filter) if filter_type else None if lock == "never": execute_lock = False elif target in operations.get("lock_datastore", []): # lock is requested (always/if-support) and supported => lets do it execute_lock = True else: # lock is requested (always/if-supported) but not supported => issue warning module.warn( "lock operation on '%s' source is not supported on this device" % target ) execute_lock = lock == "always" result = { "changed": False, "server_capabilities": capabilities.get("server_capabilities", []), } before = None after = None locked = False try: if module.params["backup"]: response = get_config( module, target, filter_spec, lock=execute_lock ) before = to_text( tostring(response), errors="surrogate_then_replace" ).strip() result["__backup__"] = before.strip() if validate: conn.validate(target) if source: if not module.check_mode: conn.copy(source, target) result["changed"] = True elif delete: if not module.check_mode: conn.delete(target) result["changed"] = True elif confirm_commit: if not module.check_mode: conn.commit() result["changed"] = True elif config: if module.check_mode and not supports_commit: module.warn( "check mode not supported as Netconf server doesn't support candidate capability" ) result["changed"] = True module.exit_json(**result) if execute_lock: conn.lock(target=target) locked = True if before is None: before = to_text( conn.get_config(source=target, filter=filter_spec), errors="surrogate_then_replace", ).strip() validate_config(module, config, format) kwargs = { "config": config, "target": target, "default_operation": module.params["default_operation"], "error_option": module.params["error_option"], "format": format, } conn.edit_config(**kwargs) if supports_commit and module.params["commit"]: after = to_text( conn.get_config(source="candidate", filter=filter_spec), errors="surrogate_then_replace", ).strip() if not module.check_mode: confirm_timeout = confirm if confirm > 0 else None confirmed_commit = True if confirm_timeout else False conn.commit( confirmed=confirmed_commit, timeout=confirm_timeout ) else: conn.discard_changes() if after is None: after = to_text( conn.get_config(source="running", filter=filter_spec), errors="surrogate_then_replace", ).strip() sanitized_before = sanitize_xml(before) sanitized_after = sanitize_xml(after) if sanitized_before != sanitized_after: result["changed"] = True if result["changed"]: if save and not module.check_mode: conn.copy_config(target, "startup") if module._diff: result["diff"] = { "before": sanitized_before, "after": sanitized_after, } except ConnectionError as e: module.fail_json( msg=to_text(e, errors="surrogate_then_replace").strip() ) finally: if locked: conn.unlock(target=target) module.exit_json(**result)