mirror of
https://github.com/kernelkit/infix.git
synced 2026-07-30 04:33:00 +02:00
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:
committed by
Joachim Wiberg
parent
1ea658ad1d
commit
9e48fb09f6
Executable
+202
@@ -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
|
||||
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user