File: //proc/self/root/usr/share/netplan/netplan_cli/cli/commands/status.py
#!/usr/bin/python3
#
# Copyright (C) 2022 Canonical, Ltd.
# Author: Lukas Märdian <slyon@ubuntu.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
'''netplan status command line'''
import json
import logging
import re
import yaml
from .. import utils
from ..state import SystemConfigState, JSON
MATCH_TAGS = re.compile(r'\[([a-z0-9]+)\].*\[\/\1\]')
RICH_OUTPUT = False
try:
from rich.console import Console
from rich.highlighter import RegexHighlighter
from rich.theme import Theme
class NetplanHighlighter(RegexHighlighter):
base_style = 'netplan.'
highlights = [
r'(^|[\s\/])(?P<int>\d+)([\s:]?\s|$)',
r'(?P<str>(\"|\').+(\"|\'))',
]
RICH_OUTPUT = True
except ImportError: # pragma: nocover (we mock RICH_OUTPUT, ignore the logging)
logging.debug("python3-rich not found, falling back to plain output")
class NetplanStatus(utils.NetplanCommand):
def __init__(self):
super().__init__(command_id='status',
description='Query networking state of the running system',
leaf=True)
self.all = False
def run(self):
self.parser.add_argument('ifname', nargs='?', type=str, default=None,
help='Show only this interface')
self.parser.add_argument('-a', '--all', action='store_true',
help='Show all interface data (incl. inactive)')
self.parser.add_argument('-v', '--verbose', action='store_true',
help='Show extra information')
self.parser.add_argument('-f', '--format', default='tabular',
help='Output in machine readable `json` or `yaml` format')
self.func = self.command
self.parse_args()
self.run_command()
def plain_print(self, *args, **kwargs):
if len(args):
lst = list(args)
for tag in MATCH_TAGS.findall(lst[0]):
# remove matching opening and closing tag
lst[0] = lst[0].replace('[{}]'.format(tag), '')\
.replace('[/{}]'.format(tag), '')
return print(*lst, **kwargs)
return print(*args, **kwargs)
def pretty_print(self, data: JSON, total: int, _console_width=None) -> None:
if RICH_OUTPUT:
# TODO: Use a proper (subiquity?) color palette
theme = Theme({
'netplan.int': 'bold cyan',
'netplan.str': 'yellow',
'muted': 'grey62',
'online': 'green bold',
'offline': 'red bold',
'unknown': 'yellow bold',
'highlight': 'bold'
})
console = Console(highlighter=NetplanHighlighter(), theme=theme,
width=_console_width, emoji=False)
pprint = console.print
else:
pprint = self.plain_print
pad = '18'
global_state = data.get('netplan-global-state', {})
interfaces = [(key, data[key]) for key in data if key != 'netplan-global-state']
# Global state
pprint(('{title:>'+pad+'} {value}').format(
title='Online state:',
value='[online]online[/online]' if global_state.get('online', False) else '[offline]offline[/offline]',
))
ns = global_state.get('nameservers', {})
dns_addr: list = ns.get('addresses', [])
dns_mode: str = ns.get('mode')
dns_search: list = ns.get('search', [])
if dns_addr:
for i, val in enumerate(dns_addr):
pprint(('{title:>'+pad+'} {value}[muted]{mode}[/muted]').format(
title='DNS Addresses:' if i == 0 else '',
value=val,
mode=' ({})'.format(dns_mode) if dns_mode else '',
))
if dns_search:
for i, val in enumerate(dns_search):
pprint(('{title:>'+pad+'} {value}').format(
title='DNS Search:' if i == 0 else '',
value=val,
))
pprint()
# Per interface
for (ifname, data) in interfaces:
state = data.get('operstate', 'UNKNOWN') + '/' + data.get('adminstate', 'UNKNOWN')
scolor = 'unknown'
if state == 'UP/UP':
state = 'UP'
scolor = 'online'
elif state == 'DOWN/DOWN':
state = 'DOWN'
scolor = 'offline'
full_type = data.get('type', 'other')
ssid = data.get('ssid')
tunnel_mode = data.get('tunnel_mode')
if full_type == 'wifi' and ssid:
full_type += ('/"' + ssid + '"')
elif full_type == 'tunnel' and tunnel_mode:
full_type += ('/' + tunnel_mode)
pprint('[{col}]●[/{col}] {idx:>2}: {name} {type} [{col}]{state}[/{col}] ({backend}{netdef})'.format(
col=scolor,
idx=data.get('index', '?'),
name=ifname,
type=full_type,
state=state,
backend=data.get('backend', 'unmanaged'),
netdef=': [highlight]{}[/highlight]'.format(data.get('id')) if data.get('id') else ''
))
if data.get('macaddress'):
pprint(('{title:>'+pad+'} {mac}[muted]{vendor}[/muted]').format(
title='MAC Address:',
mac=data.get('macaddress', ''),
vendor=' ({})'.format(data.get('vendor', '')) if data.get('vendor') else '',
))
lst: list = data.get('addresses', [])
if lst:
for i, obj in enumerate(lst):
ip, extra = list(obj.items())[0] # get first (any only) address
flags = []
if extra.get('flags'): # flags
flags = extra.get('flags', [])
highlight_start = ''
highlight_end = ''
if not flags or 'dhcp' in flags:
highlight_start = '[highlight]'
highlight_end = '[/highlight]'
pprint(('{title:>'+pad+'} {start}{ip}/{prefix}{end}[muted]{extra}[/muted]').format(
title='Addresses:' if i == 0 else '',
ip=ip,
prefix=extra.get('prefix', ''),
extra=' ('+', '.join(flags)+')' if flags else '',
start=highlight_start,
end=highlight_end,
))
lst = data.get('dns_addresses', [])
if lst:
for i, val in enumerate(lst):
pprint(('{title:>'+pad+'} {value}').format(
title='DNS Addresses:' if i == 0 else '',
value=val,
))
lst = data.get('dns_search', [])
if lst:
for i, val in enumerate(lst):
pprint(('{title:>'+pad+'} {value}').format(
title='DNS Search:' if i == 0 else '',
value=val,
))
lst = data.get('routes', [])
if lst:
for i, obj in enumerate(lst):
if not self.verbose and obj.get('table') != 'main':
continue
default_start = ''
default_end = ''
if obj['to'] == 'default':
default_start = '[highlight]'
default_end = '[/highlight]'
via = ''
if 'via' in obj:
via = ' via ' + obj['via']
src = ''
if 'from' in obj:
src = ' from ' + obj['from']
metric = ''
if 'metric' in obj:
metric = ' metric ' + str(obj['metric'])
table = ''
if self.verbose and 'table' in obj:
table = ' table ' + str(obj['table'])
extra = []
if 'protocol' in obj and obj['protocol'] != 'kernel':
proto = obj['protocol']
extra.append(proto)
if 'scope' in obj and obj['scope'] != 'global':
scope = obj['scope']
extra.append(scope)
if 'type' in obj and obj['type'] != 'unicast':
type = obj['type']
extra.append(type)
pprint(('{title:>'+pad+'} {start}{to}{via}{src}{metric}{table}{end}[muted]{extra}[/muted]').format(
title='Routes:' if i == 0 else '',
to=obj['to'],
via=via,
src=src,
metric=metric,
table=table,
extra=' ('+', '.join(extra)+')' if extra else '',
start=default_start,
end=default_end))
val = data.get('activation_mode')
if val:
pprint(('{title:>'+pad+'} {value}').format(
title='Activation Mode:',
value=val,
))
pprint()
hidden = total - len(interfaces)
if (hidden > 0):
pprint('{} inactive interfaces hidden. Use "--all" to show all.'.format(hidden))
def command(self):
state_data = SystemConfigState(self.ifname, self.all)
# Output data in requested format
output_format = self.format.lower()
if output_format == 'json': # structural JSON output
print(json.dumps(state_data.get_data()))
elif output_format == 'yaml': # stuctural YAML output
print(yaml.dump(state_data.get_data()))
else: # pretty print, human readable output
self.pretty_print(state_data.get_data(), state_data.number_of_interfaces)