From eaa7793a622ae2cf156399874e50625e2e917a4f Mon Sep 17 00:00:00 2001 From: SRBuilds Date: Fri, 12 Jul 2024 14:29:01 -0700 Subject: [PATCH] 24.7.1 released --- README.md | 2 +- docs/source/conf.py | 4 +- docs/source/examples.rst | 97 +++++++++++++- docs/source/features/23.10.rst | 15 ++- docs/source/features/24.03.rst | 26 ++++ docs/source/features/24.07.rst | 16 +++ docs/source/features/24.3.rst | 11 -- docs/source/getpass.rst | 38 ++++++ docs/source/index.rst | 10 +- docs/source/introduction.rst | 9 +- docs/source/modules.rst | 1 + examples/convert_example.py | 6 +- examples/filesystem_example.py | 1 + examples/get_all_router_and_vprn.py | 4 +- ...l_vprn_routes_with_nexthop_ipv4_address.py | 5 +- examples/get_inventory_remotely.py | 9 +- examples/get_list_keys_usage.py | 11 +- examples/intended_datastore_get.py | 124 ++++++++++++++++++ examples/make_connection.py | 7 +- examples/make_connection_extended.py | 9 +- .../make_connection_extended_with_argv.py | 6 +- examples/reset_card.py | 41 ++++++ examples/set_list.py | 2 +- examples/set_user_example.py | 8 +- examples/show_command_aliases.py | 35 ++--- examples/show_event_summary.py | 16 ++- examples/show_log_event_summary.py | 16 ++- examples/show_port_counters.py | 22 ++-- examples/show_route_table_communities.py | 4 +- examples/show_route_table_nexthop.py | 6 +- examples/show_router_bgp_asn.py | 66 ++++++---- examples/show_sdp_with_description.py | 4 +- examples/show_statistics_interval.py | 4 +- examples/show_system_commit.py | 11 +- examples/show_system_summary.py | 47 ++++--- examples/sleep.py | 69 ++++++++++ examples/to_pysros.py | 6 +- examples/user_input_example.py | 97 ++++++++++++++ examples/who.py | 38 +++++- pysros/__init__.py | 2 +- pysros/errors.py | 6 +- pysros/exceptions.py | 2 +- pysros/identifier.py | 2 +- pysros/management.py | 60 +++++++-- pysros/model.py | 2 +- pysros/model_builder.py | 2 +- pysros/model_path.py | 2 +- pysros/model_walker.py | 3 +- pysros/pprint.py | 2 +- pysros/request_data.py | 4 +- pysros/singleton.py | 2 +- pysros/tokenizer.py | 2 +- pysros/wrappers.py | 2 +- pysros/yang_type.py | 2 +- setup.py | 4 +- 55 files changed, 801 insertions(+), 201 deletions(-) create mode 100644 docs/source/features/24.03.rst create mode 100644 docs/source/features/24.07.rst delete mode 100644 docs/source/features/24.3.rst create mode 100644 docs/source/getpass.rst mode change 100644 => 100755 examples/convert_example.py create mode 100755 examples/intended_datastore_get.py create mode 100644 examples/reset_card.py create mode 100755 examples/sleep.py create mode 100644 examples/user_input_example.py diff --git a/README.md b/README.md index dda05e4..8f86221 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ In order to use the pySROS library the following pre-requisites must be met: ## License ## -Copyright 2021-2023 Nokia. +Copyright 2021-2024 Nokia. The license is located [here](LICENSE.md). diff --git a/docs/source/conf.py b/docs/source/conf.py index 20dcd7a..b9f2349 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -10,8 +10,8 @@ author = 'Nokia' # The full version, including alpha/beta/rc tags -version = '24.3.1' -release = '24.3.1' +version = '24.7.1' +release = '24.7.1' # -- General configuration --------------------------------------------------- diff --git a/docs/source/examples.rst b/docs/source/examples.rst index fe52611..f83ccbb 100644 --- a/docs/source/examples.rst +++ b/docs/source/examples.rst @@ -330,10 +330,10 @@ The following is an example of how to use these exceptions to handle specific er :caption: make_connection.py :name: make-connection-example :language: python - :emphasize-lines: 16-17, 26-64 + :emphasize-lines: 15-16, 25-63 -.. Reviewed by PLM 20210902 -.. Reviewed by TechComms 20210902 + +.. Reviewed by PLM 20240522 Obtaining data and formatted output @@ -345,9 +345,39 @@ various formats. .. Reviewed by PLM 20210902 .. Reviewed by TechComms 20210902 +Obtaining data from the intended datastore +****************************************** -Show SDP state and descriptions -******************************* +The ``intended`` datastore provides a read-only view into the expanded configuration. On SR OS +the ``intended`` datastore can be used to deliver a view of the expanded configuration as +created by configuration groups. A configuration group is defined using the +``/configure groups group `` command and is applied using the ``apply-groups`` command. + +The following example creates a configuration group template and an interface. The group is +then applied to the interface and the configuration of that interface is obtained from both +the ``running`` datastore and the ``intended`` datastore to demonstrate the difference: + +.. literalinclude:: ../../examples/intended_datastore_get.py + :caption: intended_datastore_get.py + :name: intended-datastore-get-example + :language: python + :emphasize-lines: 108-119 + +The output shows that in the ``running`` configuration you can see the ``apply-groups`` statement that +applies the template to the interface. You cannot see the ``ip-mtu`` as this configuration item exists +in the template. When displaying the configuration in the ``intended`` datastore the ``ip-mtu`` can be +seen applied to the interface and the ``apply-groups`` statement cannot be seen. The output looks +like this: + +.. code-block:: python + + Running: {'interface-name': Leaf('example-pysros'), 'apply-groups': LeafList(['test']), 'description': Leaf('Example interface')} + Intended: {'interface-name': Leaf('example-pysros'), 'description': Leaf('Example interface'), 'ip-mtu': Leaf(4444)} + +.. Reviewed by PLM 20240506 + +Creating SR OS style show commands from configuration and state data +******************************************************************** This examples creates a new show command that displays a list of the SDPs and their ID, description, administrative state, operational state, and far-end IP address. @@ -360,7 +390,7 @@ create SR OS style table output. :caption: show_sdp_with_description.py :name: show-sdp-with-description-example :language: python - :emphasize-lines: 12, 52-70, 84-94 + :emphasize-lines: 15, 52-72, 82-96 The example output for this application is shown here: @@ -476,7 +506,7 @@ instantaneous data available. :caption: filesystem_example.py :name: filesystem-example :language: python - :emphasize-lines: 18-60 + :emphasize-lines: 19-61 The example output of this application is shown below. @@ -502,6 +532,59 @@ The example output of this application is shown below. .. Reviewed by PLM 20220901 +.. _obtaining_user_inputs: + +Obtaining user input +#################### + +Some activities require the user to provide input into the Python application +in order to progress. In its simplest form, this may be confirmed with a yes/no +prompt before taking an action. In other use-cases, the input required might +be more extensive. + +User input can be obtained in Python in a number of ways. When a pySROS application +is executed remotely, the standard Python libraries are available. When executing +on an SR OS node, pySROS provides the :py:func:`input` and :py:func:`sys.stdin.readline` +procedures for obtaining input and the :py:mod:`getpass` module for obtaining password +information (without displaying it to the screen). + +.. note:: + + The :py:func:`input` and :py:func:`getpass.getpass` functions are preferred for + user input as they have better performance, particularly when using large inputs. + +Each of these methods interacts with the SR OS pager. If the output delivered by +the Python application exceeds the screen length, the pager is activated. The +pager obtains the key-presses until the pager session is concluded. At this point, +the consumption of the key-presses returns to the Python application. + +.. note:: + + Caution should be taken when chaining Python applications together with the pipe + modifier. Only one Python application in this chain should request user input. + +This example prompts the user to reset a linecard of their choosing and then confirms +that they wish to do this (using the :py:func:`input` function) before performing the +YANG-modeled operation using the :py:meth:`pysros.management.Connection.action` method. + +.. literalinclude:: ../../examples/reset_card.py + :caption: reset_card.py + :name: example-reset-card + :language: python3 + :emphasize-lines: 24, 26 + +This next example shows various options for obtaining user input. + +.. literalinclude:: ../../examples/user_input_example.py + :caption: user_input_example.py + :name: example-user-input + :language: python3 + + +.. Reviewed by PLM 20240612 +.. Reviewed by TechComms 20240612 + + .. _Converting Data Formats: Converting data formats diff --git a/docs/source/features/23.10.rst b/docs/source/features/23.10.rst index fe34b0f..8a5ad98 100644 --- a/docs/source/features/23.10.rst +++ b/docs/source/features/23.10.rst @@ -1,6 +1,20 @@ Release 23.10 ************* +23.10.5 +####### + +* No additional features + +.. Reviewed by PLM 20240520 + +23.10.4 +####### + +* No additional features + +.. Reviewed by PLM 20240326 + 23.10.3 ####### @@ -9,7 +23,6 @@ Release 23.10 .. Reviewed by PLM 20240124 .. Reviewed by TechComms 20240125 - 23.10.2 ####### diff --git a/docs/source/features/24.03.rst b/docs/source/features/24.03.rst new file mode 100644 index 0000000..f871211 --- /dev/null +++ b/docs/source/features/24.03.rst @@ -0,0 +1,26 @@ +Release 24.3 +************ + +24.3.3 +###### + +* No additional features + +.. Reviewed by PLM 20240509 +.. Reviewed by TechComms 20210510 + +24.3.2 +###### + +* No additional features + +.. Reviewed by PLM 20240403 +.. Reviewed by TechComms 20240403 + +24.3.1 +###### + +* No additional features + +.. Reviewed by PLM 20240219 +.. Reviewed by TechComms 20240227 diff --git a/docs/source/features/24.07.rst b/docs/source/features/24.07.rst new file mode 100644 index 0000000..cba3789 --- /dev/null +++ b/docs/source/features/24.07.rst @@ -0,0 +1,16 @@ +Release 24.7 +************ + +24.7.1 +###### + +* Support for the ``intended`` NMDA datastore in :py:class:`pysros.management.Connection` +* Support for :ref:`obtaining user inputs` + * Support for the :py:mod:`getpass` module for user inputs + * Support for the :py:func:`sys.stdin.readline` function for user inputs + * Support for the :py:func:`input` function for user inputs +* Provides :py:func:`pysros.management.Connection.session_id` which returns the current + connections SR OS / NETCONF session-id + +.. Reviewed by PLM 20240523 +.. Reviewed by TechComms 20240529 diff --git a/docs/source/features/24.3.rst b/docs/source/features/24.3.rst deleted file mode 100644 index 28d8d9b..0000000 --- a/docs/source/features/24.3.rst +++ /dev/null @@ -1,11 +0,0 @@ -Release 24.3 -************ - -24.3.1 -###### - -* No additional features - -.. Reviewed by PLM 20240219 -.. Reviewed by TechComms 20240227 - diff --git a/docs/source/getpass.rst b/docs/source/getpass.rst new file mode 100644 index 0000000..982a206 --- /dev/null +++ b/docs/source/getpass.rst @@ -0,0 +1,38 @@ +:mod:`getpass` --- Portable password input +========================================== + +.. module:: getpass + :synopsis: Portable reading of passwords + +.. admonition:: Differences to Python + :class: attention + + This module implements a subset of the upstream Python module. + For more information, refer to the original documentation: + `getpass `_. + +This module is used when executing on SR OS only. On a remote machine, the +native Python `getpass `_ +module is used. + +The :mod:`getpass` module provides functions for user inputs of passwords +without returning the characters to the screen. + +.. Reviewed by PLM 20240523 +.. Reviewed by TechComms 20240529 + +Functions +--------- + +.. function:: getpass(prompt='Password: ') + + Prompt the user for a password without echoing. The user is prompted using + the string *prompt*, which defaults to ``'Password: '``. + + :param prompt: Optional string prompt to provide the user guidance. + :type prompt: str, optional + :return: The inputted value + :rtype: str + +.. Reviewed by PLM 20240612 +.. Reviewed by TechComms 20240612 \ No newline at end of file diff --git a/docs/source/index.rst b/docs/source/index.rst index 48aade3..44992b2 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -17,11 +17,13 @@ documentation will be updated accordingly. .. list-table:: :header-rows: 0 - * - pySROS release: 24.3.1 - * - Document Number: 3HE 20087 AAAA TQZZA + * - pySROS release: 24.7.1 + * - Document Number: 3HE 20087 AAAD TQZZA + +.. Reviewed by PLM 20240506 +.. Reviewed by TechComms 20240529 + -.. Reviewed by PLM 20240219 -.. Reviewed by TechComms 20240227 .. toctree:: :maxdepth: 2 diff --git a/docs/source/introduction.rst b/docs/source/introduction.rst index 3768cd5..d007ee4 100644 --- a/docs/source/introduction.rst +++ b/docs/source/introduction.rst @@ -607,7 +607,7 @@ Obtaining data Use the :py:meth:`pysros.management.Datastore.get` method to obtain model-driven data from an SR OS device. This method takes a single JSON instance path (see the :ref:`modeled-paths` section) and returns a data structure. -The :py:meth:`pysros.management.Datastore.get` method can be performed against the ``running`` or the ``candidate`` datastore +The :py:meth:`pysros.management.Datastore.get` method can be performed against the ``running``, ``intended`` or ``candidate`` datastores when *configuration* data is required. When *state* data is required, it can only be performed against the ``running`` datastore. @@ -619,7 +619,7 @@ when *configuration* data is required. When *state* data is required, it can on Example: .. code-block:: python - :caption: Get example using :py:meth:`pysros.management.Datastore.get` + :caption: Get example using :py:meth:`pysros.management.Datastore.get` from the ``running`` datastore :name: get-example >>> from pysros.management import connect @@ -633,10 +633,7 @@ Example: 'add-paths': Container({'ipv4': Container({'receive': Leaf(True), 'send': Leaf('multipaths')})}), 'admin-state': Leaf('enable')})}, 'admin-state': Leaf('enable')}) - -.. Reviewed by PLM 20210902 -.. Reviewed by TechComms 20210902 - +.. Reviewed by PLM 20240506 Configuring SR OS routers ************************* diff --git a/docs/source/modules.rst b/docs/source/modules.rst index a1109f1..047999f 100755 --- a/docs/source/modules.rst +++ b/docs/source/modules.rst @@ -23,6 +23,7 @@ Python versions of the library are used. uio uos uos.path + getpass .. Reviewed by PLM 20220628 .. Reviewed by TechComms 20220706 diff --git a/examples/convert_example.py b/examples/convert_example.py old mode 100644 new mode 100755 index a6cef9a..a136bfd --- a/examples/convert_example.py +++ b/examples/convert_example.py @@ -12,12 +12,12 @@ # Import sys library import sys -# Import the connect method from the management pySROS sub-module -from pysros.management import connect - # Import the exceptions that are referenced so they can be caught on error. from pysros.exceptions import ModelProcessingError +# Import the connect method from the management pySROS sub-module +from pysros.management import connect + class Data: # pylint: disable=too-few-public-methods """Create an object containing the input data in the required format diff --git a/examples/filesystem_example.py b/examples/filesystem_example.py index 2ae5ce4..b8835e8 100644 --- a/examples/filesystem_example.py +++ b/examples/filesystem_example.py @@ -12,6 +12,7 @@ # pylint: disable=broad-except, eval-used, unspecified-encoding import sys + from pysros.management import connect, sros diff --git a/examples/get_all_router_and_vprn.py b/examples/get_all_router_and_vprn.py index ae17e82..ac547af 100755 --- a/examples/get_all_router_and_vprn.py +++ b/examples/get_all_router_and_vprn.py @@ -6,10 +6,10 @@ """Example to get all routers and VPRNs""" - import sys -from pysros.management import connect + from pysros.exceptions import ModelProcessingError +from pysros.management import connect from pysros.pprint import Table # pylint: disable=no-name-in-module credentials = { diff --git a/examples/get_all_vprn_routes_with_nexthop_ipv4_address.py b/examples/get_all_vprn_routes_with_nexthop_ipv4_address.py index 1f5e6c6..3d5b0ac 100755 --- a/examples/get_all_vprn_routes_with_nexthop_ipv4_address.py +++ b/examples/get_all_vprn_routes_with_nexthop_ipv4_address.py @@ -6,10 +6,11 @@ """Example to get all VPRN routes with a given next-hop IP address""" -import sys import ipaddress -from pysros.management import connect +import sys + from pysros.exceptions import ModelProcessingError +from pysros.management import connect from pysros.pprint import Table # pylint: disable=no-name-in-module credentials = { diff --git a/examples/get_inventory_remotely.py b/examples/get_inventory_remotely.py index 287302d..e8368cd 100755 --- a/examples/get_inventory_remotely.py +++ b/examples/get_inventory_remotely.py @@ -6,13 +6,14 @@ """Example to show obtaining a hardware inventory of multiple devices""" -# Import the required libraries -import sys import ipaddress import json -from pysros.management import connect, sros -from pysros.exceptions import ModelProcessingError +# Import the required libraries +import sys + +from pysros.exceptions import ModelProcessingError +from pysros.management import connect, sros # Global credentials dictionary for the purposes of this example. Global variables # discouraged in operational applications. diff --git a/examples/get_list_keys_usage.py b/examples/get_list_keys_usage.py index 4dc6e13..661f228 100755 --- a/examples/get_list_keys_usage.py +++ b/examples/get_list_keys_usage.py @@ -37,16 +37,13 @@ def get_connection(): # Else if the application is executed remotely else: # Import sys for returning specific exit codes - import sys # pylint: disable=import-outside-toplevel - # Import getpass to read the password import getpass # pylint: disable=import-outside-toplevel + import sys # pylint: disable=import-outside-toplevel # Import the exceptions so they can be caught on error # pylint: disable=import-outside-toplevel - from pysros.exceptions import ( - ModelProcessingError, - ) + from pysros.exceptions import ModelProcessingError # Make sure we have the right number of arguments, the host can # be an IP address or a hostname @@ -61,7 +58,9 @@ def get_connection(): sys.exit(2) # Get the password - password = getpass.getpass() + password = getpass.getpass( + prompt="Password (press Enter to use SSH key): " + ) # The try statement and except statements allow an operation # attempt with specific error conditions handled gracefully diff --git a/examples/intended_datastore_get.py b/examples/intended_datastore_get.py new file mode 100755 index 0000000..57238f8 --- /dev/null +++ b/examples/intended_datastore_get.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python3 + +### intended_datastore_get.py +# Copyright 2021-2024 Nokia +### + +""" +Tested on: SR OS 24.7.R1 + +Demonstrate the usage of the get method against the intended datastore to +show expanded configuration (such as applied configuration groups). + +Execution on SR OS + usage: pyexec bin/intended_datastore_get.py +Execution on remote machine + usage: python intended_datastore_get.py +Execution on remote machine if intended_datastore_get.py is executable + usage: ./intended_datastore_get.py + +In order to use the intended datastore from a remote machine, NMDA must be +enabled on the SR OS node using the following MD-CLI command: + +/configure system management-interface yang-modules nmda nmda-support true +""" + +# Import the connect method from the pySROS libraries +from pysros.management import connect, sros + + +def setup_example_config(connection_object): + """Deploys an example configuration-group template and an example + interface that uses the template. + + Args: + connection_object (pysros.management.Connection): pySROS Connection object + """ + config_group = { + "router": { + "Base": { + "router-name": "Base", + "interface": { + "": { + "interface-name": "", + "ip-mtu": 4444, + } + }, + } + } + } + interface = { + "description": "Example interface", + "apply-groups": ["test"], + } + connection_object.candidate.set( + '/configure/groups/group[name="test"]', config_group, method="replace" + ) + connection_object.candidate.set( + '/configure/router[router-name="Base"]/interface[interface-name="example-pysros"]', + interface, + method="replace", + ) + + +def cleanup_example_config(connection_object): + """Remove the example configuration deployed at the start of the programs + execution. + + Args: + connection_object (pysros.management.Connection): pySROS Connection object + """ + connection_object.candidate.delete( + '/configure/router[router-name="Base"]/interface[interface-name="example-pysros"]' + ) + connection_object.candidate.delete('/configure/groups/group[name="test"]') + + +def get_connection(): + """Obtain a Connection object from the node + + Raises: + SystemExit: Exits the application with the error information + + Returns: + pysros.management.Connection: pySROS Connection object + """ + try: + if sros(): + connection_object = connect() + else: + import getpass + + password = getpass.getpass(prompt="Enter password: ") + connection_object = connect( + host="hostname", + username="admin", + password=password, + hostkey_verify=False, + ) + except RuntimeError as runtime_error: + raise SystemExit(runtime_error) from runtime_error + return connection_object + + +def main(): + """Main procedure""" + connection_object = get_connection() + setup_example_config(connection_object) + print( + "Running:", + connection_object.running.get( + '/nokia-conf:configure/router[router-name="Base"]/interface[interface-name="example-pysros"]' + ), + ) + print( + "Intended:", + connection_object.intended.get( + '/nokia-conf:configure/router[router-name="Base"]/interface[interface-name="example-pysros"]' + ), + ) + cleanup_example_config(connection_object) + + +if __name__ == "__main__": + main() diff --git a/examples/make_connection.py b/examples/make_connection.py index 1453fb5..4f556fd 100755 --- a/examples/make_connection.py +++ b/examples/make_connection.py @@ -6,16 +6,15 @@ """Example to show how to make a connection and handle exceptions""" - # Import sys for returning specific exit codes import sys -# Import the connect method from the management pySROS sub-module -from pysros.management import connect - # Import the exceptions that are referenced so they can be caught on error. from pysros.exceptions import ModelProcessingError +# Import the connect method from the management pySROS sub-module +from pysros.management import connect + def get_connection(host=None, credentials=None): """Function definition to obtain a Connection object to a specific SR OS device diff --git a/examples/make_connection_extended.py b/examples/make_connection_extended.py index 0a60c3c..2cbece2 100755 --- a/examples/make_connection_extended.py +++ b/examples/make_connection_extended.py @@ -42,16 +42,15 @@ def get_connection(): # Else if the application is executed remotely else: # Import sys for returning specific exit codes - import sys - # Import getpass to read the password import getpass + import sys # Import the exceptions so they can be caught on error # fmt: off from pysros.exceptions import ModelProcessingError - # fmt: on + # fmt: on # Make sure we have the right number of arguments, the host can # be an IP address or a hostname if len(sys.argv) != 2: @@ -65,7 +64,9 @@ def get_connection(): sys.exit(2) # Get the password - password = getpass.getpass() + password = getpass.getpass( + prompt="Password (press Enter to use SSH key): " + ) # The try statement and except statements allow an operation # attempt with specific error conditions handled gracefully diff --git a/examples/make_connection_extended_with_argv.py b/examples/make_connection_extended_with_argv.py index b155996..5d12f3b 100755 --- a/examples/make_connection_extended_with_argv.py +++ b/examples/make_connection_extended_with_argv.py @@ -42,8 +42,8 @@ def get_remote_connection(my_username, my_host, my_password): # Import the exceptions so they can be caught on error # fmt: off from pysros.exceptions import ModelProcessingError - # fmt: on + # fmt: on # The try statement and except statements allow an operation # attempt with specific error conditions handled gracefully try: @@ -128,7 +128,9 @@ def get_connection_with_argv(): sys.exit(2) # Get the password - password = getpass.getpass() + password = getpass.getpass( + prompt="Password (press Enter to use SSH key): " + ) # Get a remote Connection object connection_object = get_remote_connection( diff --git a/examples/reset_card.py b/examples/reset_card.py new file mode 100644 index 0000000..dbc37e8 --- /dev/null +++ b/examples/reset_card.py @@ -0,0 +1,41 @@ +### reset_card.py +# Copyright 2024 Nokia +### + +""" +Tested on: SR OS 24.7.R1 + +Simple example to explain how to use user-input to +reset a card in a system. + +This example program is designed to be run locally on +an SR OS device. +""" + +from pysros.management import connect, sros + + +def main(): + """Main procedure to reset a specific card with a yes/no prompt. + + Raises: + SystemExit: User canceled transaction + """ + slot = input("Reset card in which slot? ") + print("About to reset the card in slot %s" % slot) + are_you_sure = input("Are you sure? ") + if are_you_sure.lower() == "y": + print("Resetting card in slot %s" % slot) + c = connect() + c.action( + "/nokia-oper-reset:reset/card[slot-number=%s]/reinitialize" % slot + ) + c.disconnect() + else: + print("Did not reset card in slot %s. Canceled by user." % slot) + raise SystemExit() + + +if __name__ == "__main__": + if sros: + main() diff --git a/examples/set_list.py b/examples/set_list.py index bce33dd..30ce0df 100755 --- a/examples/set_list.py +++ b/examples/set_list.py @@ -8,10 +8,10 @@ # Import the required libraries import sys -from pysros.management import connect # Import the exceptions so they can be caught on error. from pysros.exceptions import ModelProcessingError +from pysros.management import connect def get_connection(host=None, credentials=None): diff --git a/examples/set_user_example.py b/examples/set_user_example.py index e4ec05f..77d9d42 100755 --- a/examples/set_user_example.py +++ b/examples/set_user_example.py @@ -7,15 +7,15 @@ """Example to show how to set data""" +import random +import string + # Import the required libraries import sys -import string -import random -from pysros.management import connect # Import the exceptions so they can be caught on error. from pysros.exceptions import ModelProcessingError - +from pysros.management import connect # Global credentials dictionary for the purposes of this example. Global variables # discouraged in operational applications. diff --git a/examples/show_command_aliases.py b/examples/show_command_aliases.py index 75faf29..82c6292 100755 --- a/examples/show_command_aliases.py +++ b/examples/show_command_aliases.py @@ -7,7 +7,7 @@ # pylint: disable=import-error, import-outside-toplevel, line-too-long, too-many-branches, too-many-locals, too-many-statements """ -Tested on: SR OS 23.10.R2 +Tested on: SR OS 24.3.R1 Show command aliaseses. @@ -58,8 +58,8 @@ def get_remote_connection(my_username, my_host, my_password): # Import the exceptions so they can be caught on error # fmt: off from pysros.exceptions import ModelProcessingError - # fmt: on + # fmt: on # The try statement and except statements allow an operation # attempt with specific error conditions handled gracefully try: @@ -99,11 +99,12 @@ def get_remote_connection(my_username, my_host, my_password): def show_command_aliases_output(connection_object): """Main function for the show_command_aliases command""" - bright_red = "\u001b[31;1m" - bright_green = "\u001b[32;1m" - bright_blue = "\u001b[34;1m" - bright_cyan = "\u001b[36;1m" - reset_color = "\u001b[0m" + bright_blue = "\033[1;34m" + bright_cyan = "\033[1;36m" + bright_green = "\033[1;32m" + bright_red = "\033[1;31m" + bright_yellow = "\033[1;33m" + reset_color = "\033[0m" # Get environment configuration data try: @@ -155,7 +156,7 @@ def show_command_aliases_output(connection_object): ): print( "Alias name : " - + bright_cyan + + bright_blue + alias_name + reset_color + " (" @@ -168,11 +169,11 @@ def show_command_aliases_output(connection_object): else: print( "Alias name : " - + bright_cyan + + bright_blue + alias_name + reset_color + " (" - + bright_red + + bright_yellow + "disabled" + reset_color, end="", @@ -246,7 +247,7 @@ def show_command_aliases_output(connection_object): if mount_point == "global": print( "Availability : " - + bright_blue + + bright_cyan + alias_name + reset_color + " (global)" @@ -254,7 +255,7 @@ def show_command_aliases_output(connection_object): else: print( "Availability : " - + bright_blue + + bright_cyan + mount_point.replace("/", "") + " " + alias_name @@ -264,7 +265,7 @@ def show_command_aliases_output(connection_object): if mount_point == "global": print( " : " - + bright_blue + + bright_cyan + alias_name + reset_color + " (global)" @@ -283,7 +284,7 @@ def show_command_aliases_output(connection_object): print("-" * 80) print( "Total aliases : " - + bright_cyan + + bright_blue + str(num_admin_up_aliases + num_admin_down_aliases) + reset_color + " (" @@ -291,7 +292,7 @@ def show_command_aliases_output(connection_object): + str(num_admin_up_aliases) + reset_color + " enabled, " - + bright_red + + bright_yellow + str(num_admin_down_aliases) + reset_color + " disabled, " @@ -337,7 +338,9 @@ def get_connection_with_argv(): username_host = sys.argv[1].split("@") # Get the password - password = getpass.getpass() + password = getpass.getpass( + prompt="Password (press Enter to use SSH key): " + ) # Get a remote Connection object connection_object = get_remote_connection( diff --git a/examples/show_event_summary.py b/examples/show_event_summary.py index bd73416..7d52225 100755 --- a/examples/show_event_summary.py +++ b/examples/show_event_summary.py @@ -32,9 +32,9 @@ """ +from pysros.exceptions import InternalError, InvalidPathError, SrosMgmtError from pysros.management import connect, sros -from pysros.pprint import Table -from pysros.exceptions import InvalidPathError, SrosMgmtError, InternalError +from pysros.pprint import Table # pylint: disable=no-name-in-module def get_connection(): @@ -56,16 +56,16 @@ def get_connection(): # Else if the application is executed remotely else: # Import sys for returning specific exit codes - import sys # pylint: disable=import-outside-toplevel - # Import getpass to read the password import getpass # pylint: disable=import-outside-toplevel + import sys # pylint: disable=import-outside-toplevel # Import the exceptions so they can be caught on error # fmt: off - from pysros.exceptions import ModelProcessingError # pylint: disable=import-error disable=import-outside-toplevel - # fmt: on + from pysros.exceptions import \ + ModelProcessingError # pylint: disable=import-error disable=import-outside-toplevel + # fmt: on # Make sure we have the right number of arguments, the host can # be an IP address or a hostname if len(sys.argv) != 2: @@ -79,7 +79,9 @@ def get_connection(): sys.exit(2) # Get the password - password = getpass.getpass() + password = getpass.getpass( + prompt="Password (press Enter to use SSH key): " + ) # The try statement coupled with the except statements allow an # operation(s) to be attempted and specific error conditions handled diff --git a/examples/show_log_event_summary.py b/examples/show_log_event_summary.py index 888c047..405167c 100755 --- a/examples/show_log_event_summary.py +++ b/examples/show_log_event_summary.py @@ -7,7 +7,7 @@ # pylint: disable=line-too-long """ -Tested on: SR OS 23.10.R2 +Tested on: SR OS 24.3.R1 Obtain a summary of the log event history on the node. @@ -32,9 +32,9 @@ """ +from pysros.exceptions import InternalError, InvalidPathError, SrosMgmtError from pysros.management import connect, sros from pysros.pprint import Table -from pysros.exceptions import InvalidPathError, SrosMgmtError, InternalError def get_connection(): @@ -56,16 +56,16 @@ def get_connection(): # Else if the application is executed remotely else: # Import sys for returning specific exit codes - import sys # pylint: disable=import-outside-toplevel - # Import getpass to read the password import getpass # pylint: disable=import-outside-toplevel + import sys # pylint: disable=import-outside-toplevel # Import the exceptions so they can be caught on error # fmt: off - from pysros.exceptions import ModelProcessingError # pylint: disable=import-error disable=import-outside-toplevel - # fmt: on + from pysros.exceptions import \ + ModelProcessingError # pylint: disable=import-error disable=import-outside-toplevel + # fmt: on # Make sure we have the right number of arguments, the host can # be an IP address or a hostname if len(sys.argv) != 2: @@ -79,7 +79,9 @@ def get_connection(): sys.exit(2) # Get the password - password = getpass.getpass() + password = getpass.getpass( + prompt="Password (press Enter to use SSH key): " + ) # The try statement and except statements allow an operation # attempt with specific error conditions handled gracefully diff --git a/examples/show_port_counters.py b/examples/show_port_counters.py index f8b7daf..52f2c6d 100755 --- a/examples/show_port_counters.py +++ b/examples/show_port_counters.py @@ -7,7 +7,7 @@ # pylint: disable=import-error, import-outside-toplevel, line-too-long, too-many-branches, too-many-locals, too-many-statements """ -Tested on: SR OS 23.10.R2 +Tested on: SR OS 24.3.R1 Show port counters. @@ -36,12 +36,12 @@ /configure system { management-interface cli md-cli environment command-alias alias "counters" mount-point "/show port" } """ -# Import sys for parsing arguments and returning specific exit codes -import sys - # Import datetime to get and display the date and time import datetime +# Import sys for parsing arguments and returning specific exit codes +import sys + # Import the connect and sros methods from the management pySROS submodule from pysros.management import connect, sros @@ -302,8 +302,8 @@ def get_remote_connection(my_username, my_host, my_password): # Import the exceptions so they can be caught on error # fmt: off from pysros.exceptions import ModelProcessingError - # fmt: on + # fmt: on # The try statement and except statements allow an operation # attempt with specific error conditions handled gracefully try: @@ -372,10 +372,10 @@ def print_row_with_spacing(spacer, column, name, value): def show_port_counters_output(connection_object, language): """Main function for the show_port_counters command""" - bright_green = "\u001b[32;1m" - bright_red = "\u001b[31;1m" - bright_yellow = "\u001b[33;1m" - reset_color = "\u001b[0m" + bright_green = "\033[1;32m" + bright_red = "\033[1;31m" + bright_yellow = "\033[1;33m" + reset_color = "\033[0m" # Define local language oper-state strings and colors oper_state_str = { @@ -671,7 +671,9 @@ def get_connection_with_argv(): sys.exit(2) # Get the password - password = getpass.getpass() + password = getpass.getpass( + prompt="Password (press Enter to use SSH key): " + ) # Get a remote Connection object connection_object = get_remote_connection( diff --git a/examples/show_route_table_communities.py b/examples/show_route_table_communities.py index 74a673f..6899a37 100755 --- a/examples/show_route_table_communities.py +++ b/examples/show_route_table_communities.py @@ -8,11 +8,11 @@ import re import sys -from pysros.management import connect -from pysros.pprint import Table # pylint: disable=no-name-in-module # Import the exceptions so they can be caught on error. from pysros.exceptions import ModelProcessingError +from pysros.management import connect +from pysros.pprint import Table # pylint: disable=no-name-in-module credentials = { "host": "192.168.1.1", diff --git a/examples/show_route_table_nexthop.py b/examples/show_route_table_nexthop.py index 6259cf3..4c95002 100755 --- a/examples/show_route_table_nexthop.py +++ b/examples/show_route_table_nexthop.py @@ -6,13 +6,13 @@ """Example to show all bgp routes for a given next-hop""" -import sys import ipaddress -from pysros.management import connect -from pysros.pprint import Table # pylint: disable=no-name-in-module +import sys # Import the exceptions so they can be caught on error. from pysros.exceptions import ModelProcessingError +from pysros.management import connect +from pysros.pprint import Table # pylint: disable=no-name-in-module credentials = { "host": "192.168.1.1", diff --git a/examples/show_router_bgp_asn.py b/examples/show_router_bgp_asn.py index b5c2bb7..0506953 100755 --- a/examples/show_router_bgp_asn.py +++ b/examples/show_router_bgp_asn.py @@ -7,7 +7,7 @@ # pylint: disable=import-error, import-outside-toplevel, line-too-long, too-many-branches, too-many-locals, too-many-statements """ -Tested on: SR OS 23.10.R2 +Tested on: SR OS 24.3.R1 Show all BGP peers for an ASN. @@ -32,12 +32,12 @@ /configure system { management-interface cli md-cli environment command-alias alias "asn" mount-point "/show router bgp" } """ -# Import sys for parsing arguments and returning specific exit codes -import sys - # Import datetime to get and display the date and time import datetime +# Import sys for parsing arguments and returning specific exit codes +import sys + # Import the connect and sros methods from the management pySROS submodule from pysros.management import connect, sros @@ -61,8 +61,8 @@ def get_remote_connection(my_username, my_host, my_password): # Import the exceptions so they can be caught on error # fmt: off from pysros.exceptions import ModelProcessingError - # fmt: on + # fmt: on # The try statement and except statements allow an operation # attempt with specific error conditions handled gracefully try: @@ -102,11 +102,15 @@ def get_remote_connection(my_username, my_host, my_password): def show_router_bgp_asn_output(connection_object, asn): """Main function for the show_router_bgp_asn command""" - bright_cyan = "\u001b[36;1m" - bright_green = "\u001b[32;1m" - bright_red = "\u001b[31;1m" - bright_yellow = "\u001b[33;1m" - reset_color = "\u001b[0m" + bright_cyan = "\033[1;36m" + bright_black = "\033[1;30m" + bright_blue = "\033[1;34m" + bright_green = "\033[1;32m" + bright_magenta = "\033[1;35m" + bright_red = "\033[1;31m" + bright_yellow = "\033[1;33m" + cyan = "\033[0;36m" + reset_color = "\033[0m" bgp_stats = None oper_name = connection_object.running.get( @@ -133,11 +137,11 @@ def show_router_bgp_asn_output(connection_object, asn): if "peer-as" not in bgp_config[neighbor]: # If not, try to get the peer-as from the neighbor's group try: - bgp_config[neighbor][ - "peer-as" - ] = connection_object.running.get( - "/nokia-conf:configure/router[router-name=Base]/bgp/group[group-name=%s]/peer-as" - % bgp_config[neighbor]["group"] + bgp_config[neighbor]["peer-as"] = ( + connection_object.running.get( + "/nokia-conf:configure/router[router-name=Base]/bgp/group[group-name=%s]/peer-as" + % bgp_config[neighbor]["group"] + ) ) except LookupError as lookup_error: print( @@ -196,14 +200,18 @@ def show_router_bgp_asn_output(connection_object, asn): ) print( " State|" - + bright_cyan + + bright_blue + "Rcv" + reset_color + "/" + bright_green + "Act" + reset_color - + "/Sent (Addr Family)" + + "/" + + bright_magenta + + "Sent" + + reset_color + + " (Addr Family)" ) print("{0:<13} {1:<13}/{2:<13}".format("", "Messages Sent", "Out Queue")) print("-" * 80) @@ -215,13 +223,21 @@ def show_router_bgp_asn_output(connection_object, asn): for neighbor in sorted(bgp_config): if asn == 0 or int(asn) == bgp_config[neighbor]["peer-as"].data: # Print line 1 + print(bright_cyan, end="") print( - "{0:<45} {1} ({2})".format( + "{0:<45}{1} {2}{3}{4} {5}({6}{7}{8}){9}".format( neighbor, + reset_color, + cyan, bgp_stats[neighbor]["statistics"]["last-established-time"], + reset_color, + bright_black, + cyan, bgp_stats[neighbor]["statistics"][ "established-transitions" ], + bright_black, + reset_color, ) ) @@ -284,13 +300,13 @@ def show_router_bgp_asn_output(connection_object, asn): end="", ) print( - bright_cyan + bright_blue + str( bgp_stats[neighbor]["statistics"]["family-prefix"][ family.lower() ]["received"] ) - + reset_color + + bright_black + "/" + bright_green + str( @@ -298,13 +314,15 @@ def show_router_bgp_asn_output(connection_object, asn): family.lower() ]["received"] ) - + reset_color + + bright_black + "/" + + bright_magenta + str( bgp_stats[neighbor]["statistics"]["family-prefix"][ family.lower() ]["sent"] ) + + reset_color + " (" + str(family) + ")" @@ -363,7 +381,7 @@ def show_router_bgp_asn_output(connection_object, asn): print("-" * 80) print( "Total neighbors : " - + bright_cyan + + bright_blue + str(num_up_neighbors + num_down_neighbors + num_disabled_neighbors) + reset_color + " (" @@ -446,7 +464,9 @@ def get_connection_with_argv(): sys.exit(2) # Get the password - password = getpass.getpass() + password = getpass.getpass( + prompt="Password (press Enter to use SSH key): " + ) # Get a remote Connection object connection_object = get_remote_connection( diff --git a/examples/show_sdp_with_description.py b/examples/show_sdp_with_description.py index 85accde..cb3ea6c 100755 --- a/examples/show_sdp_with_description.py +++ b/examples/show_sdp_with_description.py @@ -8,11 +8,11 @@ # Import the required libraries for the application. import sys -from pysros.management import connect -from pysros.pprint import Table # pylint: disable=no-name-in-module # Import the exceptions so they can be caught on error. from pysros.exceptions import ModelProcessingError +from pysros.management import connect +from pysros.pprint import Table # pylint: disable=no-name-in-module # Global credentials dictionary for the purposes of this example. Global variables # discouraged in operational applications. diff --git a/examples/show_statistics_interval.py b/examples/show_statistics_interval.py index a92c20c..d9ec301 100755 --- a/examples/show_statistics_interval.py +++ b/examples/show_statistics_interval.py @@ -8,11 +8,11 @@ import sys import time -from pysros.management import connect, sros -from pysros.pprint import Table # pylint: disable=no-name-in-module # Import the exceptions so they can be caught on error. from pysros.exceptions import ModelProcessingError +from pysros.management import connect, sros +from pysros.pprint import Table # pylint: disable=no-name-in-module credentials = { "host": "192.168.1.1", diff --git a/examples/show_system_commit.py b/examples/show_system_commit.py index ccdcbc0..9e197aa 100755 --- a/examples/show_system_commit.py +++ b/examples/show_system_commit.py @@ -7,7 +7,7 @@ # pylint: disable=import-error, import-outside-toplevel, line-too-long, too-many-branches, too-many-locals, too-many-statements """ -Tested on: SR OS 22.2.R1 +Tested on: SR OS 24.3.R1 Show the commit history in an alternate format. @@ -56,16 +56,15 @@ def get_connection(): # Else if the application is executed remotely else: # Import sys for returning specific exit codes - import sys - # Import getpass to read the password import getpass + import sys # Import the exceptions so they can be caught on error # fmt: off from pysros.exceptions import ModelProcessingError - # fmt: on + # fmt: on # Make sure we have the right number of arguments, the host can # be an IP address or a hostname if len(sys.argv) != 2: @@ -79,7 +78,9 @@ def get_connection(): sys.exit(2) # Get the password - password = getpass.getpass() + password = getpass.getpass( + prompt="Password (press Enter to use SSH key): " + ) # The try statement and except statements allow an operation # attempt with specific error conditions handled gracefully diff --git a/examples/show_system_summary.py b/examples/show_system_summary.py index 6de677c..b1ebe74 100755 --- a/examples/show_system_summary.py +++ b/examples/show_system_summary.py @@ -7,7 +7,7 @@ # pylint: disable=import-error, import-outside-toplevel, line-too-long, too-many-branches, too-many-locals, too-many-statements """ -Tested on: SR OS 23.10.R2 +Tested on: SR OS 24.3.R1 Show system summary information. @@ -37,15 +37,17 @@ /configure system { management-interface cli md-cli environment command-alias alias "summary" mount-point "/show system" } """ -# Import sys for parsing arguments and returning specific exit codes -import sys - # Import datetime to get and display the date and time import datetime +# Import sys for parsing arguments and returning specific exit codes +import sys + # Import the connect and sros methods from the management pySROS submodule from pysros.management import connect, sros +# Import the Container and Leaf structures from the wrappers pySROS submodule +from pysros.wrappers import Container, Leaf # Define local language strings local_str = { @@ -154,8 +156,8 @@ def get_remote_connection(my_username, my_host, my_password): # Import the exceptions so they can be caught on error # fmt: off from pysros.exceptions import ModelProcessingError - # fmt: on + # fmt: on # The try statement and except statements allow an operation # attempt with specific error conditions handled gracefully try: @@ -196,18 +198,19 @@ def print_rows(input_data, column): """Print table rows in the specified format""" for k in sorted(input_data): - # Elements with no data are not displayed - if input_data[k].data: - # If the input data is a dict like from - # /nokia-state:state/system/alarms/active then recurse - if isinstance(input_data[k].data, dict): - print_rows(input_data[k].data, column) - else: - print( - "{0:<{width}} : {1}".format( - k, str(input_data[k].data), width=column - ) + # If the input data is a Leaf, and has data + # (elements with no data are not displayed) + if isinstance(input_data[k], Leaf) and input_data[k].data: + print( + "{0:<{width}} : {1}".format( + k, str(input_data[k].data), width=column ) + ) + # If the input data is a Container like from + # /nokia-state:state/system/alarms/active then recurse + elif isinstance(input_data[k], Container): + print_rows(input_data[k].data, column) + # Else the input data is not a Leaf or Container, so ignore it def set_column_width(input_data): @@ -248,10 +251,10 @@ def print_row_mixed_spacing(language, column, name, value): def show_system_summary_output(connection_object, language): """Main function for the show_system_summary command""" - bright_green = "\u001b[32;1m" - bright_red = "\u001b[31;1m" - bright_yellow = "\u001b[33;1m" - reset_color = "\u001b[0m" + bright_green = "\033[1;32m" + bright_red = "\033[1;31m" + bright_yellow = "\033[1;33m" + reset_color = "\033[0m" # Define local language oper-state strings and colors oper_state_str = { @@ -491,7 +494,9 @@ def get_connection_with_argv(): sys.exit(2) # Get the password - password = getpass.getpass() + password = getpass.getpass( + prompt="Password (press Enter to use SSH key): " + ) # Get a remote Connection object connection_object = get_remote_connection( diff --git a/examples/sleep.py b/examples/sleep.py new file mode 100755 index 0000000..08b4b71 --- /dev/null +++ b/examples/sleep.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python3 + +### sleep.py +# Copyright 2024 Nokia +### + +# pylint: disable=import-error, import-outside-toplevel, line-too-long, too-many-branches, too-many-locals, too-many-statements + +""" +Tested on: SR OS 24.3.R1 + +Delay for a specified amount of time. + +Execution on SR OS + usage: pyexec bin/sleep.py ( | ) + +Add the following alias so that the Python application can be run as a +native MD-CLI command. + +/configure python python-script "sleep" admin-state enable +/configure python python-script "sleep" urls ["cf3:bin/sleep.py"] +/configure python python-script "sleep" version python3 + +/configure system management-interface cli md-cli environment command-alias alias "sleep" admin-state enable +/configure system management-interface cli md-cli environment command-alias alias "sleep" description "Delay for a specified amount of time" +/configure system management-interface cli md-cli environment command-alias alias "sleep" python-script "sleep" +/configure system management-interface cli md-cli environment command-alias alias "sleep" mount-point global +""" + +# Import sys for parsing arguments and returning specific exit codes +import sys + +# Import the time module for the sleep function +import time + + +def usage(): + """Print the usage""" + + print("") + print("Usage:", sys.argv[0], "( | )") + print(" - <0..100>") + print(" - <0.00..100.99>") + + +def sleep(): + """Parse decimal number and then sleep""" + + # Check the number of arguments is 2 + if len(sys.argv) != 2: + usage() + sys.exit(2) + + # Check to see if the argument is a float. + # This also checks if the argument is an int. + try: + float(sys.argv[1]) + if float(sys.argv[1]) < 0.00 or float(sys.argv[1]) > 100.99: + usage() + sys.exit(2) + except ValueError: + usage() + sys.exit(2) + + time.sleep(float(sys.argv[1])) + + +if __name__ == "__main__": + sleep() diff --git a/examples/to_pysros.py b/examples/to_pysros.py index f82c423..5b1f992 100644 --- a/examples/to_pysros.py +++ b/examples/to_pysros.py @@ -17,13 +17,15 @@ Execution on remote machine Not supported. """ + import builtins +import json # Required imports import sys -import json -from pysros.management import connect + from pysros.exceptions import ModelProcessingError +from pysros.management import connect def get_connection(): diff --git a/examples/user_input_example.py b/examples/user_input_example.py new file mode 100644 index 0000000..e52847a --- /dev/null +++ b/examples/user_input_example.py @@ -0,0 +1,97 @@ +### user_input_example.py +# Copyright 2024 Nokia +### + +""" +Tested on: SR OS 24.7.R1 + +Simple example to show different user input options and +interaction with the SR OS pager. + +This example program is designed to be run locally on +an SR OS device. +""" + +import getpass +import sys + +from pysros.management import sros + + +def print_lines_of_data(): + for line in range(0, 100): + print("This is line %s" % line) + + +def input_without_prompt(): + """This function uses the print statement to write + a prompt to the screen and then requests an input + using the input function call which has no parameters. It prints what you entered in + response. + """ + print("Please enter your name: ", end="") + name = input() + print("You entered %s" % name) + + +def input_with_prompt(prompt="Enter your favourite fruit: "): + """This function uses the parameter to the input function + to display a prompt to the user requesting the input. The + prompt can be set when calling this function but it has a + default prompt if you do not set it yourself. It + prints what you entered in response. + + Args: + prompt (str, optional): Prompt to display to the user. Defaults to "Enter your favourite fruit: ". + """ + my_input = input(prompt) + print("You entered %s" % my_input) + + +def trigger_pager_then_input_with_prompt(prompt): + """This function prints enough text to the screen to + trigger the SR OS pager and subsequently requests input. + + Args: + prompt (str, optional): User prompt for input + """ + print_lines_of_data() + input_with_prompt(prompt) + + +def readline_without_prompt(): + """This example function requests user input using the + readline function. The readline function includes + any trailing newline character that the input function + strips off.""" + print("Enter your favourite animal: ") + animal = sys.stdin.readline() + print("You entered %s" % animal) + + +def getpass_with_prompt(prompt): + """This function uses getpass to obtain user input + that is not returned to the screen as you type it. + For demonstration purposes it then prints the input + to the screen. + + Args: + prompt (str, optional): User defined prompt + """ + password = getpass.getpass(prompt=prompt) + print("You entered %s." % password) + print("Note that it was not displayed as you typed.") + + +def main(): + """To show options for reading in user input.""" + input_without_prompt() + input_with_prompt("Enter your age: ") + trigger_pager_then_input_with_prompt("Input after the pager now: ") + readline_without_prompt() + getpass_with_prompt("Enter your password which we will print later: ") + + +if __name__ == "__main__": + if sros: + main() diff --git a/examples/who.py b/examples/who.py index ce760f7..041cc89 100755 --- a/examples/who.py +++ b/examples/who.py @@ -7,7 +7,7 @@ # pylint: disable=import-error, import-outside-toplevel, line-too-long, too-many-branches, too-many-locals, too-many-statements """ -Tested on: SR OS 23.10.R2 +Tested on: SR OS 24.3.R1 Show who is logged on. @@ -54,8 +54,8 @@ def get_remote_connection(my_username, my_host, my_password): # Import the exceptions so they can be caught on error # fmt: off from pysros.exceptions import ModelProcessingError - # fmt: on + # fmt: on # The try statement and except statements allow an operation # attempt with specific error conditions handled gracefully try: @@ -135,7 +135,9 @@ def get_connection_with_argv(): sys.exit(2) # Get the password - password = getpass.getpass() + password = getpass.getpass( + prompt="Password (press Enter to use SSH key): " + ) # Get a remote Connection object connection_object = get_remote_connection( @@ -150,6 +152,13 @@ def get_connection_with_argv(): def who_output(connection_object, arg): """Main function for the who command""" + bright_cyan = "\033[1;36m" + bright_dark_gray = "\033[1;30m" + cyan = "\033[0;36m" + reset_color = "\033[0m" + user_color = reset_color + separator_color = reset_color + # Get the users state users = connection_object.running.get("/nokia-state:state/users/session") @@ -163,11 +172,23 @@ def who_output(connection_object, arg): ): continue + # Set color on the active session + if str(users[session_id]["current-active-session"]) == "True": + user_color = bright_cyan + separator_color = bright_dark_gray + text_color = cyan + else: + user_color = reset_color + separator_color = reset_color + text_color = reset_color + # Print session info in who(1) format # GNU who displays "%-8s" for user, use same format + print(user_color, end="") print( - "{0:8.8} {1}/{2}\t{3}".format( + "{0:8.8} {1}{2}/{3}\t{4}".format( str(users[session_id]["user"]), + reset_color, str(users[session_id]["connection-type"]), str(session_id), str(users[session_id]["login-time"]), @@ -177,11 +198,18 @@ def who_output(connection_object, arg): if "connection-ip" in users[session_id]: print( - " (" + " " + + separator_color + + "(" + + text_color + str(users[session_id]["router-instance"]) + + separator_color + "/" + + text_color + str(users[session_id]["connection-ip"]) + + separator_color + ")" + + reset_color ) else: print() diff --git a/pysros/__init__.py b/pysros/__init__.py index 31aad40..00d21cc 100644 --- a/pysros/__init__.py +++ b/pysros/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2021-2023 Nokia +# Copyright 2021-2024 Nokia __all__ = ("management", "exceptions", "wrappers", "pprint", ) diff --git a/pysros/errors.py b/pysros/errors.py index af1c3b1..c2d8c85 100644 --- a/pysros/errors.py +++ b/pysros/errors.py @@ -1,4 +1,4 @@ -# Copyright 2021-2023 Nokia +# Copyright 2021-2024 Nokia from .exceptions import (ActionTerminatedIncompleteError, InternalError, InvalidPathError, JsonDecodeError, @@ -22,8 +22,8 @@ pysros_err_can_not_find_yang = (ModelProcessingError, "Cannot find yang '{yang_name}'") pysros_err_cannot_call_go_to_parent = (InvalidPathError, "Cannot call go_to_parent on root") pysros_err_cannot_delete_from_state = (SrosMgmtError, "Cannot delete from state tree") -pysros_err_cannot_lock_and_unlock_running = (SrosMgmtError, "Cannot lock and unlock running config") -pysros_err_cannot_modify_config = (SrosMgmtError, "Cannot modify running config") +pysros_err_cannot_lock_and_unlock_running = (SrosMgmtError, "Cannot lock and unlock running or intended config") +pysros_err_cannot_modify_config = (SrosMgmtError, "Cannot modify running or intended config") pysros_err_cannot_modify_state = (SrosMgmtError, "Cannot modify state tree") pysros_err_cannot_pars_path = (ModelProcessingError, "Cannot parse path {path!r}") pysros_err_cannot_remove_node = (ModelProcessingError, "Cannot remove {node}") diff --git a/pysros/exceptions.py b/pysros/exceptions.py index dfb39cb..088ec3b 100644 --- a/pysros/exceptions.py +++ b/pysros/exceptions.py @@ -1,4 +1,4 @@ -# Copyright 2021-2023 Nokia +# Copyright 2021-2024 Nokia __all__ = ( "SrosMgmtError", "InvalidPathError", "ModelProcessingError", diff --git a/pysros/identifier.py b/pysros/identifier.py index 15e8496..63eb702 100644 --- a/pysros/identifier.py +++ b/pysros/identifier.py @@ -1,4 +1,4 @@ -# Copyright 2021-2023 Nokia +# Copyright 2021-2024 Nokia import re diff --git a/pysros/management.py b/pysros/management.py index f8b20b3..2b68159 100644 --- a/pysros/management.py +++ b/pysros/management.py @@ -1,4 +1,4 @@ -# Copyright 2021-2023 Nokia +# Copyright 2021-2024 Nokia import base64 import contextlib @@ -191,6 +191,8 @@ class Connection: :ivar running: running datastore :vartype running: .Datastore + :ivar intended: intended datastore + :vartype intended: .Datastore :ivar candidate: candidate datastore :vartype candidate: .Datastore @@ -207,6 +209,7 @@ def __init__(self, *args, yang_directory, rebuild, **kwargs): ) from None self.running = Datastore(self, 'running') + self.intended = Datastore(self, 'intended') self.candidate = Datastore(self, 'candidate') self.yang_directory = yang_directory @@ -664,6 +667,19 @@ def convert(self, path, payload, *, source_format, destination_format, pretty_pr destination_format, pretty_print, action_io ) + def session_id(self): + """Returns the current connections session-id. + + :returns: Current connections session-id. + :rtype: int + + .. Reviewed by PLM 20240523 + .. Reviewed by TechComms 20240529 + """ + try: + return int(self._nc._session.id) + except: + return self._nc._session.id class Datastore: """Datastore object that can be used to perform multiple operations on a specified datastore. @@ -680,7 +696,7 @@ class _ExistReason(Enum): delete = auto() def __init__(self, connection, target): - if target != 'running' and target != 'candidate': + if target not in ('running', 'candidate', 'intended'): raise make_exception(pysros_err_invalid_target) self.connection = connection self.nc = connection._nc @@ -696,8 +712,8 @@ def _check_empty_string(self, model_walker): if '' in k.values(): raise make_exception(pysros_err_filter_empty_string) - def _prepare_root_ele(self, subtree, path): - root = etree.Element("filter") + def _prepare_root_ele(self, subtree, path, filter_tag = "filter"): + root = etree.Element(filter_tag) root.extend(subtree) if self.debug: print("GET request for path ", path) @@ -718,6 +734,23 @@ def _operation_get_config(self, subtree, defaults, path): else: return self.nc.get_config(source=self.target, with_defaults=self._get_defaults(defaults)) + def _operation_get_data(self, subtree, defaults, path): + ds_pfx = "ds" + xml_get = etree.Element("get-data", nsmap = {None : "urn:ietf:params:xml:ns:yang:ietf-netconf-nmda"}) + xml_ds = etree.SubElement(xml_get, "datastore", nsmap = {ds_pfx : "urn:ietf:params:xml:ns:yang:ietf-datastores"}) + xml_ds.text = f"{ds_pfx}:{self.target}" + + if subtree: + root = self._prepare_root_ele(subtree, path, "subtree-filter") + xml_filter = etree.SubElement(xml_get, "subtree-filter") + xml_filter.extend(root) + + if defaults: + xml_defaults = etree.SubElement(xml_get, "with-defaults") + xml_defaults.text = "report-all" + + return self.nc.rpc(xml_get) + def _get(self, path, *, defaults=False, custom_walker=None, config_only=False, filter=None): model_walker = custom_walker if custom_walker else FilteredDataModelWalker.user_path_parse(self.connection.root, path) @@ -740,12 +773,13 @@ def _get(self, path, *, defaults=False, custom_walker=None, config_only=False, f else: response = self._operation_get(config, defaults, path) else: + get_method = self._operation_get_data if self.target == "intended" else self._operation_get_config if model_walker.current.config == False: raise make_exception( pysros_err_can_get_state_from_running_only ) model_walker.config_only = True - response = self._operation_get_config(config, defaults, path) + response = get_method(config, defaults, path) if self.debug: print("GET response") @@ -811,7 +845,7 @@ def _handle_annotations_only(self, path, rd, annotations_only): return rd def _set(self, path, value, action, method="default", annotations_only=False): - if self.target == 'running': + if self.target in ('running', 'intended'): raise make_exception(pysros_err_cannot_modify_config) if method not in ("default", "merge", "replace"): raise make_exception(pysros_err_unsupported_set_method) @@ -873,7 +907,7 @@ def _exists(self, path, exist_reason): if model_walker.current.config == False: if exist_reason == Datastore._ExistReason.delete: raise make_exception(pysros_err_cannot_delete_from_state) - elif self.target == "candidate": + elif self.target in ("candidate", "intended"): raise make_exception( pysros_err_can_check_state_from_running_only ) @@ -953,7 +987,7 @@ def _get_list_keys(self, path, defaults): return [*current.to_model()] def _compare(self, output_format, user_path): - if self.target == 'running': + if self.target != 'candidate': raise make_exception(pysros_err_unsupported_compare_datastore) if output_format not in ("md-cli", "xml"): raise make_exception(pysros_err_unsupported_compare_method) @@ -1240,7 +1274,7 @@ def delete(self, path, commit=True, *, annotations_only=False): .. Reviewed by TechComms 20211202 """ with self.connection._process_connected(): - if self.target == 'running': + if self.target in ('running', 'intended'): raise make_exception(pysros_err_cannot_modify_config) if not self._exists(path, Datastore._ExistReason.delete): raise make_exception(pysros_err_no_data_found) @@ -1319,7 +1353,7 @@ def lock(self): .. Reviewed by TechComms 20220624 """ with self.connection._process_connected(): - if self.target == "running": + if self.target in ("running", "intended"): raise make_exception(pysros_err_cannot_lock_and_unlock_running) self.nc.lock(target=self.target) @@ -1336,7 +1370,7 @@ def unlock(self): .. Reviewed by TechComms 20220624 """ with self.connection._process_connected(): - if self.target == "running": + if self.target in ("running", "intended"): raise make_exception(pysros_err_cannot_lock_and_unlock_running) self.nc.unlock(target=self.target) @@ -1352,7 +1386,7 @@ def commit(self): .. Reviewed by TechComms 20220624 """ with self.connection._process_connected(): - if self.target == 'running': + if self.target in ('running', 'intended'): raise make_exception(pysros_err_cannot_modify_config) self._commit() @@ -1368,7 +1402,7 @@ def discard(self): .. Reviewed by TechComms 20220624 """ with self.connection._process_connected(): - if self.target == 'running': + if self.target in ('running', 'intended'): raise make_exception(pysros_err_cannot_modify_config) self.nc.discard_changes() diff --git a/pysros/model.py b/pysros/model.py index 4a2f8df..871c430 100644 --- a/pysros/model.py +++ b/pysros/model.py @@ -1,4 +1,4 @@ -# Copyright 2021-2023 Nokia +# Copyright 2021-2024 Nokia import copy import locale diff --git a/pysros/model_builder.py b/pysros/model_builder.py index 617d1ad..615d69a 100644 --- a/pysros/model_builder.py +++ b/pysros/model_builder.py @@ -1,4 +1,4 @@ -# Copyright 2021-2023 Nokia +# Copyright 2021-2024 Nokia import copy from collections import defaultdict diff --git a/pysros/model_path.py b/pysros/model_path.py index 5c5ce32..4c46e48 100644 --- a/pysros/model_path.py +++ b/pysros/model_path.py @@ -1,4 +1,4 @@ -# Copyright 2021-2023 Nokia +# Copyright 2021-2024 Nokia from typing import Iterable diff --git a/pysros/model_walker.py b/pysros/model_walker.py index b739b4f..7c30c74 100644 --- a/pysros/model_walker.py +++ b/pysros/model_walker.py @@ -1,4 +1,4 @@ -# Copyright 2021-2023 Nokia +# Copyright 2021-2024 Nokia import contextlib import copy @@ -602,3 +602,4 @@ class ActionOutputFilteredDataModelWalker(FilteredDataModelWalker): Model.StatementType.choice_, Model.StatementType.case_, Model.StatementType.output_ ) + diff --git a/pysros/pprint.py b/pysros/pprint.py index 7a63a7b..8d86491 100644 --- a/pysros/pprint.py +++ b/pysros/pprint.py @@ -1,4 +1,4 @@ -# Copyright 2021-2023 Nokia +# Copyright 2021-2024 Nokia from .errors import * from .errors import make_exception diff --git a/pysros/request_data.py b/pysros/request_data.py index c058d16..90fcd1d 100644 --- a/pysros/request_data.py +++ b/pysros/request_data.py @@ -1,4 +1,4 @@ -# Copyright 2021-2023 Nokia +# Copyright 2021-2024 Nokia import copy import json @@ -153,7 +153,7 @@ def set_as_xml(self, value): .. Reviewed by TechComms 20210712 """ - d = value.xpath("/ncbase:rpc-reply/ncbase:data", namespaces={"ncbase": "urn:ietf:params:xml:ns:netconf:base:1.0"}) + d = value.xpath("/ncbase:rpc-reply/*[local-name() = 'data']", namespaces={"ncbase": "urn:ietf:params:xml:ns:netconf:base:1.0"}) if len(d) != 1: raise make_exception(pysros_err_wrong_netconf_response) d = d[0] diff --git a/pysros/singleton.py b/pysros/singleton.py index b77bd2b..6b1ff04 100644 --- a/pysros/singleton.py +++ b/pysros/singleton.py @@ -1,4 +1,4 @@ -# Copyright 2021-2023 Nokia +# Copyright 2021-2024 Nokia class _Singleton(type): _instances = {} diff --git a/pysros/tokenizer.py b/pysros/tokenizer.py index 0553a5a..dfe20fd 100644 --- a/pysros/tokenizer.py +++ b/pysros/tokenizer.py @@ -1,4 +1,4 @@ -# Copyright 2021-2023 Nokia +# Copyright 2021-2024 Nokia import collections import re diff --git a/pysros/wrappers.py b/pysros/wrappers.py index 118500a..a239c2f 100644 --- a/pysros/wrappers.py +++ b/pysros/wrappers.py @@ -1,4 +1,4 @@ -# Copyright 2021-2023 Nokia +# Copyright 2021-2024 Nokia import operator import collections diff --git a/pysros/yang_type.py b/pysros/yang_type.py index 0073f0d..ddcaeb6 100644 --- a/pysros/yang_type.py +++ b/pysros/yang_type.py @@ -1,4 +1,4 @@ -# Copyright 2021-2023 Nokia +# Copyright 2021-2024 Nokia import base64 from abc import ABC, abstractmethod diff --git a/setup.py b/setup.py index 00fea9c..fb6d5bd 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,4 @@ -# Copyright 2021 Nokia +# Copyright 2021-2024 Nokia from setuptools import setup @@ -7,7 +7,7 @@ setup( name='pysros', - version='24.3.1', + version='24.7.1', packages=['pysros'], url='https://www.nokia.com', license='Copyright 2021-2024 Nokia. License available in the LICENSE.md file.',