mirror of https://github.com/ansible/ansible.git
Remove unused network test support files (#80080)
* Remove unused network test support files * Remove obsolete ignorespull/80084/head
parent
6bfe6b899a
commit
086046d478
@ -1,90 +0,0 @@
|
||||
# Copyright: (c) 2015, Ansible Inc,
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
import copy
|
||||
|
||||
from ansible.errors import AnsibleError
|
||||
from ansible.plugins.action import ActionBase
|
||||
from ansible.utils.display import Display
|
||||
|
||||
display = Display()
|
||||
|
||||
|
||||
class ActionModule(ActionBase):
|
||||
def run(self, tmp=None, task_vars=None):
|
||||
del tmp # tmp no longer has any effect
|
||||
|
||||
result = {}
|
||||
play_context = copy.deepcopy(self._play_context)
|
||||
play_context.network_os = self._get_network_os(task_vars)
|
||||
new_task = self._task.copy()
|
||||
|
||||
module = self._get_implementation_module(
|
||||
play_context.network_os, self._task.action
|
||||
)
|
||||
if not module:
|
||||
if self._task.args["fail_on_missing_module"]:
|
||||
result["failed"] = True
|
||||
else:
|
||||
result["failed"] = False
|
||||
|
||||
result["msg"] = (
|
||||
"Could not find implementation module %s for %s"
|
||||
% (self._task.action, play_context.network_os)
|
||||
)
|
||||
return result
|
||||
|
||||
new_task.action = module
|
||||
|
||||
action = self._shared_loader_obj.action_loader.get(
|
||||
play_context.network_os,
|
||||
task=new_task,
|
||||
connection=self._connection,
|
||||
play_context=play_context,
|
||||
loader=self._loader,
|
||||
templar=self._templar,
|
||||
shared_loader_obj=self._shared_loader_obj,
|
||||
)
|
||||
display.vvvv("Running implementation module %s" % module)
|
||||
return action.run(task_vars=task_vars)
|
||||
|
||||
def _get_network_os(self, task_vars):
|
||||
if "network_os" in self._task.args and self._task.args["network_os"]:
|
||||
display.vvvv("Getting network OS from task argument")
|
||||
network_os = self._task.args["network_os"]
|
||||
elif self._play_context.network_os:
|
||||
display.vvvv("Getting network OS from inventory")
|
||||
network_os = self._play_context.network_os
|
||||
elif (
|
||||
"network_os" in task_vars.get("ansible_facts", {})
|
||||
and task_vars["ansible_facts"]["network_os"]
|
||||
):
|
||||
display.vvvv("Getting network OS from fact")
|
||||
network_os = task_vars["ansible_facts"]["network_os"]
|
||||
else:
|
||||
raise AnsibleError(
|
||||
"ansible_network_os must be specified on this host to use platform agnostic modules"
|
||||
)
|
||||
|
||||
return network_os
|
||||
|
||||
def _get_implementation_module(self, network_os, platform_agnostic_module):
|
||||
module_name = (
|
||||
network_os.split(".")[-1]
|
||||
+ "_"
|
||||
+ platform_agnostic_module.partition("_")[2]
|
||||
)
|
||||
if "." in network_os:
|
||||
fqcn_module = ".".join(network_os.split(".")[0:-1])
|
||||
implementation_module = fqcn_module + "." + module_name
|
||||
else:
|
||||
implementation_module = module_name
|
||||
|
||||
if implementation_module not in self._shared_loader_obj.module_loader:
|
||||
implementation_module = None
|
||||
|
||||
return implementation_module
|
@ -1,42 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright: (c) 2018, Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = """become: enable
|
||||
short_description: Switch to elevated permissions on a network device
|
||||
description:
|
||||
- This become plugins allows elevated permissions on a remote network device.
|
||||
author: ansible (@core)
|
||||
options:
|
||||
become_pass:
|
||||
description: password
|
||||
ini:
|
||||
- section: enable_become_plugin
|
||||
key: password
|
||||
vars:
|
||||
- name: ansible_become_password
|
||||
- name: ansible_become_pass
|
||||
- name: ansible_enable_pass
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_PASS
|
||||
- name: ANSIBLE_ENABLE_PASS
|
||||
notes:
|
||||
- enable is really implemented in the network connection handler and as such can only
|
||||
be used with network connections.
|
||||
- This plugin ignores the 'become_exe' and 'become_user' settings as it uses an API
|
||||
and not an executable.
|
||||
"""
|
||||
|
||||
from ansible.plugins.become import BecomeBase
|
||||
|
||||
|
||||
class BecomeModule(BecomeBase):
|
||||
|
||||
name = "ansible.netcommon.enable"
|
||||
|
||||
def build_become_command(self, cmd, shell):
|
||||
# enable is implemented inside the network connection plugins
|
||||
return cmd
|
@ -1,324 +0,0 @@
|
||||
# (c) 2018 Red Hat Inc.
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = """author: Ansible Networking Team
|
||||
connection: httpapi
|
||||
short_description: Use httpapi to run command on network appliances
|
||||
description:
|
||||
- This connection plugin provides a connection to remote devices over a HTTP(S)-based
|
||||
api.
|
||||
options:
|
||||
host:
|
||||
description:
|
||||
- Specifies the remote device FQDN or IP address to establish the HTTP(S) connection
|
||||
to.
|
||||
default: inventory_hostname
|
||||
vars:
|
||||
- name: ansible_host
|
||||
port:
|
||||
type: int
|
||||
description:
|
||||
- Specifies the port on the remote device that listens for connections when establishing
|
||||
the HTTP(S) connection.
|
||||
- When unspecified, will pick 80 or 443 based on the value of use_ssl.
|
||||
ini:
|
||||
- section: defaults
|
||||
key: remote_port
|
||||
env:
|
||||
- name: ANSIBLE_REMOTE_PORT
|
||||
vars:
|
||||
- name: ansible_httpapi_port
|
||||
network_os:
|
||||
description:
|
||||
- Configures the device platform network operating system. This value is used
|
||||
to load the correct httpapi plugin to communicate with the remote device
|
||||
vars:
|
||||
- name: ansible_network_os
|
||||
remote_user:
|
||||
description:
|
||||
- The username used to authenticate to the remote device when the API connection
|
||||
is first established. If the remote_user is not specified, the connection will
|
||||
use the username of the logged in user.
|
||||
- Can be configured from the CLI via the C(--user) or C(-u) options.
|
||||
ini:
|
||||
- section: defaults
|
||||
key: remote_user
|
||||
env:
|
||||
- name: ANSIBLE_REMOTE_USER
|
||||
vars:
|
||||
- name: ansible_user
|
||||
password:
|
||||
description:
|
||||
- Configures the user password used to authenticate to the remote device when
|
||||
needed for the device API.
|
||||
vars:
|
||||
- name: ansible_password
|
||||
- name: ansible_httpapi_pass
|
||||
- name: ansible_httpapi_password
|
||||
use_ssl:
|
||||
type: boolean
|
||||
description:
|
||||
- Whether to connect using SSL (HTTPS) or not (HTTP).
|
||||
default: false
|
||||
vars:
|
||||
- name: ansible_httpapi_use_ssl
|
||||
validate_certs:
|
||||
type: boolean
|
||||
description:
|
||||
- Whether to validate SSL certificates
|
||||
default: true
|
||||
vars:
|
||||
- name: ansible_httpapi_validate_certs
|
||||
use_proxy:
|
||||
type: boolean
|
||||
description:
|
||||
- Whether to use https_proxy for requests.
|
||||
default: true
|
||||
vars:
|
||||
- name: ansible_httpapi_use_proxy
|
||||
become:
|
||||
type: boolean
|
||||
description:
|
||||
- The become option will instruct the CLI session to attempt privilege escalation
|
||||
on platforms that support it. Normally this means transitioning from user mode
|
||||
to C(enable) mode in the CLI session. If become is set to True and the remote
|
||||
device does not support privilege escalation or the privilege has already been
|
||||
elevated, then this option is silently ignored.
|
||||
- Can be configured from the CLI via the C(--become) or C(-b) options.
|
||||
default: false
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become
|
||||
env:
|
||||
- name: ANSIBLE_BECOME
|
||||
vars:
|
||||
- name: ansible_become
|
||||
become_method:
|
||||
description:
|
||||
- This option allows the become method to be specified in for handling privilege
|
||||
escalation. Typically the become_method value is set to C(enable) but could
|
||||
be defined as other values.
|
||||
default: sudo
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_method
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_METHOD
|
||||
vars:
|
||||
- name: ansible_become_method
|
||||
persistent_connect_timeout:
|
||||
type: int
|
||||
description:
|
||||
- Configures, in seconds, the amount of time to wait when trying to initially
|
||||
establish a persistent connection. If this value expires before the connection
|
||||
to the remote device is completed, the connection will fail.
|
||||
default: 30
|
||||
ini:
|
||||
- section: persistent_connection
|
||||
key: connect_timeout
|
||||
env:
|
||||
- name: ANSIBLE_PERSISTENT_CONNECT_TIMEOUT
|
||||
vars:
|
||||
- name: ansible_connect_timeout
|
||||
persistent_command_timeout:
|
||||
type: int
|
||||
description:
|
||||
- Configures, in seconds, the amount of time to wait for a command to return from
|
||||
the remote device. If this timer is exceeded before the command returns, the
|
||||
connection plugin will raise an exception and close.
|
||||
default: 30
|
||||
ini:
|
||||
- section: persistent_connection
|
||||
key: command_timeout
|
||||
env:
|
||||
- name: ANSIBLE_PERSISTENT_COMMAND_TIMEOUT
|
||||
vars:
|
||||
- name: ansible_command_timeout
|
||||
persistent_log_messages:
|
||||
type: boolean
|
||||
description:
|
||||
- This flag will enable logging the command executed and response received from
|
||||
target device in the ansible log file. For this option to work 'log_path' ansible
|
||||
configuration option is required to be set to a file path with write access.
|
||||
- Be sure to fully understand the security implications of enabling this option
|
||||
as it could create a security vulnerability by logging sensitive information
|
||||
in log file.
|
||||
default: false
|
||||
ini:
|
||||
- section: persistent_connection
|
||||
key: log_messages
|
||||
env:
|
||||
- name: ANSIBLE_PERSISTENT_LOG_MESSAGES
|
||||
vars:
|
||||
- name: ansible_persistent_log_messages
|
||||
"""
|
||||
|
||||
from io import BytesIO
|
||||
|
||||
from ansible.errors import AnsibleConnectionFailure
|
||||
from ansible.module_utils._text import to_bytes
|
||||
from ansible.module_utils.six import PY3
|
||||
from ansible.module_utils.six.moves import cPickle
|
||||
from ansible.module_utils.six.moves.urllib.error import HTTPError, URLError
|
||||
from ansible.module_utils.urls import open_url
|
||||
from ansible.playbook.play_context import PlayContext
|
||||
from ansible.plugins.loader import httpapi_loader
|
||||
from ansible.plugins.connection import NetworkConnectionBase, ensure_connect
|
||||
|
||||
|
||||
class Connection(NetworkConnectionBase):
|
||||
"""Network API connection"""
|
||||
|
||||
transport = "ansible.netcommon.httpapi"
|
||||
has_pipelining = True
|
||||
|
||||
def __init__(self, play_context, new_stdin, *args, **kwargs):
|
||||
super(Connection, self).__init__(
|
||||
play_context, new_stdin, *args, **kwargs
|
||||
)
|
||||
|
||||
self._url = None
|
||||
self._auth = None
|
||||
|
||||
if self._network_os:
|
||||
|
||||
self.httpapi = httpapi_loader.get(self._network_os, self)
|
||||
if self.httpapi:
|
||||
self._sub_plugin = {
|
||||
"type": "httpapi",
|
||||
"name": self.httpapi._load_name,
|
||||
"obj": self.httpapi,
|
||||
}
|
||||
self.queue_message(
|
||||
"vvvv",
|
||||
"loaded API plugin %s from path %s for network_os %s"
|
||||
% (
|
||||
self.httpapi._load_name,
|
||||
self.httpapi._original_path,
|
||||
self._network_os,
|
||||
),
|
||||
)
|
||||
else:
|
||||
raise AnsibleConnectionFailure(
|
||||
"unable to load API plugin for network_os %s"
|
||||
% self._network_os
|
||||
)
|
||||
|
||||
else:
|
||||
raise AnsibleConnectionFailure(
|
||||
"Unable to automatically determine host network os. Please "
|
||||
"manually configure ansible_network_os value for this host"
|
||||
)
|
||||
self.queue_message("log", "network_os is set to %s" % self._network_os)
|
||||
|
||||
def update_play_context(self, pc_data):
|
||||
"""Updates the play context information for the connection"""
|
||||
pc_data = to_bytes(pc_data)
|
||||
if PY3:
|
||||
pc_data = cPickle.loads(pc_data, encoding="bytes")
|
||||
else:
|
||||
pc_data = cPickle.loads(pc_data)
|
||||
play_context = PlayContext()
|
||||
play_context.deserialize(pc_data)
|
||||
|
||||
self.queue_message("vvvv", "updating play_context for connection")
|
||||
if self._play_context.become ^ play_context.become:
|
||||
self.set_become(play_context)
|
||||
if play_context.become is True:
|
||||
self.queue_message("vvvv", "authorizing connection")
|
||||
else:
|
||||
self.queue_message("vvvv", "deauthorizing connection")
|
||||
|
||||
self._play_context = play_context
|
||||
|
||||
def _connect(self):
|
||||
if not self.connected:
|
||||
protocol = "https" if self.get_option("use_ssl") else "http"
|
||||
host = self.get_option("host")
|
||||
port = self.get_option("port") or (
|
||||
443 if protocol == "https" else 80
|
||||
)
|
||||
self._url = "%s://%s:%s" % (protocol, host, port)
|
||||
|
||||
self.queue_message(
|
||||
"vvv",
|
||||
"ESTABLISH HTTP(S) CONNECTFOR USER: %s TO %s"
|
||||
% (self._play_context.remote_user, self._url),
|
||||
)
|
||||
self.httpapi.set_become(self._play_context)
|
||||
self._connected = True
|
||||
|
||||
self.httpapi.login(
|
||||
self.get_option("remote_user"), self.get_option("password")
|
||||
)
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
Close the active session to the device
|
||||
"""
|
||||
# only close the connection if its connected.
|
||||
if self._connected:
|
||||
self.queue_message("vvvv", "closing http(s) connection to device")
|
||||
self.logout()
|
||||
|
||||
super(Connection, self).close()
|
||||
|
||||
@ensure_connect
|
||||
def send(self, path, data, **kwargs):
|
||||
"""
|
||||
Sends the command to the device over api
|
||||
"""
|
||||
url_kwargs = dict(
|
||||
timeout=self.get_option("persistent_command_timeout"),
|
||||
validate_certs=self.get_option("validate_certs"),
|
||||
use_proxy=self.get_option("use_proxy"),
|
||||
headers={},
|
||||
)
|
||||
url_kwargs.update(kwargs)
|
||||
if self._auth:
|
||||
# Avoid modifying passed-in headers
|
||||
headers = dict(kwargs.get("headers", {}))
|
||||
headers.update(self._auth)
|
||||
url_kwargs["headers"] = headers
|
||||
else:
|
||||
url_kwargs["force_basic_auth"] = True
|
||||
url_kwargs["url_username"] = self.get_option("remote_user")
|
||||
url_kwargs["url_password"] = self.get_option("password")
|
||||
|
||||
try:
|
||||
url = self._url + path
|
||||
self._log_messages(
|
||||
"send url '%s' with data '%s' and kwargs '%s'"
|
||||
% (url, data, url_kwargs)
|
||||
)
|
||||
response = open_url(url, data=data, **url_kwargs)
|
||||
except HTTPError as exc:
|
||||
is_handled = self.handle_httperror(exc)
|
||||
if is_handled is True:
|
||||
return self.send(path, data, **kwargs)
|
||||
elif is_handled is False:
|
||||
raise
|
||||
else:
|
||||
response = is_handled
|
||||
except URLError as exc:
|
||||
raise AnsibleConnectionFailure(
|
||||
"Could not connect to {0}: {1}".format(
|
||||
self._url + path, exc.reason
|
||||
)
|
||||
)
|
||||
|
||||
response_buffer = BytesIO()
|
||||
resp_data = response.read()
|
||||
self._log_messages("received response: '%s'" % resp_data)
|
||||
response_buffer.write(resp_data)
|
||||
|
||||
# Try to assign a new auth token if one is given
|
||||
self._auth = self.update_auth(response, response_buffer) or self._auth
|
||||
|
||||
response_buffer.seek(0)
|
||||
|
||||
return response, response_buffer
|
@ -1,404 +0,0 @@
|
||||
# (c) 2016 Red Hat Inc.
|
||||
# (c) 2017 Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = """author: Ansible Networking Team
|
||||
connection: netconf
|
||||
short_description: Provides a persistent connection using the netconf protocol
|
||||
description:
|
||||
- This connection plugin provides a connection to remote devices over the SSH NETCONF
|
||||
subsystem. This connection plugin is typically used by network devices for sending
|
||||
and receiving RPC calls over NETCONF.
|
||||
- Note this connection plugin requires ncclient to be installed on the local Ansible
|
||||
controller.
|
||||
requirements:
|
||||
- ncclient
|
||||
options:
|
||||
host:
|
||||
description:
|
||||
- Specifies the remote device FQDN or IP address to establish the SSH connection
|
||||
to.
|
||||
default: inventory_hostname
|
||||
vars:
|
||||
- name: ansible_host
|
||||
port:
|
||||
type: int
|
||||
description:
|
||||
- Specifies the port on the remote device that listens for connections when establishing
|
||||
the SSH connection.
|
||||
default: 830
|
||||
ini:
|
||||
- section: defaults
|
||||
key: remote_port
|
||||
env:
|
||||
- name: ANSIBLE_REMOTE_PORT
|
||||
vars:
|
||||
- name: ansible_port
|
||||
network_os:
|
||||
description:
|
||||
- Configures the device platform network operating system. This value is used
|
||||
to load a device specific netconf plugin. If this option is not configured
|
||||
(or set to C(auto)), then Ansible will attempt to guess the correct network_os
|
||||
to use. If it can not guess a network_os correctly it will use C(default).
|
||||
vars:
|
||||
- name: ansible_network_os
|
||||
remote_user:
|
||||
description:
|
||||
- The username used to authenticate to the remote device when the SSH connection
|
||||
is first established. If the remote_user is not specified, the connection will
|
||||
use the username of the logged in user.
|
||||
- Can be configured from the CLI via the C(--user) or C(-u) options.
|
||||
ini:
|
||||
- section: defaults
|
||||
key: remote_user
|
||||
env:
|
||||
- name: ANSIBLE_REMOTE_USER
|
||||
vars:
|
||||
- name: ansible_user
|
||||
password:
|
||||
description:
|
||||
- Configures the user password used to authenticate to the remote device when
|
||||
first establishing the SSH connection.
|
||||
vars:
|
||||
- name: ansible_password
|
||||
- name: ansible_ssh_pass
|
||||
- name: ansible_ssh_password
|
||||
- name: ansible_netconf_password
|
||||
private_key_file:
|
||||
description:
|
||||
- The private SSH key or certificate file used to authenticate to the remote device
|
||||
when first establishing the SSH connection.
|
||||
ini:
|
||||
- section: defaults
|
||||
key: private_key_file
|
||||
env:
|
||||
- name: ANSIBLE_PRIVATE_KEY_FILE
|
||||
vars:
|
||||
- name: ansible_private_key_file
|
||||
look_for_keys:
|
||||
default: true
|
||||
description:
|
||||
- Enables looking for ssh keys in the usual locations for ssh keys (e.g. :file:`~/.ssh/id_*`).
|
||||
env:
|
||||
- name: ANSIBLE_PARAMIKO_LOOK_FOR_KEYS
|
||||
ini:
|
||||
- section: paramiko_connection
|
||||
key: look_for_keys
|
||||
type: boolean
|
||||
host_key_checking:
|
||||
description: Set this to "False" if you want to avoid host key checking by the
|
||||
underlying tools Ansible uses to connect to the host
|
||||
type: boolean
|
||||
default: true
|
||||
env:
|
||||
- name: ANSIBLE_HOST_KEY_CHECKING
|
||||
- name: ANSIBLE_SSH_HOST_KEY_CHECKING
|
||||
- name: ANSIBLE_NETCONF_HOST_KEY_CHECKING
|
||||
ini:
|
||||
- section: defaults
|
||||
key: host_key_checking
|
||||
- section: paramiko_connection
|
||||
key: host_key_checking
|
||||
vars:
|
||||
- name: ansible_host_key_checking
|
||||
- name: ansible_ssh_host_key_checking
|
||||
- name: ansible_netconf_host_key_checking
|
||||
persistent_connect_timeout:
|
||||
type: int
|
||||
description:
|
||||
- Configures, in seconds, the amount of time to wait when trying to initially
|
||||
establish a persistent connection. If this value expires before the connection
|
||||
to the remote device is completed, the connection will fail.
|
||||
default: 30
|
||||
ini:
|
||||
- section: persistent_connection
|
||||
key: connect_timeout
|
||||
env:
|
||||
- name: ANSIBLE_PERSISTENT_CONNECT_TIMEOUT
|
||||
vars:
|
||||
- name: ansible_connect_timeout
|
||||
persistent_command_timeout:
|
||||
type: int
|
||||
description:
|
||||
- Configures, in seconds, the amount of time to wait for a command to return from
|
||||
the remote device. If this timer is exceeded before the command returns, the
|
||||
connection plugin will raise an exception and close.
|
||||
default: 30
|
||||
ini:
|
||||
- section: persistent_connection
|
||||
key: command_timeout
|
||||
env:
|
||||
- name: ANSIBLE_PERSISTENT_COMMAND_TIMEOUT
|
||||
vars:
|
||||
- name: ansible_command_timeout
|
||||
netconf_ssh_config:
|
||||
description:
|
||||
- This variable is used to enable bastion/jump host with netconf connection. If
|
||||
set to True the bastion/jump host ssh settings should be present in ~/.ssh/config
|
||||
file, alternatively it can be set to custom ssh configuration file path to read
|
||||
the bastion/jump host settings.
|
||||
ini:
|
||||
- section: netconf_connection
|
||||
key: ssh_config
|
||||
version_added: '2.7'
|
||||
env:
|
||||
- name: ANSIBLE_NETCONF_SSH_CONFIG
|
||||
vars:
|
||||
- name: ansible_netconf_ssh_config
|
||||
version_added: '2.7'
|
||||
persistent_log_messages:
|
||||
type: boolean
|
||||
description:
|
||||
- This flag will enable logging the command executed and response received from
|
||||
target device in the ansible log file. For this option to work 'log_path' ansible
|
||||
configuration option is required to be set to a file path with write access.
|
||||
- Be sure to fully understand the security implications of enabling this option
|
||||
as it could create a security vulnerability by logging sensitive information
|
||||
in log file.
|
||||
default: false
|
||||
ini:
|
||||
- section: persistent_connection
|
||||
key: log_messages
|
||||
env:
|
||||
- name: ANSIBLE_PERSISTENT_LOG_MESSAGES
|
||||
vars:
|
||||
- name: ansible_persistent_log_messages
|
||||
"""
|
||||
|
||||
import os
|
||||
import logging
|
||||
import json
|
||||
|
||||
from ansible.errors import AnsibleConnectionFailure, AnsibleError
|
||||
from ansible.module_utils._text import to_bytes, to_native, to_text
|
||||
from ansible.module_utils.basic import missing_required_lib
|
||||
from ansible.module_utils.parsing.convert_bool import (
|
||||
BOOLEANS_TRUE,
|
||||
BOOLEANS_FALSE,
|
||||
)
|
||||
from ansible.plugins.loader import netconf_loader
|
||||
from ansible.plugins.connection import NetworkConnectionBase, ensure_connect
|
||||
|
||||
try:
|
||||
from ncclient import manager
|
||||
from ncclient.operations import RPCError
|
||||
from ncclient.transport.errors import SSHUnknownHostError
|
||||
from ncclient.xml_ import to_ele, to_xml
|
||||
|
||||
HAS_NCCLIENT = True
|
||||
NCCLIENT_IMP_ERR = None
|
||||
except (
|
||||
ImportError,
|
||||
AttributeError,
|
||||
) as err: # paramiko and gssapi are incompatible and raise AttributeError not ImportError
|
||||
HAS_NCCLIENT = False
|
||||
NCCLIENT_IMP_ERR = err
|
||||
|
||||
logging.getLogger("ncclient").setLevel(logging.INFO)
|
||||
|
||||
|
||||
class Connection(NetworkConnectionBase):
|
||||
"""NetConf connections"""
|
||||
|
||||
transport = "ansible.netcommon.netconf"
|
||||
has_pipelining = False
|
||||
|
||||
def __init__(self, play_context, new_stdin, *args, **kwargs):
|
||||
super(Connection, self).__init__(
|
||||
play_context, new_stdin, *args, **kwargs
|
||||
)
|
||||
|
||||
# If network_os is not specified then set the network os to auto
|
||||
# This will be used to trigger the use of guess_network_os when connecting.
|
||||
self._network_os = self._network_os or "auto"
|
||||
|
||||
self.netconf = netconf_loader.get(self._network_os, self)
|
||||
if self.netconf:
|
||||
self._sub_plugin = {
|
||||
"type": "netconf",
|
||||
"name": self.netconf._load_name,
|
||||
"obj": self.netconf,
|
||||
}
|
||||
self.queue_message(
|
||||
"vvvv",
|
||||
"loaded netconf plugin %s from path %s for network_os %s"
|
||||
% (
|
||||
self.netconf._load_name,
|
||||
self.netconf._original_path,
|
||||
self._network_os,
|
||||
),
|
||||
)
|
||||
else:
|
||||
self.netconf = netconf_loader.get("default", self)
|
||||
self._sub_plugin = {
|
||||
"type": "netconf",
|
||||
"name": "default",
|
||||
"obj": self.netconf,
|
||||
}
|
||||
self.queue_message(
|
||||
"display",
|
||||
"unable to load netconf plugin for network_os %s, falling back to default plugin"
|
||||
% self._network_os,
|
||||
)
|
||||
|
||||
self.queue_message("log", "network_os is set to %s" % self._network_os)
|
||||
self._manager = None
|
||||
self.key_filename = None
|
||||
self._ssh_config = None
|
||||
|
||||
def exec_command(self, cmd, in_data=None, sudoable=True):
|
||||
"""Sends the request to the node and returns the reply
|
||||
The method accepts two forms of request. The first form is as a byte
|
||||
string that represents xml string be send over netconf session.
|
||||
The second form is a json-rpc (2.0) byte string.
|
||||
"""
|
||||
if self._manager:
|
||||
# to_ele operates on native strings
|
||||
request = to_ele(to_native(cmd, errors="surrogate_or_strict"))
|
||||
|
||||
if request is None:
|
||||
return "unable to parse request"
|
||||
|
||||
try:
|
||||
reply = self._manager.rpc(request)
|
||||
except RPCError as exc:
|
||||
error = self.internal_error(
|
||||
data=to_text(to_xml(exc.xml), errors="surrogate_or_strict")
|
||||
)
|
||||
return json.dumps(error)
|
||||
|
||||
return reply.data_xml
|
||||
else:
|
||||
return super(Connection, self).exec_command(cmd, in_data, sudoable)
|
||||
|
||||
@property
|
||||
@ensure_connect
|
||||
def manager(self):
|
||||
return self._manager
|
||||
|
||||
def _connect(self):
|
||||
if not HAS_NCCLIENT:
|
||||
raise AnsibleError(
|
||||
"%s: %s"
|
||||
% (
|
||||
missing_required_lib("ncclient"),
|
||||
to_native(NCCLIENT_IMP_ERR),
|
||||
)
|
||||
)
|
||||
|
||||
self.queue_message("log", "ssh connection done, starting ncclient")
|
||||
|
||||
allow_agent = True
|
||||
if self._play_context.password is not None:
|
||||
allow_agent = False
|
||||
setattr(self._play_context, "allow_agent", allow_agent)
|
||||
|
||||
self.key_filename = (
|
||||
self._play_context.private_key_file
|
||||
or self.get_option("private_key_file")
|
||||
)
|
||||
if self.key_filename:
|
||||
self.key_filename = str(os.path.expanduser(self.key_filename))
|
||||
|
||||
self._ssh_config = self.get_option("netconf_ssh_config")
|
||||
if self._ssh_config in BOOLEANS_TRUE:
|
||||
self._ssh_config = True
|
||||
elif self._ssh_config in BOOLEANS_FALSE:
|
||||
self._ssh_config = None
|
||||
|
||||
# Try to guess the network_os if the network_os is set to auto
|
||||
if self._network_os == "auto":
|
||||
for cls in netconf_loader.all(class_only=True):
|
||||
network_os = cls.guess_network_os(self)
|
||||
if network_os:
|
||||
self.queue_message(
|
||||
"vvv", "discovered network_os %s" % network_os
|
||||
)
|
||||
self._network_os = network_os
|
||||
|
||||
# If we have tried to detect the network_os but were unable to i.e. network_os is still 'auto'
|
||||
# then use default as the network_os
|
||||
|
||||
if self._network_os == "auto":
|
||||
# Network os not discovered. Set it to default
|
||||
self.queue_message(
|
||||
"vvv",
|
||||
"Unable to discover network_os. Falling back to default.",
|
||||
)
|
||||
self._network_os = "default"
|
||||
try:
|
||||
ncclient_device_handler = self.netconf.get_option(
|
||||
"ncclient_device_handler"
|
||||
)
|
||||
except KeyError:
|
||||
ncclient_device_handler = "default"
|
||||
self.queue_message(
|
||||
"vvv",
|
||||
"identified ncclient device handler: %s."
|
||||
% ncclient_device_handler,
|
||||
)
|
||||
device_params = {"name": ncclient_device_handler}
|
||||
|
||||
try:
|
||||
port = self._play_context.port or 830
|
||||
self.queue_message(
|
||||
"vvv",
|
||||
"ESTABLISH NETCONF SSH CONNECTION FOR USER: %s on PORT %s TO %s WITH SSH_CONFIG = %s"
|
||||
% (
|
||||
self._play_context.remote_user,
|
||||
port,
|
||||
self._play_context.remote_addr,
|
||||
self._ssh_config,
|
||||
),
|
||||
)
|
||||
self._manager = manager.connect(
|
||||
host=self._play_context.remote_addr,
|
||||
port=port,
|
||||
username=self._play_context.remote_user,
|
||||
password=self._play_context.password,
|
||||
key_filename=self.key_filename,
|
||||
hostkey_verify=self.get_option("host_key_checking"),
|
||||
look_for_keys=self.get_option("look_for_keys"),
|
||||
device_params=device_params,
|
||||
allow_agent=self._play_context.allow_agent,
|
||||
timeout=self.get_option("persistent_connect_timeout"),
|
||||
ssh_config=self._ssh_config,
|
||||
)
|
||||
|
||||
self._manager._timeout = self.get_option(
|
||||
"persistent_command_timeout"
|
||||
)
|
||||
except SSHUnknownHostError as exc:
|
||||
raise AnsibleConnectionFailure(to_native(exc))
|
||||
except ImportError:
|
||||
raise AnsibleError(
|
||||
"connection=netconf is not supported on {0}".format(
|
||||
self._network_os
|
||||
)
|
||||
)
|
||||
|
||||
if not self._manager.connected:
|
||||
return 1, b"", b"not connected"
|
||||
|
||||
self.queue_message(
|
||||
"log", "ncclient manager object created successfully"
|
||||
)
|
||||
|
||||
self._connected = True
|
||||
|
||||
super(Connection, self)._connect()
|
||||
|
||||
return (
|
||||
0,
|
||||
to_bytes(self._manager.session_id, errors="surrogate_or_strict"),
|
||||
b"",
|
||||
)
|
||||
|
||||
def close(self):
|
||||
if self._manager:
|
||||
self._manager.close_session()
|
||||
super(Connection, self).close()
|
@ -1,66 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
|
||||
class ModuleDocFragment(object):
|
||||
|
||||
# Standard files documentation fragment
|
||||
DOCUMENTATION = r"""options:
|
||||
host:
|
||||
description:
|
||||
- Specifies the DNS host name or address for connecting to the remote device over
|
||||
the specified transport. The value of host is used as the destination address
|
||||
for the transport.
|
||||
type: str
|
||||
required: true
|
||||
port:
|
||||
description:
|
||||
- Specifies the port to use when building the connection to the remote device. The
|
||||
port value will default to port 830.
|
||||
type: int
|
||||
default: 830
|
||||
username:
|
||||
description:
|
||||
- Configures the username to use to authenticate the connection to the remote
|
||||
device. This value is used to authenticate the SSH session. If the value is
|
||||
not specified in the task, the value of environment variable C(ANSIBLE_NET_USERNAME)
|
||||
will be used instead.
|
||||
type: str
|
||||
password:
|
||||
description:
|
||||
- Specifies the password to use to authenticate the connection to the remote device. This
|
||||
value is used to authenticate the SSH session. If the value is not specified
|
||||
in the task, the value of environment variable C(ANSIBLE_NET_PASSWORD) will
|
||||
be used instead.
|
||||
type: str
|
||||
timeout:
|
||||
description:
|
||||
- Specifies the timeout in seconds for communicating with the network device for
|
||||
either connecting or sending commands. If the timeout is exceeded before the
|
||||
operation is completed, the module will error.
|
||||
type: int
|
||||
default: 10
|
||||
ssh_keyfile:
|
||||
description:
|
||||
- Specifies the SSH key to use to authenticate the connection to the remote device. This
|
||||
value is the path to the key used to authenticate the SSH session. If the value
|
||||
is not specified in the task, the value of environment variable C(ANSIBLE_NET_SSH_KEYFILE)
|
||||
will be used instead.
|
||||
type: path
|
||||
hostkey_verify:
|
||||
description:
|
||||
- If set to C(yes), the ssh host key of the device must match a ssh key present
|
||||
on the host if set to C(no), the ssh host key of the device is not checked.
|
||||
type: bool
|
||||
default: true
|
||||
look_for_keys:
|
||||
description:
|
||||
- Enables looking in the usual locations for the ssh keys (e.g. :file:`~/.ssh/id_*`)
|
||||
type: bool
|
||||
default: true
|
||||
notes:
|
||||
- For information on using netconf see the :ref:`Platform Options guide using Netconf<netconf_enabled_platform_options>`
|
||||
- For more information on using Ansible to manage network devices see the :ref:`Ansible
|
||||
Network Guide <network_guide>`
|
||||
"""
|
@ -1,14 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2019 Ansible, Inc
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
|
||||
class ModuleDocFragment(object):
|
||||
|
||||
# Standard files documentation fragment
|
||||
DOCUMENTATION = r"""options: {}
|
||||
notes:
|
||||
- This module is supported on C(ansible_network_os) network platforms. See the :ref:`Network
|
||||
Platform Options <platform_options>` for details.
|
||||
"""
|
File diff suppressed because it is too large
Load Diff
@ -1,531 +0,0 @@
|
||||
#
|
||||
# {c) 2017 Red Hat, Inc.
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible 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.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# Make coding more python3-ish
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
import re
|
||||
import os
|
||||
import traceback
|
||||
import string
|
||||
|
||||
from collections.abc import Mapping
|
||||
from xml.etree.ElementTree import fromstring
|
||||
|
||||
from ansible.module_utils._text import to_native, to_text
|
||||
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
|
||||
Template,
|
||||
)
|
||||
from ansible.module_utils.six import iteritems, string_types
|
||||
from ansible.errors import AnsibleError, AnsibleFilterError
|
||||
from ansible.utils.display import Display
|
||||
from ansible.utils.encrypt import passlib_or_crypt, random_password
|
||||
|
||||
try:
|
||||
import yaml
|
||||
|
||||
HAS_YAML = True
|
||||
except ImportError:
|
||||
HAS_YAML = False
|
||||
|
||||
try:
|
||||
import textfsm
|
||||
|
||||
HAS_TEXTFSM = True
|
||||
except ImportError:
|
||||
HAS_TEXTFSM = False
|
||||
|
||||
display = Display()
|
||||
|
||||
|
||||
def re_matchall(regex, value):
|
||||
objects = list()
|
||||
for match in re.findall(regex.pattern, value, re.M):
|
||||
obj = {}
|
||||
if regex.groupindex:
|
||||
for name, index in iteritems(regex.groupindex):
|
||||
if len(regex.groupindex) == 1:
|
||||
obj[name] = match
|
||||
else:
|
||||
obj[name] = match[index - 1]
|
||||
objects.append(obj)
|
||||
return objects
|
||||
|
||||
|
||||
def re_search(regex, value):
|
||||
obj = {}
|
||||
match = regex.search(value, re.M)
|
||||
if match:
|
||||
items = list(match.groups())
|
||||
if regex.groupindex:
|
||||
for name, index in iteritems(regex.groupindex):
|
||||
obj[name] = items[index - 1]
|
||||
return obj
|
||||
|
||||
|
||||
def parse_cli(output, tmpl):
|
||||
if not isinstance(output, string_types):
|
||||
raise AnsibleError(
|
||||
"parse_cli input should be a string, but was given a input of %s"
|
||||
% (type(output))
|
||||
)
|
||||
|
||||
if not os.path.exists(tmpl):
|
||||
raise AnsibleError("unable to locate parse_cli template: %s" % tmpl)
|
||||
|
||||
try:
|
||||
template = Template()
|
||||
except ImportError as exc:
|
||||
raise AnsibleError(to_native(exc))
|
||||
|
||||
with open(tmpl) as tmpl_fh:
|
||||
tmpl_content = tmpl_fh.read()
|
||||
|
||||
spec = yaml.safe_load(tmpl_content)
|
||||
obj = {}
|
||||
|
||||
for name, attrs in iteritems(spec["keys"]):
|
||||
value = attrs["value"]
|
||||
|
||||
try:
|
||||
variables = spec.get("vars", {})
|
||||
value = template(value, variables)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if "start_block" in attrs and "end_block" in attrs:
|
||||
start_block = re.compile(attrs["start_block"])
|
||||
end_block = re.compile(attrs["end_block"])
|
||||
|
||||
blocks = list()
|
||||
lines = None
|
||||
block_started = False
|
||||
|
||||
for line in output.split("\n"):
|
||||
match_start = start_block.match(line)
|
||||
match_end = end_block.match(line)
|
||||
|
||||
if match_start:
|
||||
lines = list()
|
||||
lines.append(line)
|
||||
block_started = True
|
||||
|
||||
elif match_end:
|
||||
if lines:
|
||||
lines.append(line)
|
||||
blocks.append("\n".join(lines))
|
||||
block_started = False
|
||||
|
||||
elif block_started:
|
||||
if lines:
|
||||
lines.append(line)
|
||||
|
||||
regex_items = [re.compile(r) for r in attrs["items"]]
|
||||
objects = list()
|
||||
|
||||
for block in blocks:
|
||||
if isinstance(value, Mapping) and "key" not in value:
|
||||
items = list()
|
||||
for regex in regex_items:
|
||||
match = regex.search(block)
|
||||
if match:
|
||||
item_values = match.groupdict()
|
||||
item_values["match"] = list(match.groups())
|
||||
items.append(item_values)
|
||||
else:
|
||||
items.append(None)
|
||||
|
||||
obj = {}
|
||||
for k, v in iteritems(value):
|
||||
try:
|
||||
obj[k] = template(
|
||||
v, {"item": items}, fail_on_undefined=False
|
||||
)
|
||||
except Exception:
|
||||
obj[k] = None
|
||||
objects.append(obj)
|
||||
|
||||
elif isinstance(value, Mapping):
|
||||
items = list()
|
||||
for regex in regex_items:
|
||||
match = regex.search(block)
|
||||
if match:
|
||||
item_values = match.groupdict()
|
||||
item_values["match"] = list(match.groups())
|
||||
items.append(item_values)
|
||||
else:
|
||||
items.append(None)
|
||||
|
||||
key = template(value["key"], {"item": items})
|
||||
values = dict(
|
||||
[
|
||||
(k, template(v, {"item": items}))
|
||||
for k, v in iteritems(value["values"])
|
||||
]
|
||||
)
|
||||
objects.append({key: values})
|
||||
|
||||
return objects
|
||||
|
||||
elif "items" in attrs:
|
||||
regexp = re.compile(attrs["items"])
|
||||
when = attrs.get("when")
|
||||
conditional = (
|
||||
"{%% if %s %%}True{%% else %%}False{%% endif %%}" % when
|
||||
)
|
||||
|
||||
if isinstance(value, Mapping) and "key" not in value:
|
||||
values = list()
|
||||
|
||||
for item in re_matchall(regexp, output):
|
||||
entry = {}
|
||||
|
||||
for item_key, item_value in iteritems(value):
|
||||
entry[item_key] = template(item_value, {"item": item})
|
||||
|
||||
if when:
|
||||
if template(conditional, {"item": entry}):
|
||||
values.append(entry)
|
||||
else:
|
||||
values.append(entry)
|
||||
|
||||
obj[name] = values
|
||||
|
||||
elif isinstance(value, Mapping):
|
||||
values = dict()
|
||||
|
||||
for item in re_matchall(regexp, output):
|
||||
entry = {}
|
||||
|
||||
for item_key, item_value in iteritems(value["values"]):
|
||||
entry[item_key] = template(item_value, {"item": item})
|
||||
|
||||
key = template(value["key"], {"item": item})
|
||||
|
||||
if when:
|
||||
if template(
|
||||
conditional, {"item": {"key": key, "value": entry}}
|
||||
):
|
||||
values[key] = entry
|
||||
else:
|
||||
values[key] = entry
|
||||
|
||||
obj[name] = values
|
||||
|
||||
else:
|
||||
item = re_search(regexp, output)
|
||||
obj[name] = template(value, {"item": item})
|
||||
|
||||
else:
|
||||
obj[name] = value
|
||||
|
||||
return obj
|
||||
|
||||
|
||||
def parse_cli_textfsm(value, template):
|
||||
if not HAS_TEXTFSM:
|
||||
raise AnsibleError(
|
||||
"parse_cli_textfsm filter requires TextFSM library to be installed"
|
||||
)
|
||||
|
||||
if not isinstance(value, string_types):
|
||||
raise AnsibleError(
|
||||
"parse_cli_textfsm input should be a string, but was given a input of %s"
|
||||
% (type(value))
|
||||
)
|
||||
|
||||
if not os.path.exists(template):
|
||||
raise AnsibleError(
|
||||
"unable to locate parse_cli_textfsm template: %s" % template
|
||||
)
|
||||
|
||||
try:
|
||||
template = open(template)
|
||||
except IOError as exc:
|
||||
raise AnsibleError(to_native(exc))
|
||||
|
||||
re_table = textfsm.TextFSM(template)
|
||||
fsm_results = re_table.ParseText(value)
|
||||
|
||||
results = list()
|
||||
for item in fsm_results:
|
||||
results.append(dict(zip(re_table.header, item)))
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def _extract_param(template, root, attrs, value):
|
||||
|
||||
key = None
|
||||
when = attrs.get("when")
|
||||
conditional = "{%% if %s %%}True{%% else %%}False{%% endif %%}" % when
|
||||
param_to_xpath_map = attrs["items"]
|
||||
|
||||
if isinstance(value, Mapping):
|
||||
key = value.get("key", None)
|
||||
if key:
|
||||
value = value["values"]
|
||||
|
||||
entries = dict() if key else list()
|
||||
|
||||
for element in root.findall(attrs["top"]):
|
||||
entry = dict()
|
||||
item_dict = dict()
|
||||
for param, param_xpath in iteritems(param_to_xpath_map):
|
||||
fields = None
|
||||
try:
|
||||
fields = element.findall(param_xpath)
|
||||
except Exception:
|
||||
display.warning(
|
||||
"Failed to evaluate value of '%s' with XPath '%s'.\nUnexpected error: %s."
|
||||
% (param, param_xpath, traceback.format_exc())
|
||||
)
|
||||
|
||||
tags = param_xpath.split("/")
|
||||
|
||||
# check if xpath ends with attribute.
|
||||
# If yes set attribute key/value dict to param value in case attribute matches
|
||||
# else if it is a normal xpath assign matched element text value.
|
||||
if len(tags) and tags[-1].endswith("]"):
|
||||
if fields:
|
||||
if len(fields) > 1:
|
||||
item_dict[param] = [field.attrib for field in fields]
|
||||
else:
|
||||
item_dict[param] = fields[0].attrib
|
||||
else:
|
||||
item_dict[param] = {}
|
||||
else:
|
||||
if fields:
|
||||
if len(fields) > 1:
|
||||
item_dict[param] = [field.text for field in fields]
|
||||
else:
|
||||
item_dict[param] = fields[0].text
|
||||
else:
|
||||
item_dict[param] = None
|
||||
|
||||
if isinstance(value, Mapping):
|
||||
for item_key, item_value in iteritems(value):
|
||||
entry[item_key] = template(item_value, {"item": item_dict})
|
||||
else:
|
||||
entry = template(value, {"item": item_dict})
|
||||
|
||||
if key:
|
||||
expanded_key = template(key, {"item": item_dict})
|
||||
if when:
|
||||
if template(
|
||||
conditional,
|
||||
{"item": {"key": expanded_key, "value": entry}},
|
||||
):
|
||||
entries[expanded_key] = entry
|
||||
else:
|
||||
entries[expanded_key] = entry
|
||||
else:
|
||||
if when:
|
||||
if template(conditional, {"item": entry}):
|
||||
entries.append(entry)
|
||||
else:
|
||||
entries.append(entry)
|
||||
|
||||
return entries
|
||||
|
||||
|
||||
def parse_xml(output, tmpl):
|
||||
if not os.path.exists(tmpl):
|
||||
raise AnsibleError("unable to locate parse_xml template: %s" % tmpl)
|
||||
|
||||
if not isinstance(output, string_types):
|
||||
raise AnsibleError(
|
||||
"parse_xml works on string input, but given input of : %s"
|
||||
% type(output)
|
||||
)
|
||||
|
||||
root = fromstring(output)
|
||||
try:
|
||||
template = Template()
|
||||
except ImportError as exc:
|
||||
raise AnsibleError(to_native(exc))
|
||||
|
||||
with open(tmpl) as tmpl_fh:
|
||||
tmpl_content = tmpl_fh.read()
|
||||
|
||||
spec = yaml.safe_load(tmpl_content)
|
||||
obj = {}
|
||||
|
||||
for name, attrs in iteritems(spec["keys"]):
|
||||
value = attrs["value"]
|
||||
|
||||
try:
|
||||
variables = spec.get("vars", {})
|
||||
value = template(value, variables)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if "items" in attrs:
|
||||
obj[name] = _extract_param(template, root, attrs, value)
|
||||
else:
|
||||
obj[name] = value
|
||||
|
||||
return obj
|
||||
|
||||
|
||||
def type5_pw(password, salt=None):
|
||||
if not isinstance(password, string_types):
|
||||
raise AnsibleFilterError(
|
||||
"type5_pw password input should be a string, but was given a input of %s"
|
||||
% (type(password).__name__)
|
||||
)
|
||||
|
||||
salt_chars = u"".join(
|
||||
(to_text(string.ascii_letters), to_text(string.digits), u"./")
|
||||
)
|
||||
if salt is not None and not isinstance(salt, string_types):
|
||||
raise AnsibleFilterError(
|
||||
"type5_pw salt input should be a string, but was given a input of %s"
|
||||
% (type(salt).__name__)
|
||||
)
|
||||
elif not salt:
|
||||
salt = random_password(length=4, chars=salt_chars)
|
||||
elif not set(salt) <= set(salt_chars):
|
||||
raise AnsibleFilterError(
|
||||
"type5_pw salt used inproper characters, must be one of %s"
|
||||
% (salt_chars)
|
||||
)
|
||||
|
||||
encrypted_password = passlib_or_crypt(password, "md5_crypt", salt=salt)
|
||||
|
||||
return encrypted_password
|
||||
|
||||
|
||||
def hash_salt(password):
|
||||
|
||||
split_password = password.split("$")
|
||||
if len(split_password) != 4:
|
||||
raise AnsibleFilterError(
|
||||
"Could not parse salt out password correctly from {0}".format(
|
||||
password
|
||||
)
|
||||
)
|
||||
else:
|
||||
return split_password[2]
|
||||
|
||||
|
||||
def comp_type5(
|
||||
unencrypted_password, encrypted_password, return_original=False
|
||||
):
|
||||
|
||||
salt = hash_salt(encrypted_password)
|
||||
if type5_pw(unencrypted_password, salt) == encrypted_password:
|
||||
if return_original is True:
|
||||
return encrypted_password
|
||||
else:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def vlan_parser(vlan_list, first_line_len=48, other_line_len=44):
|
||||
|
||||
"""
|
||||
Input: Unsorted list of vlan integers
|
||||
Output: Sorted string list of integers according to IOS-like vlan list rules
|
||||
|
||||
1. Vlans are listed in ascending order
|
||||
2. Runs of 3 or more consecutive vlans are listed with a dash
|
||||
3. The first line of the list can be first_line_len characters long
|
||||
4. Subsequent list lines can be other_line_len characters
|
||||
"""
|
||||
|
||||
# Sort and remove duplicates
|
||||
sorted_list = sorted(set(vlan_list))
|
||||
|
||||
if sorted_list[0] < 1 or sorted_list[-1] > 4094:
|
||||
raise AnsibleFilterError("Valid VLAN range is 1-4094")
|
||||
|
||||
parse_list = []
|
||||
idx = 0
|
||||
while idx < len(sorted_list):
|
||||
start = idx
|
||||
end = start
|
||||
while end < len(sorted_list) - 1:
|
||||
if sorted_list[end + 1] - sorted_list[end] == 1:
|
||||
end += 1
|
||||
else:
|
||||
break
|
||||
|
||||
if start == end:
|
||||
# Single VLAN
|
||||
parse_list.append(str(sorted_list[idx]))
|
||||
elif start + 1 == end:
|
||||
# Run of 2 VLANs
|
||||
parse_list.append(str(sorted_list[start]))
|
||||
parse_list.append(str(sorted_list[end]))
|
||||
else:
|
||||
# Run of 3 or more VLANs
|
||||
parse_list.append(
|
||||
str(sorted_list[start]) + "-" + str(sorted_list[end])
|
||||
)
|
||||
idx = end + 1
|
||||
|
||||
line_count = 0
|
||||
result = [""]
|
||||
for vlans in parse_list:
|
||||
# First line (" switchport trunk allowed vlan ")
|
||||
if line_count == 0:
|
||||
if len(result[line_count] + vlans) > first_line_len:
|
||||
result.append("")
|
||||
line_count += 1
|
||||
result[line_count] += vlans + ","
|
||||
else:
|
||||
result[line_count] += vlans + ","
|
||||
|
||||
# Subsequent lines (" switchport trunk allowed vlan add ")
|
||||
else:
|
||||
if len(result[line_count] + vlans) > other_line_len:
|
||||
result.append("")
|
||||
line_count += 1
|
||||
result[line_count] += vlans + ","
|
||||
else:
|
||||
result[line_count] += vlans + ","
|
||||
|
||||
# Remove trailing orphan commas
|
||||
for idx in range(0, len(result)):
|
||||
result[idx] = result[idx].rstrip(",")
|
||||
|
||||
# Sometimes text wraps to next line, but there are no remaining VLANs
|
||||
if "" in result:
|
||||
result.remove("")
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class FilterModule(object):
|
||||
"""Filters for working with output from network devices"""
|
||||
|
||||
filter_map = {
|
||||
"parse_cli": parse_cli,
|
||||
"parse_cli_textfsm": parse_cli_textfsm,
|
||||
"parse_xml": parse_xml,
|
||||
"type5_pw": type5_pw,
|
||||
"hash_salt": hash_salt,
|
||||
"comp_type5": comp_type5,
|
||||
"vlan_parser": vlan_parser,
|
||||
}
|
||||
|
||||
def filters(self):
|
||||
return self.filter_map
|
@ -1,91 +0,0 @@
|
||||
# Copyright (c) 2018 Cisco and/or its affiliates.
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible 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.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = """author: Ansible Networking Team
|
||||
httpapi: restconf
|
||||
short_description: HttpApi Plugin for devices supporting Restconf API
|
||||
description:
|
||||
- This HttpApi plugin provides methods to connect to Restconf API endpoints.
|
||||
options:
|
||||
root_path:
|
||||
type: str
|
||||
description:
|
||||
- Specifies the location of the Restconf root.
|
||||
default: /restconf
|
||||
vars:
|
||||
- name: ansible_httpapi_restconf_root
|
||||
"""
|
||||
|
||||
import json
|
||||
|
||||
from ansible.module_utils._text import to_text
|
||||
from ansible.module_utils.connection import ConnectionError
|
||||
from ansible.module_utils.six.moves.urllib.error import HTTPError
|
||||
from ansible.plugins.httpapi import HttpApiBase
|
||||
|
||||
|
||||
CONTENT_TYPE = "application/yang-data+json"
|
||||
|
||||
|
||||
class HttpApi(HttpApiBase):
|
||||
def send_request(self, data, **message_kwargs):
|
||||
if data:
|
||||
data = json.dumps(data)
|
||||
|
||||
path = "/".join(
|
||||
[
|
||||
self.get_option("root_path").rstrip("/"),
|
||||
message_kwargs.get("path", "").lstrip("/"),
|
||||
]
|
||||
)
|
||||
|
||||
headers = {
|
||||
"Content-Type": message_kwargs.get("content_type") or CONTENT_TYPE,
|
||||
"Accept": message_kwargs.get("accept") or CONTENT_TYPE,
|
||||
}
|
||||
response, response_data = self.connection.send(
|
||||
path, data, headers=headers, method=message_kwargs.get("method")
|
||||
)
|
||||
|
||||
return handle_response(response, response_data)
|
||||
|
||||
|
||||
def handle_response(response, response_data):
|
||||
try:
|
||||
response_data = json.loads(response_data.read())
|
||||
except ValueError:
|
||||
response_data = response_data.read()
|
||||
|
||||
if isinstance(response, HTTPError):
|
||||
if response_data:
|
||||
if "errors" in response_data:
|
||||
errors = response_data["errors"]["error"]
|
||||
error_text = "\n".join(
|
||||
(error["error-message"] for error in errors)
|
||||
)
|
||||
else:
|
||||
error_text = response_data
|
||||
|
||||
raise ConnectionError(error_text, code=response.code)
|
||||
raise ConnectionError(to_text(response), code=response.code)
|
||||
|
||||
return response_data
|
@ -1,147 +0,0 @@
|
||||
#
|
||||
# (c) 2018 Red Hat, Inc.
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible 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.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
import json
|
||||
|
||||
from copy import deepcopy
|
||||
from contextlib import contextmanager
|
||||
|
||||
try:
|
||||
from lxml.etree import fromstring, tostring
|
||||
except ImportError:
|
||||
from xml.etree.ElementTree import fromstring, tostring
|
||||
|
||||
from ansible.module_utils._text import to_text, to_bytes
|
||||
from ansible.module_utils.connection import Connection, ConnectionError
|
||||
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.netconf import (
|
||||
NetconfConnection,
|
||||
)
|
||||
|
||||
|
||||
IGNORE_XML_ATTRIBUTE = ()
|
||||
|
||||
|
||||
def get_connection(module):
|
||||
if hasattr(module, "_netconf_connection"):
|
||||
return module._netconf_connection
|
||||
|
||||
capabilities = get_capabilities(module)
|
||||
network_api = capabilities.get("network_api")
|
||||
if network_api == "netconf":
|
||||
module._netconf_connection = NetconfConnection(module._socket_path)
|
||||
else:
|
||||
module.fail_json(msg="Invalid connection type %s" % network_api)
|
||||
|
||||
return module._netconf_connection
|
||||
|
||||
|
||||
def get_capabilities(module):
|
||||
if hasattr(module, "_netconf_capabilities"):
|
||||
return module._netconf_capabilities
|
||||
|
||||
capabilities = Connection(module._socket_path).get_capabilities()
|
||||
module._netconf_capabilities = json.loads(capabilities)
|
||||
return module._netconf_capabilities
|
||||
|
||||
|
||||
def lock_configuration(module, target=None):
|
||||
conn = get_connection(module)
|
||||
return conn.lock(target=target)
|
||||
|
||||
|
||||
def unlock_configuration(module, target=None):
|
||||
conn = get_connection(module)
|
||||
return conn.unlock(target=target)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def locked_config(module, target=None):
|
||||
try:
|
||||
lock_configuration(module, target=target)
|
||||
yield
|
||||
finally:
|
||||
unlock_configuration(module, target=target)
|
||||
|
||||
|
||||
def get_config(module, source, filter=None, lock=False):
|
||||
conn = get_connection(module)
|
||||
try:
|
||||
locked = False
|
||||
if lock:
|
||||
conn.lock(target=source)
|
||||
locked = True
|
||||
response = conn.get_config(source=source, filter=filter)
|
||||
|
||||
except ConnectionError as e:
|
||||
module.fail_json(
|
||||
msg=to_text(e, errors="surrogate_then_replace").strip()
|
||||
)
|
||||
|
||||
finally:
|
||||
if locked:
|
||||
conn.unlock(target=source)
|
||||
|
||||
return response
|
||||
|
||||
|
||||
def get(module, filter, lock=False):
|
||||
conn = get_connection(module)
|
||||
try:
|
||||
locked = False
|
||||
if lock:
|
||||
conn.lock(target="running")
|
||||
locked = True
|
||||
|
||||
response = conn.get(filter=filter)
|
||||
|
||||
except ConnectionError as e:
|
||||
module.fail_json(
|
||||
msg=to_text(e, errors="surrogate_then_replace").strip()
|
||||
)
|
||||
|
||||
finally:
|
||||
if locked:
|
||||
conn.unlock(target="running")
|
||||
|
||||
return response
|
||||
|
||||
|
||||
def dispatch(module, request):
|
||||
conn = get_connection(module)
|
||||
try:
|
||||
response = conn.dispatch(request)
|
||||
except ConnectionError as e:
|
||||
module.fail_json(
|
||||
msg=to_text(e, errors="surrogate_then_replace").strip()
|
||||
)
|
||||
|
||||
return response
|
||||
|
||||
|
||||
def sanitize_xml(data):
|
||||
tree = fromstring(
|
||||
to_bytes(deepcopy(data), errors="surrogate_then_replace")
|
||||
)
|
||||
for element in tree.getiterator():
|
||||
# remove attributes
|
||||
attribute = element.attrib
|
||||
if attribute:
|
||||
for key in list(attribute):
|
||||
if key not in IGNORE_XML_ATTRIBUTE:
|
||||
attribute.pop(key)
|
||||
return to_text(tostring(tree), errors="surrogate_then_replace").strip()
|
@ -1,61 +0,0 @@
|
||||
# This code is part of Ansible, but is an independent component.
|
||||
# This particular file snippet, and this file snippet only, is BSD licensed.
|
||||
# Modules you write using this snippet, which is embedded dynamically by Ansible
|
||||
# still belong to the author of the module, and may assign their own license
|
||||
# to the complete work.
|
||||
#
|
||||
# (c) 2018 Red Hat Inc.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
||||
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#
|
||||
|
||||
from ansible.module_utils.connection import Connection
|
||||
|
||||
|
||||
def get(module, path=None, content=None, fields=None, output="json"):
|
||||
if path is None:
|
||||
raise ValueError("path value must be provided")
|
||||
if content:
|
||||
path += "?" + "content=%s" % content
|
||||
if fields:
|
||||
path += "?" + "field=%s" % fields
|
||||
|
||||
accept = None
|
||||
if output == "xml":
|
||||
accept = "application/yang-data+xml"
|
||||
|
||||
connection = Connection(module._socket_path)
|
||||
return connection.send_request(
|
||||
None, path=path, method="GET", accept=accept
|
||||
)
|
||||
|
||||
|
||||
def edit_config(module, path=None, content=None, method="GET", format="json"):
|
||||
if path is None:
|
||||
raise ValueError("path value must be provided")
|
||||
|
||||
content_type = None
|
||||
if format == "xml":
|
||||
content_type = "application/yang-data+xml"
|
||||
|
||||
connection = Connection(module._socket_path)
|
||||
return connection.send_request(
|
||||
content, path=path, method=method, content_type=content_type
|
||||
)
|
@ -1,71 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2018, Ansible by Red Hat, inc
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
ANSIBLE_METADATA = {
|
||||
"metadata_version": "1.1",
|
||||
"status": ["preview"],
|
||||
"supported_by": "network",
|
||||
}
|
||||
|
||||
|
||||
DOCUMENTATION = """module: net_get
|
||||
author: Deepak Agrawal (@dagrawal)
|
||||
short_description: Copy a file from a network device to Ansible Controller
|
||||
description:
|
||||
- This module provides functionality to copy file from network device to ansible controller.
|
||||
extends_documentation_fragment:
|
||||
- ansible.netcommon.network_agnostic
|
||||
options:
|
||||
src:
|
||||
description:
|
||||
- Specifies the source file. The path to the source file can either be the full
|
||||
path on the network device or a relative path as per path supported by destination
|
||||
network device.
|
||||
required: true
|
||||
protocol:
|
||||
description:
|
||||
- Protocol used to transfer file.
|
||||
default: scp
|
||||
choices:
|
||||
- scp
|
||||
- sftp
|
||||
dest:
|
||||
description:
|
||||
- Specifies the destination file. The path to the destination file can either
|
||||
be the full path on the Ansible control host or a relative path from the playbook
|
||||
or role root directory.
|
||||
default:
|
||||
- Same filename as specified in I(src). The path will be playbook root or role
|
||||
root directory if playbook is part of a role.
|
||||
requirements:
|
||||
- scp
|
||||
notes:
|
||||
- Some devices need specific configurations to be enabled before scp can work These
|
||||
configuration should be pre-configured before using this module e.g ios - C(ip scp
|
||||
server enable).
|
||||
- User privilege to do scp on network device should be pre-configured e.g. ios - need
|
||||
user privilege 15 by default for allowing scp.
|
||||
- Default destination of source file.
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
- name: copy file from the network device to Ansible controller
|
||||
net_get:
|
||||
src: running_cfg_ios1.txt
|
||||
|
||||
- name: copy file from ios to common location at /tmp
|
||||
net_get:
|
||||
src: running_cfg_sw1.txt
|
||||
dest : /tmp/ios1.txt
|
||||
"""
|
||||
|
||||
RETURN = """
|
||||
"""
|
@ -1,82 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2018, Ansible by Red Hat, inc
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
ANSIBLE_METADATA = {
|
||||
"metadata_version": "1.1",
|
||||
"status": ["preview"],
|
||||
"supported_by": "network",
|
||||
}
|
||||
|
||||
|
||||
DOCUMENTATION = """module: net_put
|
||||
author: Deepak Agrawal (@dagrawal)
|
||||
short_description: Copy a file from Ansible Controller to a network device
|
||||
description:
|
||||
- This module provides functionality to copy file from Ansible controller to network
|
||||
devices.
|
||||
extends_documentation_fragment:
|
||||
- ansible.netcommon.network_agnostic
|
||||
options:
|
||||
src:
|
||||
description:
|
||||
- Specifies the source file. 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.
|
||||
required: true
|
||||
protocol:
|
||||
description:
|
||||
- Protocol used to transfer file.
|
||||
default: scp
|
||||
choices:
|
||||
- scp
|
||||
- sftp
|
||||
dest:
|
||||
description:
|
||||
- Specifies the destination file. The path to destination file can either be the
|
||||
full path or relative path as supported by network_os.
|
||||
default:
|
||||
- Filename from src and at default directory of user shell on network_os.
|
||||
required: false
|
||||
mode:
|
||||
description:
|
||||
- Set the file transfer mode. If mode is set to I(text) then I(src) file will
|
||||
go through Jinja2 template engine to replace any vars if present in the src
|
||||
file. If mode is set to I(binary) then file will be copied as it is to destination
|
||||
device.
|
||||
default: binary
|
||||
choices:
|
||||
- binary
|
||||
- text
|
||||
requirements:
|
||||
- scp
|
||||
notes:
|
||||
- Some devices need specific configurations to be enabled before scp can work These
|
||||
configuration should be pre-configured before using this module e.g ios - C(ip scp
|
||||
server enable).
|
||||
- User privilege to do scp on network device should be pre-configured e.g. ios - need
|
||||
user privilege 15 by default for allowing scp.
|
||||
- Default destination of source file.
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
- name: copy file from ansible controller to a network device
|
||||
net_put:
|
||||
src: running_cfg_ios1.txt
|
||||
|
||||
- name: copy file at root dir of flash in slot 3 of sw1(ios)
|
||||
net_put:
|
||||
src: running_cfg_sw1.txt
|
||||
protocol: sftp
|
||||
dest : flash3:/running_cfg_sw1.txt
|
||||
"""
|
||||
|
||||
RETURN = """
|
||||
"""
|
@ -1,70 +0,0 @@
|
||||
#
|
||||
# (c) 2017 Red Hat Inc.
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible 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.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = """author: Ansible Networking Team
|
||||
netconf: default
|
||||
short_description: Use default netconf plugin to run standard netconf commands as
|
||||
per RFC
|
||||
description:
|
||||
- This default plugin provides low level abstraction apis for sending and receiving
|
||||
netconf commands as per Netconf RFC specification.
|
||||
options:
|
||||
ncclient_device_handler:
|
||||
type: str
|
||||
default: default
|
||||
description:
|
||||
- Specifies the ncclient device handler name for network os that support default
|
||||
netconf implementation as per Netconf RFC specification. To identify the ncclient
|
||||
device handler name refer ncclient library documentation.
|
||||
"""
|
||||
import json
|
||||
|
||||
from ansible.module_utils._text import to_text
|
||||
from ansible.plugins.netconf import NetconfBase
|
||||
|
||||
|
||||
class Netconf(NetconfBase):
|
||||
def get_text(self, ele, tag):
|
||||
try:
|
||||
return to_text(
|
||||
ele.find(tag).text, errors="surrogate_then_replace"
|
||||
).strip()
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
def get_device_info(self):
|
||||
device_info = dict()
|
||||
device_info["network_os"] = "default"
|
||||
return device_info
|
||||
|
||||
def get_capabilities(self):
|
||||
result = dict()
|
||||
result["rpc"] = self.get_base_rpc()
|
||||
result["network_api"] = "netconf"
|
||||
result["device_info"] = self.get_device_info()
|
||||
result["server_capabilities"] = [c for c in self.m.server_capabilities]
|
||||
result["client_capabilities"] = [c for c in self.m.client_capabilities]
|
||||
result["session_id"] = self.m.session_id
|
||||
result["device_operations"] = self.get_device_operations(
|
||||
result["server_capabilities"]
|
||||
)
|
||||
return json.dumps(result)
|
Loading…
Reference in New Issue