diff --git a/README.md b/README.md index 27980e4..1be9d81 100644 --- a/README.md +++ b/README.md @@ -4,16 +4,51 @@ This library wraps gNMI functionality to ease usage with Cisco implementations in Python programs. Derived from [openconfig/gnmi](https://github.com/openconfig/gnmi/tree/master/proto). +- [cisco-gnmi-python](#cisco-gnmi-python) + - [Usage](#usage) + - [cisco-gnmi CLI](#cisco-gnmi-cli) + - [ClientBuilder](#clientbuilder) + - [Initialization Examples](#initialization-examples) + - [Client](#client) + - [NXClient](#nxclient) + - [XEClient](#xeclient) + - [XRClient](#xrclient) + - [gNMI](#gnmi) + - [Development](#development) + - [Get Source](#get-source) + - [Code Hygiene](#code-hygiene) + - [Recompile Protobufs](#recompile-protobufs) + - [CLI Usage](#cli-usage) + - [Capabilities](#capabilities) + - [Usage](#usage-1) + - [Output](#output) + - [Get](#get) + - [Usage](#usage-2) + - [Output](#output-1) + - [Set](#set) + - [Usage](#usage-3) + - [Output](#output-2) + - [Subscribe](#subscribe) + - [Usage](#usage-4) + - [Output](#output-3) + - [Licensing](#licensing) + - [Issues](#issues) + - [Related Projects](#related-projects) + ## Usage ```bash pip install cisco-gnmi python -c "import cisco_gnmi; print(cisco_gnmi)" +cisco-gnmi --help ``` -This library covers the gNMI defined `capabilities`, `get`, `set`, and `subscribe` RPCs, and helper clients provide OS-specific recommendations. As commonalities and differences are identified this library will be refactored as necessary. +This library covers the gNMI defined `Capabilities`, `Get`, `Set`, and `Subscribe` RPCs, and helper clients provide OS-specific recommendations. A CLI (`cisco-gnmi`) is also available upon installation. As commonalities and differences are identified between OS functionality this library will be refactored as necessary. It is *highly* recommended that users of the library learn [Google Protocol Buffers](https://developers.google.com/protocol-buffers/) syntax to significantly ease usage. Understanding how to read Protocol Buffers, and reference [`gnmi.proto`](https://github.com/openconfig/gnmi/blob/master/proto/gnmi/gnmi.proto), will be immensely useful for utilizing gNMI and any other gRPC interface. +### cisco-gnmi CLI +Since `v1.0.5` a gNMI CLI is available as `cisco-gnmi` when this module is installed. `Capabilities`, `Get`, rudimentary `Set`, and `Subscribe` are supported. The CLI may be useful for simply interacting with a Cisco gNMI service, and also serves as a reference for how to use this `cisco_gnmi` library. CLI usage is documented at the bottom of this README in [CLI Usage](#cli-usage). + ### ClientBuilder Since `v1.0.0` a builder pattern is available with `ClientBuilder`. `ClientBuilder` provides several `set_*` methods which define the intended `Client` connectivity and a `construct` method to construct and return the desired `Client`. There are several major methods involved here: @@ -181,6 +216,352 @@ If a new `gnmi.proto` definition is released, use `update_protos.sh` to recompil ./update_protos.sh ``` +## CLI Usage +The below details the current `cisco-gnmi` usage options. Please note that `Set` operations may be destructive to operations and should be tested in lab conditions. + +``` +cisco-gnmi --help +usage: +cisco-gnmi [] + +Supported RPCs: +capabilities +subscribe +get +set + +cisco-gnmi capabilities 127.0.0.1:57500 +cisco-gnmi get 127.0.0.1:57500 +cisco-gnmi set 127.0.0.1:57500 -delete_xpath Cisco-IOS-XR-shellutil-cfg:host-names/host-name +cisco-gnmi subscribe 127.0.0.1:57500 -debug -auto_ssl_target_override -dump_file intfcounters.proto.txt + +See --help for RPC options. + + +gNMI CLI demonstrating library usage. + +positional arguments: + rpc gNMI RPC to perform against network element. + +optional arguments: + -h, --help show this help message and exit +``` + +### Capabilities +This command will output the `CapabilitiesResponse` to `stdout`. +``` +cisco-gnmi capabilities 127.0.0.1:57500 -auto_ssl_target_override +``` + +#### Usage +``` +cisco-gnmi capabilities --help +usage: cisco-gnmi [-h] [-os {None,IOS XR,NX-OS,IOS XE}] + [-root_certificates ROOT_CERTIFICATES] + [-private_key PRIVATE_KEY] + [-certificate_chain CERTIFICATE_CHAIN] + [-ssl_target_override SSL_TARGET_OVERRIDE] + [-auto_ssl_target_override] [-debug] + netloc + +Performs Capabilities RPC against network element. + +positional arguments: + netloc : + +optional arguments: + -h, --help show this help message and exit + -os {None,IOS XR,NX-OS,IOS XE} + OS wrapper to utilize. Defaults to IOS XR. + -root_certificates ROOT_CERTIFICATES + Root certificates for secure connection. + -private_key PRIVATE_KEY + Private key for secure connection. + -certificate_chain CERTIFICATE_CHAIN + Certificate chain for secure connection. + -ssl_target_override SSL_TARGET_OVERRIDE + gRPC SSL target override option. + -auto_ssl_target_override + Use root_certificates first CN as + grpc.ssl_target_name_override. + -debug Print debug messages. +``` + +#### Output +``` +[cisco-gnmi-python] cisco-gnmi capabilities redacted:57500 -auto_ssl_target_override +Username: admin +Password: +WARNING:root:Overriding SSL option from certificate could increase MITM susceptibility! +INFO:root:supported_models { + name: "Cisco-IOS-XR-qos-ma-oper" + organization: "Cisco Systems, Inc." + version: "2019-04-05" +} +... +``` + +### Get +This command will output the `GetResponse` to `stdout`. `-xpath` may be specified multiple times to specify multiple `Path`s for the `GetRequest`. +``` +cisco-gnmi get 127.0.0.1:57500 -os "IOS XR" -xpath /interfaces/interface/state/counters -auto_ssl_target_override +``` + +#### Usage +``` +usage: cisco-gnmi [-h] [-xpath XPATH] + [-encoding [{JSON,BYTES,PROTO,ASCII,JSON_IETF}]] + [-data_type [{ALL,CONFIG,STATE,OPERATIONAL}]] [-dump_json] + [-os {None,IOS XR,NX-OS,IOS XE}] + [-root_certificates ROOT_CERTIFICATES] + [-private_key PRIVATE_KEY] + [-certificate_chain CERTIFICATE_CHAIN] + [-ssl_target_override SSL_TARGET_OVERRIDE] + [-auto_ssl_target_override] [-debug] + netloc + +Performs Get RPC against network element. + +positional arguments: + netloc : + +optional arguments: + -h, --help show this help message and exit + -xpath XPATH XPaths to Get. + -encoding [{JSON,BYTES,PROTO,ASCII,JSON_IETF}] + gNMI Encoding. + -data_type [{ALL,CONFIG,STATE,OPERATIONAL}] + gNMI GetRequest DataType + -dump_json Dump as JSON instead of textual protos. + -os {None,IOS XR,NX-OS,IOS XE} + OS wrapper to utilize. Defaults to IOS XR. + -root_certificates ROOT_CERTIFICATES + Root certificates for secure connection. + -private_key PRIVATE_KEY + Private key for secure connection. + -certificate_chain CERTIFICATE_CHAIN + Certificate chain for secure connection. + -ssl_target_override SSL_TARGET_OVERRIDE + gRPC SSL target override option. + -auto_ssl_target_override + Use root_certificates first CN as + grpc.ssl_target_name_override. + -debug Print debug messages. +``` + +#### Output +``` +[cisco-gnmi-python] cisco-gnmi get redacted:57500 -os "IOS XR" -xpath /interfaces/interface/state/counters -auto_ssl_target_override +Username: admin +Password: +WARNING:root:Overriding SSL option from certificate could increase MITM susceptibility! +INFO:root:notification { + timestamp: 1585607100869287743 + update { + path { + elem { + name: "interfaces" + } + elem { + name: "interface" + } + elem { + name: "state" + } + elem { + name: "counters" + } + } + val { + json_ietf_val: "{\"in-unicast-pkts\":\"0\",\"in-octets\":\"0\"... +``` + +### Set +Please note that `Set` operations may be destructive to operations and should be tested in lab conditions. Behavior is not fully validated. + +#### Usage +``` +usage: cisco-gnmi [-h] [-update_json_config UPDATE_JSON_CONFIG] + [-replace_json_config REPLACE_JSON_CONFIG] + [-delete_xpath DELETE_XPATH] [-no_ietf] [-dump_json] + [-os {None,IOS XR,NX-OS,IOS XE}] + [-root_certificates ROOT_CERTIFICATES] + [-private_key PRIVATE_KEY] + [-certificate_chain CERTIFICATE_CHAIN] + [-ssl_target_override SSL_TARGET_OVERRIDE] + [-auto_ssl_target_override] [-debug] + netloc + +Performs Set RPC against network element. + +positional arguments: + netloc : + +optional arguments: + -h, --help show this help message and exit + -update_json_config UPDATE_JSON_CONFIG + JSON-modeled config to apply as an update. + -replace_json_config REPLACE_JSON_CONFIG + JSON-modeled config to apply as a replace. + -delete_xpath DELETE_XPATH + XPaths to delete. + -no_ietf JSON is not IETF conformant. + -dump_json Dump as JSON instead of textual protos. + -os {None,IOS XR,NX-OS,IOS XE} + OS wrapper to utilize. Defaults to IOS XR. + -root_certificates ROOT_CERTIFICATES + Root certificates for secure connection. + -private_key PRIVATE_KEY + Private key for secure connection. + -certificate_chain CERTIFICATE_CHAIN + Certificate chain for secure connection. + -ssl_target_override SSL_TARGET_OVERRIDE + gRPC SSL target override option. + -auto_ssl_target_override + Use root_certificates first CN as + grpc.ssl_target_name_override. + -debug Print debug messages. +``` + +#### Output +Let's create a harmless loopback interface based from [`openconfig-interfaces.yang`](https://github.com/openconfig/public/blob/master/release/models/interfaces/openconfig-interfaces.yang). + +`config.json` +```json +{ + "openconfig-interfaces:interfaces": { + "interface": [ + { + "name": "Loopback9339" + } + ] + } +} +``` + +``` +[cisco-gnmi-python] cisco-gnmi set redacted:57500 -os "IOS XR" -auto_ssl_target_override -update_json_config config.json +Username: admin +Password: +WARNING:root:Overriding SSL option from certificate could increase MITM susceptibility! +INFO:root:response { + path { + origin: "openconfig-interfaces" + elem { + name: "interfaces" + } + } + message { + } + op: UPDATE +} +message { +} +timestamp: 1585715036783451369 +``` + +And on IOS XR...a loopback interface! +``` +... +interface Loopback9339 +! +... +``` + +### Subscribe +This command will output the `SubscribeResponse` to `stdout` or `-dump_file`. `-xpath` may be specified multiple times to specify multiple `Path`s for the `GetRequest`. Subscribe currently only supports a sampled stream. `ON_CHANGE` is possible but not implemented in the CLI, yet. :) +``` +cisco-gnmi subscribe 127.0.0.1:57500 -os "IOS XR" -xpath /interfaces/interface/state/counters -auto_ssl_target_override +``` + +#### Usage +``` +cisco-gnmi subscribe --help +usage: cisco-gnmi [-h] [-xpath XPATH] [-interval INTERVAL] [-dump_file DUMP_FILE] + [-dump_json] [-sync_stop] + [-encoding [{JSON,BYTES,PROTO,ASCII,JSON_IETF}]] + [-os {None,IOS XR,NX-OS,IOS XE}] + [-root_certificates ROOT_CERTIFICATES] + [-private_key PRIVATE_KEY] + [-certificate_chain CERTIFICATE_CHAIN] + [-ssl_target_override SSL_TARGET_OVERRIDE] + [-auto_ssl_target_override] [-debug] + netloc + +Performs Subscribe RPC against network element. + +positional arguments: + netloc : + +optional arguments: + -h, --help show this help message and exit + -xpath XPATH XPath to subscribe to. + -interval INTERVAL Sample interval in seconds for Subscription. Defaults + to 10. + -dump_file DUMP_FILE Filename to dump to. Defaults to stdout. + -dump_json Dump as JSON instead of textual protos. + -sync_stop Stop on sync_response. + -encoding [{JSON,BYTES,PROTO,ASCII,JSON_IETF}] + gNMI Encoding. + -os {None,IOS XR,NX-OS,IOS XE} + OS wrapper to utilize. Defaults to IOS XR. + -root_certificates ROOT_CERTIFICATES + Root certificates for secure connection. + -private_key PRIVATE_KEY + Private key for secure connection. + -certificate_chain CERTIFICATE_CHAIN + Certificate chain for secure connection. + -ssl_target_override SSL_TARGET_OVERRIDE + gRPC SSL target override option. + -auto_ssl_target_override + Use root_certificates first CN as + grpc.ssl_target_name_override. + -debug Print debug messages. +``` + +#### Output +``` +[cisco-gnmi-python] cisco-gnmi subscribe redacted:57500 -os "IOS XR" -xpath /interfaces/interface/state/counters -auto_ssl_target_override +Username: admin +Password: +WARNING:root:Overriding SSL option from certificate could increase MITM susceptibility! +INFO:root:Dumping responses to stdout as textual proto ... +INFO:root:Subscribing to: +/interfaces/interface/state/counters +INFO:root:update { + timestamp: 1585607768601000000 + prefix { + origin: "openconfig" + elem { + name: "interfaces" + } + elem { + name: "interface" + key { + key: "name" + value: "Null0" + } + } + elem { + name: "state" + } + elem { + name: "counters" + } + } + update { + path { + elem { + name: "in-octets" + } + } + val { + uint_val: 0 + } + } +... +``` + ## Licensing `cisco-gnmi-python` is licensed as [Apache License, Version 2.0](LICENSE). diff --git a/setup.py b/setup.py index 08867d5..31e3303 100644 --- a/setup.py +++ b/setup.py @@ -1,3 +1,27 @@ +#!/usr/bin/env python +"""Copyright 2020 Cisco Systems +All rights reserved. + +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. + +The contents of this file are licensed under the Apache License, Version 2.0 +(the "License"); you may not use this file except in compliance with the +License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +License for the specific language governing permissions and limitations under +the License. +""" + """Derived from Flask https://github.com/pallets/flask/blob/master/setup.py """ @@ -69,4 +93,5 @@ "coverage", ], }, + entry_points={"console_scripts": ["cisco-gnmi = cisco_gnmi.cli:main"]}, ) diff --git a/src/cisco_gnmi/__init__.py b/src/cisco_gnmi/__init__.py index 0005330..cb961fe 100644 --- a/src/cisco_gnmi/__init__.py +++ b/src/cisco_gnmi/__init__.py @@ -30,4 +30,4 @@ from .xe import XEClient from .builder import ClientBuilder -__version__ = "1.0.4" +__version__ = "1.0.5" diff --git a/src/cisco_gnmi/cli.py b/src/cisco_gnmi/cli.py new file mode 100644 index 0000000..f935b54 --- /dev/null +++ b/src/cisco_gnmi/cli.py @@ -0,0 +1,323 @@ +#!/usr/bin/env python +"""Copyright 2020 Cisco Systems +All rights reserved. + +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. + +The contents of this file are licensed under the Apache License, Version 2.0 +(the "License"); you may not use this file except in compliance with the +License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +License for the specific language governing permissions and limitations under +the License. +""" + +""" +Wraps gNMI RPCs with a reasonably useful CLI for interacting with network elements. +Supports Capabilities, Subscribe, Get, and Set. + +Command parsing sourced from this wonderful blog by Chase Seibert +https://chase-seibert.github.io/blog/2014/03/21/python-multilevel-argparse.html +""" +import json +import logging +import argparse +from getpass import getpass +from google.protobuf import json_format, text_format +from . import ClientBuilder, proto +from google.protobuf.internal import enum_type_wrapper +import sys + + +def main(): + # Using a map so we don't have function overlap e.g. set() + rpc_map = { + "capabilities": gnmi_capabilities, + "subscribe": gnmi_subscribe, + "get": gnmi_get, + "set": gnmi_set, + } + parser = argparse.ArgumentParser( + description="gNMI CLI demonstrating cisco_gnmi library usage.", + usage=""" +cisco-gnmi [] + +Supported RPCs: +{supported_rpcs} + +cisco-gnmi capabilities 127.0.0.1:57500 +cisco-gnmi get 127.0.0.1:57500 -xpath /interfaces/interface/state/counters +cisco-gnmi set 127.0.0.1:57500 -update_json_config newconfig.json +cisco-gnmi subscribe 127.0.0.1:57500 -xpath /interfaces/interface/state/counters -dump_file intfcounters.proto.txt + +See --help for RPC options. + """.format( + supported_rpcs="\n".join(list(rpc_map.keys())) + ), + ) + parser.add_argument("rpc", help="gNMI RPC to perform against network element.") + args = parser.parse_args(sys.argv[1:2]) + if args.rpc not in rpc_map.keys(): + logging.error( + "%s not in supported RPCs: %s!", args.rpc, ", ".join(rpc_map.keys()) + ) + parser.print_help() + exit(1) + try: + rpc_map[args.rpc]() + except Exception: + logging.exception("Error during usage!") + exit(1) + + +def gnmi_capabilities(): + parser = argparse.ArgumentParser( + description="Performs Capabilities RPC against network element." + ) + args = __common_args_handler(parser) + client = __gen_client(args) + capability_response = client.capabilities() + logging.info(__format_message(capability_response)) + + +def gnmi_subscribe(): + """Performs a sampled Subscribe against network element. + TODO: ON_CHANGE + """ + parser = argparse.ArgumentParser( + description="Performs Subscribe RPC against network element." + ) + parser.add_argument( + "-xpath", help="XPath to subscribe to.", type=str, action="append" + ) + parser.add_argument( + "-interval", + help="Sample interval in seconds for Subscription. Defaults to 10.", + type=int, + default=10, + ) + parser.add_argument( + "-dump_file", + help="Filename to dump to. Defaults to stdout.", + type=str, + default="stdout", + ) + parser.add_argument( + "-dump_json", + help="Dump as JSON instead of textual protos.", + action="store_true", + ) + parser.add_argument( + "-sync_stop", help="Stop on sync_response.", action="store_true" + ) + parser.add_argument( + "-encoding", + help="gNMI Encoding.", + type=str, + nargs="?", + choices=proto.gnmi_pb2.Encoding.keys(), + ) + args = __common_args_handler(parser) + # Set default XPath outside of argparse due to default being persistent in argparse. + if not args.xpath: + args.xpath = ["/interfaces/interface/state/counters"] + client = __gen_client(args) + # Take care not to override options unnecessarily. + kwargs = {} + if args.encoding: + kwargs["encoding"] = args.encoding + if args.interval: + kwargs["sample_interval"] = args.interval * int(1e9) + try: + logging.info( + "Dumping responses to %s as %s ...", + args.dump_file, + "JSON" if args.dump_json else "textual proto", + ) + logging.info("Subscribing to:\n%s", "\n".join(args.xpath)) + for subscribe_response in client.subscribe_xpaths(args.xpath, **kwargs): + logging.debug("SubscribeResponse received.") + if subscribe_response.sync_response: + logging.debug("sync_response received.") + if args.sync_stop: + logging.warning("Stopping on sync_response.") + break + formatted_message = __format_message(subscribe_response) + if args.dump_file == "stdout": + logging.info(formatted_message) + else: + with open(args.dump_file, "a") as dump_fd: + dump_fd.write(formatted_message) + except KeyboardInterrupt: + logging.warning("Stopping on interrupt.") + except Exception: + logging.exception("Stopping due to exception!") + + +def gnmi_get(): + """Provides Get RPC usage. Assumes JSON or JSON_IETF style configurations. + """ + parser = argparse.ArgumentParser( + description="Performs Get RPC against network element." + ) + parser.add_argument("-xpath", help="XPaths to Get.", type=str, action="append") + parser.add_argument( + "-encoding", + help="gNMI Encoding.", + type=str, + nargs="?", + choices=proto.gnmi_pb2.Encoding.keys(), + ) + parser.add_argument( + "-data_type", + help="gNMI GetRequest DataType", + type=str, + nargs="?", + choices=enum_type_wrapper.EnumTypeWrapper( + proto.gnmi_pb2._GETREQUEST_DATATYPE + ).keys(), + ) + parser.add_argument( + "-dump_json", + help="Dump as JSON instead of textual protos.", + action="store_true", + ) + args = __common_args_handler(parser) + # Set default XPath outside of argparse due to default being persistent in argparse. + if not args.xpath: + args.xpath = ["/interfaces/interface/state/counters"] + client = __gen_client(args) + kwargs = {} + if args.encoding: + kwargs["encoding"] = args.encoding + if args.data_type: + kwargs["data_type"] = args.data_type + get_response = client.get_xpaths(args.xpath, **kwargs) + logging.info(__format_message(get_response)) + + +def gnmi_set(): + """Provides Set RPC usage. Assumes JSON or JSON_IETF style configurations. + Applies update/replace operations, and then delete operations. + TODO: This is the least well understood/implemented. Need to validate if there is an OOO for update/replace/delete. + """ + parser = argparse.ArgumentParser( + description="Performs Set RPC against network element." + ) + parser.add_argument( + "-update_json_config", help="JSON-modeled config to apply as an update." + ) + parser.add_argument( + "-replace_json_config", help="JSON-modeled config to apply as a replace." + ) + parser.add_argument( + "-delete_xpath", help="XPaths to delete.", type=str, action="append" + ) + parser.add_argument( + "-no_ietf", help="JSON is not IETF conformant.", action="store_true" + ) + parser.add_argument( + "-dump_json", + help="Dump as JSON instead of textual protos.", + action="store_true", + ) + args = __common_args_handler(parser) + if not any([args.update_json_config, args.replace_json_config, args.delete_xpath]): + raise Exception("Must specify update, replace, or delete parameters!") + + def load_json_file(filename): + config = None + with open(filename, "r") as config_fd: + config = json.load(config_fd) + return json.dumps(config) + + if args.update_json_config or args.replace_json_config: + kwargs = {} + if args.update_json_config: + kwargs["update_json_configs"] = load_json_file(args.update_json_config) + if args.replace_json_config: + kwargs["replace_json_configs"] = load_json_file(args.replace_json_config) + if args.no_ietf: + kwargs["ietf"] = False + client = __gen_client(args) + set_response = client.set_json(**kwargs) + logging.info(__format_message(set_response)) + if args.delete_xpath: + if getattr(client, "delete_xpaths", None) is not None: + delete_response = client.delete_xpaths(args.xpath) + logging.info(__format_message(delete_response)) + else: + raise Exception( + "Convenience delete_xpaths is not supported in the client library!" + ) + + +def __gen_client(args): + builder = ClientBuilder(args.netloc) + builder.set_os(args.os) + builder.set_call_authentication(args.username, args.password) + if not any([args.root_certificates, args.private_key, args.certificate_chain]): + builder.set_secure_from_target() + else: + builder.set_secure_from_file( + args.root_certificates, args.private_key, args.certificate_chain + ) + if args.ssl_target_override: + builder.set_ssl_target_override(args.ssl_target_override) + elif args.auto_ssl_target_override: + builder.set_ssl_target_override() + return builder.construct() + + +def __format_message(message, as_json=False): + formatted_message = None + if as_json: + formatted_message = json_format.MessageToJson(message, sort_keys=True) + else: + formatted_message = text_format.MessageToString(message) + return formatted_message + + +def __common_args_handler(parser): + """Ideally would be a decorator.""" + parser.add_argument("netloc", help=":", type=str) + parser.add_argument( + "-os", + help="OS wrapper to utilize. Defaults to IOS XR.", + type=str, + default="IOS XR", + choices=list(ClientBuilder.os_class_map.keys()), + ) + parser.add_argument( + "-root_certificates", help="Root certificates for secure connection." + ) + parser.add_argument("-private_key", help="Private key for secure connection.") + parser.add_argument( + "-certificate_chain", help="Certificate chain for secure connection." + ) + parser.add_argument("-ssl_target_override", help="gRPC SSL target override option.") + parser.add_argument( + "-auto_ssl_target_override", + help="Use root_certificates first CN as grpc.ssl_target_name_override.", + action="store_true", + ) + parser.add_argument("-debug", help="Print debug messages.", action="store_true") + args = parser.parse_args(sys.argv[2:]) + logging.basicConfig(level=logging.DEBUG if args.debug else logging.INFO) + args.username = input("Username: ") + args.password = getpass() + return args + + +if __name__ == "__main__": + main() diff --git a/src/cisco_gnmi/client.py b/src/cisco_gnmi/client.py index a46dcc0..7b7ca40 100755 --- a/src/cisco_gnmi/client.py +++ b/src/cisco_gnmi/client.py @@ -152,7 +152,7 @@ def get( "encoding", encoding, "Encoding", proto.gnmi_pb2.Encoding ) request = proto.gnmi_pb2.GetRequest() - if not isinstance(paths, (list, set)): + if not isinstance(paths, (list, set, map)): raise Exception("paths must be an iterable containing Path(s)!") request.path.extend(paths) request.type = data_type