diff --git a/.gitignore b/.gitignore index 5d035ff..8766018 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -SOURCES/sos-collector-1.5.tar.gz +SOURCES/sos-collector-1.7.tar.gz diff --git a/.sos-collector.metadata b/.sos-collector.metadata index cf067d7..76bf747 100644 --- a/.sos-collector.metadata +++ b/.sos-collector.metadata @@ -1 +1 @@ -681835509211b82bb11a4547b8aa648ec1e72d13 SOURCES/sos-collector-1.5.tar.gz +5a4f3d843e90ac35af70c2da2e1b18a62df036c3 SOURCES/sos-collector-1.7.tar.gz diff --git a/SOURCES/sos-collector-bytes-conversion.patch b/SOURCES/sos-collector-bytes-conversion.patch deleted file mode 100644 index 2fb2f45..0000000 --- a/SOURCES/sos-collector-bytes-conversion.patch +++ /dev/null @@ -1,36 +0,0 @@ -From 69e481e0fc455f75fcfd2dc0fb2d94af0910445f Mon Sep 17 00:00:00 2001 -From: Jake Hunsaker -Date: Mon, 15 Oct 2018 18:02:37 -0400 -Subject: [PATCH] [sosnode] Handle bytes conversion in command output - -Converts bytes to strings for formatting command output - -Signed-off-by: Jake Hunsaker ---- - soscollector/sosnode.py | 10 ++++------ - 1 file changed, 4 insertions(+), 6 deletions(-) - -diff --git a/soscollector/sosnode.py b/soscollector/sosnode.py -index c9ae2ef..dd0664e 100644 ---- a/soscollector/sosnode.py -+++ b/soscollector/sosnode.py -@@ -138,12 +138,10 @@ class SosNode(): - - def _fmt_output(self, stdout=None, stderr=None, rc=0): - '''Formats the returned output from a command into a dict''' -- c = {} -- c['status'] = rc -- if isinstance(stdout, six.string_types): -- stdout = [str(stdout)] -- if isinstance(stderr, six.string_types): -- stderr = [str(stderr)] -+ if isinstance(stdout, (six.string_types, bytes)): -+ stdout = [stdout.decode('utf-8')] -+ if isinstance(stderr, (six.string_types, bytes)): -+ stderr = [stderr.decode('utf-8')] - if stdout: - stdout = ''.join(s for s in stdout) or True - if stderr: --- -2.14.4 - diff --git a/SOURCES/sos-collector-case-id-prompt.patch b/SOURCES/sos-collector-case-id-prompt.patch deleted file mode 100644 index 6d53b07..0000000 --- a/SOURCES/sos-collector-case-id-prompt.patch +++ /dev/null @@ -1,41 +0,0 @@ -From c9157c5072aeeae23e2de25080deeea959ac7dc5 Mon Sep 17 00:00:00 2001 -From: Jake Hunsaker -Date: Wed, 17 Oct 2018 11:45:48 -0400 -Subject: [PATCH] [sos_collector] Configure initial sosreport command after - case id prompt - -We were previously configuring the initial sosreport command before the -case id prompt when a user didn't specify either --case-id or --batch on -the commandline, thus only the sos-collector archive would have the case -id and not the sosreports run on the nodes. No other options were -affected by this, and case ids are now properly set on all node -sosreports. - -Signed-off-by: Jake Hunsaker ---- - soscollector/sos_collector.py | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/soscollector/sos_collector.py b/soscollector/sos_collector.py -index 1e66de2..c0044ec 100644 ---- a/soscollector/sos_collector.py -+++ b/soscollector/sos_collector.py -@@ -264,7 +264,6 @@ this utility or remote systems that it connects to. - self.console.info("\nsos-collector (version %s)\n" % __version__) - intro_msg = self._fmt_msg(disclaimer % self.config['tmp_dir']) - self.console.info(intro_msg) -- self.configure_sos_cmd() - prompt = "\nPress ENTER to continue, or CTRL-C to quit\n" - if not self.config['batch']: - input(prompt) -@@ -332,6 +331,7 @@ this utility or remote systems that it connects to. - self.config['cluster'].modify_sos_cmd() - self.get_nodes() - self.intro() -+ self.configure_sos_cmd() - - def intro(self): - '''Prints initial messages and collects user and case if not --- -2.14.4 - diff --git a/SOURCES/sos-collector-deb-support.patch b/SOURCES/sos-collector-deb-support.patch deleted file mode 100644 index 9e23741..0000000 --- a/SOURCES/sos-collector-deb-support.patch +++ /dev/null @@ -1,113 +0,0 @@ -From 998b7596c8aa7cccbcb94ebffe9fb72a4609bce4 Mon Sep 17 00:00:00 2001 -From: Bryan Quigley -Date: Mon, 15 Oct 2018 16:52:50 -0700 -Subject: [PATCH] [Hosts] Add Debian and Ubuntu as hosts - -Currently Debian and Ubuntu share a hosts file but we can further -split them out if needed. - -Needed to define both a package name and bin path variable -so that the differences between RH and U/D could be handled. - -Signed-off-by: Bryan Quigley ---- - soscollector/hosts/__init__.py | 2 ++ - soscollector/hosts/debian.py | 36 ++++++++++++++++++++++++++++++++++++ - soscollector/hosts/redhat.py | 2 ++ - soscollector/sosnode.py | 4 ++-- - 4 files changed, 42 insertions(+), 2 deletions(-) - create mode 100644 soscollector/hosts/debian.py - -diff --git a/soscollector/hosts/__init__.py b/soscollector/hosts/__init__.py -index 02b1d71..2e93094 100644 ---- a/soscollector/hosts/__init__.py -+++ b/soscollector/hosts/__init__.py -@@ -41,6 +41,8 @@ class SosHost(): - container_runtime = None - container_image = None - sos_path_strip = None -+ sos_pkg_name = None # package name in deb/rpm/etc -+ sos_bin_path = None # path to sosreport binary - - def __init__(self, address): - self.address = address -diff --git a/soscollector/hosts/debian.py b/soscollector/hosts/debian.py -new file mode 100644 -index 0000000..7b67c83 ---- /dev/null -+++ b/soscollector/hosts/debian.py -@@ -0,0 +1,36 @@ -+# Copyright Canonical 2018, Bryan Quigley -+# 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 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., -+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -+ -+from soscollector.hosts import SosHost -+ -+ -+class DebianHost(SosHost): -+ '''Base class for defining Debian based systems''' -+ -+ distribution = 'Debian' -+ releases = ['ubuntu', 'debian'] -+ package_manager = { -+ 'name': 'dpkg', -+ 'query': "dpkg-query -W -f='${Package}-${Version}\\\n' " -+ } -+ sos_pkg_name = 'sosreport' -+ sos_bin_path = '/usr/bin/sosreport' -+ -+ def check_enabled(self, rel_string): -+ for release in self.releases: -+ if release in rel_string: -+ return True -+ return False -+# vim:ts=4 et sw=4 -diff --git a/soscollector/hosts/redhat.py b/soscollector/hosts/redhat.py -index 20db16a..f077d03 100644 ---- a/soscollector/hosts/redhat.py -+++ b/soscollector/hosts/redhat.py -@@ -26,6 +26,8 @@ class RedHatHost(SosHost): - 'name': 'rpm', - 'query': 'rpm -q' - } -+ sos_pkg_name = 'sos' -+ sos_bin_path = '/usr/sbin/sosreport' - - def check_enabled(self, rel_string): - for release in self.releases: -diff --git a/soscollector/sosnode.py b/soscollector/sosnode.py -index 98f1765..fc674b0 100644 ---- a/soscollector/sosnode.py -+++ b/soscollector/sosnode.py -@@ -154,7 +154,7 @@ class SosNode(): - def _load_sos_info(self): - '''Queries the node for information about the installed version of sos - ''' -- cmd = self.host.prefix + self.host.pkg_query('sos') -+ cmd = self.host.prefix + self.host.pkg_query(self.host.sos_pkg_name) - res = self.run_command(cmd) - if res['status'] == 0: - ver = res['stdout'].splitlines()[-1].split('-')[1] -@@ -275,7 +275,7 @@ class SosNode(): - def run_command(self, cmd, timeout=180, get_pty=False, need_root=False): - '''Runs a given cmd, either via the SSH session or locally''' - if cmd.startswith('sosreport'): -- cmd = cmd.replace('sosreport', '/usr/sbin/sosreport') -+ cmd = cmd.replace('sosreport', self.host.sos_bin_path) - need_root = True - if need_root: - get_pty = True --- -2.14.4 - diff --git a/SOURCES/sos-collector-fix-non-root-local.patch b/SOURCES/sos-collector-fix-non-root-local.patch deleted file mode 100644 index 0bb540f..0000000 --- a/SOURCES/sos-collector-fix-non-root-local.patch +++ /dev/null @@ -1,74 +0,0 @@ -From 8c0b8f944be6eb05ce5a23c85fe8c19abdbe0c32 Mon Sep 17 00:00:00 2001 -From: Jake Hunsaker -Date: Mon, 15 Oct 2018 18:51:05 -0400 -Subject: [PATCH] [sosnode] Fix non-root local execution and collection of - sosreport - -Fixes an issue where a local non-root run of sosreport could fail due to -incorrect process communication with the sosreport process started by -sos-collector. Additionally, corrects local copying of generated -sosreports to the sos-collector archive. - -Signed-off-by: Jake Hunsaker ---- - soscollector/sosnode.py | 23 ++++++++++++++++------- - 1 file changed, 16 insertions(+), 7 deletions(-) - -diff --git a/soscollector/sosnode.py b/soscollector/sosnode.py -index dd0664e..98f1765 100644 ---- a/soscollector/sosnode.py -+++ b/soscollector/sosnode.py -@@ -305,11 +305,16 @@ class SosNode(): - raise socket.timeout - else: - proc = Popen(cmd, shell=True, stdin=PIPE, stdout=PIPE, stderr=PIPE) -- stdout, stderr = proc.communicate() -- if self.config['become_root']: -- proc.communicate(input=self.config['root_password'] + '\n') -- if self.config['need_sudo']: -- proc.communicate(input=self.config['sude_pw'] + '\n') -+ if self.config['become_root'] and need_root: -+ stdout, stderr = proc.communicate( -+ input=self.config['root_password'] + '\n' -+ ) -+ elif self.config['need_sudo'] and need_root: -+ stdout, stderr = proc.communicate( -+ input=self.config['sudo_pw'] + '\n' -+ ) -+ else: -+ stdout, stderr = proc.communicate() - rc = proc.returncode - if stdout: - sout = (stdout or True) -@@ -591,7 +596,7 @@ class SosNode(): - return False - else: - self.log_debug("Moving %s to %s" % (path, destdir)) -- shutil.move(path, dest) -+ shutil.copy(path, dest) - return True - except Exception as err: - self.log_debug("Failed to retrieve %s: %s" % (path, err)) -@@ -628,6 +633,10 @@ class SosNode(): - except Exception: - self.log_error('Failed to make archive readable') - return False -+ try: -+ self.make_archive_readable(self.sos_path + '.md5') -+ except Exception: -+ self.log_debug('Failed to make md5 readable') - self.logger.info('Retrieving sosreport from %s' % self.address) - self.log_info('Retrieving sosreport...') - ret = self.retrieve_file(self.sos_path) -@@ -698,7 +707,7 @@ class SosNode(): - - This is only used when we're not connecting as root. - ''' -- cmd = 'chmod +r %s' % filepath -+ cmd = 'chmod o+r %s' % filepath - res = self.run_command(cmd, timeout=10, need_root=True) - if res['status'] == 0: - return True --- -2.14.4 - diff --git a/SOURCES/sos-collector-fix-options-reporting.patch b/SOURCES/sos-collector-fix-options-reporting.patch deleted file mode 100644 index 8466569..0000000 --- a/SOURCES/sos-collector-fix-options-reporting.patch +++ /dev/null @@ -1,44 +0,0 @@ -From a777e0713172080b4acdcd601dba4fcb3965925d Mon Sep 17 00:00:00 2001 -From: Jake Hunsaker -Date: Wed, 7 Nov 2018 15:47:46 -0500 -Subject: [PATCH] [sos_collector] Fix cluster option reporting - -No longer call a removed method that handled loading cluster profiles -before that was moved into the Configuration() class. - -Signed-off-by: Jake Hunsaker ---- - soscollector/sos_collector.py | 4 +--- - 1 file changed, 1 insertion(+), 3 deletions(-) - -diff --git a/soscollector/sos_collector.py b/soscollector/sos_collector.py -index e7f3ea7..70a3dc8 100644 ---- a/soscollector/sos_collector.py -+++ b/soscollector/sos_collector.py -@@ -48,13 +48,13 @@ class SosCollector(): - self.master = False - self.retrieved = 0 - self.need_local_sudo = False -+ self.clusters = self.config['cluster_types'] - if not self.config['list_options']: - try: - if not self.config['tmp_dir']: - self.create_tmp_dir() - self._setup_logging() - self.log_debug('Executing %s' % ' '.join(s for s in sys.argv)) -- self.clusters = self.config['cluster_types'] - self.log_debug("Found cluster profiles: %s" - % self.clusters.keys()) - self.log_debug("Found supported host types: %s" -@@ -63,8 +63,6 @@ class SosCollector(): - self.prep() - except KeyboardInterrupt: - self._exit('Exiting on user cancel', 130) -- else: -- self._load_clusters() - - def _setup_logging(self): - # behind the scenes logging --- -2.14.4 - diff --git a/SOURCES/sos-collector-nested-container-fix.patch b/SOURCES/sos-collector-nested-container-fix.patch new file mode 100644 index 0000000..0c33ae7 --- /dev/null +++ b/SOURCES/sos-collector-nested-container-fix.patch @@ -0,0 +1,59 @@ +From 0bc831a07be6ae837cb9029943c57255c47b647f Mon Sep 17 00:00:00 2001 +From: Jake Hunsaker +Date: Tue, 9 Apr 2019 12:59:03 -0400 +Subject: [PATCH] [sosnode] Don't create a container if we're in a container + +If sos-collector is being launched from a container on a containerized +host, we could potentially attempt to create a nested container. + +This change means we treat a sos-collector run that is already in a +container as a "normal" host and don't attempt the containerized bits. + +Signed-off-by: Jake Hunsaker +--- + soscollector/sosnode.py | 19 ++++++++++++++++++- + 1 file changed, 18 insertions(+), 1 deletion(-) + +diff --git a/soscollector/sosnode.py b/soscollector/sosnode.py +index a040244..405d05d 100644 +--- a/soscollector/sosnode.py ++++ b/soscollector/sosnode.py +@@ -68,10 +68,14 @@ class SosNode(): + self.connected = False + self.close_ssh_session() + return None ++ if self.local: ++ if self.check_in_container(): ++ self.host.containerized = False + self.log_debug("Host facts found to be %s" % + self.host.report_facts()) + self.get_hostname() +- self.create_sos_container() ++ if self.host.containerized: ++ self.create_sos_container() + self._load_sos_info() + + def _create_ssh_command(self): +@@ -84,6 +88,19 @@ class SosNode(): + return '{:<{}} : {}'.format(self._hostname, self.config['hostlen'] + 1, + msg) + ++ def check_in_container(self): ++ ''' ++ Tries to identify if we are currently running in a container or not. ++ ''' ++ if os.path.exists('/run/.containerenv'): ++ self.log_debug('Found /run/.containerenv. Running in container.') ++ return True ++ if os.environ.get('container') is not None: ++ self.log_debug("Found env var 'container'. Running in container") ++ return True ++ return False ++ ++ + def create_sos_container(self): + '''If the host is containerized, create the container we'll be using + ''' +-- +2.17.2 + diff --git a/SOURCES/sos-collector-none-cluster-fix.patch b/SOURCES/sos-collector-none-cluster-fix.patch new file mode 100644 index 0000000..8b7725e --- /dev/null +++ b/SOURCES/sos-collector-none-cluster-fix.patch @@ -0,0 +1,30 @@ +From a614ed8e5621f931d8ee76f1f59747a46144cb2d Mon Sep 17 00:00:00 2001 +From: Jake Hunsaker +Date: Thu, 4 Apr 2019 14:19:13 -0400 +Subject: [PATCH] [jbon] Fix typo in check_enabled() + +Fixes a typo in the check_enabled() method that could potentially +prevent sos-collector from correctly determining the cluster type on +python3 under certain circumstances. + +Signed-off-by: Jake Hunsaker +--- + soscollector/clusters/jbon.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/soscollector/clusters/jbon.py b/soscollector/clusters/jbon.py +index 28552ff..ea85ddf 100644 +--- a/soscollector/clusters/jbon.py ++++ b/soscollector/clusters/jbon.py +@@ -28,7 +28,7 @@ class jbon(Cluster): + def get_nodes(self): + return [] + +- def checK_enabled(self): ++ def check_enabled(self): + # This should never be called, but as insurance explicitly never + # allow this to be enabled via the determine_cluster() path + return False +-- +2.17.2 + diff --git a/SOURCES/sos-collector-old-pexpect.patch b/SOURCES/sos-collector-old-pexpect.patch new file mode 100644 index 0000000..93f5d92 --- /dev/null +++ b/SOURCES/sos-collector-old-pexpect.patch @@ -0,0 +1,24 @@ +diff --git a/soscollector/sosnode.py b/soscollector/sosnode.py +index 3aba0bf..ff130ba 100644 +--- a/soscollector/sosnode.py ++++ b/soscollector/sosnode.py +@@ -364,7 +364,9 @@ class SosNode(): + get_pty = True + if not self.local and not force_local: + cmd = "%s %s" % (self.ssh_cmd, quote(cmd)) +- res = pexpect.spawn(cmd, encoding='utf-8') ++ else: ++ cmd = "%s %s" % ('/bin/bash -c', quote(cmd)) ++ res = pexpect.spawn(cmd) + if need_root: + if self.config['need_sudo']: + res.sendline(self.config['sudo_pw']) +@@ -432,7 +434,7 @@ class SosNode(): + self.control_path, + self.config['ssh_user'], + self.address)) +- res = pexpect.spawn(cmd, encoding='utf-8') ++ res = pexpect.spawn(cmd) + + connect_expects = [ + u'Connected', diff --git a/SOURCES/sos-collector-quote-all-options.patch b/SOURCES/sos-collector-quote-all-options.patch deleted file mode 100644 index 741ab87..0000000 --- a/SOURCES/sos-collector-quote-all-options.patch +++ /dev/null @@ -1,247 +0,0 @@ -From c09e36927f74f955a325381a7d611ea270d04b65 Mon Sep 17 00:00:00 2001 -From: Jake Hunsaker -Date: Tue, 13 Nov 2018 12:30:45 -0500 -Subject: [PATCH] [global] Quote options locally everywhere - -An earlier commit passed cluster option values through pipes.quote() in -an effort to prevent command injection on the remote nodes, however this -did not capture every avenue through which a maliciously designed option -value or sos command could take. - -Now, quote the option values at the time of use, and also quote any sos -option that isn't an on/off toggle. - -Additionally, re-work how the ovirt/rhv database query is quoted and -filter out cluster/datacenter values that are likely to contain SQL -code, since we cannot rely on the driver to do this since we don't have -an actual connection to the database. - -Original discovery of issues and patch work thanks to Riccardo Schirone. - -Signed-off-by: Jake Hunsaker ---- - soscollector/clusters/kubernetes.py | 3 ++- - soscollector/clusters/ovirt.py | 32 ++++++++++++++++++++++------- - soscollector/configuration.py | 2 +- - soscollector/sos_collector.py | 19 +++++++++-------- - soscollector/sosnode.py | 13 ++++++------ - 5 files changed, 46 insertions(+), 23 deletions(-) - -diff --git a/soscollector/clusters/kubernetes.py b/soscollector/clusters/kubernetes.py -index 6c519f3..c5c2094 100644 ---- a/soscollector/clusters/kubernetes.py -+++ b/soscollector/clusters/kubernetes.py -@@ -13,6 +13,7 @@ - # with this program; if not, write to the Free Software Foundation, Inc., - # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -+from pipes import quote - from soscollector.clusters import Cluster - - -@@ -32,7 +33,7 @@ class kubernetes(Cluster): - def get_nodes(self): - self.cmd += ' get nodes' - if self.get_option('label'): -- self.cmd += ' -l %s ' % self.get_option('label') -+ self.cmd += ' -l %s ' % quote(self.get_option('label')) - res = self.exec_master_cmd(self.cmd) - if res['status'] == 0: - nodes = [] -diff --git a/soscollector/clusters/ovirt.py b/soscollector/clusters/ovirt.py -index 1c44c97..0a074ca 100644 ---- a/soscollector/clusters/ovirt.py -+++ b/soscollector/clusters/ovirt.py -@@ -16,6 +16,7 @@ - import os - import fnmatch - -+from pipes import quote - from soscollector.clusters import Cluster - from getpass import getpass - -@@ -31,6 +32,22 @@ class ovirt(Cluster): - ('no-hypervisors', False, 'Do not collect from hypervisors') - ] - -+ def _sql_scrub(self, val): -+ ''' -+ Manually sanitize SQL queries since we can't leave this up to the -+ driver since we do not have an actual DB connection -+ ''' -+ if not val: -+ return '%' -+ -+ invalid_chars = ['\x00', '\\', '\n', '\r', '\032', '"', '\''] -+ if any(x in invalid_chars for x in val): -+ self.log_warn("WARNING: Cluster option \'%s\' contains invalid " -+ "characters. Using '%%' instead." % val) -+ return '%' -+ -+ return val -+ - def setup(self): - self.pg_pass = False - if not self.get_option('no-database'): -@@ -38,13 +55,14 @@ class ovirt(Cluster): - self.format_db_cmd() - - def format_db_cmd(self): -- cluster = self.get_option('cluster') or '%' -- datacenter = self.get_option('datacenter') or '%' -- self.dbcmd = '/usr/share/ovirt-engine/dbscripts/engine-psql.sh -c \"' -- self.dbcmd += ("select host_name from vds_static where cluster_id in " -- "(select cluster_id from cluster where name like \'%s\'" -- " and storage_pool_id in (select id from storage_pool " -- "where name like \'%s\'))\"" % (cluster, datacenter)) -+ cluster = self._sql_scrub(self.get_option('cluster')) -+ datacenter = self._sql_scrub(self.get_option('datacenter')) -+ query = ("select host_name from vds_static where cluster_id in " -+ "(select cluster_id from cluster where name like '%s'" -+ " and storage_pool_id in (select id from storage_pool " -+ "where name like '%s'))" % (cluster, datacenter)) -+ self.dbcmd = ('/usr/share/ovirt-engine/dbscripts/engine-psql.sh ' -+ '-c {}'.format(quote(query))) - self.log_debug('Query command for ovirt DB set to: %s' % self.dbcmd) - - def get_nodes(self): -diff --git a/soscollector/configuration.py b/soscollector/configuration.py -index ea33835..7f2c3c7 100644 ---- a/soscollector/configuration.py -+++ b/soscollector/configuration.py -@@ -138,7 +138,7 @@ class Configuration(dict): - try: - # there are no instances currently where any cluster option - # should contain a legitimate space. -- value = pipes.quote(option.split('=')[1].split()[0]) -+ value = option.split('=')[1].split()[0] - except IndexError: - # conversion to boolean is handled during validation - value = 'True' -diff --git a/soscollector/sos_collector.py b/soscollector/sos_collector.py -index 70a3dc8..7331f63 100644 ---- a/soscollector/sos_collector.py -+++ b/soscollector/sos_collector.py -@@ -32,6 +32,7 @@ from concurrent.futures import ThreadPoolExecutor - from .sosnode import SosNode - from distutils.sysconfig import get_python_lib - from getpass import getpass -+from pipes import quote - from six.moves import input - from textwrap import fill - from soscollector import __version__ -@@ -360,32 +361,34 @@ this utility or remote systems that it connects to. - def configure_sos_cmd(self): - '''Configures the sosreport command that is run on the nodes''' - if self.config['sos_opt_line']: -- filt = ['&', '|', '>', '<'] -+ filt = ['&', '|', '>', '<', ';'] - if any(f in self.config['sos_opt_line'] for f in filt): - self.log_warn('Possible shell script found in provided sos ' - 'command. Ignoring --sos-cmd option entirely.') - self.config['sos_opt_line'] = None - else: - self.config['sos_cmd'] = '%s %s' % ( -- self.config['sos_cmd'], self.config['sos_opt_line']) -+ self.config['sos_cmd'], quote(self.config['sos_opt_line'])) - self.log_debug("User specified manual sosreport command. " - "Command set to %s" % self.config['sos_cmd']) - return True - if self.config['case_id']: -- self.config['sos_cmd'] += ' --case-id=%s' % self.config['case_id'] -+ self.config['sos_cmd'] += ' --case-id=%s' % ( -+ quote(self.config['case_id'])) - if self.config['alloptions']: - self.config['sos_cmd'] += ' --alloptions' - if self.config['verify']: - self.config['sos_cmd'] += ' --verify' - if self.config['log_size']: - self.config['sos_cmd'] += (' --log-size=%s' -- % self.config['log_size']) -+ % quote(self.config['log_size'])) - if self.config['sysroot']: -- self.config['sos_cmd'] += ' -s %s' % self.config['sysroot'] -+ self.config['sos_cmd'] += ' -s %s' % quote(self.config['sysroot']) - if self.config['chroot']: -- self.config['sos_cmd'] += ' -c %s' % self.config['chroot'] -+ self.config['sos_cmd'] += ' -c %s' % quote(self.config['chroot']) - if self.config['compression']: -- self.config['sos_cmd'] += ' -z %s' % self.config['compression'] -+ self.config['sos_cmd'] += ' -z %s' % ( -+ quote(self.config['compression'])) - self.log_debug('Initial sos cmd set to %s' % self.config['sos_cmd']) - - def connect_to_master(self): -@@ -408,7 +411,7 @@ this utility or remote systems that it connects to. - can still be run if the user sets a --cluster-type manually - ''' - checks = list(self.clusters.values()) -- for cluster in checks: -+ for cluster in self.clusters.values(): - checks.remove(cluster) - cluster.master = self.master - if cluster.check_enabled(): -diff --git a/soscollector/sosnode.py b/soscollector/sosnode.py -index fde1e6a..3790433 100644 ---- a/soscollector/sosnode.py -+++ b/soscollector/sosnode.py -@@ -26,6 +26,7 @@ import six - import time - - from distutils.version import LooseVersion -+from pipes import quote - from subprocess import Popen, PIPE - - -@@ -450,7 +451,7 @@ class SosNode(): - - label = self.determine_sos_label() - if label: -- self.sos_cmd = ' %s %s' % (self.sos_cmd, label) -+ self.sos_cmd = ' %s %s' % (self.sos_cmd, quote(label)) - - if self.config['sos_opt_line']: - return True -@@ -464,7 +465,7 @@ class SosNode(): - 'enabled but do not exist' % not_only) - only = self._fmt_sos_opt_list(self.config['only_plugins']) - if only: -- self.sos_cmd += ' --only-plugins=%s' % only -+ self.sos_cmd += ' --only-plugins=%s' % quote(only) - return True - - if self.config['skip_plugins']: -@@ -477,7 +478,7 @@ class SosNode(): - 'already not enabled' % not_skip) - skipln = self._fmt_sos_opt_list(skip) - if skipln: -- self.sos_cmd += ' --skip-plugins=%s' % skipln -+ self.sos_cmd += ' --skip-plugins=%s' % quote(skipln) - - if self.config['enable_plugins']: - # only run enable for plugins that are disabled -@@ -490,18 +491,18 @@ class SosNode(): - 'are already enabled or do not exist' % not_on) - enable = self._fmt_sos_opt_list(opts) - if enable: -- self.sos_cmd += ' --enable-plugins=%s' % enable -+ self.sos_cmd += ' --enable-plugins=%s' % quote(enable) - - if self.config['plugin_options']: - opts = [o for o in self.config['plugin_options'] - if self._plugin_exists(o.split('.')[0]) - and self._plugin_option_exists(o.split('=')[0])] - if opts: -- self.sos_cmd += ' -k %s' % ','.join(o for o in opts) -+ self.sos_cmd += ' -k %s' % quote(','.join(o for o in opts)) - - if self.config['preset']: - if self._preset_exists(self.config['preset']): -- self.sos_cmd += ' --preset=%s' % self.config['preset'] -+ self.sos_cmd += ' --preset=%s' % quote(self.config['preset']) - else: - self.log_debug('Requested to enable preset %s but preset does ' - 'not exist on node' % self.config['preset']) --- -2.17.2 - diff --git a/SOURCES/sos-collector-race-condition-cluster-loading.patch b/SOURCES/sos-collector-race-condition-cluster-loading.patch deleted file mode 100644 index 4cf0442..0000000 --- a/SOURCES/sos-collector-race-condition-cluster-loading.patch +++ /dev/null @@ -1,66 +0,0 @@ -From 79a29ec5111e65013020bf6fd34e962e923d8698 Mon Sep 17 00:00:00 2001 -From: Jake Hunsaker -Date: Tue, 6 Nov 2018 12:17:07 -0500 -Subject: [PATCH] [soscollector] Fix race condition in loading cluster profiles - -Fixes a race condition where a layered cluster profile would not be -properly loaded because the base profile would be checked first. - -Now, once a profile is matched, we check to see if there are any layered -profiles built on top of it. If there are, those are checked and if -their enablement check returns True, the layered profile is used instead -of the base profile. If the layered profile's check returns False, or if -no layered profiles are found, the base profile is still used. - -Resolves: #12 - -Signed-off-by: Jake Hunsaker ---- - soscollector/sos_collector.py | 31 +++++++++++++++++++++++++------ - 1 file changed, 25 insertions(+), 6 deletions(-) - -diff --git a/soscollector/sos_collector.py b/soscollector/sos_collector.py -index 60daa2e..e7f3ea7 100644 ---- a/soscollector/sos_collector.py -+++ b/soscollector/sos_collector.py -@@ -409,12 +409,31 @@ this utility or remote systems that it connects to. - If a list of nodes is given, this is not run, however the cluster - can still be run if the user sets a --cluster-type manually - ''' -- -- for clus in self.clusters: -- self.clusters[clus].master = self.master -- if self.clusters[clus].check_enabled(): -- self.config['cluster'] = self.clusters[clus] -- name = str(self.clusters[clus].__class__.__name__).lower() -+ checks = list(self.clusters.values()) -+ for cluster in checks: -+ checks.remove(cluster) -+ cluster.master = self.master -+ if cluster.check_enabled(): -+ cname = cluster.__class__.__name__ -+ self.log_debug("Installation matches %s, checking for layered " -+ "profiles" % cname) -+ for remaining in checks: -+ if issubclass(remaining.__class__, cluster.__class__): -+ rname = remaining.__class__.__name__ -+ self.log_debug("Layered profile %s found. " -+ "Checking installation" -+ % rname) -+ remaining.master = self.master -+ if remaining.check_enabled(): -+ self.log_debug("Installation matches both layered " -+ "profile %s and base profile %s, " -+ "setting cluster type to layered " -+ "profile" % (rname, cname)) -+ cluster = remaining -+ break -+ -+ self.config['cluster'] = cluster -+ name = str(cluster.__class__.__name__).lower() - self.config['cluster_type'] = name - self.log_info( - 'Cluster type set to %s' % self.config['cluster_type']) --- -2.17.2 - diff --git a/SOURCES/sos-collector-rhcos-image.patch b/SOURCES/sos-collector-rhcos-image.patch new file mode 100644 index 0000000..2640115 --- /dev/null +++ b/SOURCES/sos-collector-rhcos-image.patch @@ -0,0 +1,29 @@ +From 6dafb42064b06a80720c6017e2576aea9a7211de Mon Sep 17 00:00:00 2001 +From: Jake Hunsaker +Date: Tue, 9 Apr 2019 13:00:44 -0400 +Subject: [PATCH] [redhat] Update RHCOS image + +Updates the image used by RHCOS nodes to be the rhel8 image instead +of rhel7. + +Signed-off-by: Jake Hunsaker +--- + soscollector/hosts/redhat.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/soscollector/hosts/redhat.py b/soscollector/hosts/redhat.py +index 33cec1f..774ecec 100644 +--- a/soscollector/hosts/redhat.py ++++ b/soscollector/hosts/redhat.py +@@ -66,7 +66,7 @@ class RedHatCoreOSHost(RedHatHost): + + containerized = True + container_runtime = 'podman' +- container_image = 'registry.redhat.io/rhel7/support-tools' ++ container_image = 'registry.redhat.io/rhel8/support-tools' + sos_path_strip = '/host' + + def check_enabled(self, rel_string): +-- +2.17.2 + diff --git a/SOURCES/sos-collector-rhhiv-profile.patch b/SOURCES/sos-collector-rhhiv-profile.patch new file mode 100644 index 0000000..8a65aa9 --- /dev/null +++ b/SOURCES/sos-collector-rhhiv-profile.patch @@ -0,0 +1,234 @@ +From fb8c2af36672b5868f504bae0704392f9e9b44a5 Mon Sep 17 00:00:00 2001 +From: Jake Hunsaker +Date: Fri, 12 Apr 2019 14:45:01 -0400 +Subject: [PATCH 1/3] [clusters] Add a cluster_name class member for better + identification + +Adds a cluster_name class member to Cluster() so that clusters may +specify a well-defined name beyond the name/acronym used to enable the +cluster manually. + +Signed-off-by: Jake Hunsaker +--- + soscollector/clusters/__init__.py | 9 +++++++++ + soscollector/sos_collector.py | 3 +-- + soscollector/sosnode.py | 1 - + 3 files changed, 10 insertions(+), 3 deletions(-) + +diff --git a/soscollector/clusters/__init__.py b/soscollector/clusters/__init__.py +index b9d2418..c1bd360 100644 +--- a/soscollector/clusters/__init__.py ++++ b/soscollector/clusters/__init__.py +@@ -26,6 +26,7 @@ class Cluster(object): + sos_plugins = [] + sos_plugin_options = {} + sos_preset = '' ++ cluster_name = None + + def __init__(self, config): + '''This is the class that cluster profile should subclass in order to +@@ -50,6 +51,14 @@ class Cluster(object): + self.options = [] + self._get_options() + ++ @classmethod ++ def name(cls): ++ '''Returns the cluster's name as a string. ++ ''' ++ if cls.cluster_name: ++ return cls.cluster_name ++ return cls.__name__.lower() ++ + def _get_options(self): + '''Loads the options defined by a cluster and sets the default value''' + for opt in self.option_list: +diff --git a/soscollector/sos_collector.py b/soscollector/sos_collector.py +index fee48ab..54410a2 100644 +--- a/soscollector/sos_collector.py ++++ b/soscollector/sos_collector.py +@@ -554,8 +554,7 @@ this utility or remote systems that it connects to. + break + + self.config['cluster'] = cluster +- name = str(cluster.__class__.__name__).lower() +- self.config['cluster_type'] = name ++ self.config['cluster_type'] = cluster.name() + self.log_info( + 'Cluster type set to %s' % self.config['cluster_type']) + break +diff --git a/soscollector/sosnode.py b/soscollector/sosnode.py +index 405d05d..3aba0bf 100644 +--- a/soscollector/sosnode.py ++++ b/soscollector/sosnode.py +@@ -100,7 +100,6 @@ class SosNode(): + return True + return False + +- + def create_sos_container(self): + '''If the host is containerized, create the container we'll be using + ''' +-- +2.17.2 + + +From 8d94e9ee9162c1b7676822958a94dfcd727d6dc8 Mon Sep 17 00:00:00 2001 +From: Jake Hunsaker +Date: Fri, 12 Apr 2019 14:47:31 -0400 +Subject: [PATCH 2/3] [clusters] Add cluster_name where appropriate + +Adds a cluster_name to a few of the existing cluster profiles. + +Signed-off-by: Jake Hunsaker +--- + soscollector/clusters/kubernetes.py | 1 + + soscollector/clusters/ovirt.py | 2 ++ + 2 files changed, 3 insertions(+) + +diff --git a/soscollector/clusters/kubernetes.py b/soscollector/clusters/kubernetes.py +index c5c2094..e18ee71 100644 +--- a/soscollector/clusters/kubernetes.py ++++ b/soscollector/clusters/kubernetes.py +@@ -52,6 +52,7 @@ class kubernetes(Cluster): + + class openshift(kubernetes): + ++ cluster_name = 'OpenShift Container Platform' + packages = ('atomic-openshift',) + sos_preset = 'ocp' + cmd = 'oc' +diff --git a/soscollector/clusters/ovirt.py b/soscollector/clusters/ovirt.py +index 0a074ca..18cbf2e 100644 +--- a/soscollector/clusters/ovirt.py ++++ b/soscollector/clusters/ovirt.py +@@ -23,6 +23,7 @@ from getpass import getpass + + class ovirt(Cluster): + ++ cluster_name = 'oVirt' + packages = ('ovirt-engine',) + + option_list = [ +@@ -122,6 +123,7 @@ class ovirt(Cluster): + + class rhv(ovirt): + ++ cluster_name = 'Red Hat Virtualization' + packages = ('rhevm', 'rhvm') + sos_preset = 'rhv' + +-- +2.17.2 + + +From 4606699e9a460ebd6345444ae915ff8384619ed3 Mon Sep 17 00:00:00 2001 +From: Jake Hunsaker +Date: Tue, 16 Apr 2019 15:41:33 -0400 +Subject: [PATCH 3/3] [ovirt] Add RHHI-V support + +Adds support for RHHI-V environments which are RHV environments that +also use the hypervisors as gluster nodes. The new 'rhhi_virt' cluster +profile will be enabled when sos-collector is run against an environment +that is _both_ RHV and has nodes listed in the 'gluster_server' table in +the RHV database. Note that this means if community oVirt is in use, the +gluster bits enabled by 'rhhi_virt' will not be enabled for the oVirt +cluster type. + +Included with this change is making DB queries more programmatic, and +making minor stylistic changes to the main query used by get_nodes() to +allow easier reading of the SQL query being built. + +Finally, remove an unused import of getpass. + +Resolves: #21 + +Signed-off-by: Jake Hunsaker +--- + soscollector/clusters/ovirt.py | 44 ++++++++++++++++++++++++++-------- + 1 file changed, 34 insertions(+), 10 deletions(-) + +diff --git a/soscollector/clusters/ovirt.py b/soscollector/clusters/ovirt.py +index 18cbf2e..8697d2e 100644 +--- a/soscollector/clusters/ovirt.py ++++ b/soscollector/clusters/ovirt.py +@@ -18,13 +18,13 @@ import fnmatch + + from pipes import quote + from soscollector.clusters import Cluster +-from getpass import getpass + + + class ovirt(Cluster): + +- cluster_name = 'oVirt' ++ cluster_name = 'Community oVirt' + packages = ('ovirt-engine',) ++ db_exec = '/usr/share/ovirt-engine/dbscripts/engine-psql.sh -c' + + option_list = [ + ('no-database', False, 'Do not collect a database dump'), +@@ -33,6 +33,14 @@ class ovirt(Cluster): + ('no-hypervisors', False, 'Do not collect from hypervisors') + ] + ++ def _run_db_query(self, query): ++ ''' ++ Wrapper for running DB queries on the master. Any scrubbing of the ++ query should be done _before_ passing the query to this method. ++ ''' ++ cmd = "%s %s" % (self.db_exec, quote(query)) ++ return self.exec_master_cmd(cmd, need_root=True) ++ + def _sql_scrub(self, val): + ''' + Manually sanitize SQL queries since we can't leave this up to the +@@ -58,18 +66,16 @@ class ovirt(Cluster): + def format_db_cmd(self): + cluster = self._sql_scrub(self.get_option('cluster')) + datacenter = self._sql_scrub(self.get_option('datacenter')) +- query = ("select host_name from vds_static where cluster_id in " +- "(select cluster_id from cluster where name like '%s'" +- " and storage_pool_id in (select id from storage_pool " +- "where name like '%s'))" % (cluster, datacenter)) +- self.dbcmd = ('/usr/share/ovirt-engine/dbscripts/engine-psql.sh ' +- '-c {}'.format(quote(query))) +- self.log_debug('Query command for ovirt DB set to: %s' % self.dbcmd) ++ self.dbquery = ("SELECT host_name from vds_static where cluster_id in " ++ "(select cluster_id FROM cluster WHERE name like '%s'" ++ " and storage_pool_id in (SELECT id FROM storage_pool " ++ "WHERE name like '%s'))" % (cluster, datacenter)) ++ self.log_debug('Query command for ovirt DB set to: %s' % self.dbquery) + + def get_nodes(self): + if self.get_option('no-hypervisors'): + return [] +- res = self.exec_master_cmd(self.dbcmd, need_root=True) ++ res = self._run_db_query(self.dbquery) + if res['status'] == 0: + nodes = res['stdout'].splitlines()[2:-1] + return [n.split('(')[0].strip() for n in nodes] +@@ -134,3 +140,21 @@ class rhv(ovirt): + return 'rhvh' + else: + return 'rhelh' ++ ++ ++class rhhi_virt(rhv): ++ ++ cluster_name = 'Red Hat Hyperconverged Infrastructure - Virtualization' ++ sos_plugins = ('gluster',) ++ sos_plugin_options = {'gluster.dump': 'on'} ++ sos_preset = 'rhv' ++ ++ def check_enabled(self): ++ return (self.master.is_installed('rhvm') and self._check_for_rhhiv()) ++ ++ def _check_for_rhhiv(self): ++ ret = self._run_db_query('SELECT count(server_id) FROM gluster_server') ++ if ret['status'] == 0: ++ # if there are any entries in this table, RHHI-V is in use ++ return ret['stdout'].splitlines()[2].strip() != '0' ++ return False +-- +2.17.2 + diff --git a/SOURCES/sos-collector-setuptools.patch b/SOURCES/sos-collector-setuptools.patch new file mode 100644 index 0000000..f661895 --- /dev/null +++ b/SOURCES/sos-collector-setuptools.patch @@ -0,0 +1,13 @@ +diff --git a/setup.py b/setup.py +index 407ff65..9399865 100644 +--- a/setup.py ++++ b/setup.py +@@ -23,6 +23,7 @@ setup( + packages=find_packages(), + scripts=['sos-collector'], + data_files=[ +- ('share/licenses/sos-collector', ['LICENSE']), ++ # Workaround setuptools difference between upstream & RHEL ++ # ('share/licenses/sos-collector', ['LICENSE']), + ('share/man/man1/', ['man/en/sos-collector.1']) + ]) diff --git a/SPECS/sos-collector.spec b/SPECS/sos-collector.spec index f23d2b3..4e4434c 100644 --- a/SPECS/sos-collector.spec +++ b/SPECS/sos-collector.spec @@ -1,7 +1,7 @@ Summary: Capture sosreports from multiple nodes simultaneously Name: sos-collector -Version: 1.5 -Release: 3%{?dist} +Version: 1.7 +Release: 5%{?dist} Source0: http://people.redhat.com/jhunsake/sos-collector/%{name}-%{version}.tar.gz License: GPLv2 BuildArch: noarch @@ -10,25 +10,23 @@ Requires: sos >= 3.0 Obsoletes: clustersos < 1.2.2-2 Provides: clustersos = %{version}-%{release} -Patch0: sos-collector-bytes-conversion.patch -Patch1: sos-collector-fix-non-root-local.patch -Patch2: sos-collector-deb-support.patch -Patch3: sos-collector-case-id-prompt.patch -Patch4: sos-collector-fix-options-reporting.patch -Patch5: sos-collector-race-condition-cluster-loading.patch -Patch6: sos-collector-quote-all-options.patch +Patch0: sos-collector-setuptools.patch +Patch1: sos-collector-old-pexpect.patch +Patch2: sos-collector-none-cluster-fix.patch +Patch3: sos-collector-nested-container-fix.patch +Patch4: sos-collector-rhcos-image.patch +Patch5: sos-collector-rhhiv-profile.patch -%if 0%{?rhel} +%if 0%{?rhel} == 7 BuildRequires: python-devel -BuildRequires: python-paramiko -Requires: python-paramiko >= 2.0 +BuildRequires: python-setuptools Requires: python2-futures Requires: python-six +Requires: pexpect %else BuildRequires: python3-devel -BuildRequires: python3-paramiko -Requires: python3-paramiko >= 2.0 Requires: python3-six +Requires: python3-pexpect %endif @@ -46,10 +44,9 @@ is run on the nodes. %patch3 -p1 %patch4 -p1 %patch5 -p1 -%patch6 -p1 %build -%if 0%{?rhel} +%if 0%{?rhel} == 7 %py2_build %else %py3_build @@ -58,7 +55,7 @@ is run on the nodes. %install mkdir -p ${RPM_BUILD_ROOT}%{_mandir}/man1 install -p -m644 man/en/sos-collector.1 ${RPM_BUILD_ROOT}%{_mandir}/man1/ -%if 0%{?rhel} +%if 0%{?rhel} == 7 %py2_install %else %py3_install @@ -67,7 +64,7 @@ install -p -m644 man/en/sos-collector.1 ${RPM_BUILD_ROOT}%{_mandir}/man1/ %check -%if 0%{?rhel} +%if 0%{?rhel} == 7 %{__python2} setup.py test %else %{__python3} setup.py test @@ -75,7 +72,7 @@ install -p -m644 man/en/sos-collector.1 ${RPM_BUILD_ROOT}%{_mandir}/man1/ %files %{_bindir}/sos-collector -%if 0%{?rhel} +%if 0%{?rhel} == 7 %{python2_sitelib}/* %else %{python3_sitelib}/* @@ -85,11 +82,36 @@ install -p -m644 man/en/sos-collector.1 ${RPM_BUILD_ROOT}%{_mandir}/man1/ %doc LICENSE %changelog -* Tue Nov 13 2018 Jake Hunsaker - 1.5-3 -- Resolve race condition in cluster profile loading -- Quote all options globally -- RHBZ#1633515 -- RHBZ#1647955 +* Wed May 15 2019 Jake Hunsaker - 1.7-5 +- Add missing local fixes for older pexpect versions + +* Wed May 15 2019 Jake Hunsaker - 1.7-4 +- Correct handling of older pexpect versions + +* Tue Apr 23 2019 Jake Hunsaker - 1.7-3 +- Added RHHI-V cluster profile + +* Thu Apr 11 2019 Jake Hunsaker - 1.7-2 +- Fix 'none' cluster type enablement +- Update RHCOS image to use RHEL 8 support-tools +- Fix execution from within a container + +* Mon Apr 01 2019 Jake Hunsaker - 1.7-1 +- New upstream release +- Enhance container execution + +* Thu Mar 07 2019 Jake Hunsaker - 1.6-3 +- Fix local command execution +- Fix quoting for non-root commands +- Backport Satellite support +- Backport RHCOS support + +* Tue Dec 11 2018 Jake Hunsaker - 1.6-2 +- Handle older pexpect installations on RHEL 7 + +* Tue Dec 11 2018 Jake Hunsaker - 1.6-1 +- New upstream release +- Drop paramiko dependency, use OpenSSH ControlPersist instead. * Wed Nov 07 2018 Jake Hunsaker - 1.5-2 - Fix cluster option reporting