279 lines
9.5 KiB
Python
279 lines
9.5 KiB
Python
# (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()
|