Blame SOURCES/sos-bz1515113-postgresql-from-scl.patch

50f7da
From 138bc243aa592cd628f6e41a1b3c35f1f01f3c37 Mon Sep 17 00:00:00 2001
50f7da
From: "Bryn M. Reeves" <bmr@redhat.com>
50f7da
Date: Tue, 8 Aug 2017 16:48:40 +0100
50f7da
Subject: [PATCH] [Plugin] add executable command enablement checks
50f7da
50f7da
Add a new list/tuple member to the Plugin class that contains a
50f7da
list of executable commands that will enable the plugin if any
50f7da
are present.
50f7da
50f7da
For example, a plugin:
50f7da
50f7da
  class MyPlugin(Plugin, RedHatPlugin):
50f7da
50f7da
      commands = ('mycmd1', 'mycmd2')
50f7da
50f7da
Will be automatically enabled if either 'mycmd1' or 'mycmd2'
50f7da
is present and executable in the policy defined PATH for the
50f7da
run.
50f7da
50f7da
Related: #1051.
50f7da
50f7da
Signed-off-by: Bryn M. Reeves <bmr@redhat.com>
50f7da
---
50f7da
 sos/plugins/__init__.py | 27 ++++++++++++++++++---------
50f7da
 1 file changed, 18 insertions(+), 9 deletions(-)
50f7da
50f7da
diff --git a/sos/plugins/__init__.py b/sos/plugins/__init__.py
50f7da
index a31297f6c..2d6f6ff58 100644
50f7da
--- a/sos/plugins/__init__.py
50f7da
+++ b/sos/plugins/__init__.py
50f7da
@@ -19,7 +19,7 @@
50f7da
 from __future__ import with_statement
50f7da
 
50f7da
 from sos.utilities import (sos_get_command_output, import_module, grep,
50f7da
-                           fileobj, tail)
50f7da
+                           fileobj, tail, is_executable)
50f7da
 import os
50f7da
 import glob
50f7da
 import re
50f7da
@@ -111,6 +111,7 @@ class Plugin(object):
50f7da
     version = 'unversioned'
50f7da
     packages = ()
50f7da
     files = ()
50f7da
+    commands = ()
50f7da
     archive = None
50f7da
     profiles = ()
50f7da
     sysroot = '/'
50f7da
@@ -865,23 +866,31 @@ def get_description(self):
50f7da
 
50f7da
     def check_enabled(self):
50f7da
         """This method will be used to verify that a plugin should execute
50f7da
-        given the condition of the underlying environment. The default
50f7da
-        implementation will return True if neither class.files or
50f7da
-        class.packages is specified. If either are specified the plugin will
50f7da
-        check for the existence of any of the supplied files or packages and
50f7da
-        return True if any exist. It is encouraged to override this method if
50f7da
-        this behavior isn't applicable.
50f7da
+        given the condition of the underlying environment.
50f7da
+
50f7da
+        The default implementation will return True if none of class.files,
50f7da
+        class.packages, nor class.commands is specified. If any of these is
50f7da
+        specified the plugin will check for the existence of any of the
50f7da
+        corresponding paths, packages or commands and return True if any
50f7da
+        are present.
50f7da
+
50f7da
+        For plugins with more complex enablement checks this method may be
50f7da
+        overridden.
50f7da
         """
50f7da
         # some files or packages have been specified for this package
50f7da
-        if self.files or self.packages:
50f7da
+        if any([self.files, self.packages, self.commands]):
50f7da
             if isinstance(self.files, six.string_types):
50f7da
                 self.files = [self.files]
50f7da
 
50f7da
             if isinstance(self.packages, six.string_types):
50f7da
                 self.packages = [self.packages]
50f7da
 
50f7da
+            if isinstance(self.commands, six.string_types):
50f7da
+                self.commands = [self.commands]
50f7da
+
50f7da
             return (any(os.path.exists(fname) for fname in self.files) or
50f7da
-                    any(self.is_installed(pkg) for pkg in self.packages))
50f7da
+                    any(self.is_installed(pkg) for pkg in self.packages) or
50f7da
+                    any(is_executable(cmd) for cmd in self.commands))
50f7da
         return True
50f7da
 
50f7da
     def default_enabled(self):
50f7da
From 947e7089c58ac239bc2fd535ac0c77f93f11b895 Mon Sep 17 00:00:00 2001
50f7da
From: Pavel Moravec <pmoravec@redhat.com>
50f7da
Date: Tue, 3 Oct 2017 15:39:43 +0200
50f7da
Subject: [PATCH] [plugins] Add class SCLPlugin for Software Collections
50f7da
 support
50f7da
50f7da
Related to #900 and #1090
50f7da
50f7da
Original author: Bohuslav Kabrda <bkabrda@redhat.com>
50f7da
Signed-off-by: Pavel Moravec <pmoravec@redhat.com>
50f7da
---
50f7da
 sos/plugins/__init__.py | 109 ++++++++++++++++++++++++++++++++++++++++++++++--
50f7da
 1 file changed, 106 insertions(+), 3 deletions(-)
50f7da
50f7da
diff --git a/sos/plugins/__init__.py b/sos/plugins/__init__.py
50f7da
index 61a3083e3..540d46596 100644
50f7da
--- a/sos/plugins/__init__.py
50f7da
+++ b/sos/plugins/__init__.py
50f7da
@@ -884,6 +884,10 @@ def check_enabled(self):
50f7da
         corresponding paths, packages or commands and return True if any
50f7da
         are present.
50f7da
 
50f7da
+        For SCLPlugin subclasses, it will check whether the plugin can be run
50f7da
+        for any of installed SCLs. If so, it will store names of these SCLs
50f7da
+        on the plugin class in addition to returning True.
50f7da
+
50f7da
         For plugins with more complex enablement checks this method may be
50f7da
         overridden.
50f7da
         """
50f7da
@@ -898,11 +902,34 @@ def check_enabled(self):
50f7da
             if isinstance(self.commands, six.string_types):
50f7da
                 self.commands = [self.commands]
50f7da
 
50f7da
-            return (any(os.path.exists(fname) for fname in self.files) or
50f7da
-                    any(self.is_installed(pkg) for pkg in self.packages) or
50f7da
-                    any(is_executable(cmd) for cmd in self.commands))
50f7da
+            if isinstance(self, SCLPlugin):
50f7da
+                # save SCLs that match files or packages
50f7da
+                type(self)._scls_matched = []
50f7da
+                for scl in self._get_scls():
50f7da
+                    files = [f % {"scl_name": scl} for f in self.files]
50f7da
+                    packages = [p % {"scl_name": scl} for p in self.packages]
50f7da
+                    commands = [c % {"scl_name": scl} for c in self.commands]
50f7da
+                    if self._files_pkgs_or_cmds_present(files,
50f7da
+                                                        packages,
50f7da
+                                                        commands):
50f7da
+                        type(self)._scls_matched.append(scl)
50f7da
+                return len(type(self)._scls_matched) > 0
50f7da
+
50f7da
+            return self._files_pkgs_or_cmds_present(self.files,
50f7da
+                                                    self.packages,
50f7da
+                                                    self.commands)
50f7da
+
50f7da
+        if isinstance(self, SCLPlugin):
50f7da
+            # if files and packages weren't specified, we take all SCLs
50f7da
+            type(self)._scls_matched = self._get_scls()
50f7da
+
50f7da
         return True
50f7da
 
50f7da
+    def _files_pkgs_or_cmds_present(self, files, packages, commands):
50f7da
+            return (any(os.path.exists(fname) for fname in files) or
50f7da
+                    any(self.is_installed(pkg) for pkg in packages) or
50f7da
+                    any(is_executable(cmd) for cmd in commands))
50f7da
+
50f7da
     def default_enabled(self):
50f7da
         """This decides whether a plugin should be automatically loaded or
50f7da
         only if manually specified in the command line."""
50f7da
@@ -979,6 +1006,82 @@ class RedHatPlugin(object):
50f7da
     pass
50f7da
 
50f7da
 
50f7da
+class SCLPlugin(RedHatPlugin):
50f7da
+    """Superclass for plugins operating on Software Collections (SCLs).
50f7da
+
50f7da
+    Subclasses of this plugin class can specify class.files and class.packages
50f7da
+    using "%(scl_name)s" interpolation. The plugin invoking mechanism will try
50f7da
+    to match these against all found SCLs on the system. SCLs that do match
50f7da
+    class.files or class.packages are then accessible via self.scls_matched
50f7da
+    when the plugin is invoked.
50f7da
+
50f7da
+    Additionally, this plugin class provides "add_cmd_output_scl" (run
50f7da
+    a command in context of given SCL), and "add_copy_spec_scl" and
50f7da
+    "add_copy_spec_limit_scl" (copy package from file system of given SCL).
50f7da
+
50f7da
+    For example, you can implement a plugin that will list all global npm
50f7da
+    packages in every SCL that contains "npm" package:
50f7da
+
50f7da
+    class SCLNpmPlugin(Plugin, SCLPlugin):
50f7da
+        packages = ("%(scl_name)s-npm",)
50f7da
+
50f7da
+        def setup(self):
50f7da
+            for scl in self.scls_matched:
50f7da
+                self.add_cmd_output_scl(scl, "npm ls -g --json")
50f7da
+    """
50f7da
+
50f7da
+    @property
50f7da
+    def scls_matched(self):
50f7da
+        if not hasattr(type(self), '_scls_matched'):
50f7da
+            type(self)._scls_matched = []
50f7da
+        return type(self)._scls_matched
50f7da
+
50f7da
+    def _get_scls(self):
50f7da
+        output = sos_get_command_output("scl -l")["output"]
50f7da
+        return [scl.strip() for scl in output.splitlines()]
50f7da
+
50f7da
+    def add_cmd_output_scl(self, scl, cmds, **kwargs):
50f7da
+        """Same as add_cmd_output, except that it wraps command in
50f7da
+        "scl enable" call.
50f7da
+        """
50f7da
+        if isinstance(cmds, six.string_types):
50f7da
+            cmds = [cmds]
50f7da
+        scl_cmds = []
50f7da
+        scl_cmd_tpl = "scl enable %s \"%s\""
50f7da
+        for cmd in cmds:
50f7da
+            scl_cmds.append(scl_cmd_tpl % (scl, cmd))
50f7da
+        self.add_cmd_output(scl_cmds, **kwargs)
50f7da
+
50f7da
+    # config files for Software Collections are under /etc/opt/rh/${scl} and
50f7da
+    # var files are under /var/opt/rh/${scl}. So we need to insert the paths
50f7da
+    # after the appropriate root dir.
50f7da
+    def convert_copyspec_scl(self, scl, copyspec):
50f7da
+        for rootdir in ['etc', 'var']:
50f7da
+            p = re.compile('^/%s/' % rootdir)
50f7da
+            copyspec = p.sub('/%s/opt/rh/%s/' % (rootdir, scl), copyspec)
50f7da
+        return copyspec
50f7da
+
50f7da
+    def add_copy_spec_scl(self, scl, copyspecs):
50f7da
+        """Same as add_copy_spec, except that it prepends path to SCL root
50f7da
+        to "copyspecs".
50f7da
+        """
50f7da
+        if isinstance(copyspecs, six.string_types):
50f7da
+            copyspecs = [copyspecs]
50f7da
+        scl_copyspecs = []
50f7da
+        for copyspec in copyspecs:
50f7da
+            scl_copyspecs.append(self.convert_copyspec_scl(scl, copyspec))
50f7da
+        self.add_copy_spec(scl_copyspecs)
50f7da
+
50f7da
+    def add_copy_spec_limit_scl(self, scl, copyspec, **kwargs):
50f7da
+        """Same as add_copy_spec_limit, except that it prepends path to SCL
50f7da
+        root to "copyspec".
50f7da
+        """
50f7da
+        self.add_copy_spec_limit(
50f7da
+            self.convert_copyspec_scl(scl, copyspec),
50f7da
+            **kwargs
50f7da
+        )
50f7da
+
50f7da
+
50f7da
 class PowerKVMPlugin(RedHatPlugin):
50f7da
     """Tagging class for IBM PowerKVM Linux"""
50f7da
     pass
50f7da
From 62d6435198403abb65b925e7bf63fc39f5394e6d Mon Sep 17 00:00:00 2001
50f7da
From: Pavel Moravec <pmoravec@redhat.com>
50f7da
Date: Mon, 16 Oct 2017 13:20:44 +0200
50f7da
Subject: [PATCH] [postgresql] Collect data for postgreSQL from RHSCL
50f7da
50f7da
Collect postgreSQL data also when postgreSQL is installed from
50f7da
Red Hat Software Collections.
50f7da
50f7da
Resolves: #1090
50f7da
50f7da
Signed-off-by: Pavel Moravec <pmoravec@redhat.com>
50f7da
---
50f7da
 sos/plugins/postgresql.py | 117 +++++++++++++++++++++++++---------------------
50f7da
 1 file changed, 65 insertions(+), 52 deletions(-)
50f7da
50f7da
diff --git a/sos/plugins/postgresql.py b/sos/plugins/postgresql.py
50f7da
index 345532223..45c87e890 100644
50f7da
--- a/sos/plugins/postgresql.py
50f7da
+++ b/sos/plugins/postgresql.py
50f7da
@@ -1,3 +1,4 @@
50f7da
+# Copyright (C) 2017 Red Hat, Inc., Pavel Moravec <pmoravec@redhat.com>
50f7da
 # Copyright (C) 2014 Red Hat, Inc., Sandro Bonazzola <sbonazzo@redhat.com>
50f7da
 # Copyright (C) 2013 Chris J Arges <chris.j.arges@canonical.com>
50f7da
 # Copyright (C) 2012-2013 Red Hat, Inc., Bryn M. Reeves <bmr@redhat.com>
50f7da
@@ -20,7 +21,8 @@
50f7da
 import os
50f7da
 import tempfile
50f7da
 
50f7da
-from sos.plugins import Plugin, RedHatPlugin, UbuntuPlugin, DebianPlugin
50f7da
+from sos.plugins import (Plugin, RedHatPlugin, UbuntuPlugin, DebianPlugin,
50f7da
+                         SCLPlugin)
50f7da
 from sos.utilities import find
50f7da
 
50f7da
 
50f7da
@@ -45,47 +47,43 @@ class PostgreSQL(Plugin):
50f7da
         ('dbport', 'database server port number', '', '5432')
50f7da
     ]
50f7da
 
50f7da
-    def pg_dump(self):
50f7da
-        dest_file = os.path.join(self.tmp_dir, "sos_pgdump.tar")
50f7da
-        # We're only modifying this for ourself and our children so there
50f7da
-        # is no need to save and restore environment variables if the user
50f7da
-        # decided to pass the password on the command line.
50f7da
-        if self.get_option("password") is not False:
50f7da
-            os.environ["PGPASSWORD"] = str(self.get_option("password"))
50f7da
-
50f7da
-        if self.get_option("dbhost"):
50f7da
-            cmd = "pg_dump -U %s -h %s -p %s -w -f %s -F t %s" % (
50f7da
-                self.get_option("username"),
50f7da
-                self.get_option("dbhost"),
50f7da
-                self.get_option("dbport"),
50f7da
-                dest_file,
50f7da
-                self.get_option("dbname")
50f7da
-            )
50f7da
-        else:
50f7da
-            cmd = "pg_dump -C -U %s -w -f %s -F t %s " % (
50f7da
-                self.get_option("username"),
50f7da
-                dest_file,
50f7da
-                self.get_option("dbname")
50f7da
-            )
50f7da
-
50f7da
-        result = self.call_ext_prog(cmd)
50f7da
-        if (result['status'] == 0):
50f7da
-            self.add_copy_spec(dest_file)
50f7da
-        else:
50f7da
-            self._log_error(
50f7da
-                "Unable to execute pg_dump. Error(%s)" % (result['output'])
50f7da
-            )
50f7da
-            self.add_alert(
50f7da
-                "ERROR: Unable to execute pg_dump. Error(%s)" %
50f7da
-                (result['output'])
50f7da
-            )
50f7da
-
50f7da
-    def setup(self):
50f7da
+    def pg_dump(self, pg_dump_command="pg_dump", filename="sos_pgdump.tar"):
50f7da
         if self.get_option("dbname"):
50f7da
             if self.get_option("password") or "PGPASSWORD" in os.environ:
50f7da
                 self.tmp_dir = tempfile.mkdtemp()
50f7da
-                self.pg_dump()
50f7da
-            else:
50f7da
+                dest_file = os.path.join(self.tmp_dir, filename)
50f7da
+                # We're only modifying this for ourself and our children so
50f7da
+                # there is no need to save and restore environment variables if
50f7da
+                # the user decided to pass the password on the command line.
50f7da
+                if self.get_option("password") is not False:
50f7da
+                    os.environ["PGPASSWORD"] = str(self.get_option("password"))
50f7da
+
50f7da
+                if self.get_option("dbhost"):
50f7da
+                    cmd = "%s -U %s -h %s -p %s -w -f %s -F t %s" % (
50f7da
+                        pg_dump_command,
50f7da
+                        self.get_option("username"),
50f7da
+                        self.get_option("dbhost"),
50f7da
+                        self.get_option("dbport"),
50f7da
+                        dest_file,
50f7da
+                        self.get_option("dbname")
50f7da
+                    )
50f7da
+                else:
50f7da
+                    cmd = "%s -C -U %s -w -f %s -F t %s " % (
50f7da
+                        pg_dump_command,
50f7da
+                        self.get_option("username"),
50f7da
+                        dest_file,
50f7da
+                        self.get_option("dbname")
50f7da
+                    )
50f7da
+
50f7da
+                result = self.call_ext_prog(cmd)
50f7da
+                if (result['status'] == 0):
50f7da
+                    self.add_copy_spec(dest_file)
50f7da
+                else:
50f7da
+                    self._log_info(
50f7da
+                        "Unable to execute pg_dump. Error(%s)" %
50f7da
+                        (result['output'])
50f7da
+                    )
50f7da
+            else:  # no password in env or options
50f7da
                 self.soslog.warning(
50f7da
                     "password must be supplied to dump a database."
50f7da
                 )
50f7da
@@ -93,6 +91,9 @@ def setup(self):
50f7da
                     "WARN: password must be supplied to dump a database."
50f7da
                 )
50f7da
 
50f7da
+    def setup(self):
50f7da
+        self.pg_dump()
50f7da
+
50f7da
     def postproc(self):
50f7da
         import shutil
50f7da
         if self.tmp_dir:
50f7da
@@ -105,33 +106,45 @@ def postproc(self):
50f7da
                 self.add_alert("ERROR: Unable to remove %s." % (self.tmp_dir))
50f7da
 
50f7da
 
50f7da
-class RedHatPostgreSQL(PostgreSQL, RedHatPlugin):
50f7da
+class RedHatPostgreSQL(PostgreSQL, SCLPlugin):
50f7da
+
50f7da
+    packages = ('postgresql', 'rh-postgresql95-postgresql-server', )
50f7da
 
50f7da
     def setup(self):
50f7da
         super(RedHatPostgreSQL, self).setup()
50f7da
 
50f7da
+        scl = "rh-postgresql95"
50f7da
+        pghome = self.get_option("pghome")
50f7da
+
50f7da
         # Copy PostgreSQL log files.
50f7da
-        for filename in find("*.log", self.get_option("pghome")):
50f7da
+        for filename in find("*.log", pghome):
50f7da
+            self.add_copy_spec(filename)
50f7da
+        for filename in find("*.log", self.convert_copyspec_scl(scl, pghome)):
50f7da
             self.add_copy_spec(filename)
50f7da
+
50f7da
         # Copy PostgreSQL config files.
50f7da
-        for filename in find("*.conf", self.get_option("pghome")):
50f7da
+        for filename in find("*.conf", pghome):
50f7da
+            self.add_copy_spec(filename)
50f7da
+        for filename in find("*.conf", self.convert_copyspec_scl(scl, pghome)):
50f7da
             self.add_copy_spec(filename)
50f7da
 
50f7da
-        self.add_copy_spec(
50f7da
-            os.path.join(
50f7da
-                self.get_option("pghome"),
50f7da
-                "data",
50f7da
-                "PG_VERSION"
50f7da
-            )
50f7da
-        )
50f7da
-        self.add_copy_spec(
50f7da
-            os.path.join(
50f7da
-                self.get_option("pghome"),
50f7da
+        self.add_copy_spec(os.path.join(pghome, "data", "PG_VERSION"))
50f7da
+        self.add_copy_spec(os.path.join(pghome, "data", "postmaster.opts"))
50f7da
+
50f7da
+        self.add_copy_spec_scl(scl, os.path.join(pghome, "data", "PG_VERSION"))
50f7da
+        self.add_copy_spec_scl(scl, os.path.join(
50f7da
+                pghome,
50f7da
                 "data",
50f7da
                 "postmaster.opts"
50f7da
             )
50f7da
         )
50f7da
 
50f7da
+        if scl in self.scls_matched:
50f7da
+            self.pg_dump(
50f7da
+                pg_dump_command="scl enable rh-postgresql95 -- pg_dump",
50f7da
+                filename="sos_scl_pgdump.tar"
50f7da
+            )
50f7da
+
50f7da
 
50f7da
 class DebianPostgreSQL(PostgreSQL, DebianPlugin, UbuntuPlugin):
50f7da
 
50f7da
From 0b93d1f69ccfcc76e1896ea0e5ff7854be69be13 Mon Sep 17 00:00:00 2001
50f7da
From: Pavel Moravec <pmoravec@redhat.com>
50f7da
Date: Sat, 25 Nov 2017 12:47:35 +0100
50f7da
Subject: [PATCH] [plugins] set proper PATH for SCL commands
50f7da
50f7da
As SCL packages are deployed under /opt/${provider}/${scl}/,
50f7da
calling a SCL command needs that prefix in any path in PATH.
50f7da
50f7da
Consequently, distro-specific SCL default path prefix of the provider must be
50f7da
defined in sos policies.
50f7da
50f7da
Relevant to: #1154
50f7da
50f7da
Signed-off-by: Pavel Moravec <pmoravec@redhat.com>
50f7da
---
50f7da
 sos/plugins/__init__.py  | 37 ++++++++++++++++++++++++++++++-------
50f7da
 sos/policies/__init__.py |  4 ++++
50f7da
 sos/policies/redhat.py   |  1 +
50f7da
 3 files changed, 35 insertions(+), 7 deletions(-)
50f7da
50f7da
diff --git a/sos/plugins/__init__.py b/sos/plugins/__init__.py
50f7da
index aa69b19d..2a8bc516 100644
50f7da
--- a/sos/plugins/__init__.py
50f7da
+++ b/sos/plugins/__init__.py
50f7da
@@ -1066,25 +1066,48 @@ class SCLPlugin(RedHatPlugin):
50f7da
         output = sos_get_command_output("scl -l")["output"]
50f7da
         return [scl.strip() for scl in output.splitlines()]
50f7da
 
50f7da
+    def convert_cmd_scl(self, scl, cmd):
50f7da
+        """wrapping command in "scl enable" call and adds proper PATH
50f7da
+        """
50f7da
+        # load default SCL prefix to PATH
50f7da
+        prefix = self.policy().get_default_scl_prefix()
50f7da
+        # read prefix from /etc/scl/prefixes/${scl} and strip trailing '\n'
50f7da
+        try:
50f7da
+            prefix = open('/etc/scl/prefixes/%s' % scl, 'r').read()\
50f7da
+                     .rstrip('\n')
50f7da
+        except Exception as e:
50f7da
+            self._log_error("Failed to find prefix for SCL %s, using %s"
50f7da
+                            % (scl, prefix))
50f7da
+
50f7da
+        # expand PATH by equivalent prefixes under the SCL tree
50f7da
+        path = os.environ["PATH"]
50f7da
+        for p in path.split(':'):
50f7da
+            path = '%s/%s%s:%s' % (prefix, scl, p, path)
50f7da
+
50f7da
+        scl_cmd = "scl enable %s \"PATH=%s %s\"" % (scl, path, cmd)
50f7da
+        return scl_cmd
50f7da
+
50f7da
     def add_cmd_output_scl(self, scl, cmds, **kwargs):
50f7da
         """Same as add_cmd_output, except that it wraps command in
50f7da
-        "scl enable" call.
50f7da
+        "scl enable" call and sets proper PATH.
50f7da
         """
50f7da
         if isinstance(cmds, six.string_types):
50f7da
             cmds = [cmds]
50f7da
         scl_cmds = []
50f7da
-        scl_cmd_tpl = "scl enable %s \"%s\""
50f7da
         for cmd in cmds:
50f7da
-            scl_cmds.append(scl_cmd_tpl % (scl, cmd))
50f7da
+            scl_cmds.append(convert_cmd_scl(scl, cmd))
50f7da
         self.add_cmd_output(scl_cmds, **kwargs)
50f7da
 
50f7da
-    # config files for Software Collections are under /etc/opt/rh/${scl} and
50f7da
-    # var files are under /var/opt/rh/${scl}. So we need to insert the paths
50f7da
-    # after the appropriate root dir.
50f7da
+    # config files for Software Collections are under /etc/${prefix}/${scl} and
50f7da
+    # var files are under /var/${prefix}/${scl} where the ${prefix} is distro
50f7da
+    # specific path. So we need to insert the paths after the appropriate root
50f7da
+    # dir.
50f7da
     def convert_copyspec_scl(self, scl, copyspec):
50f7da
+        scl_prefix = self.policy().get_default_scl_prefix()
50f7da
         for rootdir in ['etc', 'var']:
50f7da
             p = re.compile('^/%s/' % rootdir)
50f7da
-            copyspec = p.sub('/%s/opt/rh/%s/' % (rootdir, scl), copyspec)
50f7da
+            copyspec = p.sub('/%s/%s/%s/' % (rootdir, scl_prefix, scl),
50f7da
+                             copyspec)
50f7da
         return copyspec
50f7da
 
50f7da
     def add_copy_spec_scl(self, scl, copyspecs):
50f7da
diff --git a/sos/policies/__init__.py b/sos/policies/__init__.py
50f7da
index dffd801c..dc043105 100644
50f7da
--- a/sos/policies/__init__.py
50f7da
+++ b/sos/policies/__init__.py
50f7da
@@ -194,6 +194,7 @@ No changes will be made to system configuration.
50f7da
     vendor_url = "http://www.example.com/"
50f7da
     vendor_text = ""
50f7da
     PATH = ""
50f7da
+    default_scl_prefix = ""
50f7da
 
50f7da
     _in_container = False
50f7da
     _host_sysroot = '/'
50f7da
@@ -271,6 +272,9 @@ No changes will be made to system configuration.
50f7da
             return tempfile.gettempdir()
50f7da
         return opt_tmp_dir
50f7da
 
50f7da
+    def get_default_scl_prefix(self):
50f7da
+        return self.default_scl_prefix
50f7da
+
50f7da
     def match_plugin(self, plugin_classes):
50f7da
         if len(plugin_classes) > 1:
50f7da
             for p in plugin_classes:
50f7da
diff --git a/sos/policies/redhat.py b/sos/policies/redhat.py
50f7da
index c7449439..2dfe0589 100644
50f7da
--- a/sos/policies/redhat.py
50f7da
+++ b/sos/policies/redhat.py
50f7da
@@ -44,6 +44,7 @@ class RedHatPolicy(LinuxPolicy):
50f7da
     _rpmq_cmd = 'rpm -qa --queryformat "%{NAME}|%{VERSION}\\n"'
50f7da
     _in_container = False
50f7da
     _host_sysroot = '/'
50f7da
+    default_scl_prefix = '/opt/rh'
50f7da
 
50f7da
     def __init__(self, sysroot=None):
50f7da
         super(RedHatPolicy, self).__init__(sysroot=sysroot)
50f7da
-- 
50f7da
2.13.6
50f7da
50f7da
From 419ebe48ea408b6596ff4d7d9837079dc3057fcf Mon Sep 17 00:00:00 2001
50f7da
From: Pavel Moravec <pmoravec@redhat.com>
50f7da
Date: Sat, 25 Nov 2017 12:58:16 +0100
50f7da
Subject: [PATCH] [postgresql] Call SCL pg_dump with proper path
50f7da
50f7da
Also stop storing pg_dump in an auxiliary tempdir but under regular
50f7da
sos_commands/postgresql directory.
50f7da
50f7da
Resolves: #1154
50f7da
50f7da
Signed-off-by: Pavel Moravec <pmoravec@redhat.com>
50f7da
---
50f7da
 sos/plugins/postgresql.py | 43 ++++++++-----------------------------------
50f7da
 1 file changed, 8 insertions(+), 35 deletions(-)
50f7da
50f7da
diff --git a/sos/plugins/postgresql.py b/sos/plugins/postgresql.py
50f7da
index 45c87e89..9ba696be 100644
50f7da
--- a/sos/plugins/postgresql.py
50f7da
+++ b/sos/plugins/postgresql.py
50f7da
@@ -34,8 +34,6 @@ class PostgreSQL(Plugin):
50f7da
 
50f7da
     packages = ('postgresql',)
50f7da
 
50f7da
-    tmp_dir = None
50f7da
-
50f7da
     password_warn_text = " (password visible in process listings)"
50f7da
 
50f7da
     option_list = [
50f7da
@@ -47,11 +45,9 @@ class PostgreSQL(Plugin):
50f7da
         ('dbport', 'database server port number', '', '5432')
50f7da
     ]
50f7da
 
50f7da
-    def pg_dump(self, pg_dump_command="pg_dump", filename="sos_pgdump.tar"):
50f7da
+    def do_pg_dump(self, scl=None, filename="pgdump.tar"):
50f7da
         if self.get_option("dbname"):
50f7da
             if self.get_option("password") or "PGPASSWORD" in os.environ:
50f7da
-                self.tmp_dir = tempfile.mkdtemp()
50f7da
-                dest_file = os.path.join(self.tmp_dir, filename)
50f7da
                 # We're only modifying this for ourself and our children so
50f7da
                 # there is no need to save and restore environment variables if
50f7da
                 # the user decided to pass the password on the command line.
50f7da
@@ -59,30 +55,21 @@ class PostgreSQL(Plugin):
50f7da
                     os.environ["PGPASSWORD"] = str(self.get_option("password"))
50f7da
 
50f7da
                 if self.get_option("dbhost"):
50f7da
-                    cmd = "%s -U %s -h %s -p %s -w -f %s -F t %s" % (
50f7da
-                        pg_dump_command,
50f7da
+                    cmd = "pg_dump -U %s -h %s -p %s -w -F t %s" % (
50f7da
                         self.get_option("username"),
50f7da
                         self.get_option("dbhost"),
50f7da
                         self.get_option("dbport"),
50f7da
-                        dest_file,
50f7da
                         self.get_option("dbname")
50f7da
                     )
50f7da
                 else:
50f7da
-                    cmd = "%s -C -U %s -w -f %s -F t %s " % (
50f7da
-                        pg_dump_command,
50f7da
+                    cmd = "pg_dump -C -U %s -w -F t %s " % (
50f7da
                         self.get_option("username"),
50f7da
-                        dest_file,
50f7da
                         self.get_option("dbname")
50f7da
                     )
50f7da
 
50f7da
-                result = self.call_ext_prog(cmd)
50f7da
-                if (result['status'] == 0):
50f7da
-                    self.add_copy_spec(dest_file)
50f7da
-                else:
50f7da
-                    self._log_info(
50f7da
-                        "Unable to execute pg_dump. Error(%s)" %
50f7da
-                        (result['output'])
50f7da
-                    )
50f7da
+                if scl is not None:
50f7da
+                    cmd = self.convert_cmd_scl(scl, cmd)
50f7da
+                self.add_cmd_output(cmd, suggest_filename=filename)
50f7da
             else:  # no password in env or options
50f7da
                 self.soslog.warning(
50f7da
                     "password must be supplied to dump a database."
50f7da
@@ -92,18 +79,7 @@ class PostgreSQL(Plugin):
50f7da
                 )
50f7da
 
50f7da
     def setup(self):
50f7da
-        self.pg_dump()
50f7da
-
50f7da
-    def postproc(self):
50f7da
-        import shutil
50f7da
-        if self.tmp_dir:
50f7da
-            try:
50f7da
-                shutil.rmtree(self.tmp_dir)
50f7da
-            except shutil.Error:
50f7da
-                self.soslog.exception(
50f7da
-                    "Unable to remove %s." % (self.tmp_dir)
50f7da
-                )
50f7da
-                self.add_alert("ERROR: Unable to remove %s." % (self.tmp_dir))
50f7da
+        self.do_pg_dump()
50f7da
 
50f7da
 
50f7da
 class RedHatPostgreSQL(PostgreSQL, SCLPlugin):
50f7da
@@ -140,10 +116,7 @@ class RedHatPostgreSQL(PostgreSQL, SCLPlugin):
50f7da
         )
50f7da
 
50f7da
         if scl in self.scls_matched:
50f7da
-            self.pg_dump(
50f7da
-                pg_dump_command="scl enable rh-postgresql95 -- pg_dump",
50f7da
-                filename="sos_scl_pgdump.tar"
50f7da
-            )
50f7da
+            self.do_pg_dump(scl=scl, filename="pgdump-scl-%s.tar" % scl)
50f7da
 
50f7da
 
50f7da
 class DebianPostgreSQL(PostgreSQL, DebianPlugin, UbuntuPlugin):
50f7da
-- 
50f7da
2.13.6
50f7da
50f7da
From ede50e9cb4a5f2755eaeaf608fb2b3708f911422 Mon Sep 17 00:00:00 2001
50f7da
From: Pavel Moravec <pmoravec@redhat.com>
50f7da
Date: Wed, 20 Dec 2017 11:47:33 +0100
50f7da
Subject: [PATCH] [plugins] allow add_cmd_output to collect binary output
50f7da
50f7da
If a command output is a true binary data, allow add_cmd_output to
50f7da
collect the raw content and dont try to decode it as UTF-8.
50f7da
50f7da
Resolves: #1169
50f7da
50f7da
Signed-off-by: Pavel Moravec <pmoravec@redhat.com>
50f7da
---
50f7da
 sos/archive.py            | 16 ++++++++++------
50f7da
 sos/plugins/__init__.py   | 33 ++++++++++++++++++---------------
50f7da
 sos/plugins/postgresql.py |  3 ++-
50f7da
 sos/utilities.py          |  5 +++--
50f7da
 4 files changed, 33 insertions(+), 24 deletions(-)
50f7da
50f7da
diff --git a/sos/archive.py b/sos/archive.py
50f7da
index 607312a71..4bc2bedea 100644
50f7da
--- a/sos/archive.py
50f7da
+++ b/sos/archive.py
50f7da
@@ -82,7 +82,7 @@ def log_debug(self, msg):
50f7da
     def add_file(self, src, dest=None):
50f7da
         raise NotImplementedError
50f7da
 
50f7da
-    def add_string(self, content, dest):
50f7da
+    def add_string(self, content, dest, treat_binary):
50f7da
         raise NotImplementedError
50f7da
 
50f7da
     def add_link(self, source, link_name):
50f7da
@@ -198,12 +198,14 @@ def add_file(self, src, dest=None):
50f7da
         self.log_debug("added %s to FileCacheArchive '%s'" %
50f7da
                        (file_name, self._archive_root))
50f7da
 
50f7da
-    def add_string(self, content, dest):
50f7da
+    def add_string(self, content, dest, treat_binary=False):
50f7da
         src = dest
50f7da
         dest = self.dest_path(dest)
50f7da
         self._check_path(dest)
50f7da
-        f = codecs.open(dest, 'w', encoding='utf-8')
50f7da
-        if isinstance(content, bytes):
50f7da
+        f = codecs.open(dest,
50f7da
+                        'wb' if treat_binary else 'w',
50f7da
+                        encoding=None if treat_binary else 'utf-8')
50f7da
+        if isinstance(content, bytes) and not treat_binary:
50f7da
             content = content.decode('utf8', 'ignore')
50f7da
         f.write(content)
50f7da
         if os.path.exists(src):
50f7da
@@ -212,8 +214,10 @@ def add_string(self, content, dest):
50f7da
             except OSError as e:
50f7da
                 self.log_error(
50f7da
                     "Unable to add '%s' to FileCacheArchive: %s" % (dest, e))
50f7da
-        self.log_debug("added string at '%s' to FileCacheArchive '%s'"
50f7da
-                       % (src, self._archive_root))
50f7da
+        self.log_debug("added %sstring at '%s' to FileCacheArchive '%s'"
50f7da
+                       % ('binary ' if treat_binary else '',
50f7da
+                           src,
50f7da
+                           self._archive_root))
50f7da
 
50f7da
     def add_link(self, source, link_name):
50f7da
         dest = self.dest_path(link_name)
50f7da
diff --git a/sos/plugins/__init__.py b/sos/plugins/__init__.py
50f7da
index 2a8bc516e..156f5ed36 100644
50f7da
--- a/sos/plugins/__init__.py
50f7da
+++ b/sos/plugins/__init__.py
50f7da
@@ -587,7 +587,8 @@ def getmtime(path):
50f7da
                 self.archive.add_link(link_path, _file)
50f7da
 
50f7da
     def get_command_output(self, prog, timeout=300, stderr=True,
50f7da
-                           chroot=True, runat=None, env=None):
50f7da
+                           chroot=True, runat=None, env=None,
50f7da
+                           treat_binary=False):
50f7da
         if chroot or self.commons['cmdlineopts'].chroot == 'always':
50f7da
             root = self.sysroot
50f7da
         else:
50f7da
@@ -595,7 +596,7 @@ def get_command_output(self, prog, timeout=300, stderr=True,
50f7da
 
50f7da
         result = sos_get_command_output(prog, timeout=timeout, stderr=stderr,
50f7da
                                         chroot=root, chdir=runat,
50f7da
-                                        env=env)
50f7da
+                                        env=env, treat_binary=treat_binary)
50f7da
 
50f7da
         if result['status'] == 124:
50f7da
             self._log_warn("command '%s' timed out after %ds"
50f7da
@@ -586,7 +586,8 @@ class Plugin(object):
50f7da
                                % (prog.split()[0], root))
50f7da
                 return self.get_command_output(prog, timeout=timeout,
50f7da
                                                chroot=False, runat=runat,
50f7da
-                                               env=env)
50f7da
+                                               env=env,
50f7da
+                                               treat_binary=treat_binary)
50f7da
             self._log_debug("could not run '%s': command not found" % prog)
50f7da
         return result
50f7da
 
50f7da
@@ -632,14 +634,14 @@ def check_ext_prog(self, prog):
50f7da
 
50f7da
     def _add_cmd_output(self, cmd, suggest_filename=None,
50f7da
                         root_symlink=None, timeout=300, stderr=True,
50f7da
-                        chroot=True, runat=None, env=None):
50f7da
+                        chroot=True, runat=None, env=None, treat_binary=False):
50f7da
         """Internal helper to add a single command to the collection list."""
50f7da
         cmdt = (
50f7da
             cmd, suggest_filename,
50f7da
             root_symlink, timeout, stderr,
50f7da
-            chroot, runat, env
50f7da
+            chroot, runat, env, treat_binary
50f7da
         )
50f7da
-        _tuplefmt = "('%s', '%s', '%s', %s, '%s', '%s', '%s', '%s')"
50f7da
+        _tuplefmt = "('%s', '%s', '%s', %s, '%s', '%s', '%s', '%s', '%s')"
50f7da
         _logstr = "packed command tuple: " + _tuplefmt
50f7da
         self._log_debug(_logstr % cmdt)
50f7da
         self.collect_cmds.append(cmdt)
50f7da
@@ -647,7 +649,7 @@ def _add_cmd_output(self, cmd, suggest_filename=None,
50f7da
 
50f7da
     def add_cmd_output(self, cmds, suggest_filename=None,
50f7da
                        root_symlink=None, timeout=300, stderr=True,
50f7da
-                       chroot=True, runat=None, env=None):
50f7da
+                       chroot=True, runat=None, env=None, treat_binary=False):
50f7da
         """Run a program or a list of programs and collect the output"""
50f7da
         if isinstance(cmds, six.string_types):
50f7da
             cmds = [cmds]
50f7da
@@ -656,7 +658,7 @@ def add_cmd_output(self, cmds, suggest_filename=None,
50f7da
         for cmd in cmds:
50f7da
             self._add_cmd_output(cmd, suggest_filename,
50f7da
                                  root_symlink, timeout, stderr,
50f7da
-                                 chroot, runat, env)
50f7da
+                                 chroot, runat, env, treat_binary)
50f7da
 
50f7da
     def get_cmd_output_path(self, name=None, make=True):
50f7da
         """Return a path into which this module should store collected
50f7da
@@ -683,14 +684,15 @@ class Plugin(object):
50f7da
 
50f7da
     def get_cmd_output_now(self, exe, suggest_filename=None,
50f7da
                            root_symlink=False, timeout=300, stderr=True,
50f7da
-                           chroot=True, runat=None, env=None):
50f7da
+                           chroot=True, runat=None, env=None,
50f7da
+                           treat_binary=False):
50f7da
         """Execute a command and save the output to a file for inclusion in the
50f7da
         report.
50f7da
         """
50f7da
         start = time()
50f7da
         result = self.get_command_output(exe, timeout=timeout, stderr=stderr,
50f7da
                                          chroot=chroot, runat=runat,
50f7da
-                                         env=env)
50f7da
+                                         env=env, treat_binary=treat_binary)
50f7da
         # 126 means 'found but not executable'
50f7da
         if result['status'] == 126 or result['status'] == 127:
50f7da
             return None
50f7da
@@ -729,7 +732,7 @@ def get_cmd_output_now(self, exe, suggest_filename=None,
50f7da
             outfn = self._make_command_filename(exe)
50f7da
 
50f7da
         outfn_strip = outfn[len(self.commons['cmddir'])+1:]
50f7da
-        self.archive.add_string(result['output'], outfn)
50f7da
+        self.archive.add_string(result['output'], outfn, treat_binary)
50f7da
         if root_symlink:
50f7da
             self.archive.add_link(outfn, root_symlink)
50f7da
 
50f7da
@@ -839,16 +842,16 @@ def _collect_cmd_output(self):
50f7da
                 timeout,
50f7da
                 stderr,
50f7da
                 chroot, runat,
50f7da
-                env
50f7da
+                env, treat_binary
50f7da
             ) = progs[0]
50f7da
-            self._log_debug("unpacked command tuple: " +
50f7da
-                            "('%s', '%s', '%s', %s, '%s', '%s', '%s', '%s')" %
50f7da
-                            progs[0])
50f7da
+            self._log_debug(("unpacked command tuple: " +
50f7da
+                            "('%s', '%s', '%s', %s, '%s', '%s', '%s', '%s'," +
50f7da
+                            "'%s')") % progs[0])
50f7da
             self._log_info("collecting output of '%s'" % prog)
50f7da
             self.get_cmd_output_now(prog, suggest_filename=suggest_filename,
50f7da
                                     root_symlink=root_symlink, timeout=timeout,
50f7da
                                     stderr=stderr, chroot=chroot, runat=runat,
50f7da
-                                    env=env)
50f7da
+                                    env=env, treat_binary=treat_binary)
50f7da
 
50f7da
     def _collect_strings(self):
50f7da
         for string, file_name in self.copy_strings:
50f7da
diff --git a/sos/plugins/postgresql.py b/sos/plugins/postgresql.py
50f7da
index 9ba696be2..07db22fdd 100644
50f7da
--- a/sos/plugins/postgresql.py
50f7da
+++ b/sos/plugins/postgresql.py
50f7da
@@ -69,7 +69,8 @@ def do_pg_dump(self, scl=None, filename="pgdump.tar"):
50f7da
 
50f7da
                 if scl is not None:
50f7da
                     cmd = self.convert_cmd_scl(scl, cmd)
50f7da
-                self.add_cmd_output(cmd, suggest_filename=filename)
50f7da
+                self.add_cmd_output(cmd, suggest_filename=filename,
50f7da
+                                    treat_binary=True)
50f7da
             else:  # no password in env or options
50f7da
                 self.soslog.warning(
50f7da
                     "password must be supplied to dump a database."
50f7da
diff --git a/sos/utilities.py b/sos/utilities.py
50f7da
index 55bb1dc96..a9040ba28 100644
50f7da
--- a/sos/utilities.py
50f7da
+++ b/sos/utilities.py
50f7da
@@ -110,7 +110,8 @@ def is_executable(command):
50f7da
 
50f7da
 
50f7da
 def sos_get_command_output(command, timeout=300, stderr=False,
50f7da
-                           chroot=None, chdir=None, env=None):
50f7da
+                           chroot=None, chdir=None, env=None,
50f7da
+                           treat_binary=False):
50f7da
     """Execute a command and return a dictionary of status and output,
50f7da
     optionally changing root or current working directory before
50f7da
     executing command.
50f7da
@@ -164,7 +165,7 @@ def _child_prep_fn():
50f7da
 
50f7da
     return {
50f7da
         'status': p.returncode,
50f7da
-        'output': stdout.decode('utf-8', 'ignore')
50f7da
+        'output': stdout if treat_binary else stdout.decode('utf-8', 'ignore')
50f7da
     }
50f7da
 
50f7da