diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | Makefile | 14 | ||||
-rw-r--r-- | debian/changelog | 5 | ||||
-rw-r--r-- | debian/compat | 1 | ||||
-rw-r--r-- | debian/control | 15 | ||||
-rw-r--r-- | debian/copyright | 0 | ||||
-rw-r--r-- | debian/pyversions | 1 | ||||
-rwxr-xr-x | debian/rules | 7 | ||||
-rw-r--r-- | sos/__init__.py.in | 74 | ||||
-rw-r--r-- | sos/plugins/__init__.py | 155 | ||||
-rw-r--r-- | sos/plugins/as7.py | 179 | ||||
-rw-r--r-- | sos/plugins/dpkg.py (renamed from sos/__init__.py) | 32 | ||||
-rw-r--r-- | sos/policies/Makefile | 20 | ||||
-rw-r--r-- | sos/policies/__init__.py | 7 | ||||
-rw-r--r-- | sos/policies/debian.py | 156 | ||||
-rw-r--r-- | sos/policies/ubuntu.py | 31 | ||||
-rw-r--r-- | sos/policies/windows.py | 10 | ||||
-rw-r--r-- | sos/sosreport.py | 3 | ||||
-rw-r--r-- | sos/utilities.py | 12 | ||||
-rw-r--r-- | tests/option_tests.py | 34 |
20 files changed, 562 insertions, 195 deletions
@@ -7,3 +7,4 @@ tags buildjar/ gpgkeys/rhsupport.* rpm-build/* +sos/__init__.py @@ -5,9 +5,9 @@ NAME = sos VERSION = $(shell echo `awk '/^Version:/ {print $$2}' sos.spec`) RELEASE = $(shell echo `awk '/^Release:/ {gsub(/\%.*/,""); print $2}' sos.spec`) -REPO = http://svn.fedorahosted.org/svn/sos +REPO = http://github.com/sosreport -SUBDIRS = po sos sos/plugins +SUBDIRS = po sos sos/plugins sos/policies PYFILES = $(wildcard *.py) # OS X via brew # MSGCAT = /usr/local/Cellar/gettext/0.18.1.1/bin/msgcat @@ -33,7 +33,7 @@ ZIP_DEST = $(SRC_BUILD)/$(ARCHIVE_NAME) build: for d in $(SUBDIRS); do make -C $$d; [ $$? = 0 ] || exit 1 ; done -install: +install: updateversion mkdir -p $(DESTDIR)/usr/sbin mkdir -p $(DESTDIR)/usr/share/man/man1 mkdir -p $(DESTDIR)/usr/share/man/man5 @@ -47,9 +47,11 @@ install: install -m644 LICENSE README TODO $(DESTDIR)/usr/share/$(NAME)/. install -m644 $(NAME).conf $(DESTDIR)/etc/$(NAME).conf install -m644 gpgkeys/rhsupport.pub $(DESTDIR)/usr/share/$(NAME)/. - sed 's/@SOSVERSION@/$(VERSION)/g' < sos/__init__.py > sos/__init__.py for d in $(SUBDIRS); do make DESTDIR=`cd $(DESTDIR); pwd` -C $$d install; [ $$? = 0 ] || exit 1; done +updateversion: + sed 's/@SOSVERSION@/$(VERSION)/g' sos/__init__.py.in > sos/__init__.py + $(NAME)-$(VERSION).tar.gz: clean gpgkey @mkdir -p $(ARCHIVE_DIR) @tar -cv sosreport sos doc man po sos.conf TODO LICENSE README sos.spec Makefile | tar -x -C $(ARCHIVE_DIR) @@ -58,7 +60,7 @@ $(NAME)-$(VERSION).tar.gz: clean gpgkey @tar Ccvzf $(RPM_BUILD_DIR) $(RPM_BUILD_DIR)/$(NAME)-$(VERSION).tar.gz $(NAME)-$(VERSION) clean: - @rm -fv *~ .*~ changenew ChangeLog.old $(NAME)-$(VERSION).tar.gz sosreport.1.gz + @rm -fv *~ .*~ changenew ChangeLog.old $(NAME)-$(VERSION).tar.gz sosreport.1.gz sos.conf.5.gz @rm -rf rpm-build @for i in `find . -iname *.pyc`; do \ rm $$i; \ @@ -85,7 +87,7 @@ po: clean cp $(PO_DIR)/sos_en.properties $(PO_DIR)/sos_en_US.properties cp $(PO_DIR)/sos_en.properties $(PO_DIR)/sos.properties -as7: po +as7: po updateversion cp -r sos/* $(SRC_BUILD)/sos/ find $(SRC_BUILD)/sos/plugins/ -not -name "*as7.py" -not -name "*__init__.py" -type f -delete diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 00000000..a788b652 --- /dev/null +++ b/debian/changelog @@ -0,0 +1,5 @@ +sosreport (2.3) unstable; urgency=low + + * Initial release. + + -- Adam Stokes <adam.stokes@canonical.com> Mon, 20 Feb 2012 16:41:39 +0000 diff --git a/debian/compat b/debian/compat new file mode 100644 index 00000000..45a4fb75 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +8 diff --git a/debian/control b/debian/control new file mode 100644 index 00000000..1625c69e --- /dev/null +++ b/debian/control @@ -0,0 +1,15 @@ +Source: sosreport +Maintainer: Adam Stokes <adam.stokes@canonical.com> +Section: python +Priority: optional +Standards-Version: 3.9.2 +Build-Depends: debhelper (>= 8), python-support, python (>=2.7), gettext + +Package: sosreport +Architecture: any +Depends: ${python:Depends}, ${misc:Depends} +Description: A set of tools to gather troubleshooting information from a system + Sos is a set of tools that gathers information about system + hardware and configuration. The information can then be used for + diagnostic purposes and debugging. Sos is commonly used to help + support technicians and developers. diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/debian/copyright diff --git a/debian/pyversions b/debian/pyversions new file mode 100644 index 00000000..3ad2293e --- /dev/null +++ b/debian/pyversions @@ -0,0 +1 @@ +2.7- diff --git a/debian/rules b/debian/rules new file mode 100755 index 00000000..3e0dfd04 --- /dev/null +++ b/debian/rules @@ -0,0 +1,7 @@ +#!/usr/bin/make -f + +DH_ALWAYS_EXCLUDE=.git + +%: + dh $@ + diff --git a/sos/__init__.py.in b/sos/__init__.py.in new file mode 100644 index 00000000..f703a834 --- /dev/null +++ b/sos/__init__.py.in @@ -0,0 +1,74 @@ +## Copyright 2010 Red Hat, Inc. +## Author: Adam Stokes <astokes@fedoraproject.org> + +## 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +""" +This module houses the i18n setup and message function. The default is to use +gettext to internationalize messages. If the client calls set_i18n and passes a +path to a resource bundle the _ method will be changed to use java +ResourceBundle code to present messages. +""" + +__version__ = "@SOSVERSION@" + +import gettext +gettext_dir = "/usr/share/locale" +gettext_app = "sos" + +gettext.bindtextdomain(gettext_app, gettext_dir) + +def _default(msg): + return gettext.dgettext(gettext_app, msg) + +_sos = _default + +def _get_classloader(jarfile): + """Makes a new classloader loaded with the jarfile. This is useful since it + seems very difficult to get jars added to the correct classpath for + ResourceBundle.getBundle to find.""" + from java.net import URLClassLoader, URL + from java.io import File + import jarray + + file_ = File(jarfile) + ary = jarray.array([file_.toURL()], URL) + classloader = URLClassLoader.newInstance(ary) + return classloader + +def set_i18n(path=None, basename="sos.po.sos"): + """Use this method to change the default i18n behavior from gettext to java + ResourceBundle.getString. This is really only useful when using jython. + Path is expected to be the path to a jarfile that contains the translation + files (.properties)""" + + # Since we are trying to modify the module-level _sos variable + # we have to declare it global + global _sos + + try: + from java.util import ResourceBundle, Locale + + rb = ResourceBundle.getBundle(basename, + Locale.getDefault(), _get_classloader(path)) + + def _java(msg): + try: + return rb.getString(msg).encode('utf-8') + except: + return msg + _sos = _java + except: + pass diff --git a/sos/plugins/__init__.py b/sos/plugins/__init__.py index ebb76ea3..a1d38f45 100644 --- a/sos/plugins/__init__.py +++ b/sos/plugins/__init__.py @@ -39,7 +39,12 @@ from time import time from itertools import * from collections import deque import logging +import urllib2 +try: + import json +except ImportError: + import simplejson as json def commonPrefix(l1, l2, common = None): """ @@ -299,14 +304,16 @@ class Plugin(object): any of the option names is returned.""" def _check(key): - if hasattr(key, "__iter__"): + if hasattr(optionname, "__iter__"): return key in optionname else: return key == optionname for name, parms in izip(self.optNames, self.optParms): if _check(name): - return parms['enabled'] + val = parms['enabled'] + if val != None: + return val for key, value in self.cInfo.get('global_plugin_options', {}).iteritems(): if _check(key): @@ -437,6 +444,8 @@ class Plugin(object): if not (status == 127 or status == 32512): # if not command_not_found outfn_strip = outfn[len(self.cInfo['cmddir'])+1:] self.archive.add_string(shout, outfn) + if root_symlink: + self.archive.add_link(outfn, root_symlink) else: self.soslog.debug("could not run command: %s" % exe) outfn = None @@ -601,10 +610,152 @@ class RedHatPlugin(object): """Tagging class to indicate that this plugin works with Red Hat Linux""" pass +class UbuntuPlugin(object): + """Tagging class to indicate that this plugin works with Ubuntu Linux""" + pass + +class DebianPlugin(object): + """Tagging class to indicate that this plugin works with Debian Linux""" + pass + class IndependentPlugin(object): """Tagging class that indicates this plugin can run on any platform""" pass +class AS7Mixin(object): + """A mixin class that adds some helpful methods for AS7 related plugins""" + + class Request(object): + + def __init__(self, resource, operation="read-resource", parameters=None): + self.resource = resource + self.operation = operation + if parameters: + self.parameters = parameters + else: + self.parameters = {} + + def url_parts(self): + """Generator function to split a url into (key, value) tuples. The url + should contain an even number of pairs. In the case of / the generator + will immediately stop iteration.""" + parts = self.resource.strip("/").split("/") + + if parts == ['']: + raise StopIteration + + while parts: + yield (parts.pop(0), parts.pop(0)) + + def get_jboss_home(self): + return self.getOption(('home', 'as7_home')) or os.getenv("JBOSS_HOME", None) + + def query(self, request_obj): + try: + return self.query_java(request_obj) + except Exception, e: + self.addAlert("JBOSS API call failed, falling back to HTTP: %s" % e) + return self.query_http(request_obj) + + def _get_opt(self, first, second, default=None): + val = self.getOption(first) + if val: + return val + val = self.getOption(second) + if val: + return val + return default + + def query_java(self, request_obj): + from org.jboss.dmr import ModelNode + controller_client = self.getOption('controller_client_proxy') + if not controller_client: + raise AttributeError("Controller Client is not available") + + request = ModelNode() + request.get("operation").set(request_obj.operation) + + for key, val in request_obj.url_parts(): + request.get('address').add(key,val) + + if request_obj.parameters: + for key, value in request_obj.parameters.iteritems(): + request.get(key).set(value) + + return controller_client.execute(request).toJSONString(True) + + def query_http(self, request_obj, postdata=None): + host = self._get_opt('host', 'as7_host') + port = self._get_opt('port', 'as7_port') + + username = self._get_opt('user', 'as7_user') + password = self._get_opt('pass', 'as7_pass') + + uri = "http://%s:%s/management" % (host,port) + + json_data = {'operation': request_obj.operation, + 'address': []} + + for key, val in request_obj.url_parts(): + json_data['address'].append({key:val}) + + for key, val in request_obj.parameters.iteritems(): + json_data[key] = val + + postdata = json.dumps(json_data) + headers = {'Content-Type': 'application/json', + 'Accept': 'application/json'} + + opener = urllib2.build_opener() + + if username and password: + passwd_manager = urllib2.HTTPPasswordMgrWithDefaultRealm() + passwd_manager.add_password(realm="ManagementRealm", + uri=uri, + user=username, + passwd=password) + digest_auth_handler = urllib2.HTTPDigestAuthHandler(passwd_manager) + basic_auth_handler = urllib2.HTTPBasicAuthHandler(passwd_manager) + + opener.add_handler(digest_auth_handler) + opener.add_handler(basic_auth_handler) + + req = urllib2.Request(uri, data=postdata, headers=headers) + + try: + resp = opener.open(req) + return resp.read() + except Exception, e: + err_msg = "Could not query url: %s; error: %s" % (uri, e) + self.addAlert(err_msg) + return err_msg + + def set_domain_info(self, parameters=None): + """This function will add host controller and server instance + name data if it is present to the desired resource. This is to support + domain-mode operation in AS7""" + host_controller_name = self.getOption("as7_host_controller_name") + server_name = self.getOption("as7_server_name") + + if host_controller_name and server_name: + if not parameters: + parameters = {} + + parameters['host'] = host_controller_name + parameters['server'] = server_name + + return parameters + + + def resource_to_file(self, resource=None, parameters=None, operation='read-resource', outfile=None): + parameters = self.set_domain_info(parameters) + + r = self.Request(resource=resource, + parameters=parameters, + operation=operation) + self.addStringAsFile(self.query(r), filename=outfile) + + def import_plugin(name): """Import name as a module and return a list of all classes defined in that module""" diff --git a/sos/plugins/as7.py b/sos/plugins/as7.py index c31696ef..e56d91dd 100644 --- a/sos/plugins/as7.py +++ b/sos/plugins/as7.py @@ -7,38 +7,10 @@ import tempfile from xml.etree import ElementTree from itertools import chain -try: - import json -except ImportError: - import simplejson as json - -from sos.plugins import Plugin, IndependentPlugin +from sos.plugins import Plugin, IndependentPlugin, AS7Mixin from sos.utilities import DirTree, find, checksum -class Request(object): - - def __init__(self, resource, operation="read-resource", parameters=None): - self.resource = resource - self.operation = operation - if parameters: - self.parameters = parameters - else: - self.parameters = {} - - def url_parts(self): - """Generator function to split a url into (key, value) tuples. The url - should contain an even number of pairs. In the case of / the generator - will immediately stop iteration.""" - parts = self.resource.strip("/").split("/") - - if parts == ['']: - raise StopIteration - - while parts: - yield (parts.pop(0), parts.pop(0)) - - -class AS7(Plugin, IndependentPlugin): +class AS7(Plugin, IndependentPlugin, AS7Mixin): """JBoss related information """ @@ -69,29 +41,18 @@ class AS7(Plugin, IndependentPlugin): self.addAlert(msg) def __getJbossHome(self): - """ - Will attempt to locate the JBoss installation dir in either jboss.home or - scrape it from the environment variable JBOSS_HOME. - Returns: - True JBOSS_HOME is set and the path exists. False otherwise. - """ - if self.getOption("home"): - ## Prefer this value first over the ENV - self.__jbossHome=self.getOption(("home", "as7_home")) - self.addAlert("INFO: The JBoss installation directory supplied to SOS is " + - self.__jbossHome) - elif os.environ.get("JBOSS_HOME"): - self.__jbossHome=os.environ.get("JBOSS_HOME") - self.addAlert("INFO: The JBoss installation directory (i.e. JBOSS_HOME) from the environment is " + - self.__jbossHome) - else: + self.__jbossHome = self.get_jboss_home() + if not self.__jbossHome: self.addAlert("ERROR: The JBoss installation directory was not supplied.\ The JBoss SOS plug-in cannot continue.") return False + self.addAlert("INFO: The JBoss installation directory supplied to SOS is " + + self.__jbossHome) return True + def __getMd5(self, file): """Returns the MD5 sum of the specified file.""" @@ -122,8 +83,8 @@ class AS7(Plugin, IndependentPlugin): return manifest def __getStdJarInfo(self): - found = False jar_info_list = [] + for jarFile in find("*.jar", self.__jbossHome): checksum = self.__getMd5(jarFile) manifest = self.__getManifest(jarFile) @@ -131,8 +92,8 @@ class AS7(Plugin, IndependentPlugin): if manifest: manifest = manifest.strip() jar_info_list.append((path, checksum, manifest)) - found = True - if found: + + if jar_info_list: jar_info_list.sort() self.addStringAsFile("\n".join([ "%s\n%s\n%s\n" % (name, checksum, manifest) @@ -141,118 +102,21 @@ class AS7(Plugin, IndependentPlugin): else: self.addAlert("WARN: No jars found in JBoss system path (" + self.__jbossHome + ").") - def query(self, request_obj): - try: - return self.query_java(request_obj) - except Exception, e: - self.addAlert("JBOSS API call failed, falling back to HTTP: %s" % e) - return self.query_http(request_obj) - - def query_java(self, request_obj): - from org.jboss.dmr import ModelNode - controller_client = self.getOption('controller_client_proxy') - if not controller_client: - raise AttributeError("Controller Client is not available") - - request = ModelNode() - request.get("operation").set(request_obj.operation) - - for key, val in request_obj.url_parts(): - request.get('address').add(key,val) - - if request_obj.parameters: - for key, value in request_obj.parameters.iteritems(): - request.get(key).set(value) - - return controller_client.execute(request).toJSONString(True) - - def query_http(self, request_obj, postdata=None): - host = self.getOption(('host', 'as7_host')) - port = self.getOption(('port', 'as7_port')) - - username = self.getOption(('user', 'as7_user'), None) - password = self.getOption(('pass', 'as7_pass'), None) - - uri = "http://%s:%s/management" % (host,port) - - json_data = {'operation': request_obj.operation, - 'address': []} - - for key, val in request_obj.url_parts(): - json_data['address'].append({key:val}) - - for key, val in request_obj.parameters.iteritems(): - json_data[key] = val - - postdata = json.dumps(json_data) - headers = {'Content-Type': 'application/json', - 'Accept': 'application/json'} - - opener = urllib2.build_opener() - - if username and password: - passwd_manager = urllib2.HTTPPasswordMgrWithDefaultRealm() - passwd_manager.add_password(realm="ManagementRealm", - uri=uri, - user=username, - passwd=password) - digest_auth_handler = urllib2.HTTPDigestAuthHandler(passwd_manager) - basic_auth_handler = urllib2.HTTPBasicAuthHandler(passwd_manager) - - opener.add_handler(digest_auth_handler) - opener.add_handler(basic_auth_handler) - - req = urllib2.Request(uri, data=postdata, headers=headers) - - try: - resp = opener.open(req) - return resp.read() - except Exception, e: - err_msg = "Could not query url: %s; error: %s" % (uri, e) - self.addAlert(err_msg) - return err_msg - - def _set_domain_info(self, parameters=None): - """This function will add host controller and server instance - name data if it is present to the desired resource. This is to support - domain-mode operation in AS7""" - host_controller_name = self.getOption("as7_host_controller_name") - server_name = self.getOption("as7_server_name") - - if host_controller_name and server_name: - if not parameters: - parameters = {} - - parameters['host'] = host_controller_name - parameters['server'] = server_name - - return parameters - - - def _resource_to_file(self, resource=None, parameters=None, operation='read-resource', outfile=None): - parameters = self._set_domain_info(parameters) - - r = Request(resource=resource, - parameters=parameters, - operation=operation) - self.addStringAsFile(self.query(r), filename=outfile) - - def get_online_data(self): """ This function co-locates calls to the management api that gather information from a running system. """ - self._resource_to_file(resource="/", + self.resource_to_file(resource="/", parameters={"recursive": "true"}, outfile="configuration.json") - self._resource_to_file(resource="/core-service/service-container", + self.resource_to_file(resource="/core-service/service-container", operation="dump-services", outfile="dump-services.json") - self._resource_to_file(resource="/subsystem/modcluster", + self.resource_to_file(resource="/subsystem/modcluster", operation="read-proxies-configuration", outfile="cluster-proxies-configuration.json") - self._resource_to_file(resource="/core-service/platform-mbean/type/threading", + self.resource_to_file(resource="/core-service/platform-mbean/type/threading", operation="dump-all-threads", parameters={"locked-synchronizers": "true", "locked-monitors": "true"}, @@ -275,14 +139,19 @@ class AS7(Plugin, IndependentPlugin): ## First get everything in the conf dir confDir = os.path.join(path, "configuration") self.addForbiddenPath(os.path.join(confDir, 'mgmt-users.properties')) - - self.doCopyFileOrDir(confDir, sub=(self.__jbossHome, 'JBOSSHOME')) + self.addForbiddenPath(os.path.join(confDir, 'application-users.properties')) for logFile in find("*.log", path): self.addCopySpecLimit(logFile, self.getOption("logsize"), sub=(self.__jbossHome, 'JBOSSHOME')) + for xml in find("*.xml", path): + self.addCopySpec(xml, sub=(self.__jbossHome, 'JBOSSHOME')) + + for prop in find("*.properties", path): + self.addCopySpec(prop, sub=(self.__jbossHome, 'JBOSSHOME')) + deployment_info = self.__get_deployment_info(confDir) deployments = self.__get_deployments(path) for deployment in deployments: @@ -337,8 +206,6 @@ class AS7(Plugin, IndependentPlugin): def setup(self): - ## We need to know where JBoss is installed and if we can't find it we - ## must exit immediately. if not self.__getJbossHome(): self.exit_please() @@ -347,11 +214,9 @@ class AS7(Plugin, IndependentPlugin): except urllib2.URLError: pass - ## Generate hashes of the stock Jar files for the report. if self.getOption("stdjar"): self.__getStdJarInfo() - ## Generate a Tree for JBOSS_HOME tree = DirTree(self.__jbossHome).as_string() self.addStringAsFile(tree, "jboss_home_tree.txt") @@ -377,7 +242,7 @@ class AS7(Plugin, IndependentPlugin): r"=(.*)", r'=********') -# Remove PW from -ds.xml files +# Remove PW from -ds.xml files tmp = os.path.join(path, "deployments") for dsFile in find("*-ds.xml", tmp): self.doRegexSub(dsFile, diff --git a/sos/__init__.py b/sos/plugins/dpkg.py index 8d45216a..47c2c9e4 100644 --- a/sos/__init__.py +++ b/sos/plugins/dpkg.py @@ -1,7 +1,4 @@ -## Copyright 2010 Red Hat, Inc. -## Author: Adam Stokes <astokes@fedoraproject.org> - -## This program is free software; you can redistribute it and/or modify +### 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. @@ -15,24 +12,11 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -__version__ = "@SOSVERSION@" - -try: - from java.util import ResourceBundle - - rb = ResourceBundle.getBundle("sos.po.sos") - - def _sos(msg): - try: - return rb.getString(msg).encode('utf-8') - except: - return msg -except: - import gettext - gettext_dir = "/usr/share/locale" - gettext_app = "sos" - - gettext.bindtextdomain(gettext_app, gettext_dir) +from sos.plugins import Plugin, DebianPlugin, UbuntuPlugin - def _sos(msg): - return gettext.dgettext(gettext_app, msg) +class dpkg(Plugin, DebianPlugin, UbuntuPlugin): + """dpkg information + """ + def setup(self): + self.addCopySpec("/var/log/dpkg.log") + self.collectExtOutput("/usr/bin/dpkg-query -W -f='${Package}-${Version}-${Architecture}\n' \*", root_symlink = "installed-debs") diff --git a/sos/policies/Makefile b/sos/policies/Makefile new file mode 100644 index 00000000..8fd6a1bf --- /dev/null +++ b/sos/policies/Makefile @@ -0,0 +1,20 @@ +PYTHON=python +PACKAGE = $(shell basename `pwd`) +PYFILES = $(wildcard *.py) +PYVER := $(shell $(PYTHON) -c 'import sys; print "%.3s" %(sys.version)') +PYSYSDIR := $(shell $(PYTHON) -c 'import sys; print sys.prefix') +PYLIBDIR = $(PYSYSDIR)/lib/python$(PYVER) +PKGDIR = $(PYLIBDIR)/site-packages/sos/$(PACKAGE) + +all: + echo "nada" + +clean: + rm -f *.pyc *.pyo *~ + +install: + mkdir -p $(DESTDIR)/$(PKGDIR) + for p in $(PYFILES) ; do \ + install -m 755 $$p $(DESTDIR)/$(PKGDIR)/$$p; \ + done + $(PYTHON) -c "import compileall; compileall.compile_dir('$(DESTDIR)/$(PKGDIR)', 1, '$(PYDIR)', 1)" diff --git a/sos/policies/__init__.py b/sos/policies/__init__.py index a2234016..ad6539c0 100644 --- a/sos/policies/__init__.py +++ b/sos/policies/__init__.py @@ -24,8 +24,11 @@ def load(cache={}): for policy in import_policy(module): if policy.check(): cache['policy'] = policy() - return policy() - return GenericPolicy() + + if 'policy' not in cache: + cache['policy'] = GenericPolicy() + + return cache['policy'] class PackageManager(object): diff --git a/sos/policies/debian.py b/sos/policies/debian.py new file mode 100644 index 00000000..6d11482d --- /dev/null +++ b/sos/policies/debian.py @@ -0,0 +1,156 @@ +from __future__ import with_statement + +from sos import _sos as _ +from sos.plugins import DebianPlugin, IndependentPlugin +from sos.policies import PackageManager, Policy +from sos.utilities import shell_out + +import os +import sys +import re + +class DebianPackageManager(PackageManager): + + def _get_deb_list(self): + pkg_list = shell_out(["dpkg-query", + "-W", + "-f=", + "'${Package}|${Version}\\n' \*"]).splitlines() + self._debs = {} + for pkg in pkg_list: + name, version = pkg.split("|") + self._debs[name] = { + 'name': name, + 'version': version + } + + def allPkgsByName(self, name): + return fnmatch.filter(self.allPkgs().keys(), name) + + def allPkgsByNameRegex(self, regex_name, flags=None): + reg = re.compile(regex_name, flags) + return [pkg for pkg in self.allPkgs().keys() if reg.match(pkg)] + + def pkgByName(self, name): + try: + self.AllPkgsByName(name)[-1] + except Exception: + return None + + def allPkgs(self): + if not self._debs: + self._debs = self._get_deb_list() + return self._debs + + def pkgNVRA(self, pkg): + fields = pkg.split("-") + version, release, arch = fields[-3:] + name = "-".join(fields[:-3]) + return (name, version, release, arch) + +class DebianPolicy(Policy): + def __init__(self): + super(DebianPolicy, self).__init__() + self.reportName = "" + self.ticketNumber = "" + self.package_manager = DebianPackageManager() + + def validatePlugin(self, plugin_class): + "Checks that the plugin will execute given the environment" + return issubclass(plugin_class, DebianPlugin) or issubclass(plugin_class, IndependentPlugin) + + @classmethod + def check(self): + """This method checks to see if we are running on Debian. + It returns True or False.""" + if os.path.isfile('/etc/debian_version'): + return True + return False + + def preferedArchive(self): + from sos.utilities import TarFileArchive + return TarFileArchive + + def getPreferredHashAlgorithm(self): + checksum = "md5" + try: + fp = open("/proc/sys/crypto/fips_enabled", "r") + except: + return checksum + + fips_enabled = fp.read() + if fips_enabled.find("1") >= 0: + checksum = "sha256" + fp.close() + return checksum + + def pkgByName(self, name): + return self.package_manager.pkgByName(name) + + def runlevelDefault(self): + try: + with open("/etc/inittab") as fp: + pattern = r"id:(\d{1}):initdefault:" + text = fp.read() + return int(re.findall(pattern, text)[0]) + except: + return 3 + + def kernelVersion(self): + return self.release + + def hostName(self): + return self.hostname + + def debianVersion(self): + try: + fp = open("/etc/debian_version").read() + if "wheezy/sid" in fp: + return 6 + fp.close() + except: + pass + return False + + def isKernelSMP(self): + return self.smp + + def getArch(self): + return self.machine + + def preWork(self): + # this method will be called before the gathering begins + + localname = self.hostName() + + if not self.commons['cmdlineopts'].batch and not self.commons['cmdlineopts'].silent: + try: + self.reportName = raw_input(_("Please enter your first initial and last name [%s]: ") % localname) + self.reportName = re.sub(r"[^a-zA-Z.0-9]", "", self.reportName) + + self.ticketNumber = raw_input(_("Please enter the case number that you are generating this report for: ")) + self.ticketNumber = re.sub(r"[^0-9]", "", self.ticketNumber) + self._print() + except: + self._print() + sys.exit(0) + + if len(self.reportName) == 0: + self.reportName = localname + + if self.commons['cmdlineopts'].customerName: + self.reportName = self.commons['cmdlineopts'].customerName + self.reportName = re.sub(r"[^a-zA-Z.0-9]", "", self.reportName) + + if self.commons['cmdlineopts'].ticketNumber: + self.ticketNumber = self.commons['cmdlineopts'].ticketNumber + self.ticketNumber = re.sub(r"[^0-9]", "", self.ticketNumber) + + return + + def packageResults(self, archive_filename): + self._print(_("Creating compressed archive...")) + + def get_msg(self): + msg_dict = {"distro": "Debian"} + return self.msg % msg_dict diff --git a/sos/policies/ubuntu.py b/sos/policies/ubuntu.py new file mode 100644 index 00000000..7d61d054 --- /dev/null +++ b/sos/policies/ubuntu.py @@ -0,0 +1,31 @@ +from __future__ import with_statement +from sos import _sos as _ +from sos.plugins import UbuntuPlugin, IndependentPlugin +from sos.policies.debian import DebianPolicy, DebianPackageManager +from sos.utilities import shell_out + +import os + +class UbuntuPolicy(DebianPolicy): + def __init__(self): + super(UbuntuPolicy, self).__init__() + + def validatePlugin(self, plugin_class): + "Checks that the plugin will execute given the environment" + return issubclass(plugin_class, UbuntuPlugin) or issubclass(plugin_class, IndependentPlugin) + + @classmethod + def check(self): + """This method checks to see if we are running on Ubuntu. + It returns True or False.""" + if os.path.isfile('/etc/lsb-release'): + try: + with open('/etc/lsb-release', 'r') as fp: + return "Ubuntu" in fp.read() + except: + return False + return False + + def get_msg(self): + msg_dict = {"distro": "Ubuntu"} + return self.msg % msg_dict diff --git a/sos/policies/windows.py b/sos/policies/windows.py index 64e780bf..abb46494 100644 --- a/sos/policies/windows.py +++ b/sos/policies/windows.py @@ -13,6 +13,7 @@ ## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. import os import time +import platform from sos.policies import PackageManager, Policy from sos.utilities import shell_out @@ -23,10 +24,13 @@ class WindowsPolicy(Policy): @classmethod def check(class_): + is_windows = False try: - return "Windows" in shell_out("ver") - except Exception, e: - return False + from java.lang import System + is_windows = "win" in System.getProperty('os.name').lower() + except: + is_windows = "win" in platform.system().lower() + return is_windows def is_root(self): if "S-1-16-12288" in shell_out("whoami /groups"): diff --git a/sos/sosreport.py b/sos/sosreport.py index 08f11fd6..fb2622ab 100644 --- a/sos/sosreport.py +++ b/sos/sosreport.py @@ -429,7 +429,8 @@ class SoSReport(object): for plugin_class in plugin_classes: if not self.policy.validatePlugin(plugin_class): self.soslog.debug(_("plugin %s does not validate, skipping") % plug) - self._skip(plugin_class, _("does not validate")) + if self.opts.verbosity > 0: + self._skip(plugin_class, _("does not validate")) continue if plugin_class.requires_root and not self._is_root: diff --git a/sos/utilities.py b/sos/utilities.py index e91586de..55fab228 100644 --- a/sos/utilities.py +++ b/sos/utilities.py @@ -217,6 +217,8 @@ class ImporterHelper(object): try: path_to_zip, tail = self._get_path_to_zip(path) zf = zipfile.ZipFile(path_to_zip) + # the path will have os separators, but the zipfile will always have '/' + tail = tail.replace(os.path.sep, "/") root_names = [name for name in zf.namelist() if tail in name] candidates = self._get_plugins_from_list(root_names) zf.close() @@ -296,6 +298,9 @@ class Archive(object): name = os.path.split(self._name)[-1] return os.path.join(name, src.lstrip(os.sep)) + def add_link(self, dest, link_name): + pass + class TarFileArchive(Archive): @@ -329,6 +334,13 @@ class TarFileArchive(Archive): tar_info.mtime = time.time() self.tarfile.addfile(tar_info, StringIO(content)) + def add_link(self, dest, link_name): + tar_info = tarfile.TarInfo(name=self.prepend(link_name)) + tar_info.type = tarfile.SYMTYPE + tar_info.linkname = dest + tar_info.mtime = time.time() + self.tarfile.addfile(tar_info, None) + def open_file(self, name): try: self.tarfile.close() diff --git a/tests/option_tests.py b/tests/option_tests.py new file mode 100644 index 00000000..03804dec --- /dev/null +++ b/tests/option_tests.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python + +import unittest + +from sos.plugins import Plugin + +class GlobalOptionTest(unittest.TestCase): + + def setUp(self): + self.commons = { + 'global_plugin_options': { + 'test_option': 'foobar', + 'baz': None, + 'empty_global': True, + }, + } + self.plugin = Plugin(self.commons) + self.plugin.optNames = ['baz', 'empty'] + self.plugin.optParms = [{'enabled': False}, {'enabled': None}] + + def test_simple_lookup(self): + self.assertEquals(self.plugin.getOption('test_option'), 'foobar') + + def test_multi_lookup(self): + self.assertEquals(self.plugin.getOption(('not_there', 'test_option')), 'foobar') + + def test_cascade(self): + self.assertEquals(self.plugin.getOption(('baz')), False) + + def test_none_should_cascade(self): + self.assertEquals(self.plugin.getOption(('empty', 'empty_global')), True) + +if __name__ == "__main__": + unittest.main() |