diff --git a/files/scripts/updates-uptime-cmd.py b/files/scripts/updates-uptime-cmd.py index 8bb8879b87..627c9e8299 100755 --- a/files/scripts/updates-uptime-cmd.py +++ b/files/scripts/updates-uptime-cmd.py @@ -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' : '..org', - '.fedorainfracloud.org' : '..org', + '.fedorainfracloud.org' : '..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 .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 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