cli: rewrite interface pretty printer in phyton3

The old version (bash) where slow and cluttered. This version is much
faster and easier to read / maintain.

Signed-off-by: Richard Alpe <richard@bit42.se>
This commit is contained in:
Richard Alpe
2023-09-06 16:11:59 +02:00
committed by Joachim Wiberg
parent 1ea658ad1d
commit 9e48fb09f6
3 changed files with 208 additions and 129 deletions
+202
View File
@@ -0,0 +1,202 @@
#!/usr/bin/env python3
import json
import sys
import argparse
parser = argparse.ArgumentParser(description="JSON CLI Pretty Printer")
parser.add_argument("module", help="IETF Module")
parser.add_argument("-n", "--name", help="Focus on specific name")
args = parser.parse_args()
class Pad:
iface = 15 + 2 # + 2 is ASCII tree for nesting
proto = 18
state = 15
data = 30
class Decore():
@staticmethod
def decorate(sgr, txt, restore="0"):
return f"\033[{sgr}m{txt}\033[{restore}m"
@staticmethod
def invert(txt):
return Decore.decorate("7", txt)
@staticmethod
def red(txt):
return Decore.decorate("31", txt, "39")
@staticmethod
def green(txt):
return Decore.decorate("32", txt, "39")
class Iface:
def __init__(self, data):
self.data = data
self.name = data.get('name', '')
self.index = data.get('if-index', None)
self.oper_status = data.get('oper-status', None)
self.phys_address = data.get('phys-address', None)
self.parent = data.get('ietf-if-extensions:parent-interface', None)
if self.data.get('ietf-ip:ipv4'):
self.mtu = self.data.get('ietf-ip:ipv4').get('mtu', None)
self.ipv4_addr = self.data.get('ietf-ip:ipv4').get('address', None)
else:
self.mtu = None
self.ipv4_addr = []
if self.data.get('infix-interfaces:bridge-port'):
self.bridge = self.data.get('infix-interfaces:bridge-port').get('bridge', None)
else:
self.bridge = None
def is_vlan(self):
return self.data['type'] == "iana-if-type:l2vlan"
def is_etherent(self):
return self.data['type'] == "iana-if-type:ethernetCsmacd"
def is_bridge(self):
return self.data['type'] == "iana-if-type:bridge"
def pr_name(self, pipe=""):
print(f"{pipe}{self.name:<{Pad.iface - len(pipe)}}", end="")
def pr_proto_ipv4(self, pipe=''):
addr = self.data.get("ietf-ip:ipv4")
if not addr:
return
addresses = self.data.get("ietf-ip:ipv4", {}).get("address", [])
for addr in addresses:
row = f"{pipe:<{Pad.iface}}"
row += f"{'ipv4':<{Pad.proto}}"
row += f"{'':<{Pad.state}}{addr['ip']}/{addr['prefix-length']}"
print(row)
def pr_proto_eth(self):
row = f"{'ethernet':<{Pad.proto}}"
dec = Decore.green if self.data['oper-status'] == "up" else Decore.red
row += dec(f"{self.data['oper-status'].upper():<{Pad.state}}")
row += f"{self.data['phys-address']:<{Pad.data}}"
print(row)
def pr_bridge(self, _ifaces):
self.pr_name(pipe="")
self.pr_proto_eth()
lowers = []
for _iface in [Iface(data) for data in _ifaces]:
if _iface.bridge and _iface.bridge == self.name:
lowers.append(_iface)
if lowers:
self.pr_proto_ipv4(pipe='│')
else:
self.pr_proto_ipv4()
for i, lower in enumerate(lowers):
pipe = '└ ' if (i == len(lowers) -1) else '├ '
lower.pr_name(pipe)
lower.pr_proto_eth()
def pr_vlan(self, _ifaces):
self.pr_name(pipe="")
self.pr_proto_eth()
if self.parent:
self.pr_proto_ipv4(pipe='│')
else:
self.pr_proto_ipv4()
return
parent = find_iface(_ifaces, self.parent)
if not parent:
print(f"Error, didn't find parent interface for vlan {self.name}")
sys.exit(1)
parent.pr_name(pipe='└ ')
parent.pr_proto_eth()
def pr_iface(self):
print(f"{'name':<{20}}: {self.name}")
if self.index:
print(f"{'index':<{20}}: {self.index}")
if self.mtu:
print(f"{'mtu':<{20}}: {self.mtu}")
if self.oper_status:
print(f"{'operational status':<{20}}: {self.oper_status}")
if self.phys_address:
print(f"{'physical address':<{20}}: {self.phys_address}")
if self.ipv4_addr:
first = True
print("")
for addr in self.ipv4_addr:
key = 'ipv4 addresses' if first else ''
print(f"{key:<{20}}: {addr['ip']}/{addr['prefix-length']}")
first = False
def find_iface(_ifaces, name):
for _iface in [Iface(data) for data in _ifaces]:
if _iface.name == name:
return _iface
return False
def pr_interface_list(json):
hdr = (f"{'INTERFACE':<{Pad.iface}}"
f"{'PROTOCOL':<{Pad.proto}}"
f"{'STATE':<{Pad.state}}"
f"{'DATA':<{Pad.data}}")
print(Decore.invert(hdr))
ifaces = sorted(json["ietf-interfaces:interfaces"]["interface"], key=lambda x: x['name'])
for iface in [Iface(data) for data in ifaces]:
if iface.is_bridge():
iface.pr_bridge(ifaces)
continue
if iface.is_vlan():
iface.pr_vlan(ifaces)
continue
# These interfaces are printed by there parent, such as bridge
if iface.parent:
continue
if iface.bridge:
continue
iface.pr_name()
iface.pr_proto_eth()
iface.pr_proto_ipv4()
def ietf_interfaces(json, name):
if not json or not json.get("ietf-interfaces:interfaces"):
print(f"Error, top level \"ietf-interfaces:interfaces\" missing")
sys.exit(1)
if not name:
return pr_interface_list(json)
iface = find_iface(json["ietf-interfaces:interfaces"]["interface"], name)
if not iface:
print(f"Interface {name} not found")
sys.exit(1)
return iface.pr_iface()
json = json.load(sys.stdin)
if args.module == "ietf-interfaces":
sys.exit(ietf_interfaces(json, args.name))
else:
print(f"Error, unknown module {args.module}")
sys.exit(1)
@@ -1,127 +0,0 @@
#!/bin/bash
CYAN='\033[0;36m' # Used in headers
RED='\033[0;31m'
GREEN='\033[0;32m'
NC='\033[0m' # No Color
GREY_BG="\e[48;5;235m"
RESET="\e[0m"
HEADER_WIDHT=80
declare -A IETF_TYPE_MAP
IETF_TYPE_MAP["iana-if-type:softwareLoopback"]="loopback"
IETF_TYPE_MAP["iana-if-type:ethernetCsmacd"]="ethernet"
IETF_TYPE_MAP["iana-if-type:l2vlan"]="vlan"
IETF_TYPE_MAP["infix-if-type:veth"]="veth"
IETF_TYPE_MAP["iana-if-type:bridge"]="bridge"
usage() {
printf "Please provide a valid base field as the first argument\n"
exit 1
}
if [ -z "$1" ]; then
usage
fi
json=$(cat)
show_ietf_interface() {
name="$1"
shift
iface=$(echo "$json" | jq --arg name "$name" \
'.["ietf-interfaces:interfaces"].interface[] | select(.name == $name)')
if [ -z "$iface" ]; then
printf "Error, interface named \"$name\" not found"
exit 1
fi
printf "%-20s : %s\n" "name" "$name"
printf "%-20s : %s\n" "interface-index" "$(echo "$iface" | jq -r '.["if-index"]')"
printf "%-20s : %s\n" "operational-status" "$(echo "$iface" | jq -r '.["oper-status"]')"
printf "%-20s : %s\n" "physical-address" "$(echo "$iface" | jq -r '.["phys-address"]')"
# MTU isn't set for loopback
mtu="$(echo "$iface" | jq -r '.["ietf-ip:ipv4"] | select(.mtu != null) | .mtu')"
if [ -n "$mtu" ]; then
printf "%-20s : %s\n" "mtu" "$mtu"
fi
printf "\n${CYAN}%-5s${NC}\n" "ipv4:"
addresses=$(echo "$iface" | jq -c --arg field "$field" '.["ietf-ip:ipv4"].address[]' 2>/dev/null)
for addr in $addresses; do
ip="$(echo "$addr" | jq -r '."ip"')"
prefix="$(echo "$addr" | jq -r '."prefix-length"')"
printf "%-5s %s/%s\n" "" "$ip" "$prefix"
done
printf "\n${CYAN}%-5s %5s\n${NC}" "in:" "octets"
printf "%-5s %s\n" "" "$(echo "$iface" | jq -r '.statistics["in-octets"]')"
printf "\n${CYAN}%-5s %5s${NC}\n" "out:" "octets"
printf "%-5s %s\n" "" "$(echo "$iface" | jq -r '.statistics["out-octets"]')"
}
show_ietf_interfaces() {
field="ietf-interfaces:interfaces"
if [ $# -ne 0 ]; then
show_ietf_interface "$1"
return
fi
interfaces=$(echo "$json" | jq -c --arg field "$field" '.[$field].interface[]')
HEADER=$(printf "%-15s %-14s %-25s %s\n" "INTERFACE" "STATE" "PROTOCOL/ADDRESS" "SOURCE")
HEADER_LEN=${#HEADER}
RIGHT_PADDING=$(( HEADER_WIDHT - HEADER_LEN ))
printf "${GREY_BG}%s%${RIGHT_PADDING}s${RESET}\n" "$HEADER" ""
for iface in $interfaces; do
name="$(echo "$iface" | jq -r '.["name"]')"
state="$(echo "$iface" | jq -r '.["oper-status"]')"
mac="$(echo "$iface" | jq -r '.["phys-address"]')"
type="$(echo "$iface" | jq -r '.["type"]')"
state_color=""
if [[ -v IETF_TYPE_MAP["$type"] ]]; then
src="${IETF_TYPE_MAP["$type"]}"
else
src="unknown"
fi
if [ "$state" = "up" ]; then
state_color="$GREEN"
elif [ "$state" = "down" ]; then
state_color="$RED"
fi
printf "%-15s ${state_color}%-15s${NC}%s" "$name" "$state"
addresses=$(echo "$iface" | jq -c --arg field "$field" '.["ietf-ip:ipv4"].address[]' 2>/dev/null)
printf "%-25s %s\n" "$mac" "$src"
for addr in $addresses; do
ip="$(echo "$addr" | jq -r '."ip"')"
prefix="$(echo "$addr" | jq -r '."prefix-length"')"
printf "%-30s %s/%s\n" "" "$ip" "$prefix"
done
if [ -n "$addresses" ]; then
printf "\n"
fi
done
}
field="$1"
shift
case "$field" in
"ietf-interfaces")
show_ietf_interfaces $*
;;
*)
usage
;;
esac
+6 -2
View File
@@ -239,9 +239,13 @@
</COMMAND>
</SWITCH>
<ACTION sym="script" interactive="false">
name="${KLISH_PARAM_name:-}"
if [ -n "$KLISH_PARAM_name" ]; then
sysrepocfg -f json -X -d operational -m ietf-interfaces | \
/lib/infix/json-cfg-pretty "ietf-interfaces" "$name"
/lib/infix/cli-pretty "ietf-interfaces" -n "$KLISH_PARAM_name"
else
sysrepocfg -f json -X -d operational -m ietf-interfaces | \
/lib/infix/cli-pretty "ietf-interfaces"
fi
</ACTION>
</COMMAND>