diff --git a/docs/blog/posts/2023/sshx.md b/docs/blog/posts/2023/sshx.md index c6f2267b..d77ba53e 100644 --- a/docs/blog/posts/2023/sshx.md +++ b/docs/blog/posts/2023/sshx.md @@ -13,7 +13,7 @@ Countless times I've been in a situation where I needed to share my terminal wit Either I exhausted my networking foo and needed help from a colleague, or I was the one who was asked to help. In both cases, the problem was the same - how to **quickly**, **securely**[^1] and **effortlessly** share the terminal with someone else. -The problem is not new and there are many options on the table. From installing a VPN software and sharing the credentials, through zero-trust solutions like [teleport](https://goteleport.com/), to using a simple SSH tunnel. All of these solutions are great, but they require some setup and configuration. And sometimes you just want to share your terminal with someone without going through the hassle of setting up a VPN or a zero-trust solution. +The problem is not new and there are many options on the table. From installing a VPN software and sharing the credentials, through zero-trust solutions like Teleport, to using a simple SSH tunnel. All of these solutions are great, but they require some setup and configuration. And sometimes you just want to share your terminal with someone without going through the hassle of setting up a VPN or a zero-trust solution. The [sshx.io](https://sshx.io) open-source service that [just](https://twitter.com/ekzhang1/status/1721288674204131523) popped out offers a simple solution to this problem. diff --git a/docs/cli/index.md b/docs/cli/index.md index b0856753..cd01f92d 100644 --- a/docs/cli/index.md +++ b/docs/cli/index.md @@ -1,3 +1,49 @@ # SR Linux CLI -Documentation under construction... +It seems that the technological progress touched every bit of a network OS, but CLI. We've witnessed significant development in the speeds and feeds, network management protocols, automation paradigms and yet CLI is largely the same as 20 years ago. + +Maybe there is not much one could ask from a CLI? It accepts an input from a user and spits out the text output, what else does one need? +Or maybe CLI is so dead that no one is interested to spend time thinking how to make it better? + +We at SR Linux product team believe that CLI is still very much relevant and is an important human-facing interface to any Network OS. And there is definitely a lot of improvement to be found when thinking what a modern network CLI should be. + +When designing the SR Linux CLI we made sure it feels fresh and powerful, leverages the recent software advancements, all with unprecedented customization capabilities and operator' UX at its heart. + +## Core features + +Even before touching any of the CLI programmability aspects, there are some core CLI features we made better in SR Linux. Starting from a modern two-line prompt with a customizable toolbar with clear indication of a current context a user is in, through the powerful autocompletion engine with autosuggestions, all the way to a wide range of output modifiers to give you the ultimate flexibility in the output format. + +These core features are explained in details in our [getting started guide](../get-started/cli.md) and we encourage every user to get through it. + +## Plugins + +Pluggable architecture is at the core of our CLI. It enables our users to customize almost every bit of the CLI behavior and that is something no other CLI can offer. + +Ever found yourself in need of a CLI command that a vendor does not provide? With SR Linux CLI we allow you write your own CLI commands. And not just show commands! +By giving you access to the same infrastructure and APIs that our stock CLI commands use, you will be able to create the CLI experience tailored for your ops teams. + +As with every powerful system, there are some bits and pieces you will first need to get familiar with. Open the [CLI Plugins section](plugins/index.md) to become a professional CLI plugin developer. + +## Aliases + +Have you seen power Linux users swiftly typing in `gcb new-branch` or `k get po` and getting a meaningful output? These are command aliases that allow you to type less to get more. And we have them in SR Linux! + +Aliases in SR Linux can be used to substitute a long command and can even be customized to take in arguments to have an ultimate CLI experience. + +While we are working on an in-depth blog post about aliases, check out the [Hackathon exercise around aliases](https://github.com/nokia/SReXperts/tree/main/hackathon/activities/srlinux-b-cli-aliasing) to learn more about them, you'll love them! + +## Wildcards and ranges + +Let's talk about something that SR Linux CLI doesn't have -- apply groups :scared_face: Apply groups bloat the device model and makes the YANG-based code bindings bloated out of proportion. + +But fear not, you won't miss apply groups, because SR Linux CLI comes with wildcards and ranges. These two will make the bulk edits a joy as they allow you to use familiar shell-like expansion and wildcards. + +Checkout [Wildcards and ranges blog post](../blog/posts/2023/cli-ranges.md) for a deep dive on these concepts. Master it and you'll feel like having a CLI black belt! + +## Prompt customization + +SR Linux ships with a powerful two-line prompt conveying the most important information like the CLI mode, current context, username and hostname, and current date. A sane default. But what if you don't like it? + +In Linux world the prompt customization is a form of art. There are hundreds of prompt engines for different prompts and power users create the custom prompt for their shells first thing they log in in a new system. + +We wanted to give you the same flexibility in choosing what your prompt should look like in case you are not happy with the default one. You can customize both the prompt look and feel as well as the bottom toolbar. We plan to have a post about all the options and give you some ideas for prompt customization. diff --git a/docs/cli/plugins/getting-started.md b/docs/cli/plugins/getting-started.md new file mode 100644 index 00000000..72d27014 --- /dev/null +++ b/docs/cli/plugins/getting-started.md @@ -0,0 +1,651 @@ +# Get started with CLI plugin development + + + +As custom CLI plugins use the same infrastructure as the native commands, they get the same superpowers as the native commands. But with great power comes... some complexity. + +This Getting Started guide will introduce you to the main concepts of CLI plugins development, and the rest of the documentation will help you nail down the details. + +/// admonition | Prerequisites + type: subtle-note + +To follow along, you will need the following tools installed on your machine: + +* [Containerlab](https://containerlab.dev/install/#quick-setup) v0.64.0 or later +* `git` +/// + +Learning CLI programmability by developing your own CLI plugin is the way to go. Today we will be building a simple **`show uptime`** command that will display the uptime of an SR Linux device and the UTC time it was last booted. + +```srl +--{ running }--[ ]-- +A:srl# show uptime +-------------------------------------------------------------------------- +Uptime : 1 days 0 hours 29 minutes 48 seconds +Last Booted: 2025-02-15T16:23:47.956Z +-------------------------------------------------------------------------- +``` + +Nothing sophisticated, yet it provides a good starting point for you to get familiar with the most important building blocks of the CLI plugin development. The full code is available in the [uptime-cli-plugin](https://github.com/srl-labs/uptime-cli-plugin) repository. + +## Setting up the environment + +Spending some time on setting up the dev environment will greatly simplify code navigation and debugging. To assist you with the environment setup, our repo features a small script with some helper functions. +Start with cloning the [uptime-cli-plugin](https://github.com/srl-labs/uptime-cli-plugin) project: + +``` +git clone https://github.com/srl-labs/uptime-cli-plugin.git +cd uptime-cli-plugin +``` + +Once in the project directory, checkout the `get-started` branch that contains just the blank project template: + +``` +git checkout get-started +``` + +Once checked out, you will find the following important directories and files: + +* `run` - a bash script that contains helper functions to orchestrate various steps of the development process +* `cli.clab.yml` - a containerlab topology file that will be used to deploy the virtual lab +* `uptime` - a directory where we will put the source code of our CLI plugin +* `pyproject.toml` and `uv.lock` - project-specific configuration files for the Python project + +Now it is time to create the dev environment. The `run` script runner has a one-shot command to create the environment: + +```bash +./run setup-dev-env #(1)! +``` + +1. This commands does a few things: + + * ensures [uv](https://docs.astral.sh/uv/)[^1] is installed + * creates a virtual lab with a single SR Linux node + * installs Python and a few dependencies that match the virtual environment of the SR Linux node + * copies the SR Linux package from the lab node to the `./private/srlinux` directory to make sure import paths are resolved in the IDE + +In under 30 seconds, you will have a running SR Linux node and a fully functional Python development environment. + +/// admonition | `.env` file + type: tip + +In the repo root you will find a `.env` file that contains the universal environment variables that ensure that the local `.venv` virtual environment is used during the development. + +Load the environment variables by sourcing the `.env` file: + +```bash +source .env +``` + +Now running `which python` will show the path to the Python executable in the virtual environment. + +``` +โฏ which python +.venv/bin/python +``` + +/// + +And that's it! You are ready to write your first CLI plugin! + +## Plugin structure + +Now open the [`uptime/uptime.py`](https://github.com/srl-labs/uptime-cli-plugin/blob/get-started/uptime/uptime.py) file in your favorite IDE and let's have a look at what makes an empty CLI plugin: + +```python +from srlinux.mgmt.cli import CliPlugin #(1)! + + +class Plugin(CliPlugin): + """ + Adds `show uptime` command. + + Example output: + + --{ running }--[ ]-- + A:srl# show uptime + ---------------------------------------------------------------------- + Uptime : 0 days 6 hours 0 minutes 25 seconds + Last Booted: 2024-10-24T03:31:50.561Z + ---------------------------------------------------------------------- + """ + + def load(self, cli, arguments): + pass +``` + +1. As part of the dev env setup, we copy out the `srlinux` package from the SR Linux node to the `private/srlinux` directory. This is done to make sure that the import paths are resolved correctly in the IDE. + +The CLI template only needs to have a class called `Plugin` that inherits from the `CliPlugin` with a `load` public method. The SR Linux CLI engine scans the user [directories](index.md#user-defined-commands) and loads all the plugins that match this signature. + +All our dev work will be done in the `load` method, as this is the enclosing method that is called by the CLI engine. + +## The `load` method + +The `load` method of the `Plugin` class is the entry point for the CLI plugin. It is where you add your new CLI command to one of the CLI modes - `show`, `global` or `tools`. +Since we want to create a `show uptime` command, we are going to "mount" our command to the `show` mode. + +The `load` method signature is as follows: + +```python +def load(self, cli, arguments) +``` + +where the `cli` argument is a `CliLoader` object that allows you to add your new command to the CLI tree. Here is how we add a command to the `show` mode: + +```python +class Plugin(CliPlugin): + # ... + def load(self, cli, arguments): + cli.show_mode.add_command( + syntax=self._syntax(), + schema=self._schema(), + callback=self._print, + ) +``` + +The `add_command` method of the CLI mode receives the command definition arguments such as: + +* `syntax` - how the command is structured syntactically +* `schema` - what schema defines the data that the command operates on +* `callback` - what function to call when the command is executed + +Let's have a look at each of these arguments in more detail. + +## Syntax + +The command's syntax defines the command representation - its name, help strings and the arguments it accepts. To define a command syntax, we need to create an object of the `Syntax` class; this is what the `_syntax` method does: + +```python +from srlinux.syntax import Syntax #(1)! + +class Plugin(CliPlugin): + # ... + def _syntax(self): + return Syntax( + name="uptime", + short_help="โŒ› Show platform uptime", + help="โŒ› Show platform uptime in days, hours, minutes and seconds.", + help_epilogue="๐Ÿ“– It is easy to wrap up your own CLI command. Learn more about SR Linux at https://learn.srlinux.dev", + ) +``` + +1. Do not forget to import the `Syntax` class from the `srlinux.syntax` module. + +For our `show uptime` command we just define the command name and the help text in different flavors in the Syntax object. + +/// details | Want to try out what we have so far? + type: subtle-note +If you want to try the plugin in its current state, you need to comment out the two methods that we haven't defined yet. + +```python +from srlinux.mgmt.cli import CliPlugin +from srlinux.syntax import Syntax + + +class Plugin(CliPlugin): + def load(self, cli, arguments): + cli.show_mode.add_command( + syntax=self._syntax(), + # schema=self._schema(), + # callback=self._print, + ) + + def _syntax(self): + return Syntax( + name="uptime", + short_help="โŒ› Show platform uptime", + help="โŒ› Show platform uptime in days, hours, minutes and seconds.", + help_epilogue="๐Ÿ“– It is easy to wrap up your own CLI command. Learn more about SR Linux at https://learn.srlinux.dev", + ) + +``` + +Now save the changes in the `uptime/uptime.py` file and ssh into the SR Linux node: + +``` +ssh srl +``` + +Now, start typing `show upt` and you will see how CLI will autocomplete the command `show uptime` for you based on the syntax you defined. After you hit Tab and autocomplete the full command, hit ? to see the help text: + +``` +--{ running }--[ ]-- +A:srl# show uptime +usage: uptime + +โŒ› Show platform uptime in days, hours, minutes and seconds. + +๐Ÿ“– It is easy to wrap up your own CLI command. Learn more about SR Linux at https://learn.srlinux.dev +``` + +Hey, that's what we wanted! + +/// + +The syntax alone is not enough to make the command work. We need to define the schema and the callback. + +## Schema + +You might be wondering, what is a schema and why do we need it for such a simple thing as a CLI command? + +For a given `show` command the schema **describes the data** that the command intends to print out. As per our intent, the `show uptime` command should print out two things + +* the uptime of the SR Linux system +* and the last booted time. + +But, still, why do we need a schema to print values? Can't we just use `print` and go about our day? + +```python +print(f"Uptime: {uptime}") +``` + +The answer is that **a schema makes it possible to have multiple output formats** without implementing the logic for each of them. Have a look at all these formats that our `show uptime` command gets for free: + +/// tab | default (tag/value) + +```srl +--{ running }--[ ]-- +A:srl# show uptime +-------------------------------------------------------------------------- +Uptime : 1 days 0 hours 29 minutes 48 seconds +Last Booted: 2025-02-15T16:23:47.956Z +-------------------------------------------------------------------------- +``` + +/// +/// tab | table + +```srl +--{ running }--[ ]-- +A:srl# show uptime | as table ++-----------------------------------+-----------------------------------+ +| Uptime | Last Booted | ++===================================+===================================+ +| 1 days 0 hours 32 minutes 29 | 2025-02-15T16:23:47.956Z | +| seconds | | ++-----------------------------------+-----------------------------------+ +``` + +/// +/// tab | json + +```srl +--{ running }--[ ]-- +A:srl# show uptime | as json +{ + "uptime": { + "Uptime": "1 days 0 hours 30 minutes 56 seconds", + "Last Booted": "2025-02-15T16:23:47.956Z" + } +} +``` + +/// + +/// tab | yaml + +```srl +--{ running }--[ ]-- +A:srl# show uptime | as yaml +--- +uptime: + Uptime: 1 days 0 hours 30 minutes 42 seconds + Last Booted: '2025-02-15T16:23:47.956Z' +``` + +/// +/// tab | xml + +```srl +--{ running }--[ ]-- +A:srl# show uptime | as xml + + 1 days 0 hours 31 minutes 15 seconds + 2025-02-15T16:23:47.956Z + +``` + +/// + +Without having a schema-modeled data structure, we would have to implement the logic for each of the output formats ourselves, which is quite some work. + +Since our command has only two fields to display - uptime and last booted time - the schema can be defined simply as a container with two fields: + +``` ++-- uptime (container) + +-- uptime (field) + +-- last booted (field) +``` + +Here is how we define the schema in our Python code: + +```python hl_lines="8" +from srlinux.schema import FixedSchemaRoot #(1)! + +class Plugin(CliPlugin): + # ... + def load(self, cli, arguments): + cli.show_mode.add_command( + syntax=self._syntax(), + schema=self._schema(), + callback=self._print, + ) + + def _schema(self): + root = FixedSchemaRoot() + root.add_child( + "uptime", + fields=[ + "Uptime", + "Last Booted", + ], + ) + return root +``` + +1. Do not forget to add a new import statement for the `FixedSchemaRoot` function. + +The creation of the schema in the `_schema` method consists of two parts: + +1. The `FixedSchemaRoot` function creates a new schema root object. +2. The `add_child` method adds a new child to the schema root. + In the `add_child` method, we can add either a list or a container element as a child. For the uptime command we don't have a use case for a list element, so we created a container named `uptime` with two fields inside of it. + +Visually this process can be depicted as follows: + +-{{ diagram(url='srl-labs/uptime-cli-plugin/main/diagrams/cli.drawio', title='Schema for the uptime command', page=1) }}- + +## Callback + +We described the syntax of the `show uptime` command and defined the schema for the data it operates on. The final task is to create the callback function - the one that gets called when the command is executed and does all the useful work. + +We provide the callback function as the third argument to the `add_command` method and it is up to us how we call it. Most often the show commands will have the callback function named `_print`, as show commands print out some data to the output. + +```python hl_lines="7" +class Plugin(CliPlugin): + # ... + def load(self, cli, arguments): + cli.show_mode.add_command( + syntax=self._syntax(), + schema=self._schema(), + callback=self._print, + ) +``` + +The signature of the callback function is rather uncomplicated: + +```python +class Plugin(CliPlugin): + # ... + def _print(self, state, output, arguments, **_kwargs): +``` + +The `state` argument gives you access to the entire state engine. We will use it later to query the state data from the SR Linux system. + +The `output` argument is a CLI output object which we will use to print out the data. + +And the `arguments` contains the arguments that were passed to the command. The one argument that we care about is the `schema` argument that we supplied to the `add_command` method a few steps ago. + +Almost every callback function of a show command performs these high-level steps: + +1. Query the state data from the SR Linux system that is necessary for the command to build the desired output. +2. Populate the data structure modelled by the schema with the data retrieved from the state engine in step 1. +3. Set output styling for different parts of the schema elements so that a composite output may be displayed in the best possible way. +4. And finally print the output to the CLI. + +Our simple `show uptime` command callback function has these steps exactly, with each method performing one of the steps. + +```python +class Plugin(CliPlugin): + # ... + def _print(self, state, output, arguments, **_kwargs): + self._fetch_state(state) + data = self._populate_data(arguments) + self._set_formatters(data) + output.print_data(data) +``` + +### Fetching state + +Our show command needs to display the uptime of the SR Linux system. To calculate the uptime, we need to find a leaf in the SR Linux state tree that contains the last booted time information. Using the last booted time, we can calculate the uptime by subtracting the last booted time from the current time. + +You can find the leaf that contains the last booted time by browsing the SR Linux state tree in the following ways: + +1. Using the CLI + + /// tab | `info flat from state` with `grep` + An efficient way to find the needed leaf if you know some keywords is to use the `info flat from state` command and pipe it to `grep`. + Since we know that we are looking for the time when the SR Linux system was last booted, we could use something like this: + + ```srl hl_lines="16" + --{ running }--[ ]-- + A:srl# tree flat from state | grep last | grep boot + platform chassis last-boot-type + platform chassis last-booted + platform chassis last-booted-reason + platform control last-booted + platform control last-booted-reason + platform fan-tray last-booted + platform fan-tray last-booted-reason + platform linecard last-booted + platform linecard last-booted-reason + platform linecard forwarding-complex last-booted + platform linecard forwarding-complex last-booted-reason + platform power-supply last-booted + platform power-supply last-booted-reason + system information last-booted + ``` + + The path that contains the last booted time is `/ system information last-booted`. + + /// + /// tab | Interactively with `enter state` + You can also interactively browse the state of the SR Linux system using the `enter state` command. + + ```srl hl_lines="11" + --{ running }--[ ]-- + A:srl# enter state + + --{ state }--[ ]-- + A:srl# system information + + --{ state }--[ system information ]-- + A:srl# info flat + / system information description "SRLinux-v24.10.2-357-ga1dd6e02b5 7220 IXR-D2L Copyright (c) 2000-2020 Nokia. Kernel 6.12.13-orbstack-00304-gede1cf3337c4 #60 SMP Wed Feb 12 20:25:12 UTC 2025" + / system information current-datetime "2025-02-17T10:58:16.579Z (now)" + / system information last-booted "2025-02-17T10:50:35.471Z (7 minutes ago)" + / system information version v24.10.2-357-ga1dd6e02b5 + ``` + + /// + +2. Using [YANG Browser](../../yang/browser.md) + SR Linux YANG Browser offers a very efficient and user-friendly way to browse the configuration and state of the SR Linux system. You may opt to use the Path Browser functionality and provide keywords, or open up the Tree Browser and explore the branches of the state tree until you find the needed leaf. + +Regardless of the way you choose to browse the state tree, you will find the path to the last booted time leaf, which is `system information last-booted`. + +Knowing the path to query, we can use it in the `_fetch_state` method like this: + +```python +from srlinux.location import build_path #(1)! + +class Plugin(CliPlugin): + # ... + def _fetch_state(self, state): + last_booted_path = build_path("/system/information/last-booted") + + try: + self._last_booted_data = state.server_data_store.get_data( + last_booted_path, recursive=False + ) + except ServerError: + self._last_booted_data = None +``` + +1. Do not forget to add a new import statement for the `build_path` function. + +Using the imported `build_path` function, we can convert the path to the object representation that SR Linux server expects. + +Then we make use of the `state` argument that we passed to the method and query the state data from the SR Linux system by providing the path object to it. + +We store the returned value (the last booted time of the system) in a private variable `_last_booted_data` as we will need it in the next steps. + +### Populating data + +Now that we acquired the last booted time information, we need to create a data structure modeled after the schema we defined earlier and fill it with the data we want to display. + +This is how we do it in the `_populate_data` method: + +```python +from srlinux.data import Data + +class Plugin(CliPlugin): + # ... + def _populate_data(self, arguments): + data = Data(schema=arguments.schema) + + uptime_container = data.uptime.create() + + uptime_container.last_booted = ( + self._last_booted_data.system.get().information.get().last_booted + ) + + uptime_container.uptime = _calculate_uptime(uptime_container.last_booted) + + return data +``` + +To create the data structure, we use the `Data` class from the `srlinux.data` module. The constructor of this class takes in a schema and returns an empty Data object that is modeled after the schema. + +Here is a reminder of the schema we defined earlier: + +-{{ diagram(url='srl-labs/uptime-cli-plugin/main/diagrams/cli.drawio', title='Schema for the uptime command', page=1) }}- + +Once we created the Data object from a schema, we need to instantiate the `uptime` container that encloses our fields. We do this with the `create` method like so: + +```python +uptime_container = data.uptime.create() +``` + +What we want now is to populate the `uptime_container` Data object fields with the values: + +1. The last booted time as fetched from the SR Linux state. +2. The uptime string value calculated as the difference between the current time and the last booted time. + +Recall, that we stored the last booted time in the `_last_booted_data` private variable of our plugin object, but it is being kept not as scalar value, but as a Data object. To get access to the value, we need to use the `get` method and traverse the data structure: + +Here is a visual aid for this process: + +-{{ diagram(url='srl-labs/uptime-cli-plugin/main/diagrams/cli.drawio', title='Accessing fields of the Data object', page=3) }}- + +Note, that when we queried the state engine for the `/system information last-booted` leaf, we got the Data object back with the `last-booted` field nested under its parent containers. Hence, we needed to traverse this path using attribute accessors to retrieve the data. The attributes we used to reach the `last-booted` leaf match the names of the containers in the YANG schema of SR Linux. + +After we got our `last-booted` value, we can use it to calculate the uptime value: + +```python +class Plugin(CliPlugin): + # ... + def _populate_data(self, arguments): + # ... + uptime_container.uptime = _calculate_uptime(uptime_container.last_booted) +``` + +The `_calculate_uptime` function we write ourselves and are free to choose the representation of the uptime value. For example, we chose to display the uptime as a human-readable string - e.g. "0 days 1 hours 16 minutes 4 seconds". + +> The function itself is irrelevant for this tutorial, feel free to check it out in the [repository](https://github.com/srl-labs/uptime-cli-plugin). + +The calculated value we write to the `uptime_container` object and in the end return the `data` object that we initialized in the beginning of this function. + +```python +from srlinux.data import Data + +class Plugin(CliPlugin): + # ... + def _populate_data(self, arguments): + data = Data(schema=arguments.schema) + # ... + return data +``` + +### Adding formatters + +Once the data object is populated with the computed and fetched data, we need to specify what format we want this data to be printed in. This is the task for the formatters. + +Formatters drive the way each container or list of the Data object is printed. There are several types of formatters that are available to the user: + +1. **Tag/value formatter** + Print the data as simple tag/value (or key/value) pairs. Typically used for simple, flat data structures. For example, `show version` uses this formatter. +2. **Table formatter** + Print the data in a tabular format. Speaks for itself, an example is `show interfaces brief`. +3. **Custom formatter** + For more elaborated output, you can define your own formatter that prints the data in a way you want while still using the schema and benefitting from the automated format conversion to various output flavors. See `show interface` command implementation for an example. + +> Keep an eye on the documentation updates; we will add a detailed guide for the custom formatters. + +```python hl_lines="8" +from srlinux.data import Border, Data, TagValueFormatter + +class Plugin(CliPlugin): + # ... + def _print(self, state, output, arguments, **_kwargs): + self._fetch_state(state) + data = self._populate_data(arguments) + self._set_formatters(data) + output.print_data(data) + + def _set_formatters(self, data): + data.set_formatter( + schema="/uptime", + formatter=Border(TagValueFormatter(), Border.Above | Border.Below), + ) +``` + +The formatters are set using the `set_formatter` method of the Data object. The first argument is the path to the schema node that we want to bind a formatter to. The second argument is the formatter object. +We will use a simple `TagValueFormatter` function that prints the data as tag/value pairs and we will wrap it with borders using the `Border` decorator. + +This will give us the desired output: + +``` +-------------------------------------------------------------------------- +Uptime : 1 days 0 hours 29 minutes 48 seconds +Last Booted: 2025-02-15T16:23:47.956Z +-------------------------------------------------------------------------- +``` + +> Note, that we apply the formatter to either a container or list element. In our case, it is the `uptime` container that we defined in our schema. + +### Printing output + +The last step is the simplest one. We wanted to print our Data object with the formatter we added earlier. All it takes is to use the `output` argument that is passed by the CLI engine to the callback function: + +```python hl_lines="7" +class Plugin(CliPlugin): + # ... + def _print(self, state, output, arguments, **_kwargs): + self._fetch_state(state) + data = self._populate_data(arguments) + self._set_formatters(data) + output.print_data(data) +``` + +And that's it! If you followed along, you will see you uptime data printed to the screen. + +## Summary + +We have created a simple CLI plugin that prints the uptime of the system and added it to the CLI as if it was a built-in command. + +/// admonition | What about the types? + type: subtle-note +You may have noticed that we did not use type hinting/annotations when explaining the project' code. This was on purpose, as we wanted to focus on the concepts. + +But we strongly suggest you use type hinting in your code, as we did in the [`upstream.py`](https://github.com/srl-labs/uptime-cli-plugin/blob/main/uptime/uptime.py) file. The non-type hinted code that we used in this tutorial is saved as [`uptime_simple.py`](https://github.com/srl-labs/uptime-cli-plugin/blob/main/uptime/uptime_simple.py) file. +/// + +Our command uses the same plugin infrastructure as SR Linux's native commands, and used the state engine to query the state of the system to get the last booted time value that is used in the uptime calculation function. + +The CLI Plugin infrastructure allows you to create commands that make operational sense to you whenever you want it, without any vendor involvement. It provides the full visibility into the system and makes it easy to get the data with all the output formats SR Linux supports - text, table, json, yaml, xml, etc. + +The `show uptime` is obviously a very simple example used for introduction purposes, you can explore [existing show commands](index.md#native-commands) to have an idea of what it takes to create a more complex and feature rich commands. + +[^1]: `uv` is a modern tool to manage python virtual environments and packages. diff --git a/docs/cli/plugins/index.md b/docs/cli/plugins/index.md new file mode 100644 index 00000000..f53a1157 --- /dev/null +++ b/docs/cli/plugins/index.md @@ -0,0 +1,116 @@ +# CLI Plugins + + + +[SR Linux CLI](../index.md) is an open-source, Python-based, and pluggable engine. This means that you can extend the CLI with your own commands whenever you need to. + +Ever wanted to create a CLI command that would make your operational life easier but is too specific to be included in the default CLI command tree? The CLI Plugins is the answer. + +The pluggable architecture of our CLI allows you to create your own CLI commands whenever you need it that use the same infrastructure as the commands SR Linux ships with. + +The Python-based CLI engine allows a user to create the custom CLI commands in the the following categories: + +* **show** commands. + These are your much-loved `show` commands that print out the state of the system in a human-readable format, often in sort of a table. +* **global** commands + These are operational commands like `ping`, `traceroute`, `file`, `bash`, etc. +* **tools** commands + Often represent a run-to-completion task or an operation. Like a reboot or a request to load a configuration from a file. + +-{{ diagram(url='srl-labs/uptime-cli-plugin/main/diagrams/cli.drawio', title='CLI engine and its plugin architecture', page=0) }}- + +> The configuration commands are not implemented as CLI plugins, they directly modify the candidate configuration datastore and are not subject to customization. + +As shown in the diagram above, the CLI plugins infrastructure is used to support both SR Linux native and custom commands. Users can add their own command simply by putting a Python file in one of the directories used in the CLI plugin discovery process. + +When SR Linux CLI is started, the available commands (native and user-defined) are loaded by the engine based on the plugin discovery process that scans the known directories for Python files implementing the `CliPlugin` interface. + +## Native commands + +The native commands, such as `show interface brief`, `diff` or `tools system configuration save`, are plugins implemented in exactly same way as the custom commands the users create. What makes them different is that they are shipped with the SR Linux image and make up the core of the CLI. + +The native commands can be found in the following directory: + +``` +/opt/srlinux/python/virtual-env/lib/python3.11/dist-packages/srlinux/mgmt/cli/plugins +``` + +The native commands are part of the `srlinux` Python package located in the virtual environment. For brevity, hereafter we will refer to the directory containing the native commands as just `/srlinux/mgmt/cli/plugins`. + +If you list the contents of this directory, you will recognize some commands that are implemented as Python files: + +```bash title="An incomplete list of native plugins" +ls -l \ +/opt/srlinux/python/virtual-env/lib/python3.11/dist-packages/srlinux/mgmt/cli/plugins +``` + +
+``` +-rw-r--r-- 1 root root 27199 Jan 23 23:35 alias.py +-rw-r--r-- 1 root root 16334 Jan 23 23:35 commit.py +-rw-r--r-- 1 root root 1495 Jan 23 23:35 echo.py +-rw-r--r-- 1 root root 4900 Jan 23 23:35 ping.py +drwxr-xr-x 1 root root 8704 Jan 24 01:01 reports +-rw-r--r-- 1 root root 4117 Jan 23 23:35 tools.py +-rw-r--r-- 1 root root 3874 Jan 23 23:35 traceroute.py +-rw-r--r-- 1 root root 7620 Jan 23 23:35 tree.py +``` +
+ +You can make an educated guess that the files in the listing are CLI plugins that implement the corresponding functionality. +The `alias.py` file implements the `alias` command. And the `tools.py` file adds support for the `tools` command. + +The only directory in the listing above - `reports`[^1] - contains plugin files that share a common trait - they all implement the relevant `show` commands: + +```title="An incomplete list of show plugins" +-rw-r--r-- 1 root root 3330 Jan 23 23:35 acl_reports.py +-rw-r--r-- 1 root root 20324 Jan 23 23:35 bgp_ipv4_exact_route_detail_report.py +-rw-r--r-- 1 root root 61888 Jan 23 23:35 bgp_neighbor_detail_report.py +-rw-r--r-- 1 root root 4251 Jan 23 23:35 interface_reports.py +-rw-r--r-- 1 root root 20812 Jan 23 23:35 ospf_interface_report.py +-rw-r--r-- 1 root root 12257 Jan 23 23:35 platform_reports.py +-rw-r--r-- 1 root root 6476 Jan 23 23:35 power_component_report.py +-rw-r--r-- 1 root root 2968 Jan 23 23:35 redundancy_report.py +-rw-r--r-- 1 root root 569 Jan 23 23:35 system.py +-rw-r--r-- 1 root root 6578 Jan 23 23:35 version.py +``` + +The `version.py` file contains the code that is called when the `show version` command is executed; the `system.py` implements the `show system` command and its subcommands, and so on. + +As a user of SR Linux, you can dive into the code of the native commands and see how they are implemented. + +/// admonition | Note + type: subtle-note +You can even tune the native commands to your liking by modifying the source code, but be aware that the changes made to the existing native commands will be overwritten when you upgrade the SR Linux image. +/// + +## User-defined commands + +The user-defined (aka custom) commands are implemented in the same way as the native commands. The only difference is that they are not part of the SR Linux image, but are created by the user. + +There are two paths where the user-defined commands can be located: + +1. **System-wide user commands** + When a plugin is installed in the the following directory, it is available system-wide: + + ``` + /etc/opt/srlinux/cli/plugins + ``` + + The commands implemented by the plugins in this directory are available to all users of the SR Linux system[^2]. + +2. **Per-user commands** + To make a command available to a specific user, the plugin file should be placed in the user's home directory: + + ``` + /home//cli/plugins + ``` + +The user-defined commands (both with system-wide and per-user scope) are kept intact during the SR Linux image upgrade. Hence, it is important to make sure that the custom plugins are stored in the appropriate directory. + +So, what it takes to create a new command? How to setup the environment for the plugin development? And how to test the command? Let us take you on a quick tour of the plugin development process outlined in the Getting Started section. + +:octicons-arrow-right-24: [Getting started](getting-started.md) + +[^1]: full path to the directory - `/opt/srlinux/python/virtual-env/lib/python3.11/dist-packages/srlinux/mgmt/cli/plugins/reports` +[^2]: plugin access can be narrowed down using the plugin authorization AAA mechanism diff --git a/docs/get-started/cli.md b/docs/get-started/cli.md index 8bd8f061..54ca3e20 100644 --- a/docs/get-started/cli.md +++ b/docs/get-started/cli.md @@ -1,5 +1,7 @@ --- comments: true +tags: + - cli --- # SR Linux CLI diff --git a/docs/tutorials/programmability/json-rpc/basics.md b/docs/tutorials/programmability/json-rpc/basics.md index 271b4d53..6f51f2b3 100644 --- a/docs/tutorials/programmability/json-rpc/basics.md +++ b/docs/tutorials/programmability/json-rpc/basics.md @@ -16,7 +16,7 @@ title: JSON-RPC Basics | **Main ref documents** | [JSON-RPC Configuration][json-cfg-guide], [JSON-RPC Management][json-mgmt-guide] | | **Version information**[^1] | [`srlinux:23.10.1`][srlinux-container], [`containerlab:0.48.6`][clab-install] | | **Authors** | Roman Dodin [:material-twitter:][rd-twitter] [:material-linkedin:][rd-linkedin] | -| **Discussions** | [:material-twitter: Twitter][twitter-share] ยท [:material-linkedin: LinkedIn][linkedin-share] | +| **Discussions** | [:material-twitter: Twitter][twitter-share] | [rd-linkedin]: https://linkedin.com/in/rdodin [rd-twitter]: https://twitter.com/ntdvps @@ -26,7 +26,6 @@ title: JSON-RPC Basics [srlinux-container]: https://github.com/nokia/srlinux-container-image [clab-install]: https://containerlab.dev/install/ [twitter-share]: https://twitter.com/ntdvps/status/1600261024719917057 -[linkedin-share]: https://www.linkedin.com/feed/update/urn:li:activity:7006028123045548033/ As of release 23.10.1, Nokia SR Linux Network OS employs three fully modeled management interfaces: diff --git a/mkdocs.yml b/mkdocs.yml index 4dc82ad3..525cf5c6 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -28,6 +28,7 @@ nav: - Mirroring: cli/show-commands/mirroring.md - DHCP Relay: cli/show-commands/dhcp-relay.md - TACACS: cli/show-commands/tacacs.md + - Wildcards and ranges: blog/posts/2023/cli-ranges.md - SNMP: - SNMP Framework: snmp/snmp_framework.md @@ -92,6 +93,9 @@ nav: - config: ansible/collection/config.md - validate: ansible/collection/validate.md - cli: ansible/collection/cli.md + - CLI Plugins: + - cli/plugins/index.md + - Getting started: cli/plugins/getting-started.md - Tutorials: # - SR Linux tutorials: tutorials/about.md - tutorials/index.md @@ -161,7 +165,7 @@ repo_name: srl-labs/learn-srlinux repo_url: https://github.com/srl-labs/learn-srlinux edit_uri: edit/main/docs/ site_url: "https://learn.srlinux.dev/" -copyright: Copyright © 2021-2023 Nokia +copyright: Copyright © 2021-2025 Nokia theme: name: material custom_dir: docs/overrides