added brk2tmuxp.py script (#20)

This script provides a way to turn the CML breakout labs config into tmux configuration for easy console access.
This commit is contained in:
sgherdao
2022-05-05 21:48:21 +01:00
committed by GitHub
parent 7451c5ae7d
commit b636b84985
7 changed files with 487 additions and 0 deletions

14
scripts/brk2tmuxp/Pipfile Normal file
View File

@@ -0,0 +1,14 @@
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"
[packages]
pyyaml = "*"
[dev-packages]
mypy = "*"
types-pyyaml = "*"
[requires]
python_version = "3.9"

121
scripts/brk2tmuxp/Pipfile.lock generated Normal file
View File

@@ -0,0 +1,121 @@
{
"_meta": {
"hash": {
"sha256": "2924ea98850621a19a9fdc0e93c8dec4fdf2af78648276c0a2f0ae8cb8994f97"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.9"
},
"sources": [
{
"name": "pypi",
"url": "https://pypi.org/simple",
"verify_ssl": true
}
]
},
"default": {
"pyyaml": {
"hashes": [
"sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293",
"sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b",
"sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57",
"sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b",
"sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4",
"sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07",
"sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba",
"sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9",
"sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287",
"sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513",
"sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0",
"sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0",
"sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92",
"sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f",
"sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2",
"sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc",
"sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c",
"sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86",
"sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4",
"sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c",
"sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34",
"sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b",
"sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c",
"sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb",
"sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737",
"sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3",
"sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d",
"sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53",
"sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78",
"sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803",
"sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a",
"sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174",
"sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"
],
"index": "pypi",
"version": "==6.0"
}
},
"develop": {
"mypy": {
"hashes": [
"sha256:0112752a6ff07230f9ec2f71b0d3d4e088a910fdce454fdb6553e83ed0eced7d",
"sha256:0384d9f3af49837baa92f559d3fa673e6d2652a16550a9ee07fc08c736f5e6f8",
"sha256:1b333cfbca1762ff15808a0ef4f71b5d3eed8528b23ea1c3fb50543c867d68de",
"sha256:1fdeb0a0f64f2a874a4c1f5271f06e40e1e9779bf55f9567f149466fc7a55038",
"sha256:4c653e4846f287051599ed8f4b3c044b80e540e88feec76b11044ddc5612ffed",
"sha256:563514c7dc504698fb66bb1cf897657a173a496406f1866afae73ab5b3cdb334",
"sha256:5b231afd6a6e951381b9ef09a1223b1feabe13625388db48a8690f8daa9b71ff",
"sha256:5ce6a09042b6da16d773d2110e44f169683d8cc8687e79ec6d1181a72cb028d2",
"sha256:5e7647df0f8fc947388e6251d728189cfadb3b1e558407f93254e35abc026e22",
"sha256:6003de687c13196e8a1243a5e4bcce617d79b88f83ee6625437e335d89dfebe2",
"sha256:61504b9a5ae166ba5ecfed9e93357fd51aa693d3d434b582a925338a2ff57fd2",
"sha256:77423570c04aca807508a492037abbd72b12a1fb25a385847d191cd50b2c9605",
"sha256:a4d9898f46446bfb6405383b57b96737dcfd0a7f25b748e78ef3e8c576bba3cb",
"sha256:a952b8bc0ae278fc6316e6384f67bb9a396eb30aced6ad034d3a76120ebcc519",
"sha256:b5b5bd0ffb11b4aba2bb6d31b8643902c48f990cc92fda4e21afac658044f0c0",
"sha256:ca75ecf2783395ca3016a5e455cb322ba26b6d33b4b413fcdedfc632e67941dc",
"sha256:cf9c261958a769a3bd38c3e133801ebcd284ffb734ea12d01457cb09eacf7d7b",
"sha256:dd4d670eee9610bf61c25c940e9ade2d0ed05eb44227275cce88701fee014b1f",
"sha256:e19736af56947addedce4674c0971e5dceef1b5ec7d667fe86bcd2b07f8f9075",
"sha256:eaea21d150fb26d7b4856766e7addcf929119dd19fc832b22e71d942835201ef",
"sha256:eaff8156016487c1af5ffa5304c3e3fd183edcb412f3e9c72db349faf3f6e0eb",
"sha256:ee0a36edd332ed2c5208565ae6e3a7afc0eabb53f5327e281f2ef03a6bc7687a",
"sha256:ef7beb2a3582eb7a9f37beaf38a28acfd801988cde688760aea9e6cc4832b10b"
],
"index": "pypi",
"version": "==0.950"
},
"mypy-extensions": {
"hashes": [
"sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d",
"sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"
],
"version": "==0.4.3"
},
"tomli": {
"hashes": [
"sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc",
"sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"
],
"markers": "python_version < '3.11'",
"version": "==2.0.1"
},
"types-pyyaml": {
"hashes": [
"sha256:59480cf44595d836aaae050f35e3c39f197f3a833679ef3978d97aa9f2fb7def",
"sha256:7b273a34f32af9910cf9405728c9d2ad3afc4be63e4048091a1a73d76681fe67"
],
"index": "pypi",
"version": "==6.0.7"
},
"typing-extensions": {
"hashes": [
"sha256:6657594ee297170d19f67d55c05852a874e7eb634f4f753dbd667855e07c1708",
"sha256:f1c24655a0da0d1b67f07e17a5e6b2a105894e6824b92096378bb3668ef02376"
],
"markers": "python_version >= '3.7'",
"version": "==4.2.0"
}
}
}

151
scripts/brk2tmuxp/README.md Normal file
View File

@@ -0,0 +1,151 @@
## CML breakout to tmuxp
The script reads the CML breakout `labs.yaml` and generates [tmuxp](https://github.com/tmux-python/tmuxp) sessions.
Both YAML and JSON format are supported (default to YAML).
The sessions can then be loaded with `tmuxp load [labs]` and will perform the following:
The `windows` variant (the default) will:
1. create a tmux session named after the lab title
2. the first window created will run `breakout run` for that particular lab
3. for each node, open a node-named new window and telnet to the node
the `panes` variant will:
1. create a tmux session named after the lab title
2. the first window created will run `breakout run` for that particular lab
3. the second window will open a pane per node
<p align="right">(<a href="#top">back to top</a>)</p>
## Getting Started
### Prerequisites
Use your favorite package manager to install `tmux` and `tmuxp`, for example:
- tmux
```sh
brew install tmux
```
```sh
apt install tmux
```
- tmuxp
```sh
brew install tmuxp
```
```sh
apt install tmuxp
```
### Installation
Clone the repository, cd in the directory and install the requirements:
- pipenv
```sh
pipenv install
```
- pip
```sh
python3 -m venv /path/to/directory
pip install -r requirements
```
Note: the only dependency is `pyyaml`.
## Usage
The following options are available:
```sh
python brk2tmuxp.py -h
usage: brk2tmuxp.py [-h] [-d BRK_DIR] [-f YAML_FILE] [-p] [-l LISTEN_ADDR] [-s SLEEP] [-j]
reads a CML breakout labs.yaml and generates a tmuxp session files
optional arguments:
-h, --help show this help message and exit
-d BRK_DIR, --brk-dir BRK_DIR
path to dir containing the breakout labs YAML files (default:: current directory)
-f YAML_FILE, --yaml-file YAML_FILE
name of the 'labs' YAML file, (default to labs.yaml)
-p, --panes if set, all telnet sessions will be in one window (default: each telnet session has its own window)
-l LISTEN_ADDR, --listen_addr LISTEN_ADDR
specify the listen address (default: ::1)
-s SLEEP, --sleep SLEEP
sleep time (in seconds) before initiating telnet session (default: 3)
-j, --format-json output JSON tmuxp session files (default: YAML)
```
### Use `breakout init` to fetch the labs and nodes from the controller:
```sh
~/CML via 🐍 v3.9.12 (brk2tmuxp)
breakout init
get simplified node definitions from controller...
get active console keys from controller...
get active VNC keys from controller...
get all the labs from controller...
get all the nodes for the labs from controller...
get nodes for lab L2L IKEv2 from controller...
get nodes for lab cisco_isis_sr_101_v1 from controller...
config written.
~/CML via 🐍 v3.9.12 (brk2tmuxp)
ls -l
.rwxrwxrwx 746 sgherdao 11 Mar 21:04 config.yaml
.rwxrwxrwx 1.3k sgherdao 3 May 21:50 labs.yaml
```
### Generate the "windows" variant and load it with `tmuxp`:
```sh
~/CML via 🐍 v3.9.12 (brk2tmuxp)
python brk2tmuxp.py
~/CML via 🐍 v3.9.12 (brk2tmuxp)
ls -l
.rw-r--r-- 513 sgherdao 3 May 21:55 1eaf2c3b-9207-4524-9e7c-3eeaada67886.yaml
.rwxrwxrwx 746 sgherdao 11 Mar 21:04 config.yaml
.rw-r--r-- 883 sgherdao 3 May 22:04 d231681f-a88c-4004-884a-4c9639ad8b07.yaml
.rwxrwxrwx 4.3k sgherdao 3 May 22:01 labs.yaml
~/CML via 🐍 v3.9.12 (brk2tmuxp) took 3s
tmuxp load d231681f-a88c-4004-884a-4c9639ad8b07.yaml
[Loading] CML/d231681f-a88c-4004-884a-4c9639ad8b07.yaml
Already inside TMUX, switch to session? yes/no
Or (a)ppend windows in the current active session?
[y/n/a]: y
```
![windows-01](windows-01.jpg)
### Generate the "panes" variant and load it with `tmuxp`:
```sh
~/CML via 🐍 v3.9.12 (brk2tmuxp)
python PythonTemp/brk2tmuxp/brk2tmuxp.py -p
~/CML via 🐍 v3.9.12 (brk2tmuxp)
tmuxp load 1eaf2c3b-9207-4524-9e7c-3eeaada67886.yaml
[Loading] CML/1eaf2c3b-9207-4524-9e7c-3eeaada67886.yaml
Already inside TMUX, switch to session? yes/no
Or (a)ppend windows in the current active session?
[y/n/a]: y
```
![panes-01](panes-01.jpg)
<p align="right">(<a href="#top">back to top</a>)</p>

View File

@@ -0,0 +1,200 @@
#!/usr/bin/env python3
import argparse
import json
import sys
import yaml
def main() -> int:
"""
reads a CML breakout labs.yaml file and returns a list of dict ready to be
translated into multiple JSON or YAML tmuxp session files.
By default, each telnet session will have its own window but if the `-p` or
`--panes` flag is set, then each telnet session will be in a pane in one
(and only one) window.
VNC sessions are ignored.
"""
parser = argparse.ArgumentParser(
description="reads a CML breakout labs.yaml and generates a tmuxp session files"
)
parser.add_argument(
"-d",
"--brk-dir",
type=str,
default=".",
help="path to dir containing the breakout labs YAML files (default:: current directory)",
)
parser.add_argument(
"-f",
"--yaml-file",
type=str,
default="labs.yaml",
help="name of the 'labs' YAML file, (default to labs.yaml)",
)
parser.add_argument(
"-p",
"--panes",
help="if set, all telnet sessions will be in one window (default: each telnet session has its own window)",
action="store_true",
)
parser.add_argument(
"-l",
"--listen_addr",
default="::1",
help="specify the listen address (default: ::1)",
)
parser.add_argument(
"-s",
"--sleep",
type=float,
default="3",
help="sleep time (in seconds) before initiating telnet session (default: 3)",
)
parser.add_argument(
"-j",
"--format-json",
action="store_true",
help="output JSON tmuxp session files (default: YAML)",
)
args = parser.parse_args()
# load labs yaml file
brk_dir = args.brk_dir
yaml_file = args.yaml_file
with open(f"{brk_dir}/{yaml_file}") as f:
labs = yaml.safe_load(f)
panes = args.panes
listen_addr = args.listen_addr
sleep = args.sleep
format_json = args.format_json
tmux_sessions = {}
for uuid, lab in labs.items():
if panes:
tmux_sessions[uuid] = panes_configs(
lab=lab, brk_dir=brk_dir, listen_addr=listen_addr, sleep=sleep
)
else:
tmux_sessions[uuid] = windows_configs(
lab=lab, brk_dir=brk_dir, listen_addr=listen_addr, sleep=sleep
)
for uuid, tmux_session in tmux_sessions.items():
if format_json:
with open(f"{uuid}.json", "w") as f:
json.dump(tmux_session, f, indent=2)
else:
with open(f"{uuid}.yaml", "w") as f:
yaml.dump(tmux_session, f)
return 0
def panes_configs(lab: dict, brk_dir: str, listen_addr: str, sleep: float) -> dict:
"""
returns a dict representing a tmuxp session file for one CML lab
This is the 'panes' variant, the tmuxp session will consist of:
- one session named based on the lab title containing:
- the first window will cd into the CML dir and launch `breakout run`
for that lab
- subsequent windows, will telnet to one lab node
"""
lab_title = lab["lab_title"]
conf = {
"session_name": lab_title,
"windows": [
{
"window_name": "breakout",
"panes": [
{
"shell_command": [
f"cd {brk_dir}",
f"breakout run '{lab_title}'",
]
}
],
},
{"window_name": "nodes", "layout": "tiled"},
],
}
panes: list = []
for _, node in lab["nodes"].items():
node_label = node["label"]
panes.extend(
{
"shell_command":
# dirty way to set tmux pane title
# see https://github.com/tmux-python/tmuxp/issues/384
[
f"printf '\\033]2;%s\\033\\\\' '{node_label}/{n['name'][-1]}'",
f"time sleep {sleep}",
f"telnet {listen_addr} {n['listen_port']}",
]
}
for n in node["devices"]
if n["enabled"] and n["name"] != "vnc"
)
conf["windows"][1]["panes"] = panes
return conf
def windows_configs(lab: dict, brk_dir: str, listen_addr: str, sleep: int) -> dict:
"""
returns a dict representing a tmuxp session file for one CML lab
This is the 'windows' variant, the tmuxp session will consist of:
- one session named based on the lab title containing:
- the first window will launch `breakout run` for that lab
- next windows, will telnet to one lab node
"""
lab_title = lab["lab_title"]
conf = {
"session_name": lab_title,
"windows": [
{
"window_name": "breakout",
"panes": [
{
"shell_command": [
f"cd {brk_dir}",
f"breakout run '{lab_title}'",
]
}
],
},
],
}
for _, node in lab["nodes"].items():
node_label = node["label"]
conf["windows"].extend(
{
"window_name": f"{node_label}/{n['name'][-1]}",
"panes": [
{
"shell_command": [
f"time sleep {sleep}",
f"telnet {listen_addr} {n['listen_port']}",
]
}
],
}
for n in node["devices"]
if n["enabled"] and n["name"] != "vnc"
)
return conf
if __name__ == "__main__":
sys.exit(main())

Binary file not shown.

After

Width:  |  Height:  |  Size: 557 KiB

View File

@@ -0,0 +1 @@
pyyaml

Binary file not shown.

After

Width:  |  Height:  |  Size: 652 KiB