func.spec | 11 func/overlord/scripts.py | 45 ++ scripts/func-command | 43 ++ scripts/func-down-hosts | 35 ++ scripts/func-find-user | 73 ++++ scripts/func-grep | 76 ++++ scripts/func-list-vms-per-host | 66 ++++ scripts/func-ps-compare | 71 ++++ scripts/func-whatsmyname | 58 +++ scripts/func-yum | 650 +++++++++++++++++++++++++++++++++++++++++ 10 files changed, 1128 insertions(+) New commits: commit 74a190db8a7677ae17e760a672e08a97d65c882b Author: Seth Vidal <skvidal@xxxxxxxxxxxxxxxxx> Date: Fri Oct 8 16:47:01 2010 -0400 - add func/overlord/scripts.py as basis for common options in func-overlord cli scripts - add func-commands and func-yum to scripts/ - use new scripts routines in func-command - modify spec file for new commands diff --git a/func.spec b/func.spec index 1493859..faa33e6 100644 --- a/func.spec +++ b/func.spec @@ -75,6 +75,14 @@ rm -fr $RPM_BUILD_ROOT %{_bindir}/func-create-module %{_bindir}/func-transmit %{_bindir}/func-build-map +%{_bindir}/func-command +%{_bindir}/func-down-hosts +%{_bindir}/func-find-user +%{_bindir}/func-grep +%{_bindir}/func-list-vms-per-host +%{_bindir}/func-ps-compare +%{_bindir}/func-whatsmyname +%{_bindir}/func-yum %{_bindir}/func-group #%{_bindir}/update-func @@ -174,6 +182,9 @@ fi %changelog +* Fri Oct 8 2010 Seth Vidal <skvidal at fedoraproject.org> - 0.27-2 +- add func-commands and func-yum + * Wed Aug 25 2010 Seth Vidal <skvidal at fedoraproject.org> - 0.27-1 - bump to 0.27 diff --git a/func/overlord/scripts.py b/func/overlord/scripts.py new file mode 100644 index 0000000..57a6255 --- /dev/null +++ b/func/overlord/scripts.py @@ -0,0 +1,45 @@ +# python modules for doing normal/standard things with func command scripts +# parsing/checking for errors +# returning hosts +# returning results +# standard option parser for --forks, --outputpath, --timeout, --hosts-from-file, -- + + +from optparse import OptionParser +import sys + + +def base_func_parser(opthosts=True, outputpath=True): + parser = OptionParser() + if opthosts: + parser.add_option('--host', default=[], action='append', + help="hosts to act on, defaults to ALL") + parser.add_option('--hosts-from-file', default=None, dest="hostfile", + help="read list of hosts from this file, if '-' read from stdin") + + parser.add_option('--timeout', default=300, type='int', + help='set the wait timeout for func commands') + parser.add_option('--forks', default=40, type='int', + help='set the number of forks to start up') + if outputpath: + parser.add_option('--outputpath', default='/var/lib/func/data/', dest="outputpath", + help="basepath to store results/errors output.") + return parser + +def handle_base_func_options(parser, opts): + if hasattr(opts, 'hostfile') and opts.hostfile: + hosts = [] + if opts.hostfile == '-': + hosts = sys.stdin.readlines() + else: + hosts = open(opts.hostfile, 'r').readlines() + + for hn in hosts: + hn = hn.strip() + if hn.startswith('#'): + continue + hn = hn.replace('\n', '') + opts.host.append(hn) + + return opts + diff --git a/scripts/func-command b/scripts/func-command new file mode 100755 index 0000000..e6b88f9 --- /dev/null +++ b/scripts/func-command @@ -0,0 +1,43 @@ +#!/usr/bin/python -tt +# by skvidal +# gplv2+ + + +import sys +import func.overlord.client +from func.overlord.scripts import base_func_parser, handle_base_func_options +from func.utils import is_error + +parser = base_func_parser(outputpath=False) +opts, args, parser = parse_args(sys.argv[1:]) +opts = handle_base_func_options(parser, opts) + +if len(args) < 1: + print parser.format_help() + sys.exit(1) + +mycmd = ' '.join(args) + +hosts ='*' +if opts.host: + hosts = ';'.join(opts.host) + +fc = func.overlord.client.Client(hosts, timeout=opts.timeout, nforks=opts.forks) + +print mycmd +results = fc.command.run(mycmd) +for (hn, output) in results.items(): + if is_error(output): + msg = 'Error: %s: ' % hn + for item in output[1:3]: + if type(item) == type(''): + msg += ' %s' % item + print >> sys.stderr, msg + continue + + # FIXME - maybe for commands don't do it one line with hostname first + + print '%s' % hn + print output[1] + print '' + diff --git a/scripts/func-down-hosts b/scripts/func-down-hosts new file mode 100755 index 0000000..5d556f0 --- /dev/null +++ b/scripts/func-down-hosts @@ -0,0 +1,35 @@ +#!/usr/bin/python -tt +# by skvidal +# gplv2+ + +import sys +import func.overlord.client +from optparse import OptionParser + +def parse_args(args): + parser = OptionParser(version = "1.0") + parser.add_option('--host', default=[], action='append', + help="hosts to act on, defaults to ALL") + parser.add_option('--timeout', default=10, type='int', + help='set the wait timeout for func commands') + parser.add_option('--forks', default=40, type='int', + help='set the number of forks to start up') + (opts, args) = parser.parse_args(args) + + return opts, args, parser + + +opts, args, parser = parse_args(sys.argv[1:]) +hosts ='*' +if opts.host: + hosts = ';'.join(opts.host) + +fc = func.overlord.client.Client(hosts, timeout=opts.timeout, nforks=opts.forks) + +results = fc.test.ping() +offline = [] +for (hn, out) in results.items(): + if out != 1: + offline.append(hn) + +print '\n'.join(sorted(offline)) diff --git a/scripts/func-find-user b/scripts/func-find-user new file mode 100755 index 0000000..99e037f --- /dev/null +++ b/scripts/func-find-user @@ -0,0 +1,73 @@ +#!/usr/bin/python -tt +# by skvidal +# gplv2+ +# return all the process owned or matching the username given + + + +import sys +import func.overlord.client +from func.utils import is_error +from optparse import OptionParser + +def parse_args(args): + parser = OptionParser(version = "1.0") + parser.add_option('--host', default=[], action='append', + help="hosts to act on, defaults to ALL") + parser.add_option('--timeout', default=300, type='int', + help='set the wait timeout for func commands') + parser.add_option('--forks', default=40, type='int', + help='set the number of forks to start up') + parser.add_option('--hosts-from-file', default=None, dest="hostfile", + help="read list of hosts from this file, if '-' read from stdin") + (opts, args) = parser.parse_args(args) + + if opts.hostfile: + hosts = [] + if opts.hostfile == '-': + hosts = sys.stdin.readlines() + else: + hosts = open(opts.hostfile, 'r').readlines() + + for hn in hosts: + hn = hn.strip() + if hn.startswith('#'): + continue + hn = hn.replace('\n', '') + opts.host.append(hn) + + + return opts, args, parser + + +opts, args, parser = parse_args(sys.argv[1:]) + +if len(args) < 1: + print parser.format_help() + sys.exit(1) + +username = args[0] + +hosts ='*' +if opts.host: + hosts = ';'.join(opts.host) + +fc = func.overlord.client.Client(hosts, timeout=opts.timeout, nforks=opts.forks) + +results = fc.process.grep(username) +for (hn, output) in results.items(): + if is_error(output): + msg = 'Error: %s: ' % hn + for item in output[1:3]: + if type(item) == type(''): + msg += ' %s' % item + print >> sys.stderr, msg + continue + + for val in output.values(): + for line in val: + if line.find('func') != -1: # if we're seeing the func process for any reason, skip it + continue + print '%s:%s' % (hn, line) + + diff --git a/scripts/func-grep b/scripts/func-grep new file mode 100755 index 0000000..21d8b18 --- /dev/null +++ b/scripts/func-grep @@ -0,0 +1,76 @@ +#!/usr/bin/python -tt +# by skvidal +# gplv2+ + + +import sys +import func.overlord.client +from optparse import OptionParser +from func.utils import is_error + +def parse_args(args): + parser = OptionParser(version = "1.0") + parser.add_option('--host', default=[], action='append', + help="hosts to act on, defaults to ALL") + parser.add_option('--timeout', default=300, type='int', + help='set the wait timeout for func commands') + parser.add_option('--forks', default=40, type='int', + help='set the number of forks to start up') + parser.add_option('--grep-options', default='-n', dest='grep_options', + help='set options to pass to grep "-r -i" for example') + parser.add_option('--hosts-from-file', default=None, dest="hostfile", + help="read list of hosts from this file, if '-' read from stdin") + (opts, args) = parser.parse_args(args) + + if opts.hostfile: + hosts = [] + if opts.hostfile == '-': + hosts = sys.stdin.readlines() + else: + hosts = open(opts.hostfile, 'r').readlines() + + for hn in hosts: + hn = hn.strip() + if hn.startswith('#'): + continue + hn = hn.replace('\n', '') + opts.host.append(hn) + + return opts, args, parser + + +opts, args, parser = parse_args(sys.argv[1:]) + +if len(args) < 2: + print parser.format_help() + sys.exit(1) + +search_str = args[0] +search_where = args[1:] + +hosts ='*' +if opts.host: + hosts = ';'.join(opts.host) + +fc = func.overlord.client.Client(hosts, timeout=opts.timeout, nforks=opts.forks) + +cmd = '/bin/grep %s %s %s' % (opts.grep_options, search_str, ' '.join(search_where)) +print cmd +results = fc.command.run(cmd) +for (hn, output) in results.items(): + if is_error(output): + msg = 'Error: %s: ' % hn + for item in output[1:3]: + if type(item) == type(''): + msg += ' %s' % item + print >> sys.stderr, msg + continue + + + for line in output[1].split('\n'): + line = line.strip() + if not line: + continue + print '%s:%s' % (hn, line) + + diff --git a/scripts/func-list-vms-per-host b/scripts/func-list-vms-per-host new file mode 100755 index 0000000..ab461fe --- /dev/null +++ b/scripts/func-list-vms-per-host @@ -0,0 +1,66 @@ +#!/usr/bin/python -tt +# by skvidal +# gplv2+ + +import sys +import func.overlord.client +from optparse import OptionParser + +def parse_args(args): + parser = OptionParser(version = "1.0") + parser.add_option('--host', default=[], action='append', + help="hosts to act on, defaults to ALL") + parser.add_option('--timeout', default=30, type='int', + help='set the wait timeout for func commands') + parser.add_option('--forks', default=40, type='int', + help='set the number of forks to start up') + parser.add_option('--hosts-from-file', default=None, dest="hostfile", + help="read list of hosts from this file, if '-' read from stdin") + (opts, args) = parser.parse_args(args) + + if opts.hostfile: + hosts = [] + if opts.hostfile == '-': + hosts = sys.stdin.readlines() + else: + hosts = open(opts.hostfile, 'r').readlines() + + for hn in hosts: + hn = hn.strip() + if hn.startswith('#'): + continue + hn = hn.replace('\n', '') + opts.host.append(hn) + + return opts, args, parser + + +opts, args, parser = parse_args(sys.argv[1:]) +hosts ='*' +if opts.host: + hosts = ';'.join(opts.host) + +fc = func.overlord.client.Client(hosts, timeout=opts.timeout, nforks=opts.forks) + +results = fc.system.list_modules() +hosts_to_scan = [] +for (hn, mods) in results.items(): + if 'virt' in mods: + hosts_to_scan.append(hn) + +fc = func.overlord.client.Client(';'.join(hosts_to_scan), timeout=opts.timeout, nforks=len(hosts_to_scan)) +results = fc.virt.list_vms() +vms_per_host = {} + +for (hn, vms) in results.items(): + vms_per_host[hn] = [] + for vm in vms: + if vm == 'Domain-0': + continue + vms_per_host[hn].append(vm) + +for hn in sorted(vms_per_host.keys()): + if vms_per_host[hn]: + for vm in sorted(vms_per_host[hn]): + print '%s:%s' % (hn, vm) + diff --git a/scripts/func-ps-compare b/scripts/func-ps-compare new file mode 100755 index 0000000..cc2bfb8 --- /dev/null +++ b/scripts/func-ps-compare @@ -0,0 +1,71 @@ +#!/usr/bin/python -tt +# by skvidal +# gplv2+ + +import sys +import func.overlord.client as fclient +from optparse import OptionParser +from func.utils import is_error + + +def get_host_list(hosts): + fc = fclient.Client(hosts) + host_list = fc.minions_class.get_all_hosts() # grumble + return host_list + + +def parse_args(args): + parser = OptionParser(version = "1.0") + parser.add_option('--timeout', default=10, type='int', + help='set the wait timeout for func commands') + (opts, args) = parser.parse_args(args) + + return opts, args, parser + + +opts, extcmds, parser = parse_args(sys.argv[1:]) + + +if len(extcmds) != 2: + print("func-ps-compare hostname1 hostname2") + print("Must specify exactly two hosts to compare") + sys.exit(1) + +hosts = ';'.join(extcmds) +host_list = get_host_list(hosts) + +if len(host_list) != 2: + print("Must specify exactly two hosts to compare, hosts found: %s" % ' '.join(host_list)) + sys.exit(1) + +host1 = host_list[0] +host2 = host_list[1] + +fc = fclient.Client(hosts, timeout=opts.timeout, nforks=2) +results = fc.process.info("axw") + +processes = {} +for n in [host1, host2]: + processes[n] = set([]) + if is_error(results[n]): + print 'Error from %s' % n + print items + sys.exit(1) + + for items in results[n]: + if not items: + continue + comm = ' '.join(items[4:]) + processes[n].add(comm) + +host1diff = processes[host1].difference(processes[host2]) +host2diff = processes[host2].difference(processes[host1]) + +print 'Processes running on %s not on %s' % (host1, host2) +print '\n'.join(host1diff) + +print '\nProcesses running on %s not on %s' % (host2, host1) +print '\n'.join(host2diff) + + + diff --git a/scripts/func-whatsmyname b/scripts/func-whatsmyname new file mode 100755 index 0000000..579b483 --- /dev/null +++ b/scripts/func-whatsmyname @@ -0,0 +1,58 @@ +#!/usr/bin/python -tt + + +import sys +import func.overlord.client +from func.utils import is_error +from optparse import OptionParser + +def parse_args(args): + parser = OptionParser(version = "1.0") + parser.add_option('--host', default=[], action='append', + help="hosts to act on, defaults to ALL") + parser.add_option('--timeout', default=10, type='int', + help='set the wait timeout for func commands') + parser.add_option('--forks', default=40, type='int', + help='set the number of forks to start up') + parser.add_option('--hosts-from-file', default=None, dest="hostfile", + help="read list of hosts from this file, if '-' read from stdin") + (opts, args) = parser.parse_args(args) + + if opts.hostfile: + hosts = [] + if opts.hostfile == '-': + hosts = sys.stdin.readlines() + else: + hosts = open(opts.hostfile, 'r').readlines() + + for hn in hosts: + hn = hn.strip() + if hn.startswith('#'): + continue + hn = hn.replace('\n', '') + opts.host.append(hn) + + return opts, args, parser + +opts, args, parser = parse_args(sys.argv[1:]) + +hosts ='*' +if opts.host: + hosts = ';'.join(opts.host) + +fc = func.overlord.client.Client(hosts, timeout=opts.timeout, nforks=opts.forks) +results = fc.command.run('/bin/hostname -f') +for (hn, output) in results.items(): + if is_error(output): + msg = 'Error: %s: ' % hn + for item in output[1:3]: + if type(item) == type(''): + msg += ' %s' % item + print >> sys.stderr, msg + continue + + myname = output[1][:-1] + if hn != myname: + print "mismatch puppetname:%s does not match host known name: %s" % (hn, myname) + + diff --git a/scripts/func-yum b/scripts/func-yum new file mode 100755 index 0000000..e05a7ce --- /dev/null +++ b/scripts/func-yum @@ -0,0 +1,650 @@ +#!/usr/bin/python -tt + +# 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; either version 2 of the License, or +# (at your option) any later version. +# +# 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 Library General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# Copyright 2010 Red Hat, Inc +# Written By Seth Vidal - skvidal@xxxxxxxxxxxxxxxxx + +#func yum overlord script + + +# TODO: +# install .... +# remove .... +# push custom module over func and activate +# config file +# --test mode/options +# add arbitrary args to a command (include, exclude, etc) +# needs restarting +# is running kernel latest installed (needs reboot?) +# get list of repos +# some kind of locking mechanism - so we hold off hitting a request on a host that's already doing something +# maybe that means client-local locking on the minion-side. + + + +import sys +import os +import time +import stat +import re +import glob +from optparse import OptionParser +import func.overlord.client as fclient +from func.utils import is_error +from certmaster.config import read_config + + +class FYError(Exception): + def __init__(self, value=None): + Exception.__init__(self) + self.value = value + def __str__(self): + return "%s" %(self.value,) + + +def parse_time(s): + MULTS = {'d': 60 * 60 * 24, 'h' : 60 * 60, 'm' : 60, 's': 1} + + + if s[-1].isalpha(): + n = s[:-1] + unit = s[-1].lower() + mult = MULTS.get(unit, None) + if not mult: + raise ValueError("unknown unit '%s'" % unit) + else: + n = s + mult = 1 + + try: + n = float(n) + except (ValueError, TypeError), e: + raise ValueError('invalid value') + + if n < 0: + raise ValueError("seconds value may not be negative") + + return int(n * mult) + +def errorprint(msg): + print >> sys.stderr, msg + +def parse_args(args): + basecmds = ('update', 'getinfo', 'status', 'install', 'remove', 'list', + 'custom', 'clean', 'search', 'compare') + + usage = """func-yum [options] [command] +commands: \n %s""" % '\n '.join(sorted(basecmds)) + parser = OptionParser(version = "func-yum 1.0", usage=usage) + parser.add_option('--host', default=[], action='append', + help="hosts to act on, defaults to ALL") + parser.add_option('--hosts-from-file', default=None, dest="hostfile", + help="read list of hosts from this file, if '-' read from stdin") + parser.add_option('--timeout', default=5000, type='int', + help='set the wait timeout for func commands') + parser.add_option('--short-timeout', default=5, type='int', dest='short_timeout', + help='set the short timeout wait for connecting to hosts') + parser.add_option('--forks', default=60, type='int', + help='set the number of forks to start up') + parser.add_option('-q','--quiet', default=False, action='store_true', + help='only output what you asked for') + parser.add_option('--store-custom-as', default=None, dest='store_custom_as', + help='store the custom command output as this keyname') + parser.add_option('--clean-older-than', default='2W', dest='clean_older_than', + help='data stored which is older than this will be cleaned up.') + parser.add_option('--outputpath', default='/var/cache/func-yum/', dest='outputpath', + help="path where func-yum's cache will be stored") + + (opts, args) = parser.parse_args(args) + + if not args: + print parser.format_help() + sys.exit(1) + + if args[0] not in basecmds: + print parser.usage + sys.exit(1) + + if opts.outputpath[-1] != '/': + opts.outputpath = opts.outputpath + '/' + + + if opts.hostfile: + hosts = [] + if opts.hostfile == '-': + hosts = sys.stdin.readlines() + else: + hosts = open(opts.hostfile, 'r').readlines() + + for hn in hosts: + hn = hn.strip() + if hn.startswith('#'): + continue + hn = hn.replace('\n', '') + opts.host.append(hn) + + + return opts, args + +def filter_hosts(hosts, opts): + """returns two lists: online and offline hosts""" + + online = [] + offline = [] + fc = fclient.Client(hosts, timeout=opts.short_timeout, nforks=opts.forks) + results = fc.test.ping() + for (hn, out) in results.items(): + if out != 1: + offline.append(hn) + else: + online.append(hn) + + if not online: + errorprint("No hosts online after filter to access") + errorprint("Offline Hosts: %s" % ' '.join(offline)) + sys.exit(1) + + return online, offline + + +def _write_data(basepath, data_key, data_val, timestamp=None, make_latest=True, error=False): + """take data and output it into a location, mark it as the latest, too""" + if not timestamp: + timestamp = time.strftime('%Y-%m-%d-%H:%M:%S') + # someplace/$hostname/data_key/timestamp and then symlinked to 'latest' + + dn = basepath + '/' + data_key + latest = dn + '/latest' + latesterror = dn + '/latest-error' + + if not os.path.exists(dn): + os.makedirs(dn) + + fn = dn + '/' + timestamp + if error: + fn += '-error' + + fo = open(fn, 'w') + if type(data_val) == type([]): + for line in data_val: + if line.strip(): + fo.write(line + '\n') + else: + if data_val.strip(): + fo.write(data_val) + fo.flush() + fo.close() + + if make_latest: + if os.path.exists(latest): + os.unlink(latest) + os.symlink(fn, latest) + + if error: + if os.path.exists(latesterror): + os.unlink(latesterror) + os.symlink(fn, latesterror) + + return latest + +def _wait_for_async(fc, opts, jobid): + finished = False + last_completed = -1 + results = {} + while not finished: + (jobstatus, info) = fc.job_status(jobid) + if type(info) != type({}): + completed = 0 + else: + completed = len(info.keys()) + + if not opts.quiet: + if completed != last_completed: + print '%s/%s hosts finished' % (completed, len(fc.minions)) + last_completed = completed + + if jobstatus == 1: + finished=True + results = info + break + time.sleep(5) + + return results + +def store_info(fc, opts): + # retrieve info to outputpath/$hostname/installed/timestamp + + # ping the box first - if it fails - move on. + + now = time.strftime('%Y-%m-%d-%H:%M:%S') + #results = fc.rpms.inventory()# would like to use inventory, but no can do, + # until I fix/check the module problems on python 2.4 + + errors = [] + # installed pkgs + results = fc.command.run('rpm -qa') + data_key = 'installed' + for (hn, output) in results.items(): + error = False + if is_error(output): + errors.append('Error getting installed from %s' % hn) + error = True + + basepath = opts.outputpath + hn + data_val = sorted(output[1].split('\n')) + _write_data(basepath, data_key, data_val, timestamp=now, error=error) + + # available updates + results = fc.yumcmd.check_update() + data_key = 'updates' + for (hn, output) in results.items(): + error = False + if is_error(output): + errors.append('Error getting updates from %s' % hn) + error = True + + basepath = opts.outputpath + hn + data_val = sorted(output) + _write_data(basepath, data_key, data_val, timestamp=now, error=error) + + # orphaned/extras pkgs + # fixme - make this not a command.run but something in yumcmd + results = fc.command.run('/usr/bin/package-cleanup -q --orphans') + data_key = 'orphans' + for (hn, output) in results.items(): + error = False + if is_error(output): + errors.append('Error getting orphans from %s' % hn) + error = True + + basepath = opts.outputpath + hn + data_val = output[1] + _write_data(basepath, data_key, data_val, timestamp=now, error=error) + + + # get yum list-security if we can + results = fc.command.run('/usr/bin/yum list-security') + data_key = 'security-updates' + for (hn, output) in results.items(): + error = False + if is_error(output): + errors.append('Error getting security-list from %s' % hn) + error = True + + basepath = opts.outputpath + hn + res = [] + for line in output[1].split('\n'): + if line.startswith('Loaded plugins'): + continue + if line.startswith('list-security'): + continue + res.append(line) + data_val = res + _write_data(basepath, data_key, data_val, timestamp=now, error=error) + + # get the needs_restarting code over to the clients and generate that list + # as well + + return errors + +def update(fc, opts, pkg_list): + errors = [] + pkg_str = None + if pkg_list: + pkg_str = ' '.join(pkg_list) + if pkg_str: + jobid = fc.yumcmd.update(pkg_str) + else: + jobid = fc.yumcmd.update() + + results = _wait_for_async(fc, opts, jobid) + + data_key = 'updated' + for (hn, output) in results.items(): + error = False + if is_error(output): + errors.append('Error updating %s' % hn) + error = True + + basepath = opts.outputpath + hn + data_val = output + res = _write_data(basepath, data_key, data_val, error=error) + if not opts.quiet: print 'outputted results for %s to:\n %s' % (hn, res) + return errors + +def custom(fc, opts, args): + errors = [] + fullcmd = '' + if args[0][0] != '/': + fullcmd += '/usr/bin/yum ' + fullcmd += '%s' % ' '.join(args) + + print fullcmd + data_key = 'custom' + if opts.store_custom_as: + data_key = opts.store_custom_as + + results = fc.command.run(fullcmd) + for (hn, output) in results.items(): + error = False + if is_error(output): + errors.append('Error running custom command: %s on %s' % (fullcmd, hn)) + error = True + + data_val = fullcmd + '\n' + data_val += output[1] + basepath = opts.outputpath + hn + + res = _write_data(basepath, data_key, data_val, error=error) + if not opts.quiet: print 'outputted results for %s to:\n %s' % (hn, res) + return errors + +def return_status(hosts, opts): + + # needs updates + # last updates applied on + # num pkgs installed + # num orphans + # last time inventory was gotten + status = {} + for hn in hosts: + if hn not in status: + status[hn] = {'last_check': None, + 'latest_updated': None, + 'num_updates':'unknown', + 'num_installed': 'unknown', + 'num_orphans': 'unknown'} + hnstats = status[hn] + mypath = opts.outputpath+hn + if os.path.exists(mypath + '/installed/latest'): + hnstats['last_check'] = os.stat(mypath +'/installed/latest')[stat.ST_MTIME] + if os.path.exists(mypath + '/updated/latest'): + hnstats['latest_updated'] = os.stat(mypath +'/updated/latest')[stat.ST_MTIME] + if os.path.exists(mypath + '/updates/latest'): + hnstats['num_updates'] = len(open(mypath + '/updates/latest').readlines()) + if os.path.exists(mypath + '/installed/latest'): + hnstats['num_installed'] = len(open(mypath + '/installed/latest').readlines()) + if os.path.exists(mypath + '/orphans/latest'): + hnstats['num_orphans'] = len(open(mypath + '/orphans/latest').readlines()) + + return status + +def _convert_date_to_relative(now, then): + """return a time relative to now of the timestamp (then)""" + + if not then: + return 'Never' + + difftime = now - then + + if difftime > 86400*28: + return "LONG LONG AGO: %s" % str(time.strftime('%Y-%m-%d-%H:%M', time.localtime(then))) + + if difftime > 86400*7: # weeks + weeks = difftime / (86400*7) + return "%s weeks ago" % int(weeks) + + if difftime > 86400: #days + days = difftime / 86400 + return "%s days ago" % int(days) + + if difftime > 3600: #hours + hours = difftime / 3600 + return "%s hours ago" % int(hours) + + if difftime > 60: #minutes + mins = difftime / 60 + return "%s minutes ago" % int(mins) + + if difftime < 60: + return "Just Now" + + + + +def return_info(hn, opts, infotype=None, as_list=False): + if not infotype: + raise FYError, "No Infotype specified" + + if (not os.path.exists(opts.outputpath + '/' + hn) or + not os.path.exists(opts.outputpath + '/' + hn + '/' + infotype) or + not os.path.exists(opts.outputpath + '/' + hn + '/' + infotype + '/latest')): + msg = 'info of type: %s not available for: %s\n' % (infotype,hn) + raise FYError, msg + + fo = open(opts.outputpath + '/' + hn + '/' + infotype + '/latest', 'r') + if as_list: + info = fo.readlines() + else: + info = fo.read() + fo.close() + return info + + +def search(hosts, opts, search_str, target=None): + results = {} # hostname = [target: matched line] + re_obj = re.compile(search_str) + if not target: + target=['*'] + elif type(target) == type(''): + target = [target] + for hn in hosts: + for tgt in target: + fns = glob.glob(opts.outputpath + '/' + hn + '/' + tgt + '/latest') + for fn in fns: + thistarget = fn.replace('/latest', '') + thistarget = thistarget.replace(opts.outputpath + '/' + hn + '/', '') + for r in open(fn, 'r').readlines(): + if re_obj.search(r): + if hn not in results: + results[hn] = [] + results[hn].append('%s:%s' % (thistarget, r.strip())) + return results + +def get_host_list(hosts): + fc = fclient.Client(hosts) + host_list = fc.minions_class.get_all_hosts() # grumble + return host_list + +def main(args): + + opts, args = parse_args(args) + basecmd = args[0] + extcmds = args[1:] + hosts ='*' + if opts.host: + hosts = ';'.join(opts.host) + + + + if basecmd == 'getinfo': + + hosts, offline = filter_hosts(hosts, opts) + getinfo_forks = len(hosts) # gives us a slight advantage on an expensive operation + fc = fclient.Client(';'.join(hosts), timeout=opts.timeout, nforks=getinfo_forks) + errors = store_info(fc, opts) + if not opts.quiet: + print 'stored info for:' + for h in sorted(hosts): + print ' %s' % h + + print 'offline hosts:' + for h in sorted(offline): + print ' %s' % h + + for error in errors: + errorprint(' %s' % error) + + elif basecmd == 'update': + hosts, offline = filter_hosts(hosts, opts) + + fc = fclient.Client(';'.join(hosts), timeout=opts.timeout, nforks=opts.forks, async=True) + errors = update(fc, opts, extcmds) + for error in errors: + errorprint(' %s' % error) + if not opts.quiet: + print 'updating stored info for updated hosts' + fc = fclient.Client(';'.join(hosts), timeout=opts.timeout, nforks=opts.forks) + errors = store_info(fc, opts) # get latest info for the hosts + for error in errors: + errorprint(' %s' % error) + + + + elif basecmd == 'status': + host_list = get_host_list(hosts) + now = time.time() + status = return_status(host_list, opts) + for hn in sorted(status.keys()): + msg = """%s: + Last checked: %s + Last update run: %s + Updates available: %s + Installed pkgs: %s + Orphaned Pkgs: %s + """ % (hn, _convert_date_to_relative(now, status[hn]['last_check']), + _convert_date_to_relative(now, status[hn]['latest_updated']), + status[hn]['num_updates'], status[hn]['num_installed'], + status[hn]['num_orphans']) + print msg + + + elif basecmd == 'list': + host_list = get_host_list(hosts) + extopts = ['installed', 'updates', 'orphans', 'security-updates', + 'updated', 'with-security', 'with-updates'] + if len(extcmds) == 0: + errorprint("specify %s" % ' '.join(extopts)) + return 1 + + for item in extcmds: + if item in ['installed', 'updates', 'orphans', 'updated', 'security-updates']: + for hn in sorted(host_list): + try: + info = return_info(hn,opts, item) + except FYError, e: + errorprint(str(e)) + else: + print '%s %s:' % (hn, item) + print info + print '' + + elif item.startswith('with-'): + if item == 'with-security': + item = 'with-security-updates' + item_name = item.replace('with-', '') + + hwu = {} + for hn in sorted(host_list): + res = [] + try: + this_list = return_info(hn, opts, item_name, as_list=True) + except FYError, e: + res.append(str(e)) + continue + + for line in this_list: + if re.match('\s*(#|$)', line): + continue + res.append(line) + + if res: + hwu[hn]=len(res) + + for h,num in sorted(hwu.items()): + print '%s %s : %s' % (item_name, h, num) + else: + for hn in sorted(host_list): + try: + info = return_info(hn,opts, item) + except FYError, e: + continue + print '%s %s' % (hn, item) + print info + print '' + + elif basecmd == 'custom': + hosts, offline = filter_hosts(hosts, opts) + fc = fclient.Client(';'.join(hosts), timeout=opts.timeout, nforks=opts.forks) + errors = custom(fc, opts, extcmds) + for error in errors: + errorprint(' %s' % error) + + elif basecmd == 'clean': + host_list = get_host_list(hosts) + extopts = ['old-data', 'downed-hosts', 'empty-hosts'] + if len(extcmds) == 0: + errorprint("specify %s" % ' '.join(extopts)) + return 1 + + for item in extcmds: + if item == 'old-data': + pass + elif basecmd == 'search': + host_list = get_host_list(hosts) + if not extcmds: + errorprint("search searchstring [where to search: installed, updates, updated]") + errorprint("must specify at least what to search for") + return 1 + search_str = extcmds[0] + if len(extcmds) > 1: + search_target = extcmds[1:] + else: + search_target = None + results = search(host_list, opts, search_str, target=search_target) + for hn in sorted(results.keys()): + for i in sorted(results[hn]): + print '%s:%s' % (hn, i) + + elif basecmd == 'compare': + if len(extcmds) != 2: + errorprint("func-yum compare hostname1 hostname2") + errorprint("Must specify exactly two hosts to compare") + return 1 + hosts = ';'.join(extcmds) + host_list = get_host_list(hosts) + if len(host_list) != 2: + errorprint("Must specify exactly two hosts to compare, hosts found: %s" % ' '.join(host_list)) + return 1 + host1 = host_list[0] + host2 = host_list[1] + try: + host1_inst = set(return_info(host1, opts, 'installed', as_list=True)) + host2_inst = set(return_info(host2, opts, 'installed', as_list=True)) + except FYError, msg: + errorprint("Error: %s" % msg) + return 1 + host1diff = host1_inst.difference(host2_inst) + host2diff = host2_inst.difference(host1_inst) + print 'Packages on %s not on %s' % (host1, host2) + print ''.join(host1diff) + print 'Packages on %s not on %s' % (host2, host1) + print ''.join(host2diff) + + else: + errorprint('command %s not implemented yet' % basecmd) + return 1 + + + # install # pkg + # remove # pkg pkg pkg + + + return 0 + +if __name__ == "__main__": + sys.exit(main(sys.argv[1:])) + + _______________________________________________ Func-list mailing list Func-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/func-list