updates-uptimes: Add old-list cmd, and UI tweaks to see old data hosts. Fixes.

Signed-off-by: James Antill <james@and.org>
This commit is contained in:
James Antill
2025-09-30 21:56:03 -04:00
parent b44a22d694
commit 394102a651

View File

@@ -6,25 +6,7 @@
# This is very helpful when doing upgrade+reboot runs, as we can easily see
# what has/hasn't been upgraded and/or rebooted.
# Examples: ($0 = updates-uptime-cmd.py)
# $0 update = create the file and/or do backups
# $0 diff [x] [y] = see the difference between the current state and history
# $0 uptime [x] = see the current state, can be filtered for uptime >= x
# $0 info [x] = see the current state, in long form, can be filtered by name
# $0 host [x] = see the current state of a host(s), can be filtered by name
# $0 list [x] = see the current state, can be filtered by name
# $0 history = see history
# $0 history-keep = clenaup old history
# $0 stats [x] = see stats, can specify a backup
# $0 list '*.stg.*' ... see what staging looks like.
# $0 list '*copr*' ... see what copr looks like.
# $0 history-keep 4 ... keep four days of history (including today)
# $0 uptime-min 1d ... see what hasn't been rebooted in the last 24 hours.
# $0 uptime-max 1d ... see what has been rebooted in the last 24 hours.
# $0 uptime-min 25w ... see what hasn't been rebooted in too damn long.
# $0 update-daily-refresh ... daily update, including a new history, and
# refresh the main file (so any old hosts aren't there anymore).
# For examples, see the help command.
import os
import sys
@@ -93,6 +75,9 @@ conf_host_end_total_hostnum = 20
# How many history files do we show by default (-v shows all).
conf_hist_show = 20
# Make it easier to see different date's
conf_ui_date = True
# Do we use a shorter duration by default (drop minutes/seconds)
conf_short_duration = True
@@ -118,7 +103,7 @@ conf_important_hosts = ["batcave*", "bastion01*", "noc*"]
# Remove suffix noise in names.
conf_suffix_dns_replace = {
'.fedoraproject.org' : '.<FP>.org',
'.fedorainfracloud.org' : '.<FIC>.org',
'.fedorainfracloud.org' : '.<FitC>.org',
}
_suffix_dns_replace = {}
@@ -211,14 +196,21 @@ for i, j in ((0, '8'), (1, '9'), (2, 'a'), (3, 'b'),
(4, 'c'), (5, 'd'), (6, 'e'), (7, 'f')):
ansi['fg:' + j] = '\033[9' + str(i) + 'm'
ansi['bg:' + j] = '\033[10' + str(i) + 'm'
def _ui_t_ansi(text, codes, align=0):
olen = len(text)
def _ui_t_align(text, align=None, olen=None):
if align is None or align == 0:
return text
if olen is None:
olen = len(text)
if abs(align) > olen: # "%*s", align, text
extra = abs(align) - olen
if align > 0:
text = " " * extra + text
else:
text = text + " " * extra
return text
def _ui_t_ansi(text, codes, align=0):
olen = len(text)
text = _ui_t_align(text, align)
if not conf_ansi_terminal or not codes or olen == 0:
return text
@@ -262,6 +254,27 @@ def _ui_t_diffstat_instl(text, align=0):
def _ui_t_diffstat_boots(text, align=0):
return _ui_t_ansi(text, conf_term_diffstat_boots, align=align)
# Make it easier to spot when hosts aren't getting their data updated.
def _ui_date(d1, align=None, prev=None):
if not conf_ui_date:
return _ui_t_align(d1.date, align)
if prev is not None and d1.date == prev:
return _ui_t_align(" \" ", align)
if False and d1.date == backup_today: # Better, or no?
return _ui_t_align("today", align)
if False and d1.date == backup_yesterday:
return _ui_t_align("yesterday", align)
# YYYY-MM-DD
# 1234567890
if prev is None:
prev = backups[-1]
if conf_ansi_terminal and d1.date != prev:
for i in (9, 8, 7, 5):
if d1.date[:i] == prev[:i]:
ndate = d1.date[:i] + _ui_t_high(d1.date[i:])
return _ui_t_align(ndate, align, len(d1.date))
return _ui_t_align(d1.date, align)
# History files are named <fname>.YYYY-MM-DD
def _glob_hist_suffix():
for fn in os.listdir(os.path.dirname(fname)):
@@ -325,6 +338,8 @@ def _pre_cmd__verbose(args):
if args.verbose <= 0:
return
if args.verbose >= 3:
globals()['conf_ui_date'] = False
if args.verbose >= 2:
globals()['conf_small_osinfo'] = False
globals()['conf_suffix_dns_replace'] = {}
@@ -442,6 +457,10 @@ class Host():
_max_len_osvr, vers, rest)
return "%s/%s" % (self.osname_small, self.osvers)
@property
def date_tm(self):
return time.mktime(time.strptime(self.date, "%Y-%m-%d"))
_tm_d = {'d' : 60*60*24, 'h' : 60*60, 'm' : 60, 's' : 1,
'w' : 60*60*24*7,
@@ -604,6 +623,14 @@ def filter_osname_datas(datas, names):
continue
yield data
# Filter datas using the date the data is gathered
def filter_age_min_datas(datas, age):
now = time.time()
for data in datas:
if (now - data.date_tm) < age:
continue
yield data
# Filter datas using uptime as a minium.
def filter_uptime_min_datas(datas, uptime):
for data in datas:
@@ -721,8 +748,8 @@ def host_rebooted(d1, d2):
if d1.date == d2.date and d1.uptime > d2.uptime:
return True
# However, we can be looking at old history
tm1 = time.mktime(time.strptime(d1.date, "%Y-%m-%d"))
tm2 = time.mktime(time.strptime(d2.date, "%Y-%m-%d"))
tm1 = d1.date_tm
tm2 = d2.date_tm
if tm1 > tm2: # Looking backwards in time...
return False
d1up = d1.uptime
@@ -766,7 +793,16 @@ def _max_update_data(data):
def _max_update_correct(prefix):
global _max_len_name
global _max_len_rpms
global _max_len_upts
global _max_len_date
mw = _max_terminal_width - len(prefix)
if _max_len_name + _max_len_rpms + _max_len_upts + _max_len_date < (mw-8):
_max_len_name += 1
_max_len_rpms += 1
_max_len_upts += 1
_max_len_date += 1
while _max_len_name + _max_len_rpms + _max_len_upts + _max_len_date >= mw:
_max_len_name -= 1
@@ -855,67 +891,82 @@ def _print_diffstats(hostnum, updates, cmpds):
# This is the real __main__ start ...
def _usage():
prog = "updates+uptime"
if sys.argv:
prog = os.path.basename(sys.argv[0])
pl = " " * len(prog)
print("""
def _usage(short=False):
prog = "updates+uptime"
if sys.argv:
prog = os.path.basename(sys.argv[0])
print("""\
Usage: %s <cmd>
Optional arguments:
-h, --help show this help message and exit
--verbose, -v increase verbosity
--conf CONF specify configuration
--db-dir DB_DIR Change the path to the files
--ansi ANSI Use ansi terminal codes
--help, -h Show this help message and exit.
--verbose, -v Increase verbosity.
--conf CONF Specify configuration.
--db-dir DB_DIR Change the path to the files.
--ansi ANSI Use ansi terminal codes.
Cmds:
""" % (prog,), end='')
if short:
print("""\
diff/-u [backup1] [backup2]
help
= This message.
diff [backup1] [backup2]
history
history-keep [days]
hosts/-u [host*] [host*]...
info [host*] [backup] [backup]...
list [host*] [backup]
list-n [host*] [host*]...
old-list duration
oslist [os*] [backup]
oslist-n [os*] [os*]...
stats [backup] [host*] [host*]...
update
update-host host
uptime/-min/-max duration [backup]
""", end='')
else:
# Also see: _cmd_help() below...
print("""\
diff [backup1] [backup2]
= See the difference between the current state and backups.
diff-u [backup1] [backup2]
diff-u [backup1] [backup2]
= Shows before/after instead of modified (like diff -u).
history
= Show history data.
history-keep [days]
= Show summary of current data, and how it changed over time.
history-keep [days]
= Cleanup old history.
hosts [host*] [host*]...
hosts/-u [host*] [host*]...
= See the history of a host(s).
hosts-u [host*] [host*]...
= See the history of a host(s), shows before/after.
info [host*] [backup] [backup]...
info [host*] [backup] [backup]...
= See the current state, in long form, can be filtered by name.
list [host*] [backup]
list [host*] [backup]
list-n [host*] [host*]...
= See the current state, can be filtered by name.
list-n [host*] [host*]...
= See the current state, can be filtered by multiple names.
oslist [os*] [backup]
old-list duration
= See the current state of hosts with data older than duration.
oslist [os*] [backup]
oslist-n [os*] [os*]...
= See the current state, can be filtered by OS.
oslist-n [os*] [os*]...
= See the current state, can be filtered by multiple OSes.
stats [backup] [host*] [host*]...
= Show stats.
stats [backup] [host*] [host*]...
= Show general stats.
update
= Create the file and/or do backups.
= Run update-fast, or update-daily if no daily backup.
update-fast
update-flush
= Create the file.
update-host host
= Create/update the main file, for the specified host(s).
update-daily
= update-flush and do backups.
= Run update-fast and force do a backup for today.
update-daily-refresh
= update-daily with new main file.
= Run update-daily with an empty main file.
uptime-min [duration] [backup]
uptime-min duration [backup]
= See the current state, can be filtered for uptime >= duration.
uptime-max [duration] [backup]
uptime-max duration [backup]
= See the current state, can be filtered for uptime <= duration.
""" % (prog,))
""", end='')
def _cmd_history_keep(args):
@@ -928,7 +979,7 @@ def _cmd_history_keep(args):
os.unlink(fn)
def _cmd_update(args):
global cmd
cmd = args.cmd
if cmd == "update":
cmd = "update-flush"
if not os.path.exists(fname):
@@ -940,6 +991,9 @@ def _cmd_update(args):
if (int(time.time()) - mtime) > conf_dur_flush_cache:
cmd = "update-fast"
if cmd == "update-host":
os.chdir("/srv/web/infra/ansible/playbooks")
os.system("ansible-playbook generate-updates-uptimes-per-host-file.yml -t updates --limit '" + args.host + "'")
if cmd == "update-flush": # Get the latest uptime.
# No need to flush caches now, the new playbook should DTRT.
os.chdir("/srv/web/infra/ansible/playbooks")
@@ -1274,12 +1328,13 @@ def _print_info(hosts, data):
print(" OS:", host.osinfo)
print(" Updates:", _ui_int(host.rpms))
print(" Uptime:", format_duration(host.uptime)) # !ui_dur
print(" Checked:", host.date)
print(" Checked:", _ui_date(host))
if conf_info_machine_ids:
print(" Machine:", host.machine_id)
print(" Boot:", host.boot_id)
def _cmd_info(args):
hists = ['main']
hosts = conf_important_hosts.copy()
if args.host:
# print("JDBG:", args.host)
@@ -1288,7 +1343,9 @@ def _cmd_info(args):
for b in backups:
print("History:", b)
_print_info(hosts, lines2datas(bfname2lines(b)))
for hist in args.hists:
if args.hists:
hists = args.hists[:]
for hist in hists:
if hist != "main": # One or more historical files...
print("History:", hist)
else:
@@ -1308,16 +1365,18 @@ def _print_line_reset():
ret = _prnt_line_saved is not None
_prnt_line_saved = []
return ret
def _print_line(prefix, data, high='', save=False):
def _print_line(prefix, data, high='', save=False, prev=None):
global _prnt_line_saved
if prev is not None:
prev = prev.date
uiname = "%-*s" % (_max_len_name, _ui_name(data.name))
if high:
uiname = _ui_t_ansi(uiname, high)
line = "%s%s %*s %*s %*s %s" % (prefix,
line = "%s%s %*s %*s %s %s" % (prefix,
uiname,
_max_len_rpms, _ui_int(data.rpms),
_max_len_upts, _ui_dur(data.uptime),
_max_len_date, data.date, _ui_osinfo(data))
_ui_date(data, align=_max_len_date, prev=prev), _ui_osinfo(data))
if save and _prnt_line_saved is not None:
_prnt_line_saved.append(line)
return
@@ -1327,20 +1386,34 @@ def _print_line(prefix, data, high='', save=False):
_prnt_line_saved = None
print(line)
def _print_lines(prefix, data, explain=True):
pd1 = None
for d1 in data:
_print_line(prefix, d1, prev=pd1)
pd1 = d1
if explain:
_explain_ui_name()
# -n variants match multiple things, but only allow looking at current data
def _cmd_list(args):
# FIXME: Ideally argparse would do this for us :(
hists = []
hosts = []
osnames = []
if hasattr(args, 'host'): # FIXME: argparse can't do this for us :(
args.hosts = [args.host]
if hasattr(args, 'osname'):
args.osnames = [args.osname]
if args.hosts:
if hasattr(args, 'hists'):
hists = args.hists[:]
if hasattr(args, 'hosts'):
hosts = args.hosts[:]
if args.osnames:
if hasattr(args, 'osnames'):
osnames = args.osnames[:]
if hasattr(args, 'host') and args.host is not None:
hosts += [args.host]
if hasattr(args, 'osname') and args.osname is not None:
osnames += [args.osname]
data = fname1(args.hists[:])
data = fname1(hists)
if hasattr(args, 'dateage'):
data = list(filter_age_min_datas(data, args.dateage))
data = list(filter_name_datas(data, hosts))
data = list(filter_osname_datas(data, osnames))
_max_update(data)
@@ -1349,9 +1422,7 @@ def _cmd_list(args):
_ui_t_title("*", _max_len_rpms),
_ui_t_title("Up", _max_len_upts),
_ui_t_title("Date", _max_len_date), _ui_t_title("OS"))
for d1 in data:
_print_line('', d1)
_explain_ui_name()
_print_lines('', data)
def _cmd_uptime(args):
age = 0
@@ -1365,32 +1436,31 @@ def _cmd_uptime(args):
data = list(filter_uptime_min_datas(data, age))
_max_update(data)
_max_update_correct('')
for d1 in data:
_print_line('', d1)
_explain_ui_name()
_print_lines('', data)
def _diff_hosts(data1, data2, show_both=False, show_utf8=True, skip_eq=False):
pdata = None
while len(data1) > 0 or len(data2) > 0:
if len(data1) <= 0:
_print_line('+', data2[0])
data2.pop(0)
_print_line('+', data2[0], prev=pdata)
pdata = data2.pop(0)
continue
if len(data2) <= 0:
_print_line('-', data1[0])
data1.pop(0)
_print_line('-', data1[0], prev=pdata)
pdata = data1.pop(0)
continue
d1 = data1[0]
d2 = data2[0]
if d1.name < d2.name:
_print_line('-', d1)
data1.pop(0)
_print_line('-', d1, prev=pdata)
pdata = data1.pop(0)
continue
if d1.name > d2.name:
_print_line('+', d2)
data2.pop(0)
_print_line('+', d2, prev=pdata)
pdata = data2.pop(0)
continue
# d1.name == d2.name; so both are going now
@@ -1405,9 +1475,11 @@ def _diff_hosts(data1, data2, show_both=False, show_utf8=True, skip_eq=False):
if not d2.rpms:
u_ed_or_up = _conf_utf8_boot_up
h_ed_or_up = conf_term_host_boot_up
_print_line(u_ed_or_up, d2, high=h_ed_or_up)
_print_line(u_ed_or_up, d2, high=h_ed_or_up, prev=pdata)
pdata = d2
continue
_print_line(' ', d2, save=skip_eq)
_print_line(' ', d2, save=skip_eq, prev=pdata)
pdata = d2
continue
# Something changed, see what and set utf8 prefix and highlight
@@ -1430,16 +1502,16 @@ def _diff_hosts(data1, data2, show_both=False, show_utf8=True, skip_eq=False):
# Something about host changed, show old/new...
if show_both:
_print_line('-', d1)
_print_line(utf8, d2, high=high)
_print_line('-', d1, prev=pdata)
_print_line(utf8, d2, high=high, prev=pdata)
pdata = d2
continue
# Something changed, but we only show the new data...
if not conf_utf8:
utf8 = '!'
_print_line(utf8, d2, high=high)
continue
_print_line(utf8, d2, high=high, prev=pdata)
pdata = d2
def _cmd_diff(args):
hists = args.hists[:]
@@ -1540,9 +1612,7 @@ def _cmd_host(args):
if done:
print("")
print("Host data: %s" % (_ui_t_time(last_name),), file=sys.stderr)
for d1 in last_data:
_print_line(' ', d1)
_explain_ui_name()
_print_lines(' ', last_data)
def _cmdline_arg_ansi(oval):
@@ -1594,6 +1664,205 @@ def _cmdline_arg_hist(oval):
msg += "\n History:", ", ".join([] + names + backups)
raise argparse.ArgumentTypeError(msg)
def _cmd_help(args):
prog = "updates+uptime"
if sys.argv:
prog = os.path.basename(sys.argv[0])
if not args.hcmd:
_usage()
elif args.hcmd in ("diff", "diff-u"):
print(f"""\
Usage: {prog} diff [backup1] [backup2]
See the difference between the current state and backups.
The -u variant shows before/after instead of modified.
Eg. {prog} {args.hcmd}
{prog} {args.hcmd} yesterday
{prog} {args.hcmd} 2025-08-16 main
""", end='')
elif args.hcmd in ("history", "hist"):
print(f"""\
Usage: {prog} {args.hcmd}
Show summary of current data, and how it changed over time.
Kind of like: git log --pretty=oneline --abbrev-commit --decorate
Eg. {prog} {args.hcmd}
""", end='')
elif args.hcmd == "history-keep":
print(f"""\
Usage: {prog} {args.hcmd} [days]
Cleanup old history, older than the given number of days.
The main file and today's history have to be kept, which happens
if you pass "1".
Logrotate, for history.
Eg. {prog} {args.hcmd}
{prog} {args.hcmd} 32
""", end='')
elif args.hcmd in ("host", "hosts", "host-u", "hosts-u"):
print(f"""\
Usage: {prog} {args.hcmd}
See the history of a host(s). A cross between looking at diff and history.
The -u variants show before/after instead of modified.
Easiest way to see how hosts have changed over time, the more history the
better for this. Kind of like git blame, but instead of lines it's events.
Eg. {prog} {args.hcmd}
{prog} {args.hcmd} 'batcave*'
{prog} {args.hcmd} 'batcave*' 'noc*'
""", end='')
elif args.hcmd in ("information", "info"):
print(f"""\
Usage: {prog} {args.hcmd} [host*] [backup] [backup]...
See the current state, in long form, can be filtered by name.
If you want to compare things by hand, use this.
Eg. {prog} {args.hcmd}
{prog} {args.hcmd} 'batcave*'
{prog} {args.hcmd} 'noc*' main yesterday
""", end='')
elif args.hcmd in ("list",):
print(f"""\
Usage: {prog} {args.hcmd} [host*] [backup]
See the current state of the hosts, can be filtered by name.
Eg. {prog} {args.hcmd}
{prog} {args.hcmd} 'batcave*'
{prog} {args.hcmd} 'noc*' yesterday
""", end='')
elif args.hcmd in ("list-n",):
print(f"""\
Usage: {prog} {args.hcmd} [host*] [host*]...
See the current state of the hosts, can be filtered by name.
Eg. {prog} {args.hcmd}
{prog} {args.hcmd} 'batcave*'
{prog} {args.hcmd} 'batcave*' 'noc*'
""", end='')
elif args.hcmd in ("old-list", ):
print(f"""\
Usage: {prog} {args.hcmd} duration
See the current state of hosts, with data older than duration.
Easiest way to see what hosts we aren't getting data for.
Eg. {prog} {args.hcmd} 2d
""", end='')
elif args.hcmd in ("oslist",):
print(f"""\
Usage: {prog} {args.hcmd} [os*] [backup]
See the current state, can be filtered by OS.
Eg. {prog} {args.hcmd}
{prog} {args.hcmd} RedHat
{prog} {args.hcmd} 10 yesterday
""", end='')
elif args.hcmd in ("oslist-n",):
print(f"""\
Usage: {prog} {args.hcmd} [os*] [os*]...
See the current state, can be filtered by OS.
Eg. {prog} {args.hcmd}
{prog} {args.hcmd} RedHat
{prog} {args.hcmd} F 10
""", end='')
elif args.hcmd in ("statistics", "stats"):
print(f"""\
Usage: {prog} {args.hcmd} [backup] [host*] [host*]...
Show general statistics.
Easiest way to see the current state of the hosts.
Eg. {prog} {args.hcmd}
{prog} {args.hcmd} yesterday
{prog} {args.hcmd} newest '*.stg.*' '*-test.*'
""", end='')
elif args.hcmd in ("update",):
print(f"""\
Usage: {prog} {args.hcmd}
Run update-fast, or update-daily if no daily backup.
Easiest way to update, from cron or cmdline after you change things. DTRT.
Eg. {prog} {args.hcmd}
""", end='')
elif args.hcmd in ("update-fast",):
print(f"""\
Usage: {prog} {args.hcmd}
Update the data for the hosts, in the main file (creating it if needed).
Eg. {prog} {args.hcmd}
""", end='')
elif args.hcmd in ("update-host",):
print(f"""\
Usage: {prog} {args.hcmd} host*
Run update-fast, only for the specified host(s).
Eg. {prog} {args.hcmd} bat\*
""", end='')
elif args.hcmd in ("update-daily",):
print(f"""\
Usage: {prog} {args.hcmd}
Run update-fast and force do a backup for today.
Eg. {prog} {args.hcmd}
""", end='')
elif args.hcmd in ("update-daily-refresh",):
print(f"""\
Usage: {prog} {args.hcmd}
Delete the current file, then run update and also force do a backup for today.
Easiest way to refresh the current history for today.
Eg. {prog} {args.hcmd}
""", end='')
elif args.hcmd in ("uptime", "uptime-min"):
print(f"""\
Usage: {prog} {args.hcmd} duration [backup]
See the current state, can be filtered for uptime >= duration.
Easy way to see what has been rebooted recently.
Eg. {prog} {args.hcmd} 32h
{prog} {args.hcmd} 1d yesterday
""", end='')
elif args.hcmd in ("uptime-max",):
print(f"""\
Usage: {prog} {args.hcmd} duration [backup]
See the current state, can be filtered for uptime <= duration.
Easy way to see what hasn't been rebooted recently.
Eg. {prog} {args.hcmd} 26w
{prog} {args.hcmd} 4w4d yesterday
""", end='')
else:
print(" Unknown command:", args.hcmd)
_usage()
def _main():
global conf_ansi_terminal
global conf_path
@@ -1601,7 +1870,7 @@ def _main():
_user_conf()
parser = argparse.ArgumentParser()
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument('--verbose', '-v', action='count', default=0)
parser.add_argument("--conf", action='append', default=[])
parser.add_argument("--db-dir")
@@ -1611,14 +1880,21 @@ def _main():
help=argparse.SUPPRESS)
parser.add_argument("--color", type=_cmdline_arg_ansi, dest='ansi',
help=argparse.SUPPRESS)
parser.add_argument('-h', '--help', action='store_true',
help='Show this help message')
# We do this here so that `$0 -v stats -v` works.
margs, args = parser.parse_known_args()
if margs.help:
_usage(short=True)
sys.exit(0)
subparsers = parser.add_subparsers(dest="cmd")
cmd = subparsers.add_parser("help")
cmd.set_defaults(func=lambda x: _usage()) # parser.print_help())
cmd.add_argument("hcmd", nargs='?', help="cmd to get help for")
cmd.set_defaults(func=_cmd_help)
def __defs(func):
cmd.set_defaults(func=func)
@@ -1670,27 +1946,28 @@ def _main():
cmd.add_argument("hists", nargs='*', type=_cmdline_arg_hist, help="history file")
__defs(func=_cmd_info)
# list/list-n/oslist/oslist-n commands
# list/list-n/old-list/olist/oslist/oslist-n commands
cmd = subparsers.add_parser("list", help="list hosts")
cmd.add_argument("host", nargs='?', help="wildcard hostname")
cmd.add_argument("hists", nargs='*', type=_cmdline_arg_hist, help="history file")
cmd.set_defaults(osnames=[])
__defs(func=_cmd_list)
cmd = subparsers.add_parser("list-n", help="list hosts")
cmd.add_argument("hosts", nargs='*', help="wildcard hostname(s)")
cmd.set_defaults(osnames=[])
__defs(func=_cmd_list)
cmd = subparsers.add_parser("old-list", aliases=['olist'],help="list hosts")
cmd.add_argument("dateage", type=_cmdline_arg_duration,
help="data age minimum duration")
__defs(func=_cmd_list)
cmd = subparsers.add_parser("oslist", help="list hosts")
cmd.add_argument("osname", nargs='?', help="wildcard OSname")
cmd.add_argument("hists", nargs='*', type=_cmdline_arg_hist, help="history file")
cmd.set_defaults(hosts=[])
__defs(func=_cmd_list)
cmd = subparsers.add_parser("oslist-n", help="list hosts")
cmd.add_argument("osnames", nargs='*', help="wildcard OSname(s)")
cmd.set_defaults(hosts=[])
__defs(func=_cmd_list)
# stats commands
@@ -1703,7 +1980,11 @@ def _main():
# update commands
als = ['update-daily','update-daily-refresh', 'update-fast', 'update-flush']
cmd = subparsers.add_parser("update", aliases=als, help="show history")
cmd = subparsers.add_parser("update", aliases=als, help="update DB")
__defs(func=_cmd_update)
cmd = subparsers.add_parser("update-host", help="update DB for hosts")
cmd.add_argument("host", help="wildcard hostname")
__defs(func=_cmd_update)
# uptime/uptime-min/uptime-max commands