collection 교체

This commit is contained in:
정훈 변
2024-02-23 16:37:40 +09:00
parent b494779b5b
commit 3fd554eee9
38862 changed files with 220204 additions and 6600073 deletions

View File

@@ -0,0 +1,278 @@
# (c) 2022 Ansible Project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = """
author:
- Ansible Networking Team (@ansible-network)
name: grpc
short_description: Provides a persistent connection using the gRPC protocol
description:
- This connection plugin provides a connection to remote devices over gRPC and
is typically used with devices for sending and receiving RPC calls
over gRPC framework.
- Note this connection plugin requires the grpcio python library to be installed on the
local Ansible controller.
version_added: "3.1.0"
requirements:
- grpcio
- protobuf
extends_documentation_fragment:
- ansible.netcommon.connection_persistent
options:
host:
description:
- Specifies the remote device FQDN or IP address to establish the gRPC
connection to.
default: inventory_hostname
type: string
vars:
- name: ansible_host
port:
type: int
description:
- Specifies the port on the remote device that listens for connections
when establishing the gRPC connection. If None only the C(host) part will
be used.
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 grpc plugin to communicate with the remote
device.
type: string
vars:
- name: ansible_network_os
remote_user:
description:
- The username used to authenticate to the remote device when the gRPC
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.
type: string
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 gRPC connection.
type: string
vars:
- name: ansible_password
- name: ansible_ssh_pass
private_key_file:
description:
- The PEM encoded private key file used to authenticate to the
remote device when first establishing the grpc connection.
type: string
ini:
- section: grpc_connection
key: private_key_file
env:
- name: ANSIBLE_PRIVATE_KEY_FILE
vars:
- name: ansible_private_key_file
root_certificates_file:
description:
- The PEM encoded root certificate file used to create a SSL-enabled
channel, if the value is None it reads the root certificates from
a default location chosen by gRPC at runtime.
type: string
ini:
- section: grpc_connection
key: root_certificates_file
env:
- name: ANSIBLE_ROOT_CERTIFICATES_FILE
vars:
- name: ansible_root_certificates_file
certificate_chain_file:
description:
- The PEM encoded certificate chain file used to create a SSL-enabled
channel. If the value is None, no certificate chain is used.
type: string
ini:
- section: grpc_connection
key: certificate_chain_file
env:
- name: ANSIBLE_CERTIFICATE_CHAIN_FILE
vars:
- name: ansible_certificate_chain_file
ssl_target_name_override:
description:
- The option overrides SSL target name used for SSL host name checking.
The name used for SSL host name checking will be the target parameter
(assuming that the secure channel is an SSL channel). If this parameter is
specified and the underlying is not an SSL channel, it will just be ignored.
type: string
ini:
- section: grpc_connection
key: ssl_target_name_override
env:
- name: ANSIBLE_GPRC_SSL_TARGET_NAME_OVERRIDE
vars:
- name: ansible_grpc_ssl_target_name_override
grpc_type:
description:
- This option indicates the grpc type and it can be used
in place of network_os. (example cisco.iosxr.iosxr)
default: False
ini:
- section: grpc_connection
key: type
env:
- name: ANSIBLE_GRPC_CONNECTION_TYPE
vars:
- name: ansible_grpc_connection_type
"""
from importlib import import_module
from ansible.errors import AnsibleConnectionFailure, AnsibleError
from ansible.plugins.connection import NetworkConnectionBase
try:
from grpc import insecure_channel, secure_channel, ssl_channel_credentials
from grpc.beta import implementations
HAS_GRPC = True
except ImportError:
HAS_GRPC = False
try:
from google import protobuf # noqa: F401 # pylint: disable=unused-import
HAS_PROTOBUF = True
except ImportError:
HAS_PROTOBUF = False
class Connection(NetworkConnectionBase):
"""GRPC connections"""
transport = "ansible.netcommon.grpc"
has_pipelining = False
def __init__(self, play_context, new_stdin, *args, **kwargs):
super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs)
grpc_type = self._network_os or self.get_option("grpc_type")
if grpc_type:
if not HAS_PROTOBUF:
raise AnsibleError(
"protobuf is required to use the grpc connection type. Please run 'pip install protobuf'"
)
if not self._network_os:
self._network_os = grpc_type
cref = dict(zip(["corg", "cname", "plugin"], grpc_type.split(".")))
grpclib = "ansible_collections.{corg}.{cname}.plugins.sub_plugins.grpc.{plugin}".format(
**cref
)
grpccls = getattr(import_module(grpclib), "Grpc")
grpc_obj = grpccls(self)
if grpc_obj:
self._sub_plugin = {
"type": "grpc",
"name": grpc_type,
"obj": grpc_obj,
}
self.queue_message("log", "loaded gRPC plugin for type %s" % grpc_type)
self.queue_message("log", "grpc type is set to %s" % grpc_type)
else:
raise AnsibleConnectionFailure(
"unable to load API plugin for network_os %s" % grpc_type
)
else:
raise AnsibleConnectionFailure(
"Unable to automatically determine gRPC implementation type."
" Please manually configure ansible_network_os value or grpc_type configuration for this host",
)
def _connect(self):
"""
Create GRPC connection to target host
:return: None
"""
if not HAS_GRPC:
raise AnsibleError(
"grpcio is required to use the gRPC connection type. Please run 'pip install grpcio'"
)
host = self.get_option("host")
host = self._play_context.remote_addr
if self.connected:
self.queue_message("log", "gRPC connection to host %s already exist" % host)
return
port = self.get_option("port")
self._target = host if port is None else "%s:%d" % (host, port)
self._timeout = self.get_option("persistent_command_timeout")
self._login_credentials = [
("username", self.get_option("remote_user")),
("password", self.get_option("password")),
]
ssl_target_name_override = self.get_option("ssl_target_name_override")
if ssl_target_name_override:
self._channel_options = [
("grpc.ssl_target_name_override", ssl_target_name_override),
]
else:
self._channel_options = None
certs = {}
private_key_file = self.get_option("private_key_file")
root_certificates_file = self.get_option("root_certificates_file")
certificate_chain_file = self.get_option("certificate_chain_file")
try:
if root_certificates_file:
with open(root_certificates_file, "rb") as f:
certs["root_certificates"] = f.read()
if private_key_file:
with open(private_key_file, "rb") as f:
certs["private_key"] = f.read()
if certificate_chain_file:
with open(certificate_chain_file, "rb") as f:
certs["certificate_chain"] = f.read()
except Exception as e:
raise AnsibleConnectionFailure("Failed to read certificate keys: %s" % e)
if certs:
creds = ssl_channel_credentials(**certs)
channel = secure_channel(self._target, creds, options=self._channel_options)
else:
channel = insecure_channel(self._target, options=self._channel_options)
self.queue_message(
"vvv",
"ESTABLISH GRPC CONNECTION FOR USER: %s on PORT %s TO %s"
% (self.get_option("remote_user"), port, host),
)
self._channel = implementations.Channel(channel)
self.queue_message("vvvv", "grpc connection has completed successfully")
self._connected = True
def close(self):
"""
Close the active session to the device
:return: None
"""
if self._connected:
self.queue_message("vvvv", "closing gRPC connection to target host")
self._channel.close()
super(Connection, self).close()

View File

@@ -1,8 +1,10 @@
# (c) 2018 Red Hat Inc.
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = """
@@ -17,30 +19,14 @@ version_added: 1.0.0
extends_documentation_fragment:
- ansible.netcommon.connection_persistent
options:
import_modules:
type: boolean
description:
- Reduce CPU usage and network module execution time
by enabling direct execution. Instead of the module being packaged
and executed by the shell, it will be directly executed by the Ansible
control node using the same python interpreter as the Ansible process.
Note- Incompatible with C(asynchronous mode).
Note- Python 3 and Ansible 2.9.16 or greater required.
Note- With Ansible 2.9.x fully qualified modules names are required in tasks.
default: false
ini:
- section: ansible_network
key: import_modules
env:
- name: ANSIBLE_NETWORK_IMPORT_MODULES
vars:
- name: ansible_network_import_modules
host:
description:
- Specifies the remote device FQDN or IP address to establish the HTTP(S) connection
to.
default: inventory_hostname
type: string
vars:
- name: inventory_hostname
- name: ansible_host
port:
type: int
@@ -59,6 +45,7 @@ options:
description:
- Configures the device platform network operating system. This value is used
to load the correct httpapi plugin to communicate with the remote device
type: string
vars:
- name: ansible_network_os
remote_user:
@@ -67,6 +54,7 @@ options:
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.
type: string
ini:
- section: defaults
key: remote_user
@@ -78,6 +66,7 @@ options:
description:
- Configures the user password used to authenticate to the remote device when
needed for the device API.
type: string
vars:
- name: ansible_password
- name: ansible_httpapi_pass
@@ -92,6 +81,34 @@ options:
- When specified, I(password) is ignored.
vars:
- name: ansible_httpapi_session_key
ca_path:
description:
- Path to CA cert bundle to use.
type: path
version_added: 5.2.0
vars:
- name: ansible_httpapi_ca_path
client_cert:
description:
- PEM formatted certificate chain file to be used for SSL client
authentication. This file can also include the key as well, and if the key
is included, I(client_key) is not required
version_added: 5.2.0
vars:
- name: ansible_httpapi_client_cert
client_key:
description:
- PEM formatted file that contains the private key to be used for SSL client
authentication. If I(client_cert) contains both the certificate and key,
this option is not required.
version_added: 5.2.0
vars:
- name: ansible_httpapi_client_key
http_agent:
description: User-Agent to use in the request.
version_added: 5.2.0
vars:
- name: ansible_httpapi_http_agent
use_ssl:
type: boolean
description:
@@ -113,6 +130,19 @@ options:
default: true
vars:
- name: ansible_httpapi_use_proxy
ciphers:
description:
- SSL/TLS Ciphers to use for requests
- 'When a list is provided, all ciphers are joined in order with C(:)'
- See the L(OpenSSL Cipher List Format,https://www.openssl.org/docs/manmaster/man1/openssl-ciphers.html#CIPHER-LIST-FORMAT)
for more details.
- The available ciphers is dependent on the Python and OpenSSL/LibreSSL versions.
- This option will have no effect on ansible-core<2.14 but a warning will be emitted.
version_added: 5.0.0
type: list
elements: string
vars:
- name: ansible_httpapi_ciphers
become:
type: boolean
description:
@@ -136,6 +166,7 @@ options:
escalation. Typically the become_method value is set to C(enable) but could
be defined as other values.
default: sudo
type: string
ini:
- section: privilege_escalation
key: become_method
@@ -146,6 +177,7 @@ options:
platform_type:
description:
- Set type of platform.
type: string
env:
- name: ANSIBLE_PLATFORM_TYPE
vars:
@@ -161,11 +193,14 @@ 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 ensure_connect
from ansible.plugins.loader import httpapi_loader
from ansible.release import __version__ as ANSIBLE_CORE_VERSION
from ansible_collections.ansible.netcommon.plugins.plugin_utils.connection_base import (
NetworkConnectionBase,
)
from ansible_collections.ansible.netcommon.plugins.plugin_utils.version import Version
class Connection(NetworkConnectionBase):
@@ -175,9 +210,7 @@ class Connection(NetworkConnectionBase):
has_pipelining = True
def __init__(self, play_context, new_stdin, *args, **kwargs):
super(Connection, self).__init__(
play_context, new_stdin, *args, **kwargs
)
super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs)
self._auth = None
if self._network_os:
@@ -205,8 +238,7 @@ class Connection(NetworkConnectionBase):
)
else:
raise AnsibleConnectionFailure(
"unable to load API plugin for platform type %s"
% platform_type
"unable to load API plugin for platform type %s" % platform_type
)
else:
@@ -256,9 +288,7 @@ class Connection(NetworkConnectionBase):
if self.get_option("session_key"):
self._auth = self.get_option("session_key")
else:
self.httpapi.login(
self.get_option("remote_user"), self.get_option("password")
)
self.httpapi.login(self.get_option("remote_user"), self.get_option("password"))
def close(self):
"""
@@ -277,12 +307,29 @@ class Connection(NetworkConnectionBase):
Sends the command to the device over api
"""
url_kwargs = dict(
headers={},
use_proxy=self.get_option("use_proxy"),
timeout=self.get_option("persistent_command_timeout"),
validate_certs=self.get_option("validate_certs"),
use_proxy=self.get_option("use_proxy"),
headers={},
http_agent=self.get_option("http_agent"),
client_cert=self.get_option("client_cert"),
client_key=self.get_option("client_key"),
ca_path=self.get_option("ca_path"),
)
url_kwargs.update(kwargs)
ciphers = self.get_option("ciphers")
if ciphers:
if Version(ANSIBLE_CORE_VERSION) >= Version("2.14.0"):
# Only insert "ciphers" kwarg for ansible-core versions >= 2.14.0.
url_kwargs["ciphers"] = ciphers
else:
# Emit warning when "ansible_httpapi_ciphers" is set but not supported
self.queue_message(
"warning",
"'ansible_httpapi_ciphers' option is unavailable on ansible-core<2.14",
)
if self._auth:
# Avoid modifying passed-in headers
headers = dict(kwargs.get("headers", {}))
@@ -296,8 +343,7 @@ class Connection(NetworkConnectionBase):
try:
url = self._url + path
self._log_messages(
"send url '%s' with data '%s' and kwargs '%s'"
% (url, data, url_kwargs)
"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:
@@ -314,9 +360,7 @@ class Connection(NetworkConnectionBase):
response = is_handled
except URLError as exc:
raise AnsibleConnectionFailure(
"Could not connect to {0}: {1}".format(
self._url + path, exc.reason
)
"Could not connect to {0}: {1}".format(self._url + path, exc.reason)
)
response_buffer = BytesIO()

View File

@@ -1,14 +1,17 @@
# (c) 2020 Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = """
author:
- Ansible Networking Team (@ansible-network)
name: libssh
short_description: (Tech preview) Run tasks using libssh for ssh connection
short_description: Run tasks using libssh for ssh connection
description:
- Use the ansible-pylibssh python bindings to connect to targets
- The python bindings use libssh C library (https://www.libssh.org/) to connect to targets
@@ -18,8 +21,10 @@ DOCUMENTATION = """
remote_addr:
description:
- Address of the remote target
type: string
default: inventory_hostname
vars:
- name: inventory_hostname
- name: ansible_host
- name: ansible_ssh_host
- name: ansible_libssh_host
@@ -27,6 +32,7 @@ DOCUMENTATION = """
description:
- User to login/authenticate as
- Can be set from the CLI via the C(--user) or C(-u) options.
type: string
vars:
- name: ansible_user
- name: ansible_ssh_user
@@ -43,12 +49,22 @@ DOCUMENTATION = """
description:
- Secret used to either login the ssh server or as a passphrase for ssh keys that require it
- Can be set from the CLI via the C(--ask-pass) option.
type: string
vars:
- name: ansible_password
- name: ansible_ssh_pass
- name: ansible_ssh_password
- name: ansible_libssh_pass
- name: ansible_libssh_password
password_prompt:
description:
- Text to match when using keyboard-interactive authentication to determine if the prompt is
for the password.
- Requires ansible-pylibssh version >= 1.0.0
type: string
vars:
- name: ansible_libssh_password_prompt
version_added: 3.1.0
host_key_auto_add:
description: 'TODO: write it'
env: [{name: ANSIBLE_LIBSSH_HOST_KEY_AUTO_ADD}]
@@ -67,6 +83,7 @@ DOCUMENTATION = """
description:
- Proxy information for running the connection via a jumphost.
- Also this plugin will scan 'ssh_args', 'ssh_extra_args' and 'ssh_common_args' from the 'ssh' plugin settings for proxy information if set.
type: string
env:
- name: ANSIBLE_LIBSSH_PROXY_COMMAND
ini:
@@ -109,33 +126,95 @@ DOCUMENTATION = """
ini:
- section: defaults
key: use_persistent_connections
ssh_args:
version_added: 3.2.0
description:
- Arguments to pass to all ssh CLI tools.
- ProxyCommand is the only supported argument.
- This option is deprecated in favor of I(proxy_command) and will be removed
in a release after 2026-01-01.
type: string
ini:
- section: 'ssh_connection'
key: 'ssh_args'
env:
- name: ANSIBLE_SSH_ARGS
vars:
- name: ansible_ssh_args
cli:
- name: ssh_args
ssh_common_args:
version_added: 3.2.0
description:
- Common extra arguments for all ssh CLI tools.
- ProxyCommand is the only supported argument.
- This option is deprecated in favor of I(proxy_command) and will be removed
in a release after 2026-01-01.
type: string
ini:
- section: 'ssh_connection'
key: 'ssh_common_args'
env:
- name: ANSIBLE_SSH_COMMON_ARGS
vars:
- name: ansible_ssh_common_args
cli:
- name: ssh_common_args
ssh_extra_args:
version_added: 3.2.0
description:
- Extra arguments exclusive to the 'ssh' CLI tool.
- ProxyCommand is the only supported argument.
- This option is deprecated in favor of I(proxy_command) and will be removed
in a release after 2026-01-01.
type: string
vars:
- name: ansible_ssh_extra_args
env:
- name: ANSIBLE_SSH_EXTRA_ARGS
ini:
- key: ssh_extra_args
section: ssh_connection
cli:
- name: ssh_extra_args
config_file:
version_added: 5.1.0
description: Alternate SSH config file location
type: path
env:
- name: ANSIBLE_LIBSSH_CONFIG_FILE
ini:
- section: libssh_connection
key: config_file
vars:
- name: ansible_libssh_config_file
# TODO:
#timeout=self._play_context.timeout,
"""
import logging
import os
import socket
import re
import socket
import sys
from termios import tcflush, TCIFLUSH
from termios import TCIFLUSH, tcflush
from ansible.errors import (
AnsibleConnectionFailure,
AnsibleError,
AnsibleFileNotFound,
)
from ansible.errors import AnsibleConnectionFailure, AnsibleError, AnsibleFileNotFound
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.six.moves import input
from ansible.plugins.connection import ConnectionBase
from ansible.utils.display import Display
from ansible.module_utils._text import to_bytes, to_native, to_text
from ansible.module_utils.basic import missing_required_lib
import logging
from ansible_collections.ansible.netcommon.plugins.plugin_utils.version import Version
display = Display()
try:
from pylibsshext import __version__ as PYLIBSSH_VERSION
from pylibsshext.errors import LibsshSCPException, LibsshSessionException
from pylibsshext.session import Session
from pylibsshext.errors import LibsshSessionException, LibsshSCPException
HAS_PYLIBSSH = True
except ImportError:
@@ -167,17 +246,13 @@ class MyAddPolicy(object):
self.connection = connection
self._options = connection._options
def missing_host_key(
self, session, hostname, username, key_type, fingerprint, message
):
def missing_host_key(self, session, hostname, username, key_type, fingerprint, message):
if all(
(
self._options["host_key_checking"],
not self._options["host_key_auto_add"],
)
):
if (
self.connection.get_option("use_persistent_connections")
or self.connection.force_persistence
@@ -185,8 +260,7 @@ class MyAddPolicy(object):
# don't print the prompt string since the user cannot respond
# to the question anyway
raise AnsibleError(
AUTHENTICITY_MSG.rsplit("\n", 2)[0]
% (hostname, message, key_type, fingerprint)
AUTHENTICITY_MSG.rsplit("\n", 2)[0] % (hostname, message, key_type, fingerprint)
)
self.connection.connection_lock()
@@ -196,9 +270,7 @@ class MyAddPolicy(object):
# clear out any premature input on sys.stdin
tcflush(sys.stdin, TCIFLUSH)
inp = input(
AUTHENTICITY_MSG % (hostname, message, key_type, fingerprint)
)
inp = input(AUTHENTICITY_MSG % (hostname, message, key_type, fingerprint))
sys.stdin = old_stdin
self.connection.connection_unlock()
@@ -233,9 +305,7 @@ class Connection(ConnectionBase):
if cache_key in SSH_CONNECTION_CACHE:
self.ssh = SSH_CONNECTION_CACHE[cache_key]
else:
self.ssh = SSH_CONNECTION_CACHE[
cache_key
] = self._connect_uncached()
self.ssh = SSH_CONNECTION_CACHE[cache_key] = self._connect_uncached()
return self
def _set_log_channel(self, name):
@@ -245,12 +315,15 @@ class Connection(ConnectionBase):
proxy_command = None
# Parse ansible_ssh_common_args, specifically looking for ProxyCommand
ssh_args = [
getattr(self._play_context, "ssh_extra_args", "") or "",
getattr(self._play_context, "ssh_common_args", "") or "",
getattr(self._play_context, "ssh_args", "") or "",
self.get_option("ssh_extra_args") or "",
self.get_option("ssh_common_args") or "",
self.get_option("ssh_args") or "",
]
if ssh_args is not None:
if any(ssh_args):
display.warning(
"The ssh_*_args options are deprecated and will be removed in a release after 2026-01-01. Please use the proxy_command option instead."
)
args = self._split_ssh_args(" ".join(ssh_args))
for i, arg in enumerate(args):
if arg.lower() == "proxycommand":
@@ -284,18 +357,20 @@ class Connection(ConnectionBase):
if not HAS_PYLIBSSH:
raise AnsibleError(missing_required_lib("ansible-pylibssh"))
display.vvv(
"USING PYLIBSSH VERSION %s" % PYLIBSSH_VERSION,
host=self._play_context.remote_addr,
)
ssh_connect_kwargs = {}
remote_user = self.get_option("remote_user")
remote_addr = self.get_option("remote_addr")
port = self._play_context.port or 22
display.vvv(
"ESTABLISH LIBSSH CONNECTION FOR USER: %s on PORT %s TO %s"
% (
self._play_context.remote_user,
port,
self._play_context.remote_addr,
),
host=self._play_context.remote_addr,
% (remote_user, port, remote_addr),
host=remote_addr,
)
self.ssh = Session()
@@ -310,31 +385,35 @@ class Connection(ConnectionBase):
try:
private_key = None
if self._play_context.private_key_file:
with open(
os.path.expanduser(self._play_context.private_key_file)
) as fp:
with open(os.path.expanduser(self._play_context.private_key_file)) as fp:
b_content = fp.read()
private_key = to_bytes(
b_content, errors="surrogate_or_strict"
)
private_key = to_bytes(b_content, errors="surrogate_or_strict")
if proxy_command:
ssh_connect_kwargs["proxycommand"] = proxy_command
self.ssh.set_missing_host_key_policy(
MyAddPolicy(self._new_stdin, self)
)
if self.get_option("config_file"):
ssh_connect_kwargs["config_file"] = self.get_option("config_file")
if self.get_option("password_prompt") and (Version(PYLIBSSH_VERSION) < "1.0.0"):
raise AnsibleError(
"Configuring password prompt is not supported in ansible-pylibssh version %s. "
"Please upgrade to ansible-pylibssh 1.0.0 or newer." % PYLIBSSH_VERSION
)
self.ssh.set_missing_host_key_policy(MyAddPolicy(self._new_stdin, self))
self.ssh.connect(
host=self._play_context.remote_addr.lower(),
user=self._play_context.remote_user,
host=remote_addr.lower(),
user=remote_user,
look_for_keys=self.get_option("look_for_keys"),
host_key_checking=self.get_option("host_key_checking"),
password=self._play_context.password,
password=self.get_option("password"),
password_prompt=self.get_option("password_prompt"),
private_key=private_key,
timeout=self._play_context.timeout,
port=port,
**ssh_connect_kwargs
**ssh_connect_kwargs,
)
except LibsshSessionException as e:
msg = "ssh connection failed: " + to_text(e)
@@ -348,9 +427,7 @@ class Connection(ConnectionBase):
def exec_command(self, cmd, in_data=None, sudoable=True):
"""run a command on the remote host"""
super(Connection, self).exec_command(
cmd, in_data=in_data, sudoable=sudoable
)
super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable)
if in_data:
raise AnsibleError(
@@ -405,9 +482,7 @@ class Connection(ConnectionBase):
playcontext=self._play_context,
)
)
raise AnsibleError(
"user %s does not exist" % n_become_user
)
raise AnsibleError("user %s does not exist" % n_become_user)
else:
break
# raise AnsibleError('ssh connection closed waiting for password prompt')
@@ -427,25 +502,17 @@ class Connection(ConnectionBase):
"become_pass", playcontext=self._play_context
)
self.chan.sendall(
to_bytes(become_pass, errors="surrogate_or_strict")
+ b"\n"
to_bytes(become_pass, errors="surrogate_or_strict") + b"\n"
)
else:
raise AnsibleError(
"A password is required but none was supplied"
)
raise AnsibleError("A password is required but none was supplied")
else:
no_prompt_out += become_output
no_prompt_err += become_output
else:
result = self.chan.exec_command(
to_text(cmd, errors="surrogate_or_strict")
)
result = self.chan.exec_command(to_text(cmd, errors="surrogate_or_strict"))
except socket.timeout:
raise AnsibleError(
"ssh timed out waiting for privilege escalation.\n"
+ become_output
)
raise AnsibleError("ssh timed out waiting for privilege escalation.\n" + become_output)
if result:
rc = result.returncode
@@ -466,9 +533,7 @@ class Connection(ConnectionBase):
)
if not os.path.exists(to_bytes(in_path, errors="surrogate_or_strict")):
raise AnsibleFileNotFound(
"file or module does not exist: %s" % in_path
)
raise AnsibleFileNotFound("file or module does not exist: %s" % in_path)
if proto == "sftp":
try:
@@ -488,14 +553,9 @@ class Connection(ConnectionBase):
try:
scp.put(in_path, out_path)
except LibsshSCPException as exc:
raise AnsibleError(
"Error transferring file to %s: %s"
% (out_path, to_text(exc))
)
raise AnsibleError("Error transferring file to %s: %s" % (out_path, to_text(exc)))
else:
raise AnsibleError(
"Don't know how to transfer file over protocol %s" % proto
)
raise AnsibleError("Don't know how to transfer file over protocol %s" % proto)
def _connect_sftp(self):
cache_key = "%s__%s__" % (
@@ -505,9 +565,7 @@ class Connection(ConnectionBase):
if cache_key in SFTP_CONNECTION_CACHE:
return SFTP_CONNECTION_CACHE[cache_key]
else:
result = SFTP_CONNECTION_CACHE[
cache_key
] = self._connect().ssh.sftp()
result = SFTP_CONNECTION_CACHE[cache_key] = self._connect().ssh.sftp()
return result
def fetch_file(self, in_path, out_path, proto="sftp"):
@@ -524,9 +582,7 @@ class Connection(ConnectionBase):
try:
self.sftp = self._connect_sftp()
except Exception as e:
raise AnsibleError(
"failed to open a SFTP connection (%s)" % to_native(e)
)
raise AnsibleError("failed to open a SFTP connection (%s)" % to_native(e))
try:
self.sftp.get(
@@ -540,14 +596,9 @@ class Connection(ConnectionBase):
try:
scp.get(out_path, in_path)
except LibsshSCPException as exc:
raise AnsibleError(
"Error transferring file from %s: %s"
% (out_path, to_text(exc))
)
raise AnsibleError("Error transferring file from %s: %s" % (out_path, to_text(exc)))
else:
raise AnsibleError(
"Don't know how to transfer file over protocol %s" % proto
)
raise AnsibleError("Don't know how to transfer file over protocol %s" % proto)
def reset(self):
self.close()

View File

@@ -1,185 +0,0 @@
# (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 = """
author:
- Ansible Networking Team (@ansible-network)
name: napalm
short_description: Provides persistent connection using NAPALM
description:
- This connection plugin provides connectivity to network devices using the NAPALM
network device abstraction library. This library requires certain features to be
enabled on network devices depending on the destination device operating system. The
connection plugin requires C(napalm) to be installed locally on the Ansible controller.
version_added: 1.0.0
deprecated:
alternative: network_cli
why: I am pretty sure no one has ever tried to use these modules
removed_at_date: '2022-06-01'
requirements:
- napalm
extends_documentation_fragment:
- ansible.netcommon.connection_persistent
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: 22
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 napalm device abstraction.
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
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
timeout:
type: int
description:
- Sets the connection time, in seconds, for communicating with the remote device. This
timeout is used as the default timeout value for commands when issuing a command
to the network CLI. If the command does not return in timeout seconds, an error
is generated.
default: 120
host_key_auto_add:
type: boolean
description:
- By default, Ansible will prompt the user before adding SSH keys to the known
hosts file. By enabling this option, unknown host keys will automatically be
added to the known hosts file.
- Be sure to fully understand the security implications of enabling this option
on production systems as it could create a security vulnerability.
default: false
ini:
- section: paramiko_connection
key: host_key_auto_add
env:
- name: ANSIBLE_HOST_KEY_AUTO_ADD
"""
from ansible.errors import AnsibleConnectionFailure, AnsibleError
from ansible.module_utils.basic import missing_required_lib
from ansible_collections.ansible.netcommon.plugins.plugin_utils.connection_base import (
NetworkConnectionBase,
)
try:
from napalm import get_network_driver
from napalm.base import ModuleImportError
HAS_NAPALM = True
except ImportError:
HAS_NAPALM = False
class Connection(NetworkConnectionBase):
"""Napalm connections"""
transport = "ansible.netcommon.napalm"
has_pipelining = False
def __init__(self, play_context, new_stdin, *args, **kwargs):
super(Connection, self).__init__(
play_context, new_stdin, *args, **kwargs
)
self.napalm = None
def _connect(self):
if not HAS_NAPALM:
raise AnsibleError(missing_required_lib("napalm"))
super(Connection, self)._connect()
if not self.connected:
if not self._network_os:
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
)
try:
driver = get_network_driver(self._network_os)
except ModuleImportError:
raise AnsibleConnectionFailure(
"Failed to import napalm driver for {0}".format(
self._network_os
)
)
host = self.get_option("host")
self.napalm = driver(
hostname=host,
username=self.get_option("remote_user"),
password=self.get_option("password"),
timeout=self.get_option("persistent_command_timeout"),
)
self.napalm.open()
self._sub_plugin = {"name": "napalm", "obj": self.napalm}
self.queue_message(
"vvvv",
"created napalm device for network_os %s" % self._network_os,
)
self._connected = True
def close(self):
if self.napalm:
self.napalm.close()
self.napalm = None
super(Connection, self).close()

View File

@@ -1,9 +1,11 @@
# (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)
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = """
@@ -23,30 +25,14 @@ requirements:
extends_documentation_fragment:
- ansible.netcommon.connection_persistent
options:
import_modules:
type: boolean
description:
- Reduce CPU usage and network module execution time
by enabling direct execution. Instead of the module being packaged
and executed by the shell, it will be directly executed by the Ansible
control node using the same python interpreter as the Ansible process.
Note- Incompatible with C(asynchronous mode).
Note- Python 3 and Ansible 2.9.16 or greater required.
Note- With Ansible 2.9.x fully qualified modules names are required in tasks.
default: false
ini:
- section: ansible_network
key: import_modules
env:
- name: ANSIBLE_NETWORK_IMPORT_MODULES
vars:
- name: ansible_network_import_modules
host:
description:
- Specifies the remote device FQDN or IP address to establish the SSH connection
to.
default: inventory_hostname
type: string
vars:
- name: inventory_hostname
- name: ansible_host
port:
type: int
@@ -67,6 +53,7 @@ options:
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).
type: string
vars:
- name: ansible_network_os
remote_user:
@@ -75,6 +62,7 @@ options:
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.
type: string
ini:
- section: defaults
key: remote_user
@@ -86,6 +74,7 @@ options:
description:
- Configures the user password used to authenticate to the remote device when
first establishing the SSH connection.
type: string
vars:
- name: ansible_password
- name: ansible_ssh_pass
@@ -95,6 +84,7 @@ options:
description:
- The private SSH key or certificate file used to authenticate to the remote device
when first establishing the SSH connection.
type: string
ini:
- section: defaults
key: private_key_file
@@ -135,6 +125,7 @@ options:
description:
- Proxy information for running the connection via a jumphost.
- This requires ncclient >= 0.6.10 to be installed on the controller.
type: string
env:
- name: ANSIBLE_NETCONF_PROXY_COMMAND
ini:
@@ -148,6 +139,7 @@ options:
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.
type: string
ini:
- section: netconf_connection
key: ssh_config
@@ -157,35 +149,31 @@ options:
- name: ansible_netconf_ssh_config
"""
import os
import logging
import json
import logging
import os
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_FALSE, BOOLEANS_TRUE
from ansible.module_utils.six import PY3
from ansible.module_utils.six.moves import cPickle
from ansible.module_utils.parsing.convert_bool import (
BOOLEANS_TRUE,
BOOLEANS_FALSE,
)
from ansible.playbook.play_context import PlayContext
from ansible.plugins.loader import netconf_loader
from ansible.plugins.connection import ensure_connect
from ansible.plugins.loader import netconf_loader
from ansible_collections.ansible.netcommon.plugins.plugin_utils.connection_base import (
NetworkConnectionBase,
)
from distutils.version import LooseVersion
from ansible_collections.ansible.netcommon.plugins.plugin_utils.version import Version
try:
from ncclient import __version__ as NCCLIENT_VERSION
from ncclient import manager
from ncclient.operations import RPCError
from ncclient.transport.errors import (
AuthenticationError,
SSHUnknownHostError,
)
from ncclient.transport.errors import AuthenticationError, SSHUnknownHostError
from ncclient.xml_ import to_ele, to_xml
from paramiko import ProxyCommand
@@ -208,9 +196,7 @@ class Connection(NetworkConnectionBase):
has_pipelining = False
def __init__(self, play_context, new_stdin, *args, **kwargs):
super(Connection, self).__init__(
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.
@@ -300,11 +286,10 @@ class Connection(NetworkConnectionBase):
sock = None
if proxy_command:
if LooseVersion(NCCLIENT_VERSION) < LooseVersion("0.6.10"):
if Version(NCCLIENT_VERSION) < "0.6.10":
raise AnsibleError(
"Configuring jumphost settings through ProxyCommand is unsupported in ncclient version %s. "
"Please upgrade to ncclient 0.6.10 or newer."
% NCCLIENT_VERSION
"Please upgrade to ncclient 0.6.10 or newer." % NCCLIENT_VERSION
)
replacers = {
@@ -336,9 +321,8 @@ class Connection(NetworkConnectionBase):
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")
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))
@@ -354,9 +338,7 @@ class Connection(NetworkConnectionBase):
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.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'
@@ -370,15 +352,12 @@ class Connection(NetworkConnectionBase):
)
self._network_os = "default"
try:
ncclient_device_handler = self.netconf.get_option(
"ncclient_device_handler"
)
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,
"identified ncclient device handler: %s." % ncclient_device_handler,
)
device_params = {"name": ncclient_device_handler}
@@ -419,9 +398,7 @@ class Connection(NetworkConnectionBase):
self._manager = manager.connect(**params)
self._manager._timeout = self.get_option(
"persistent_command_timeout"
)
self._manager._timeout = self.get_option("persistent_command_timeout")
except SSHUnknownHostError as exc:
raise AnsibleConnectionFailure(to_native(exc))
except AuthenticationError as exc:
@@ -434,17 +411,13 @@ class Connection(NetworkConnectionBase):
raise
except ImportError:
raise AnsibleError(
"connection=netconf is not supported on {0}".format(
self._network_os
)
"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.queue_message("log", "ncclient manager object created successfully")
self._connected = True

View File

@@ -1,9 +1,11 @@
# (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)
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = """
@@ -26,7 +28,9 @@ options:
- Specifies the remote device FQDN or IP address to establish the SSH connection
to.
default: inventory_hostname
type: string
vars:
- name: inventory_hostname
- name: ansible_host
port:
type: int
@@ -46,6 +50,7 @@ options:
- Configures the device platform network operating system. This value is used
to load the correct terminal and cliconf plugins to communicate with the remote
device.
type: string
vars:
- name: ansible_network_os
remote_user:
@@ -54,6 +59,7 @@ options:
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.
type: string
ini:
- section: defaults
key: remote_user
@@ -65,6 +71,7 @@ options:
description:
- Configures the user password used to authenticate to the remote device when
first establishing the SSH connection.
type: string
vars:
- name: ansible_password
- name: ansible_ssh_pass
@@ -73,6 +80,7 @@ options:
description:
- The private SSH key or certificate file used to authenticate to the remote device
when first establishing the SSH connection.
type: string
ini:
- section: defaults
key: private_key_file
@@ -109,12 +117,26 @@ options:
- name: ansible_network_become_errors
default: fail
choices: ["ignore", "warn", "fail"]
terminal_errors:
type: str
description:
- This option determines how failures while setting terminal parameters
are handled.
- When set to C(ignore), the errors are silently ignored.
When set to C(warn), a warning message is displayed.
The default option C(fail), triggers a failure and halts execution.
vars:
- name: ansible_network_terminal_errors
default: fail
choices: ["ignore", "warn", "fail"]
version_added: 3.1.0
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
type: string
ini:
- section: privilege_escalation
key: become_method
@@ -122,24 +144,6 @@ options:
- name: ANSIBLE_BECOME_METHOD
vars:
- name: ansible_become_method
import_modules:
type: boolean
description:
- Reduce CPU usage and network module execution time
by enabling direct execution. Instead of the module being packaged
and executed by the shell, it will be directly executed by the Ansible
control node using the same python interpreter as the Ansible process.
Note- Incompatible with C(asynchronous mode).
Note- Python 3 and Ansible 2.9.16 or greater required.
Note- With Ansible 2.9.x fully qualified modules names are required in tasks.
default: false
ini:
- section: ansible_network
key: import_modules
env:
- name: ANSIBLE_NETWORK_IMPORT_MODULES
vars:
- name: ansible_network_import_modules
host_key_auto_add:
type: boolean
description:
@@ -196,6 +200,7 @@ options:
- name: ansible_terminal_stderr_re
terminal_initial_prompt:
type: list
elements: string
description:
- A single regex pattern or a sequence of patterns to evaluate the expected prompt
at the time of initial login to the remote host.
@@ -203,6 +208,7 @@ options:
- name: ansible_terminal_initial_prompt
terminal_initial_answer:
type: list
elements: string
description:
- The answer to reply with if the C(terminal_initial_prompt) is matched. The value
can be a single answer or a list of answers for multiple terminal_initial_prompt.
@@ -249,10 +255,13 @@ options:
- name: ansible_network_cli_retries
ssh_type:
description:
- The type of the transport used by C(network_cli) connection plugin to connection to remote host.
Valid value is either I(paramiko) or I(libssh)
- In order to use I(libssh), the ansible-pylibssh package needs to be installed
default: paramiko
- The python package that will be used by the C(network_cli) connection plugin to create a SSH connection to remote host.
- I(libssh) will use the ansible-pylibssh package, which needs to be installed in order to work.
- I(paramiko) will instead use the paramiko package to manage the SSH connection.
- I(auto) will use ansible-pylibssh if that package is installed, otherwise will fallback to paramiko.
default: auto
choices: ["libssh", "paramiko", "auto"]
type: string
env:
- name: ANSIBLE_NETWORK_CLI_SSH_TYPE
ini:
@@ -289,37 +298,34 @@ options:
- name: ansible_network_single_user_mode
"""
from functools import wraps
import getpass
import json
import logging
import re
import os
import re
import signal
import socket
import time
import traceback
from functools import wraps
from io import BytesIO
from ansible.errors import AnsibleConnectionFailure, AnsibleError
from ansible.module_utils._text import to_bytes, to_text
from ansible.module_utils.basic import missing_required_lib
from ansible.module_utils.six import PY3
from ansible.module_utils.six.moves import cPickle
from ansible.module_utils._text import to_bytes, to_text
from ansible.playbook.play_context import PlayContext
from ansible.plugins.loader import (
cliconf_loader,
terminal_loader,
connection_loader,
)
from ansible.plugins.loader import cache_loader
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
to_list,
)
from ansible.plugins.loader import cache_loader, cliconf_loader, connection_loader, terminal_loader
from ansible_collections.ansible.netcommon.plugins.connection.libssh import HAS_PYLIBSSH
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list
from ansible_collections.ansible.netcommon.plugins.plugin_utils.connection_base import (
NetworkConnectionBase,
)
try:
from scp import SCPClient
@@ -350,9 +356,7 @@ class Connection(NetworkConnectionBase):
has_pipelining = True
def __init__(self, play_context, new_stdin, *args, **kwargs):
super(Connection, self).__init__(
play_context, new_stdin, *args, **kwargs
)
super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs)
self._ssh_shell = None
self._matched_prompt = None
@@ -379,32 +383,33 @@ class Connection(NetworkConnectionBase):
if self._network_os:
self._terminal = terminal_loader.get(self._network_os, self)
if not self._terminal:
raise AnsibleConnectionFailure(
"network os %s is not supported" % self._network_os
)
raise AnsibleConnectionFailure("network os %s is not supported" % self._network_os)
self.cliconf = cliconf_loader.get(self._network_os, self)
if self.cliconf:
self._sub_plugin = {
"type": "cliconf",
"name": self.cliconf._load_name,
"obj": self.cliconf,
}
if not self.cliconf:
self.queue_message(
"vvvv",
"loaded cliconf plugin %s from path %s for network_os %s"
% (
self.cliconf._load_name,
self.cliconf._original_path,
self._network_os,
),
)
else:
self.queue_message(
"vvvv",
"unable to load cliconf for network_os %s"
"unable to load cliconf for network_os %s. Falling back to default"
% self._network_os,
)
self.cliconf = cliconf_loader.get("ansible.netcommon.default", self)
if not self.cliconf:
raise AnsibleConnectionFailure("Couldn't load fallback cliconf plugin")
self._sub_plugin = {
"type": "cliconf",
"name": self.cliconf._load_name,
"obj": self.cliconf,
}
self.queue_message(
"vvvv",
"loaded cliconf plugin %s from path %s for network_os %s"
% (
self.cliconf._load_name,
self.cliconf._original_path,
self._network_os,
),
)
else:
raise AnsibleConnectionFailure(
"Unable to automatically determine host network os. Please "
@@ -412,27 +417,50 @@ class Connection(NetworkConnectionBase):
)
self.queue_message("log", "network_os is set to %s" % self._network_os)
@property
def ssh_type(self):
if self._ssh_type is None:
self._ssh_type = self.get_option("ssh_type")
self.queue_message("vvvv", "ssh type is set to %s" % self._ssh_type)
# Support autodetection of supported library
if self._ssh_type == "auto":
self.queue_message("vvvv", "autodetecting ssh_type")
if HAS_PYLIBSSH:
self._ssh_type = "libssh"
else:
self.queue_message(
"warning",
"ansible-pylibssh not installed, falling back to paramiko",
)
self._ssh_type = "paramiko"
self.queue_message("vvvv", "ssh type is now set to %s" % self._ssh_type)
if self._ssh_type not in ["paramiko", "libssh"]:
raise AnsibleConnectionFailure(
"Invalid value '%s' set for ssh_type option."
" Expected value is either 'libssh' or 'paramiko'" % self._ssh_type
)
return self._ssh_type
@property
def ssh_type_conn(self):
self._ssh_type = self.get_option("ssh_type")
if self._ssh_type_conn is None:
if self._ssh_type not in ["paramiko", "libssh"]:
if self.ssh_type == "libssh":
connection_plugin = "ansible.netcommon.libssh"
elif self.ssh_type == "paramiko":
# NOTE: This MUST be paramiko or things will break
connection_plugin = "paramiko"
else:
raise AnsibleConnectionFailure(
"Invalid value '%s' set for ssh_type option."
" Expected value is either 'libssh' or 'paramiko'"
% self._ssh_type
" Expected value is either 'libssh' or 'paramiko'" % self._ssh_type
)
# TODO: Remove this check if/when libssh connection plugin is moved to ansible-base
if self._ssh_type == "libssh":
self._ssh_type = "ansible.netcommon.libssh"
self._ssh_type_conn = connection_loader.get(
self._ssh_type, self._play_context, "/dev/null"
)
self.queue_message(
"vvvv", "ssh type is set to %s" % self.get_option("ssh_type")
connection_plugin, self._play_context, "/dev/null"
)
return self._ssh_type_conn
# To maintain backward compatibility
@@ -441,8 +469,17 @@ class Connection(NetworkConnectionBase):
return self.ssh_type_conn
def _get_log_channel(self):
name = "p=%s u=%s | " % (os.getpid(), getpass.getuser())
name += "%s [%s]" % (self._ssh_type, self._play_context.remote_addr)
user = ""
try:
user = getpass.getuser()
except KeyError:
self.queue_message(
"vv",
"Current user (uid=%s) does not seem to exist on this system, leaving user empty."
% os.getuid(),
)
name = "p=%s u=%s | " % (os.getpid(), user)
name += "%s [%s]" % (self.ssh_type, self._play_context.remote_addr)
return name
@ensure_connect
@@ -458,11 +495,7 @@ class Connection(NetworkConnectionBase):
if self._ssh_shell:
try:
cmd = json.loads(to_text(cmd, errors="surrogate_or_strict"))
kwargs = {
"command": to_bytes(
cmd["command"], errors="surrogate_or_strict"
)
}
kwargs = {"command": to_bytes(cmd["command"], errors="surrogate_or_strict")}
for key in (
"prompt",
"answer",
@@ -473,9 +506,7 @@ class Connection(NetworkConnectionBase):
if cmd.get(key) is True or cmd.get(key) is False:
kwargs[key] = cmd[key]
elif cmd.get(key) is not None:
kwargs[key] = to_bytes(
cmd[key], errors="surrogate_or_strict"
)
kwargs[key] = to_bytes(cmd[key], errors="surrogate_or_strict")
return self.send(**kwargs)
except ValueError:
cmd = to_bytes(cmd, errors="surrogate_or_strict")
@@ -493,9 +524,7 @@ class Connection(NetworkConnectionBase):
super(Connection, self).set_options(
task_keys=task_keys, var_options=var_options, direct=direct
)
self.ssh_type_conn.set_options(
task_keys=task_keys, var_options=var_options, direct=direct
)
self.ssh_type_conn.set_options(task_keys=task_keys, var_options=var_options, direct=direct)
# Retain old look_for_keys behaviour, but only if not set
if not any(
[
@@ -505,8 +534,7 @@ class Connection(NetworkConnectionBase):
]
):
look_for_keys = not bool(
self.get_option("password")
and not self.get_option("private_key_file")
self.get_option("password") and not self.get_option("private_key_file")
)
if not look_for_keys:
# This actually can't be overridden yet without changes in ansible-core
@@ -570,11 +598,9 @@ class Connection(NetworkConnectionBase):
Connects to the remote device and starts the terminal
"""
if self._play_context.verbosity > 3:
logging.getLogger(self._ssh_type).setLevel(logging.DEBUG)
logging.getLogger(self.ssh_type).setLevel(logging.DEBUG)
self.queue_message(
"vvvv", "invoked shell using ssh_type: %s" % self._ssh_type
)
self.queue_message("vvvv", "invoked shell using ssh_type: %s" % self.ssh_type)
self._single_user_mode = self.get_option("single_user_mode")
@@ -601,14 +627,16 @@ class Connection(NetworkConnectionBase):
except Exception as e:
pause = 2 ** (attempt + 1)
if attempt == retries or total_pause >= max_pause:
raise AnsibleConnectionFailure(
to_text(e, errors="surrogate_or_strict")
)
raise AnsibleConnectionFailure(to_text(e, errors="surrogate_or_strict"))
else:
msg = "network_cli_retry: attempt: %d, caught exception(%s), " "pausing for %d seconds" % (
attempt + 1,
to_text(e, errors="surrogate_or_strict"),
pause,
msg = (
"network_cli_retry: attempt: %d, caught exception(%s), "
"pausing for %d seconds"
% (
attempt + 1,
to_text(e, errors="surrogate_or_strict"),
pause,
)
)
self.queue_message("vv", msg)
@@ -620,7 +648,7 @@ class Connection(NetworkConnectionBase):
self._connected = True
self._ssh_shell = ssh.ssh.invoke_shell()
if self._ssh_type == "paramiko":
if self.ssh_type == "paramiko":
self._ssh_shell.settimeout(command_timeout)
self.queue_message(
@@ -629,20 +657,16 @@ class Connection(NetworkConnectionBase):
)
terminal_initial_prompt = (
self.get_option("terminal_initial_prompt")
or self._terminal.terminal_initial_prompt
self.get_option("terminal_initial_prompt") or self._terminal.terminal_initial_prompt
)
terminal_initial_answer = (
self.get_option("terminal_initial_answer")
or self._terminal.terminal_initial_answer
self.get_option("terminal_initial_answer") or self._terminal.terminal_initial_answer
)
newline = (
self.get_option("terminal_inital_prompt_newline")
or self._terminal.terminal_inital_prompt_newline
)
check_all = (
self.get_option("terminal_initial_prompt_checkall") or False
)
check_all = self.get_option("terminal_initial_prompt_checkall") or False
self.receive(
prompts=terminal_initial_prompt,
@@ -657,11 +681,9 @@ class Connection(NetworkConnectionBase):
self._on_become(become_pass=auth_pass)
self.queue_message("vvvv", "firing event: on_open_shell()")
self._terminal.on_open_shell()
self._on_open_shell()
self.queue_message(
"vvvv", "ssh connection has completed successfully"
)
self.queue_message("vvvv", "ssh connection has completed successfully")
return self
@@ -677,8 +699,25 @@ class Connection(NetworkConnectionBase):
if on_become_error == "ignore":
pass
elif on_become_error == "warn":
self.queue_message("warning", "on_become: privilege escalation failed")
else:
raise
def _on_open_shell(self):
"""
Wraps terminal.on_open_shell() to handle
terminal setting failures based on user preference
"""
on_terminal_error = self.get_option("terminal_errors")
try:
self._terminal.on_open_shell()
except AnsibleConnectionFailure:
if on_terminal_error == "ignore":
pass
elif on_terminal_error == "warn":
self.queue_message(
"warning", "on_become: privilege escalation failed"
"warning",
"on_open_shell: failed to set terminal parameters",
)
else:
raise
@@ -699,9 +738,7 @@ class Connection(NetworkConnectionBase):
self.ssh_type_conn.close()
self._ssh_type_conn = None
self.queue_message(
"debug", "ssh connection has been closed successfully"
)
self.queue_message("debug", "ssh connection has been closed successfully")
super(Connection, self).close()
def _read_post_command_prompt_match(self):
@@ -719,7 +756,6 @@ class Connection(NetworkConnectionBase):
check_all=False,
strip_prompt=True,
):
recv = BytesIO()
cache_socket_timeout = self.get_option("persistent_command_timeout")
self._ssh_shell.settimeout(cache_socket_timeout)
@@ -730,17 +766,11 @@ class Connection(NetworkConnectionBase):
while True:
if command_prompt_matched:
try:
signal.signal(
signal.SIGALRM, self._handle_buffer_read_timeout
)
signal.setitimer(
signal.ITIMER_REAL, self._buffer_read_timeout
)
signal.signal(signal.SIGALRM, self._handle_buffer_read_timeout)
signal.setitimer(signal.ITIMER_REAL, self._buffer_read_timeout)
data = self._ssh_shell.recv(256)
signal.alarm(0)
self._log_messages(
"response-%s: %s" % (self._window_count + 1, data)
)
self._log_messages("response-%s: %s" % (self._window_count + 1, data))
# if data is still received on channel it indicates the prompt string
# is wrongly matched in between response chunks, continue to read
# remaining response.
@@ -755,9 +785,7 @@ class Connection(NetworkConnectionBase):
return self._command_response
else:
data = self._ssh_shell.recv(256)
self._log_messages(
"response-%s: %s" % (self._window_count + 1, data)
)
self._log_messages("response-%s: %s" % (self._window_count + 1, data))
# when a channel stream is closed, received data will be empty
if not data:
break
@@ -771,9 +799,7 @@ class Connection(NetworkConnectionBase):
self._window_count += 1
if prompts and not handled:
handled = self._handle_prompt(
window, prompts, answer, newline, False, check_all
)
handled = self._handle_prompt(window, prompts, answer, newline, False, check_all)
self._matched_prompt_window = self._window_count
elif (
prompts
@@ -793,8 +819,7 @@ class Connection(NetworkConnectionBase):
check_all,
):
raise AnsibleConnectionFailure(
"For matched prompt '%s', answer is not valid"
% self._matched_cmd_prompt
"For matched prompt '%s', answer is not valid" % self._matched_cmd_prompt
)
if self._find_error(window):
@@ -807,9 +832,7 @@ class Connection(NetworkConnectionBase):
raise AnsibleConnectionFailure(errored_response)
self._last_response = recv.getvalue()
resp = self._strip(self._last_response)
self._command_response = self._sanitize(
resp, command, strip_prompt
)
self._command_response = self._sanitize(resp, command, strip_prompt)
if self._buffer_read_timeout == 0.0:
# reset socket timeout to global timeout
return self._command_response
@@ -832,7 +855,6 @@ class Connection(NetworkConnectionBase):
errored_response = None
while True:
if command_prompt_matched:
data = self._read_post_command_prompt_match()
if data:
@@ -856,9 +878,7 @@ class Connection(NetworkConnectionBase):
self._log_messages("response-%s: %s" % (self._window_count, data))
if prompts and not handled:
handled = self._handle_prompt(
resp, prompts, answer, newline, False, check_all
)
handled = self._handle_prompt(resp, prompts, answer, newline, False, check_all)
self._matched_prompt_window = self._window_count
elif (
prompts
@@ -878,8 +898,7 @@ class Connection(NetworkConnectionBase):
check_all,
):
raise AnsibleConnectionFailure(
"For matched prompt '%s', answer is not valid"
% self._matched_cmd_prompt
"For matched prompt '%s', answer is not valid" % self._matched_cmd_prompt
)
if self._find_error(resp):
@@ -891,9 +910,7 @@ class Connection(NetworkConnectionBase):
if errored_response:
raise AnsibleConnectionFailure(errored_response)
self._last_response = data
self._command_response += self._sanitize(
resp, command, strip_prompt
)
self._command_response = self._sanitize(resp, command, strip_prompt)
command_prompt_matched = True
def receive(
@@ -915,27 +932,17 @@ class Connection(NetworkConnectionBase):
self._window_count = 0
# set terminal regex values for command prompt and errors in response
self._terminal_stderr_re = self._get_terminal_std_re(
"terminal_stderr_re"
)
self._terminal_stdout_re = self._get_terminal_std_re(
"terminal_stdout_re"
)
self._terminal_stderr_re = self._get_terminal_std_re("terminal_stderr_re")
self._terminal_stdout_re = self._get_terminal_std_re("terminal_stdout_re")
self._command_timeout = self.get_option("persistent_command_timeout")
self._validate_timeout_value(
self._command_timeout, "persistent_command_timeout"
)
self._validate_timeout_value(self._command_timeout, "persistent_command_timeout")
self._buffer_read_timeout = self.get_option(
"persistent_buffer_read_timeout"
)
self._validate_timeout_value(
self._buffer_read_timeout, "persistent_buffer_read_timeout"
)
self._buffer_read_timeout = self.get_option("persistent_buffer_read_timeout")
self._validate_timeout_value(self._buffer_read_timeout, "persistent_buffer_read_timeout")
self._log_messages("command: %s" % command)
if self._ssh_type == "libssh":
if self.ssh_type == "libssh":
response = self.receive_libssh(
command,
prompts,
@@ -945,7 +952,7 @@ class Connection(NetworkConnectionBase):
check_all,
strip_prompt,
)
else:
elif self.ssh_type == "paramiko":
response = self.receive_paramiko(
command,
prompts,
@@ -977,9 +984,7 @@ class Connection(NetworkConnectionBase):
if (not prompt) and (self._single_user_mode):
out = self.get_cache().lookup(command)
if out:
self.queue_message(
"vvvv", "cache hit for command: %s" % command
)
self.queue_message("vvvv", "cache hit for command: %s" % command)
return out
if check_all:
@@ -1012,15 +1017,11 @@ class Connection(NetworkConnectionBase):
if self._needs_cache_invalidation(command):
# invalidate the existing cache
if self.get_cache().keys():
self.queue_message(
"vvvv", "invalidating existing cache"
)
self.queue_message("vvvv", "invalidating existing cache")
self.get_cache().invalidate()
else:
# populate cache
self.queue_message(
"vvvv", "populating cache for command: %s" % command
)
self.queue_message("vvvv", "populating cache for command: %s" % command)
self.get_cache().populate(command, response)
return response
@@ -1094,22 +1095,16 @@ class Connection(NetworkConnectionBase):
match = regex.search(resp)
if match:
self._matched_cmd_prompt = match.group()
self._log_messages(
"matched command prompt: %s" % self._matched_cmd_prompt
)
self._log_messages("matched command prompt: %s" % self._matched_cmd_prompt)
# if prompt_retry_check is enabled to check if same prompt is
# repeated don't send answer again.
if not prompt_retry_check:
prompt_answer = to_bytes(
answer[index] if len(answer) > index else answer[0]
)
prompt_answer = to_bytes(answer[index] if len(answer) > index else answer[0])
if newline:
prompt_answer += b"\r"
self._ssh_shell.sendall(prompt_answer)
self._log_messages(
"matched command prompt answer: %s" % prompt_answer
)
self._log_messages("matched command prompt answer: %s" % prompt_answer)
if check_all and prompts and not single_prompt:
prompts.pop(0)
answer.pop(0)
@@ -1198,7 +1193,7 @@ class Connection(NetworkConnectionBase):
"'pattern' is a required key for option '%s',"
" received option value is %s" % (option, item)
)
pattern = br"%s" % to_bytes(item["pattern"])
pattern = rb"%s" % to_bytes(item["pattern"])
flag = item.get("flags", 0)
if flag:
flag = getattr(re, flag.split(".")[1])
@@ -1209,9 +1204,7 @@ class Connection(NetworkConnectionBase):
return terminal_std_re
def copy_file(
self, source=None, destination=None, proto="scp", timeout=30
):
def copy_file(self, source=None, destination=None, proto="scp", timeout=30):
"""Copies file over scp/sftp to remote device
:param source: Source file path
@@ -1222,30 +1215,22 @@ class Connection(NetworkConnectionBase):
remote host before triggering timeout exception
:return: None
"""
ssh_type = self.get_option("ssh_type")
ssh = self.ssh_type_conn._connect_uncached()
if ssh_type == "libssh":
if self.ssh_type == "libssh":
self.ssh_type_conn.put_file(source, destination, proto=proto)
elif ssh_type == "paramiko":
elif self.ssh_type == "paramiko":
if proto == "scp":
if not HAS_SCP:
raise AnsibleError(missing_required_lib("scp"))
with SCPClient(
ssh.get_transport(), socket_timeout=timeout
) as scp:
with SCPClient(ssh.get_transport(), socket_timeout=timeout) as scp:
scp.put(source, destination)
elif proto == "sftp":
with ssh.open_sftp() as sftp:
sftp.put(source, destination)
else:
raise AnsibleError(
"Do not know how to do transfer file over protocol %s"
% proto
)
raise AnsibleError("Do not know how to do transfer file over protocol %s" % proto)
else:
raise AnsibleError(
"Do not know how to do SCP with ssh_type %s" % ssh_type
)
raise AnsibleError("Do not know how to do SCP with ssh_type %s" % self.ssh_type)
def get_file(self, source=None, destination=None, proto="scp", timeout=30):
"""Fetch file over scp/sftp from remote device
@@ -1258,18 +1243,15 @@ class Connection(NetworkConnectionBase):
:return: None
"""
"""Fetch file over scp/sftp from remote device"""
ssh_type = self.get_option("ssh_type")
ssh = self.ssh_type_conn._connect_uncached()
if ssh_type == "libssh":
if self.ssh_type == "libssh":
self.ssh_type_conn.fetch_file(source, destination, proto=proto)
elif ssh_type == "paramiko":
elif self.ssh_type == "paramiko":
if proto == "scp":
if not HAS_SCP:
raise AnsibleError(missing_required_lib("scp"))
try:
with SCPClient(
ssh.get_transport(), socket_timeout=timeout
) as scp:
with SCPClient(ssh.get_transport(), socket_timeout=timeout) as scp:
scp.get(source, destination)
except EOFError:
# This appears to be benign.
@@ -1278,14 +1260,9 @@ class Connection(NetworkConnectionBase):
with ssh.open_sftp() as sftp:
sftp.get(source, destination)
else:
raise AnsibleError(
"Do not know how to do transfer file over protocol %s"
% proto
)
raise AnsibleError("Do not know how to do transfer file over protocol %s" % proto)
else:
raise AnsibleError(
"Do not know how to do SCP with ssh_type %s" % ssh_type
)
raise AnsibleError("Do not know how to do SCP with ssh_type %s" % self.ssh_type)
def get_cache(self):
if not self._cache:
@@ -1303,9 +1280,7 @@ class Connection(NetworkConnectionBase):
:returns: A boolean indicating if the device is in config mode or not.
"""
cfg_mode = False
cur_prompt = to_text(
self.get_prompt(), errors="surrogate_then_replace"
).strip()
cur_prompt = to_text(self.get_prompt(), errors="surrogate_then_replace").strip()
cfg_prompt = getattr(self._terminal, "terminal_config_prompt", None)
if cfg_prompt and cfg_prompt.match(cur_prompt):
cfg_mode = True

View File

@@ -1,9 +1,11 @@
# 2017 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)
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = """
@@ -18,11 +20,12 @@ extends_documentation_fragment:
- ansible.netcommon.connection_persistent
"""
from ansible.executor.task_executor import start_connection
from ansible.plugins.connection import ConnectionBase
from ansible.module_utils._text import to_text
from ansible.module_utils.connection import Connection as SocketConnection
from ansible.plugins.connection import ConnectionBase
from ansible.utils.display import Display
display = Display()
@@ -33,9 +36,7 @@ class Connection(ConnectionBase):
has_pipelining = False
def __init__(self, play_context, new_stdin, *args, **kwargs):
super(Connection, self).__init__(
play_context, new_stdin, *args, **kwargs
)
super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs)
self._task_uuid = to_text(kwargs.get("task_uuid", ""))
def _connect(self):
@@ -71,14 +72,8 @@ class Connection(ConnectionBase):
"starting connection from persistent connection plugin",
host=self._play_context.remote_addr,
)
variables = {
"ansible_command_timeout": self.get_option(
"persistent_command_timeout"
)
}
socket_path = start_connection(
self._play_context, variables, self._task_uuid
)
variables = {"ansible_command_timeout": self.get_option("persistent_command_timeout")}
socket_path = start_connection(self._play_context, variables, self._task_uuid)
display.vvvv(
"local domain socket path is %s" % socket_path,
host=self._play_context.remote_addr,